about summary refs log tree commit diff
path: root/nixos/modules/system/boot/loader/grub
diff options
context:
space:
mode:
authorEelco Dolstra <eelco.dolstra@logicblox.com>2013-10-10 13:28:20 +0200
committerEelco Dolstra <eelco.dolstra@logicblox.com>2013-10-10 13:28:20 +0200
commit5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010 (patch)
treea6c0f605be6de3f372ae69905b331f9f75452da7 /nixos/modules/system/boot/loader/grub
parent6070bc016bd2fd945b04347e25cfd3738622d2ac (diff)
downloadnixlib-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar
nixlib-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.gz
nixlib-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.bz2
nixlib-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.lz
nixlib-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.xz
nixlib-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.zst
nixlib-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.zip
Move all of NixOS to nixos/ in preparation of the repository merge
Diffstat (limited to 'nixos/modules/system/boot/loader/grub')
-rw-r--r--nixos/modules/system/boot/loader/grub/grub.nix261
-rw-r--r--nixos/modules/system/boot/loader/grub/install-grub.pl265
-rw-r--r--nixos/modules/system/boot/loader/grub/memtest.nix39
-rw-r--r--nixos/modules/system/boot/loader/grub/winkler-gnu-blue-640x480.pngbin0 -> 74487 bytes
-rw-r--r--nixos/modules/system/boot/loader/grub/winkler-gnu-blue.README6
5 files changed, 571 insertions, 0 deletions
diff --git a/nixos/modules/system/boot/loader/grub/grub.nix b/nixos/modules/system/boot/loader/grub/grub.nix
new file mode 100644
index 000000000000..8e9f3253f877
--- /dev/null
+++ b/nixos/modules/system/boot/loader/grub/grub.nix
@@ -0,0 +1,261 @@
+{ config, pkgs, ... }:
+
+with pkgs.lib;
+
+let
+
+  cfg = config.boot.loader.grub;
+
+  realGrub = if cfg.version == 1 then pkgs.grub else pkgs.grub2;
+
+  grub =
+    # Don't include GRUB if we're only generating a GRUB menu (e.g.,
+    # in EC2 instances).
+    if cfg.devices == ["nodev"]
+    then null
+    else realGrub;
+
+  f = x: if x == null then "" else "" + x;
+
+  grubConfig = pkgs.writeText "grub-config.xml" (builtins.toXML
+    { splashImage = f config.boot.loader.grub.splashImage;
+      grub = f grub;
+      shell = "${pkgs.stdenv.shell}";
+      fullVersion = (builtins.parseDrvName realGrub.name).version;
+      inherit (cfg)
+        version extraConfig extraPerEntryConfig extraEntries
+        extraEntriesBeforeNixOS extraPrepareConfig configurationLimit copyKernels timeout
+        default devices;
+      path = (makeSearchPath "bin" [
+        pkgs.coreutils pkgs.gnused pkgs.gnugrep pkgs.findutils pkgs.diffutils
+      ]) + ":" + (makeSearchPath "sbin" [
+        pkgs.mdadm
+      ]);
+    });
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    boot.loader.grub = {
+
+      enable = mkOption {
+        default = true;
+        type = types.bool;
+        description = ''
+          Whether to enable the GNU GRUB boot loader.
+        '';
+      };
+
+      version = mkOption {
+        default = 2;
+        example = 1;
+        type = types.int;
+        description = ''
+          The version of GRUB to use: <literal>1</literal> for GRUB
+          Legacy (versions 0.9x), or <literal>2</literal> (the
+          default) for GRUB 2.
+        '';
+      };
+
+      device = mkOption {
+        default = "";
+        example = "/dev/hda";
+        type = types.uniq types.string;
+        description = ''
+          The device on which the GRUB boot loader will be installed.
+          The special value <literal>nodev</literal> means that a GRUB
+          boot menu will be generated, but GRUB itself will not
+          actually be installed.  To install GRUB on multiple devices,
+          use <literal>boot.loader.grub.devices</literal>.
+        '';
+      };
+
+      devices = mkOption {
+        default = [];
+        example = [ "/dev/hda" ];
+        type = types.listOf types.string;
+        description = ''
+          The devices on which the boot loader, GRUB, will be
+          installed. Can be used instead of <literal>device</literal> to
+          install grub into multiple devices (e.g., if as softraid arrays holding /boot).
+        '';
+      };
+
+      # !!! How can we mark options as obsolete?
+      bootDevice = mkOption {
+        default = "";
+        description = "Obsolete.";
+      };
+
+      configurationName = mkOption {
+        default = "";
+        example = "Stable 2.6.21";
+        type = types.uniq types.string;
+        description = ''
+          GRUB entry name instead of default.
+        '';
+      };
+
+      extraPrepareConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Additional bash commands to be run at the script that
+          prepares the grub menu entries.
+        '';
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        example = "serial; terminal_output.serial";
+        type = types.lines;
+        description = ''
+          Additional GRUB commands inserted in the configuration file
+          just before the menu entries.
+        '';
+      };
+
+      extraPerEntryConfig = mkOption {
+        default = "";
+        example = "root (hd0)";
+        type = types.lines;
+        description = ''
+          Additional GRUB commands inserted in the configuration file
+          at the start of each NixOS menu entry.
+        '';
+      };
+
+      extraEntries = mkOption {
+        default = "";
+        type = types.lines;
+        example = ''
+          # GRUB 1 example (not GRUB 2 compatible)
+          title Windows
+            chainloader (hd0,1)+1
+
+          # GRUB 2 example
+          menuentry "Windows7" {
+            title Windows7
+            insmod ntfs
+            set root='(hd1,1)'
+            chainloader +1
+          }
+        '';
+        description = ''
+          Any additional entries you want added to the GRUB boot menu.
+        '';
+      };
+
+      extraEntriesBeforeNixOS = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether extraEntries are included before the default option.
+        '';
+      };
+
+      extraFiles = mkOption {
+        default = {};
+        example = literalExample ''
+          { "memtest.bin" = "${pkgs.memtest86plus}/memtest.bin"; }
+        '';
+        description = ''
+          A set of files to be copied to <filename>/boot</filename>.
+          Each attribute name denotes the destination file name in
+          <filename>/boot</filename>, while the corresponding
+          attribute value specifies the source file.
+        '';
+      };
+
+      splashImage = mkOption {
+        default =
+          if cfg.version == 1
+          then pkgs.fetchurl {
+            url = http://www.gnome-look.org/CONTENT/content-files/36909-soft-tux.xpm.gz;
+            sha256 = "14kqdx2lfqvh40h6fjjzqgff1mwk74dmbjvmqphi6azzra7z8d59";
+          }
+          # GRUB 1.97 doesn't support gzipped XPMs.
+          else ./winkler-gnu-blue-640x480.png;
+        example = null;
+        description = ''
+          Background image used for GRUB.  It must be a 640x480,
+          14-colour image in XPM format, optionally compressed with
+          <command>gzip</command> or <command>bzip2</command>.  Set to
+          <literal>null</literal> to run GRUB in text mode.
+        '';
+      };
+
+      configurationLimit = mkOption {
+        default = 100;
+        example = 120;
+        type = types.int;
+        description = ''
+          Maximum of configurations in boot menu. GRUB has problems when
+          there are too many entries.
+        '';
+      };
+
+      copyKernels = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether the GRUB menu builder should copy kernels and initial
+          ramdisks to /boot.  This is done automatically if /boot is
+          on a different partition than /.
+        '';
+      };
+
+      timeout = mkOption {
+        default = 5;
+        type = types.int;
+        description = ''
+          Timeout (in seconds) until GRUB boots the default menu item.
+        '';
+      };
+
+      default = mkOption {
+        default = 0;
+        type = types.int;
+        description = ''
+          Index of the default menu item to be booted.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    boot.loader.grub.devices = optional (cfg.device != "") cfg.device;
+
+    system.build = mkAssert (cfg.devices != [])
+      "You must set the ‘boot.loader.grub.device’ option to make the system bootable."
+      { installBootLoader =
+          "PERL5LIB=${makePerlPath [ pkgs.perlPackages.XMLLibXML pkgs.perlPackages.XMLSAX ]} " +
+          "${pkgs.perl}/bin/perl ${./install-grub.pl} ${grubConfig}";
+        inherit grub;
+      };
+
+    # Common attribute for boot loaders so only one of them can be
+    # set at once.
+    system.boot.loader.id = "grub";
+
+    environment.systemPackages = [ grub ];
+
+    boot.loader.grub.extraPrepareConfig =
+      concatStrings (mapAttrsToList (n: v: ''
+        ${pkgs.coreutils}/bin/cp -pf "${v}" "/boot/${n}"
+      '') config.boot.loader.grub.extraFiles);
+
+  };
+
+}
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"));
+}
diff --git a/nixos/modules/system/boot/loader/grub/memtest.nix b/nixos/modules/system/boot/loader/grub/memtest.nix
new file mode 100644
index 000000000000..a0726c01e204
--- /dev/null
+++ b/nixos/modules/system/boot/loader/grub/memtest.nix
@@ -0,0 +1,39 @@
+# This module adds Memtest86+ to the GRUB boot menu.
+
+{ config, pkgs, ... }:
+
+with pkgs.lib;
+
+let
+  memtest86 = pkgs.memtest86plus;
+in
+
+{
+  options = {
+
+    boot.loader.grub.memtest86 = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Make Memtest86+, a memory testing program, available from the
+        GRUB boot menu.
+      '';
+    };
+  };
+
+  config = mkIf config.boot.loader.grub.memtest86 {
+
+    boot.loader.grub.extraEntries = mkFixStrictness (
+      if config.boot.loader.grub.version == 2 then
+        ''
+          menuentry "Memtest86+" {
+            linux16 @bootRoot@/memtest.bin
+          }
+        ''
+      else
+        throw "Memtest86+ is not supported with GRUB 1.");
+
+    boot.loader.grub.extraFiles."memtest.bin" = "${memtest86}/memtest.bin";
+
+  };
+}
diff --git a/nixos/modules/system/boot/loader/grub/winkler-gnu-blue-640x480.png b/nixos/modules/system/boot/loader/grub/winkler-gnu-blue-640x480.png
new file mode 100644
index 000000000000..35bbb57b51ee
--- /dev/null
+++ b/nixos/modules/system/boot/loader/grub/winkler-gnu-blue-640x480.png
Binary files differdiff --git a/nixos/modules/system/boot/loader/grub/winkler-gnu-blue.README b/nixos/modules/system/boot/loader/grub/winkler-gnu-blue.README
new file mode 100644
index 000000000000..9616362dce2a
--- /dev/null
+++ b/nixos/modules/system/boot/loader/grub/winkler-gnu-blue.README
@@ -0,0 +1,6 @@
+This is a resized version of
+
+  http://www.gnu.org/graphics/winkler-gnu-blue.png
+
+by Kyle Winkler and released under the Free Art License
+(http://artlibre.org/licence.php/lalgb.html).