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

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