source: devel/rpmbootstrap/bin/rpmbootstrap @ 1082

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