diff options
Diffstat (limited to 'nixos/modules/system/boot/loader/grub/install-grub.pl')
-rw-r--r-- | nixos/modules/system/boot/loader/grub/install-grub.pl | 265 |
1 files changed, 265 insertions, 0 deletions
diff --git a/nixos/modules/system/boot/loader/grub/install-grub.pl b/nixos/modules/system/boot/loader/grub/install-grub.pl new file mode 100644 index 000000000000..a83733db63b0 --- /dev/null +++ b/nixos/modules/system/boot/loader/grub/install-grub.pl @@ -0,0 +1,265 @@ +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 $extraPrepareConfig = get("extraPrepareConfig"); +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")); +$ENV{'PATH'} = get("path"); + +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; + +# extraEntries could refer to @bootRoot@, which we have to substitute +$conf =~ s/\@bootRoot\@/$bootRoot/g; + +# Emit submenus for all system profiles. +sub addProfile { + my ($profile, $description) = @_; + + # Add entries for all generations of this profile. + $conf .= "submenu \"$description\" {\n" if $grubVersion == 2; + + sub nrFromGen { my ($x) = @_; $x =~ /\/\w+-(\d+)-link/; return $1; } + + my @links = sort + { nrFromGen($b) <=> nrFromGen($a) } + (glob "$profile-*-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; +} + +addProfile "/nix/var/nix/profiles/system", "NixOS - All configurations"; + +if ($grubVersion == 2) { + for my $profile (glob "/nix/var/nix/profiles/system-profiles/*") { + my $name = basename($profile); + next unless $name =~ /^\w+$/; + addProfile $profile, "NixOS - Profile '$name'"; + } +} + +# Run extraPrepareConfig in sh +if ($extraPrepareConfig ne "") { + system((get("shell"), "-c", $extraPrepareConfig)); +} + +# 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")); +} |