#!/usr/bin/perl -w # # pbmkbm, a project-builder.org utility to make boot media # # $Id$ # # Copyright B. Cornec 2011 # Provided under the GPL v2 # Syntax: see at end use strict 'vars'; use Getopt::Long qw(:config auto_abbrev no_ignore_case); use Data::Dumper; use English; use File::Basename; use File::Copy; use POSIX qw(strftime); use ProjectBuilder::Version; use ProjectBuilder::Base; use ProjectBuilder::Env; use ProjectBuilder::Conf; use ProjectBuilder::Distribution; use ProjectBuilder::VE; # Global variables my %opts; # CLI Options =pod =head1 NAME pbmkbm - a project-builder.org utility to make boot media =head1 DESCRIPTION pbmkbm creates a bootable media (CD/DVD, USB device, Network, tape, ...) with a minimal distribution in it, suited for building packages for example. It aims at supporting all distributions supported by project-builder.org (RHEL, RH, Fedora, OpeSUSE, SLES, Mandriva, ...) It is inspired by work done by Jean-Marc André around the HP SSSTK and aim at replacing the mindi project (http://www.mondorescue.org), but fully integrated with project-builder.org pbmkbm works in different phases. The first one is to check all pbmkbm needs to gather a certain number of components that could come from various sources and could be put on a different target media. We need a kernel, an initrd/initramfs for additional modules and init script, a root filesystem and a boot configuration file. Kernel, modules could come either from the local installed system (typically for disaster recovery context) or from a kernel package of a given configuration or a referenced content. Utilities could come from busybox, local utilities or set of packages. The root filesystem is made with them. The initrd/initramfs could be made internaly or by calling dracut. The boot config file is generated from analysis content or provided externally. =head1 SYNOPSIS pbmkbm [-vhq][-t boot-type [-d device]][-b boot-method][-m os-ver-arch] [-s script][-a pkg1[,pkg2,...]] [target-dir] pbmkbm [--verbose][--help][--man][--quiet][--type boot-type [--device device]] [--machine os-ver-arch][--boot boot-method] [--script script][--add pkg1,[pkg2,...]] [target-dir] =head1 OPTIONS =over 4 =item B<-v|--verbose> Print a brief help message and exits. =item B<-h|--help> Print a brief help message and exits. =item B<--man> Prints the manual page and exits. =item B<-q|--quiet> Do not print any output. =item B<-t|--type boot-type> Type of the boot device to generate. A boot-type can be: =over 4 =item B Generate an ISO9660 image format (suitable to be burned later on or loopback mounted. Uses isolinux. =item B Generate a USB image format (typically a key of external hard drive). Uses syslinux. =item B Generate a PXE environement (suitable to be integrated in a PXElinux configuration). Uses pxelinux. =back =item B<-d|--device device-file> Name of the device or file on which you want to create the boot media. =item B<-b|--boot boot-method> This is the boot method to use to create the boot media. A boot-method can be: =over 4 =item B Use the tools of the native distribution to create the boot media. No other dependency. =item B Use the project-builder.org virtual environment notion to create the boot media. No other dependency outside of the project. =item B Use the busybox tool to create the boot media. Cf: L =item B Use the dracut tool to create the boot media. Cf: L =back =item B<-s|--script script> Name of the script you want to execute on the related boot media at the end o the build. =item B<-a|--add pkg1[,pkg2,...]> Additional packages to add from the distribution you want to install on the related boot media at the end of the build. =item B<-m|--machine os-ver-arch> This is the target tuple operating system-version-architecture for which you want to create the boot media. =back =head1 ARGUMENTS target-dir is the directory under which the boot media will be build. =over 4 =back =head1 EXAMPLE To setup a USB busybox based boot media on the /dev/sdb device for a Fedora 12 distribution with an i386 architecture issue: pbmkbm -t usb -d /dev/sdb -m fedora-12-i386 -b busybox To setup an ISO image under /tmp for a RHEL 6 x86_64 distribution issue using the native environment: pbmkbm -t iso -d /tmp -m rhel-6-x86_64 -b ve =head1 WEB SITES The main Web site of the project is available at L. Bug reports should be filled using the trac instance of the project at L. =head1 USER MAILING LIST Cf: L for announces and L for the development of the pb project. =head1 CONFIGURATION FILE Uses Project-Builder.org configuration file (/etc/pb/pb.conf or /usr/local/etc/pb/pb.conf) =head1 AUTHORS The Project-Builder.org team L lead by Bruno Cornec L. =head1 COPYRIGHT Project-Builder.org is distributed under the GPL v2.0 license described in the file C included with the distribution. =cut # --------------------------------------------------------------------------- my ($projectbuilderver,$projectbuilderrev) = pb_version_init(); my $appname = "pbmkbm"; $ENV{'PBPROJ'} = $appname; # Initialize the syntax string pb_syntax_init("$appname Version $projectbuilderver-$projectbuilderrev\n"); pb_temp_init(); GetOptions("help|?|h" => \$opts{'h'}, "man" => \$opts{'man'}, "verbose|v+" => \$opts{'v'}, "quiet|q" => \$opts{'q'}, "log-files|l=s" => \$opts{'l'}, "script|s=s" => \$opts{'s'}, "machine|m=s" => \$opts{'m'}, "add|a=s" => \$opts{'a'}, "device|d=s" => \$opts{'d'}, "type|t=s" => \$opts{'t'}, "boot|b=s" => \$opts{'b'}, "version|V=s" => \$opts{'V'}, ) || pb_syntax(-1,0); if (defined $opts{'h'}) { pb_syntax(0,1); } if (defined $opts{'man'}) { pb_syntax(0,2); } if (defined $opts{'v'}) { $pbdebug = $opts{'v'}; } if (defined $opts{'q'}) { $pbdebug=-1; } if (defined $opts{'l'}) { open(pbLOG,"> $opts{'l'}") || die "Unable to log to $opts{'l'}: $!"; $pbLOG = \*pbLOG; $pbdebug = 0 if ($pbdebug == -1); } pb_log_init($pbdebug, $pbLOG); pb_log(1,"$appname Version $projectbuilderver-$projectbuilderrev\n"); my @date = pb_get_date(); my $pbdate = strftime("%Y-%m-%d %H:%M:%S", @date); pb_log(1,"Start: $pbdate\n"); # Get VE name $ENV{'PBV'} = $opts{'m'}; # # Initialize distribution info from pb conf file # my $pbos = pb_distro_get_context($ENV{'PBV'}); pb_log(0,"Starting boot media build for $pbos->{'name'}-$pbos->{'version'}-$pbos->{'arch'}\n"); pb_env_init_pbrc(); # to get content of HOME/.pbrc # # Check target dir # Create if not existent and use default if none given # my $targetdir = shift @ARGV; # # Check for command requirements # my ($req,$opt) = pb_conf_get_if("oscmd","oscmdopt"); pb_check_requirements($req,$opt,$appname); # After that we will need root access die "$appname needs to be run as root" if ($EFFECTIVE_USER_ID != 0); # # Where is our build target directory # if (not defined $targetdir) { $targetdir = "/var/cache/pbmkbm"; my ($vestdpath) = pb_conf_get("mkbmpath"); $targetdir = "$vestdpath->{'default'}/$pbos->{'name'}/$pbos->{'version'}/$pbos->{'arch'}" if (defined $vestdpath->{'default'}); pb_log(1,"No target-dir specified, using $targetdir\n"); } # Point to the right subdir and create it if needed pb_mkdir_p($targetdir) if (! -d $targetdir); # Log information on our system # TODO: this should be put in a subfunction my ($logcmd) = pb_conf_get("logcmd"); my ($logopt) = pb_conf_get_if("logopt"); # check that the command is there first and then use it. if ($logcmd->{$appname} ne "internal") { $logcmd = pb_check_req($logcmd->{$appname},1); if (not defined $logcmd) { pb_log(1,"INFO: command $logcmd->{$appname} doesn't exist. No log report is available\n"); } else { my $c = $logcmd->{$appname}; $c .= " $logopt->{$appname}" if (defined $logopt->{$appname}); pb_system("$c","Creating a log report of your system"); } } else { # We provide our own internal log reporter as a std one isn't found my ($lcmds,$lfiles) = pb_conf_get_if("logcommands","logfiles"); pb_log(1,"------------------\n"); if (defined $lcmds->{$appname}) { foreach my $c (split(/,/,$lcmds->{$appname})) { my $lcmd = $c; $lcmd =~ s/ .*$//; $lcmd = pb_check_req($lcmd,1); if (not defined $lcmd) { pb_log(1,"INFO: command $lcmd (in $c) doesn't exist\n"); pb_log(1,"------------------\n"); next; } pb_log(1,"Execution of $c\n"); pb_log(1,"------------------\n"); pb_system($c,"",1); pb_log(1,"------------------\n"); } } if (defined $lfiles->{$appname}) { foreach my $f (split(/,/,$lfiles->{$appname})) { if (! -e $f) { pb_log(1,"INFO: $f doesn't exist\n"); pb_log(1,"------------------\n"); next; } if (! -r $f) { pb_log(1,"INFO: $f isn't readable\n"); pb_log(1,"------------------\n"); next; } if ((-f $f) || (-l $f)) { if (! open(FILE,$f)) { pb_log(1,"INFO: Unable to open $f\n"); pb_log(1,"------------------\n"); next; } pb_log(1,"Content of $f\n"); pb_log(1,"------------------\n"); while () { pb_log(1,$_); } close(FILE); pb_log(1,"------------------\n"); } elsif (-d $f) { my $dh; if (! opendir($dh,$f)) { pb_log(1,"INFO: Unable to opendir $f\n"); pb_log(1,"------------------\n"); next; } pb_log(1,"Content of directory $f\n"); pb_log(1,"----------------------------\n"); while (readdir $dh) { my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat("$f/$_"); my $str = sprintf("%10s %2s %5s %5s %10s %14s %s",$mode,$ino,$uid,$gid,$size,$mtime,$_); pb_log(1,"$str\n"); } closedir($dh); pb_log(1,"------------------\n"); } else { pb_log(1,"INFO: $f is special\n"); pb_log(1,"------------------\n"); } } } } # Now the preparation is over, we need to do something useful :-) # But it all depends on how we're asked to do it. # # First we need to copy into the target dir all the relevant content pb_mkbm_create_content(); # Then we need to package this content in the destination format pb_mkbm_create_media(); @date = pb_get_date(); $pbdate = strftime("%Y-%m-%d %H:%M:%S", @date); pb_log(1,"End: $pbdate\n"); sub pb_mkbm_create_content { pb_log(1,"Creating boot media content\n"); # If not defined use VE mode by default $opts{'b'} = "ve" if (not defined $opts{'b'}); # Hash of target content # atribute could be, copy, remove, link, dir my %targettree; if ($opts{'b'} eq "ve") { # Use project-builder VE mecanism to create a good VE ! pb_ve_launch($ENV{'PBV'},1); } elsif ($opts{'b'} eq "native") { # Use native tools to create a good VE ! } elsif ($opts{'b'} eq "drakut") { # Use drakut to create a good VE ! } elsif ($opts{'b'} eq "busybox") { # Use busybox to create a good VE ! pb_mkbm_create_busybox_ve(\%targettree); } else { die "Unknown method $opts{'b'} used to create the media content"; } # Create the directory structure needed on the target dir my ($tdirs,$bdirs,$bfiles,$bcmds) = pb_distro_get_param($pbos,pb_conf_get("mkbmtargetdirs","mkbmbootdirs","mkbmbootfiles","mkbmbootcmds"); # Create empty dirs for these foreach my $d (split(/,/,$tdirs)) { $targettree->{$d} = "emptydir"; } # And copy dirs for those foreach my $d (split(/,/,$bdirs)) { if (-d $d) { $targettree->{$d} = "dir"; } else { pb_log( } foreach my $f (split(/,/,$bfiles)) { $targettree->{$d} = "dir"; } # Once the environment is made, add what is needed for this boot media to it. # Keyboard # Terminfo # List of commands # List of dependencies # Kernel # Initrd # init # BootLoader and its configuration # Additional data files coming from a potential caller (MondoRescue/Mindi e.g. with fstab, LVM, mountlist, ...) } sub pb_mkbm_create_busybox_ve { my $tgtree = shift; # First, check which are the supported command in that version of busybox # and create the links for it in the target VE my $busycmd = pb_distro_get_param($pbos,pb_conf_get("ospathcmd-busybox")); open(BUSY,"$busycmd |") || die "Unable to execute $busycmd"; my $cmdlist = 0; while () { pb_log(3,"busybox line : $_"); chomp($_); # After these words, we have the list of functions, so trigger their analysis if ($_ =~ /defined functions:/) { $cmdlist = 1; next; } # Analyse the list of commands provided by that busybox (, separated) if ($cmdlist == 1) { pb_log(3,"Analyzing that busybox line\n"); foreach my $c (split(/,/,$_)) { $c =~ s/\s*//g; # skip empty strings next if ($c =~ /^$/); my $c1 = pb_check_req($c,0); if (defined $c1) { $tgtree->{$c1} = "link:$busycmd"; } else { # When not found on the system, create the link under /usr/bin by default $tgtree->{"/usr/bin/$c"} = "link:$busycmd"; } } } } pb_log(2,"Target Tree is now: ".Dumper($tgtree)."\n"); close(BUSY); } sub pb_mkbm_create_media { } # Get the package list to download, store them in a cache directory # #my ($mkbmcachedir) = pb_conf_get_if("mkbmcachedir"); #my ($pkgs) = pb_distro_get_param($pbos,pb_conf_get("mkbmmindep")); # # /proc needed # #pb_system("mount -o bind /proc $targetdir/proc","Mounting /proc"); # Installed additional packages we were asked to #if (defined $opts{'a'}) { #$opts{'a'} =~ s/,/ /g; #pb_system("chroot $targetdir /bin/bash -c \"$pbos->{'install'} $opts{'a'} \"","Adding packages to OS by running $pbos->{'install'} $opts{'a'}"); #} # # Clean up # #pb_log(1,"Cleaning up\n"); #pb_system("umount $targetdir/proc","Unmounting /proc"); # Executes post-install step if asked for #if ($opts{'s'}) { #pb_system("$opts{'s'} $targetdir","Executing the post-install script: $opts{'s'} $targetdir"); #}