243 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
			
		
		
	
	
			243 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
use strict;
 | 
						||
use warnings;
 | 
						||
use XML::LibXML;
 | 
						||
use File::Basename;
 | 
						||
use File::Path;
 | 
						||
use File::stat;
 | 
						||
use File::Copy;
 | 
						||
use POSIX;
 | 
						||
use Cwd;
 | 
						||
 | 
						||
my $defaultConfig = $ARGV[1] or die;
 | 
						||
 | 
						||
my $dom = XML::LibXML->load_xml(location => $ARGV[0]);
 | 
						||
 | 
						||
sub get { my ($name) = @_; return $dom->findvalue("/expr/attrs/attr[\@name = '$name']/*/\@value"); }
 | 
						||
 | 
						||
sub readFile {
 | 
						||
    my ($fn) = @_; local $/ = undef;
 | 
						||
    open FILE, "<$fn" or return undef; my $s = <FILE>; close FILE;
 | 
						||
    local $/ = "\n"; chomp $s; return $s;
 | 
						||
}
 | 
						||
 | 
						||
sub writeFile {
 | 
						||
    my ($fn, $s) = @_;
 | 
						||
    open FILE, ">$fn" or die "cannot create $fn: $!\n";
 | 
						||
    print FILE $s or die;
 | 
						||
    close FILE or die;
 | 
						||
}
 | 
						||
 | 
						||
my $grub = get("grub");
 | 
						||
my $grubVersion = int(get("version"));
 | 
						||
my $extraConfig = get("extraConfig");
 | 
						||
my $extraPerEntryConfig = get("extraPerEntryConfig");
 | 
						||
my $extraEntries = get("extraEntries");
 | 
						||
my $extraEntriesBeforeNixOS = get("extraEntriesBeforeNixOS") eq "true";
 | 
						||
my $splashImage = get("splashImage");
 | 
						||
my $configurationLimit = int(get("configurationLimit"));
 | 
						||
my $copyKernels = get("copyKernels") eq "true";
 | 
						||
my $timeout = int(get("timeout"));
 | 
						||
my $defaultEntry = int(get("default"));
 | 
						||
 | 
						||
die "unsupported GRUB version\n" if $grubVersion != 1 && $grubVersion != 2;
 | 
						||
 | 
						||
print STDERR "updating GRUB $grubVersion menu...\n";
 | 
						||
 | 
						||
mkpath("/boot/grub", 0, 0700);
 | 
						||
 | 
						||
 | 
						||
# Discover whether /boot is on the same filesystem as / and
 | 
						||
# /nix/store.  If not, then all kernels and initrds must be copied to
 | 
						||
# /boot, and all paths in the GRUB config file must be relative to the
 | 
						||
# root of the /boot filesystem.  `$bootRoot' is the path to be
 | 
						||
# prepended to paths under /boot.
 | 
						||
my $bootRoot = "/boot";
 | 
						||
if (stat("/")->dev != stat("/boot")->dev) {
 | 
						||
    $bootRoot = "";
 | 
						||
    $copyKernels = 1;
 | 
						||
} elsif (stat("/boot")->dev != stat("/nix/store")->dev) {
 | 
						||
    $copyKernels = 1;
 | 
						||
}
 | 
						||
 | 
						||
 | 
						||
# Generate the header.
 | 
						||
my $conf .= "# Automatically generated.  DO NOT EDIT THIS FILE!\n";
 | 
						||
 | 
						||
if ($grubVersion == 1) {
 | 
						||
    $conf .= "
 | 
						||
        default $defaultEntry
 | 
						||
        timeout $timeout
 | 
						||
    ";
 | 
						||
    if ($splashImage) {
 | 
						||
        copy $splashImage, "/boot/background.xpm.gz" or die "cannot copy $splashImage to /boot\n";
 | 
						||
        $conf .= "splashimage $bootRoot/background.xpm.gz\n";
 | 
						||
    }
 | 
						||
}
 | 
						||
 | 
						||
