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

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