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

Last change on this file since 1402 was 1402, checked in by Bruno Cornec, 12 years ago

r4487@cabanilles: bruno | 2012-02-01 16:21:48 +0100

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