diff --git a/modules/installer/efi-boot-stub/efi-boot-stub-builder.sh b/modules/installer/efi-boot-stub/efi-boot-stub-builder.sh new file mode 100644 index 000000000000..d37b56c7ffe4 --- /dev/null +++ b/modules/installer/efi-boot-stub/efi-boot-stub-builder.sh @@ -0,0 +1,118 @@ +#! @bash@/bin/sh -e + +shopt -s nullglob + +export PATH=/empty +for i in @path@; do PATH=$PATH:$i/bin:$i/sbin; done + +default=$1 +if test -z "$1"; then + echo "Syntax: efi-boot-stub-builder.sh " + exit 1 +fi + +echo "updating the efi system partition..." + +# Convert a path to a file in the Nix store such as +# /nix/store/-/file to --. +# Also, efi executables need the .efi extension +cleanName() { + local path="$1" + echo "$path" | sed 's|^/nix/store/||' | sed 's|/|-|g' | sed 's|@kernelFile@$|@kernelFile@.efi|' +} + +# Copy a file from the Nix store to the EFI system partition +declare -A filesCopied + +copyToKernelsDir() { + local src="$1" + local dst="@efiSysMountPoint@/efi/nixos/$(cleanName $src)" + # 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=$dst +} + +# Copy its kernel, initrd, and startup script to the efi system partition +# Add the efibootmgr entry if requested +addEntry() { + local path="$1" + local generation="$2" + + if ! test -e $path/kernel -a -e $path/initrd; then + return + fi + + local kernel=$(readlink -f $path/kernel) + local initrd=$(readlink -f $path/initrd) + copyToKernelsDir $kernel; kernel=$result + copyToKernelsDir $initrd; initrd=$result + + local startup="@efiSysMountPoint@/efi/nixos/$(cleanName $(readlink -f $path))-startup.nsh" + if ! test -e $startup; then + local dstTmp=$startup.tmp.$$ + echo "$(echo $kernel | sed 's|@efiSysMountPoint@||' | sed 's|/|\\|g') systemConfig=$(readlink -f $path) init=$(readlink -f $path/init) initrd=$(echo $initrd | sed 's|@efiSysMountPoint@||' | sed 's|/|\\|g') $(cat $path/kernel-params)" > $dstTmp + mv $dstTmp $startup + fi + filesCopied[$startup]=1 + + if test -n "@runEfibootmgr@"; then + set +e + efibootmgr -c -d "@efiDisk@" -g -l $(echo $kernel | sed 's|@efiSysMountPoint@||' | sed 's|/|\\|g') -L "NixOS $generation Generation" -p "@efiPartition@" \ + -u systemConfig=$(readlink -f $path) init=$(readlink -f $path/init) initrd=$(echo $initrd | sed 's|@efiSysMountPoint@||' | sed 's|/|\\|g') $(cat $path/kernel-params) + set -e + fi + + if test $(readlink -f "$path") = "$default"; then + if test -n "@runEfibootmgr@"; then + set +e + defaultbootnum=$(efibootmgr | grep "NixOS $generation Generation" | sed 's/Boot//' | sed 's/\*.*//') + set -e + fi + if test -n "@installStartupNsh@"; then + sed 's|.*@kernelFile@.efi|@kernelFile@.efi|' < $startup > "@efiSysMountPoint@/startup.nsh" + cp $kernel "@efiSysMountPoint@/@kernelFile@.efi" + fi + fi +} + +mkdir -p "@efiSysMountPoint@/efi/nixos/" + +# Remove all old boot manager entries +if test -n "@runEfibootmgr@"; then + set +e + modprobe efivars + for bootnum in $(efibootmgr | grep "NixOS" | grep "Generation" | sed 's/Boot//' | sed 's/\*.*//'); do + efibootmgr -B -b "$bootnum" + set -e + done +fi + +# Add all generations of the system profile to the system partition, in reverse +# (most recent to least recent) order. +for generation in $( + (cd /nix/var/nix/profiles && ls -d system-*-link) \ + | sed 's/system-\([0-9]\+\)-link/\1/' \ + | sort -n -r); do + link=/nix/var/nix/profiles/system-$generation-link + addEntry $link $generation +done + +if test -n "@runEfibootmgr@"; then + set +e + efibootmgr -o $defaultbootnum + set -e +fi + +# Remove obsolete files from the EFI system partition +for fn in "@efiSysMountPoint@/efi/nixos/"*; do + if ! test "${filesCopied[$fn]}" = 1; then + rm -vf -- "$fn" + fi +done diff --git a/modules/installer/efi-boot-stub/efi-boot-stub.nix b/modules/installer/efi-boot-stub/efi-boot-stub.nix new file mode 100644 index 000000000000..eeabd55c7292 --- /dev/null +++ b/modules/installer/efi-boot-stub/efi-boot-stub.nix @@ -0,0 +1,98 @@ +{pkgs, config, ...}: + +###### interface +let + inherit (pkgs.lib) mkOption mkIf; + + options = { + boot = { + loader = { + efiBootStub = { + + enable = mkOption { + default = false; + description = '' + Whether to use the linux kernel as an EFI bootloader. + When enabled, the kernel, initrd, and an EFI shell script + to boot the system are copied to the EFI system partition. + ''; + }; + + 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! Use 'bless' on + Apple efi systems. + ''; + }; + + installStartupNsh = mkOption { + default = false; + description = '' + Whether to install a startup.nsh in the root of the EFI system partition. + For now, it will just boot the latest version when run, the eventual goal + is to have a basic menu-type interface. + ''; + }; + + }; + }; + }; + }; + +in + +###### implementation +let + efiBootStubBuilder = pkgs.substituteAll { + src = ./efi-boot-stub-builder.sh; + isExecutable = true; + inherit (pkgs) bash; + path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep] ++ (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; + kernelFile = platform.kernelTarget; + }; + + # Temporary check, for nixos to cope both with nixpkgs stdenv-updates and trunk + platform = pkgs.stdenv.platform; +in +{ + require = [ + options + + # config.system.build + # ../system/system-options.nix + ]; + + system = mkIf config.boot.loader.efiBootStub.enable { + build = { + menuBuilder = efiBootStubBuilder; + }; + boot.loader.id = "efiBootStub"; + boot.loader.kernelFile = platform.kernelTarget; + }; +} diff --git a/modules/module-list.nix b/modules/module-list.nix index 668841d3da27..fa234f552902 100644 --- a/modules/module-list.nix +++ b/modules/module-list.nix @@ -22,6 +22,7 @@ ./hardware/network/rtl8192c.nix ./hardware/pcmcia.nix ./hardware/all-firmware.nix + ./installer/efi-boot-stub/efi-boot-stub.nix ./installer/generations-dir/generations-dir.nix ./installer/grub/grub.nix ./installer/init-script/init-script.nix diff --git a/modules/system/activation/switch-to-configuration.sh b/modules/system/activation/switch-to-configuration.sh index d24bf4848246..c09fa89083bd 100644 --- a/modules/system/activation/switch-to-configuration.sh +++ b/modules/system/activation/switch-to-configuration.sh @@ -50,6 +50,8 @@ if [ "$action" = "switch" -o "$action" = "boot" ]; then elif [ "@bootLoader@" = "generationsDir" ]; then @menuBuilder@ @out@ + elif [ "@bootLoader@" = "efiBootStub" ]; then + @menuBuilder@ @out@ else echo "Warning: don't know how to make this configuration bootable; please enable a boot loader." 1>&2 fi