source: ProjectBuilder/devel/rpmbootstrap/bin/rpmbootstrap@ 1315

Last change on this file since 1315 was 1315, checked in by Bruno Cornec, 13 years ago
  • Improved documentation and web pages (Eric Anderson eric.anderson4_at_hp.com)
File size: 16.8 KB
Line 
1#!/usr/bin/perl -w
2#
3# rpmbootstrap application, a debootstrap like for RPM distros
4#
5# $Id$
6#
7# Copyright B. Cornec 2010
8# Provided under the GPL v2
9
10# Syntax: see at end
11
12use strict 'vars';
13use Getopt::Long qw(:config auto_abbrev no_ignore_case);
14use Data::Dumper;
15use English;
16use LWP::UserAgent;
17use File::Basename;
18use File::Copy;
19use File::Find;
20use ProjectBuilder::Version;
21use ProjectBuilder::Base;
22use ProjectBuilder::Env;
23use ProjectBuilder::Conf;
24use ProjectBuilder::Distribution;
25
26# Global variables
27my %opts; # CLI Options
28
29=pod
30
31=head1 NAME
32
33rpmbootstrap - creates a chrooted RPM based distribution a la debootstrap, aka Virtual Environment (VE)
34
35=head1 DESCRIPTION
36
37rpmbootstrap creates a chroot environment (Virtual Environment or VE)
38with a minimal distribution in it, suited for building packages for example.
39It's very much like debootstrap but for RPM based distribution.
40It aims at supporting all distributions supported by project-builder.org
41(RHEL, RH, Fedora, OpeSUSE, SLES, Mandriva, ...)
42
43It is inspired by work done by Steve Kemp for rinse (http://www.steve.org.uk/),
44and similar to mock, but fully integrated with project-builder.org
45(which also supports rinse and mock).
46
47rpmbootstrap works in 2 phases. The first one is used to download all
48the required packages to have a working package management system in the
49chroot working. This list of packages is stored in /etc/pb/pb.conf under
50the rbsmindep parameter (aka rpmbootstrap minimal dependencies). Once the
51packages have been downloaded from the mirror, they are extracted with
52rpm2cpio. At that point you should be able to use yum on Fedora, urpmi
53on Mandriva/Mageia and zypper on OpenSuSE.
54The second phase uses exactly the previous mentioned tools to install
55exactly the same package list to have a coherent RPM db at the end.
56
57rpmbootstrap has additional options to execute a post-install script
58(-s) or to add packages (-a). Then pb can use the chroot to perform even
59more actions in it.
60
61=head1 SYNOPSIS
62
63rpmbootstrap [-vhmqpdk][-s script][-i iso][-a pkg1[,pkg2,...]] distribution-version-arch [target-dir] [mirror [script]]
64
65rpmbootstrap [--verbose][--help][--man][--quiet][--print-rpms][--download-only]
66[--keep][--script script][--iso iso][--add pkg1,[pkg2,...]] distribution-version-arch [target-dir] [mirror [script]]
67
68=head1 OPTIONS
69
70=over 4
71
72=item B<-v|--verbose>
73
74Print a brief help message and exits.
75
76=item B<-h|--help>
77
78Print a brief help message and exits.
79
80=item B<--man>
81
82Prints the manual page and exits.
83
84=item B<-q|--quiet>
85
86Do not print any output.
87
88=item B<-p|--print-rpms>
89
90Print the packages to be installed, and exit.
91Note that a target directory must be specified so rpmbootstrap can determine
92which packages should be installed, and to resolve dependencies.
93The target directory will be deleted.
94
95=item B<-d|--download-only>
96
97Download packages, but don't perform installation.
98
99=item B<-k|--keep>
100
101Keep packages in the cache dir for later reuse. By default remove them.
102
103=item B<-s|--script script>
104
105Name of the script you want to execute on the related VEs after the installation.
106It is executed in host environment.
107You can use the chroot command to execute actions in the VE.
108
109=item B<-i|--iso iso_image>
110
111Name of the ISO image of the distribution you want to install on the related VE.
112
113=item B<-a|--add pkg1[,pkg2,...]>
114
115Additional packages to add from the distribution you want to install on the related VE
116at the end of the chroot build.
117
118=back
119
120=head1 ARGUMENTS
121
122=over 4
123
124=item B<distribution-version-arch>
125
126Full name of the distribution that needs to be installed in the VE. E.g. fedora-11-x86_64.
127
128=item B<target-dir>
129
130This is the target directory under which the VE will be created.
131Created on the fly if needed.
132If none is given use the default directory hosting VE for project-builder.org
133(Cf: vepath parameter in $HOME/.pbrc)
134
135=back
136
137=head1 EXAMPLE
138
139To setup a Fedora 12 distribution with an i386 architecture issue:
140
141rpmbootstrap fedora-12-i386 /tmp/fedora/12/i386
142
143=head1 WEB SITES
144
145The main Web site of the project is available at L<http://www.project-builder.org/>.
146Bug reports should be filled using the trac instance of the project at L<http://trac.project-builder.org/>.
147
148=head1 USER MAILING LIST
149
150Cf: L<http://www.mondorescue.org/sympa/info/pb-announce> for announces and
151L<http://www.mondorescue.org/sympa/info/pb-devel> for the development of the pb project.
152
153=head1 CONFIGURATION FILE
154
155Uses Project-Builder.org configuration file (/etc/pb/pb.conf or /usr/local/etc/pb/pb.conf)
156
157=head1 AUTHORS
158
159The Project-Builder.org team L<http://trac.project-builder.org/> lead by Bruno Cornec L<mailto:bruno@project-builder.org>.
160
161=head1 COPYRIGHT
162
163Project-Builder.org is distributed under the GPL v2.0 license
164described in the file C<COPYING> included with the distribution.
165
166=cut
167
168# ---------------------------------------------------------------------------
169
170my ($projectbuilderver,$projectbuilderrev) = pb_version_init();
171my $appname = "rpmbootstrap";
172$ENV{'PBPROJ'} = $appname;
173
174# Initialize the syntax string
175
176pb_syntax_init("$appname Version $projectbuilderver-$projectbuilderrev\n");
177pb_temp_init();
178
179GetOptions("help|?|h" => \$opts{'h'},
180 "man|m" => \$opts{'man'},
181 "verbose|v+" => \$opts{'v'},
182 "quiet|q" => \$opts{'q'},
183 "log-files|l=s" => \$opts{'l'},
184 "script|s=s" => \$opts{'s'},
185 "print-rpms|p" => \$opts{'p'},
186 "download-only|d" => \$opts{'d'},
187 "keep|k" => \$opts{'k'},
188 "iso|i=s" => \$opts{'i'},
189 "add|a=s" => \$opts{'a'},
190 "version|V=s" => \$opts{'V'},
191) || pb_syntax(-1,0);
192
193if (defined $opts{'h'}) {
194 pb_syntax(0,1);
195}
196if (defined $opts{'man'}) {
197 pb_syntax(0,2);
198}
199if (defined $opts{'v'}) {
200 $pbdebug = $opts{'v'};
201}
202if (defined $opts{'q'}) {
203 $pbdebug=-1;
204}
205if (defined $opts{'l'}) {
206 open(pbLOG,"> $opts{'l'}") || die "Unable to log to $opts{'l'}: $!";
207 $pbLOG = \*pbLOG;
208 $pbdebug = 0 if ($pbdebug == -1);
209}
210pb_log_init($pbdebug, $pbLOG);
211#pb_display_init("text","");
212
213#if (defined $opts{'s'}) {
214#$pbscript = $opts{'s'};
215#}
216#if (defined $opts{'i'}) {
217#$iso = $opts{'i'};
218#}
219
220# Get VE name
221$ENV{'PBV'} = shift @ARGV;
222die pb_syntax(-1,1) if (not defined $ENV{'PBV'});
223
224die "Needs to be run as root" if ($EFFECTIVE_USER_ID != 0);
225
226#
227# Initialize distribution info from pb conf file
228#
229pb_log(0,"Starting VE build for $ENV{'PBV'}\n");
230my $pbos = pb_distro_get_context($ENV{'PBV'});
231
232#
233# Check target dir
234# Create if not existent and use default if none given
235#
236pb_env_init_pbrc(); # to get content of HOME/.pbrc
237my $vepath = shift @ARGV;
238
239#
240# Check for command requirements
241#
242my ($req,$opt) = pb_conf_get_if("oscmd","oscmdopt");
243pb_check_requirements($req,$opt,$appname);
244
245if (not defined $vepath) {
246 my ($vestdpath) = pb_conf_get("vepath");
247 $vepath = "$vestdpath->{'default'}/$pbos->{'name'}/$pbos->{'version'}/$pbos->{'arch'}" if (defined $vestdpath->{'default'});
248}
249
250die pb_log(0,"No target-dir specified and no default vepath found in $ENV{'PBETC'}\n") if (not defined $vepath);
251
252pb_mkdir_p($vepath) if (! -d $vepath);
253
254#
255# Get the package list to download, store them in a cache directory
256#
257my ($rbscachedir) = pb_conf_get_if("rbscachedir");
258my ($pkgs,$mirror) = pb_distro_get_param($pbos,pb_conf_get("rbsmindep","rbsmirrorsrv"));
259
260my $cachedir = "/var/cache/rpmbootstrap";
261$cachedir = $rbscachedir->{'default'} if (defined $rbscachedir->{'default'});
262
263# Point to the right subdir and create it if needed
264$cachedir .= "/$pbos->{'name'}-$pbos->{'version'}-$pbos->{'arch'}";
265pb_mkdir_p($cachedir) if (! -d $cachedir);
266
267# Get the complete package name from the mirror
268#
269my $ua = LWP::UserAgent->new;
270$ua->timeout(10);
271$ua->env_proxy;
272
273die "No mirror defined for $pbos->{'name'}-$pbos->{'version'}-$pbos->{'arch'}" if ((not defined $mirror) || ($mirror =~ /^\t*$/));
274pb_log(0,"Downloading package list from $mirror ...\n");
275my $response = $ua->get($mirror);
276if (! $response->is_success) {
277 if ($mirror =~ /i386/) {
278 # Some distro have an i586 or i686 mirror dir instead for i386
279 warn "Unable to download packages from $mirror for $pbos->{'name'}-$pbos->{'version'}-$pbos->{'arch'}.";
280 $mirror =~ s|/i386/|/i586/|;
281 $response = $ua->get($mirror);
282 if (! $response->is_success) {
283 die "Unable to download packages from $mirror for $pbos->{'name'}-$pbos->{'version'}-$pbos->{'arch'}";
284 }
285 }
286}
287pb_log(3,"Mirror $mirror gave answer: ".Dumper($response->dump(maxlength => 0))."\n");
288
289# Try to find where the repodata structure is for later usage
290my $repo = $mirror;
291my $found = 0;
292if ($pbos->{'install'} =~ /yum/) {
293 my $response1;
294 while ($found == 0) {
295 $response1 = $ua->get("$repo/repodata");
296 pb_log(2,"REPO analyzed: $repo\n");
297 if (! $response1->is_success) {
298 $repo = dirname($repo);
299
300 # There is a limit to the loop, when / is reached and nothing found
301 my ($scheme, $account, $host, $port, $path) = pb_get_uri($repo);
302 die "Unable to find the repodata structure of the mirror $mirror\nPlease check the URL or warn the dev team.\n" if (($path =~ /^[\/]+$/) || ($path =~ /^$/));
303
304 # / not reached, so looping
305 next;
306 } else {
307 # repodata found $repo is correct
308 $found = 1;
309 pb_log(2,"REPO found: $repo\n");
310 last;
311 }
312 }
313}
314
315# Manages architectures specificities
316my $parch = $pbos->{'arch'};
317$parch = "i[3456]86" if ($pbos->{'arch'} eq "i386");
318
319# Get the list of packages and their URL in this hash
320my %url;
321foreach my $l (split(/\n/,$response->as_string())) {
322 # Find a href ref
323 if ($l =~ /<a href="(.*)">(.*)<\/a>/i) {
324 my $url = $1;
325 my $pkg = $1;
326 my $desc = $2;
327 pb_log(3,"Found desc URL $desc: ");
328 # find an rpm package ref name-ver-tag.arch.rpm
329 if ($pkg =~ /(.+)-([^-]+)-([^-]+)\.(noarch|$parch)\.rpm$/) {
330 pb_log(3,"package ($1 + $2 + $3 + $4)\n");
331 $url{$1} = "$mirror/$url";
332 } else {
333 pb_log(3,"not a package\n");
334 }
335 }
336}
337
338#
339# Prepare early the yum cache env for the VE in order to copy in it packages on the fly
340#
341my $oscachedir = "/tmp";
342my $osupdcachedir;
343my $osupdname = "";
344
345if ($pbos->{'install'} =~ /yum/) {
346 $oscachedir = "$vepath/var/cache/yum/core/packages/";
347 $osupdcachedir = "$vepath/var/cache/yum/updates-released/packages/";
348 $osupdname = "YUM";
349 # Recent Fedora release use a new yum cache dir
350 if (($pbos->{'name'} eq "fedora") && ($pbos->{'version'} > 8)) {
351 $oscachedir = "$vepath/var/cache/yum/$pbos->{'arch'}/$pbos->{'version'}/fedora/packages";
352 $osupdcachedir = "$vepath/var/cache/yum/$pbos->{'arch'}/$pbos->{'version'}/updates/packages";
353 $osupdcachedir = "$vepath/var/cache/yum/updates-released/packages/";
354 }
355} elsif ($pbos->{'install'} =~ /zypper/) {
356 $oscachedir = "$vepath/var/cache/zypp/packages/opensuse/suse/$pbos->{'arch'}";
357 $osupdname = "Zypper";
358} elsif ($pbos->{'install'} =~ /urpmi/) {
359 $oscachedir = "$vepath/var/cache/urpmi/rpms";
360 $osupdname = "URPMI";
361}
362pb_log(1,"Setting up $osupdname cache in VE\n");
363pb_mkdir_p($oscachedir);
364pb_mkdir_p($osupdcachedir) if (defined $osupdcachedir);
365
366# For each package to process, get it, put it in the cache dir
367# and extract it in the target dir. If not asked to keep, remove it
368# Just download if asked so.
369
370my $warning = 0;
371my $lwpkg ="";
372foreach my $p (split(/,/,$pkgs)) {
373 pb_log(1,"Processing package $p ...\n");
374 # Just print packages names if asked so.
375 if (defined $url{$p}) {
376 if ($opts{'p'}) {
377 pb_log(0,"$url{$p}\n");
378 next;
379 } else {
380 # Now download if not already in cache
381 my $p1 = basename($url{$p});
382 if (! -f "$cachedir/$p1") {
383 pb_system("wget --quiet -O $cachedir/$p1 $url{$p}","Downloading package $p1 ...");
384 } else {
385 pb_log(1,"Package $p1 already in cache\n");
386 }
387
388 # End if download only
389 if ($opts{'d'}) {
390 next;
391 }
392
393 #
394 # Copy the cached .RPM files into the oscachedir directory, so that os doesn't need to download them again.
395 #
396 pb_log(1,"Link package into $oscachedir\n");
397 copy("$cachedir/$p1",$oscachedir) if (defined $oscachedir);
398 symlink("$oscachedir/$p1","$osupdcachedir/p1") if (defined $osupdcachedir);
399
400 # And extract it to the finale dir
401 pb_system("cd $vepath ; rpm2cpio $cachedir/$p1 | cpio -ivdum","Extracting package $p1 into $vepath");
402
403 # Remove cached package if not asked to keep
404 if (! $opts{'k'}) {
405 unlink("$cachedir/$p1");
406 }
407
408 }
409 } else {
410 pb_log(0,"WARNING: unable to find URL for $p\n");
411 $warning++;
412 $lwpkg .= " $p";
413 }
414}
415
416if ($warning ge 1) {
417pb_log(0,"$warning WARNINGS found.\nMaybe you should review your package list for $pbos->{'name'}-$pbos->{'version'}-$pbos->{'arch'}\nand remove$lwpkg\n");
418}
419
420# Stop here if we just print
421if ($opts{'p'}) {
422 exit(0);
423}
424
425# Now executes the VE finalization steps required for it to work correctly
426pb_log(0,"VE post configuration\n");
427
428# yum needs that distro-release package be installed, so force it
429if ($pbos->{'install'} =~ /yum/) {
430 my $ddir = $pbos->{'name'};
431 foreach my $p1 (<$cachedir/($ddir|redhat)-release-*.rpm>) {
432 copy("$cachedir/$p1","$vepath/tmp");
433 pb_system("chroot $vepath rpm -ivh --force --nodeps /tmp/$p1","Forcing RPM installation of $p1");
434 unlink("$vepath/tmp/$p1");
435 }
436}
437#
438# Make sure there is a resolv.conf file present, such that DNS lookups succeed.
439#
440pb_log(1,"Creating resolv.conf\n");
441pb_mkdir_p("$vepath/etc");
442copy("/etc/resolv.conf","$vepath/etc/");
443
444#
445# BUGFIX:
446#
447if ((($pbos->{'name'} eq "centos") || ($pbos->{'name'} eq "rhel")) && ($pbos->{'version'} eq "5")) {
448 pb_log(1,"BUGFIX for centos-5\n");
449 pb_mkdir_p("$vepath/usr/lib/python2.4/site-packages/urlgrabber.skx");
450 foreach my $i (<$vepath/usr/lib/python2.4/site-packages/urlgrabber/keepalive.*>) {
451 move($i,"$vepath/usr/lib/python2.4/site-packages/urlgrabber.skx/");
452 }
453}
454
455#
456# /proc needed
457#
458pb_mkdir_p("$vepath/proc");
459pb_system("mount -o bind /proc $vepath/proc","Mounting /proc");
460
461#
462# Some devices may be needed
463#
464pb_mkdir_p("$vepath/dev");
465chmod 0755,"$vepath/dev";
466pb_system("mknod -m 644 $vepath/dev/random c 1 8","Creating $vepath/dev/random") if (! -c "$vepath/dev/random");
467pb_system("mknod -m 644 $vepath/dev/urandom c 1 9","Creating $vepath/dev/urandom") if (! -c "$vepath/dev/urandom");
468pb_system("mknod -m 666 $vepath/dev/zero c 1 5","Creating $vepath/dev/zero") if (! -c "$vepath/dev/zero");
469pb_system("mknod -m 666 $vepath/dev/null c 1 3","Creating $vepath/dev/null") if (! -c "$vepath/dev/null");
470
471my $minipkglist;
472
473pb_log(1,"Adapting $osupdname repository entries\n");
474if ($pbos->{'install'} =~ /yum/) {
475 #
476 # Force the architecture for yum
477 # The goal is to allow i386 chroot on x86_64
478 #
479 # FIX: Not sufficient to have yum working
480 # mirrorlist is not usable
481 # $releasever also needs to be filtered
482 # yum.conf as well
483 foreach my $i (<$vepath/etc/yum.repos.d/*>,"$vepath/etc/yum.conf") {
484 pb_system("sed -i -e 's/\$basearch/$pbos->{'arch'}/g' $i","","quiet");
485 pb_system("sed -i -e 's/\$releasever/$pbos->{'version'}/g' $i","","quiet");
486 pb_system("sed -i -e 's/^mirrorlist/#mirrorlist/' $i","","quiet");
487 # rather use neutral separators here
488 pb_system("sed -i -e 's|^#baseurl.*\$|baseurl=$repo|' $i","","quiet");
489 }
490 $minipkglist = "ldconfig yum passwd vim-minimal dhclient authconfig";
491} elsif ($pbos->{'install'} =~ /zypper/) {
492 pb_mkdir_p("$vepath/etc/zypp/repos.d");
493 open(REPO,"> $vepath/etc/zypp/repos.d/$pbos->{'name'}-$pbos->{'version'}") || die "Unable to create repo file";
494 my $baseurl = dirname(dirname($mirror));
495 # Setup the repo
496 if ($pbos->{'version'} eq "10.2") {
497 pb_system("chroot $vepath /bin/bash -c \"yes | /usr/bin/zypper sa $baseurl $pbos->{'name'}-$pbos->{'version'}\"","Bootstrapping Zypper");
498 } else {
499 pb_system("chroot $vepath /bin/bash -c \"/usr/bin/zypper ar $baseurl $pbos->{'name'}-$pbos->{'version'}\"","Bootstrapping Zypper");
500 }
501 #print REPO << "EOF";
502#[opensuse]
503#name=$pbos->{'name'}-$pbos->{'version'}
504#baseurl=$baseurl
505#enabled=1
506#gpgcheck=1
507#
508#EOF
509 close(REPO);
510 $minipkglist = "zypper";
511} elsif ($pbos->{'install'} =~ /urpmi/) {
512 # Setup the repo
513 my $baseurl = dirname(dirname(dirname($mirror)));
514 pb_system("chroot $vepath /bin/bash -c \"urpmi.addmedia --distrib $baseurl\"","Bootstrapping URPMI");
515 $minipkglist = "ldconfig urpmi passwd vim-minimal dhcp-client";
516}
517
518#
519# Run "install the necessary modules".
520# No need for sudo here
521#
522$pbos->{'install'} =~ s/sudo//g;
523pb_system("chroot $vepath /bin/bash -c \"$pbos->{'install'} $minipkglist \"","Bootstrapping OS by running $pbos->{'install'} $minipkglist");
524
525#
526# make 'passwd' work.
527#
528pb_log(1,"Authfix\n");
529pb_system("chroot $vepath /bin/bash -c \"if [ -x /usr/bin/authconfig ]; then /usr/bin/authconfig --enableshadow --update; fi\"","Calling authconfig");
530
531# Installed additional packages we were asked to
532if (defined $opts{'a'}) {
533 $opts{'a'} =~ s/,/ /g;
534 pb_system("chroot $vepath /bin/bash -c \"$pbos->{'install'} $opts{'a'} \"","Adding packages to OS by running $pbos->{'install'} $opts{'a'}");
535}
536
537#
538# Clean up
539#
540pb_log(1,"Cleaning up\n");
541if ($pbos->{'install'} =~ /yum/) {
542 pb_system("chroot $vepath /usr/bin/yum clean all","Cleaning yum");
543}
544pb_system("umount $vepath/proc","Unmounting /proc");
545find(\&unlink_old_conf, $vepath);
546
547# Executes post-install step if asked for
548if ($opts{'s'}) {
549 pb_system("$opts{'s'} $vepath","Executing the post-install script: $opts{'s'} $vepath");
550}
551
552# Function for File::Find
553sub unlink_old_conf {
554
555 unlink($_) if ($_ =~ /\.rpmorig$/);
556 unlink($_) if ($_ =~ /\.rpmnew$/);
557}
Note: See TracBrowser for help on using the repository browser.