Add gummiboot module.

We should probably eventually get rid of the old direct boot stub approach.
wip/yesman
Shea Levy 11 years ago
parent 3c2f45695f
commit 02e209b139
  1. 2
      modules/module-list.nix
  2. 7
      modules/rename.nix
  3. 51
      modules/system/boot/loader/efi-boot-stub/efi-boot-stub.nix
  4. 53
      modules/system/boot/loader/efi.nix
  5. 144
      modules/system/boot/loader/gummiboot/gummiboot-builder.py
  6. 71
      modules/system/boot/loader/gummiboot/gummiboot.nix

@ -199,7 +199,9 @@
./system/activation/top-level.nix
./system/boot/kernel.nix
./system/boot/loader/efi-boot-stub/efi-boot-stub.nix
./system/boot/loader/efi.nix
./system/boot/loader/generations-dir/generations-dir.nix
./system/boot/loader/gummiboot/gummiboot.nix
./system/boot/loader/raspberrypi/raspberrypi.nix
./system/boot/loader/grub/grub.nix
./system/boot/loader/grub/memtest.nix

@ -99,4 +99,11 @@ in zipModules ([]
++ rename deprecated "kde.extraPackages" "environment.kdePackages"
# ++ rename obsolete "environment.kdePackages" "environment.systemPackages" # !!! doesn't work!
# Multiple efi bootloaders now
++ rename obsolete "boot.loader.efiBootStub.efiSysMountPoint" "boot.loader.efi.efiSysMountPoint"
++ rename obsolete "boot.loader.efiBootStub.efiDisk" "boot.loader.efi.efibootmgr.efiDisk"
++ rename obsolete "boot.loader.efiBootStub.efiPartition" "boot.loader.efi.efibootmgr.efiPartition"
++ rename obsolete "boot.loader.efiBootStub.postEfiBootMgrCommands" "boot.loader.efi.efibootmgr.postEfiBootMgrCommands"
++ rename obsolete "boot.loader.efiBootStub.runEfibootmgr" "boot.loader.efi.efibootmgr.enable"
) # do not add renaming after this.

@ -8,7 +8,13 @@ let
isExecutable = true;
inherit (pkgs) bash;
path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep pkgs.glibc] ++ (pkgs.stdenv.lib.optionals config.boot.loader.efiBootStub.runEfibootmgr [pkgs.efibootmgr pkgs.module_init_tools]);
inherit (config.boot.loader.efiBootStub) efiSysMountPoint runEfibootmgr installStartupNsh efiDisk efiPartition postEfiBootMgrCommands;
inherit (config.boot.loader.efiBootStub) installStartupNsh;
inherit (config.boot.loader.efi) efiSysMountPoint;
inherit (config.boot.loader.efi.efibootmgr) efiDisk efiPartition postEfiBootMgrCommands;
runEfibootmgr = config.boot.loader.efi.efibootmgr.enable;
efiShell = if config.boot.loader.efiBootStub.installShell then
if pkgs.stdenv.isi686 then
@ -51,38 +57,6 @@ in
'';
};
efiDisk = mkOption {
default = "/dev/sda";
description = ''
The disk that contains the EFI system partition. Only used by
efibootmgr
'';
};
efiPartition = mkOption {
default = "1";
description = ''
The partition number of the EFI system partition. Only used by
efibootmgr
'';
};
efiSysMountPoint = mkOption {
default = "/boot";
description = ''
Where the EFI System Partition is mounted.
'';
};
runEfibootmgr = mkOption {
default = false;
description = ''
Whether to run efibootmgr to add the configuration to the boot options list.
WARNING! efibootmgr has been rumored to brick Apple firmware on
old kernels! Don't use it on kernels older than 2.6.39!
'';
};
installStartupNsh = mkOption {
default = false;
description = ''
@ -103,17 +77,6 @@ in
'';
};
postEfiBootMgrCommands = mkOption {
default = "";
type = types.string;
description = ''
Shell commands to be executed immediately after efibootmgr has setup the system EFI.
Some systems do not follow the EFI specifications properly and insert extra entries.
Others will brick (fix by removing battery) on boot when it finds more than X entries.
This hook allows for running a few extra efibootmgr commands to combat these issues.
'';
};
};
};
};

