From 63555aa270059488b013e6c802cca64a8d6eebdc Mon Sep 17 00:00:00 2001 From: Martin Whitaker Date: Tue, 9 Jan 2018 23:17:35 +0000 Subject: draklive: allow installer GUI to be used and run as normal user. Read all configuration from the main config file and automatically generate the auto_inst.cfg.pl file. Run the installer GUI in a nested X server if any items are not specified in the config file. Use sudo to run any steps that need root privileges, to avoid running the X server as root. --- drakclassic | 10 +- draklive | 16 +- lib/MGA/DrakISO/BuildBoot.pm | 27 +- lib/MGA/DrakISO/BuildLoop.pm | 14 +- lib/MGA/DrakISO/BuildMedia.pm | 4 +- lib/MGA/DrakISO/BuildRoot.pm | 683 +++++++++++++++++++++++++++++++----------- lib/MGA/DrakISO/Config.pm | 3 +- lib/MGA/DrakISO/ISOBuild.pm | 2 +- lib/MGA/DrakISO/LiveBuild.pm | 5 + lib/MGA/DrakISO/Loopback.pm | 11 +- lib/MGA/DrakISO/Utils.pm | 60 +++- 11 files changed, 610 insertions(+), 225 deletions(-) diff --git a/drakclassic b/drakclassic index 9414544..308cf46 100755 --- a/drakclassic +++ b/drakclassic @@ -52,12 +52,12 @@ $::force = 0; sub clean { my ($build) = @_; - if (-e ($build->get_system_root)) { - # There shouldn't be anything mounted in the chroot, but play it safe... - umount_all_in_chroot($build); - system('sudo rm -rf ' . $build->get_system_root); + if (-e $build->get_builddir) { + system('rm -rf ' . $build->get_builddir); + } + if (-e $build->get_chroot_dir) { + system('sudo rm -rf ' . $build->get_chroot_dir); } - system('rm -rf ' . $build->get_builddir) if -e ($build->get_builddir); } ############################################################################### diff --git a/draklive b/draklive index 13cfa51..6d2b675 100755 --- a/draklive +++ b/draklive @@ -51,12 +51,14 @@ $::verbose = 1; sub clean { my ($build) = @_; - if (-e ($build->get_system_root)) { - # Make sure there's nothing left mounted in the chroot. - umount_all_in_chroot($build); - system('sudo rm -rf ' . $build->get_system_root); + if (-e $build->get_builddir) { + umount_all_in_root($build->get_builddir); + system('sudo rm -rf ' . $build->get_builddir); + } + if (-e $build->get_chroot_dir) { + umount_all_in_root($build->get_chroot_dir); + system('sudo rm -rf ' . $build->get_chroot_dir); } - system('rm -rf ' . $build->get_builddir) if -e ($build->get_builddir); } sub prepare_root { @@ -79,7 +81,7 @@ my @actions = ( { name => 'dump-config', do => \&MGA::DrakISO::Config::dump_config }, { name => 'clean', do => \&clean }, { name => 'root', do => \&prepare_root }, - { name => 'root-create', do => \&MGA::DrakISO::BuildRoot::install_live_system }, + { name => 'root-install', do => \&MGA::DrakISO::BuildRoot::install_live_system }, { name => 'root-customise', do => \&MGA::DrakISO::BuildRoot::customise_live_system }, { name => 'boot', do => \&prepare_boot }, { name => 'boot-system', do => \&MGA::DrakISO::BuildBoot::prepare_live_system_boot }, @@ -89,8 +91,6 @@ my @actions = ( ); my @all = qw(root boot loop master); -die "you must be root to run this program\n" if $>; - my $build_object = 'MGA::DrakISO::LiveBuild'->new; my $config_root = '/etc/draklive'; my $config_path = 'config/build.cfg'; diff --git a/lib/MGA/DrakISO/BuildBoot.pm b/lib/MGA/DrakISO/BuildBoot.pm index ed3347c..276217b 100644 --- a/lib/MGA/DrakISO/BuildBoot.pm +++ b/lib/MGA/DrakISO/BuildBoot.pm @@ -51,6 +51,8 @@ our @EXPORT = qw(prepare_live_system_boot prepare_iso_bootloader); sub prepare_live_system_boot { my ($build) = @_; + my $root = $build->get_system_root; + # Create a build directory. This will contain all the files we need to # exist in /boot on the ISO. my $boot_dir = $build->get_builddir('boot'); @@ -61,21 +63,24 @@ sub prepare_live_system_boot { print "Using kernel $kernel->{version}\n"; # Copy the kernel into the build directory. - my $vmlinuz = $build->get_system_root . '/boot/vmlinuz-' . $kernel->{version}; - -e $vmlinuz or die "ERROR: cannot find kernel $kernel->{version} in root system\n"; - cp_f($vmlinuz, $boot_dir . '/vmlinuz'); + my $vmlinuz = '/boot/vmlinuz-' . $kernel->{version}; + -e $root . $vmlinuz or die "ERROR: cannot find kernel $kernel->{version} in root system\n"; + cp_f($root . $vmlinuz, $boot_dir . '/vmlinuz'); # Build an initrd suitable for Live boot. - my $initrd = $build->get_system_root . '/boot/' . $build->get_initrd_name; - unlink($initrd); - { - my $bootloader = {}; - local $::prefix = $build->get_system_root; - bootloader::add_kernel($bootloader, $kernel, { label => 'linux', vga => $build->{system}{vga_mode} }, '', $build->{system}{no_initrd}); - } + my $initrd = '/boot/' . $build->get_initrd_name; + run_in_root($root, undef, 'mkinitrd', '-f', if_($::verbose < 3, '-q'), $initrd, $kernel->{version}) + or die "ERROR: cannot create initrd\n"; + run_as_root('chmod', '644', $root . $initrd) + or die "ERROR: cannot chmod initrd\n"; # Move the initrd into the build directory. - mv($initrd, $boot_dir . '/initrd.gz') or die "ERROR: cannot move initrd: $!\n"; + run_as_root('mv', $root . $initrd, $boot_dir . '/initrd.gz') + or die "ERROR: cannot move initrd\n"; + + # Remove anything written to /dev/null. We haven't mounted the /dev + # filesystem, so this will just be an ordinary file. + run_as_root('rm', $root . '/dev/null') if -f $root . '/dev/null'; } ############################################################################### diff --git a/lib/MGA/DrakISO/BuildLoop.pm b/lib/MGA/DrakISO/BuildLoop.pm index a16ecd8..01aefca 100644 --- a/lib/MGA/DrakISO/BuildLoop.pm +++ b/lib/MGA/DrakISO/BuildLoop.pm @@ -45,7 +45,7 @@ our @EXPORT = qw(build_live_loopback_files list_loopback_modules); sub build_live_loopback_files { my ($build) = @_; # make sure no external filesystems are mounted before creating the loopback - umount_all_in_chroot($build); + umount_all_in_root($build->get_system_root); my @excluded_files = expand_file_list($build, @{$build->{loopbacks}{exclude}{files} || []}); my @modules_files = expand_file_list($build, @{$build->{loopbacks}{modules} || []}); @@ -57,11 +57,11 @@ sub build_live_loopback_files { foreach my $module (list_loopback_modules($build)) { my $copy_tree = $build->get_system_root . "/tmp/draklive/loop/$module->{name}"; - eval { rm_rf($copy_tree) }; + run_as_root('rm', '-rf', $copy_tree); hardlink_filtered($build->get_system_root, $copy_tree, $module->{files}); my $loop = $loop_types{$module->{type}}; $loop->{build}->($build, { path => "/modules/$module->{name}", root => $copy_tree, exclude => \@excluded_files }); - eval { rm_rf($copy_tree) }; + run_as_root('rm', '-rf', $copy_tree); } if (@excluded_files) { @@ -71,17 +71,17 @@ sub build_live_loopback_files { foreach my $module (list_loopback_modules($build)) { my $copy_tree = $build->get_system_root . "/tmp/draklive/excluded/$module->{name}"; - eval { rm_rf($copy_tree) }; + run_as_root('rm', '-rf', $copy_tree); hardlink_filtered($excluded_tree, $copy_tree, $module->{files}); my $loop = $loop_types{$module->{type}}; $loop->{build}->($build, { path => "/modules/excluded-$module->{name}", root => $copy_tree }); - eval { rm_rf($copy_tree) }; + run_as_root('rm', '-rf', $copy_tree); } my $loop = $loop_types{$build->{loopbacks}{exclude}{type}}; $loop->{build}->($build, { path => "/excluded", root => $excluded_tree, exclude => \@modules_files }); - eval { rm_rf($excluded_tree) }; + run_as_root('rm', '-rf', $excluded_tree); } } @@ -101,7 +101,7 @@ sub hardlink_filtered { my $list_file = tmpnam(); output_p($list_file, map { "$_\n" } grep { -e $src . $_ } @$files); #- cpio -pldm won't copy recursively, use rsync -r instead - system('rsync', '-ar', '--files-from=' . $list_file, '--link-dest=' . $src, $src, $dest); + run_as_root('rsync', '-ar', '--files-from=' . $list_file, '--link-dest=' . $src, $src, $dest); unlink $list_file; } diff --git a/lib/MGA/DrakISO/BuildMedia.pm b/lib/MGA/DrakISO/BuildMedia.pm index 8fe59bd..5e75c1c 100644 --- a/lib/MGA/DrakISO/BuildMedia.pm +++ b/lib/MGA/DrakISO/BuildMedia.pm @@ -175,7 +175,7 @@ sub get_available_packages { $urpm = urpm->new; - urpm::set_files($urpm, $build->get_system_root); + urpm::set_files($urpm, $build->get_chroot_dir); urpm::get_global_options($urpm); $urpm->{info} = sub { }; @@ -600,7 +600,7 @@ sub create_index { sub run_urpm { my ($build, $cmd, $parameters, $o_not_fatal) = @_; - my $urpmi_root = '--urpmi-root ' . $build->get_system_root; + my $urpmi_root = '--urpmi-root ' . $build->get_chroot_dir; my $error = system("LC_ALL=C sudo $cmd $urpmi_root $parameters"); $error == 0 || $o_not_fatal or die "ERROR: $cmd command failed\n"; diff --git a/lib/MGA/DrakISO/BuildRoot.pm b/lib/MGA/DrakISO/BuildRoot.pm index 616e3a6..af06769 100644 --- a/lib/MGA/DrakISO/BuildRoot.pm +++ b/lib/MGA/DrakISO/BuildRoot.pm @@ -27,8 +27,11 @@ package MGA::DrakISO::BuildRoot; use strict; use MDK::Common; +use Try::Tiny; use common; +use File::Temp qw(tmpnam); + use MGA::DrakISO::LiveBuild; use MGA::DrakISO::Loopback; use MGA::DrakISO::Utils; @@ -37,163 +40,479 @@ use Exporter; our @ISA = qw(Exporter); our @EXPORT = qw(install_live_system customise_live_system); +############################################################################### +# System Installation +############################################################################### + # This is the top-level function called to create the basic root filesystem. It # uses stage2 of the Mageia installer to do the actual work. It is independent # of any other preparatory step. # +# This function is largely derived from drakx_in_chroot. +# sub install_live_system { my ($build) = @_; - my $repository = $build->{settings}{repository} . '/' . $build->{settings}{arch}; + print "Installing Live system\n" if $::verbose; - my $drakx_in_chroot = $repository . '/misc/drakx-in-chroot'; - my $remote_repository = $repository =~ m!^(ftp|http)://! && $1; - if ($remote_repository) { - my $local_drakx_in_chroot = $build->get_builddir('scripts') . '/drakx-in-chroot'; - mkdir_p(dirname($local_drakx_in_chroot)); - run_('curl', '--silent', '-o', $local_drakx_in_chroot, $drakx_in_chroot) - or die "unable to get drakx-in-chroot from remote repository\n"; - $drakx_in_chroot = $local_drakx_in_chroot; - } + my $arch = $build->{settings}{arch}; - local %ENV = ( - %ENV, - (map { "DRAKLIVE_" . uc($_->[0]) => $_->[1] } group_by2(%{$build->{settings}})), - %{$build->{system}{install_env}}, - ); - $ENV{DRAKLIVE_LANGS} = join(':', $build->get_langs); - run_({ targetarch => $build->{settings}{arch} }, - 'perl', $drakx_in_chroot, - $repository, - $build->get_system_root, - if_($build->{system}{auto_install}, '--auto_install', $build->{settings}{config_root} . '/' . $build->{system}{auto_install}), - if_($build->{system}{patch_install}, '--defcfg', $build->{settings}{config_root} . '/' . $build->{system}{patch_install}), - if_($build->{system}{rpmsrate}, '--rpmsrate', $build->{settings}{config_root} . '/' . $build->{system}{rpmsrate}), - ($build->{system}{stage2_updates} ? (map { ('--stage2-update', $build->{settings}{config_root} . '/' . $_->[0], $_->[1]) } @{$build->{system}{stage2_updates}}) : ()), - ) or die "unable to install system chroot\n"; -} + my $base_repository = $build->{settings}{repository}; + my $arch_repository = $base_repository . '/' . $arch; + my $remote_method = $arch_repository =~ m!^(ftp|http)://! && $1; -# This is the top-level function called to customise the root filesystem. It -# allows a standard Mageia installation to be fine-tuned for use as a Live -# system. The basic root filesystem must have been prepared before calling -# this function. -# -sub customise_live_system { - my ($build) = @_; + my $chroot = $build->get_chroot_dir; - my $previous_umask = umask; - #- workaround buggy installation of directories that are not owned by any packages - umask 022; + my $rooted_stage2 = '/tmp/stage2'; + my $chroot_stage2 = $chroot . $rooted_stage2; + + my $live_root = $build->get_system_root; + if (-e $live_root) { + # We want a clean start... + umount_all_in_root($live_root); + run_as_root('rm', '-rf', $live_root); + } + mkdir_p($live_root); + + my $Xserver_pid; + my $error_message; + try { + # Mount the directory where we want to install the Live system. + mount($chroot . '/mnt', $live_root, '-o bind'); + + # Mount the standard system pseudo-filesystems, so that the installer + # has a proper environment to run in. + mount_system_fs($chroot); + mount($chroot . '/sys/kernel/debug', 'none', '-t debugfs'); + + # Mount the stage2 installer filesystem. + if ($remote_method) { + my $local_mdkinst = $chroot . '/tmp/mdkinst.sqfs'; + system("curl --silent -o $local_mdkinst $arch_repository/install/stage2/mdkinst.sqfs") + or die "ERROR: failed to download mdkinst.sqfs from remote repository\n"; + mount($chroot_stage2, $local_mdkinst, '-t squashfs -o loop,ro'); + + } elsif (-d $arch_repository . '/install/stage2/live') { + mount($chroot_stage2, $arch_repository . '/install/stage2/live', '-o bind,ro'); + + } elsif (-f $arch_repository . '/install/stage2/mdkinst.sqfs') { + mount($chroot_stage2, $arch_repository . '/install/stage2/mdkinst.sqfs', '-t squashfs -o loop,ro'); + + } else { + die "ERROR: failed to find installer stage2\n"; + } + + # The stage2 installer expects to find the full repository in this + # location... + mount($chroot . '/tmp/media', $base_repository, '-o bind,ro') if !$remote_method; + # and the arch-specific repository in this location. + symlinkf('media/' . $arch, $chroot . '/tmp/image'); + # Because the installer uses the above symlink, relative paths in + # the urpmi configuration don't work unless we add this extra link. + symlinkf('media/i586', $chroot . '/tmp/i586') if $arch eq 'x86_64'; + + # If the user has provided an auto-install configuration file, use it, + # otherwise construct one. + my $rooted_auto_inst = '/tmp/auto_inst.cfg.pl'; + my $system_auto_inst = $build->get_absolute_path($build->{system}{auto_install}); + my $interactive; + if (defined $system_auto_inst) { + -f $system_auto_inst or die "ERROR: can't find the auto-install configuration file $system_auto_inst\n"; + cp_f($system_auto_inst, $chroot . $rooted_auto_inst); + } else { + write_auto_inst_cfg($build, $chroot . $rooted_auto_inst, \$interactive); + } + + # Add a few more things to complete the installer's working environment. + + my $etc = $chroot . '/etc'; + mkdir_p($etc); + output($etc . '/hosts', "127.0.0.1 localhost\n"); + + my $var = $chroot . '/var'; + mkdir_p($var); + + create_initial_symlinks($chroot, $rooted_stage2); + + chomp(my $kernel_version = `uname -r`); + my $modules_dir = '/modules/' . $kernel_version; + output_p($chroot . $modules_dir . $_, "\n") + foreach "/lib/$modules_dir/modules.dep", "/lib/$modules_dir/modules.alias"; + + # If some of the installer steps will be interactive, we need to start a + # nested X server to display the GUI. + my $DISPLAY = ''; + if ($interactive) { + my $Xserver = whereis_binary('Xephyr') || whereis_binary('Xnest') + or die "ERROR: Xephyr or Xnest not found - cannot run installer GUI\n"; + my $screen_size = $Xserver =~ /Xephyr/ ? '-screen' : '-geometry'; + $DISPLAY = ':8'; + if ($Xserver_pid = fork()) { + } else { + close(STDOUT); open(STDOUT, '>', '/dev/null'); + close(STDERR); open(STDERR, '>', '/dev/null'); + exec($Xserver, $DISPLAY, '-ac', $screen_size, '1024x768'); + } + } + + # Run the installer. The chroot command sets up a new environment, + # so we need to set the variables we want after we've entered the + # chroot. + my $env = join(' ', + "PATH=/usr/sbin:/mnt/usr/sbin:/usr/bin:/mnt/usr/bin", + "LD_LIBRARY_PATH=/usr/lib:/mnt/usr/lib:/usr/lib64:/mnt/usr/lib64", + if_($remote_method, "URLPREFIX=$arch_repository"), + "DISPLAY=$DISPLAY", + "TERM=linux", + "HOME=/", + ); + my $cmd = "/usr/bin/runinstall2 --local_install --auto_install $rooted_auto_inst"; + $cmd .= "--method $remote_method" if $remote_method; + run_in_root($chroot, $arch, 'sh', '-c', "$env $cmd") + or die "ERROR: failed to install base system\n"; + } catch { + $error_message = $_; + } finally { + # During package installation, a dbus daemon may get automatically + # launched in the Live system root. We need to kill it before we + # can unmount the root filesystem. + run_as_root('fuser', '-s', '-k', "$chroot/mnt"); + # Allow a bit of time for the processes to die. + sleep(1); + # Now we can unmount everything. The order is important. + umount_all_in_root($live_root); + umount_all_in_root($chroot); + # And finally kill off our nested X server. + kill(15, $Xserver_pid) if $Xserver_pid; + }; + defined $error_message && die $error_message; +} - mount_system_fs($build); +sub write_auto_inst_cfg { + my ($build, $file, $interactive) = @_; + + my $region = $build->{settings}{region}; + my $enabled_media = $build->{system}{enabled_media}; + my $rpmsrate_flags = $build->{system}{rpmsrate_flags}; + my $rpmsrate_level = $build->{system}{rpmsrate_level} || 5; + my $include_packages = $build->{system}{include_packages}; + my $exclude_packages = $build->{system}{exclude_packages}; + my $preferred_packages = $build->{system}{preferred_packages}; + my @desktops = split(/\|/, $build->{settings}{desktop}); + my $default_user = $build->{settings}{default_user}; + my $post_install_nr = $build->{system}{post_install_nr}; + my $post_install = $build->{system}{post_install}; + + $$interactive = !$region || !$enabled_media || !$rpmsrate_flags || !$default_user; + + my @text; + push @text, ( + "\$o = {", + " security => 1,", + " authentication => {", + " shadow => 1,", + " local => 1,", + " blowfish => 1,", + " },", + ); + push @text, ( + " interactiveSteps => [", + if_(!$region, + " 'selectLanguage',", + " 'selectKeyboard',", + ), + if_(!$enabled_media, + " 'chooseMedia',", + ), + if_(!$rpmsrate_flags, + " 'choosePackages',", + ), + if_(!$default_user, + " 'addUser',", + ), + " ],", + ) if $$interactive; + push @text, ( + " media => [", + " {", + " type => 'media_cfg',", + " url => 'drakx://media',", + " selected_names => '" . join(', ', @$enabled_media) . "',", + " },", + " ],", + " # temporary (?) fix for mga#12299", + " enabled_media => [ " . join(', ', map { "'$_'" } @$enabled_media) . " ],", + ) if $enabled_media; + push @text, ( + " rpmsrate_flags_chosen => {", + (map { " $_ => 1," } @$rpmsrate_flags), + " },", + " compssListLevel => $rpmsrate_level,", + ) if $rpmsrate_flags; + push @text, ( + " default_packages => [", + (map { " '$_'," } @$include_packages), + " ],", + ) if $include_packages; + push @text, ( + " skipped_packages => [", + (map { " '$_'," } @$exclude_packages), + " ],", + ) if $exclude_packages; + push @text, ( + " preferred_packages => '" . join(', ', @$preferred_packages) . "',", + ) if $preferred_packages; + push @text, ( + " meta_class => 'desktop',", + " desktop => '$desktops[0]',", + ) if @desktops; + push @text, ( + " autologin => '$default_user',", + " users => [", + " {", + " realname => '',", + " name => '$default_user',", + " shell => '/bin/bash',", + " icon => 'default',", + " groups => [],", + " gid => '',", + " uid => '',", + " },", + " ],", + " superuser => {", + " pw => '',", + " realname => 'root',", + " shell => '/bin/bash',", + " home => '/root',", + " gid => '0',", + " uid => '0',", + " },", + ) if $default_user; + push @text, ( + " locale => {", + " lang => 'en_US',", + " country => 'US',", + " IM => undef,", + if_($region ne 'all', + " langs => { " . join(', ', map { "$_ => 1" } $build->get_langs) . " },", + ), + if_($region eq 'all', + " langs => { all => 1 },", + ), + " utf8 => 1,", + " },", + " keyboard => {", + " KEYBOARD => 'us',", + " KEYTABLE => 'us',", + " KBCHARSET => 'C',", + " GRP_TOGGLE => '',", + " },", + " timezone => {", + " ntp => undef,", + " timezone => 'America/New_York',", + " UTC => 1,", + " },", + ) if $region; + push @text, ( + " postInstallNonRooted => \"$post_install_nr\"," + ) if $post_install_nr; + push @text, ( + " postInstall => \"$post_install\"," + ) if $post_install; + push @text, ( + " X => { disabled => 1 },", + " keep_unrequested_dependencies => 0,", + " match_all_hardware => 1,", + " autoExitInstall => 1,", + "};", + ); + output($file, map { "$_\n" } @text); +} - #- copy resolv.conf for name resolution to work when adding media - cp_f("/etc/resolv.conf", $build->get_system_root . "/etc/"); +sub create_initial_symlinks { + my ($chroot, $rooted_stage2) = @_; - #- remove previous draklive leftovers if needed - run_({ root => $build->get_system_root }, 'urpmi.removemedia', '-a'); + my $chroot_stage2 = $chroot . $rooted_stage2; - foreach (@{$build->{system}{additional_media}}) { - run_({ root => $build->get_system_root }, 'urpmi.addmedia', if_($_->{distrib}, '--distrib'), $_->{name}, $_->{path}) - or die "unable to add media from $_->{path}\n"; - @{$_->{packages} || []} or next; - run_({ root => $build->get_system_root, targetarch => $build->{settings}{arch} }, - 'urpmi', '--auto', '--no-verify-rpm', if_(!$_->{distrib}, '--searchmedia', $_->{name}), @{$_->{packages}}) - or die "unable to install packages from $_->{path}\n"; + foreach my $line (cat_or_die($chroot_stage2 . '/usr/share/symlinks')) { + my ($from, $to_) = split(' ', $line); + my $to = $chroot . ($to_ || $from); + $from = $rooted_stage2 . $from if !$to_; + symlinkf($from, $to) or die "ERROR: symlinking $to failed\n"; } - #- additional rpms may have dependencies in additional media - if (@{$build->{system}{rpms} || []}) { - my $rpm_tmp_dir = '/tmp/draklive_rpms'; - mkdir_p($build->get_system_root . $rpm_tmp_dir); - cp_f((map { $build->{settings}{config_root} . '/' . $_ } @{$build->{system}{rpms}}), $build->get_system_root . $rpm_tmp_dir); - run_({ root => $build->get_system_root, targetarch => $build->{settings}{arch} }, - 'urpmi', '--auto', '--no-verify-rpm', - map { $rpm_tmp_dir . '/' . basename($_) } @{$build->{system}{rpms}}) - or die "unable to install additional system rpms\n"; - rm_rf($build->get_system_root . $rpm_tmp_dir); - } + my $from = $rooted_stage2 . '/usr'; + my $to = $chroot . '/usr'; + symlinkf $from, $to or die "ERROR: symlinking $to failed\n"; - #- remove urpmi media added by drakx-in-chroot and additional media, they're unusable - run_({ root => $build->get_system_root }, 'urpmi.removemedia', '-a'); - - my $erase = join(' ', @{$build->{system}{erase_rpms} || []}); - run_({ root => $build->get_system_root, targetarch => $build->{settings}{arch} }, - 'sh', '-c', "rpm -qa $erase | xargs rpm -e ") if $erase; - - run_({ root => $build->get_system_root }, 'systemctl', if_($::verbose < 2, '-q'), 'disable', $_ . '.service') - foreach @{$build->{system}{disable_services}}; - run_({ root => $build->get_system_root }, 'systemctl', if_($::verbose < 2, '-q'), 'disable', $_ . '.timer') - foreach @{$build->{system}{disable_timers}}; - - #- make sure harddrake is run: - #- if previous HW config file is empty, we assumes DrakX has just completed the installation - #- (do it in chroot, or else Storable from the build box may write an incompatible config file) - run_({ root => $build->get_system_root }, - 'perl', '-MStorable', '-e', qq(Storable::store({ UNKNOWN => {} }, '/etc/sysconfig/harddrake2/previous_hw'))); - - #- remove some build-machine specific configuration - clean_system_conf_file($build, $_) - foreach qw(/etc/mtab /etc/iftab /etc/shorewall/interfaces /etc/mdadm.conf), - if_(!$build->{system}{skip_fstab}, '/etc/fstab'); - unlink($_) foreach map { glob($build->get_system_root . $_) } @{$build->{system}{remove_files} || []}; - - if ($build->{system}{modules_conf}) { - local $::prefix = $build->get_system_root; - local *modules::write_preload_conf = sub {}; #- FIXME, make this an option - my $modules_conf = modules::any_conf->vnew; - put_in_hash($modules_conf, $build->{system}{modules_conf}); - $modules_conf->write; + foreach my $dir ('/bin', '/sbin', '/lib', '/lib64') { + $from = 'usr' . $dir; + $to = $chroot . $dir; + symlinkf($from, $to) or die "ERROR: symlinking $to failed\n"; } +} - my $mount_options = $build->get_media_setting('mount_options') || "defaults"; - output_with_perm($build->get_system_root . '/etc/fstab', 0644, - $build->{mount}{overlay} - ? "none / $build->{mount}{overlay} $mount_options 0 0\n" - : $build->get_media_setting('source') . " / " . $build->get_media_setting('fs') . " $mount_options 1 1\n" - ) unless $build->{system}{skip_fstab}; +############################################################################### +# System Customisation +############################################################################### - #- interactive mode can lead to race in initscripts - #- (don't use addVarsInSh from MDK::Common, it breaks shell escapes) - substInFile { s/^PROMPT=.*/PROMPT=no/ } $build->get_system_root . '/etc/sysconfig/init'; +# This is the top-level function called to customise the root filesystem. It +# allows the standard Mageia installation to be fine-tuned for use as a Live +# system. The basic root filesystem must have been prepared before calling +# this function. +# +sub customise_live_system { + my ($build) = @_; - configure_draklive_resize($build); + print "Customising Live system\n" if $::verbose; - if ($build->{system}{preselect_kdm_user}) { - #- preselect specified user in kdm - my $kdm_cfg = $build->get_system_root . '/etc/kde/kdm/kdmrc'; - update_gnomekderc($kdm_cfg, 'X-:0-Greeter' => (PreselectUser => 'Default', DefaultUser => $build->{system}{preselect_kdm_user})) if -f $kdm_cfg; - } + my $arch = $build->{settings}{arch}; + my $root = $build->get_system_root; - #- apply patches and install files after the configuration is cleaned - #- to allow special configuration files (especially modprobe.preload) - foreach (@{$build->{system}{patches}}) { - my $patch = $build->{settings}{config_root} . '/' . $_; - my @args = ('-p0', '-d', $build->get_system_root, '-i', $patch); - run_program::run('patch', '>', '/dev/null', '--dry-run', '-f', '-R', @args) || run_('patch', @args) - or die "unable to apply patch " . $patch . "\n"; - } + # Workaround buggy installation of directories that are not owned by any + # packages. + my $previous_umask = umask; + umask 022; - copy_files_to($build, $build->{system}{files}, $build->get_system_root); - my @no_install_files = map { $_->[1] } grep { $_->[2] && $_->[2]{no_install} } @{$build->{system}{files}}; - output_p($build->get_system_root . '/etc/draklive-install.d/remove.d/draklive', map { "$_\n" } @no_install_files); + my $error_message; + try { + mount_system_fs($root); + + # Copy resolv.conf for name resolution to work when adding media. + copy_to_root($root, '/etc/', undef, '/etc/resolv.conf'); - eval { rm_rf($build->get_builddir('files')) }; - mkdir_p($build->get_builddir('files')); - if ($build->{media}{files}) { - copy_files_to($build, $build->{media}{files}, $build->get_builddir('files')); - } - remove_files_from($build->{media}{remove_files}, $build->get_builddir('files')); + # Remove urpmi media added by drakx-in-chroot, they're unusable. + run_in_root($root, undef, 'urpmi.removemedia', if_($::verbose < 3, '-q'), '-a'); + + print "..adding additional media\n" if $::verbose > 1; - run_({ targetarch => $build->{settings}{arch} }, - "chroot", $build->get_system_root, "bash", "-c", $build->{system}{final_fixes}) if $build->{system}{final_fixes}; + # Add additional media for installing RPMs not in the main repository. + foreach (@{$build->{system}{additional_media}}) { + run_in_root($root, undef, 'urpmi.addmedia', if_($::verbose < 2, '-q'), + if_($_->{distrib}, '--distrib'), $_->{name}, $_->{path}) + or die "ERROR: unable to add media from $_->{path}\n"; - clean_system_conf_file($build, "/etc/resolv.conf"); - write_dist_lists($build); + @{$_->{packages} || []} or next; + + run_in_root($root, $arch, 'urpmi', if_($::verbose < 3, '-q'), '--auto', '--no-verify-rpm', + if_(!$_->{distrib}, '--searchmedia', $_->{name}), @{$_->{packages}}) + or die "ERROR: unable to install packages from $_->{path}\n"; + } - umount_all_in_chroot($build); + print "..installing additional packages\n" if $::verbose > 1; + + # Additional rpms may have dependencies in additional media. + if (@{$build->{system}{rpms} || []}) { + my $rpm_tmp_dir = '/tmp/draklive_rpms/'; + mkdir_in_root($root, $rpm_tmp_dir); - umask $previous_umask; + my @rpm_files = map { $build->get_absolute_path($_) } @{$build->{system}{rpms}}; + copy_to_root($root, $rpm_tmp_dir, undef, @rpm_files); + + @rpm_files = map { $rpm_tmp_dir . basename($_) } @{$build->{system}{rpms}}; + run_in_root($root, $arch, 'urpmi', if_($::verbose < 3, '-q'), '--auto', '--no-verify-rpm', @rpm_files) + or die "ERROR: unable to install additional system rpms\n"; + + rm_in_root($root, $rpm_tmp_dir); + } + + # Remove any additional media. + if (@{$build->{system}{additional_media}}) { + run_in_root($root, undef, 'urpmi.removemedia', if_($::verbose < 3, '-q'),'-a'); + } + + print "..removing unwanted packages\n" if $::verbose > 1; + + # Remove any packages as requested by the user. + my @erase = @{$build->{system}{erase_rpms} || []}; + run_in_root($root, $arch, 'rpm -e', @erase) if @erase; + + print "..disabling unwanted services\n" if $::verbose > 1; + + # Disable services as requested by the user. + foreach (@{$build->{system}{disable_services}}) { + run_in_root($root, undef, 'systemctl', if_($::verbose < 3, '-q'), 'disable', "$_.service"); + } + foreach (@{$build->{system}{disable_timers}}) { + run_in_root($root, undef, 'systemctl', if_($::verbose < 3, '-q'), 'disable', "$_.timer"); + } + + print "..adjusting system files\n" if $::verbose > 1; + + # Make sure harddrake is run: + # if previous HW config file is empty, we assume DrakX has just completed the installation + # (do it in chroot, or else Storable from the build system may write an incompatible config file) + run_in_root($root, undef, 'perl', '-MStorable', '-e', qq(Storable::store({ UNKNOWN => {} }, '/etc/sysconfig/harddrake2/previous_hw'))); + + # Remove some build-machine specific configuration. + foreach (qw(/etc/shorewall/interfaces /etc/mdadm.conf)) { + clean_system_conf_file($root . $_); + } + + # Create fstab. + my $mount_options = $build->get_media_setting('mount_options') || 'defaults'; + my $fstab_entry; + if ($build->{mount}{overlay}) { + $fstab_entry = "none / $build->{mount}{overlay} $mount_options 0 0"; + } else { + $fstab_entry = $build->get_media_setting('source') . " / " . $build->get_media_setting('fs') . " $mount_options 1 1"; + } + output_to_root($root, '/etc/fstab', 0644, $fstab_entry); + + # Interactive mode can lead to race in initscripts. + run_as_root('sed', '-i', 's/^PROMPT=.*/PROMPT=no/', $root . '/etc/sysconfig/init'); + + configure_draklive_resize($build); + + print "..copying additional files\n" if $::verbose > 1; + + # Copy extra files as requested by the user. + foreach (@{$build->{system}{files}}) { + my ($source, $dest, $o_opts) = @$_; + mkdir_in_root($root, $dest =~ m|/$| ? $dest : dirname($dest)); + my @sources = glob__($build->get_absolute_path($source)); + my $mode = $o_opts && $o_opts->{mode}; + copy_to_root($root, $dest, $mode, @sources); + } + my @no_install_files = map { $_->[1] } grep { $_->[2] && $_->[2]{no_install} } @{$build->{system}{files}}; + my $installer_file = '/etc/draklive-install.d/remove.d/draklive'; + mkdir_in_root($root, dirname($installer_file)); + output_to_root($root, $installer_file, undef, @no_install_files); + + print "..removing unwanted files\n" if $::verbose > 1; + + # Remove any files as requested by the user. + my @remove = @{$build->{system}{remove_files} || []}; + rm_in_root($root, @remove) if @remove; + + print "..applying patches\n" if $::verbose > 1; + + # Apply patches as requested by the user. + foreach (@{$build->{system}{patches}}) { + my $patch_file = $build->get_absolute_path($_); + my @patch_cmd = ( 'patch', '-p0', '-d', $root, '-i', $patch_file, if_($::verbose < 3, '-s') ); + run_as_root(join(' ', @patch_cmd, '--dry-run -f -R > /dev/null')) || run_as_root(@patch_cmd) + or die "ERROR: unable to apply patch $patch_file\n"; + } + + print "..performing final fixes\n" if $::verbose > 1; + + # Perform any final fixes as requested by the user. + if ($build->{system}{final_fixes}) { + run_in_root($root, $arch, 'bash', '-c', $build->{system}{final_fixes}); + } + + # Final cleanup. + clean_system_conf_file($root . '/etc/resolv.conf'); + + write_dist_lists($build); + } catch { + $error_message = $_; + } finally { + umount_all_in_root($root); + umask $previous_umask; + }; + defined $error_message && die $error_message; } sub configure_draklive_resize { @@ -202,46 +521,23 @@ sub configure_draklive_resize { my $resizable_loopback = find { $_->{min_size} } @{$build->{mount}{dirs} || []}; if ($resizable_loopback) { my $ext = $loop_types{$resizable_loopback->{type}}{extension}; - output($build->get_system_root . '/etc/sysconfig/draklive-resize', <{path}$ext -TYPE=$resizable_loopback->{fs} -MIN_SIZE=$resizable_loopback->{min_size} -MOUNT=/live$resizable_loopback->{mountpoint}_resized -OLD_MOUNT=/live$resizable_loopback->{mountpoint} -UNION=/ -EOF + my @text = ( + "DRAKLIVE_RESIZE=yes", + "LOOPBACK=/live/media/loopbacks$resizable_loopback->{path}$ext", + "TYPE=$resizable_loopback->{fs}", + "MIN_SIZE=$resizable_loopback->{min_size}", + "MOUNT=/live$resizable_loopback->{mountpoint}_resized", + "OLD_MOUNT=/live$resizable_loopback->{mountpoint}", + "UNION=/", + ); + output_to_root($build->get_system_root, '/etc/sysconfig/draklive-resize', undef, @text); } } -sub copy_files_to { - my ($build, $files, $root) = @_; - foreach (@$files) { - my ($source, $dest, $o_opts) = @$_; - $dest = $root . '/' . $dest; - mkdir_p($dest =~ m|/$| ? $dest : dirname($dest)); - my @sources = MGA::DrakISO::Utils::glob__($build->{settings}{config_root} . '/' . $source); - print "copying @sources to $dest\n" if $::verbose > 1; - cp_af(@sources, $dest); - my $o_perm = $o_opts && $o_opts->{mode}; - chmod $o_perm, $dest if $o_perm; - } -} - -sub remove_files_from { - my ($files, $root) = @_; - run_('find', $root, '(', join_lists('-o', map { [ '-name', $_ ] } @$files), ')', '-exec', 'rm', '-r', '{}', ';') - if $files && @$files; -} - -sub join_lists { - my ($separator, $head, @lists) = @_; - @{$head || []}, map { $separator, @$_ } @lists; -} - sub clean_system_conf_file { - my ($build, $file) = @_; - substInFile { undef $_ if /^[^#]/ } $build->get_system_root . $file; + my ($file) = @_; + + run_as_root('sed', '-i', '/^[^#]/d', $file) if -f $file; } sub write_dist_lists { @@ -250,19 +546,19 @@ sub write_dist_lists { my $lists_dir = $build->get_builddir('dist'); mkdir_p($lists_dir); - run_("chroot " . $build->get_system_root . " rpm -qa | sort > " . - $lists_dir . '/' . $build->get_name . '.lst'); + my $root = $build->get_system_root; - run_("chroot " . $build->get_system_root . " rpm -qa --qf '%{name}\n' | sort > " . - $lists_dir . '/' . $build->get_name . '.lst.names'); + run_in_root($root, undef, "rpm -qa | sort > " . + $lists_dir . '/' . $build->get_name . '.lst'); - run_("chroot " . $build->get_system_root . - qq( sh -c "rpm -qa --qf '[%{NAME} %{FILESIZES} %{FILESTATES}\n]' | awk '{if(\\\$3==0) {s[\\\$1]+=\\\$2}} END{for (p in s){print s[p],p}}' | sort -n" > ) . - $lists_dir . '/' . $build->get_name . '.lst.full'); + run_in_root($root, undef, "rpm -qa --qf '%{name}\n' | sort > " . + $lists_dir . '/' . $build->get_name . '.lst.names'); - run_("chroot " . $build->get_system_root . - qq( sh -c "urpmi_rpm-find-leaves | xargs rpm -q --qf '[%{NAME} %{FILESIZES} %{FILESTATES}\n]' | awk '{if(\\\$3==0) {s[\\\$1]+=\\\$2}} END{for (p in s){print s[p],p}}' | sort -n" > ) . - $lists_dir . '/' . $build->get_name . '.lst.leaves'); + run_in_root($root, undef, qq( sh -c "rpm -qa --qf '[%{NAME} %{FILESIZES} %{FILESTATES}\n]' | awk '{if(\\\$3==0) {s[\\\$1]+=\\\$2}} END{for (p in s){print s[p],p}}' | sort -n" > ) . + $lists_dir . '/' . $build->get_name . '.lst.full'); + + run_in_root($root, undef, qq( sh -c "urpmi_rpm-find-leaves | xargs rpm -q --qf '[%{NAME} %{FILESIZES} %{FILESTATES}\n]' | awk '{if(\\\$3==0) {s[\\\$1]+=\\\$2}} END{for (p in s){print s[p],p}}' | sort -n" > ) . + $lists_dir . '/' . $build->get_name . '.lst.leaves'); require lang; my @live_langs = $build->get_langs; @@ -271,4 +567,45 @@ sub write_dist_lists { output_p($langs_file, map { lang::l2name($_) . " (" . $_ . ")\n" } sort(@langs)); } +############################################################################### +# Helper Functions +############################################################################### + +sub mkdir_in_root { + my ($root, $dir) = @_; + + run_as_root('mkdir', '-p', $root . $dir) + or die "ERROR: failed to make directory $dir in Live system root\n"; +} + +sub copy_to_root { + my ($root, $dest, $mode, @files) = @_; + + run_as_root('cp', '-af', @files, $root . $dest) + or die "ERROR: failed to copy file to $dest in Live system root\n"; + + return if !defined $mode; + + run_as_root('chmod', sprintf("%o", $mode), $root . $dest) + or die "ERROR: failed to change mode of $dest in Live system root\n"; +} + +sub output_to_root { + my ($root, $file, $mode, @text) = @_; + + my $temp_file = tmpnam(); + output($temp_file, map { "$_\n" } @text); + chmod($mode, $temp_file) if $mode; + + run_as_root('mv', $temp_file, $root . $file) + or die "ERROR: failed to write $file in Live system root\n"; +} + +sub rm_in_root { + my ($root, @targets) = @_; + + run_as_root('rm', '-rf', map { $root . $_ } @targets) + or die "ERROR: failed to remove files in Live system root\n"; +} + 1; diff --git a/lib/MGA/DrakISO/Config.pm b/lib/MGA/DrakISO/Config.pm index d88df90..a3d0511 100644 --- a/lib/MGA/DrakISO/Config.pm +++ b/lib/MGA/DrakISO/Config.pm @@ -74,8 +74,7 @@ sub complete_config { $build->{settings}{arch} ||= chomp_(`rpm --eval '%{_target_cpu}'`); mkdir_p($build->get_builddir); - mkdir_p($build->get_system_root); - $build->{mnt} ||= $build->get_builddir('/mnt'); + mkdir_p($build->get_chroot_dir); } sub dump_config { diff --git a/lib/MGA/DrakISO/ISOBuild.pm b/lib/MGA/DrakISO/ISOBuild.pm index edc91c1..e6c2aef 100644 --- a/lib/MGA/DrakISO/ISOBuild.pm +++ b/lib/MGA/DrakISO/ISOBuild.pm @@ -54,7 +54,7 @@ sub get_builddir { $build->{settings}{workdir} . '/build/' . $build->get_name . $build->get_set_suffix . if_($o_subdir, '/' . $o_subdir); } -sub get_system_root { +sub get_chroot_dir { my ($build) = @_; $build->{settings}{workdir} . '/chroot/' . $build->get_name . $build->get_set_suffix; } diff --git a/lib/MGA/DrakISO/LiveBuild.pm b/lib/MGA/DrakISO/LiveBuild.pm index 8b61601..c41618b 100644 --- a/lib/MGA/DrakISO/LiveBuild.pm +++ b/lib/MGA/DrakISO/LiveBuild.pm @@ -55,6 +55,11 @@ sub get_langs { ); } +sub get_system_root { + my ($build) = @_; + $build->get_builddir('root'); +} + sub find_kernel { my ($build) = @_; require bootloader; diff --git a/lib/MGA/DrakISO/Loopback.pm b/lib/MGA/DrakISO/Loopback.pm index e06d2a4..219b9b8 100644 --- a/lib/MGA/DrakISO/Loopback.pm +++ b/lib/MGA/DrakISO/Loopback.pm @@ -55,14 +55,13 @@ our %loop_types; my $root = $dir->{root} || $build->get_system_root; my $src = $root . $dir->{build_from}; my $total = directory_usage($src); - print "have to process " . int($total/1000000) . " MB\n"; + print "Have to process " . int($total/1000000) . " MB\n" if $::verbose; my $exclude_file = tmpnam(); output_p($exclude_file, map { $root . "$_\n" } grep { -e $root . $_ } @{$dir->{exclude} || []}); my $sort = $build->{settings}{config_root} . '/' . $dir->{sort}; my $squashfs4_comp = best_squashfs4_compression($build); - run_($squashfs4_comp ? 'mksquashfs' : 'mksquashfs3', - # unless/until we get a quiet option - if_($::verbose < 2, '>', '/dev/null'), + run_as_root(join(' ', + $squashfs4_comp ? 'mksquashfs' : 'mksquashfs3', $src, $dest, $squashfs4_comp ? ('-comp', $squashfs4_comp) : '-lzma', '-noappend', '-b', '1048576', @@ -71,7 +70,9 @@ our %loop_types; if_(-f $sort, '-sort', $sort), if_($::verbose > 2, '-info', '-progress'), if_($::verbose < 2, '-no-progress'), - ) or die "unable to run mksquashfs\n"; + # due to lack of a -quiet option + if_($::verbose < 2, '> /dev/null'), + )) or die "ERROR: unable to run mksquashfs\n"; unlink $exclude_file; }, mount => sub { diff --git a/lib/MGA/DrakISO/Utils.pm b/lib/MGA/DrakISO/Utils.pm index 85a9892..4e85207 100644 --- a/lib/MGA/DrakISO/Utils.pm +++ b/lib/MGA/DrakISO/Utils.pm @@ -34,12 +34,14 @@ use IO::Select; use Exporter; our @ISA = qw(Exporter); -our @EXPORT = qw(directory_usage run_ copy_or_link mount_system_fs umount_all_in_chroot); +our @EXPORT = qw($sudo directory_usage glob__ run_ run_as_root run_in_root copy_or_link mount mount_system_fs umount_all_in_root); + +our $sudo = $> ? 'sudo' : ''; sub directory_usage { my ($dir, $o_apparent) = @_; my $apparent = $o_apparent && "--apparent-size"; - first(split /\s/, `du -s -B 1 $apparent $dir`); + first(split /\s/, `$sudo du -s -B 1 $apparent $dir`); } #- expand only if the pattern contains '*' @@ -61,6 +63,34 @@ sub run_ { run_program::raw($options, @cmd); } +sub run_as_root { + my (@cmd) = @_; + + if (@cmd > 1) { + unshift @cmd, $sudo if $sudo; + } else { + @cmd[0] = join(' ', $sudo, @cmd[0]); + } + print "@cmd\n" if $::verbose > 2; + system(@cmd) == 0; +} + +sub run_in_root { + my ($root, $arch, @cmd) = @_; + + if (@cmd > 1) { + unshift @cmd, ( 'chroot', $root ); + unshift @cmd, ( 'setarch', $arch ) if $arch; + unshift @cmd, $sudo if $sudo; + } else { + @cmd[0] = join(' ', $sudo, if_($arch, 'setarch', $arch), 'chroot', $root, @cmd[0]); + } + print "@cmd\n" if $::verbose > 2; + local %ENV = %ENV; + $ENV{LC_ALL} = 'C'; + system(@cmd) == 0; +} + sub device_allocate_file { my ($device, $size) = @_; run_('dd', "of=$device", 'count=0', 'bs=1', "seek=" . removeXiBSuffix($size)); @@ -93,20 +123,28 @@ sub copy_or_link { or die "ERROR: couldn't link $src_file to $dst_file\n"; } +sub mount { + my ($dst, $src, $o_options) = @_; + mkdir_p($dst); + system(join(' ', 'sudo mount', $o_options, $src, $dst)) == 0 + or die "ERROR: failed to mount $src on $dst\n"; +} + sub mount_system_fs { - my ($build) = @_; - run_('mount', '-t', 'devtmpfs', '/dev', $build->get_system_root . '/dev'); - run_('mount', '-t', 'proc', '/proc', $build->get_system_root . '/proc'); - run_('mount', '-t', 'sysfs', '/sys', $build->get_system_root . '/sys'); + my ($root) = @_; + mount($root . '/dev', '/dev', '--bind -o ro'); + mount($root . '/proc', 'none', '-t proc'); + mount($root . '/sys', 'none', '-t sysfs'); + mount($root . '/run', 'none', '-t tmpfs'); } -sub umount_all_in_chroot { - my ($build) = @_; - my $system_root = Cwd::abs_path($build->get_system_root); - my @mounts = grep { $_ =~ $system_root } split("\n", cat_('/proc/mounts')); +sub umount_all_in_root { + my ($root) = @_; + $root = Cwd::abs_path($root); + my @mounts = grep { $_ =~ $root } split("\n", cat_('/proc/mounts')); foreach (reverse(@mounts)) { my @field = split(' ' , $_); - fs::mount::umount($field[1]); + system(join(' ', 'sudo umount', $field[1])); } } -- cgit v1.2.1