diff --git a/modules/installer/grub/grub-menu-builder.pl b/modules/installer/grub/grub-menu-builder.pl new file mode 100644 index 00000000000..7f0dc15836b --- /dev/null +++ b/modules/installer/grub/grub-menu-builder.pl @@ -0,0 +1,217 @@ +use strict; +use warnings; +use XML::LibXML; +use File::Basename; +use File::Path; +use File::stat; +use File::Copy; +use IO::File; +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"); } + +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; + +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 { + copy "$grub/share/grub/unicode.pf2", "/boot/grub/unicode.pf2" or die "cannot copy unicode.pf2 to /boot/grub: $!\n"; + + $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/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. +my $curEntry = 0; +$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 if $curEntry++ > $configurationLimit; + 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")) : undef; + + # FIXME: $confName + + my $kernelParams = + "systemConfig=" . Cwd::abs_path($path) . " " . + "init=" . Cwd::abs_path("$path/init") . " " . + join " ", IO::File->new("$path/kernel-params")->getlines; + my $xenParams = $xen && -e "$path/xen-params" ? join " ", IO::File->new("$path/xen-params")->getlines : ""; + + 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"); + +foreach my $link (@links) { + my $date = strftime("%F", localtime(lstat($link)->mtime)); + my $version = + -e "$link/nixos-version" + ? IO::File->new("$link/nixos-version")->getline + : 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"; +open CONF, ">$tmpFile" or die "cannot open $tmpFile for writing\n"; +print CONF $conf or die; +close 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; +} diff --git a/modules/installer/grub/grub-menu-builder.sh b/modules/installer/grub/grub-menu-builder.sh deleted file mode 100644 index f0e9fdb288c..00000000000 --- a/modules/installer/grub/grub-menu-builder.sh +++ /dev/null @@ -1,305 +0,0 @@ -#! @bash@/bin/sh -e - -shopt -s nullglob - -export PATH=/empty -for i in @path@; do PATH=$PATH:$i/bin; done - -if test $# -ne 1; then - echo "Usage: grub-menu-builder.sh DEFAULT-CONFIG" - exit 1 -fi - -grubVersion="@version@" -defaultConfig="$1" - -case "$grubVersion" in - 1|2) - echo "updating GRUB $grubVersion menu..." - ;; - *) - echo "Unsupported GRUB version \`$grubVersion'" >&2 - echo "Supported versions are \`1' (GRUB Legacy) and \`2' (GRUB 1.9x)." >&2 - exit 1 - ;; -esac - - -# 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. -if [ "$(stat -c '%D' /.)" != "$(stat -c '%D' /boot/.)" ]; then - bootRoot= - copyKernels=1 -elif [ "$(stat -c '%D' /boot/.)" != "$(stat -c '%D' /nix/store/.)" ]; then - bootRoot=/boot - copyKernels=1 -else - bootRoot=/boot - copyKernels="@copyKernels@" # user can override in the NixOS config -fi - - -prologue() { - case "$grubVersion" in - 1) - cat > "$1" << GRUBEND -# Automatically generated. DO NOT EDIT THIS FILE! -default @default@ -timeout @timeout@ -GRUBEND - if [ -n "@splashImage@" ]; then - cp -f "@splashImage@" /boot/background.xpm.gz - echo "splashimage $bootRoot/background.xpm.gz" >> "$1" - fi - ;; - 2) - cp -f @grub@/share/grub/unicode.pf2 /boot/grub/unicode.pf2 - cat > "$1" <> "$1" <-/file to --. -cleanName() { - local path="$1" - echo "$path" | sed 's|^/nix/store/||' | sed 's|/|-|g' -} - - -# Copy a file from the Nix store to /boot/kernels. -declare -A filesCopied - -copyToKernelsDir() { - local src="$1" - local p="kernels/$(cleanName $src)" - local dst="/boot/$p" - # 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 ! test -e $dst; then - local dstTmp=$dst.tmp.$$ - cp "$src" "$dstTmp" - mv $dstTmp $dst - fi - filesCopied[$dst]=1 - result="$bootRoot/$p" -} - - -# Add an entry for a configuration to the Grub menu, and if -# appropriate, copy its kernel and initrd to /boot/kernels. -addEntry() { - local name="$1" - local path="$2" - local shortSuffix="$3" - - configurationCounter=$((configurationCounter + 1)) - if test $configurationCounter -gt @configurationLimit@; then - return - fi - - if ! test -e $path/kernel -a -e $path/initrd; then - return - fi - - local kernel=$(readlink -f $path/kernel) - local initrd=$(readlink -f $path/initrd) - local xen=$([ -f $path/xen.gz ] && readlink -f $path/xen.gz) - - if test "$path" = "$defaultConfig"; then - cp "$kernel" /boot/nixos-kernel - cp "$initrd" /boot/nixos-initrd - cp "$(readlink -f "$path/init")" /boot/nixos-init - case "$grubVersion" in - 1) - cat > /boot/nixos-grub-config < /boot/nixos-grub-config </dev/null || true) - if test -n "$confName"; then - name="$confName $3" - fi - - local kernelParams="systemConfig=$(readlink -f $path) init=$(readlink -f $path/init) $(cat $path/kernel-params)" - local xenParams="$([ -n "$xen" ] && cat $path/xen-params)" - - case "$grubVersion" in - 1) - cat >> "$tmp" << GRUBEND -title $name - @extraPerEntryConfig@ - ${xen:+kernel $xen $xenParams} - $(if [ -z "$xen" ]; then echo kernel; else echo module; fi) $kernel $kernelParams - $(if [ -z "$xen" ]; then echo initrd; else echo module; fi) $initrd -GRUBEND - ;; - 2) - cat >> "$tmp" << GRUBEND -menuentry "$name" { - @extraPerEntryConfig@ - ${xen:+multiboot $xen $xenParams} - $(if [ -z "$xen" ]; then echo linux; else echo module; fi) $kernel $kernelParams - $(if [ -z "$xen" ]; then echo initrd; else echo module; fi) $initrd -} -GRUBEND - ;; - esac -} - - -if test -n "$copyKernels"; then - mkdir -p /boot/kernels -fi - -@extraPrepareConfig@ - -# Additional entries specified verbatim by the configuration. -extraEntries=`cat <> $tmp <> $tmp -fi - -addEntry "NixOS - Default" $defaultConfig "" - -if test -z "@extraEntriesBeforeNixOS@"; then - echo "$extraEntries" >> $tmp -fi - -# Add all generations of the system profile to the menu, in reverse -# (most recent to least recent) order. -for link in $((ls -d $defaultConfig/fine-tune/* ) | sort -n); do - date=$(stat --printf="%y\n" $link | sed 's/\..*//') - addEntry "NixOS - variation" $link "" -done - -if [ "$grubVersion" = 2 ]; then - cat >> $tmp <> $tmp <