@ -0,0 +1,53 @@
{ pkgs, ... }:
with pkgs.lib;
{
options.boot.loader.efi = {
efibootmgr = {
efiDisk = mkOption {
default = "/dev/sda";
type = types.string;
description = "The disk that contains the EFI system partition.";
};
enable = mkOption {
default = false;
type = types.bool;
description = ''
Whether to run efibootmgr to add the efi bootloaders configuration to the boot options list.
WARNING! efibootmgr has been rumored to brick Apple firmware on
old kernels! Don't use it on kernels older than 2.6.39!
'';
};
efiPartition = mkOption {
default = "1";
description = "The partition number of the EFI system partition.";
};
postEfiBootMgrCommands = mkOption {
default = "";
type = types.string;
description = ''
Shell commands to be executed immediately after efibootmgr has setup the system EFI.
Some systems do not follow the EFI specifications properly and insert extra entries.
Others will brick (fix by removing battery) on boot when it finds more than X entries.
This hook allows for running a few extra efibootmgr commands to combat these issues.
'';
};
};
efiSysMountPoint = mkOption {
default = "/boot";
type = types.string;
description = "Where the EFI System Partition is mounted.";
};
};
}

@ -0,0 +1,144 @@
#! @python@/bin/python
import argparse
import shutil
import os
import errno
import subprocess
import glob
import tempfile
def copy_if_not_exists(source, dest):
known_paths.append(dest)
if not os.path.exists(dest):
shutil.copyfile(source, dest)
system_dir = lambda generation: "/nix/var/nix/profiles/system-%d-link" % (generation)
def write_entry(generation, kernel, initrd):
entry_file = "@efiSysMountPoint@/loader/entries/nixos-generation-%d.conf" % (generation)
if os.path.exists(entry_file):
return
generation_dir = os.readlink(system_dir(generation))
tmp_path = "%s.tmp" % (entry_file)
kernel_params = "systemConfig=%s init=%s/init " % (generation_dir, generation_dir)
with open("%s/kernel-params" % (generation_dir)) as params_file:
kernel_params = kernel_params + params_file.read()
with open("/etc/machine-id") as machine_file:
machine_id = machine_file.readlines()[0]
with open(tmp_path, 'w') as f:
print >> f, "title NixOS"
print >> f, "version Generation %d" % (generation)
print >> f, "machine-id %s" % (machine_id)
print >> f, "linux %s" % (kernel)
print >> f, "initrd %s" % (initrd)
print >> f, "options %s" % (kernel_params)
os.rename(tmp_path, entry_file)
def write_loader_conf(generation):
with open("@efiSysMountPoint@/loader/loader.conf.tmp", 'w') as f:
if "@timeout@" != "":
print >> f, "timeout @timeout@"
print >> f, "default nixos-generation-%d" % (generation)
os.rename("@efiSysMountPoint@/loader/loader.conf.tmp", "@efiSysMountPoint@/loader/loader.conf")
def copy_from_profile(generation, name):
store_file_path = os.readlink("%s/%s" % (system_dir(generation), name))
suffix = os.path.basename(store_file_path)
store_dir = os.path.basename(os.path.dirname(store_file_path))
efi_file_path = "/efi/nixos/%s-%s.efi" % (store_dir, suffix)
copy_if_not_exists(store_file_path, "@efiSysMountPoint@%s" % (efi_file_path))
return efi_file_path
def add_entry(generation):
efi_kernel_path = copy_from_profile(generation, "kernel")
efi_initrd_path = copy_from_profile(generation, "initrd")
write_entry(generation, efi_kernel_path, efi_initrd_path)
def mkdir_p(path):
try:
os.makedirs(path)
except OSError as e:
if e.errno != errno.EEXIST or not os.path.isdir(path):
raise
def get_generations(profile):
gen_list = subprocess.check_output([
"@nix@/bin/nix-env",
"--list-generations",
"-p",
"/nix/var/nix/profiles/%s" % (profile)
])
gen_lines = gen_list.split('\n')
gen_lines.pop()
return [ int(line.split()[0]) for line in gen_lines ]
def remove_old_entries(gens):
slice_start = len("@efiSysMountPoint@/loader/entries/nixos-generation-")
slice_end = -1 * len(".conf")
for path in glob.iglob("@efiSysMountPoint@/loader/entries/nixos-generation-[1-9][0-9]*.conf"):
gen = int(path[slice_start:slice_end])
if not gen in gens:
os.unlink(path)
for path in glob.iglob("@efiSysMountPoint@/efi/nixos/*"):
if not path in known_paths:
os.unlink(path)
def update_gummiboot():
mkdir_p("@efiSysMountPoint@/efi/gummiboot")
store_file_path = "@gummiboot@/bin/gummiboot.efi"
store_dir = os.path.basename("@gummiboot@")
efi_file_path = "/efi/gummiboot/%s-gummiboot.efi" % (store_dir)
copy_if_not_exists(store_file_path, "@efiSysMountPoint@%s" % (efi_file_path))
return efi_file_path
def update_efibootmgr(path):
subprocess.call(["@kmod@/sbin/modprobe", "efivars"])
post_efibootmgr = """
@postEfiBootMgrCommands@
"""
efibootmgr_entries = subprocess.check_output(["@efibootmgr@/sbin/efibootmgr"]).split("\n")
for entry in efibootmgr_entries:
columns = entry.split()
if len(columns) > 2:
if ' '.join(columns[1:3]) == "NixOS gummiboot":
subprocess.call([
"@efibootmgr@/sbin/efibootmgr",
"-B",
"-b",
columns[0][4:8]
])
subprocess.call([
"@efibootmgr@/sbin/efibootmgr",
"-c",
"-d",
"@efiDisk@",
"-g",
"-l",
path.replace("/", "\\"),
"-L",
"NixOS gummiboot",
"-p",
"@efiPartition@",
])
subprocess.call(post_efibootmgr, shell=True)
parser = argparse.ArgumentParser(description='Update NixOS-related gummiboot files')
parser.add_argument('default_config', metavar='DEFAULT-CONFIG', help='The default NixOS config to boot')
args = parser.parse_args()
known_paths = []
mkdir_p("@efiSysMountPoint@/efi/nixos")
mkdir_p("@efiSysMountPoint@/loader/entries")
gens = get_generations("system")
for gen in gens:
add_entry(gen)
if os.readlink(system_dir(gen)) == args.default_config:
write_loader_conf(gen)
remove_old_entries(gens)
# We deserve our own env var!
if os.getenv("NIXOS_INSTALL_GRUB") == "1":
gummiboot_path = update_gummiboot()
if "@runEfibootmgr@" == "1":
update_efibootmgr(gummiboot_path)

