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

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