uboot: (firmwareOdroidC2/C4) don't invoke patch tool, use patches = [] instead
https://github.com/NixOS/nixpkgs/blob/master/pkgs/stdenv/generic/setup.sh#L948 this can do it nicely. Signed-off-by: Anton Arapov <anton@deadbeef.mx>
This commit is contained in:
commit
56de2bcd43
30691 changed files with 3076956 additions and 0 deletions
20
nixos/modules/system/boot/loader/efi.nix
Normal file
20
nixos/modules/system/boot/loader/efi.nix
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{ lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
{
|
||||
options.boot.loader.efi = {
|
||||
|
||||
canTouchEfiVariables = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = "Whether the installation process is allowed to modify EFI boot variables.";
|
||||
};
|
||||
|
||||
efiSysMountPoint = mkOption {
|
||||
default = "/boot";
|
||||
type = types.str;
|
||||
description = "Where the EFI System Partition is mounted.";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
#! @bash@/bin/sh -e
|
||||
|
||||
shopt -s nullglob
|
||||
|
||||
export PATH=/empty
|
||||
for i in @path@; do PATH=$PATH:$i/bin; done
|
||||
|
||||
default=$1
|
||||
if test -z "$1"; then
|
||||
echo "Syntax: generations-dir-builder.sh <DEFAULT-CONFIG>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "updating the boot generations directory..."
|
||||
|
||||
mkdir -p /boot
|
||||
|
||||
rm -Rf /boot/system* || true
|
||||
|
||||
target=/boot/grub/menu.lst
|
||||
tmp=$target.tmp
|
||||
|
||||
# Convert a path to a file in the Nix store such as
|
||||
# /nix/store/<hash>-<name>/file to <hash>-<name>-<file>.
|
||||
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 dst="/boot/kernels/$(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 and initrd to /boot/kernels.
|
||||
addEntry() {
|
||||
local path="$1"
|
||||
local generation="$2"
|
||||
local outdir=/boot/system-$generation
|
||||
|
||||
if ! test -e $path/kernel -a -e $path/initrd; then
|
||||
return
|
||||
fi
|
||||
|
||||
local kernel=$(readlink -f $path/kernel)
|
||||
local initrd=$(readlink -f $path/initrd)
|
||||
|
||||
if test -n "@copyKernels@"; then
|
||||
copyToKernelsDir $kernel; kernel=$result
|
||||
copyToKernelsDir $initrd; initrd=$result
|
||||
fi
|
||||
|
||||
mkdir -p $outdir
|
||||
ln -sf $(readlink -f $path) $outdir/system
|
||||
ln -sf $(readlink -f $path/init) $outdir/init
|
||||
ln -sf $initrd $outdir/initrd
|
||||
ln -sf $kernel $outdir/kernel
|
||||
|
||||
if test $(readlink -f "$path") = "$default"; then
|
||||
cp "$kernel" /boot/nixos-kernel
|
||||
cp "$initrd" /boot/nixos-initrd
|
||||
cp "$(readlink -f "$path/init")" /boot/nixos-init
|
||||
|
||||
mkdir -p /boot/default
|
||||
# ln -sfT: overrides target even if it exists.
|
||||
ln -sfT $(readlink -f $path) /boot/default/system
|
||||
ln -sfT $(readlink -f $path/init) /boot/default/init
|
||||
ln -sfT $initrd /boot/default/initrd
|
||||
ln -sfT $kernel /boot/default/kernel
|
||||
fi
|
||||
}
|
||||
|
||||
if test -n "@copyKernels@"; then
|
||||
mkdir -p /boot/kernels
|
||||
fi
|
||||
|
||||
# Add all generations of the system profile to the menu, 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
|
||||
|
||||
# Remove obsolete files from /boot/kernels.
|
||||
for fn in /boot/kernels/*; do
|
||||
if ! test "${filesCopied[$fn]}" = 1; then
|
||||
rm -vf -- "$fn"
|
||||
fi
|
||||
done
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
generationsDirBuilder = pkgs.substituteAll {
|
||||
src = ./generations-dir-builder.sh;
|
||||
isExecutable = true;
|
||||
inherit (pkgs) bash;
|
||||
path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
|
||||
inherit (config.boot.loader.generationsDir) copyKernels;
|
||||
};
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
|
||||
boot.loader.generationsDir = {
|
||||
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to create symlinks to the system generations under
|
||||
<literal>/boot</literal>. When enabled,
|
||||
<literal>/boot/default/kernel</literal>,
|
||||
<literal>/boot/default/initrd</literal>, etc., are updated to
|
||||
point to the current generation's kernel image, initial RAM
|
||||
disk, and other bootstrap files.
|
||||
|
||||
This optional is not necessary with boot loaders such as GNU GRUB
|
||||
for which the menu is updated to point to the latest bootstrap
|
||||
files. However, it is needed for U-Boot on platforms where the
|
||||
boot command line is stored in flash memory rather than in a
|
||||
menu file.
|
||||
'';
|
||||
};
|
||||
|
||||
copyKernels = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether copy the necessary boot files into /boot, so
|
||||
/nix/store is not needed by the boot loader.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
config = mkIf config.boot.loader.generationsDir.enable {
|
||||
|
||||
system.build.installBootLoader = generationsDirBuilder;
|
||||
system.boot.loader.id = "generationsDir";
|
||||
system.boot.loader.kernelFile = pkgs.stdenv.hostPlatform.linux-kernel.target;
|
||||
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
blCfg = config.boot.loader;
|
||||
dtCfg = config.hardware.deviceTree;
|
||||
cfg = blCfg.generic-extlinux-compatible;
|
||||
|
||||
timeoutStr = if blCfg.timeout == null then "-1" else toString blCfg.timeout;
|
||||
|
||||
# The builder used to write during system activation
|
||||
builder = import ./extlinux-conf-builder.nix { inherit pkgs; };
|
||||
# The builder exposed in populateCmd, which runs on the build architecture
|
||||
populateBuilder = import ./extlinux-conf-builder.nix { pkgs = pkgs.buildPackages; };
|
||||
in
|
||||
{
|
||||
options = {
|
||||
boot.loader.generic-extlinux-compatible = {
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to generate an extlinux-compatible configuration file
|
||||
under <literal>/boot/extlinux.conf</literal>. For instance,
|
||||
U-Boot's generic distro boot support uses this file format.
|
||||
|
||||
See <link xlink:href="http://git.denx.de/?p=u-boot.git;a=blob;f=doc/README.distro;hb=refs/heads/master">U-boot's documentation</link>
|
||||
for more information.
|
||||
'';
|
||||
};
|
||||
|
||||
useGenerationDeviceTree = mkOption {
|
||||
default = true;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to generate Device Tree-related directives in the
|
||||
extlinux configuration.
|
||||
|
||||
When enabled, the bootloader will attempt to load the device
|
||||
tree binaries from the generation's kernel.
|
||||
|
||||
Note that this affects all generations, regardless of the
|
||||
setting value used in their configurations.
|
||||
'';
|
||||
};
|
||||
|
||||
configurationLimit = mkOption {
|
||||
default = 20;
|
||||
example = 10;
|
||||
type = types.int;
|
||||
description = ''
|
||||
Maximum number of configurations in the boot menu.
|
||||
'';
|
||||
};
|
||||
|
||||
populateCmd = mkOption {
|
||||
type = types.str;
|
||||
readOnly = true;
|
||||
description = ''
|
||||
Contains the builder command used to populate an image,
|
||||
honoring all options except the <literal>-c <path-to-default-configuration></literal>
|
||||
argument.
|
||||
Useful to have for sdImage.populateRootCommands
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
config = let
|
||||
builderArgs = "-g ${toString cfg.configurationLimit} -t ${timeoutStr}"
|
||||
+ lib.optionalString (dtCfg.name != null) " -n ${dtCfg.name}"
|
||||
+ lib.optionalString (!cfg.useGenerationDeviceTree) " -r";
|
||||
in
|
||||
mkIf cfg.enable {
|
||||
system.build.installBootLoader = "${builder} ${builderArgs} -c";
|
||||
system.boot.loader.id = "generic-extlinux-compatible";
|
||||
|
||||
boot.loader.generic-extlinux-compatible.populateCmd = "${populateBuilder} ${builderArgs}";
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{ pkgs }:
|
||||
|
||||
pkgs.substituteAll {
|
||||
src = ./extlinux-conf-builder.sh;
|
||||
isExecutable = true;
|
||||
path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
|
||||
inherit (pkgs) bash;
|
||||
}
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
#! @bash@/bin/sh -e
|
||||
|
||||
shopt -s nullglob
|
||||
|
||||
export PATH=/empty
|
||||
for i in @path@; do PATH=$PATH:$i/bin; done
|
||||
|
||||
usage() {
|
||||
echo "usage: $0 -t <timeout> -c <path-to-default-configuration> [-d <boot-dir>] [-g <num-generations>] [-n <dtbName>] [-r]" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
timeout= # Timeout in centiseconds
|
||||
default= # Default configuration
|
||||
target=/boot # Target directory
|
||||
numGenerations=0 # Number of other generations to include in the menu
|
||||
|
||||
while getopts "t:c:d:g:n:r" opt; do
|
||||
case "$opt" in
|
||||
t) # U-Boot interprets '0' as infinite and negative as instant boot
|
||||
if [ "$OPTARG" -lt 0 ]; then
|
||||
timeout=0
|
||||
elif [ "$OPTARG" = 0 ]; then
|
||||
timeout=-10
|
||||
else
|
||||
timeout=$((OPTARG * 10))
|
||||
fi
|
||||
;;
|
||||
c) default="$OPTARG" ;;
|
||||
d) target="$OPTARG" ;;
|
||||
g) numGenerations="$OPTARG" ;;
|
||||
n) dtbName="$OPTARG" ;;
|
||||
r) noDeviceTree=1 ;;
|
||||
\?) usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ "$timeout" = "" -o "$default" = "" ] && usage
|
||||
|
||||
mkdir -p $target/nixos
|
||||
mkdir -p $target/extlinux
|
||||
|
||||
# Convert a path to a file in the Nix store such as
|
||||
# /nix/store/<hash>-<name>/file to <hash>-<name>-<file>.
|
||||
cleanName() {
|
||||
local path="$1"
|
||||
echo "$path" | sed 's|^/nix/store/||' | sed 's|/|-|g'
|
||||
}
|
||||
|
||||
# Copy a file from the Nix store to $target/nixos.
|
||||
declare -A filesCopied
|
||||
|
||||
copyToKernelsDir() {
|
||||
local src=$(readlink -f "$1")
|
||||
local dst="$target/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 -r $src $dstTmp
|
||||
mv $dstTmp $dst
|
||||
fi
|
||||
filesCopied[$dst]=1
|
||||
result=$dst
|
||||
}
|
||||
|
||||
# Copy its kernel, initrd and dtbs to $target/nixos, and echo out an
|
||||
# extlinux menu entry
|
||||
addEntry() {
|
||||
local path=$(readlink -f "$1")
|
||||
local tag="$2" # Generation number or 'default'
|
||||
|
||||
if ! test -e $path/kernel -a -e $path/initrd; then
|
||||
return
|
||||
fi
|
||||
|
||||
copyToKernelsDir "$path/kernel"; kernel=$result
|
||||
copyToKernelsDir "$path/initrd"; initrd=$result
|
||||
dtbDir=$(readlink -m "$path/dtbs")
|
||||
if [ -e "$dtbDir" ]; then
|
||||
copyToKernelsDir "$dtbDir"; dtbs=$result
|
||||
fi
|
||||
|
||||
timestampEpoch=$(stat -L -c '%Z' $path)
|
||||
|
||||
timestamp=$(date "+%Y-%m-%d %H:%M" -d @$timestampEpoch)
|
||||
nixosLabel="$(cat $path/nixos-version)"
|
||||
extraParams="$(cat $path/kernel-params)"
|
||||
|
||||
echo
|
||||
echo "LABEL nixos-$tag"
|
||||
if [ "$tag" = "default" ]; then
|
||||
echo " MENU LABEL NixOS - Default"
|
||||
else
|
||||
echo " MENU LABEL NixOS - Configuration $tag ($timestamp - $nixosLabel)"
|
||||
fi
|
||||
echo " LINUX ../nixos/$(basename $kernel)"
|
||||
echo " INITRD ../nixos/$(basename $initrd)"
|
||||
echo " APPEND init=$path/init $extraParams"
|
||||
|
||||
if [ -n "$noDeviceTree" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -d "$dtbDir" ]; then
|
||||
# if a dtbName was specified explicitly, use that, else use FDTDIR
|
||||
if [ -n "$dtbName" ]; then
|
||||
echo " FDT ../nixos/$(basename $dtbs)/${dtbName}"
|
||||
else
|
||||
echo " FDTDIR ../nixos/$(basename $dtbs)"
|
||||
fi
|
||||
else
|
||||
if [ -n "$dtbName" ]; then
|
||||
echo "Explicitly requested dtbName $dtbName, but there's no FDTDIR - bailing out." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
tmpFile="$target/extlinux/extlinux.conf.tmp.$$"
|
||||
|
||||
cat > $tmpFile <<EOF
|
||||
# Generated file, all changes will be lost on nixos-rebuild!
|
||||
|
||||
# Change this to e.g. nixos-42 to temporarily boot to an older configuration.
|
||||
DEFAULT nixos-default
|
||||
|
||||
MENU TITLE ------------------------------------------------------------
|
||||
TIMEOUT $timeout
|
||||
EOF
|
||||
|
||||
addEntry $default default >> $tmpFile
|
||||
|
||||
if [ "$numGenerations" -gt 0 ]; then
|
||||
# Add up to $numGenerations generations of the system profile to the menu,
|
||||
# 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 \
|
||||
| head -n $numGenerations); do
|
||||
link=/nix/var/nix/profiles/system-$generation-link
|
||||
addEntry $link $generation
|
||||
done >> $tmpFile
|
||||
fi
|
||||
|
||||
mv -f $tmpFile $target/extlinux/extlinux.conf
|
||||
|
||||
# Remove obsolete files from $target/nixos.
|
||||
for fn in $target/nixos/*; do
|
||||
if ! test "${filesCopied[$fn]}" = 1; then
|
||||
echo "Removing no longer needed boot file: $fn"
|
||||
chmod +w -- "$fn"
|
||||
rm -rf -- "$fn"
|
||||
fi
|
||||
done
|
||||
874
nixos/modules/system/boot/loader/grub/grub.nix
Normal file
874
nixos/modules/system/boot/loader/grub/grub.nix
Normal file
|
|
@ -0,0 +1,874 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.boot.loader.grub;
|
||||
|
||||
efi = config.boot.loader.efi;
|
||||
|
||||
grubPkgs =
|
||||
# Package set of targeted architecture
|
||||
if cfg.forcei686 then pkgs.pkgsi686Linux else pkgs;
|
||||
|
||||
realGrub = if cfg.version == 1 then grubPkgs.grub
|
||||
else if cfg.zfsSupport then grubPkgs.grub2.override { zfsSupport = true; }
|
||||
else if cfg.trustedBoot.enable
|
||||
then if cfg.trustedBoot.isHPLaptop
|
||||
then grubPkgs.trustedGrub-for-HP
|
||||
else grubPkgs.trustedGrub
|
||||
else grubPkgs.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;
|
||||
|
||||
grubEfi =
|
||||
# EFI version of Grub v2
|
||||
if cfg.efiSupport && (cfg.version == 2)
|
||||
then realGrub.override { efiSupport = cfg.efiSupport; }
|
||||
else null;
|
||||
|
||||
f = x: if x == null then "" else "" + x;
|
||||
|
||||
grubConfig = args:
|
||||
let
|
||||
efiSysMountPoint = if args.efiSysMountPoint == null then args.path else args.efiSysMountPoint;
|
||||
efiSysMountPoint' = replaceChars [ "/" ] [ "-" ] efiSysMountPoint;
|
||||
in
|
||||
pkgs.writeText "grub-config.xml" (builtins.toXML
|
||||
{ splashImage = f cfg.splashImage;
|
||||
splashMode = f cfg.splashMode;
|
||||
backgroundColor = f cfg.backgroundColor;
|
||||
entryOptions = f cfg.entryOptions;
|
||||
subEntryOptions = f cfg.subEntryOptions;
|
||||
grub = f grub;
|
||||
grubTarget = f (grub.grubTarget or "");
|
||||
shell = "${pkgs.runtimeShell}";
|
||||
fullName = lib.getName realGrub;
|
||||
fullVersion = lib.getVersion realGrub;
|
||||
grubEfi = f grubEfi;
|
||||
grubTargetEfi = if cfg.efiSupport && (cfg.version == 2) then f (grubEfi.grubTarget or "") else "";
|
||||
bootPath = args.path;
|
||||
storePath = config.boot.loader.grub.storePath;
|
||||
bootloaderId = if args.efiBootloaderId == null then "NixOS${efiSysMountPoint'}" else args.efiBootloaderId;
|
||||
timeout = if config.boot.loader.timeout == null then -1 else config.boot.loader.timeout;
|
||||
users = if cfg.users == {} || cfg.version != 1 then cfg.users else throw "GRUB version 1 does not support user accounts.";
|
||||
theme = f cfg.theme;
|
||||
inherit efiSysMountPoint;
|
||||
inherit (args) devices;
|
||||
inherit (efi) canTouchEfiVariables;
|
||||
inherit (cfg)
|
||||
version extraConfig extraPerEntryConfig extraEntries forceInstall useOSProber
|
||||
extraGrubInstallArgs
|
||||
extraEntriesBeforeNixOS extraPrepareConfig configurationLimit copyKernels
|
||||
default fsIdentifier efiSupport efiInstallAsRemovable gfxmodeEfi gfxmodeBios gfxpayloadEfi gfxpayloadBios;
|
||||
path = with pkgs; makeBinPath (
|
||||
[ coreutils gnused gnugrep findutils diffutils btrfs-progs util-linux mdadm ]
|
||||
++ optional (cfg.efiSupport && (cfg.version == 2)) efibootmgr
|
||||
++ optionals cfg.useOSProber [ busybox os-prober ]);
|
||||
font = if cfg.font == null then ""
|
||||
else (if lib.last (lib.splitString "." cfg.font) == "pf2"
|
||||
then cfg.font
|
||||
else "${convertedFont}");
|
||||
});
|
||||
|
||||
bootDeviceCounters = foldr (device: attr: attr // { ${device} = (attr.${device} or 0) + 1; }) {}
|
||||
(concatMap (args: args.devices) cfg.mirroredBoots);
|
||||
|
||||
convertedFont = (pkgs.runCommand "grub-font-converted.pf2" {}
|
||||
(builtins.concatStringsSep " "
|
||||
([ "${realGrub}/bin/grub-mkfont"
|
||||
cfg.font
|
||||
"--output" "$out"
|
||||
] ++ (optional (cfg.fontSize!=null) "--size ${toString cfg.fontSize}")))
|
||||
);
|
||||
|
||||
defaultSplash = pkgs.nixos-artwork.wallpapers.simple-dark-gray-bootloader.gnomeFilePath;
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
boot.loader.grub = {
|
||||
|
||||
enable = mkOption {
|
||||
default = !config.boot.isContainer;
|
||||
defaultText = literalExpression "!config.boot.isContainer";
|
||||
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/disk/by-id/wwn-0x500001234567890a";
|
||||
type = types.str;
|
||||
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/disk/by-id/wwn-0x500001234567890a" ];
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
The devices on which the boot loader, GRUB, will be
|
||||
installed. Can be used instead of <literal>device</literal> to
|
||||
install GRUB onto multiple devices.
|
||||
'';
|
||||
};
|
||||
|
||||
users = mkOption {
|
||||
default = {};
|
||||
example = {
|
||||
root = { hashedPasswordFile = "/path/to/file"; };
|
||||
};
|
||||
description = ''
|
||||
User accounts for GRUB. When specified, the GRUB command line and
|
||||
all boot options except the default are password-protected.
|
||||
All passwords and hashes provided will be stored in /boot/grub/grub.cfg,
|
||||
and will be visible to any local user who can read this file. Additionally,
|
||||
any passwords and hashes provided directly in a Nix configuration
|
||||
(as opposed to external files) will be copied into the Nix store, and
|
||||
will be visible to all local users.
|
||||
'';
|
||||
type = with types; attrsOf (submodule {
|
||||
options = {
|
||||
hashedPasswordFile = mkOption {
|
||||
example = "/path/to/file";
|
||||
default = null;
|
||||
type = with types; uniq (nullOr str);
|
||||
description = ''
|
||||
Specifies the path to a file containing the password hash
|
||||
for the account, generated with grub-mkpasswd-pbkdf2.
|
||||
This hash will be stored in /boot/grub/grub.cfg, and will
|
||||
be visible to any local user who can read this file.
|
||||
'';
|
||||
};
|
||||
hashedPassword = mkOption {
|
||||
example = "grub.pbkdf2.sha512.10000.674DFFDEF76E13EA...2CC972B102CF4355";
|
||||
default = null;
|
||||
type = with types; uniq (nullOr str);
|
||||
description = ''
|
||||
Specifies the password hash for the account,
|
||||
generated with grub-mkpasswd-pbkdf2.
|
||||
This hash will be copied to the Nix store, and will be visible to all local users.
|
||||
'';
|
||||
};
|
||||
passwordFile = mkOption {
|
||||
example = "/path/to/file";
|
||||
default = null;
|
||||
type = with types; uniq (nullOr str);
|
||||
description = ''
|
||||
Specifies the path to a file containing the
|
||||
clear text password for the account.
|
||||
This password will be stored in /boot/grub/grub.cfg, and will
|
||||
be visible to any local user who can read this file.
|
||||
'';
|
||||
};
|
||||
password = mkOption {
|
||||
example = "Pa$$w0rd!";
|
||||
default = null;
|
||||
type = with types; uniq (nullOr str);
|
||||
description = ''
|
||||
Specifies the clear text password for the account.
|
||||
This password will be copied to the Nix store, and will be visible to all local users.
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
mirroredBoots = mkOption {
|
||||
default = [ ];
|
||||
example = [
|
||||
{ path = "/boot1"; devices = [ "/dev/disk/by-id/wwn-0x500001234567890a" ]; }
|
||||
{ path = "/boot2"; devices = [ "/dev/disk/by-id/wwn-0x500009876543210a" ]; }
|
||||
];
|
||||
description = ''
|
||||
Mirror the boot configuration to multiple partitions and install grub
|
||||
to the respective devices corresponding to those partitions.
|
||||
'';
|
||||
|
||||
type = with types; listOf (submodule {
|
||||
options = {
|
||||
|
||||
path = mkOption {
|
||||
example = "/boot1";
|
||||
type = types.str;
|
||||
description = ''
|
||||
The path to the boot directory where GRUB will be written. Generally
|
||||
this boot path should double as an EFI path.
|
||||
'';
|
||||
};
|
||||
|
||||
efiSysMountPoint = mkOption {
|
||||
default = null;
|
||||
example = "/boot1/efi";
|
||||
type = types.nullOr types.str;
|
||||
description = ''
|
||||
The path to the efi system mount point. Usually this is the same
|
||||
partition as the above path and can be left as null.
|
||||
'';
|
||||
};
|
||||
|
||||
efiBootloaderId = mkOption {
|
||||
default = null;
|
||||
example = "NixOS-fsid";
|
||||
type = types.nullOr types.str;
|
||||
description = ''
|
||||
The id of the bootloader to store in efi nvram.
|
||||
The default is to name it NixOS and append the path or efiSysMountPoint.
|
||||
This is only used if <literal>boot.loader.efi.canTouchEfiVariables</literal> is true.
|
||||
'';
|
||||
};
|
||||
|
||||
devices = mkOption {
|
||||
default = [ ];
|
||||
example = [ "/dev/disk/by-id/wwn-0x500001234567890a" "/dev/disk/by-id/wwn-0x500009876543210a" ];
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
The path to the devices which will have the GRUB MBR written.
|
||||
Note these are typically device paths and not paths to partitions.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
configurationName = mkOption {
|
||||
default = "";
|
||||
example = "Stable 2.6.21";
|
||||
type = types.str;
|
||||
description = ''
|
||||
GRUB entry name instead of default.
|
||||
'';
|
||||
};
|
||||
|
||||
storePath = mkOption {
|
||||
default = "/nix/store";
|
||||
type = types.str;
|
||||
description = ''
|
||||
Path to the Nix store when looking for kernels at boot.
|
||||
Only makes sense when copyKernels is false.
|
||||
'';
|
||||
};
|
||||
|
||||
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 --unit=0 --speed=115200 --word=8 --parity=no --stop=1
|
||||
terminal_input --append serial
|
||||
terminal_output --append serial
|
||||
'';
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Additional GRUB commands inserted in the configuration file
|
||||
just before the menu entries.
|
||||
'';
|
||||
};
|
||||
|
||||
extraGrubInstallArgs = mkOption {
|
||||
default = [ ];
|
||||
example = [ "--modules=nativedisk ahci pata part_gpt part_msdos diskfilter mdraid1x lvm ext2" ];
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
Additional arguments passed to <literal>grub-install</literal>.
|
||||
|
||||
A use case for this is to build specific GRUB2 modules
|
||||
directly into the GRUB2 kernel image, so that they are available
|
||||
and activated even in the <literal>grub rescue</literal> shell.
|
||||
|
||||
They are also necessary when the BIOS/UEFI is bugged and cannot
|
||||
correctly read large disks (e.g. above 2 TB), so GRUB2's own
|
||||
<literal>nativedisk</literal> and related modules can be used
|
||||
to use its own disk drivers. The example shows one such case.
|
||||
This is also useful for booting from USB.
|
||||
See the
|
||||
<link xlink:href="http://git.savannah.gnu.org/cgit/grub.git/tree/grub-core/commands/nativedisk.c?h=grub-2.04#n326">
|
||||
GRUB source code
|
||||
</link>
|
||||
for which disk modules are available.
|
||||
|
||||
The list elements are passed directly as <literal>argv</literal>
|
||||
arguments to the <literal>grub-install</literal> program, in order.
|
||||
'';
|
||||
};
|
||||
|
||||
extraInstallCommands = mkOption {
|
||||
default = "";
|
||||
example = ''
|
||||
# the example below generates detached signatures that GRUB can verify
|
||||
# https://www.gnu.org/software/grub/manual/grub/grub.html#Using-digital-signatures
|
||||
''${pkgs.findutils}/bin/find /boot -not -path "/boot/efi/*" -type f -name '*.sig' -delete
|
||||
old_gpg_home=$GNUPGHOME
|
||||
export GNUPGHOME="$(mktemp -d)"
|
||||
''${pkgs.gnupg}/bin/gpg --import ''${priv_key} > /dev/null 2>&1
|
||||
''${pkgs.findutils}/bin/find /boot -not -path "/boot/efi/*" -type f -exec ''${pkgs.gnupg}/bin/gpg --detach-sign "{}" \; > /dev/null 2>&1
|
||||
rm -rf $GNUPGHOME
|
||||
export GNUPGHOME=$old_gpg_home
|
||||
'';
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Additional shell commands inserted in the bootloader installer
|
||||
script after generating 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 "Windows 7" {
|
||||
chainloader (hd0,4)+1
|
||||
}
|
||||
|
||||
# GRUB 2 with UEFI example, chainloading another distro
|
||||
menuentry "Fedora" {
|
||||
set root=(hd1,1)
|
||||
chainloader /efi/fedora/grubx64.efi
|
||||
}
|
||||
'';
|
||||
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 {
|
||||
type = types.attrsOf types.path;
|
||||
default = {};
|
||||
example = literalExpression ''
|
||||
{ "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.
|
||||
'';
|
||||
};
|
||||
|
||||
useOSProber = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
If set to true, append entries for other OSs detected by os-prober.
|
||||
'';
|
||||
};
|
||||
|
||||
splashImage = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
example = literalExpression "./my-background.png";
|
||||
description = ''
|
||||
Background image used for GRUB.
|
||||
Set to <literal>null</literal> to run GRUB in text mode.
|
||||
|
||||
<note><para>
|
||||
For grub 1:
|
||||
It must be a 640x480,
|
||||
14-colour image in XPM format, optionally compressed with
|
||||
<command>gzip</command> or <command>bzip2</command>.
|
||||
</para></note>
|
||||
|
||||
<note><para>
|
||||
For grub 2:
|
||||
File must be one of .png, .tga, .jpg, or .jpeg. JPEG images must
|
||||
not be progressive.
|
||||
The image will be scaled if necessary to fit the screen.
|
||||
</para></note>
|
||||
'';
|
||||
};
|
||||
|
||||
backgroundColor = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
example = "#7EBAE4";
|
||||
default = null;
|
||||
description = ''
|
||||
Background color to be used for GRUB to fill the areas the image isn't filling.
|
||||
|
||||
<note><para>
|
||||
This options has no effect for GRUB 1.
|
||||
</para></note>
|
||||
'';
|
||||
};
|
||||
|
||||
entryOptions = mkOption {
|
||||
default = "--class nixos --unrestricted";
|
||||
type = types.nullOr types.str;
|
||||
description = ''
|
||||
Options applied to the primary NixOS menu entry.
|
||||
|
||||
<note><para>
|
||||
This options has no effect for GRUB 1.
|
||||
</para></note>
|
||||
'';
|
||||
};
|
||||
|
||||
subEntryOptions = mkOption {
|
||||
default = "--class nixos";
|
||||
type = types.nullOr types.str;
|
||||
description = ''
|
||||
Options applied to the secondary NixOS submenu entry.
|
||||
|
||||
<note><para>
|
||||
This options has no effect for GRUB 1.
|
||||
</para></note>
|
||||
'';
|
||||
};
|
||||
|
||||
theme = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
example = literalExpression "pkgs.nixos-grub2-theme";
|
||||
default = null;
|
||||
description = ''
|
||||
Grub theme to be used.
|
||||
|
||||
<note><para>
|
||||
This options has no effect for GRUB 1.
|
||||
</para></note>
|
||||
'';
|
||||
};
|
||||
|
||||
splashMode = mkOption {
|
||||
type = types.enum [ "normal" "stretch" ];
|
||||
default = "stretch";
|
||||
description = ''
|
||||
Whether to stretch the image or show the image in the top-left corner unstretched.
|
||||
|
||||
<note><para>
|
||||
This options has no effect for GRUB 1.
|
||||
</para></note>
|
||||
'';
|
||||
};
|
||||
|
||||
font = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = "${realGrub}/share/grub/unicode.pf2";
|
||||
defaultText = literalExpression ''"''${pkgs.grub2}/share/grub/unicode.pf2"'';
|
||||
description = ''
|
||||
Path to a TrueType, OpenType, or pf2 font to be used by Grub.
|
||||
'';
|
||||
};
|
||||
|
||||
fontSize = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
example = 16;
|
||||
default = null;
|
||||
description = ''
|
||||
Font size for the grub menu. Ignored unless <literal>font</literal>
|
||||
is set to a ttf or otf font.
|
||||
'';
|
||||
};
|
||||
|
||||
gfxmodeEfi = mkOption {
|
||||
default = "auto";
|
||||
example = "1024x768";
|
||||
type = types.str;
|
||||
description = ''
|
||||
The gfxmode to pass to GRUB when loading a graphical boot interface under EFI.
|
||||
'';
|
||||
};
|
||||
|
||||
gfxmodeBios = mkOption {
|
||||
default = "1024x768";
|
||||
example = "auto";
|
||||
type = types.str;
|
||||
description = ''
|
||||
The gfxmode to pass to GRUB when loading a graphical boot interface under BIOS.
|
||||
'';
|
||||
};
|
||||
|
||||
gfxpayloadEfi = mkOption {
|
||||
default = "keep";
|
||||
example = "text";
|
||||
type = types.str;
|
||||
description = ''
|
||||
The gfxpayload to pass to GRUB when loading a graphical boot interface under EFI.
|
||||
'';
|
||||
};
|
||||
|
||||
gfxpayloadBios = mkOption {
|
||||
default = "text";
|
||||
example = "keep";
|
||||
type = types.str;
|
||||
description = ''
|
||||
The gfxpayload to pass to GRUB when loading a graphical boot interface under BIOS.
|
||||
'';
|
||||
};
|
||||
|
||||
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 /.
|
||||
'';
|
||||
};
|
||||
|
||||
default = mkOption {
|
||||
default = "0";
|
||||
type = types.either types.int types.str;
|
||||
apply = toString;
|
||||
description = ''
|
||||
Index of the default menu item to be booted.
|
||||
Can also be set to "saved", which will make GRUB select
|
||||
the menu item that was used at the last boot.
|
||||
'';
|
||||
};
|
||||
|
||||
fsIdentifier = mkOption {
|
||||
default = "uuid";
|
||||
type = types.enum [ "uuid" "label" "provided" ];
|
||||
description = ''
|
||||
Determines how GRUB will identify devices when generating the
|
||||
configuration file. A value of uuid / label signifies that grub
|
||||
will always resolve the uuid or label of the device before using
|
||||
it in the configuration. A value of provided means that GRUB will
|
||||
use the device name as show in <command>df</command> or
|
||||
<command>mount</command>. Note, zfs zpools / datasets are ignored
|
||||
and will always be mounted using their labels.
|
||||
'';
|
||||
};
|
||||
|
||||
zfsSupport = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether GRUB should be built against libzfs.
|
||||
ZFS support is only available for GRUB v2.
|
||||
This option is ignored for GRUB v1.
|
||||
'';
|
||||
};
|
||||
|
||||
efiSupport = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether GRUB should be built with EFI support.
|
||||
EFI support is only available for GRUB v2.
|
||||
This option is ignored for GRUB v1.
|
||||
'';
|
||||
};
|
||||
|
||||
efiInstallAsRemovable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to invoke <literal>grub-install</literal> with
|
||||
<literal>--removable</literal>.</para>
|
||||
|
||||
<para>Unless you turn this on, GRUB will install itself somewhere in
|
||||
<literal>boot.loader.efi.efiSysMountPoint</literal> (exactly where
|
||||
depends on other config variables). If you've set
|
||||
<literal>boot.loader.efi.canTouchEfiVariables</literal> *AND* you
|
||||
are currently booted in UEFI mode, then GRUB will use
|
||||
<literal>efibootmgr</literal> to modify the boot order in the
|
||||
EFI variables of your firmware to include this location. If you are
|
||||
*not* booted in UEFI mode at the time GRUB is being installed, the
|
||||
NVRAM will not be modified, and your system will not find GRUB at
|
||||
boot time. However, GRUB will still return success so you may miss
|
||||
the warning that gets printed ("<literal>efibootmgr: EFI variables
|
||||
are not supported on this system.</literal>").</para>
|
||||
|
||||
<para>If you turn this feature on, GRUB will install itself in a
|
||||
special location within <literal>efiSysMountPoint</literal> (namely
|
||||
<literal>EFI/boot/boot$arch.efi</literal>) which the firmwares
|
||||
are hardcoded to try first, regardless of NVRAM EFI variables.</para>
|
||||
|
||||
<para>To summarize, turn this on if:
|
||||
<itemizedlist>
|
||||
<listitem><para>You are installing NixOS and want it to boot in UEFI mode,
|
||||
but you are currently booted in legacy mode</para></listitem>
|
||||
<listitem><para>You want to make a drive that will boot regardless of
|
||||
the NVRAM state of the computer (like a USB "removable" drive)</para></listitem>
|
||||
<listitem><para>You simply dislike the idea of depending on NVRAM
|
||||
state to make your drive bootable</para></listitem>
|
||||
</itemizedlist>
|
||||
'';
|
||||
};
|
||||
|
||||
enableCryptodisk = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Enable support for encrypted partitions. GRUB should automatically
|
||||
unlock the correct encrypted partition and look for filesystems.
|
||||
'';
|
||||
};
|
||||
|
||||
forceInstall = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to try and forcibly install GRUB even if problems are
|
||||
detected. It is not recommended to enable this unless you know what
|
||||
you are doing.
|
||||
'';
|
||||
};
|
||||
|
||||
forcei686 = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to force the use of a ia32 boot loader on x64 systems. Required
|
||||
to install and run NixOS on 64bit x86 systems with 32bit (U)EFI.
|
||||
'';
|
||||
};
|
||||
|
||||
trustedBoot = {
|
||||
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Enable trusted boot. GRUB will measure all critical components during
|
||||
the boot process to offer TCG (TPM) support.
|
||||
'';
|
||||
};
|
||||
|
||||
systemHasTPM = mkOption {
|
||||
default = "";
|
||||
example = "YES_TPM_is_activated";
|
||||
type = types.str;
|
||||
description = ''
|
||||
Assertion that the target system has an activated TPM. It is a safety
|
||||
check before allowing the activation of 'trustedBoot.enable'. TrustedBoot
|
||||
WILL FAIL TO BOOT YOUR SYSTEM if no TPM is available.
|
||||
'';
|
||||
};
|
||||
|
||||
isHPLaptop = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Use a special version of TrustedGRUB that is needed by some HP laptops
|
||||
and works only for the HP laptops.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkMerge [
|
||||
|
||||
{ boot.loader.grub.splashImage = mkDefault (
|
||||
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 defaultSplash);
|
||||
}
|
||||
|
||||
(mkIf (cfg.splashImage == defaultSplash) {
|
||||
boot.loader.grub.backgroundColor = mkDefault "#2F302F";
|
||||
boot.loader.grub.splashMode = mkDefault "normal";
|
||||
})
|
||||
|
||||
(mkIf cfg.enable {
|
||||
|
||||
boot.loader.grub.devices = optional (cfg.device != "") cfg.device;
|
||||
|
||||
boot.loader.grub.mirroredBoots = optionals (cfg.devices != [ ]) [
|
||||
{ path = "/boot"; inherit (cfg) devices; inherit (efi) efiSysMountPoint; }
|
||||
];
|
||||
|
||||
boot.loader.supportsInitrdSecrets = true;
|
||||
|
||||
system.build.installBootLoader =
|
||||
let
|
||||
install-grub-pl = pkgs.substituteAll {
|
||||
src = ./install-grub.pl;
|
||||
utillinux = pkgs.util-linux;
|
||||
btrfsprogs = pkgs.btrfs-progs;
|
||||
};
|
||||
perl = pkgs.perl.withPackages (p: with p; [
|
||||
FileSlurp FileCopyRecursive
|
||||
XMLLibXML XMLSAX XMLSAXBase
|
||||
ListCompare JSON
|
||||
]);
|
||||
in pkgs.writeScript "install-grub.sh" (''
|
||||
#!${pkgs.runtimeShell}
|
||||
set -e
|
||||
${optionalString cfg.enableCryptodisk "export GRUB_ENABLE_CRYPTODISK=y"}
|
||||
'' + flip concatMapStrings cfg.mirroredBoots (args: ''
|
||||
${perl}/bin/perl ${install-grub-pl} ${grubConfig args} $@
|
||||
'') + cfg.extraInstallCommands);
|
||||
|
||||
system.build.grub = grub;
|
||||
|
||||
# Common attribute for boot loaders so only one of them can be
|
||||
# set at once.
|
||||
system.boot.loader.id = "grub";
|
||||
|
||||
environment.systemPackages = optional (grub != null) grub;
|
||||
|
||||
boot.loader.grub.extraPrepareConfig =
|
||||
concatStrings (mapAttrsToList (n: v: ''
|
||||
${pkgs.coreutils}/bin/cp -pf "${v}" "@bootPath@/${n}"
|
||||
'') config.boot.loader.grub.extraFiles);
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = !cfg.zfsSupport || cfg.version == 2;
|
||||
message = "Only GRUB version 2 provides ZFS support";
|
||||
}
|
||||
{
|
||||
assertion = cfg.mirroredBoots != [ ];
|
||||
message = "You must set the option ‘boot.loader.grub.devices’ or "
|
||||
+ "'boot.loader.grub.mirroredBoots' to make the system bootable.";
|
||||
}
|
||||
{
|
||||
assertion = cfg.efiSupport || all (c: c < 2) (mapAttrsToList (n: c: if n == "nodev" then 0 else c) bootDeviceCounters);
|
||||
message = "You cannot have duplicated devices in mirroredBoots";
|
||||
}
|
||||
{
|
||||
assertion = !cfg.trustedBoot.enable || cfg.version == 2;
|
||||
message = "Trusted GRUB is only available for GRUB 2";
|
||||
}
|
||||
{
|
||||
assertion = !cfg.efiSupport || !cfg.trustedBoot.enable;
|
||||
message = "Trusted GRUB does not have EFI support";
|
||||
}
|
||||
{
|
||||
assertion = !cfg.zfsSupport || !cfg.trustedBoot.enable;
|
||||
message = "Trusted GRUB does not have ZFS support";
|
||||
}
|
||||
{
|
||||
assertion = !cfg.trustedBoot.enable || cfg.trustedBoot.systemHasTPM == "YES_TPM_is_activated";
|
||||
message = "Trusted GRUB can break the system! Confirm that the system has an activated TPM by setting 'systemHasTPM'.";
|
||||
}
|
||||
{
|
||||
assertion = cfg.efiInstallAsRemovable -> cfg.efiSupport;
|
||||
message = "If you wish to to use boot.loader.grub.efiInstallAsRemovable, then turn on boot.loader.grub.efiSupport";
|
||||
}
|
||||
{
|
||||
assertion = cfg.efiInstallAsRemovable -> !config.boot.loader.efi.canTouchEfiVariables;
|
||||
message = "If you wish to to use boot.loader.grub.efiInstallAsRemovable, then turn off boot.loader.efi.canTouchEfiVariables";
|
||||
}
|
||||
] ++ flip concatMap cfg.mirroredBoots (args: [
|
||||
{
|
||||
assertion = args.devices != [ ];
|
||||
message = "A boot path cannot have an empty devices string in ${args.path}";
|
||||
}
|
||||
{
|
||||
assertion = hasPrefix "/" args.path;
|
||||
message = "Boot paths must be absolute, not ${args.path}";
|
||||
}
|
||||
{
|
||||
assertion = if args.efiSysMountPoint == null then true else hasPrefix "/" args.efiSysMountPoint;
|
||||
message = "EFI paths must be absolute, not ${args.efiSysMountPoint}";
|
||||
}
|
||||
] ++ forEach args.devices (device: {
|
||||
assertion = device == "nodev" || hasPrefix "/" device;
|
||||
message = "GRUB devices must be absolute paths, not ${device} in ${args.path}";
|
||||
}));
|
||||
})
|
||||
|
||||
];
|
||||
|
||||
|
||||
imports =
|
||||
[ (mkRemovedOptionModule [ "boot" "loader" "grub" "bootDevice" ] "")
|
||||
(mkRenamedOptionModule [ "boot" "copyKernels" ] [ "boot" "loader" "grub" "copyKernels" ])
|
||||
(mkRenamedOptionModule [ "boot" "extraGrubEntries" ] [ "boot" "loader" "grub" "extraEntries" ])
|
||||
(mkRenamedOptionModule [ "boot" "extraGrubEntriesBeforeNixos" ] [ "boot" "loader" "grub" "extraEntriesBeforeNixOS" ])
|
||||
(mkRenamedOptionModule [ "boot" "grubDevice" ] [ "boot" "loader" "grub" "device" ])
|
||||
(mkRenamedOptionModule [ "boot" "bootMount" ] [ "boot" "loader" "grub" "bootDevice" ])
|
||||
(mkRenamedOptionModule [ "boot" "grubSplashImage" ] [ "boot" "loader" "grub" "splashImage" ])
|
||||
(mkRemovedOptionModule [ "boot" "loader" "grub" "extraInitrd" ] ''
|
||||
This option has been replaced with the bootloader agnostic
|
||||
boot.initrd.secrets option. To migrate to the initrd secrets system,
|
||||
extract the extraInitrd archive into your main filesystem:
|
||||
|
||||
# zcat /boot/extra_initramfs.gz | cpio -idvmD /etc/secrets/initrd
|
||||
/path/to/secret1
|
||||
/path/to/secret2
|
||||
|
||||
then replace boot.loader.grub.extraInitrd with boot.initrd.secrets:
|
||||
|
||||
boot.initrd.secrets = {
|
||||
"/path/to/secret1" = "/etc/secrets/initrd/path/to/secret1";
|
||||
"/path/to/secret2" = "/etc/secrets/initrd/path/to/secret2";
|
||||
};
|
||||
|
||||
See the boot.initrd.secrets option documentation for more information.
|
||||
'')
|
||||
];
|
||||
|
||||
}
|
||||
782
nixos/modules/system/boot/loader/grub/install-grub.pl
Normal file
782
nixos/modules/system/boot/loader/grub/install-grub.pl
Normal file
|
|
@ -0,0 +1,782 @@
|
|||
use strict;
|
||||
use warnings;
|
||||
use Class::Struct;
|
||||
use XML::LibXML;
|
||||
use File::Basename;
|
||||
use File::Path;
|
||||
use File::stat;
|
||||
use File::Copy;
|
||||
use File::Copy::Recursive qw(rcopy pathrm);
|
||||
use File::Slurp;
|
||||
use File::Temp;
|
||||
use JSON;
|
||||
use File::Find;
|
||||
require List::Compare;
|
||||
use POSIX;
|
||||
use Cwd;
|
||||
|
||||
# system.build.toplevel path
|
||||
my $defaultConfig = $ARGV[1] or die;
|
||||
|
||||
# Grub config XML generated by grubConfig function in grub.nix
|
||||
my $dom = XML::LibXML->load_xml(location => $ARGV[0]);
|
||||
|
||||
sub get { my ($name) = @_; return $dom->findvalue("/expr/attrs/attr[\@name = '$name']/*/\@value"); }
|
||||
|
||||
sub getList {
|
||||
my ($name) = @_;
|
||||
my @list = ();
|
||||
foreach my $entry ($dom->findnodes("/expr/attrs/attr[\@name = '$name']/list/string/\@value")) {
|
||||
$entry = $entry->findvalue(".") or die;
|
||||
push(@list, $entry);
|
||||
}
|
||||
return @list;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
sub runCommand {
|
||||
my ($cmd) = @_;
|
||||
open FILE, "$cmd 2>/dev/null |" or die "Failed to execute: $cmd\n";
|
||||
my @ret = <FILE>;
|
||||
close FILE;
|
||||
return ($?, @ret);
|
||||
}
|
||||
|
||||
my $grub = get("grub");
|
||||
my $grubVersion = int(get("version"));
|
||||
my $grubTarget = get("grubTarget");
|
||||
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 $splashMode = get("splashMode");
|
||||
my $entryOptions = get("entryOptions");
|
||||
my $subEntryOptions = get("subEntryOptions");
|
||||
my $backgroundColor = get("backgroundColor");
|
||||
my $configurationLimit = int(get("configurationLimit"));
|
||||
my $copyKernels = get("copyKernels") eq "true";
|
||||
my $timeout = int(get("timeout"));
|
||||
my $defaultEntry = get("default");
|
||||
my $fsIdentifier = get("fsIdentifier");
|
||||
my $grubEfi = get("grubEfi");
|
||||
my $grubTargetEfi = get("grubTargetEfi");
|
||||
my $bootPath = get("bootPath");
|
||||
my $storePath = get("storePath");
|
||||
my $canTouchEfiVariables = get("canTouchEfiVariables");
|
||||
my $efiInstallAsRemovable = get("efiInstallAsRemovable");
|
||||
my $efiSysMountPoint = get("efiSysMountPoint");
|
||||
my $gfxmodeEfi = get("gfxmodeEfi");
|
||||
my $gfxmodeBios = get("gfxmodeBios");
|
||||
my $gfxpayloadEfi = get("gfxpayloadEfi");
|
||||
my $gfxpayloadBios = get("gfxpayloadBios");
|
||||
my $bootloaderId = get("bootloaderId");
|
||||
my $forceInstall = get("forceInstall");
|
||||
my $font = get("font");
|
||||
my $theme = get("theme");
|
||||
my $saveDefault = $defaultEntry eq "saved";
|
||||
$ENV{'PATH'} = get("path");
|
||||
|
||||
die "unsupported GRUB version\n" if $grubVersion != 1 && $grubVersion != 2;
|
||||
|
||||
print STDERR "updating GRUB $grubVersion menu...\n";
|
||||
|
||||
mkpath("$bootPath/grub", 0, 0700);
|
||||
|
||||
# Discover whether the bootPath is on the same filesystem as / and
|
||||
# /nix/store. If not, then all kernels and initrds must be copied to
|
||||
# the bootPath.
|
||||
if (stat($bootPath)->dev != stat("/nix/store")->dev) {
|
||||
$copyKernels = 1;
|
||||
}
|
||||
|
||||
# Discover information about the location of the bootPath
|
||||
struct(Fs => {
|
||||
device => '$',
|
||||
type => '$',
|
||||
mount => '$',
|
||||
});
|
||||
sub PathInMount {
|
||||
my ($path, $mount) = @_;
|
||||
my @splitMount = split /\//, $mount;
|
||||
my @splitPath = split /\//, $path;
|
||||
if ($#splitPath < $#splitMount) {
|
||||
return 0;
|
||||
}
|
||||
for (my $i = 0; $i <= $#splitMount; $i++) {
|
||||
if ($splitMount[$i] ne $splitPath[$i]) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
# Figure out what filesystem is used for the directory with init/initrd/kernel files
|
||||
sub GetFs {
|
||||
my ($dir) = @_;
|
||||
my $bestFs = Fs->new(device => "", type => "", mount => "");
|
||||
foreach my $fs (read_file("/proc/self/mountinfo")) {
|
||||
chomp $fs;
|
||||
my @fields = split / /, $fs;
|
||||
my $mountPoint = $fields[4];
|
||||
next unless -d $mountPoint;
|
||||
my @mountOptions = split /,/, $fields[5];
|
||||
|
||||
# Skip the optional fields.
|
||||
my $n = 6; $n++ while $fields[$n] ne "-"; $n++;
|
||||
my $fsType = $fields[$n];
|
||||
my $device = $fields[$n + 1];
|
||||
my @superOptions = split /,/, $fields[$n + 2];
|
||||
|
||||
# Skip the bind-mount on /nix/store.
|
||||
next if $mountPoint eq "/nix/store" && (grep { $_ eq "rw" } @superOptions);
|
||||
# Skip mount point generated by systemd-efi-boot-generator?
|
||||
next if $fsType eq "autofs";
|
||||
|
||||
# Ensure this matches the intended directory
|
||||
next unless PathInMount($dir, $mountPoint);
|
||||
|
||||
# Is it better than our current match?
|
||||
if (length($mountPoint) > length($bestFs->mount)) {
|
||||
$bestFs = Fs->new(device => $device, type => $fsType, mount => $mountPoint);
|
||||
}
|
||||
}
|
||||
return $bestFs;
|
||||
}
|
||||
struct (Grub => {
|
||||
path => '$',
|
||||
search => '$',
|
||||
});
|
||||
my $driveid = 1;
|
||||
sub GrubFs {
|
||||
my ($dir) = @_;
|
||||
my $fs = GetFs($dir);
|
||||
my $path = substr($dir, length($fs->mount));
|
||||
if (substr($path, 0, 1) ne "/") {
|
||||
$path = "/$path";
|
||||
}
|
||||
my $search = "";
|
||||
|
||||
if ($grubVersion > 1) {
|
||||
# ZFS is completely separate logic as zpools are always identified by a label
|
||||
# or custom UUID
|
||||
if ($fs->type eq 'zfs') {
|
||||
my $sid = index($fs->device, '/');
|
||||
|
||||
if ($sid < 0) {
|
||||
$search = '--label ' . $fs->device;
|
||||
$path = '/@' . $path;
|
||||
} else {
|
||||
$search = '--label ' . substr($fs->device, 0, $sid);
|
||||
$path = '/' . substr($fs->device, $sid) . '/@' . $path;
|
||||
}
|
||||
} else {
|
||||
my %types = ('uuid' => '--fs-uuid', 'label' => '--label');
|
||||
|
||||
if ($fsIdentifier eq 'provided') {
|
||||
# If the provided dev is identifying the partition using a label or uuid,
|
||||
# we should get the label / uuid and do a proper search
|
||||
my @matches = $fs->device =~ m/\/dev\/disk\/by-(label|uuid)\/(.*)/;
|
||||
if ($#matches > 1) {
|
||||
die "Too many matched devices"
|
||||
} elsif ($#matches == 1) {
|
||||
$search = "$types{$matches[0]} $matches[1]"
|
||||
}
|
||||
} else {
|
||||
# Determine the identifying type
|
||||
$search = $types{$fsIdentifier} . ' ';
|
||||
|
||||
# Based on the type pull in the identifier from the system
|
||||
my ($status, @devInfo) = runCommand("@utillinux@/bin/blkid -o export @{[$fs->device]}");
|
||||
if ($status != 0) {
|
||||
die "Failed to get blkid info (returned $status) for @{[$fs->mount]} on @{[$fs->device]}";
|
||||
}
|
||||
my @matches = join("", @devInfo) =~ m/@{[uc $fsIdentifier]}=([^\n]*)/;
|
||||
if ($#matches != 0) {
|
||||
die "Couldn't find a $types{$fsIdentifier} for @{[$fs->device]}\n"
|
||||
}
|
||||
$search .= $matches[0];
|
||||
}
|
||||
|
||||
# BTRFS is a special case in that we need to fix the referrenced path based on subvolumes
|
||||
if ($fs->type eq 'btrfs') {
|
||||
my ($status, @id_info) = runCommand("@btrfsprogs@/bin/btrfs subvol show @{[$fs->mount]}");
|
||||
if ($status != 0) {
|
||||
die "Failed to retrieve subvolume info for @{[$fs->mount]}\n";
|
||||
}
|
||||
my @ids = join("\n", @id_info) =~ m/^(?!\/\n).*Subvolume ID:[ \t\n]*([0-9]+)/s;
|
||||
if ($#ids > 0) {
|
||||
die "Btrfs subvol name for @{[$fs->device]} listed multiple times in mount\n"
|
||||
} elsif ($#ids == 0) {
|
||||
my ($status, @path_info) = runCommand("@btrfsprogs@/bin/btrfs subvol list @{[$fs->mount]}");
|
||||
if ($status != 0) {
|
||||
die "Failed to find @{[$fs->mount]} subvolume id from btrfs\n";
|
||||
}
|
||||
my @paths = join("", @path_info) =~ m/ID $ids[0] [^\n]* path ([^\n]*)/;
|
||||
if ($#paths > 0) {
|
||||
die "Btrfs returned multiple paths for a single subvolume id, mountpoint @{[$fs->mount]}\n";
|
||||
} elsif ($#paths != 0) {
|
||||
die "Btrfs did not return a path for the subvolume at @{[$fs->mount]}\n";
|
||||
}
|
||||
$path = "/$paths[0]$path";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (not $search eq "") {
|
||||
$search = "search --set=drive$driveid " . $search;
|
||||
$path = "(\$drive$driveid)$path";
|
||||
$driveid += 1;
|
||||
}
|
||||
}
|
||||
return Grub->new(path => $path, search => $search);
|
||||
}
|
||||
my $grubBoot = GrubFs($bootPath);
|
||||
my $grubStore;
|
||||
if ($copyKernels == 0) {
|
||||
$grubStore = GrubFs($storePath);
|
||||
}
|
||||
|
||||
# Generate the header.
|
||||
my $conf .= "# Automatically generated. DO NOT EDIT THIS FILE!\n";
|
||||
|
||||
if ($grubVersion == 1) {
|
||||
# $defaultEntry might be "saved", indicating that we want to use the last selected configuration as default.
|
||||
# Incidentally this is already the correct value for the grub 1 config to achieve this behaviour.
|
||||
$conf .= "
|
||||
default $defaultEntry
|
||||
timeout $timeout
|
||||
";
|
||||
if ($splashImage) {
|
||||
copy $splashImage, "$bootPath/background.xpm.gz" or die "cannot copy $splashImage to $bootPath: $!\n";
|
||||
$conf .= "splashimage " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background.xpm.gz\n";
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
my @users = ();
|
||||
foreach my $user ($dom->findnodes('/expr/attrs/attr[@name = "users"]/attrs/attr')) {
|
||||
my $name = $user->findvalue('@name') or die;
|
||||
my $hashedPassword = $user->findvalue('./attrs/attr[@name = "hashedPassword"]/string/@value');
|
||||
my $hashedPasswordFile = $user->findvalue('./attrs/attr[@name = "hashedPasswordFile"]/string/@value');
|
||||
my $password = $user->findvalue('./attrs/attr[@name = "password"]/string/@value');
|
||||
my $passwordFile = $user->findvalue('./attrs/attr[@name = "passwordFile"]/string/@value');
|
||||
|
||||
if ($hashedPasswordFile) {
|
||||
open(my $f, '<', $hashedPasswordFile) or die "Can't read file '$hashedPasswordFile'!";
|
||||
$hashedPassword = <$f>;
|
||||
chomp $hashedPassword;
|
||||
}
|
||||
if ($passwordFile) {
|
||||
open(my $f, '<', $passwordFile) or die "Can't read file '$passwordFile'!";
|
||||
$password = <$f>;
|
||||
chomp $password;
|
||||
}
|
||||
|
||||
if ($hashedPassword) {
|
||||
if (index($hashedPassword, "grub.pbkdf2.") == 0) {
|
||||
$conf .= "\npassword_pbkdf2 $name $hashedPassword";
|
||||
}
|
||||
else {
|
||||
die "Password hash for GRUB user '$name' is not valid!";
|
||||
}
|
||||
}
|
||||
elsif ($password) {
|
||||
$conf .= "\npassword $name $password";
|
||||
}
|
||||
else {
|
||||
die "GRUB user '$name' has no password!";
|
||||
}
|
||||
push(@users, $name);
|
||||
}
|
||||
if (@users) {
|
||||
$conf .= "\nset superusers=\"" . join(' ',@users) . "\"\n";
|
||||
}
|
||||
|
||||
if ($copyKernels == 0) {
|
||||
$conf .= "
|
||||
" . $grubStore->search;
|
||||
}
|
||||
# FIXME: should use grub-mkconfig.
|
||||
my $defaultEntryText = $defaultEntry;
|
||||
if ($saveDefault) {
|
||||
$defaultEntryText = "\"\${saved_entry}\"";
|
||||
}
|
||||
$conf .= "
|
||||
" . $grubBoot->search . "
|
||||
if [ -s \$prefix/grubenv ]; then
|
||||
load_env
|
||||
fi
|
||||
|
||||
# ‘grub-reboot’ sets a one-time saved entry, which we process here and
|
||||
# then delete.
|
||||
if [ \"\${next_entry}\" ]; then
|
||||
set default=\"\${next_entry}\"
|
||||
set next_entry=
|
||||
save_env next_entry
|
||||
set timeout=1
|
||||
set boot_once=true
|
||||
else
|
||||
set default=$defaultEntryText
|
||||
set timeout=$timeout
|
||||
fi
|
||||
|
||||
function savedefault {
|
||||
if [ -z \"\${boot_once}\"]; then
|
||||
saved_entry=\"\${chosen}\"
|
||||
save_env saved_entry
|
||||
fi
|
||||
}
|
||||
|
||||
# Setup the graphics stack for bios and efi systems
|
||||
if [ \"\${grub_platform}\" = \"efi\" ]; then
|
||||
insmod efi_gop
|
||||
insmod efi_uga
|
||||
else
|
||||
insmod vbe
|
||||
fi
|
||||
";
|
||||
|
||||
if ($font) {
|
||||
copy $font, "$bootPath/converted-font.pf2" or die "cannot copy $font to $bootPath: $!\n";
|
||||
$conf .= "
|
||||
insmod font
|
||||
if loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/converted-font.pf2; then
|
||||
insmod gfxterm
|
||||
if [ \"\${grub_platform}\" = \"efi\" ]; then
|
||||
set gfxmode=$gfxmodeEfi
|
||||
set gfxpayload=$gfxpayloadEfi
|
||||
else
|
||||
set gfxmode=$gfxmodeBios
|
||||
set gfxpayload=$gfxpayloadBios
|
||||
fi
|
||||
terminal_output gfxterm
|
||||
fi
|
||||
";
|
||||
}
|
||||
if ($splashImage) {
|
||||
# Keeps the image's extension.
|
||||
my ($filename, $dirs, $suffix) = fileparse($splashImage, qr"\..[^.]*$");
|
||||
# The module for jpg is jpeg.
|
||||
if ($suffix eq ".jpg") {
|
||||
$suffix = ".jpeg";
|
||||
}
|
||||
if ($backgroundColor) {
|
||||
$conf .= "
|
||||
background_color '$backgroundColor'
|
||||
";
|
||||
}
|
||||
copy $splashImage, "$bootPath/background$suffix" or die "cannot copy $splashImage to $bootPath: $!\n";
|
||||
$conf .= "
|
||||
insmod " . substr($suffix, 1) . "
|
||||
if background_image --mode '$splashMode' " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background$suffix; 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
|
||||
";
|
||||
}
|
||||
|
||||
rmtree("$bootPath/theme") or die "cannot clean up theme folder in $bootPath\n" if -e "$bootPath/theme";
|
||||
|
||||
if ($theme) {
|
||||
# Copy theme
|
||||
rcopy($theme, "$bootPath/theme") or die "cannot copy $theme to $bootPath\n";
|
||||
$conf .= "
|
||||
# Sets theme.
|
||||
set theme=" . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/theme.txt
|
||||
export theme
|
||||
# Load theme fonts, if any
|
||||
";
|
||||
|
||||
find( { wanted => sub {
|
||||
if ($_ =~ /\.pf2$/i) {
|
||||
$font = File::Spec->abs2rel($File::Find::name, $theme);
|
||||
$conf .= "
|
||||
loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/$font
|
||||
";
|
||||
}
|
||||
}, no_chdir => 1 }, $theme );
|
||||
}
|
||||
}
|
||||
|
||||
$conf .= "$extraConfig\n";
|
||||
|
||||
|
||||
# Generate the menu entries.
|
||||
$conf .= "\n";
|
||||
|
||||
my %copied;
|
||||
mkpath("$bootPath/kernels", 0, 0755) if $copyKernels;
|
||||
|
||||
sub copyToKernelsDir {
|
||||
my ($path) = @_;
|
||||
return $grubStore->path . substr($path, length("/nix/store")) unless $copyKernels;
|
||||
$path =~ /\/nix\/store\/(.*)/ or die;
|
||||
my $name = $1; $name =~ s/\//-/g;
|
||||
my $dst = "$bootPath/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 ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$name";
|
||||
}
|
||||
|
||||
sub addEntry {
|
||||
my ($name, $path, $options) = @_;
|
||||
return unless -e "$path/kernel" && -e "$path/initrd";
|
||||
|
||||
my $kernel = copyToKernelsDir(Cwd::abs_path("$path/kernel"));
|
||||
my $initrd = copyToKernelsDir(Cwd::abs_path("$path/initrd"));
|
||||
|
||||
# Include second initrd with secrets
|
||||
if (-e -x "$path/append-initrd-secrets") {
|
||||
my $initrdName = basename($initrd);
|
||||
my $initrdSecretsPath = "$bootPath/kernels/$initrdName-secrets";
|
||||
|
||||
mkpath(dirname($initrdSecretsPath), 0, 0755);
|
||||
my $oldUmask = umask;
|
||||
# Make sure initrd is not world readable (won't work if /boot is FAT)
|
||||
umask 0137;
|
||||
my $initrdSecretsPathTemp = File::Temp::mktemp("$initrdSecretsPath.XXXXXXXX");
|
||||
system("$path/append-initrd-secrets", $initrdSecretsPathTemp) == 0 or die "failed to create initrd secrets: $!\n";
|
||||
# Check whether any secrets were actually added
|
||||
if (-e $initrdSecretsPathTemp && ! -z _) {
|
||||
rename $initrdSecretsPathTemp, $initrdSecretsPath or die "failed to move initrd secrets into place: $!\n";
|
||||
$copied{$initrdSecretsPath} = 1;
|
||||
$initrd .= " " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$initrdName-secrets";
|
||||
} else {
|
||||
unlink $initrdSecretsPathTemp;
|
||||
rmdir dirname($initrdSecretsPathTemp);
|
||||
}
|
||||
umask $oldUmask;
|
||||
}
|
||||
|
||||
my $xen = -e "$path/xen.gz" ? copyToKernelsDir(Cwd::abs_path("$path/xen.gz")) : undef;
|
||||
|
||||
# FIXME: $confName
|
||||
|
||||
my $kernelParams =
|
||||
"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";
|
||||
if ($saveDefault) {
|
||||
$conf .= " savedefault\n";
|
||||
}
|
||||
$conf .= "\n";
|
||||
} else {
|
||||
$conf .= "menuentry \"$name\" " . ($options||"") . " {\n";
|
||||
if ($saveDefault) {
|
||||
$conf .= " savedefault\n";
|
||||
}
|
||||
$conf .= $grubBoot->search . "\n";
|
||||
if ($copyKernels == 0) {
|
||||
$conf .= $grubStore->search . "\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, $entryOptions);
|
||||
|
||||
$conf .= "$extraEntries\n" unless $extraEntriesBeforeNixOS;
|
||||
|
||||
# Find all the children of the current default configuration
|
||||
# Do not search for grand children
|
||||
my @links = sort (glob "$defaultConfig/specialisation/*");
|
||||
foreach my $link (@links) {
|
||||
|
||||
my $entryName = "";
|
||||
|
||||
my $cfgName = readFile("$link/configuration-name");
|
||||
|
||||
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]);
|
||||
|
||||
if ($cfgName) {
|
||||
$entryName = $cfgName;
|
||||
} else {
|
||||
my $linkname = basename($link);
|
||||
$entryName = "($linkname - $date - $version)";
|
||||
}
|
||||
addEntry("NixOS - $entryName", $link);
|
||||
}
|
||||
|
||||
my $grubBootPath = $grubBoot->path;
|
||||
# extraEntries could refer to @bootRoot@, which we have to substitute
|
||||
$conf =~ s/\@bootRoot\@/$grubBootPath/g;
|
||||
|
||||
# Emit submenus for all system profiles.
|
||||
sub addProfile {
|
||||
my ($profile, $description) = @_;
|
||||
|
||||
# Add entries for all generations of this profile.
|
||||
$conf .= "submenu \"$description\" --class submenu {\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;
|
||||
if (! -e "$link/nixos-version") {
|
||||
warn "skipping corrupt system profile entry ‘$link’\n";
|
||||
next;
|
||||
}
|
||||
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, $subEntryOptions);
|
||||
}
|
||||
|
||||
$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'";
|
||||
}
|
||||
}
|
||||
|
||||
# extraPrepareConfig could refer to @bootPath@, which we have to substitute
|
||||
$extraPrepareConfig =~ s/\@bootPath\@/$bootPath/g;
|
||||
|
||||
# Run extraPrepareConfig in sh
|
||||
if ($extraPrepareConfig ne "") {
|
||||
system((get("shell"), "-c", $extraPrepareConfig));
|
||||
}
|
||||
|
||||
# write the GRUB config.
|
||||
my $confFile = $grubVersion == 1 ? "$bootPath/grub/menu.lst" : "$bootPath/grub/grub.cfg";
|
||||
my $tmpFile = $confFile . ".tmp";
|
||||
writeFile($tmpFile, $conf);
|
||||
|
||||
|
||||
# check whether to install GRUB EFI or not
|
||||
sub getEfiTarget {
|
||||
if ($grubVersion == 1) {
|
||||
return "no"
|
||||
} elsif (($grub ne "") && ($grubEfi ne "")) {
|
||||
# EFI can only be installed when target is set;
|
||||
# A target is also required then for non-EFI grub
|
||||
if (($grubTarget eq "") || ($grubTargetEfi eq "")) { die }
|
||||
else { return "both" }
|
||||
} elsif (($grub ne "") && ($grubEfi eq "")) {
|
||||
# TODO: It would be safer to disallow non-EFI grub installation if no taget is given.
|
||||
# If no target is given, then grub auto-detects the target which can lead to errors.
|
||||
# E.g. it seems as if grub would auto-detect a EFI target based on the availability
|
||||
# of a EFI partition.
|
||||
# However, it seems as auto-detection is currently relied on for non-x86_64 and non-i386
|
||||
# architectures in NixOS. That would have to be fixed in the nixos modules first.
|
||||
return "no"
|
||||
} elsif (($grub eq "") && ($grubEfi ne "")) {
|
||||
# EFI can only be installed when target is set;
|
||||
if ($grubTargetEfi eq "") { die }
|
||||
else {return "only" }
|
||||
} else {
|
||||
# prevent an installation if neither grub nor grubEfi is given
|
||||
return "neither"
|
||||
}
|
||||
}
|
||||
|
||||
my $efiTarget = getEfiTarget();
|
||||
|
||||
# Append entries detected by os-prober
|
||||
if (get("useOSProber") eq "true") {
|
||||
if ($saveDefault) {
|
||||
# os-prober will read this to determine if "savedefault" should be added to generated entries
|
||||
$ENV{'GRUB_SAVEDEFAULT'} = "true";
|
||||
}
|
||||
|
||||
my $targetpackage = ($efiTarget eq "no") ? $grub : $grubEfi;
|
||||
system(get("shell"), "-c", "pkgdatadir=$targetpackage/share/grub $targetpackage/etc/grub.d/30_os-prober >> $tmpFile");
|
||||
}
|
||||
|
||||
# Atomically switch to the new config
|
||||
rename $tmpFile, $confFile or die "cannot rename $tmpFile to $confFile: $!\n";
|
||||
|
||||
|
||||
# Remove obsolete files from $bootPath/kernels.
|
||||
foreach my $fn (glob "$bootPath/kernels/*") {
|
||||
next if defined $copied{$fn};
|
||||
print STDERR "removing obsolete file $fn\n";
|
||||
unlink $fn;
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# Install GRUB if the parameters changed from the last time we installed it.
|
||||
#
|
||||
|
||||
struct(GrubState => {
|
||||
name => '$',
|
||||
version => '$',
|
||||
efi => '$',
|
||||
devices => '$',
|
||||
efiMountPoint => '$',
|
||||
extraGrubInstallArgs => '@',
|
||||
});
|
||||
# If you add something to the state file, only add it to the end
|
||||
# because it is read line-by-line.
|
||||
sub readGrubState {
|
||||
my $defaultGrubState = GrubState->new(name => "", version => "", efi => "", devices => "", efiMountPoint => "", extraGrubInstallArgs => () );
|
||||
open FILE, "<$bootPath/grub/state" or return $defaultGrubState;
|
||||
local $/ = "\n";
|
||||
my $name = <FILE>;
|
||||
chomp($name);
|
||||
my $version = <FILE>;
|
||||
chomp($version);
|
||||
my $efi = <FILE>;
|
||||
chomp($efi);
|
||||
my $devices = <FILE>;
|
||||
chomp($devices);
|
||||
my $efiMountPoint = <FILE>;
|
||||
chomp($efiMountPoint);
|
||||
# Historically, arguments in the state file were one per each line, but that
|
||||
# gets really messy when newlines are involved, structured arguments
|
||||
# like lists are needed (they have to have a separator encoding), or even worse,
|
||||
# when we need to remove a setting in the future. Thus, the 6th line is a JSON
|
||||
# object that can store structured data, with named keys, and all new state
|
||||
# should go in there.
|
||||
my $jsonStateLine = <FILE>;
|
||||
# For historical reasons we do not check the values above for un-definedness
|
||||
# (that is, when the state file has too few lines and EOF is reached),
|
||||
# because the above come from the first version of this logic and are thus
|
||||
# guaranteed to be present.
|
||||
$jsonStateLine = defined $jsonStateLine ? $jsonStateLine : '{}'; # empty JSON object
|
||||
chomp($jsonStateLine);
|
||||
if ($jsonStateLine eq "") {
|
||||
$jsonStateLine = '{}'; # empty JSON object
|
||||
}
|
||||
my %jsonState = %{decode_json($jsonStateLine)};
|
||||
my @extraGrubInstallArgs = exists($jsonState{'extraGrubInstallArgs'}) ? @{$jsonState{'extraGrubInstallArgs'}} : ();
|
||||
close FILE;
|
||||
my $grubState = GrubState->new(name => $name, version => $version, efi => $efi, devices => $devices, efiMountPoint => $efiMountPoint, extraGrubInstallArgs => \@extraGrubInstallArgs );
|
||||
return $grubState
|
||||
}
|
||||
|
||||
my @deviceTargets = getList('devices');
|
||||
my $prevGrubState = readGrubState();
|
||||
my @prevDeviceTargets = split/,/, $prevGrubState->devices;
|
||||
my @extraGrubInstallArgs = getList('extraGrubInstallArgs');
|
||||
my @prevExtraGrubInstallArgs = @{$prevGrubState->extraGrubInstallArgs};
|
||||
|
||||
my $devicesDiffer = scalar (List::Compare->new( '-u', '-a', \@deviceTargets, \@prevDeviceTargets)->get_symmetric_difference());
|
||||
my $extraGrubInstallArgsDiffer = scalar (List::Compare->new( '-u', '-a', \@extraGrubInstallArgs, \@prevExtraGrubInstallArgs)->get_symmetric_difference());
|
||||
my $nameDiffer = get("fullName") ne $prevGrubState->name;
|
||||
my $versionDiffer = get("fullVersion") ne $prevGrubState->version;
|
||||
my $efiDiffer = $efiTarget ne $prevGrubState->efi;
|
||||
my $efiMountPointDiffer = $efiSysMountPoint ne $prevGrubState->efiMountPoint;
|
||||
if (($ENV{'NIXOS_INSTALL_GRUB'} // "") eq "1") {
|
||||
warn "NIXOS_INSTALL_GRUB env var deprecated, use NIXOS_INSTALL_BOOTLOADER";
|
||||
$ENV{'NIXOS_INSTALL_BOOTLOADER'} = "1";
|
||||
}
|
||||
my $requireNewInstall = $devicesDiffer || $extraGrubInstallArgsDiffer || $nameDiffer || $versionDiffer || $efiDiffer || $efiMountPointDiffer || (($ENV{'NIXOS_INSTALL_BOOTLOADER'} // "") eq "1");
|
||||
|
||||
# install a symlink so that grub can detect the boot drive
|
||||
my $tmpDir = File::Temp::tempdir(CLEANUP => 1) or die "Failed to create temporary space: $!";
|
||||
symlink "$bootPath", "$tmpDir/boot" or die "Failed to symlink $tmpDir/boot: $!";
|
||||
|
||||
# install non-EFI GRUB
|
||||
if (($requireNewInstall != 0) && ($efiTarget eq "no" || $efiTarget eq "both")) {
|
||||
foreach my $dev (@deviceTargets) {
|
||||
next if $dev eq "nodev";
|
||||
print STDERR "installing the GRUB $grubVersion boot loader on $dev...\n";
|
||||
my @command = ("$grub/sbin/grub-install", "--recheck", "--root-directory=$tmpDir", Cwd::abs_path($dev), @extraGrubInstallArgs);
|
||||
if ($forceInstall eq "true") {
|
||||
push @command, "--force";
|
||||
}
|
||||
if ($grubTarget ne "") {
|
||||
push @command, "--target=$grubTarget";
|
||||
}
|
||||
(system @command) == 0 or die "$0: installation of GRUB on $dev failed: $!\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# install EFI GRUB
|
||||
if (($requireNewInstall != 0) && ($efiTarget eq "only" || $efiTarget eq "both")) {
|
||||
print STDERR "installing the GRUB $grubVersion EFI boot loader into $efiSysMountPoint...\n";
|
||||
my @command = ("$grubEfi/sbin/grub-install", "--recheck", "--target=$grubTargetEfi", "--boot-directory=$bootPath", "--efi-directory=$efiSysMountPoint", @extraGrubInstallArgs);
|
||||
if ($forceInstall eq "true") {
|
||||
push @command, "--force";
|
||||
}
|
||||
if ($canTouchEfiVariables eq "true") {
|
||||
push @command, "--bootloader-id=$bootloaderId";
|
||||
} else {
|
||||
push @command, "--no-nvram";
|
||||
push @command, "--removable" if $efiInstallAsRemovable eq "true";
|
||||
}
|
||||
|
||||
(system @command) == 0 or die "$0: installation of GRUB EFI into $efiSysMountPoint failed: $!\n";
|
||||
}
|
||||
|
||||
|
||||
# update GRUB state file
|
||||
if ($requireNewInstall != 0) {
|
||||
# Temp file for atomic rename.
|
||||
my $stateFile = "$bootPath/grub/state";
|
||||
my $stateFileTmp = $stateFile . ".tmp";
|
||||
|
||||
open FILE, ">$stateFileTmp" or die "cannot create $stateFileTmp: $!\n";
|
||||
print FILE get("fullName"), "\n" or die;
|
||||
print FILE get("fullVersion"), "\n" or die;
|
||||
print FILE $efiTarget, "\n" or die;
|
||||
print FILE join( ",", @deviceTargets ), "\n" or die;
|
||||
print FILE $efiSysMountPoint, "\n" or die;
|
||||
my %jsonState = (
|
||||
extraGrubInstallArgs => \@extraGrubInstallArgs
|
||||
);
|
||||
my $jsonStateLine = encode_json(\%jsonState);
|
||||
print FILE $jsonStateLine, "\n" or die;
|
||||
close FILE or die;
|
||||
|
||||
# Atomically switch to the new state file
|
||||
rename $stateFileTmp, $stateFile or die "cannot rename $stateFileTmp to $stateFile: $!\n";
|
||||
}
|
||||
64
nixos/modules/system/boot/loader/grub/ipxe.nix
Normal file
64
nixos/modules/system/boot/loader/grub/ipxe.nix
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# This module adds a scripted iPXE entry to the GRUB boot menu.
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
scripts = builtins.attrNames config.boot.loader.grub.ipxe;
|
||||
|
||||
grubEntry = name:
|
||||
''
|
||||
menuentry "iPXE - ${name}" {
|
||||
linux16 @bootRoot@/ipxe.lkrn
|
||||
initrd16 @bootRoot@/${name}.ipxe
|
||||
}
|
||||
|
||||
'';
|
||||
|
||||
scriptFile = name:
|
||||
let
|
||||
value = builtins.getAttr name config.boot.loader.grub.ipxe;
|
||||
in
|
||||
if builtins.typeOf value == "path" then value
|
||||
else builtins.toFile "${name}.ipxe" value;
|
||||
in
|
||||
{
|
||||
options =
|
||||
{ boot.loader.grub.ipxe = mkOption {
|
||||
type = types.attrsOf (types.either types.path types.str);
|
||||
description =
|
||||
''
|
||||
Set of iPXE scripts available for
|
||||
booting from the GRUB boot menu.
|
||||
'';
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
{ demo = '''
|
||||
#!ipxe
|
||||
dhcp
|
||||
chain http://boot.ipxe.org/demo/boot.php
|
||||
''';
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf (builtins.length scripts != 0) {
|
||||
|
||||
boot.loader.grub.extraEntries =
|
||||
if config.boot.loader.grub.version == 2 then
|
||||
toString (map grubEntry scripts)
|
||||
else
|
||||
throw "iPXE is not supported with GRUB 1.";
|
||||
|
||||
boot.loader.grub.extraFiles =
|
||||
{ "ipxe.lkrn" = "${pkgs.ipxe}/ipxe.lkrn"; }
|
||||
//
|
||||
builtins.listToAttrs ( map
|
||||
(name: { name = name+".ipxe"; value = scriptFile name; })
|
||||
scripts
|
||||
);
|
||||
};
|
||||
|
||||
}
|
||||
116
nixos/modules/system/boot/loader/grub/memtest.nix
Normal file
116
nixos/modules/system/boot/loader/grub/memtest.nix
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
# This module adds Memtest86+/Memtest86 to the GRUB boot menu.
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
memtest86 = pkgs.memtest86plus;
|
||||
efiSupport = config.boot.loader.grub.efiSupport;
|
||||
cfg = config.boot.loader.grub.memtest86;
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
|
||||
boot.loader.grub.memtest86 = {
|
||||
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Make Memtest86+ (or MemTest86 if EFI support is enabled),
|
||||
a memory testing program, available from the
|
||||
GRUB boot menu. MemTest86 is an unfree program, so
|
||||
this requires <literal>allowUnfree</literal> to be set to
|
||||
<literal>true</literal>.
|
||||
'';
|
||||
};
|
||||
|
||||
params = mkOption {
|
||||
default = [];
|
||||
example = [ "console=ttyS0,115200" ];
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
Parameters added to the Memtest86+ command line. As of memtest86+ 5.01
|
||||
the following list of (apparently undocumented) parameters are
|
||||
accepted:
|
||||
|
||||
<itemizedlist>
|
||||
|
||||
<listitem>
|
||||
<para><literal>console=...</literal>, set up a serial console.
|
||||
Examples:
|
||||
<literal>console=ttyS0</literal>,
|
||||
<literal>console=ttyS0,9600</literal> or
|
||||
<literal>console=ttyS0,115200n8</literal>.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para><literal>btrace</literal>, enable boot trace.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para><literal>maxcpus=N</literal>, limit number of CPUs.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para><literal>onepass</literal>, run one pass and exit if there
|
||||
are no errors.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para><literal>tstlist=...</literal>, list of tests to run.
|
||||
Example: <literal>0,1,2</literal>.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para><literal>cpumask=...</literal>, set a CPU mask, to select CPUs
|
||||
to use for testing.</para>
|
||||
</listitem>
|
||||
|
||||
</itemizedlist>
|
||||
|
||||
This list of command line options was obtained by reading the
|
||||
Memtest86+ source code.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
config = mkMerge [
|
||||
(mkIf (cfg.enable && efiSupport) {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.params == [];
|
||||
message = "Parameters are not available for MemTest86";
|
||||
}
|
||||
];
|
||||
|
||||
boot.loader.grub.extraFiles = {
|
||||
"memtest86.efi" = "${pkgs.memtest86-efi}/BOOTX64.efi";
|
||||
};
|
||||
|
||||
boot.loader.grub.extraEntries = ''
|
||||
menuentry "Memtest86" {
|
||||
chainloader /memtest86.efi
|
||||
}
|
||||
'';
|
||||
})
|
||||
|
||||
(mkIf (cfg.enable && !efiSupport) {
|
||||
boot.loader.grub.extraEntries =
|
||||
if config.boot.loader.grub.version == 2 then
|
||||
''
|
||||
menuentry "Memtest86+" {
|
||||
linux16 @bootRoot@/memtest.bin ${toString cfg.params}
|
||||
}
|
||||
''
|
||||
else
|
||||
throw "Memtest86+ is not supported with GRUB 1.";
|
||||
|
||||
boot.loader.grub.extraFiles."memtest.bin" = "${memtest86}/memtest.bin";
|
||||
})
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
#! @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: init-script-builder.sh DEFAULT-CONFIG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
defaultConfig="$1"
|
||||
|
||||
|
||||
[ "$(stat -f -c '%i' /)" = "$(stat -f -c '%i' /boot)" ] || {
|
||||
# see grub-menu-builder.sh
|
||||
echo "WARNING: /boot being on a different filesystem not supported by init-script-builder.sh"
|
||||
}
|
||||
|
||||
|
||||
|
||||
target="/sbin/init"
|
||||
targetOther="/boot/init-other-configurations-contents.txt"
|
||||
|
||||
tmp="$target.tmp"
|
||||
tmpOther="$targetOther.tmp"
|
||||
|
||||
|
||||
configurationCounter=0
|
||||
numAlienEntries=`cat <<EOF | egrep '^[[:space:]]*title' | wc -l
|
||||
@extraEntries@
|
||||
EOF`
|
||||
|
||||
|
||||
|
||||
|
||||
# Add an entry to $targetOther
|
||||
addEntry() {
|
||||
local name="$1"
|
||||
local path="$2"
|
||||
local shortSuffix="$3"
|
||||
|
||||
configurationCounter=$((configurationCounter + 1))
|
||||
|
||||
local stage2=$path/init
|
||||
|
||||
content="$(
|
||||
echo "#!/bin/sh"
|
||||
echo "# $name"
|
||||
echo "# created by init-script-builder.sh"
|
||||
echo "exec $stage2"
|
||||
)"
|
||||
|
||||
[ "$path" != "$defaultConfig" ] || {
|
||||
echo "$content" > $tmp
|
||||
echo "# older configurations: $targetOther" >> $tmp
|
||||
chmod +x $tmp
|
||||
}
|
||||
|
||||
echo -e "$content\n\n" >> $tmpOther
|
||||
}
|
||||
|
||||
|
||||
mkdir -p /boot /sbin
|
||||
|
||||
addEntry "NixOS - Default" $defaultConfig ""
|
||||
|
||||
# Add all generations of the system profile to the menu, in reverse
|
||||
# (most recent to least recent) order.
|
||||
for link in $((ls -d $defaultConfig/specialisation/* ) | sort -n); do
|
||||
date=$(stat --printf="%y\n" $link | sed 's/\..*//')
|
||||
addEntry "NixOS - variation" $link ""
|
||||
done
|
||||
|
||||
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
|
||||
date=$(stat --printf="%y\n" $link | sed 's/\..*//')
|
||||
if [ -d $link/kernel ]; then
|
||||
kernelVersion=$(cd $(dirname $(readlink -f $link/kernel))/lib/modules && echo *)
|
||||
suffix="($date - $kernelVersion)"
|
||||
else
|
||||
suffix="($date)"
|
||||
fi
|
||||
addEntry "NixOS - Configuration $generation $suffix" $link "$generation ($date)"
|
||||
done
|
||||
|
||||
mv $tmpOther $targetOther
|
||||
mv $tmp $target
|
||||
51
nixos/modules/system/boot/loader/init-script/init-script.nix
Normal file
51
nixos/modules/system/boot/loader/init-script/init-script.nix
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
initScriptBuilder = pkgs.substituteAll {
|
||||
src = ./init-script-builder.sh;
|
||||
isExecutable = true;
|
||||
inherit (pkgs) bash;
|
||||
path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
|
||||
};
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
boot.loader.initScript = {
|
||||
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Some systems require a /sbin/init script which is started.
|
||||
Or having it makes starting NixOS easier.
|
||||
This applies to some kind of hosting services and user mode linux.
|
||||
|
||||
Additionally this script will create
|
||||
/boot/init-other-configurations-contents.txt containing
|
||||
contents of remaining configurations. You can copy paste them into
|
||||
/sbin/init manually running a rescue system or such.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf config.boot.loader.initScript.enable {
|
||||
|
||||
system.build.installBootLoader = initScriptBuilder;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
20
nixos/modules/system/boot/loader/loader.nix
Normal file
20
nixos/modules/system/boot/loader/loader.nix
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{ lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
{
|
||||
imports = [
|
||||
(mkRenamedOptionModule [ "boot" "loader" "grub" "timeout" ] [ "boot" "loader" "timeout" ])
|
||||
(mkRenamedOptionModule [ "boot" "loader" "gummiboot" "timeout" ] [ "boot" "loader" "timeout" ])
|
||||
];
|
||||
|
||||
options = {
|
||||
boot.loader.timeout = mkOption {
|
||||
default = 5;
|
||||
type = types.nullOr types.int;
|
||||
description = ''
|
||||
Timeout (in seconds) until loader boots the default menu item. Use null if the loader menu should be displayed indefinitely.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{ pkgs, configTxt, firmware ? pkgs.raspberrypifw }:
|
||||
|
||||
pkgs.substituteAll {
|
||||
src = ./raspberrypi-builder.sh;
|
||||
isExecutable = true;
|
||||
inherit (pkgs) bash;
|
||||
path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
|
||||
inherit firmware configTxt;
|
||||
}
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
#! @bash@/bin/sh
|
||||
|
||||
# This can end up being called disregarding the shebang.
|
||||
set -e
|
||||
|
||||
shopt -s nullglob
|
||||
|
||||
export PATH=/empty
|
||||
for i in @path@; do PATH=$PATH:$i/bin; done
|
||||
|
||||
usage() {
|
||||
echo "usage: $0 -c <path-to-default-configuration> [-d <boot-dir>]" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
default= # Default configuration
|
||||
target=/boot # Target directory
|
||||
|
||||
while getopts "c:d:" opt; do
|
||||
case "$opt" in
|
||||
c) default="$OPTARG" ;;
|
||||
d) target="$OPTARG" ;;
|
||||
\?) usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "updating the boot generations directory..."
|
||||
|
||||
mkdir -p $target/old
|
||||
|
||||
# Convert a path to a file in the Nix store such as
|
||||
# /nix/store/<hash>-<name>/file to <hash>-<name>-<file>.
|
||||
cleanName() {
|
||||
local path="$1"
|
||||
echo "$path" | sed 's|^/nix/store/||' | sed 's|/|-|g'
|
||||
}
|
||||
|
||||
# Copy a file from the Nix store to $target/kernels.
|
||||
declare -A filesCopied
|
||||
|
||||
copyToKernelsDir() {
|
||||
local src="$1"
|
||||
local dst="$target/old/$(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
|
||||
}
|
||||
|
||||
copyForced() {
|
||||
local src="$1"
|
||||
local dst="$2"
|
||||
cp $src $dst.tmp
|
||||
mv $dst.tmp $dst
|
||||
}
|
||||
|
||||
outdir=$target/old
|
||||
mkdir -p $outdir || true
|
||||
|
||||
# Copy its kernel and initrd to $target/old.
|
||||
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)
|
||||
local dtb_path=$(readlink -f $path/dtbs)
|
||||
|
||||
if test -n "@copyKernels@"; then
|
||||
copyToKernelsDir $kernel; kernel=$result
|
||||
copyToKernelsDir $initrd; initrd=$result
|
||||
fi
|
||||
|
||||
echo $(readlink -f $path) > $outdir/$generation-system
|
||||
echo $(readlink -f $path/init) > $outdir/$generation-init
|
||||
cp $path/kernel-params $outdir/$generation-cmdline.txt
|
||||
echo $initrd > $outdir/$generation-initrd
|
||||
echo $kernel > $outdir/$generation-kernel
|
||||
|
||||
if test "$generation" = "default"; then
|
||||
copyForced $kernel $target/kernel.img
|
||||
copyForced $initrd $target/initrd
|
||||
for dtb in $dtb_path/{broadcom,}/bcm*.dtb; do
|
||||
dst="$target/$(basename $dtb)"
|
||||
copyForced $dtb "$dst"
|
||||
filesCopied[$dst]=1
|
||||
done
|
||||
cp "$(readlink -f "$path/init")" $target/nixos-init
|
||||
echo "`cat $path/kernel-params` init=$path/init" >$target/cmdline.txt
|
||||
fi
|
||||
}
|
||||
|
||||
addEntry $default default
|
||||
|
||||
# Add all generations of the system profile to the menu, 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
|
||||
|
||||
# Add the firmware files
|
||||
fwdir=@firmware@/share/raspberrypi/boot/
|
||||
copyForced $fwdir/bootcode.bin $target/bootcode.bin
|
||||
copyForced $fwdir/fixup.dat $target/fixup.dat
|
||||
copyForced $fwdir/fixup4.dat $target/fixup4.dat
|
||||
copyForced $fwdir/fixup4cd.dat $target/fixup4cd.dat
|
||||
copyForced $fwdir/fixup4db.dat $target/fixup4db.dat
|
||||
copyForced $fwdir/fixup4x.dat $target/fixup4x.dat
|
||||
copyForced $fwdir/fixup_cd.dat $target/fixup_cd.dat
|
||||
copyForced $fwdir/fixup_db.dat $target/fixup_db.dat
|
||||
copyForced $fwdir/fixup_x.dat $target/fixup_x.dat
|
||||
copyForced $fwdir/start.elf $target/start.elf
|
||||
copyForced $fwdir/start4.elf $target/start4.elf
|
||||
copyForced $fwdir/start4cd.elf $target/start4cd.elf
|
||||
copyForced $fwdir/start4db.elf $target/start4db.elf
|
||||
copyForced $fwdir/start4x.elf $target/start4x.elf
|
||||
copyForced $fwdir/start_cd.elf $target/start_cd.elf
|
||||
copyForced $fwdir/start_db.elf $target/start_db.elf
|
||||
copyForced $fwdir/start_x.elf $target/start_x.elf
|
||||
|
||||
# Add the config.txt
|
||||
copyForced @configTxt@ $target/config.txt
|
||||
|
||||
# Remove obsolete files from $target and $target/old.
|
||||
for fn in $target/old/*linux* $target/old/*initrd-initrd* $target/bcm*.dtb; do
|
||||
if ! test "${filesCopied[$fn]}" = 1; then
|
||||
rm -vf -- "$fn"
|
||||
fi
|
||||
done
|
||||
105
nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
Normal file
105
nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.boot.loader.raspberryPi;
|
||||
|
||||
builderUboot = import ./uboot-builder.nix { inherit pkgs configTxt; inherit (cfg) version; };
|
||||
builderGeneric = import ./raspberrypi-builder.nix { inherit pkgs configTxt; };
|
||||
|
||||
builder =
|
||||
if cfg.uboot.enable then
|
||||
"${builderUboot} -g ${toString cfg.uboot.configurationLimit} -t ${timeoutStr} -c"
|
||||
else
|
||||
"${builderGeneric} -c";
|
||||
|
||||
blCfg = config.boot.loader;
|
||||
timeoutStr = if blCfg.timeout == null then "-1" else toString blCfg.timeout;
|
||||
|
||||
isAarch64 = pkgs.stdenv.hostPlatform.isAarch64;
|
||||
optional = pkgs.lib.optionalString;
|
||||
|
||||
configTxt =
|
||||
pkgs.writeText "config.txt" (''
|
||||
# U-Boot used to need this to work, regardless of whether UART is actually used or not.
|
||||
# TODO: check when/if this can be removed.
|
||||
enable_uart=1
|
||||
|
||||
# Prevent the firmware from smashing the framebuffer setup done by the mainline kernel
|
||||
# when attempting to show low-voltage or overtemperature warnings.
|
||||
avoid_warnings=1
|
||||
'' + optional isAarch64 ''
|
||||
# Boot in 64-bit mode.
|
||||
arm_64bit=1
|
||||
'' + (if cfg.uboot.enable then ''
|
||||
kernel=u-boot-rpi.bin
|
||||
'' else ''
|
||||
kernel=kernel.img
|
||||
initramfs initrd followkernel
|
||||
'') + optional (cfg.firmwareConfig != null) cfg.firmwareConfig);
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
|
||||
boot.loader.raspberryPi = {
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to create files with the system generations in
|
||||
<literal>/boot</literal>.
|
||||
<literal>/boot/old</literal> will hold files from old generations.
|
||||
'';
|
||||
};
|
||||
|
||||
version = mkOption {
|
||||
default = 2;
|
||||
type = types.enum [ 0 1 2 3 4 ];
|
||||
description = "";
|
||||
};
|
||||
|
||||
uboot = {
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Enable using uboot as bootmanager for the raspberry pi.
|
||||
'';
|
||||
};
|
||||
|
||||
configurationLimit = mkOption {
|
||||
default = 20;
|
||||
example = 10;
|
||||
type = types.int;
|
||||
description = ''
|
||||
Maximum number of configurations in the boot menu.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
firmwareConfig = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.lines;
|
||||
description = ''
|
||||
Extra options that will be appended to <literal>/boot/config.txt</literal> file.
|
||||
For possible values, see: https://www.raspberrypi.org/documentation/configuration/config-txt/
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = singleton {
|
||||
assertion = !pkgs.stdenv.hostPlatform.isAarch64 || cfg.version >= 3;
|
||||
message = "Only Raspberry Pi >= 3 supports aarch64.";
|
||||
};
|
||||
|
||||
system.build.installBootLoader = builder;
|
||||
system.boot.loader.id = "raspberrypi";
|
||||
system.boot.loader.kernelFile = pkgs.stdenv.hostPlatform.linux-kernel.target;
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
{ pkgs, version, configTxt }:
|
||||
|
||||
let
|
||||
isAarch64 = pkgs.stdenv.hostPlatform.isAarch64;
|
||||
|
||||
uboot =
|
||||
if version == 0 then
|
||||
pkgs.ubootRaspberryPiZero
|
||||
else if version == 1 then
|
||||
pkgs.ubootRaspberryPi
|
||||
else if version == 2 then
|
||||
pkgs.ubootRaspberryPi2
|
||||
else if version == 3 then
|
||||
if isAarch64 then
|
||||
pkgs.ubootRaspberryPi3_64bit
|
||||
else
|
||||
pkgs.ubootRaspberryPi3_32bit
|
||||
else
|
||||
throw "U-Boot is not yet supported on the raspberry pi 4.";
|
||||
|
||||
extlinuxConfBuilder =
|
||||
import ../generic-extlinux-compatible/extlinux-conf-builder.nix {
|
||||
pkgs = pkgs.buildPackages;
|
||||
};
|
||||
in
|
||||
pkgs.substituteAll {
|
||||
src = ./uboot-builder.sh;
|
||||
isExecutable = true;
|
||||
inherit (pkgs) bash;
|
||||
path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
|
||||
firmware = pkgs.raspberrypifw;
|
||||
inherit uboot;
|
||||
inherit configTxt;
|
||||
inherit extlinuxConfBuilder;
|
||||
inherit version;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
#! @bash@/bin/sh -e
|
||||
|
||||
target=/boot # Target directory
|
||||
|
||||
while getopts "t:c:d:g:" opt; do
|
||||
case "$opt" in
|
||||
d) target="$OPTARG" ;;
|
||||
*) ;;
|
||||
esac
|
||||
done
|
||||
|
||||
copyForced() {
|
||||
local src="$1"
|
||||
local dst="$2"
|
||||
cp $src $dst.tmp
|
||||
mv $dst.tmp $dst
|
||||
}
|
||||
|
||||
# Call the extlinux builder
|
||||
"@extlinuxConfBuilder@" "$@"
|
||||
|
||||
# Add the firmware files
|
||||
fwdir=@firmware@/share/raspberrypi/boot/
|
||||
copyForced $fwdir/bootcode.bin $target/bootcode.bin
|
||||
copyForced $fwdir/fixup.dat $target/fixup.dat
|
||||
copyForced $fwdir/fixup_cd.dat $target/fixup_cd.dat
|
||||
copyForced $fwdir/fixup_db.dat $target/fixup_db.dat
|
||||
copyForced $fwdir/fixup_x.dat $target/fixup_x.dat
|
||||
copyForced $fwdir/start.elf $target/start.elf
|
||||
copyForced $fwdir/start_cd.elf $target/start_cd.elf
|
||||
copyForced $fwdir/start_db.elf $target/start_db.elf
|
||||
copyForced $fwdir/start_x.elf $target/start_x.elf
|
||||
|
||||
# Add the uboot file
|
||||
copyForced @uboot@/u-boot.bin $target/u-boot-rpi.bin
|
||||
|
||||
# Add the config.txt
|
||||
copyForced @configTxt@ $target/config.txt
|
||||
|
|
@ -0,0 +1,317 @@
|
|||
#! @python3@/bin/python3 -B
|
||||
import argparse
|
||||
import shutil
|
||||
import os
|
||||
import sys
|
||||
import errno
|
||||
import subprocess
|
||||
import glob
|
||||
import tempfile
|
||||
import errno
|
||||
import warnings
|
||||
import ctypes
|
||||
libc = ctypes.CDLL("libc.so.6")
|
||||
import re
|
||||
import datetime
|
||||
import glob
|
||||
import os.path
|
||||
from typing import NamedTuple, List, Optional
|
||||
|
||||
class SystemIdentifier(NamedTuple):
|
||||
profile: Optional[str]
|
||||
generation: int
|
||||
specialisation: Optional[str]
|
||||
|
||||
|
||||
def copy_if_not_exists(source: str, dest: str) -> None:
|
||||
if not os.path.exists(dest):
|
||||
shutil.copyfile(source, dest)
|
||||
|
||||
|
||||
def generation_dir(profile: Optional[str], generation: int) -> str:
|
||||
if profile:
|
||||
return "/nix/var/nix/profiles/system-profiles/%s-%d-link" % (profile, generation)
|
||||
else:
|
||||
return "/nix/var/nix/profiles/system-%d-link" % (generation)
|
||||
|
||||
def system_dir(profile: Optional[str], generation: int, specialisation: Optional[str]) -> str:
|
||||
d = generation_dir(profile, generation)
|
||||
if specialisation:
|
||||
return os.path.join(d, "specialisation", specialisation)
|
||||
else:
|
||||
return d
|
||||
|
||||
BOOT_ENTRY = """title NixOS{profile}{specialisation}
|
||||
version Generation {generation} {description}
|
||||
linux {kernel}
|
||||
initrd {initrd}
|
||||
options {kernel_params}
|
||||
"""
|
||||
|
||||
def generation_conf_filename(profile: Optional[str], generation: int, specialisation: Optional[str]) -> str:
|
||||
pieces = [
|
||||
"nixos",
|
||||
profile or None,
|
||||
"generation",
|
||||
str(generation),
|
||||
f"specialisation-{specialisation}" if specialisation else None,
|
||||
]
|
||||
return "-".join(p for p in pieces if p) + ".conf"
|
||||
|
||||
|
||||
def write_loader_conf(profile: Optional[str], generation: int, specialisation: Optional[str]) -> None:
|
||||
with open("@efiSysMountPoint@/loader/loader.conf.tmp", 'w') as f:
|
||||
if "@timeout@" != "":
|
||||
f.write("timeout @timeout@\n")
|
||||
f.write("default %s\n" % generation_conf_filename(profile, generation, specialisation))
|
||||
if not @editor@:
|
||||
f.write("editor 0\n");
|
||||
f.write("console-mode @consoleMode@\n");
|
||||
os.rename("@efiSysMountPoint@/loader/loader.conf.tmp", "@efiSysMountPoint@/loader/loader.conf")
|
||||
|
||||
|
||||
def profile_path(profile: Optional[str], generation: int, specialisation: Optional[str], name: str) -> str:
|
||||
return os.path.realpath("%s/%s" % (system_dir(profile, generation, specialisation), name))
|
||||
|
||||
|
||||
def copy_from_profile(profile: Optional[str], generation: int, specialisation: Optional[str], name: str, dry_run: bool = False) -> str:
|
||||
store_file_path = profile_path(profile, generation, specialisation, 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)
|
||||
if not dry_run:
|
||||
copy_if_not_exists(store_file_path, "@efiSysMountPoint@%s" % (efi_file_path))
|
||||
return efi_file_path
|
||||
|
||||
|
||||
def describe_generation(generation_dir: str) -> str:
|
||||
try:
|
||||
with open("%s/nixos-version" % generation_dir) as f:
|
||||
nixos_version = f.read()
|
||||
except IOError:
|
||||
nixos_version = "Unknown"
|
||||
|
||||
kernel_dir = os.path.dirname(os.path.realpath("%s/kernel" % generation_dir))
|
||||
module_dir = glob.glob("%s/lib/modules/*" % kernel_dir)[0]
|
||||
kernel_version = os.path.basename(module_dir)
|
||||
|
||||
build_time = int(os.path.getctime(generation_dir))
|
||||
build_date = datetime.datetime.fromtimestamp(build_time).strftime('%F')
|
||||
|
||||
description = "NixOS {}, Linux Kernel {}, Built on {}".format(
|
||||
nixos_version, kernel_version, build_date
|
||||
)
|
||||
|
||||
return description
|
||||
|
||||
|
||||
def write_entry(profile: Optional[str], generation: int, specialisation: Optional[str], machine_id: str) -> None:
|
||||
kernel = copy_from_profile(profile, generation, specialisation, "kernel")
|
||||
initrd = copy_from_profile(profile, generation, specialisation, "initrd")
|
||||
try:
|
||||
append_initrd_secrets = profile_path(profile, generation, specialisation, "append-initrd-secrets")
|
||||
subprocess.check_call([append_initrd_secrets, "@efiSysMountPoint@%s" % (initrd)])
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
entry_file = "@efiSysMountPoint@/loader/entries/%s" % (
|
||||
generation_conf_filename(profile, generation, specialisation))
|
||||
generation_dir = os.readlink(system_dir(profile, generation, specialisation))
|
||||
tmp_path = "%s.tmp" % (entry_file)
|
||||
kernel_params = "init=%s/init " % generation_dir
|
||||
|
||||
with open("%s/kernel-params" % (generation_dir)) as params_file:
|
||||
kernel_params = kernel_params + params_file.read()
|
||||
with open(tmp_path, 'w') as f:
|
||||
f.write(BOOT_ENTRY.format(profile=" [" + profile + "]" if profile else "",
|
||||
specialisation=" (%s)" % specialisation if specialisation else "",
|
||||
generation=generation,
|
||||
kernel=kernel,
|
||||
initrd=initrd,
|
||||
kernel_params=kernel_params,
|
||||
description=describe_generation(generation_dir)))
|
||||
if machine_id is not None:
|
||||
f.write("machine-id %s\n" % machine_id)
|
||||
os.rename(tmp_path, entry_file)
|
||||
|
||||
|
||||
def mkdir_p(path: str) -> None:
|
||||
try:
|
||||
os.makedirs(path)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST or not os.path.isdir(path):
|
||||
raise
|
||||
|
||||
|
||||
def get_generations(profile: Optional[str] = None) -> List[SystemIdentifier]:
|
||||
gen_list = subprocess.check_output([
|
||||
"@nix@/bin/nix-env",
|
||||
"--list-generations",
|
||||
"-p",
|
||||
"/nix/var/nix/profiles/%s" % ("system-profiles/" + profile if profile else "system"),
|
||||
"--option", "build-users-group", ""],
|
||||
universal_newlines=True)
|
||||
gen_lines = gen_list.split('\n')
|
||||
gen_lines.pop()
|
||||
|
||||
configurationLimit = @configurationLimit@
|
||||
configurations = [
|
||||
SystemIdentifier(
|
||||
profile=profile,
|
||||
generation=int(line.split()[0]),
|
||||
specialisation=None
|
||||
)
|
||||
for line in gen_lines
|
||||
]
|
||||
return configurations[-configurationLimit:]
|
||||
|
||||
|
||||
def get_specialisations(profile: Optional[str], generation: int, _: Optional[str]) -> List[SystemIdentifier]:
|
||||
specialisations_dir = os.path.join(
|
||||
system_dir(profile, generation, None), "specialisation")
|
||||
if not os.path.exists(specialisations_dir):
|
||||
return []
|
||||
return [SystemIdentifier(profile, generation, spec) for spec in os.listdir(specialisations_dir)]
|
||||
|
||||
|
||||
def remove_old_entries(gens: List[SystemIdentifier]) -> None:
|
||||
rex_profile = re.compile("^@efiSysMountPoint@/loader/entries/nixos-(.*)-generation-.*\.conf$")
|
||||
rex_generation = re.compile("^@efiSysMountPoint@/loader/entries/nixos.*-generation-(.*)\.conf$")
|
||||
known_paths = []
|
||||
for gen in gens:
|
||||
known_paths.append(copy_from_profile(*gen, "kernel", True))
|
||||
known_paths.append(copy_from_profile(*gen, "initrd", True))
|
||||
for path in glob.iglob("@efiSysMountPoint@/loader/entries/nixos*-generation-[1-9]*.conf"):
|
||||
try:
|
||||
if rex_profile.match(path):
|
||||
prof = rex_profile.sub(r"\1", path)
|
||||
else:
|
||||
prof = "system"
|
||||
gen_number = int(rex_generation.sub(r"\1", path))
|
||||
if not (prof, gen_number) in gens:
|
||||
os.unlink(path)
|
||||
except ValueError:
|
||||
pass
|
||||
for path in glob.iglob("@efiSysMountPoint@/efi/nixos/*"):
|
||||
if not path in known_paths and not os.path.isdir(path):
|
||||
os.unlink(path)
|
||||
|
||||
|
||||
def get_profiles() -> List[str]:
|
||||
if os.path.isdir("/nix/var/nix/profiles/system-profiles/"):
|
||||
return [x
|
||||
for x in os.listdir("/nix/var/nix/profiles/system-profiles/")
|
||||
if not x.endswith("-link")]
|
||||
else:
|
||||
return []
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description='Update NixOS-related systemd-boot files')
|
||||
parser.add_argument('default_config', metavar='DEFAULT-CONFIG', help='The default NixOS config to boot')
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
with open("/etc/machine-id") as machine_file:
|
||||
machine_id = machine_file.readlines()[0]
|
||||
except IOError as e:
|
||||
if e.errno != errno.ENOENT:
|
||||
raise
|
||||
# Since systemd version 232 a machine ID is required and it might not
|
||||
# be there on newly installed systems, so let's generate one so that
|
||||
# bootctl can find it and we can also pass it to write_entry() later.
|
||||
cmd = ["@systemd@/bin/systemd-machine-id-setup", "--print"]
|
||||
machine_id = subprocess.run(
|
||||
cmd, text=True, check=True, stdout=subprocess.PIPE
|
||||
).stdout.rstrip()
|
||||
|
||||
if os.getenv("NIXOS_INSTALL_GRUB") == "1":
|
||||
warnings.warn("NIXOS_INSTALL_GRUB env var deprecated, use NIXOS_INSTALL_BOOTLOADER", DeprecationWarning)
|
||||
os.environ["NIXOS_INSTALL_BOOTLOADER"] = "1"
|
||||
|
||||
if os.getenv("NIXOS_INSTALL_BOOTLOADER") == "1":
|
||||
# bootctl uses fopen() with modes "wxe" and fails if the file exists.
|
||||
if os.path.exists("@efiSysMountPoint@/loader/loader.conf"):
|
||||
os.unlink("@efiSysMountPoint@/loader/loader.conf")
|
||||
|
||||
flags = []
|
||||
|
||||
if "@canTouchEfiVariables@" != "1":
|
||||
flags.append("--no-variables")
|
||||
|
||||
if "@graceful@" == "1":
|
||||
flags.append("--graceful")
|
||||
|
||||
subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@"] + flags + ["install"])
|
||||
else:
|
||||
# Update bootloader to latest if needed
|
||||
available_out = subprocess.check_output(["@systemd@/bin/bootctl", "--version"], universal_newlines=True).split()[2]
|
||||
installed_out = subprocess.check_output(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "status"], universal_newlines=True)
|
||||
|
||||
# See status_binaries() in systemd bootctl.c for code which generates this
|
||||
installed_match = re.search(r"^\W+File:.*/EFI/(?:BOOT|systemd)/.*\.efi \(systemd-boot ([\d.]+[^)]*)\)$",
|
||||
installed_out, re.IGNORECASE | re.MULTILINE)
|
||||
|
||||
available_match = re.search(r"^\((.*)\)$", available_out)
|
||||
|
||||
if installed_match is None:
|
||||
raise Exception("could not find any previously installed systemd-boot")
|
||||
|
||||
if available_match is None:
|
||||
raise Exception("could not determine systemd-boot version")
|
||||
|
||||
installed_version = installed_match.group(1)
|
||||
available_version = available_match.group(1)
|
||||
|
||||
if installed_version < available_version:
|
||||
print("updating systemd-boot from %s to %s" % (installed_version, available_version))
|
||||
subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "update"])
|
||||
else:
|
||||
print("leaving systemd-boot %s in place (%s is not newer)" % (installed_version, available_version))
|
||||
|
||||
mkdir_p("@efiSysMountPoint@/efi/nixos")
|
||||
mkdir_p("@efiSysMountPoint@/loader/entries")
|
||||
|
||||
gens = get_generations()
|
||||
for profile in get_profiles():
|
||||
gens += get_generations(profile)
|
||||
remove_old_entries(gens)
|
||||
for gen in gens:
|
||||
try:
|
||||
write_entry(*gen, machine_id)
|
||||
for specialisation in get_specialisations(*gen):
|
||||
write_entry(*specialisation, machine_id)
|
||||
if os.readlink(system_dir(*gen)) == args.default_config:
|
||||
write_loader_conf(*gen)
|
||||
except OSError as e:
|
||||
profile = f"profile '{gen.profile}'" if gen.profile else "default profile"
|
||||
print("ignoring {} in the list of boot entries because of the following error:\n{}".format(profile, e), file=sys.stderr)
|
||||
|
||||
for root, _, files in os.walk('@efiSysMountPoint@/efi/nixos/.extra-files', topdown=False):
|
||||
relative_root = root.removeprefix("@efiSysMountPoint@/efi/nixos/.extra-files").removeprefix("/")
|
||||
actual_root = os.path.join("@efiSysMountPoint@", relative_root)
|
||||
|
||||
for file in files:
|
||||
actual_file = os.path.join(actual_root, file)
|
||||
|
||||
if os.path.exists(actual_file):
|
||||
os.unlink(actual_file)
|
||||
os.unlink(os.path.join(root, file))
|
||||
|
||||
if not len(os.listdir(actual_root)):
|
||||
os.rmdir(actual_root)
|
||||
os.rmdir(root)
|
||||
|
||||
mkdir_p("@efiSysMountPoint@/efi/nixos/.extra-files")
|
||||
|
||||
subprocess.check_call("@copyExtraFiles@")
|
||||
|
||||
# Since fat32 provides little recovery facilities after a crash,
|
||||
# it can leave the system in an unbootable state, when a crash/outage
|
||||
# happens shortly after an update. To decrease the likelihood of this
|
||||
# event sync the efi filesystem after each update.
|
||||
rc = libc.syncfs(os.open("@efiSysMountPoint@", os.O_RDONLY))
|
||||
if rc != 0:
|
||||
print("could not sync @efiSysMountPoint@: {}".format(os.strerror(rc)), file=sys.stderr)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
303
nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
Normal file
303
nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.boot.loader.systemd-boot;
|
||||
|
||||
efi = config.boot.loader.efi;
|
||||
|
||||
systemdBootBuilder = pkgs.substituteAll {
|
||||
src = ./systemd-boot-builder.py;
|
||||
|
||||
isExecutable = true;
|
||||
|
||||
inherit (pkgs) python3;
|
||||
|
||||
systemd = config.systemd.package;
|
||||
|
||||
nix = config.nix.package.out;
|
||||
|
||||
timeout = if config.boot.loader.timeout != null then config.boot.loader.timeout else "";
|
||||
|
||||
editor = if cfg.editor then "True" else "False";
|
||||
|
||||
configurationLimit = if cfg.configurationLimit == null then 0 else cfg.configurationLimit;
|
||||
|
||||
inherit (cfg) consoleMode graceful;
|
||||
|
||||
inherit (efi) efiSysMountPoint canTouchEfiVariables;
|
||||
|
||||
memtest86 = if cfg.memtest86.enable then pkgs.memtest86-efi else "";
|
||||
|
||||
netbootxyz = if cfg.netbootxyz.enable then pkgs.netbootxyz-efi else "";
|
||||
|
||||
copyExtraFiles = pkgs.writeShellScript "copy-extra-files" ''
|
||||
empty_file=$(${pkgs.coreutils}/bin/mktemp)
|
||||
|
||||
${concatStrings (mapAttrsToList (n: v: ''
|
||||
${pkgs.coreutils}/bin/install -Dp "${v}" "${efi.efiSysMountPoint}/"${escapeShellArg n}
|
||||
${pkgs.coreutils}/bin/install -D $empty_file "${efi.efiSysMountPoint}/efi/nixos/.extra-files/"${escapeShellArg n}
|
||||
'') cfg.extraFiles)}
|
||||
|
||||
${concatStrings (mapAttrsToList (n: v: ''
|
||||
${pkgs.coreutils}/bin/install -Dp "${pkgs.writeText n v}" "${efi.efiSysMountPoint}/loader/entries/"${escapeShellArg n}
|
||||
${pkgs.coreutils}/bin/install -D $empty_file "${efi.efiSysMountPoint}/efi/nixos/.extra-files/loader/entries/"${escapeShellArg n}
|
||||
'') cfg.extraEntries)}
|
||||
'';
|
||||
};
|
||||
|
||||
checkedSystemdBootBuilder = pkgs.runCommand "systemd-boot" {
|
||||
nativeBuildInputs = [ pkgs.mypy ];
|
||||
} ''
|
||||
install -m755 ${systemdBootBuilder} $out
|
||||
mypy \
|
||||
--no-implicit-optional \
|
||||
--disallow-untyped-calls \
|
||||
--disallow-untyped-defs \
|
||||
$out
|
||||
'';
|
||||
in {
|
||||
|
||||
imports =
|
||||
[ (mkRenamedOptionModule [ "boot" "loader" "gummiboot" "enable" ] [ "boot" "loader" "systemd-boot" "enable" ])
|
||||
];
|
||||
|
||||
options.boot.loader.systemd-boot = {
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
|
||||
type = types.bool;
|
||||
|
||||
description = "Whether to enable the systemd-boot (formerly gummiboot) EFI boot manager";
|
||||
};
|
||||
|
||||
editor = mkOption {
|
||||
default = true;
|
||||
|
||||
type = types.bool;
|
||||
|
||||
description = ''
|
||||
Whether to allow editing the kernel command-line before
|
||||
boot. It is recommended to set this to false, as it allows
|
||||
gaining root access by passing init=/bin/sh as a kernel
|
||||
parameter. However, it is enabled by default for backwards
|
||||
compatibility.
|
||||
'';
|
||||
};
|
||||
|
||||
configurationLimit = mkOption {
|
||||
default = null;
|
||||
example = 120;
|
||||
type = types.nullOr types.int;
|
||||
description = ''
|
||||
Maximum number of latest generations in the boot menu.
|
||||
Useful to prevent boot partition running out of disk space.
|
||||
|
||||
<literal>null</literal> means no limit i.e. all generations
|
||||
that were not garbage collected yet.
|
||||
'';
|
||||
};
|
||||
|
||||
consoleMode = mkOption {
|
||||
default = "keep";
|
||||
|
||||
type = types.enum [ "0" "1" "2" "auto" "max" "keep" ];
|
||||
|
||||
description = ''
|
||||
The resolution of the console. The following values are valid:
|
||||
|
||||
<itemizedlist>
|
||||
<listitem><para>
|
||||
<literal>"0"</literal>: Standard UEFI 80x25 mode
|
||||
</para></listitem>
|
||||
<listitem><para>
|
||||
<literal>"1"</literal>: 80x50 mode, not supported by all devices
|
||||
</para></listitem>
|
||||
<listitem><para>
|
||||
<literal>"2"</literal>: The first non-standard mode provided by the device firmware, if any
|
||||
</para></listitem>
|
||||
<listitem><para>
|
||||
<literal>"auto"</literal>: Pick a suitable mode automatically using heuristics
|
||||
</para></listitem>
|
||||
<listitem><para>
|
||||
<literal>"max"</literal>: Pick the highest-numbered available mode
|
||||
</para></listitem>
|
||||
<listitem><para>
|
||||
<literal>"keep"</literal>: Keep the mode selected by firmware (the default)
|
||||
</para></listitem>
|
||||
</itemizedlist>
|
||||
'';
|
||||
};
|
||||
|
||||
memtest86 = {
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Make MemTest86 available from the systemd-boot menu. MemTest86 is a
|
||||
program for testing memory. MemTest86 is an unfree program, so
|
||||
this requires <literal>allowUnfree</literal> to be set to
|
||||
<literal>true</literal>.
|
||||
'';
|
||||
};
|
||||
|
||||
entryFilename = mkOption {
|
||||
default = "memtest86.conf";
|
||||
type = types.str;
|
||||
description = ''
|
||||
<literal>systemd-boot</literal> orders the menu entries by the config file names,
|
||||
so if you want something to appear after all the NixOS entries,
|
||||
it should start with <filename>o</filename> or onwards.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
netbootxyz = {
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Make <literal>netboot.xyz</literal> available from the
|
||||
<literal>systemd-boot</literal> menu. <literal>netboot.xyz</literal>
|
||||
is a menu system that allows you to boot OS installers and
|
||||
utilities over the network.
|
||||
'';
|
||||
};
|
||||
|
||||
entryFilename = mkOption {
|
||||
default = "o_netbootxyz.conf";
|
||||
type = types.str;
|
||||
description = ''
|
||||
<literal>systemd-boot</literal> orders the menu entries by the config file names,
|
||||
so if you want something to appear after all the NixOS entries,
|
||||
it should start with <filename>o</filename> or onwards.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
extraEntries = mkOption {
|
||||
type = types.attrsOf types.lines;
|
||||
default = {};
|
||||
example = literalExpression ''
|
||||
{ "memtest86.conf" = '''
|
||||
title MemTest86
|
||||
efi /efi/memtest86/memtest86.efi
|
||||
'''; }
|
||||
'';
|
||||
description = ''
|
||||
Any additional entries you want added to the <literal>systemd-boot</literal> menu.
|
||||
These entries will be copied to <filename>/boot/loader/entries</filename>.
|
||||
Each attribute name denotes the destination file name,
|
||||
and the corresponding attribute value is the contents of the entry.
|
||||
|
||||
<literal>systemd-boot</literal> orders the menu entries by the config file names,
|
||||
so if you want something to appear after all the NixOS entries,
|
||||
it should start with <filename>o</filename> or onwards.
|
||||
'';
|
||||
};
|
||||
|
||||
extraFiles = mkOption {
|
||||
type = types.attrsOf types.path;
|
||||
default = {};
|
||||
example = literalExpression ''
|
||||
{ "efi/memtest86/memtest86.efi" = "''${pkgs.memtest86-efi}/BOOTX64.efi"; }
|
||||
'';
|
||||
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.
|
||||
'';
|
||||
};
|
||||
|
||||
graceful = mkOption {
|
||||
default = false;
|
||||
|
||||
type = types.bool;
|
||||
|
||||
description = ''
|
||||
Invoke <literal>bootctl install</literal> with the <literal>--graceful</literal> option,
|
||||
which ignores errors when EFI variables cannot be written or when the EFI System Partition
|
||||
cannot be found. Currently only applies to random seed operations.
|
||||
|
||||
Only enable this option if <literal>systemd-boot</literal> otherwise fails to install, as the
|
||||
scope or implication of the <literal>--graceful</literal> option may change in the future.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = (config.boot.kernelPackages.kernel.features or { efiBootStub = true; }) ? efiBootStub;
|
||||
message = "This kernel does not support the EFI boot stub";
|
||||
}
|
||||
] ++ concatMap (filename: [
|
||||
{
|
||||
assertion = !(hasInfix "/" filename);
|
||||
message = "boot.loader.systemd-boot.extraEntries.${lib.strings.escapeNixIdentifier filename} is invalid: entries within folders are not supported";
|
||||
}
|
||||
{
|
||||
assertion = hasSuffix ".conf" filename;
|
||||
message = "boot.loader.systemd-boot.extraEntries.${lib.strings.escapeNixIdentifier filename} is invalid: entries must have a .conf file extension";
|
||||
}
|
||||
]) (builtins.attrNames cfg.extraEntries)
|
||||
++ concatMap (filename: [
|
||||
{
|
||||
assertion = !(hasPrefix "/" filename);
|
||||
message = "boot.loader.systemd-boot.extraFiles.${lib.strings.escapeNixIdentifier filename} is invalid: paths must not begin with a slash";
|
||||
}
|
||||
{
|
||||
assertion = !(hasInfix ".." filename);
|
||||
message = "boot.loader.systemd-boot.extraFiles.${lib.strings.escapeNixIdentifier filename} is invalid: paths must not reference the parent directory";
|
||||
}
|
||||
{
|
||||
assertion = !(hasInfix "nixos/.extra-files" (toLower filename));
|
||||
message = "boot.loader.systemd-boot.extraFiles.${lib.strings.escapeNixIdentifier filename} is invalid: files cannot be placed in the nixos/.extra-files directory";
|
||||
}
|
||||
]) (builtins.attrNames cfg.extraFiles);
|
||||
|
||||
boot.loader.grub.enable = mkDefault false;
|
||||
|
||||
boot.loader.supportsInitrdSecrets = true;
|
||||
|
||||
boot.loader.systemd-boot.extraFiles = mkMerge [
|
||||
# TODO: This is hard-coded to use the 64-bit EFI app, but it could probably
|
||||
# be updated to use the 32-bit EFI app on 32-bit systems. The 32-bit EFI
|
||||
# app filename is BOOTIA32.efi.
|
||||
(mkIf cfg.memtest86.enable {
|
||||
"efi/memtest86/BOOTX64.efi" = "${pkgs.memtest86-efi}/BOOTX64.efi";
|
||||
})
|
||||
(mkIf cfg.netbootxyz.enable {
|
||||
"efi/netbootxyz/netboot.xyz.efi" = "${pkgs.netbootxyz-efi}";
|
||||
})
|
||||
];
|
||||
|
||||
boot.loader.systemd-boot.extraEntries = mkMerge [
|
||||
(mkIf cfg.memtest86.enable {
|
||||
"${cfg.memtest86.entryFilename}" = ''
|
||||
title MemTest86
|
||||
efi /efi/memtest86/BOOTX64.efi
|
||||
'';
|
||||
})
|
||||
(mkIf cfg.netbootxyz.enable {
|
||||
"${cfg.netbootxyz.entryFilename}" = ''
|
||||
title netboot.xyz
|
||||
efi /efi/netbootxyz/netboot.xyz.efi
|
||||
'';
|
||||
})
|
||||
];
|
||||
|
||||
system = {
|
||||
build.installBootLoader = checkedSystemdBootBuilder;
|
||||
|
||||
boot.loader.id = "systemd-boot";
|
||||
|
||||
requiredKernelConfig = with config.lib.kernelConfig; [
|
||||
(isYes "EFI_STUB")
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue