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
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, but fully integrated with project-builder.org
45(which also supports rinse and mock).
46
47=head1 SYNOPSIS
48
49rpmbootstrap [-vhmqpdk][-s script][-i iso][-a pkg1[,pkg2,...]] distribution-version-arch [target-dir] [mirror [script]]
50
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]]
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
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.
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.
92It is executed in host environment.
93You can use the chroot command to execute actions in the VE.
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
99=item B<-a|--add pkg1[,pkg2,...]>
100
101Additional packages to add from the distribution you want to install on the related VE
102at the end of the chroot build.
103
104=back
105
106=head1 ARGUMENTS
107
108=over 4
109
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
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)
120
121=back
122
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
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/>.
133
134=head1 USER MAILING LIST
135
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.
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";
158$ENV{'PBPROJ'} = $appname;
159
160# Initialize the syntax string
161
162pb_syntax_init("$appname Version $projectbuilderver-$projectbuilderrev\n");
163pb_temp_init();
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'},
175 "add|a=s" => \$opts{'a'},
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);
197#pb_display_init("text","");
198
199#if (defined $opts{'s'}) {
200#$pbscript = $opts{'s'};
201#}
202#if (defined $opts{'i'}) {
203#$iso = $opts{'i'};
204#}
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#
215pb_log(0,"Starting VE build for $ENV{'PBV'}\n");
216my $pbos = pb_distro_get_context($ENV{'PBV'});
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
225#
226# Check for command requirements
227#
228my ($req,$opt) = pb_conf_get_if("oscmd","oscmdopt");
229pb_check_requirements($req,$opt,$appname);
230
231if (not defined $vepath) {
232 my ($vestdpath) = pb_conf_get("vepath");
233 $vepath = "$vestdpath->{'default'}/$pbos->{'name'}/$pbos->{'version'}/$pbos->{'arch'}" if (defined $vestdpath->{'default'});
234}
235
236die pb_log(0,"No target-dir specified and no default vepath found in $ENV{'PBETC'}\n") if (not defined $vepath);
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");
244my ($pkgs,$mirror) = pb_distro_get_param($pbos,pb_conf_get("rbsmindep","rbsmirrorsrv"));
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
250$cachedir .= "/$pbos->{'name'}-$pbos->{'version'}-$pbos->{'arch'}";
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
259die "No mirror defined for $pbos->{'name'}-$pbos->{'version'}-$pbos->{'arch'}" if ((not defined $mirror) || ($mirror =~ /^\t*$/));
260pb_log(0,"Downloading package list from $mirror ...\n");
261my $response = $ua->get($mirror);
262if (! $response->is_success) {
263 if ($mirror =~ /i386/) {
264 # Some distro have an i586 or i686 mirror dir instead for i386
265 warn "Unable to download packages from $mirror for $pbos->{'name'}-$pbos->{'version'}-$pbos->{'arch'}.";
266 $mirror =~ s|/i386/|/i586/|;
267 $response = $ua->get($mirror);
268 if (! $response->is_success) {
269 die "Unable to download packages from $mirror for $pbos->{'name'}-$pbos->{'version'}-$pbos->{'arch'}";
270 }
271 }
272}
273pb_log(3,"Mirror $mirror gave answer: ".Dumper($response->dump(maxlength => 0))."\n");
274
275# Try to find where the repodata structure is for later usage
276my $repo = $mirror;
277my $found = 0;
278if ($pbos->{'install'} =~ /yum/) {
279 my $response1;
280 while ($found == 0) {
281 $response1 = $ua->get("$repo/repodata");
282 pb_log(2,"REPO analyzed: $repo\n");
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);
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 =~ /^$/));
289
290 # / not reached, so looping
291 next;
292 } else {
293 # repodata found $repo is correct
294 $found = 1;
295 pb_log(2,"REPO found: $repo\n");
296 last;
297 }
298 }
299}
300
301# Manages architectures specificities
302my $parch = $pbos->{'arch'};
303$parch = "i[3456]86" if ($pbos->{'arch'} eq "i386");
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 }
322}
323
324#
325# Prepare early the yum cache env for the VE in order to copy in it packages on the fly
326#
327my $oscachedir = "/tmp";
328my $osupdcachedir;
329my $osupdname = "";
330
331if ($pbos->{'install'} =~ /yum/) {
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
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";
339 $osupdcachedir = "$vepath/var/cache/yum/updates-released/packages/";
340 }
341} elsif ($pbos->{'install'} =~ /zypper/) {
342 $oscachedir = "$vepath/var/cache/zypp/packages/opensuse/suse/$pbos->{'arch'}";
343 $osupdname = "Zypper";
344} elsif ($pbos->{'install'} =~ /urpmi/) {
345 $oscachedir = "$vepath/var/cache/urpmi/rpms";
346 $osupdname = "URPMI";
347}
348pb_log(1,"Setting up $osupdname cache in VE\n");
349pb_mkdir_p($oscachedir);
350pb_mkdir_p($osupdcachedir) if (defined $osupdcachedir);
351
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
356my $warning = 0;
357my $lwpkg ="";
358foreach my $p (split(/,/,$pkgs)) {
359 pb_log(1,"Processing package $p ...\n");
360 # Just print packages names if asked so.
361 if (defined $url{$p}) {
362 if ($opts{'p'}) {
363 pb_log(0,"$url{$p}\n");
364 next;
365 } else {
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 }
373
374 # End if download only
375 if ($opts{'d'}) {
376 next;
377 }
378
379 #
380 # Copy the cached .RPM files into the oscachedir directory, so that os doesn't need to download them again.
381 #
382 pb_log(1,"Link package into $oscachedir\n");
383 copy("$cachedir/$p1",$oscachedir) if (defined $oscachedir);
384 symlink("$oscachedir/$p1","$osupdcachedir/p1") if (defined $osupdcachedir);
385
386 # And extract it to the finale dir
387 pb_system("cd $vepath ; rpm2cpio $cachedir/$p1 | cpio -ivdum","Extracting package $p1 into $vepath");
388
389 # Remove cached package if not asked to keep
390 if (! $opts{'k'}) {
391 unlink("$cachedir/$p1");
392 }
393
394 }
395 } else {
396 pb_log(0,"WARNING: unable to find URL for $p\n");
397 $warning++;
398 $lwpkg .= " $p";
399 }
400}
401
402if ($warning ge 1) {
403pb_log(0,"$warning WARNINGS found.\nMaybe you should review your package list for $pbos->{'name'}-$pbos->{'version'}-$pbos->{'arch'}\nand remove$lwpkg\n");
404}
405
406# Stop here if we just print
407if ($opts{'p'}) {
408 exit(0);
409}
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
415if ($pbos->{'install'} =~ /yum/) {
416 my $ddir = $pbos->{'name'};
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 }
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#
433if ((($pbos->{'name'} eq "centos") || ($pbos->{'name'} eq "rhel")) && ($pbos->{'version'} eq "5")) {
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#
450pb_mkdir_p("$vepath/dev");
451chmod 0755,"$vepath/dev";
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");
455pb_system("mknod -m 666 $vepath/dev/null c 1 3","Creating $vepath/dev/null") if (! -c "$vepath/dev/null");
456
457my $minipkglist;
458
459pb_log(1,"Adapting $osupdname repository entries\n");
460if ($pbos->{'install'} =~ /yum/) {
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/$pbos->{'arch'}/g' $i","","quiet");
471 pb_system("sed -i -e 's/\$releasever/$pbos->{'version'}/g' $i","","quiet");
472 pb_system("sed -i -e 's/^mirrorlist/#mirrorlist/' $i","","quiet");
473 # rather use neutral separators here
474 pb_system("sed -i -e 's|^#baseurl.*\$|baseurl=$repo|' $i","","quiet");
475 }
476 $minipkglist = "ldconfig yum passwd vim-minimal dhclient authconfig";
477} elsif ($pbos->{'install'} =~ /zypper/) {
478 pb_mkdir_p("$vepath/etc/zypp/repos.d");
479 open(REPO,"> $vepath/etc/zypp/repos.d/$pbos->{'name'}-$pbos->{'version'}") || die "Unable to create repo file";
480 my $baseurl = dirname(dirname($mirror));
481 # Setup the repo
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");
484 } else {
485 pb_system("chroot $vepath /bin/bash -c \"/usr/bin/zypper ar $baseurl $pbos->{'name'}-$pbos->{'version'}\"","Bootstrapping Zypper");
486 }
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";
497} elsif ($pbos->{'install'} =~ /urpmi/) {
498 # Setup the repo
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";
502}
503
504#
505# Run "install the necessary modules".
506# No need for sudo here
507#
508$pbos->{'install'} =~ s/sudo//g;
509pb_system("chroot $vepath /bin/bash -c \"$pbos->{'install'} $minipkglist \"","Bootstrapping OS by running $pbos->{'install'} $minipkglist");
510
511#
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
517# Installed additional packages we were asked to
518if (defined $opts{'a'}) {
519 $opts{'a'} =~ s/,/ /g;
520 pb_system("chroot $vepath /bin/bash -c \"$pbos->{'install'} $opts{'a'} \"","Adding packages to OS by running $pbos->{'install'} $opts{'a'}");
521}
522
523#
524# Clean up
525#
526pb_log(1,"Cleaning up\n");
527if ($pbos->{'install'} =~ /yum/) {
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.