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

Last change on this file since 1326 was 1326, checked in by Bruno Cornec, 13 years ago
  • Improve rpmbootstrap description thanks to contributors feedback
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("rbscachedir");
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
265# Point to the right subdir and create it if needed
266$cachedir .= "/$pbos->{'name'}-$pbos->{'version'}-$pbos->{'arch'}";
267pb_mkdir_p($cachedir) if (! -d $cachedir);
268
269# Get the complete package name from the mirror
270#
271my $ua = LWP::UserAgent->new;
272$ua->timeout(10);
273$ua->env_proxy;
274
275die "No mirror defined for $pbos->{'name'}-$pbos->{'version'}-$pbos->{'arch'}" if ((not defined $mirror) || ($mirror =~ /^\t*$/));
276pb_log(0,"Downloading package list from $mirror ...\n");
277my $response = $ua->get($mirror);
278if (! $response->is_success) {
279 if ($mirror =~ /i386/) {
280 # Some distro have an i586 or i686 mirror dir instead for i386
281 warn "Unable to download packages from $mirror for $pbos->{'name'}-$pbos->{'version'}-$pbos->{'arch'}.";
282 $mirror =~ s|/i386/|/i586/|;
283 $response = $ua->get($mirror);
284 if (! $response->is_success) {
285 die "Unable to download packages from $mirror for $pbos->{'name'}-$pbos->{'version'}-$pbos->{'arch'}";
286 }
287 }
288}
289pb_log(3,"Mirror $mirror gave answer: ".Dumper($response->dump(maxlength => 0))."\n");
290
291# Try to find where the repodata structure is for later usage
292my $repo = $mirror;
293my $found = 0;
294if ($pbos->{'install'} =~ /yum/) {
295 my $response1;
296 while ($found == 0) {
297 $response1 = $ua->get("$repo/repodata");
298 pb_log(2,"REPO analyzed: $repo\n");
299 if (! $response1->is_success) {
300 $repo = dirname($repo);
301
302 # There is a limit to the loop, when / is reached and nothing found
303 my ($scheme, $account, $host, $port, $path) = pb_get_uri($repo);
304 die "Unable to find the repodata structure of the mirror $mirror\nPlease check the URL or warn the dev team.\n" if (($path =~ /^[\/]+$/) || ($path =~ /^$/));
305
306 # / not reached, so looping
307 next;
308 } else {
309 # repodata found $repo is correct
310 $found = 1;
311 pb_log(2,"REPO found: $repo\n");
312 last;
313 }
314 }
315}
316
317# Manages architectures specificities
318my $parch = $pbos->{'arch'};
319$parch = "i[3456]86" if ($pbos->{'arch'} eq "i386");
320
321# Get the list of packages and their URL in this hash
322my %url;
323foreach my $l (split(/\n/,$response->as_string())) {
324 # Find a href ref
325 if ($l =~ /<a href="(.*)">(.*)<\/a>/i) {
326 my $url = $1;
327 my $pkg = $1;
328 my $desc = $2;
329 pb_log(3,"Found desc URL $desc: ");
330 # find an rpm package ref name-ver-tag.arch.rpm
331 if ($pkg =~ /(.+)-([^-]+)-([^-]+)\.(noarch|$parch)\.rpm$/) {
332 pb_log(3,"package ($1 + $2 + $3 + $4)\n");
333 $url{$1} = "$mirror/$url";
334 } else {
335 pb_log(3,"not a package\n");
336 }
337 }
338}
339
340#
341# Prepare early the yum cache env for the VE in order to copy in it packages on the fly
342#
343my $oscachedir = "/tmp";
344my $osupdcachedir;
345my $osupdname = "";
346
347if ($pbos->{'install'} =~ /yum/) {
348 $oscachedir = "$vepath/var/cache/yum/core/packages/";
349 $osupdcachedir = "$vepath/var/cache/yum/updates-released/packages/";
350 $osupdname = "YUM";
351 # Recent Fedora release use a new yum cache dir
352 if (($pbos->{'name'} eq "fedora") && ($pbos->{'version'} > 8)) {
353 $oscachedir = "$vepath/var/cache/yum/$pbos->{'arch'}/$pbos->{'version'}/fedora/packages";
354 $osupdcachedir = "$vepath/var/cache/yum/$pbos->{'arch'}/$pbos->{'version'}/updates/packages";
355 $osupdcachedir = "$vepath/var/cache/yum/updates-released/packages/";
356 }
357} elsif ($pbos->{'install'} =~ /zypper/) {
358 $oscachedir = "$vepath/var/cache/zypp/packages/opensuse/suse/$pbos->{'arch'}";
359 $osupdname = "Zypper";
360} elsif ($pbos->{'install'} =~ /urpmi/) {
361 $oscachedir = "$vepath/var/cache/urpmi/rpms";
362 $osupdname = "URPMI";
363}
364pb_log(1,"Setting up $osupdname cache in VE\n");
365pb_mkdir_p($oscachedir);
366pb_mkdir_p($osupdcachedir) if (defined $osupdcachedir);
367
368# For each package to process, get it, put it in the cache dir
369# and extract it in the target dir. If not asked to keep, remove it
370# Just download if asked so.
371
372my $warning = 0;
373my $lwpkg ="";
374foreach my $p (split(/,/,$pkgs)) {
375 pb_log(1,"Processing package $p ...\n");
376 # Just print packages names if asked so.
377 if (defined $url{$p}) {
378 if ($opts{'p'}) {
379 pb_log(0,"$url{$p}\n");
380 next;
381 } else {
382 # Now download if not already in cache
383 my $p1 = basename($url{$p});
384 if (! -f "$cachedir/$p1") {
385 pb_system("wget --quiet -O $cachedir/$p1 $url{$p}","Downloading package $p1 ...");
386 } else {
387 pb_log(1,"Package $p1 already in cache\n");
388 }
389
390 # End if download only
391 if ($opts{'d'}) {
392 next;
393 }
394
395 #
396 # Copy the cached .RPM files into the oscachedir directory, so that os doesn't need to download them again.
397 #
398 pb_log(1,"Link package into $oscachedir\n");
399 copy("$cachedir/$p1",$oscachedir) if (defined $oscachedir);
400 symlink("$oscachedir/$p1","$osupdcachedir/p1") if (defined $osupdcachedir);
401
402 # And extract it to the finale dir
403 pb_system("cd $vepath ; rpm2cpio $cachedir/$p1 | cpio -ivdum","Extracting package $p1 into $vepath");
404
405 # Remove cached package if not asked to keep
406 if (! $opts{'k'}) {
407 unlink("$cachedir/$p1");
408 }
409
410 }
411 } else {
412 pb_log(0,"WARNING: unable to find URL for $p\n");
413 $warning++;
414 $lwpkg .= " $p";
415 }
416}
417
418if ($warning ge 1) {
419pb_log(0,"$warning WARNINGS found.\nMaybe you should review your package list for $pbos->{'name'}-$pbos->{'version'}-$pbos->{'arch'}\nand remove$lwpkg\n");
420}
421
422# Stop here if we just print
423if ($opts{'p'}) {
424 exit(0);
425}
426
427# Now executes the VE finalization steps required for it to work correctly
428pb_log(0,"VE post configuration\n");
429
430# yum needs that distro-release package be installed, so force it
431if ($pbos->{'install'} =~ /yum/) {
432 my $ddir = $pbos->{'name'};
433 foreach my $p1 (<$cachedir/($ddir|redhat)-release-*.rpm>) {
434 copy("$cachedir/$p1","$vepath/tmp");
435 pb_system("chroot $vepath rpm -ivh --force --nodeps /tmp/$p1","Forcing RPM installation of $p1");
436 unlink("$vepath/tmp/$p1");
437 }
438}
439#
440# Make sure there is a resolv.conf file present, such that DNS lookups succeed.
441#
442pb_log(1,"Creating resolv.conf\n");
443pb_mkdir_p("$vepath/etc");
444copy("/etc/resolv.conf","$vepath/etc/");
445
446#
447# BUGFIX:
448#
449if ((($pbos->{'name'} eq "centos") || ($pbos->{'name'} eq "rhel")) && ($pbos->{'version'} eq "5")) {
450 pb_log(1,"BUGFIX for centos-5\n");
451 pb_mkdir_p("$vepath/usr/lib/python2.4/site-packages/urlgrabber.skx");
452 foreach my $i (<$vepath/usr/lib/python2.4/site-packages/urlgrabber/keepalive.*>) {
453 move($i,"$vepath/usr/lib/python2.4/site-packages/urlgrabber.skx/");
454 }
455}
456
457#
458# /proc needed
459#
460pb_mkdir_p("$vepath/proc");
461pb_system("mount -o bind /proc $vepath/proc","Mounting /proc");
462
463#
464# Some devices may be needed
465#
466pb_mkdir_p("$vepath/dev");
467chmod 0755,"$vepath/dev";
468pb_system("mknod -m 644 $vepath/dev/random c 1 8","Creating $vepath/dev/random") if (! -c "$vepath/dev/random");
469pb_system("mknod -m 644 $vepath/dev/urandom c 1 9","Creating $vepath/dev/urandom") if (! -c "$vepath/dev/urandom");
470pb_system("mknod -m 666 $vepath/dev/zero c 1 5","Creating $vepath/dev/zero") if (! -c "$vepath/dev/zero");
471pb_system("mknod -m 666 $vepath/dev/null c 1 3","Creating $vepath/dev/null") if (! -c "$vepath/dev/null");
472
473my $minipkglist;
474
475pb_log(1,"Adapting $osupdname repository entries\n");
476if ($pbos->{'install'} =~ /yum/) {
477 #
478 # Force the architecture for yum
479 # The goal is to allow i386 chroot on x86_64
480 #
481 # FIX: Not sufficient to have yum working
482 # mirrorlist is not usable
483 # $releasever also needs to be filtered
484 # yum.conf as well
485 foreach my $i (<$vepath/etc/yum.repos.d/*>,"$vepath/etc/yum.conf") {
486 pb_system("sed -i -e 's/\$basearch/$pbos->{'arch'}/g' $i","","quiet");
487 pb_system("sed -i -e 's/\$releasever/$pbos->{'version'}/g' $i","","quiet");
488 pb_system("sed -i -e 's/^mirrorlist/#mirrorlist/' $i","","quiet");
489 # rather use neutral separators here
490 pb_system("sed -i -e 's|^#baseurl.*\$|baseurl=$repo|' $i","","quiet");
491 }
492 $minipkglist = "ldconfig yum passwd vim-minimal dhclient authconfig";
493} elsif ($pbos->{'install'} =~ /zypper/) {
494 pb_mkdir_p("$vepath/etc/zypp/repos.d");
495 open(REPO,"> $vepath/etc/zypp/repos.d/$pbos->{'name'}-$pbos->{'version'}") || die "Unable to create repo file";
496 my $baseurl = dirname(dirname($mirror));
497 # Setup the repo
498 if ($pbos->{'version'} eq "10.2") {
499 pb_system("chroot $vepath /bin/bash -c \"yes | /usr/bin/zypper sa $baseurl $pbos->{'name'}-$pbos->{'version'}\"","Bootstrapping Zypper");
500 } else {
501 pb_system("chroot $vepath /bin/bash -c \"/usr/bin/zypper ar $baseurl $pbos->{'name'}-$pbos->{'version'}\"","Bootstrapping Zypper");
502 }
503 #print REPO << "EOF";
504#[opensuse]
505#name=$pbos->{'name'}-$pbos->{'version'}
506#baseurl=$baseurl
507#enabled=1
508#gpgcheck=1
509#
510#EOF
511 close(REPO);
512 $minipkglist = "zypper";
513} elsif ($pbos->{'install'} =~ /urpmi/) {
514 # Setup the repo
515 my $baseurl = dirname(dirname(dirname($mirror)));
516 pb_system("chroot $vepath /bin/bash -c \"urpmi.addmedia --distrib $baseurl\"","Bootstrapping URPMI");
517 $minipkglist = "ldconfig urpmi passwd vim-minimal dhcp-client";
518}
519
520#
521# Run "install the necessary modules".
522# No need for sudo here
523#
524$pbos->{'install'} =~ s/sudo//g;
525pb_system("chroot $vepath /bin/bash -c \"$pbos->{'install'} $minipkglist \"","Bootstrapping OS by running $pbos->{'install'} $minipkglist");
526
527#
528# make 'passwd' work.
529#
530pb_log(1,"Authfix\n");
531pb_system("chroot $vepath /bin/bash -c \"if [ -x /usr/bin/authconfig ]; then /usr/bin/authconfig --enableshadow --update; fi\"","Calling authconfig");
532
533# Installed additional packages we were asked to
534if (defined $opts{'a'}) {
535 $opts{'a'} =~ s/,/ /g;
536 pb_system("chroot $vepath /bin/bash -c \"$pbos->{'install'} $opts{'a'} \"","Adding packages to OS by running $pbos->{'install'} $opts{'a'}");
537}
538
539#
540# Clean up
541#
542pb_log(1,"Cleaning up\n");
543if ($pbos->{'install'} =~ /yum/) {
544 pb_system("chroot $vepath /usr/bin/yum clean all","Cleaning yum");
545}
546pb_system("umount $vepath/proc","Unmounting /proc");
547find(\&unlink_old_conf, $vepath);
548
549# Executes post-install step if asked for
550if ($opts{'s'}) {
551 pb_system("$opts{'s'} $vepath","Executing the post-install script: $opts{'s'} $vepath");
552}
553
554# Function for File::Find
555sub unlink_old_conf {
556
557 unlink($_) if ($_ =~ /\.rpmorig$/);
558 unlink($_) if ($_ =~ /\.rpmnew$/);
559}
Note: See TracBrowser for help on using the repository browser.