@ -0,0 +1,71 @@
{ config, pkgs, ... }:
with pkgs.lib;
let
cfg = config.boot.loader.gummiboot;
efi = config.boot.loader.efi;
gummibootBuilder = pkgs.substituteAll {
src = ./gummiboot-builder.py;
isExecutable = true;
inherit (pkgs) python gummiboot kmod efibootmgr;
inherit (config.environment) nix;
inherit (cfg) timeout;
inherit (efi) efiSysMountPoint;
inherit (efi.efibootmgr) postEfiBootMgrCommands efiDisk efiPartition;
runEfibootmgr = efi.efibootmgr.enable;
};
in {
options.boot.loader.gummiboot = {
enable = mkOption {
default = false;
type = types.bool;
description = "Whether to enable the gummiboot UEFI boot manager";
};
timeout = mkOption {
default = null;
example = 4;
type = types.nullOr types.int;
description = ''
Timeout (in seconds) for how long to show the menu (null if none).
Note that even with no timeout the menu can be forced if the space
key is pressed during bootup
'';
};
};
config = mkIf cfg.enable {
assertions = [
{
assertion = (config.boot.kernelPacakges.kernel.features or { efiBootStub = true; }) ? efiBootStub;
message = "This kernel does not support the EFI boot stub";
}
];
system = {
build.installBootLoader = gummibootBuilder;
boot.loader.id = "gummiboot";
requiredKernelConfig = with config.lib.kernelConfig; [
(isYes "EFI_STUB")
];
};
};
}
Loading…
Cancel
Save