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

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