else {
 | 
						||
    $conf .= "
 | 
						||
        if [ -s \$prefix/grubenv ]; then
 | 
						||
          load_env
 | 
						||
        fi
 | 
						||
 | 
						||
        # ‘grub-reboot’ sets a one-time saved entry, which we process here and
 | 
						||
        # then delete.
 | 
						||
        if [ \"\${saved_entry}\" ]; then
 | 
						||
          # The next line *has* to look exactly like this, otherwise KDM's
 | 
						||
          # reboot feature won't work properly with GRUB 2.
 | 
						||
          set default=\"\${saved_entry}\"
 | 
						||
          set saved_entry=
 | 
						||
          set prev_saved_entry=
 | 
						||
          save_env saved_entry
 | 
						||
          save_env prev_saved_entry
 | 
						||
          set timeout=1
 | 
						||
        else
 | 
						||
          set default=$defaultEntry
 | 
						||
          set timeout=$timeout
 | 
						||
        fi
 | 
						||
 | 
						||
        if loadfont $bootRoot/grub/fonts/unicode.pf2; then
 | 
						||
          set gfxmode=640x480
 | 
						||
          insmod gfxterm
 | 
						||
          insmod vbe
 | 
						||
          terminal_output gfxterm
 | 
						||
        fi
 | 
						||
    ";
 | 
						||
 | 
						||
    if ($splashImage) {
 | 
						||
        # FIXME: GRUB 1.97 doesn't resize the background image if it
 | 
						||
        # doesn't match the video resolution.
 | 
						||
        copy $splashImage, "/boot/background.png" or die "cannot copy $splashImage to /boot\n";
 | 
						||
        $conf .= "
 | 
						||
            insmod png
 | 
						||
            if background_image $bootRoot/background.png; then
 | 
						||
              set color_normal=white/black
 | 
						||
              set color_highlight=black/white
 | 
						||
            else
 | 
						||
              set menu_color_normal=cyan/blue
 | 
						||
              set menu_color_highlight=white/blue
 | 
						||
            fi
 | 
						||
        ";
 | 
						||
    }
 | 
						||
}
 | 
						||
 | 
						||
$conf .= "$extraConfig\n";
 | 
						||
 | 
						||
 | 
						||
# Generate the menu entries.
 | 
						||
$conf .= "\n";
 | 
						||
 | 
						||
my %copied;
 | 
						||
mkpath("/boot/kernels", 0, 0755) if $copyKernels;
 | 
						||
 | 
						||
sub copyToKernelsDir {
 | 
						||
    my ($path) = @_;
 | 
						||
    return $path unless $copyKernels;
 | 
						||
    $path =~ /\/nix\/store\/(.*)/ or die;
 | 
						||
    my $name = $1; $name =~ s/\//-/g;
 | 
						||
    my $dst = "/boot/kernels/$name";
 | 
						||
    # Don't copy the file if $dst already exists.  This means that we
 | 
						||
    # have to create $dst atomically to prevent partially copied
 | 
						||
    # kernels or initrd if this script is ever interrupted.
 | 
						||
    if (! -e $dst) {
 | 
						||
        my $tmp = "$dst.tmp";
 | 
						||
        copy $path, $tmp or die "cannot copy $path to $tmp\n";
 | 
						||
        rename $tmp, $dst or die "cannot rename $tmp to $dst\n";
 | 
						||
    }
 | 
						||
    $copied{$dst} = 1;
 | 
						||
    return "$bootRoot/kernels/$name";
 | 
						||
}
 | 
						||
 | 
						||
sub addEntry {
 | 
						||
    my ($name, $path) = @_;
 | 
						||
    return unless -e "$path/kernel" && -e "$path/initrd";
 | 
						||
 | 
						||
    my $kernel = copyToKernelsDir(Cwd::abs_path("$path/kernel"));
 | 
						||
    my $initrd = copyToKernelsDir(Cwd::abs_path("$path/initrd"));
 | 
						||
    my $xen = -e "$path/xen.gz" ? copyToKernelsDir(Cwd::abs_path("$path/xen.gz")) : undef;
 | 
						||
 | 
						||
    # FIXME: $confName
 | 
						||
 | 
						||
    my $kernelParams =
 | 
						||
        "systemConfig=" . Cwd::abs_path($path) . " " .
 | 
						||
        "init=" . Cwd::abs_path("$path/init") . " " .
 | 
						||
        readFile("$path/kernel-params");
 | 
						||
    my $xenParams = $xen && -e "$path/xen-params" ? readFile("$path/xen-params") : "";
 | 
						||
 | 
						||
    if ($grubVersion == 1) {
 | 
						||
        $conf .= "title $name\n";
 | 
						||
        $conf .= "  $extraPerEntryConfig\n" if $extraPerEntryConfig;
 | 
						||
        $conf .= "  kernel $xen $xenParams\n" if $xen;
 | 
						||
        $conf .= "  " . ($xen ? "module" : "kernel") . " $kernel $kernelParams\n";
 | 
						||
        $conf .= "  " . ($xen ? "module" : "initrd") . " $initrd\n\n";
 | 
						||
    } else {
 | 
						||
        $conf .= "menuentry \"$name\" {\n";
 | 
						||
        $conf .= "  $extraPerEntryConfig\n" if $extraPerEntryConfig;
 | 
						||
        $conf .= "  multiboot $xen $xenParams\n" if $xen;
 | 
						||
        $conf .= "  " . ($xen ? "module" : "linux") . " $kernel $kernelParams\n";
 | 
						||
        $conf .= "  " . ($xen ? "module" : "initrd") . " $initrd\n";
 | 
						||
        $conf .= "}\n\n";
 | 
						||
    }
 | 
						||
}
 | 
						||
 | 
						||
 | 
						||
# Add default entries.
 | 
						||
$conf .= "$extraEntries\n" if $extraEntriesBeforeNixOS;
 | 
						||
 | 
						||
addEntry("NixOS - Default", $defaultConfig);
 | 
						||
 | 
						||
$conf .= "$extraEntries\n" unless $extraEntriesBeforeNixOS;
 | 
						||
 | 
						||
 | 
						||
# Add entries for all previous generations of the system profile.
 | 
						||
$conf .= "submenu \"NixOS - Old configurations\" {\n" if $grubVersion == 2;
 | 
						||
 | 
						||
sub nrFromGen { my ($x) = @_; $x =~ /system-(.*)-link/; return $1; }
 | 
						||
 | 
						||
my @links = sort
 | 
						||
    { nrFromGen($b) <=> nrFromGen($a) }
 | 
						||
    (glob "/nix/var/nix/profiles/system-*-link");
 | 
						||
 | 
						||
my $curEntry = 0;
 | 
						||
foreach my $link (@links) {
 | 
						||
    last if $curEntry++ >= $configurationLimit;
 | 
						||
    my $date = strftime("%F", localtime(lstat($link)->mtime));
 | 
						||
    my $version =
 | 
						||
        -e "$link/nixos-version"
 | 
						||
        ? readFile("$link/nixos-version")
 | 
						||
        : basename((glob(dirname(Cwd::abs_path("$link/kernel")) . "/lib/modules/*"))[0]);
 | 
						||
    addEntry("NixOS - Configuration " . nrFromGen($link) . " ($date - $version)", $link);
 | 
						||
}
 | 
						||
 | 
						||
$conf .= "}\n" if $grubVersion == 2;
 | 
						||
 | 
						||
 | 
						||
# Atomically update the GRUB config.
 | 
						||
my $confFile = $grubVersion == 1 ? "/boot/grub/menu.lst" : "/boot/grub/grub.cfg";
 | 
						||
my $tmpFile = $confFile . ".tmp";
 | 
						||
writeFile($tmpFile, $conf);
 | 
						||
rename $tmpFile, $confFile or die "cannot rename $tmpFile to $confFile\n";
 | 
						||
 | 
						||
 | 
						||
# Remove obsolete files from /boot/kernels.
 | 
						||
foreach my $fn (glob "/boot/kernels/*") {
 | 
						||
    next if defined $copied{$fn};
 | 
						||
    print STDERR "removing obsolete file $fn\n";
 | 
						||
    unlink $fn;
 | 
						||
}
 | 
						||
 | 
						||
 | 
						||
# Install GRUB if the version changed from the last time we installed
 | 
						||
# it.  FIXME: shouldn't we reinstall if ‘devices’ changed?
 | 
						||
my $prevVersion = readFile("/boot/grub/version") // "";
 | 
						||
if (($ENV{'NIXOS_INSTALL_GRUB'} // "") eq "1" || get("fullVersion") ne $prevVersion) {
 | 
						||
    foreach my $dev ($dom->findnodes('/expr/attrs/attr[@name = "devices"]/list/string/@value')) {
 | 
						||
        $dev = $dev->findvalue(".") or die;
 | 
						||
        next if $dev eq "nodev";
 | 
						||
        print STDERR "installing the GRUB $grubVersion boot loader on $dev...\n";
 | 
						||
        system("$grub/sbin/grub-install", "--recheck", Cwd::abs_path($dev)) == 0
 | 
						||
            or die "$0: installation of GRUB on $dev failed\n";
 | 
						||
    }
 | 
						||
    writeFile("/boot/grub/version", get("fullVersion"));
 | 
						||
}
 |