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
325
nixos/modules/system/boot/binfmt.nix
Normal file
325
nixos/modules/system/boot/binfmt.nix
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
inherit (lib) mkOption types optionalString stringAfter;
|
||||
|
||||
cfg = config.boot.binfmt;
|
||||
|
||||
makeBinfmtLine = name: { recognitionType, offset, magicOrExtension
|
||||
, mask, preserveArgvZero, openBinary
|
||||
, matchCredentials, fixBinary, ...
|
||||
}: let
|
||||
type = if recognitionType == "magic" then "M" else "E";
|
||||
offset' = toString offset;
|
||||
mask' = toString mask;
|
||||
interpreter = "/run/binfmt/${name}";
|
||||
flags = if !(matchCredentials -> openBinary)
|
||||
then throw "boot.binfmt.registrations.${name}: you can't specify openBinary = false when matchCredentials = true."
|
||||
else optionalString preserveArgvZero "P" +
|
||||
optionalString (openBinary && !matchCredentials) "O" +
|
||||
optionalString matchCredentials "C" +
|
||||
optionalString fixBinary "F";
|
||||
in ":${name}:${type}:${offset'}:${magicOrExtension}:${mask'}:${interpreter}:${flags}";
|
||||
|
||||
activationSnippet = name: { interpreter, wrapInterpreterInShell, ... }: if wrapInterpreterInShell then ''
|
||||
rm -f /run/binfmt/${name}
|
||||
cat > /run/binfmt/${name} << 'EOF'
|
||||
#!${pkgs.bash}/bin/sh
|
||||
exec -- ${interpreter} "$@"
|
||||
EOF
|
||||
chmod +x /run/binfmt/${name}
|
||||
'' else ''
|
||||
rm -f /run/binfmt/${name}
|
||||
ln -s ${interpreter} /run/binfmt/${name}
|
||||
'';
|
||||
|
||||
getEmulator = system: (lib.systems.elaborate { inherit system; }).emulator pkgs;
|
||||
getQemuArch = system: (lib.systems.elaborate { inherit system; }).qemuArch;
|
||||
|
||||
# Mapping of systems to “magicOrExtension” and “mask”. Mostly taken from:
|
||||
# - https://github.com/cleverca22/nixos-configs/blob/master/qemu.nix
|
||||
# and
|
||||
# - https://github.com/qemu/qemu/blob/master/scripts/qemu-binfmt-conf.sh
|
||||
# TODO: maybe put these in a JSON file?
|
||||
magics = {
|
||||
armv6l-linux = {
|
||||
magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00'';
|
||||
mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff'';
|
||||
};
|
||||
armv7l-linux = {
|
||||
magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00'';
|
||||
mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff'';
|
||||
};
|
||||
aarch64-linux = {
|
||||
magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00'';
|
||||
mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff'';
|
||||
};
|
||||
aarch64_be-linux = {
|
||||
magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7'';
|
||||
mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
|
||||
};
|
||||
i386-linux = {
|
||||
magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x03\x00'';
|
||||
mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
|
||||
};
|
||||
i486-linux = {
|
||||
magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00'';
|
||||
mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
|
||||
};
|
||||
i586-linux = {
|
||||
magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00'';
|
||||
mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
|
||||
};
|
||||
i686-linux = {
|
||||
magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00'';
|
||||
mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
|
||||
};
|
||||
x86_64-linux = {
|
||||
magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00'';
|
||||
mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
|
||||
};
|
||||
alpha-linux = {
|
||||
magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x26\x90'';
|
||||
mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
|
||||
};
|
||||
sparc64-linux = {
|
||||
magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x02'';
|
||||
mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
|
||||
};
|
||||
sparc-linux = {
|
||||
magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x12'';
|
||||
mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
|
||||
};
|
||||
powerpc-linux = {
|
||||
magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14'';
|
||||
mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
|
||||
};
|
||||
powerpc64-linux = {
|
||||
magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15'';
|
||||
mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
|
||||
};
|
||||
powerpc64le-linux = {
|
||||
magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15\x00'';
|
||||
mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\x00'';
|
||||
};
|
||||
mips-linux = {
|
||||
magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08'';
|
||||
mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
|
||||
};
|
||||
mipsel-linux = {
|
||||
magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00'';
|
||||
mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
|
||||
};
|
||||
mips64-linux = {
|
||||
magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08'';
|
||||
mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
|
||||
};
|
||||
mips64el-linux = {
|
||||
magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00'';
|
||||
mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
|
||||
};
|
||||
riscv32-linux = {
|
||||
magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00'';
|
||||
mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
|
||||
};
|
||||
riscv64-linux = {
|
||||
magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00'';
|
||||
mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
|
||||
};
|
||||
wasm32-wasi = {
|
||||
magicOrExtension = ''\x00asm'';
|
||||
mask = ''\xff\xff\xff\xff'';
|
||||
};
|
||||
wasm64-wasi = {
|
||||
magicOrExtension = ''\x00asm'';
|
||||
mask = ''\xff\xff\xff\xff'';
|
||||
};
|
||||
x86_64-windows = {
|
||||
magicOrExtension = ".exe";
|
||||
recognitionType = "extension";
|
||||
};
|
||||
i686-windows = {
|
||||
magicOrExtension = ".exe";
|
||||
recognitionType = "extension";
|
||||
};
|
||||
};
|
||||
|
||||
in {
|
||||
imports = [
|
||||
(lib.mkRenamedOptionModule [ "boot" "binfmtMiscRegistrations" ] [ "boot" "binfmt" "registrations" ])
|
||||
];
|
||||
|
||||
options = {
|
||||
boot.binfmt = {
|
||||
registrations = mkOption {
|
||||
default = {};
|
||||
|
||||
description = ''
|
||||
Extra binary formats to register with the kernel.
|
||||
See https://www.kernel.org/doc/html/latest/admin-guide/binfmt-misc.html for more details.
|
||||
'';
|
||||
|
||||
type = types.attrsOf (types.submodule ({ config, ... }: {
|
||||
options = {
|
||||
recognitionType = mkOption {
|
||||
default = "magic";
|
||||
description = "Whether to recognize executables by magic number or extension.";
|
||||
type = types.enum [ "magic" "extension" ];
|
||||
};
|
||||
|
||||
offset = mkOption {
|
||||
default = null;
|
||||
description = "The byte offset of the magic number used for recognition.";
|
||||
type = types.nullOr types.int;
|
||||
};
|
||||
|
||||
magicOrExtension = mkOption {
|
||||
description = "The magic number or extension to match on.";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
mask = mkOption {
|
||||
default = null;
|
||||
description =
|
||||
"A mask to be ANDed with the byte sequence of the file before matching";
|
||||
type = types.nullOr types.str;
|
||||
};
|
||||
|
||||
interpreter = mkOption {
|
||||
description = ''
|
||||
The interpreter to invoke to run the program.
|
||||
|
||||
Note that the actual registration will point to
|
||||
/run/binfmt/''${name}, so the kernel interpreter length
|
||||
limit doesn't apply.
|
||||
'';
|
||||
type = types.path;
|
||||
};
|
||||
|
||||
preserveArgvZero = mkOption {
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to pass the original argv[0] to the interpreter.
|
||||
|
||||
See the description of the 'P' flag in the kernel docs
|
||||
for more details;
|
||||
'';
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
openBinary = mkOption {
|
||||
default = config.matchCredentials;
|
||||
description = ''
|
||||
Whether to pass the binary to the interpreter as an open
|
||||
file descriptor, instead of a path.
|
||||
'';
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
matchCredentials = mkOption {
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to launch with the credentials and security
|
||||
token of the binary, not the interpreter (e.g. setuid
|
||||
bit).
|
||||
|
||||
See the description of the 'C' flag in the kernel docs
|
||||
for more details.
|
||||
|
||||
Implies/requires openBinary = true.
|
||||
'';
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
fixBinary = mkOption {
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to open the interpreter file as soon as the
|
||||
registration is loaded, rather than waiting for a
|
||||
relevant file to be invoked.
|
||||
|
||||
See the description of the 'F' flag in the kernel docs
|
||||
for more details.
|
||||
'';
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
wrapInterpreterInShell = mkOption {
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to wrap the interpreter in a shell script.
|
||||
|
||||
This allows a shell command to be set as the interpreter.
|
||||
'';
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
interpreterSandboxPath = mkOption {
|
||||
internal = true;
|
||||
default = null;
|
||||
description = ''
|
||||
Path of the interpreter to expose in the build sandbox.
|
||||
'';
|
||||
type = types.nullOr types.path;
|
||||
};
|
||||
};
|
||||
}));
|
||||
};
|
||||
|
||||
emulatedSystems = mkOption {
|
||||
default = [];
|
||||
example = [ "wasm32-wasi" "x86_64-windows" "aarch64-linux" ];
|
||||
description = ''
|
||||
List of systems to emulate. Will also configure Nix to
|
||||
support your new systems.
|
||||
Warning: the builder can execute all emulated systems within the same build, which introduces impurities in the case of cross compilation.
|
||||
'';
|
||||
type = types.listOf types.str;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
boot.binfmt.registrations = builtins.listToAttrs (map (system: {
|
||||
name = system;
|
||||
value = let
|
||||
interpreter = getEmulator system;
|
||||
qemuArch = getQemuArch system;
|
||||
|
||||
preserveArgvZero = "qemu-${qemuArch}" == baseNameOf interpreter;
|
||||
interpreterReg = let
|
||||
wrapperName = "qemu-${qemuArch}-binfmt-P";
|
||||
wrapper = pkgs.wrapQemuBinfmtP wrapperName interpreter;
|
||||
in
|
||||
if preserveArgvZero then "${wrapper}/bin/${wrapperName}"
|
||||
else interpreter;
|
||||
in {
|
||||
inherit preserveArgvZero;
|
||||
|
||||
interpreter = interpreterReg;
|
||||
wrapInterpreterInShell = !preserveArgvZero;
|
||||
interpreterSandboxPath = dirOf (dirOf interpreterReg);
|
||||
} // (magics.${system} or (throw "Cannot create binfmt registration for system ${system}"));
|
||||
}) cfg.emulatedSystems);
|
||||
nix.settings = lib.mkIf (cfg.emulatedSystems != []) {
|
||||
extra-platforms = cfg.emulatedSystems ++ lib.optional pkgs.stdenv.hostPlatform.isx86_64 "i686-linux";
|
||||
extra-sandbox-paths = let
|
||||
ruleFor = system: cfg.registrations.${system};
|
||||
hasWrappedRule = lib.any (system: (ruleFor system).wrapInterpreterInShell) cfg.emulatedSystems;
|
||||
in [ "/run/binfmt" ]
|
||||
++ lib.optional hasWrappedRule "${pkgs.bash}"
|
||||
++ (map (system: (ruleFor system).interpreterSandboxPath) cfg.emulatedSystems);
|
||||
};
|
||||
|
||||
environment.etc."binfmt.d/nixos.conf".source = builtins.toFile "binfmt_nixos.conf"
|
||||
(lib.concatStringsSep "\n" (lib.mapAttrsToList makeBinfmtLine config.boot.binfmt.registrations));
|
||||
system.activationScripts.binfmt = stringAfter [ "specialfs" ] ''
|
||||
mkdir -p -m 0755 /run/binfmt
|
||||
${lib.concatStringsSep "\n" (lib.mapAttrsToList activationSnippet config.boot.binfmt.registrations)}
|
||||
'';
|
||||
systemd.additionalUpstreamSystemUnits = lib.mkIf (config.boot.binfmt.registrations != {}) [
|
||||
"proc-sys-fs-binfmt_misc.automount"
|
||||
"proc-sys-fs-binfmt_misc.mount"
|
||||
"systemd-binfmt.service"
|
||||
];
|
||||
};
|
||||
}
|
||||
37
nixos/modules/system/boot/emergency-mode.nix
Normal file
37
nixos/modules/system/boot/emergency-mode.nix
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
systemd.enableEmergencyMode = mkOption {
|
||||
default = true;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to enable emergency mode, which is an
|
||||
<command>sulogin</command> shell started on the console if
|
||||
mounting a filesystem fails. Since some machines (like EC2
|
||||
instances) have no console of any kind, emergency mode doesn't
|
||||
make sense, and it's better to continue with the boot insofar
|
||||
as possible.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = {
|
||||
|
||||
systemd.additionalUpstreamSystemUnits = optionals
|
||||
config.systemd.enableEmergencyMode [
|
||||
"emergency.target" "emergency.service"
|
||||
];
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
53
nixos/modules/system/boot/grow-partition.nix
Normal file
53
nixos/modules/system/boot/grow-partition.nix
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
# This module automatically grows the root partition.
|
||||
# This allows an instance to be created with a bigger root filesystem
|
||||
# than provided by the machine image.
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
{
|
||||
imports = [
|
||||
(mkRenamedOptionModule [ "virtualisation" "growPartition" ] [ "boot" "growPartition" ])
|
||||
];
|
||||
|
||||
options = {
|
||||
boot.growPartition = mkEnableOption "grow the root partition on boot";
|
||||
};
|
||||
|
||||
config = mkIf config.boot.growPartition {
|
||||
|
||||
boot.initrd.extraUtilsCommands = ''
|
||||
copy_bin_and_libs ${pkgs.gawk}/bin/gawk
|
||||
copy_bin_and_libs ${pkgs.gnused}/bin/sed
|
||||
copy_bin_and_libs ${pkgs.util-linux}/sbin/sfdisk
|
||||
copy_bin_and_libs ${pkgs.util-linux}/sbin/lsblk
|
||||
|
||||
substitute "${pkgs.cloud-utils.guest}/bin/.growpart-wrapped" "$out/bin/growpart" \
|
||||
--replace "${pkgs.bash}/bin/sh" "/bin/sh" \
|
||||
--replace "awk" "gawk" \
|
||||
--replace "sed" "gnused"
|
||||
|
||||
ln -s sed $out/bin/gnused
|
||||
'';
|
||||
|
||||
boot.initrd.postDeviceCommands = ''
|
||||
rootDevice="${config.fileSystems."/".device}"
|
||||
if waitDevice "$rootDevice"; then
|
||||
rootDevice="$(readlink -f "$rootDevice")"
|
||||
parentDevice="$rootDevice"
|
||||
while [ "''${parentDevice%[0-9]}" != "''${parentDevice}" ]; do
|
||||
parentDevice="''${parentDevice%[0-9]}";
|
||||
done
|
||||
partNum="''${rootDevice#''${parentDevice}}"
|
||||
if [ "''${parentDevice%[0-9]p}" != "''${parentDevice}" ] && [ -b "''${parentDevice%p}" ]; then
|
||||
parentDevice="''${parentDevice%p}"
|
||||
fi
|
||||
TMPDIR=/run sh $(type -P growpart) "$parentDevice" "$partNum"
|
||||
udevadm settle
|
||||
fi
|
||||
'';
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
148
nixos/modules/system/boot/initrd-network.nix
Normal file
148
nixos/modules/system/boot/initrd-network.nix
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.boot.initrd.network;
|
||||
|
||||
dhcpInterfaces = lib.attrNames (lib.filterAttrs (iface: v: v.useDHCP == true) (config.networking.interfaces or {}));
|
||||
doDhcp = config.networking.useDHCP || dhcpInterfaces != [];
|
||||
dhcpIfShellExpr = if config.networking.useDHCP
|
||||
then "$(ls /sys/class/net/ | grep -v ^lo$)"
|
||||
else lib.concatMapStringsSep " " lib.escapeShellArg dhcpInterfaces;
|
||||
|
||||
udhcpcScript = pkgs.writeScript "udhcp-script"
|
||||
''
|
||||
#! /bin/sh
|
||||
if [ "$1" = bound ]; then
|
||||
ip address add "$ip/$mask" dev "$interface"
|
||||
if [ -n "$mtu" ]; then
|
||||
ip link set mtu "$mtu" dev "$interface"
|
||||
fi
|
||||
if [ -n "$staticroutes" ]; then
|
||||
echo "$staticroutes" \
|
||||
| sed -r "s@(\S+) (\S+)@ ip route add \"\1\" via \"\2\" dev \"$interface\" ; @g" \
|
||||
| sed -r "s@ via \"0\.0\.0\.0\"@@g" \
|
||||
| /bin/sh
|
||||
fi
|
||||
if [ -n "$router" ]; then
|
||||
ip route add "$router" dev "$interface" # just in case if "$router" is not within "$ip/$mask" (e.g. Hetzner Cloud)
|
||||
ip route add default via "$router" dev "$interface"
|
||||
fi
|
||||
if [ -n "$dns" ]; then
|
||||
rm -f /etc/resolv.conf
|
||||
for server in $dns; do
|
||||
echo "nameserver $server" >> /etc/resolv.conf
|
||||
done
|
||||
fi
|
||||
fi
|
||||
'';
|
||||
|
||||
udhcpcArgs = toString cfg.udhcpc.extraArgs;
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
options = {
|
||||
|
||||
boot.initrd.network.enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Add network connectivity support to initrd. The network may be
|
||||
configured using the <literal>ip</literal> kernel parameter,
|
||||
as described in <link
|
||||
xlink:href="https://www.kernel.org/doc/Documentation/filesystems/nfs/nfsroot.txt">the
|
||||
kernel documentation</link>. Otherwise, if
|
||||
<option>networking.useDHCP</option> is enabled, an IP address
|
||||
is acquired using DHCP.
|
||||
|
||||
You should add the module(s) required for your network card to
|
||||
boot.initrd.availableKernelModules.
|
||||
<literal>lspci -v | grep -iA8 'network\|ethernet'</literal>
|
||||
will tell you which.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.network.flushBeforeStage2 = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to clear the configuration of the interfaces that were set up in
|
||||
the initrd right before stage 2 takes over. Stage 2 will do the regular network
|
||||
configuration based on the NixOS networking options.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.network.udhcpc.extraArgs = mkOption {
|
||||
default = [];
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
Additional command-line arguments passed verbatim to udhcpc if
|
||||
<option>boot.initrd.network.enable</option> and <option>networking.useDHCP</option>
|
||||
are enabled.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.network.postCommands = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Shell commands to be executed after stage 1 of the
|
||||
boot has initialised the network.
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
boot.initrd.kernelModules = [ "af_packet" ];
|
||||
|
||||
boot.initrd.extraUtilsCommands = ''
|
||||
copy_bin_and_libs ${pkgs.klibc}/lib/klibc/bin.static/ipconfig
|
||||
'';
|
||||
|
||||
boot.initrd.preLVMCommands = mkBefore (
|
||||
# Search for interface definitions in command line.
|
||||
''
|
||||
ifaces=""
|
||||
for o in $(cat /proc/cmdline); do
|
||||
case $o in
|
||||
ip=*)
|
||||
ipconfig $o && ifaces="$ifaces $(echo $o | cut -d: -f6)"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
''
|
||||
|
||||
# Otherwise, use DHCP.
|
||||
+ optionalString doDhcp ''
|
||||
# Bring up all interfaces.
|
||||
for iface in ${dhcpIfShellExpr}; do
|
||||
echo "bringing up network interface $iface..."
|
||||
ip link set "$iface" up && ifaces="$ifaces $iface"
|
||||
done
|
||||
|
||||
# Acquire DHCP leases.
|
||||
for iface in ${dhcpIfShellExpr}; do
|
||||
echo "acquiring IP address via DHCP on $iface..."
|
||||
udhcpc --quit --now -i $iface -O staticroutes --script ${udhcpcScript} ${udhcpcArgs}
|
||||
done
|
||||
''
|
||||
|
||||
+ cfg.postCommands);
|
||||
|
||||
boot.initrd.postMountCommands = mkIf cfg.flushBeforeStage2 ''
|
||||
for iface in $ifaces; do
|
||||
ip address flush "$iface"
|
||||
ip link set "$iface" down
|
||||
done
|
||||
'';
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
81
nixos/modules/system/boot/initrd-openvpn.nix
Normal file
81
nixos/modules/system/boot/initrd-openvpn.nix
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.boot.initrd.network.openvpn;
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
options = {
|
||||
|
||||
boot.initrd.network.openvpn.enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Starts an OpenVPN client during initrd boot. It can be used to e.g.
|
||||
remotely accessing the SSH service controlled by
|
||||
<option>boot.initrd.network.ssh</option> or other network services
|
||||
included. Service is killed when stage-1 boot is finished.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.network.openvpn.configuration = mkOption {
|
||||
type = types.path; # Same type as boot.initrd.secrets
|
||||
description = ''
|
||||
The configuration file for OpenVPN.
|
||||
|
||||
<warning>
|
||||
<para>
|
||||
Unless your bootloader supports initrd secrets, this configuration
|
||||
is stored insecurely in the global Nix store.
|
||||
</para>
|
||||
</warning>
|
||||
'';
|
||||
example = literalExpression "./configuration.ovpn";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf (config.boot.initrd.network.enable && cfg.enable) {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.configuration != null;
|
||||
message = "You should specify a configuration for initrd OpenVPN";
|
||||
}
|
||||
];
|
||||
|
||||
# Add kernel modules needed for OpenVPN
|
||||
boot.initrd.kernelModules = [ "tun" "tap" ];
|
||||
|
||||
# Add openvpn and ip binaries to the initrd
|
||||
# The shared libraries are required for DNS resolution
|
||||
boot.initrd.extraUtilsCommands = ''
|
||||
copy_bin_and_libs ${pkgs.openvpn}/bin/openvpn
|
||||
copy_bin_and_libs ${pkgs.iproute2}/bin/ip
|
||||
|
||||
cp -pv ${pkgs.glibc}/lib/libresolv.so.2 $out/lib
|
||||
cp -pv ${pkgs.glibc}/lib/libnss_dns.so.2 $out/lib
|
||||
'';
|
||||
|
||||
boot.initrd.secrets = {
|
||||
"/etc/initrd.ovpn" = cfg.configuration;
|
||||
};
|
||||
|
||||
# openvpn --version would exit with 1 instead of 0
|
||||
boot.initrd.extraUtilsCommandsTest = ''
|
||||
$out/bin/openvpn --show-gateway
|
||||
'';
|
||||
|
||||
# Add `iproute /bin/ip` to the config, to ensure that openvpn
|
||||
# is able to set the routes
|
||||
boot.initrd.network.postCommands = ''
|
||||
(cat /etc/initrd.ovpn; echo -e '\niproute /bin/ip') | \
|
||||
openvpn /dev/stdin &
|
||||
'';
|
||||
};
|
||||
|
||||
}
|
||||
215
nixos/modules/system/boot/initrd-ssh.nix
Normal file
215
nixos/modules/system/boot/initrd-ssh.nix
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.boot.initrd.network.ssh;
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
options.boot.initrd.network.ssh = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Start SSH service during initrd boot. It can be used to debug failing
|
||||
boot on a remote server, enter pasphrase for an encrypted partition etc.
|
||||
Service is killed when stage-1 boot is finished.
|
||||
|
||||
The sshd configuration is largely inherited from
|
||||
<option>services.openssh</option>.
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
default = 22;
|
||||
description = ''
|
||||
Port on which SSH initrd service should listen.
|
||||
'';
|
||||
};
|
||||
|
||||
shell = mkOption {
|
||||
type = types.str;
|
||||
default = "/bin/ash";
|
||||
description = ''
|
||||
Login shell of the remote user. Can be used to limit actions user can do.
|
||||
'';
|
||||
};
|
||||
|
||||
hostKeys = mkOption {
|
||||
type = types.listOf (types.either types.str types.path);
|
||||
default = [];
|
||||
example = [
|
||||
"/etc/secrets/initrd/ssh_host_rsa_key"
|
||||
"/etc/secrets/initrd/ssh_host_ed25519_key"
|
||||
];
|
||||
description = ''
|
||||
Specify SSH host keys to import into the initrd.
|
||||
|
||||
To generate keys, use
|
||||
<citerefentry><refentrytitle>ssh-keygen</refentrytitle><manvolnum>1</manvolnum></citerefentry>:
|
||||
|
||||
<screen>
|
||||
<prompt># </prompt>ssh-keygen -t rsa -N "" -f /etc/secrets/initrd/ssh_host_rsa_key
|
||||
<prompt># </prompt>ssh-keygen -t ed25519 -N "" -f /etc/secrets/initrd/ssh_host_ed25519_key
|
||||
</screen>
|
||||
|
||||
<warning>
|
||||
<para>
|
||||
Unless your bootloader supports initrd secrets, these keys
|
||||
are stored insecurely in the global Nix store. Do NOT use
|
||||
your regular SSH host private keys for this purpose or
|
||||
you'll expose them to regular users!
|
||||
</para>
|
||||
<para>
|
||||
Additionally, even if your initrd supports secrets, if
|
||||
you're using initrd SSH to unlock an encrypted disk then
|
||||
using your regular host keys exposes the private keys on
|
||||
your unencrypted boot partition.
|
||||
</para>
|
||||
</warning>
|
||||
'';
|
||||
};
|
||||
|
||||
authorizedKeys = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = config.users.users.root.openssh.authorizedKeys.keys;
|
||||
defaultText = literalExpression "config.users.users.root.openssh.authorizedKeys.keys";
|
||||
description = ''
|
||||
Authorized keys for the root user on initrd.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "Verbatim contents of <filename>sshd_config</filename>.";
|
||||
};
|
||||
};
|
||||
|
||||
imports =
|
||||
map (opt: mkRemovedOptionModule ([ "boot" "initrd" "network" "ssh" ] ++ [ opt ]) ''
|
||||
The initrd SSH functionality now uses OpenSSH rather than Dropbear.
|
||||
|
||||
If you want to keep your existing initrd SSH host keys, convert them with
|
||||
$ dropbearconvert dropbear openssh dropbear_host_$type_key ssh_host_$type_key
|
||||
and then set options.boot.initrd.network.ssh.hostKeys.
|
||||
'') [ "hostRSAKey" "hostDSSKey" "hostECDSAKey" ];
|
||||
|
||||
config = let
|
||||
# Nix complains if you include a store hash in initrd path names, so
|
||||
# as an awful hack we drop the first character of the hash.
|
||||
initrdKeyPath = path: if isString path
|
||||
then path
|
||||
else let name = builtins.baseNameOf path; in
|
||||
builtins.unsafeDiscardStringContext ("/etc/ssh/" +
|
||||
substring 1 (stringLength name) name);
|
||||
|
||||
sshdCfg = config.services.openssh;
|
||||
|
||||
sshdConfig = ''
|
||||
Port ${toString cfg.port}
|
||||
|
||||
PasswordAuthentication no
|
||||
ChallengeResponseAuthentication no
|
||||
|
||||
${flip concatMapStrings cfg.hostKeys (path: ''
|
||||
HostKey ${initrdKeyPath path}
|
||||
'')}
|
||||
|
||||
KexAlgorithms ${concatStringsSep "," sshdCfg.kexAlgorithms}
|
||||
Ciphers ${concatStringsSep "," sshdCfg.ciphers}
|
||||
MACs ${concatStringsSep "," sshdCfg.macs}
|
||||
|
||||
LogLevel ${sshdCfg.logLevel}
|
||||
|
||||
${if sshdCfg.useDns then ''
|
||||
UseDNS yes
|
||||
'' else ''
|
||||
UseDNS no
|
||||
''}
|
||||
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
in mkIf (config.boot.initrd.network.enable && cfg.enable) {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.authorizedKeys != [];
|
||||
message = "You should specify at least one authorized key for initrd SSH";
|
||||
}
|
||||
|
||||
{
|
||||
assertion = cfg.hostKeys != [];
|
||||
message = ''
|
||||
You must now pre-generate the host keys for initrd SSH.
|
||||
See the boot.initrd.network.ssh.hostKeys documentation
|
||||
for instructions.
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
boot.initrd.extraUtilsCommands = ''
|
||||
copy_bin_and_libs ${pkgs.openssh}/bin/sshd
|
||||
cp -pv ${pkgs.glibc.out}/lib/libnss_files.so.* $out/lib
|
||||
'';
|
||||
|
||||
boot.initrd.extraUtilsCommandsTest = ''
|
||||
# sshd requires a host key to check config, so we pass in the test's
|
||||
tmpkey="$(mktemp initrd-ssh-testkey.XXXXXXXXXX)"
|
||||
cp "${../../../tests/initrd-network-ssh/ssh_host_ed25519_key}" "$tmpkey"
|
||||
# keys from Nix store are world-readable, which sshd doesn't like
|
||||
chmod 600 "$tmpkey"
|
||||
echo -n ${escapeShellArg sshdConfig} |
|
||||
$out/bin/sshd -t -f /dev/stdin \
|
||||
-h "$tmpkey"
|
||||
rm "$tmpkey"
|
||||
'';
|
||||
|
||||
boot.initrd.network.postCommands = ''
|
||||
echo '${cfg.shell}' > /etc/shells
|
||||
echo 'root:x:0:0:root:/root:${cfg.shell}' > /etc/passwd
|
||||
echo 'sshd:x:1:1:sshd:/var/empty:/bin/nologin' >> /etc/passwd
|
||||
echo 'passwd: files' > /etc/nsswitch.conf
|
||||
|
||||
mkdir -p /var/log /var/empty
|
||||
touch /var/log/lastlog
|
||||
|
||||
mkdir -p /etc/ssh
|
||||
echo -n ${escapeShellArg sshdConfig} > /etc/ssh/sshd_config
|
||||
|
||||
echo "export PATH=$PATH" >> /etc/profile
|
||||
echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> /etc/profile
|
||||
|
||||
mkdir -p /root/.ssh
|
||||
${concatStrings (map (key: ''
|
||||
echo ${escapeShellArg key} >> /root/.ssh/authorized_keys
|
||||
'') cfg.authorizedKeys)}
|
||||
|
||||
${flip concatMapStrings cfg.hostKeys (path: ''
|
||||
# keys from Nix store are world-readable, which sshd doesn't like
|
||||
chmod 0600 "${initrdKeyPath path}"
|
||||
'')}
|
||||
|
||||
/bin/sshd -e
|
||||
'';
|
||||
|
||||
boot.initrd.postMountCommands = ''
|
||||
# Stop sshd cleanly before stage 2.
|
||||
#
|
||||
# If you want to keep it around to debug post-mount SSH issues,
|
||||
# run `touch /.keep_sshd` (either from an SSH session or in
|
||||
# another initrd hook like preDeviceCommands).
|
||||
if ! [ -e /.keep_sshd ]; then
|
||||
pkill -x sshd
|
||||
fi
|
||||
'';
|
||||
|
||||
boot.initrd.secrets = listToAttrs
|
||||
(map (path: nameValuePair (initrdKeyPath path) path) cfg.hostKeys);
|
||||
};
|
||||
|
||||
}
|
||||
347
nixos/modules/system/boot/kernel.nix
Normal file
347
nixos/modules/system/boot/kernel.nix
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
inherit (config.boot) kernelPatches;
|
||||
inherit (config.boot.kernel) features randstructSeed;
|
||||
inherit (config.boot.kernelPackages) kernel;
|
||||
|
||||
kernelModulesConf = pkgs.writeText "nixos.conf"
|
||||
''
|
||||
${concatStringsSep "\n" config.boot.kernelModules}
|
||||
'';
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
boot.kernel.features = mkOption {
|
||||
default = {};
|
||||
example = literalExpression "{ debug = true; }";
|
||||
internal = true;
|
||||
description = ''
|
||||
This option allows to enable or disable certain kernel features.
|
||||
It's not API, because it's about kernel feature sets, that
|
||||
make sense for specific use cases. Mostly along with programs,
|
||||
which would have separate nixos options.
|
||||
`grep features pkgs/os-specific/linux/kernel/common-config.nix`
|
||||
'';
|
||||
};
|
||||
|
||||
boot.kernelPackages = mkOption {
|
||||
default = pkgs.linuxPackages;
|
||||
type = types.raw;
|
||||
apply = kernelPackages: kernelPackages.extend (self: super: {
|
||||
kernel = super.kernel.override (originalArgs: {
|
||||
inherit randstructSeed;
|
||||
kernelPatches = (originalArgs.kernelPatches or []) ++ kernelPatches;
|
||||
features = lib.recursiveUpdate super.kernel.features features;
|
||||
});
|
||||
});
|
||||
# We don't want to evaluate all of linuxPackages for the manual
|
||||
# - some of it might not even evaluate correctly.
|
||||
defaultText = literalExpression "pkgs.linuxPackages";
|
||||
example = literalExpression "pkgs.linuxKernel.packages.linux_5_10";
|
||||
description = ''
|
||||
This option allows you to override the Linux kernel used by
|
||||
NixOS. Since things like external kernel module packages are
|
||||
tied to the kernel you're using, it also overrides those.
|
||||
This option is a function that takes Nixpkgs as an argument
|
||||
(as a convenience), and returns an attribute set containing at
|
||||
the very least an attribute <varname>kernel</varname>.
|
||||
Additional attributes may be needed depending on your
|
||||
configuration. For instance, if you use the NVIDIA X driver,
|
||||
then it also needs to contain an attribute
|
||||
<varname>nvidia_x11</varname>.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.kernelPatches = mkOption {
|
||||
type = types.listOf types.attrs;
|
||||
default = [];
|
||||
example = literalExpression "[ pkgs.kernelPatches.ubuntu_fan_4_4 ]";
|
||||
description = "A list of additional patches to apply to the kernel.";
|
||||
};
|
||||
|
||||
boot.kernel.randstructSeed = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
example = "my secret seed";
|
||||
description = ''
|
||||
Provides a custom seed for the <varname>RANDSTRUCT</varname> security
|
||||
option of the Linux kernel. Note that <varname>RANDSTRUCT</varname> is
|
||||
only enabled in NixOS hardened kernels. Using a custom seed requires
|
||||
building the kernel and dependent packages locally, since this
|
||||
customization happens at build time.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.kernelParams = mkOption {
|
||||
type = types.listOf (types.strMatching ''([^"[:space:]]|"[^"]*")+'' // {
|
||||
name = "kernelParam";
|
||||
description = "string, with spaces inside double quotes";
|
||||
});
|
||||
default = [ ];
|
||||
description = "Parameters added to the kernel command line.";
|
||||
};
|
||||
|
||||
boot.consoleLogLevel = mkOption {
|
||||
type = types.int;
|
||||
default = 4;
|
||||
description = ''
|
||||
The kernel console <literal>loglevel</literal>. All Kernel Messages with a log level smaller
|
||||
than this setting will be printed to the console.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.vesa = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
(Deprecated) This option, if set, activates the VESA 800x600 video
|
||||
mode on boot and disables kernel modesetting. It is equivalent to
|
||||
specifying <literal>[ "vga=0x317" "nomodeset" ]</literal> in the
|
||||
<option>boot.kernelParams</option> option. This option is
|
||||
deprecated as of 2020: Xorg now works better with modesetting, and
|
||||
you might want a different VESA vga setting, anyway.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.extraModulePackages = mkOption {
|
||||
type = types.listOf types.package;
|
||||
default = [];
|
||||
example = literalExpression "[ config.boot.kernelPackages.nvidia_x11 ]";
|
||||
description = "A list of additional packages supplying kernel modules.";
|
||||
};
|
||||
|
||||
boot.kernelModules = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
The set of kernel modules to be loaded in the second stage of
|
||||
the boot process. Note that modules that are needed to
|
||||
mount the root file system should be added to
|
||||
<option>boot.initrd.availableKernelModules</option> or
|
||||
<option>boot.initrd.kernelModules</option>.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.availableKernelModules = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
example = [ "sata_nv" "ext3" ];
|
||||
description = ''
|
||||
The set of kernel modules in the initial ramdisk used during the
|
||||
boot process. This set must include all modules necessary for
|
||||
mounting the root device. That is, it should include modules
|
||||
for the physical device (e.g., SCSI drivers) and for the file
|
||||
system (e.g., ext3). The set specified here is automatically
|
||||
closed under the module dependency relation, i.e., all
|
||||
dependencies of the modules list here are included
|
||||
automatically. The modules listed here are available in the
|
||||
initrd, but are only loaded on demand (e.g., the ext3 module is
|
||||
loaded automatically when an ext3 filesystem is mounted, and
|
||||
modules for PCI devices are loaded when they match the PCI ID
|
||||
of a device in your system). To force a module to be loaded,
|
||||
include it in <option>boot.initrd.kernelModules</option>.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.kernelModules = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = "List of modules that are always loaded by the initrd.";
|
||||
};
|
||||
|
||||
boot.initrd.includeDefaultModules = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
This option, if set, adds a collection of default kernel modules
|
||||
to <option>boot.initrd.availableKernelModules</option> and
|
||||
<option>boot.initrd.kernelModules</option>.
|
||||
'';
|
||||
};
|
||||
|
||||
system.modulesTree = mkOption {
|
||||
type = types.listOf types.path;
|
||||
internal = true;
|
||||
default = [];
|
||||
description = ''
|
||||
Tree of kernel modules. This includes the kernel, plus modules
|
||||
built outside of the kernel. Combine these into a single tree of
|
||||
symlinks because modprobe only supports one directory.
|
||||
'';
|
||||
# Convert the list of path to only one path.
|
||||
apply = pkgs.aggregateModules;
|
||||
};
|
||||
|
||||
system.requiredKernelConfig = mkOption {
|
||||
default = [];
|
||||
example = literalExpression ''
|
||||
with config.lib.kernelConfig; [
|
||||
(isYes "MODULES")
|
||||
(isEnabled "FB_CON_DECOR")
|
||||
(isEnabled "BLK_DEV_INITRD")
|
||||
]
|
||||
'';
|
||||
internal = true;
|
||||
type = types.listOf types.attrs;
|
||||
description = ''
|
||||
This option allows modules to specify the kernel config options that
|
||||
must be set (or unset) for the module to work. Please use the
|
||||
lib.kernelConfig functions to build list elements.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkMerge
|
||||
[ (mkIf config.boot.initrd.enable {
|
||||
boot.initrd.availableKernelModules =
|
||||
optionals config.boot.initrd.includeDefaultModules ([
|
||||
# Note: most of these (especially the SATA/PATA modules)
|
||||
# shouldn't be included by default since nixos-generate-config
|
||||
# detects them, but I'm keeping them for now for backwards
|
||||
# compatibility.
|
||||
|
||||
# Some SATA/PATA stuff.
|
||||
"ahci"
|
||||
"sata_nv"
|
||||
"sata_via"
|
||||
"sata_sis"
|
||||
"sata_uli"
|
||||
"ata_piix"
|
||||
"pata_marvell"
|
||||
|
||||
# Standard SCSI stuff.
|
||||
"sd_mod"
|
||||
"sr_mod"
|
||||
|
||||
# SD cards and internal eMMC drives.
|
||||
"mmc_block"
|
||||
|
||||
# Support USB keyboards, in case the boot fails and we only have
|
||||
# a USB keyboard, or for LUKS passphrase prompt.
|
||||
"uhci_hcd"
|
||||
"ehci_hcd"
|
||||
"ehci_pci"
|
||||
"ohci_hcd"
|
||||
"ohci_pci"
|
||||
"xhci_hcd"
|
||||
"xhci_pci"
|
||||
"usbhid"
|
||||
"hid_generic" "hid_lenovo" "hid_apple" "hid_roccat"
|
||||
"hid_logitech_hidpp" "hid_logitech_dj" "hid_microsoft" "hid_cherry"
|
||||
|
||||
] ++ optionals pkgs.stdenv.hostPlatform.isx86 [
|
||||
# Misc. x86 keyboard stuff.
|
||||
"pcips2" "atkbd" "i8042"
|
||||
|
||||
# x86 RTC needed by the stage 2 init script.
|
||||
"rtc_cmos"
|
||||
]);
|
||||
|
||||
boot.initrd.kernelModules =
|
||||
optionals config.boot.initrd.includeDefaultModules [
|
||||
# For LVM.
|
||||
"dm_mod"
|
||||
];
|
||||
})
|
||||
|
||||
(mkIf (!config.boot.isContainer) {
|
||||
system.build = { inherit kernel; };
|
||||
|
||||
system.modulesTree = [ kernel ] ++ config.boot.extraModulePackages;
|
||||
|
||||
# Implement consoleLogLevel both in early boot and using sysctl
|
||||
# (so you don't need to reboot to have changes take effect).
|
||||
boot.kernelParams =
|
||||
[ "loglevel=${toString config.boot.consoleLogLevel}" ] ++
|
||||
optionals config.boot.vesa [ "vga=0x317" "nomodeset" ];
|
||||
|
||||
boot.kernel.sysctl."kernel.printk" = mkDefault config.boot.consoleLogLevel;
|
||||
|
||||
boot.kernelModules = [ "loop" "atkbd" ];
|
||||
|
||||
# Create /etc/modules-load.d/nixos.conf, which is read by
|
||||
# systemd-modules-load.service to load required kernel modules.
|
||||
environment.etc =
|
||||
{ "modules-load.d/nixos.conf".source = kernelModulesConf;
|
||||
};
|
||||
|
||||
systemd.services.systemd-modules-load =
|
||||
{ wantedBy = [ "multi-user.target" ];
|
||||
restartTriggers = [ kernelModulesConf ];
|
||||
serviceConfig =
|
||||
{ # Ignore failed module loads. Typically some of the
|
||||
# modules in ‘boot.kernelModules’ are "nice to have but
|
||||
# not required" (e.g. acpi-cpufreq), so we don't want to
|
||||
# barf on those.
|
||||
SuccessExitStatus = "0 1";
|
||||
};
|
||||
};
|
||||
|
||||
lib.kernelConfig = {
|
||||
isYes = option: {
|
||||
assertion = config: config.isYes option;
|
||||
message = "CONFIG_${option} is not yes!";
|
||||
configLine = "CONFIG_${option}=y";
|
||||
};
|
||||
|
||||
isNo = option: {
|
||||
assertion = config: config.isNo option;
|
||||
message = "CONFIG_${option} is not no!";
|
||||
configLine = "CONFIG_${option}=n";
|
||||
};
|
||||
|
||||
isModule = option: {
|
||||
assertion = config: config.isModule option;
|
||||
message = "CONFIG_${option} is not built as a module!";
|
||||
configLine = "CONFIG_${option}=m";
|
||||
};
|
||||
|
||||
### Usually you will just want to use these two
|
||||
# True if yes or module
|
||||
isEnabled = option: {
|
||||
assertion = config: config.isEnabled option;
|
||||
message = "CONFIG_${option} is not enabled!";
|
||||
configLine = "CONFIG_${option}=y";
|
||||
};
|
||||
|
||||
# True if no or omitted
|
||||
isDisabled = option: {
|
||||
assertion = config: config.isDisabled option;
|
||||
message = "CONFIG_${option} is not disabled!";
|
||||
configLine = "CONFIG_${option}=n";
|
||||
};
|
||||
};
|
||||
|
||||
# The config options that all modules can depend upon
|
||||
system.requiredKernelConfig = with config.lib.kernelConfig;
|
||||
[
|
||||
# !!! Should this really be needed?
|
||||
(isYes "MODULES")
|
||||
(isYes "BINFMT_ELF")
|
||||
] ++ (optional (randstructSeed != "") (isYes "GCC_PLUGIN_RANDSTRUCT"));
|
||||
|
||||
# nixpkgs kernels are assumed to have all required features
|
||||
assertions = if config.boot.kernelPackages.kernel ? features then [] else
|
||||
let cfg = config.boot.kernelPackages.kernel.config; in map (attrs:
|
||||
{ assertion = attrs.assertion cfg; inherit (attrs) message; }
|
||||
) config.system.requiredKernelConfig;
|
||||
|
||||
})
|
||||
|
||||
];
|
||||
|
||||
}
|
||||
117
nixos/modules/system/boot/kernel_config.nix
Normal file
117
nixos/modules/system/boot/kernel_config.nix
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
{ lib, config, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
mergeFalseByDefault = locs: defs:
|
||||
if defs == [] then abort "This case should never happen."
|
||||
else if any (x: x == false) (getValues defs) then false
|
||||
else true;
|
||||
|
||||
kernelItem = types.submodule {
|
||||
options = {
|
||||
tristate = mkOption {
|
||||
type = types.enum [ "y" "m" "n" null ];
|
||||
default = null;
|
||||
internal = true;
|
||||
visible = true;
|
||||
description = ''
|
||||
Use this field for tristate kernel options expecting a "y" or "m" or "n".
|
||||
'';
|
||||
};
|
||||
|
||||
freeform = mkOption {
|
||||
type = types.nullOr types.str // {
|
||||
merge = mergeEqualOption;
|
||||
};
|
||||
default = null;
|
||||
example = ''MMC_BLOCK_MINORS.freeform = "32";'';
|
||||
description = ''
|
||||
Freeform description of a kernel configuration item value.
|
||||
'';
|
||||
};
|
||||
|
||||
optional = mkOption {
|
||||
type = types.bool // { merge = mergeFalseByDefault; };
|
||||
default = false;
|
||||
description = ''
|
||||
Whether option should generate a failure when unused.
|
||||
Upon merging values, mandatory wins over optional.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
mkValue = with lib; val:
|
||||
let
|
||||
isNumber = c: elem c ["0" "1" "2" "3" "4" "5" "6" "7" "8" "9"];
|
||||
|
||||
in
|
||||
if (val == "") then "\"\""
|
||||
else if val == "y" || val == "m" || val == "n" then val
|
||||
else if all isNumber (stringToCharacters val) then val
|
||||
else if substring 0 2 val == "0x" then val
|
||||
else val; # FIXME: fix quoting one day
|
||||
|
||||
|
||||
# generate nix intermediate kernel config file of the form
|
||||
#
|
||||
# VIRTIO_MMIO m
|
||||
# VIRTIO_BLK y
|
||||
# VIRTIO_CONSOLE n
|
||||
# NET_9P_VIRTIO? y
|
||||
#
|
||||
# Borrowed from copumpkin https://github.com/NixOS/nixpkgs/pull/12158
|
||||
# returns a string, expr should be an attribute set
|
||||
# Use mkValuePreprocess to preprocess option values, aka mark 'modules' as 'yes' or vice-versa
|
||||
# use the identity if you don't want to override the configured values
|
||||
generateNixKConf = exprs:
|
||||
let
|
||||
mkConfigLine = key: item:
|
||||
let
|
||||
val = if item.freeform != null then item.freeform else item.tristate;
|
||||
in
|
||||
if val == null
|
||||
then ""
|
||||
else if (item.optional)
|
||||
then "${key}? ${mkValue val}\n"
|
||||
else "${key} ${mkValue val}\n";
|
||||
|
||||
mkConf = cfg: concatStrings (mapAttrsToList mkConfigLine cfg);
|
||||
in mkConf exprs;
|
||||
|
||||
in
|
||||
{
|
||||
|
||||
options = {
|
||||
|
||||
intermediateNixConfig = mkOption {
|
||||
readOnly = true;
|
||||
type = types.lines;
|
||||
example = ''
|
||||
USB? y
|
||||
DEBUG n
|
||||
'';
|
||||
description = ''
|
||||
The result of converting the structured kernel configuration in settings
|
||||
to an intermediate string that can be parsed by generate-config.pl to
|
||||
answer the kernel `make defconfig`.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = types.attrsOf kernelItem;
|
||||
example = literalExpression '' with lib.kernel; {
|
||||
"9P_NET" = yes;
|
||||
USB = option yes;
|
||||
MMC_BLOCK_MINORS = freeform "32";
|
||||
}'';
|
||||
description = ''
|
||||
Structured kernel configuration.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
intermediateNixConfig = generateNixKConf config.settings;
|
||||
};
|
||||
}
|
||||
32
nixos/modules/system/boot/kexec.nix
Normal file
32
nixos/modules/system/boot/kexec.nix
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{ pkgs, lib, ... }:
|
||||
|
||||
{
|
||||
config = lib.mkIf (lib.meta.availableOn pkgs.stdenv.hostPlatform pkgs.kexec-tools) {
|
||||
environment.systemPackages = [ pkgs.kexec-tools ];
|
||||
|
||||
systemd.services.prepare-kexec =
|
||||
{ description = "Preparation for kexec";
|
||||
wantedBy = [ "kexec.target" ];
|
||||
before = [ "systemd-kexec.service" ];
|
||||
unitConfig.DefaultDependencies = false;
|
||||
serviceConfig.Type = "oneshot";
|
||||
path = [ pkgs.kexec-tools ];
|
||||
script =
|
||||
''
|
||||
# Don't load the current system profile if we already have a kernel loaded
|
||||
if [[ 1 = "$(</sys/kernel/kexec_loaded)" ]] ; then
|
||||
echo "kexec kernel has already been loaded, prepare-kexec skipped"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
p=$(readlink -f /nix/var/nix/profiles/system)
|
||||
if ! [[ -d $p ]]; then
|
||||
echo "Could not find system profile for prepare-kexec"
|
||||
exit 1
|
||||
fi
|
||||
echo "Loading NixOS system via kexec."
|
||||
exec kexec --load $p/kernel --initrd=$p/initrd --append="$(cat $p/kernel-params) init=$p/init"
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
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")
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
1008
nixos/modules/system/boot/luksroot.nix
Normal file
1008
nixos/modules/system/boot/luksroot.nix
Normal file
File diff suppressed because it is too large
Load diff
70
nixos/modules/system/boot/modprobe.nix
Normal file
70
nixos/modules/system/boot/modprobe.nix
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
boot.blacklistedKernelModules = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
example = [ "cirrusfb" "i2c_piix4" ];
|
||||
description = ''
|
||||
List of names of kernel modules that should not be loaded
|
||||
automatically by the hardware probing code.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.extraModprobeConfig = mkOption {
|
||||
default = "";
|
||||
example =
|
||||
''
|
||||
options parport_pc io=0x378 irq=7 dma=1
|
||||
'';
|
||||
description = ''
|
||||
Any additional configuration to be appended to the generated
|
||||
<filename>modprobe.conf</filename>. This is typically used to
|
||||
specify module options. See
|
||||
<citerefentry><refentrytitle>modprobe.d</refentrytitle>
|
||||
<manvolnum>5</manvolnum></citerefentry> for details.
|
||||
'';
|
||||
type = types.lines;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf (!config.boot.isContainer) {
|
||||
|
||||
environment.etc."modprobe.d/ubuntu.conf".source = "${pkgs.kmod-blacklist-ubuntu}/modprobe.conf";
|
||||
|
||||
environment.etc."modprobe.d/nixos.conf".text =
|
||||
''
|
||||
${flip concatMapStrings config.boot.blacklistedKernelModules (name: ''
|
||||
blacklist ${name}
|
||||
'')}
|
||||
${config.boot.extraModprobeConfig}
|
||||
'';
|
||||
environment.etc."modprobe.d/debian.conf".source = pkgs.kmod-debian-aliases;
|
||||
|
||||
environment.etc."modprobe.d/systemd.conf".source = "${config.systemd.package}/lib/modprobe.d/systemd.conf";
|
||||
|
||||
environment.systemPackages = [ pkgs.kmod ];
|
||||
|
||||
system.activationScripts.modprobe = stringAfter ["specialfs"]
|
||||
''
|
||||
# Allow the kernel to find our wrapped modprobe (which searches
|
||||
# in the right location in the Nix store for kernel modules).
|
||||
# We need this when the kernel (or some module) auto-loads a
|
||||
# module.
|
||||
echo ${pkgs.kmod}/bin/modprobe > /proc/sys/kernel/modprobe
|
||||
'';
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
1987
nixos/modules/system/boot/networkd.nix
Normal file
1987
nixos/modules/system/boot/networkd.nix
Normal file
File diff suppressed because it is too large
Load diff
38
nixos/modules/system/boot/pbkdf2-sha512.c
Normal file
38
nixos/modules/system/boot/pbkdf2-sha512.c
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
void hextorb(uint8_t* hex, uint8_t* rb)
|
||||
{
|
||||
while(sscanf(hex, "%2x", rb) == 1)
|
||||
{
|
||||
hex += 2;
|
||||
rb += 1;
|
||||
}
|
||||
*rb = '\0';
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
uint8_t k_user[2048];
|
||||
uint8_t salt[2048];
|
||||
uint8_t key[4096];
|
||||
|
||||
uint32_t key_length = atoi(argv[1]);
|
||||
uint32_t iteration_count = atoi(argv[2]);
|
||||
|
||||
hextorb(argv[3], salt);
|
||||
uint32_t salt_length = strlen(argv[3]) / 2;
|
||||
|
||||
fgets(k_user, 2048, stdin);
|
||||
uint32_t k_user_length = strlen(k_user);
|
||||
if(k_user[k_user_length - 1] == '\n') {
|
||||
k_user[k_user_length - 1] = '\0';
|
||||
}
|
||||
|
||||
PKCS5_PBKDF2_HMAC(k_user, k_user_length, salt, salt_length, iteration_count, EVP_sha512(), key_length, key);
|
||||
fwrite(key, 1, key_length, stdout);
|
||||
|
||||
return 0;
|
||||
}
|
||||
321
nixos/modules/system/boot/plymouth.nix
Normal file
321
nixos/modules/system/boot/plymouth.nix
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
{ config, lib, options, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
inherit (pkgs) nixos-icons;
|
||||
plymouth = pkgs.plymouth.override {
|
||||
systemd = config.boot.initrd.systemd.package;
|
||||
};
|
||||
|
||||
cfg = config.boot.plymouth;
|
||||
opt = options.boot.plymouth;
|
||||
|
||||
nixosBreezePlymouth = pkgs.plasma5Packages.breeze-plymouth.override {
|
||||
logoFile = cfg.logo;
|
||||
logoName = "nixos";
|
||||
osName = "NixOS";
|
||||
osVersion = config.system.nixos.release;
|
||||
};
|
||||
|
||||
plymouthLogos = pkgs.runCommand "plymouth-logos" { inherit (cfg) logo; } ''
|
||||
mkdir -p $out
|
||||
|
||||
# For themes that are compiled with PLYMOUTH_LOGO_FILE
|
||||
mkdir -p $out/etc/plymouth
|
||||
ln -s $logo $out/etc/plymouth/logo.png
|
||||
|
||||
# Logo for bgrt theme
|
||||
# Note this is technically an abuse of watermark for the bgrt theme
|
||||
# See: https://gitlab.freedesktop.org/plymouth/plymouth/-/issues/95#note_813768
|
||||
mkdir -p $out/share/plymouth/themes/spinner
|
||||
ln -s $logo $out/share/plymouth/themes/spinner/watermark.png
|
||||
|
||||
# Logo for spinfinity theme
|
||||
# See: https://gitlab.freedesktop.org/plymouth/plymouth/-/issues/106
|
||||
mkdir -p $out/share/plymouth/themes/spinfinity
|
||||
ln -s $logo $out/share/plymouth/themes/spinfinity/header-image.png
|
||||
'';
|
||||
|
||||
themesEnv = pkgs.buildEnv {
|
||||
name = "plymouth-themes";
|
||||
paths = [
|
||||
plymouth
|
||||
plymouthLogos
|
||||
] ++ cfg.themePackages;
|
||||
};
|
||||
|
||||
configFile = pkgs.writeText "plymouthd.conf" ''
|
||||
[Daemon]
|
||||
ShowDelay=0
|
||||
DeviceTimeout=8
|
||||
Theme=${cfg.theme}
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
options = {
|
||||
|
||||
boot.plymouth = {
|
||||
|
||||
enable = mkEnableOption "Plymouth boot splash screen";
|
||||
|
||||
font = mkOption {
|
||||
default = "${pkgs.dejavu_fonts.minimal}/share/fonts/truetype/DejaVuSans.ttf";
|
||||
defaultText = literalExpression ''"''${pkgs.dejavu_fonts.minimal}/share/fonts/truetype/DejaVuSans.ttf"'';
|
||||
type = types.path;
|
||||
description = ''
|
||||
Font file made available for displaying text on the splash screen.
|
||||
'';
|
||||
};
|
||||
|
||||
themePackages = mkOption {
|
||||
default = lib.optional (cfg.theme == "breeze") nixosBreezePlymouth;
|
||||
defaultText = literalDocBook ''
|
||||
A NixOS branded variant of the breeze theme when
|
||||
<literal>config.${opt.theme} == "breeze"</literal>, otherwise
|
||||
<literal>[ ]</literal>.
|
||||
'';
|
||||
type = types.listOf types.package;
|
||||
description = ''
|
||||
Extra theme packages for plymouth.
|
||||
'';
|
||||
};
|
||||
|
||||
theme = mkOption {
|
||||
default = "bgrt";
|
||||
type = types.str;
|
||||
description = ''
|
||||
Splash screen theme.
|
||||
'';
|
||||
};
|
||||
|
||||
logo = mkOption {
|
||||
type = types.path;
|
||||
# Dimensions are 48x48 to match GDM logo
|
||||
default = "${nixos-icons}/share/icons/hicolor/48x48/apps/nix-snowflake-white.png";
|
||||
defaultText = literalExpression ''pkgs.fetchurl {
|
||||
url = "https://nixos.org/logo/nixos-hires.png";
|
||||
sha256 = "1ivzgd7iz0i06y36p8m5w48fd8pjqwxhdaavc0pxs7w1g7mcy5si";
|
||||
}'';
|
||||
description = ''
|
||||
Logo which is displayed on the splash screen.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Literal string to append to <literal>configFile</literal>
|
||||
and the config file generated by the plymouth module.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
boot.kernelParams = [ "splash" ];
|
||||
|
||||
# To be discoverable by systemd.
|
||||
environment.systemPackages = [ plymouth ];
|
||||
|
||||
environment.etc."plymouth/plymouthd.conf".source = configFile;
|
||||
environment.etc."plymouth/plymouthd.defaults".source = "${plymouth}/share/plymouth/plymouthd.defaults";
|
||||
environment.etc."plymouth/logo.png".source = cfg.logo;
|
||||
environment.etc."plymouth/themes".source = "${themesEnv}/share/plymouth/themes";
|
||||
# XXX: Needed because we supply a different set of plugins in initrd.
|
||||
environment.etc."plymouth/plugins".source = "${plymouth}/lib/plymouth";
|
||||
|
||||
systemd.packages = [ plymouth ];
|
||||
|
||||
systemd.services.plymouth-kexec.wantedBy = [ "kexec.target" ];
|
||||
systemd.services.plymouth-halt.wantedBy = [ "halt.target" ];
|
||||
systemd.services.plymouth-quit-wait.wantedBy = [ "multi-user.target" ];
|
||||
systemd.services.plymouth-quit.wantedBy = [ "multi-user.target" ];
|
||||
systemd.services.plymouth-poweroff.wantedBy = [ "poweroff.target" ];
|
||||
systemd.services.plymouth-reboot.wantedBy = [ "reboot.target" ];
|
||||
systemd.services.plymouth-read-write.wantedBy = [ "sysinit.target" ];
|
||||
systemd.services.systemd-ask-password-plymouth.wantedBy = [ "multi-user.target" ];
|
||||
systemd.paths.systemd-ask-password-plymouth.wantedBy = [ "multi-user.target" ];
|
||||
|
||||
boot.initrd.systemd = {
|
||||
extraBin.plymouth = "${plymouth}/bin/plymouth"; # for the recovery shell
|
||||
storePaths = [
|
||||
"${lib.getBin config.boot.initrd.systemd.package}/bin/systemd-tty-ask-password-agent"
|
||||
"${plymouth}/bin/plymouthd"
|
||||
"${plymouth}/sbin/plymouthd"
|
||||
];
|
||||
packages = [ plymouth ]; # systemd units
|
||||
contents = {
|
||||
# Files
|
||||
"/etc/plymouth/plymouthd.conf".source = configFile;
|
||||
"/etc/plymouth/plymouthd.defaults".source = "${plymouth}/share/plymouth/plymouthd.defaults";
|
||||
"/etc/plymouth/logo.png".source = cfg.logo;
|
||||
# Directories
|
||||
"/etc/plymouth/plugins".source = pkgs.runCommand "plymouth-initrd-plugins" {} ''
|
||||
# Check if the actual requested theme is here
|
||||
if [[ ! -d ${themesEnv}/share/plymouth/themes/${cfg.theme} ]]; then
|
||||
echo "The requested theme: ${cfg.theme} is not provided by any of the packages in boot.plymouth.themePackages"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
moduleName="$(sed -n 's,ModuleName *= *,,p' ${themesEnv}/share/plymouth/themes/${cfg.theme}/${cfg.theme}.plymouth)"
|
||||
|
||||
mkdir -p $out/renderers
|
||||
# module might come from a theme
|
||||
cp ${themesEnv}/lib/plymouth/{text,details,label,$moduleName}.so $out
|
||||
cp ${plymouth}/lib/plymouth/renderers/{drm,frame-buffer}.so $out/renderers
|
||||
'';
|
||||
"/etc/plymouth/themes".source = pkgs.runCommand "plymouth-initrd-themes" {} ''
|
||||
# Check if the actual requested theme is here
|
||||
if [[ ! -d ${themesEnv}/share/plymouth/themes/${cfg.theme} ]]; then
|
||||
echo "The requested theme: ${cfg.theme} is not provided by any of the packages in boot.plymouth.themePackages"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir $out
|
||||
cp -r ${themesEnv}/share/plymouth/themes/${cfg.theme} $out
|
||||
# Copy more themes if the theme depends on others
|
||||
for theme in $(grep -hRo '/etc/plymouth/themes/.*$' ${themesEnv} | xargs -n1 basename); do
|
||||
if [[ -d "${themesEnv}/theme" ]]; then
|
||||
cp -r "${themesEnv}/theme" $out
|
||||
fi
|
||||
done
|
||||
'';
|
||||
|
||||
# Fonts
|
||||
"/etc/plymouth/fonts".source = pkgs.runCommand "plymouth-initrd-fonts" {} ''
|
||||
mkdir -p $out
|
||||
cp ${cfg.font} $out
|
||||
'';
|
||||
"/etc/fonts/fonts.conf".text = ''
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
|
||||
<fontconfig>
|
||||
<dir>/etc/plymouth/fonts</dir>
|
||||
</fontconfig>
|
||||
'';
|
||||
};
|
||||
# Properly enable units. These are the units that arch copies
|
||||
services = {
|
||||
plymouth-halt.wantedBy = [ "halt.target" ];
|
||||
plymouth-kexec.wantedBy = [ "kexec.target" ];
|
||||
plymouth-poweroff.wantedBy = [ "poweroff.target" ];
|
||||
plymouth-quit-wait.wantedBy = [ "multi-user.target" ];
|
||||
plymouth-quit.wantedBy = [ "multi-user.target" ];
|
||||
plymouth-read-write.wantedBy = [ "sysinit.target" ];
|
||||
plymouth-reboot.wantedBy = [ "reboot.target" ];
|
||||
plymouth-start.wantedBy = [ "initrd-switch-root.target" "sysinit.target" ];
|
||||
plymouth-switch-root-initramfs.wantedBy = [ "halt.target" "kexec.target" "plymouth-switch-root-initramfs.service" "poweroff.target" "reboot.target" ];
|
||||
plymouth-switch-root.wantedBy = [ "initrd-switch-root.target" ];
|
||||
};
|
||||
};
|
||||
|
||||
# Insert required udev rules. We take stage 2 systemd because the udev
|
||||
# rules are only generated when building with logind.
|
||||
boot.initrd.services.udev.packages = [ (pkgs.runCommand "initrd-plymouth-udev-rules" {} ''
|
||||
mkdir -p $out/etc/udev/rules.d
|
||||
cp ${config.systemd.package.out}/lib/udev/rules.d/{70-uaccess,71-seat}.rules $out/etc/udev/rules.d
|
||||
sed -i '/loginctl/d' $out/etc/udev/rules.d/71-seat.rules
|
||||
'') ];
|
||||
|
||||
boot.initrd.extraUtilsCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
|
||||
copy_bin_and_libs ${plymouth}/bin/plymouth
|
||||
copy_bin_and_libs ${plymouth}/bin/plymouthd
|
||||
|
||||
# Check if the actual requested theme is here
|
||||
if [[ ! -d ${themesEnv}/share/plymouth/themes/${cfg.theme} ]]; then
|
||||
echo "The requested theme: ${cfg.theme} is not provided by any of the packages in boot.plymouth.themePackages"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
moduleName="$(sed -n 's,ModuleName *= *,,p' ${themesEnv}/share/plymouth/themes/${cfg.theme}/${cfg.theme}.plymouth)"
|
||||
|
||||
mkdir -p $out/lib/plymouth/renderers
|
||||
# module might come from a theme
|
||||
cp ${themesEnv}/lib/plymouth/{text,details,label,$moduleName}.so $out/lib/plymouth
|
||||
cp ${plymouth}/lib/plymouth/renderers/{drm,frame-buffer}.so $out/lib/plymouth/renderers
|
||||
|
||||
mkdir -p $out/share/plymouth/themes
|
||||
cp ${plymouth}/share/plymouth/plymouthd.defaults $out/share/plymouth
|
||||
|
||||
# Copy themes into working directory for patching
|
||||
mkdir themes
|
||||
|
||||
# Use -L to copy the directories proper, not the symlinks to them.
|
||||
# Copy all themes because they're not large assets, and bgrt depends on the ImageDir of
|
||||
# the spinner theme.
|
||||
cp -r -L ${themesEnv}/share/plymouth/themes/* themes
|
||||
|
||||
# Patch out any attempted references to the theme or plymouth's themes directory
|
||||
chmod -R +w themes
|
||||
find themes -type f | while read file
|
||||
do
|
||||
sed -i "s,/nix/.*/share/plymouth/themes,$out/share/plymouth/themes,g" $file
|
||||
done
|
||||
|
||||
# Install themes
|
||||
cp -r themes/* $out/share/plymouth/themes
|
||||
|
||||
# Install logo
|
||||
mkdir -p $out/etc/plymouth
|
||||
cp -r -L ${themesEnv}/etc/plymouth $out
|
||||
|
||||
# Setup font
|
||||
mkdir -p $out/share/fonts
|
||||
cp ${cfg.font} $out/share/fonts
|
||||
mkdir -p $out/etc/fonts
|
||||
cat > $out/etc/fonts/fonts.conf <<EOF
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
|
||||
<fontconfig>
|
||||
<dir>$out/share/fonts</dir>
|
||||
</fontconfig>
|
||||
EOF
|
||||
'';
|
||||
|
||||
boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) ''
|
||||
$out/bin/plymouthd --help >/dev/null
|
||||
$out/bin/plymouth --help >/dev/null
|
||||
'';
|
||||
|
||||
boot.initrd.extraUdevRulesCommands = mkIf (!config.boot.initrd.systemd.enable) ''
|
||||
cp ${config.systemd.package}/lib/udev/rules.d/{70-uaccess,71-seat}.rules $out
|
||||
sed -i '/loginctl/d' $out/71-seat.rules
|
||||
'';
|
||||
|
||||
# We use `mkAfter` to ensure that LUKS password prompt would be shown earlier than the splash screen.
|
||||
boot.initrd.preLVMCommands = mkIf (!config.boot.initrd.systemd.enable) (mkAfter ''
|
||||
mkdir -p /etc/plymouth
|
||||
mkdir -p /run/plymouth
|
||||
ln -s ${configFile} /etc/plymouth/plymouthd.conf
|
||||
ln -s $extraUtils/share/plymouth/plymouthd.defaults /etc/plymouth/plymouthd.defaults
|
||||
ln -s $extraUtils/share/plymouth/logo.png /etc/plymouth/logo.png
|
||||
ln -s $extraUtils/share/plymouth/themes /etc/plymouth/themes
|
||||
ln -s $extraUtils/lib/plymouth /etc/plymouth/plugins
|
||||
ln -s $extraUtils/etc/fonts /etc/fonts
|
||||
|
||||
plymouthd --mode=boot --pid-file=/run/plymouth/pid --attach-to-session
|
||||
plymouth show-splash
|
||||
'');
|
||||
|
||||
boot.initrd.postMountCommands = mkIf (!config.boot.initrd.systemd.enable) ''
|
||||
plymouth update-root-fs --new-root-dir="$targetRoot"
|
||||
'';
|
||||
|
||||
# `mkBefore` to ensure that any custom prompts would be visible.
|
||||
boot.initrd.preFailCommands = mkIf (!config.boot.initrd.systemd.enable) (mkBefore ''
|
||||
plymouth quit --wait
|
||||
'');
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
183
nixos/modules/system/boot/resolved.nix
Normal file
183
nixos/modules/system/boot/resolved.nix
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.resolved;
|
||||
|
||||
dnsmasqResolve = config.services.dnsmasq.enable &&
|
||||
config.services.dnsmasq.resolveLocalQueries;
|
||||
|
||||
in
|
||||
{
|
||||
|
||||
options = {
|
||||
|
||||
services.resolved.enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to enable the systemd DNS resolver daemon.
|
||||
'';
|
||||
};
|
||||
|
||||
services.resolved.fallbackDns = mkOption {
|
||||
default = [ ];
|
||||
example = [ "8.8.8.8" "2001:4860:4860::8844" ];
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
A list of IPv4 and IPv6 addresses to use as the fallback DNS servers.
|
||||
If this option is empty, a compiled-in list of DNS servers is used instead.
|
||||
'';
|
||||
};
|
||||
|
||||
services.resolved.domains = mkOption {
|
||||
default = config.networking.search;
|
||||
defaultText = literalExpression "config.networking.search";
|
||||
example = [ "example.com" ];
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
A list of domains. These domains are used as search suffixes
|
||||
when resolving single-label host names (domain names which
|
||||
contain no dot), in order to qualify them into fully-qualified
|
||||
domain names (FQDNs).
|
||||
|
||||
For compatibility reasons, if this setting is not specified,
|
||||
the search domains listed in
|
||||
<filename>/etc/resolv.conf</filename> are used instead, if
|
||||
that file exists and any domains are configured in it.
|
||||
'';
|
||||
};
|
||||
|
||||
services.resolved.llmnr = mkOption {
|
||||
default = "true";
|
||||
example = "false";
|
||||
type = types.enum [ "true" "resolve" "false" ];
|
||||
description = ''
|
||||
Controls Link-Local Multicast Name Resolution support
|
||||
(RFC 4795) on the local host.
|
||||
|
||||
If set to
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><literal>"true"</literal></term>
|
||||
<listitem><para>
|
||||
Enables full LLMNR responder and resolver support.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><literal>"false"</literal></term>
|
||||
<listitem><para>
|
||||
Disables both.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><literal>"resolve"</literal></term>
|
||||
<listitem><para>
|
||||
Only resolution support is enabled, but responding is disabled.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
'';
|
||||
};
|
||||
|
||||
services.resolved.dnssec = mkOption {
|
||||
default = "allow-downgrade";
|
||||
example = "true";
|
||||
type = types.enum [ "true" "allow-downgrade" "false" ];
|
||||
description = ''
|
||||
If set to
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><literal>"true"</literal></term>
|
||||
<listitem><para>
|
||||
all DNS lookups are DNSSEC-validated locally (excluding
|
||||
LLMNR and Multicast DNS). Note that this mode requires a
|
||||
DNS server that supports DNSSEC. If the DNS server does
|
||||
not properly support DNSSEC all validations will fail.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><literal>"allow-downgrade"</literal></term>
|
||||
<listitem><para>
|
||||
DNSSEC validation is attempted, but if the server does not
|
||||
support DNSSEC properly, DNSSEC mode is automatically
|
||||
disabled. Note that this mode makes DNSSEC validation
|
||||
vulnerable to "downgrade" attacks, where an attacker might
|
||||
be able to trigger a downgrade to non-DNSSEC mode by
|
||||
synthesizing a DNS response that suggests DNSSEC was not
|
||||
supported.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><literal>"false"</literal></term>
|
||||
<listitem><para>
|
||||
DNS lookups are not DNSSEC validated.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
'';
|
||||
};
|
||||
|
||||
services.resolved.extraConfig = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Extra config to append to resolved.conf.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
assertions = [
|
||||
{ assertion = !config.networking.useHostResolvConf;
|
||||
message = "Using host resolv.conf is not supported with systemd-resolved";
|
||||
}
|
||||
];
|
||||
|
||||
users.users.systemd-resolve.group = "systemd-resolve";
|
||||
|
||||
# add resolve to nss hosts database if enabled and nscd enabled
|
||||
# system.nssModules is configured in nixos/modules/system/boot/systemd.nix
|
||||
# added with order 501 to allow modules to go before with mkBefore
|
||||
system.nssDatabases.hosts = (mkOrder 501 ["resolve [!UNAVAIL=return]"]);
|
||||
|
||||
systemd.additionalUpstreamSystemUnits = [
|
||||
"systemd-resolved.service"
|
||||
];
|
||||
|
||||
systemd.services.systemd-resolved = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
aliases = [ "dbus-org.freedesktop.resolve1.service" ];
|
||||
restartTriggers = [ config.environment.etc."systemd/resolved.conf".source ];
|
||||
};
|
||||
|
||||
environment.etc = {
|
||||
"systemd/resolved.conf".text = ''
|
||||
[Resolve]
|
||||
${optionalString (config.networking.nameservers != [])
|
||||
"DNS=${concatStringsSep " " config.networking.nameservers}"}
|
||||
${optionalString (cfg.fallbackDns != [])
|
||||
"FallbackDNS=${concatStringsSep " " cfg.fallbackDns}"}
|
||||
${optionalString (cfg.domains != [])
|
||||
"Domains=${concatStringsSep " " cfg.domains}"}
|
||||
LLMNR=${cfg.llmnr}
|
||||
DNSSEC=${cfg.dnssec}
|
||||
${config.services.resolved.extraConfig}
|
||||
'';
|
||||
|
||||
# symlink the dynamic stub resolver of resolv.conf as recommended by upstream:
|
||||
# https://www.freedesktop.org/software/systemd/man/systemd-resolved.html#/etc/resolv.conf
|
||||
"resolv.conf".source = "/run/systemd/resolve/stub-resolv.conf";
|
||||
} // optionalAttrs dnsmasqResolve {
|
||||
"dnsmasq-resolv.conf".source = "/run/systemd/resolve/resolv.conf";
|
||||
};
|
||||
|
||||
# If networkmanager is enabled, ask it to interface with resolved.
|
||||
networking.networkmanager.dns = "systemd-resolved";
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
27
nixos/modules/system/boot/shutdown.nix
Normal file
27
nixos/modules/system/boot/shutdown.nix
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
{
|
||||
|
||||
# This unit saves the value of the system clock to the hardware
|
||||
# clock on shutdown.
|
||||
systemd.services.save-hwclock =
|
||||
{ description = "Save Hardware Clock";
|
||||
|
||||
wantedBy = [ "shutdown.target" ];
|
||||
|
||||
unitConfig = {
|
||||
DefaultDependencies = false;
|
||||
ConditionPathExists = "/dev/rtc";
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = "${pkgs.util-linux}/sbin/hwclock --systohc ${if config.time.hardwareClockInLocalTime then "--localtime" else "--utc"}";
|
||||
};
|
||||
};
|
||||
|
||||
boot.kernel.sysctl."kernel.poweroff_cmd" = "${config.systemd.package}/sbin/poweroff";
|
||||
|
||||
}
|
||||
641
nixos/modules/system/boot/stage-1-init.sh
Normal file
641
nixos/modules/system/boot/stage-1-init.sh
Normal file
|
|
@ -0,0 +1,641 @@
|
|||
#! @shell@
|
||||
|
||||
targetRoot=/mnt-root
|
||||
console=tty1
|
||||
verbose="@verbose@"
|
||||
|
||||
info() {
|
||||
if [[ -n "$verbose" ]]; then
|
||||
echo "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
extraUtils="@extraUtils@"
|
||||
export LD_LIBRARY_PATH=@extraUtils@/lib
|
||||
export PATH=@extraUtils@/bin
|
||||
ln -s @extraUtils@/bin /bin
|
||||
# hardcoded in util-linux's mount helper search path `/run/wrappers/bin:/run/current-system/sw/bin:/sbin`
|
||||
ln -s @extraUtils@/bin /sbin
|
||||
|
||||
# Copy the secrets to their needed location
|
||||
if [ -d "@extraUtils@/secrets" ]; then
|
||||
for secret in $(cd "@extraUtils@/secrets"; find . -type f); do
|
||||
mkdir -p $(dirname "/$secret")
|
||||
ln -s "@extraUtils@/secrets/$secret" "$secret"
|
||||
done
|
||||
fi
|
||||
|
||||
# Stop LVM complaining about fd3
|
||||
export LVM_SUPPRESS_FD_WARNINGS=true
|
||||
|
||||
fail() {
|
||||
if [ -n "$panicOnFail" ]; then exit 1; fi
|
||||
|
||||
@preFailCommands@
|
||||
|
||||
# If starting stage 2 failed, allow the user to repair the problem
|
||||
# in an interactive shell.
|
||||
cat <<EOF
|
||||
|
||||
An error occurred in stage 1 of the boot process, which must mount the
|
||||
root filesystem on \`$targetRoot' and then start stage 2. Press one
|
||||
of the following keys:
|
||||
|
||||
EOF
|
||||
if [ -n "$allowShell" ]; then cat <<EOF
|
||||
i) to launch an interactive shell
|
||||
f) to start an interactive shell having pid 1 (needed if you want to
|
||||
start stage 2's init manually)
|
||||
EOF
|
||||
fi
|
||||
cat <<EOF
|
||||
r) to reboot immediately
|
||||
*) to ignore the error and continue
|
||||
EOF
|
||||
|
||||
read -n 1 reply
|
||||
|
||||
if [ -n "$allowShell" -a "$reply" = f ]; then
|
||||
exec setsid @shell@ -c "exec @shell@ < /dev/$console >/dev/$console 2>/dev/$console"
|
||||
elif [ -n "$allowShell" -a "$reply" = i ]; then
|
||||
echo "Starting interactive shell..."
|
||||
setsid @shell@ -c "exec @shell@ < /dev/$console >/dev/$console 2>/dev/$console" || fail
|
||||
elif [ "$reply" = r ]; then
|
||||
echo "Rebooting..."
|
||||
reboot -f
|
||||
else
|
||||
info "Continuing..."
|
||||
fi
|
||||
}
|
||||
|
||||
trap 'fail' 0
|
||||
|
||||
|
||||
# Print a greeting.
|
||||
info
|
||||
info "[1;32m<<< NixOS Stage 1 >>>[0m"
|
||||
info
|
||||
|
||||
# Make several required directories.
|
||||
mkdir -p /etc/udev
|
||||
touch /etc/fstab # to shut up mount
|
||||
ln -s /proc/mounts /etc/mtab # to shut up mke2fs
|
||||
touch /etc/udev/hwdb.bin # to shut up udev
|
||||
touch /etc/initrd-release
|
||||
|
||||
# Function for waiting for device(s) to appear.
|
||||
waitDevice() {
|
||||
local device="$1"
|
||||
# Split device string using ':' as a delimiter as bcachefs
|
||||
# uses this for multi-device filesystems, i.e. /dev/sda1:/dev/sda2:/dev/sda3
|
||||
local IFS=':'
|
||||
|
||||
# USB storage devices tend to appear with some delay. It would be
|
||||
# great if we had a way to synchronously wait for them, but
|
||||
# alas... So just wait for a few seconds for the device to
|
||||
# appear.
|
||||
for dev in $device; do
|
||||
if test ! -e $dev; then
|
||||
echo -n "waiting for device $dev to appear..."
|
||||
try=20
|
||||
while [ $try -gt 0 ]; do
|
||||
sleep 1
|
||||
# also re-try lvm activation now that new block devices might have appeared
|
||||
lvm vgchange -ay
|
||||
# and tell udev to create nodes for the new LVs
|
||||
udevadm trigger --action=add
|
||||
if test -e $dev; then break; fi
|
||||
echo -n "."
|
||||
try=$((try - 1))
|
||||
done
|
||||
echo
|
||||
[ $try -ne 0 ]
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Mount special file systems.
|
||||
specialMount() {
|
||||
local device="$1"
|
||||
local mountPoint="$2"
|
||||
local options="$3"
|
||||
local fsType="$4"
|
||||
|
||||
mkdir -m 0755 -p "$mountPoint"
|
||||
mount -n -t "$fsType" -o "$options" "$device" "$mountPoint"
|
||||
}
|
||||
source @earlyMountScript@
|
||||
|
||||
# Copy initrd secrets from /.initrd-secrets to their actual destinations
|
||||
if [ -d "/.initrd-secrets" ]; then
|
||||
#
|
||||
# Secrets are named by their full destination pathname and stored
|
||||
# under /.initrd-secrets/
|
||||
#
|
||||
for secret in $(cd "/.initrd-secrets"; find . -type f); do
|
||||
mkdir -p $(dirname "/$secret")
|
||||
cp "/.initrd-secrets/$secret" "$secret"
|
||||
done
|
||||
fi
|
||||
|
||||
# Log the script output to /dev/kmsg or /run/log/stage-1-init.log.
|
||||
mkdir -p /tmp
|
||||
mkfifo /tmp/stage-1-init.log.fifo
|
||||
logOutFd=8 && logErrFd=9
|
||||
eval "exec $logOutFd>&1 $logErrFd>&2"
|
||||
if test -w /dev/kmsg; then
|
||||
tee -i < /tmp/stage-1-init.log.fifo /proc/self/fd/"$logOutFd" | while read -r line; do
|
||||
if test -n "$line"; then
|
||||
echo "<7>stage-1-init: [$(date)] $line" > /dev/kmsg
|
||||
fi
|
||||
done &
|
||||
else
|
||||
mkdir -p /run/log
|
||||
tee -i < /tmp/stage-1-init.log.fifo /run/log/stage-1-init.log &
|
||||
fi
|
||||
exec > /tmp/stage-1-init.log.fifo 2>&1
|
||||
|
||||
|
||||
# Process the kernel command line.
|
||||
export stage2Init=/init
|
||||
for o in $(cat /proc/cmdline); do
|
||||
case $o in
|
||||
console=*)
|
||||
set -- $(IFS==; echo $o)
|
||||
params=$2
|
||||
set -- $(IFS=,; echo $params)
|
||||
console=$1
|
||||
;;
|
||||
init=*)
|
||||
set -- $(IFS==; echo $o)
|
||||
stage2Init=$2
|
||||
;;
|
||||
boot.persistence=*)
|
||||
set -- $(IFS==; echo $o)
|
||||
persistence=$2
|
||||
;;
|
||||
boot.persistence.opt=*)
|
||||
set -- $(IFS==; echo $o)
|
||||
persistence_opt=$2
|
||||
;;
|
||||
boot.trace|debugtrace)
|
||||
# Show each command.
|
||||
set -x
|
||||
;;
|
||||
boot.shell_on_fail)
|
||||
allowShell=1
|
||||
;;
|
||||
boot.debug1|debug1) # stop right away
|
||||
allowShell=1
|
||||
fail
|
||||
;;
|
||||
boot.debug1devices) # stop after loading modules and creating device nodes
|
||||
allowShell=1
|
||||
debug1devices=1
|
||||
;;
|
||||
boot.debug1mounts) # stop after mounting file systems
|
||||
allowShell=1
|
||||
debug1mounts=1
|
||||
;;
|
||||
boot.panic_on_fail|stage1panic=1)
|
||||
panicOnFail=1
|
||||
;;
|
||||
root=*)
|
||||
# If a root device is specified on the kernel command
|
||||
# line, make it available through the symlink /dev/root.
|
||||
# Recognise LABEL= and UUID= to support UNetbootin.
|
||||
set -- $(IFS==; echo $o)
|
||||
if [ $2 = "LABEL" ]; then
|
||||
root="/dev/disk/by-label/$3"
|
||||
elif [ $2 = "UUID" ]; then
|
||||
root="/dev/disk/by-uuid/$3"
|
||||
else
|
||||
root=$2
|
||||
fi
|
||||
ln -s "$root" /dev/root
|
||||
;;
|
||||
copytoram)
|
||||
copytoram=1
|
||||
;;
|
||||
findiso=*)
|
||||
# if an iso name is supplied, try to find the device where
|
||||
# the iso resides on
|
||||
set -- $(IFS==; echo $o)
|
||||
isoPath=$2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Set hostid before modules are loaded.
|
||||
# This is needed by the spl/zfs modules.
|
||||
@setHostId@
|
||||
|
||||
# Load the required kernel modules.
|
||||
mkdir -p /lib
|
||||
ln -s @modulesClosure@/lib/modules /lib/modules
|
||||
ln -s @modulesClosure@/lib/firmware /lib/firmware
|
||||
# see comment in stage-1.nix for explanation
|
||||
echo @extraUtils@/bin/modprobe-kernel > /proc/sys/kernel/modprobe
|
||||
for i in @kernelModules@; do
|
||||
info "loading module $(basename $i)..."
|
||||
modprobe $i
|
||||
done
|
||||
|
||||
|
||||
# Create device nodes in /dev.
|
||||
@preDeviceCommands@
|
||||
info "running udev..."
|
||||
ln -sfn /proc/self/fd /dev/fd
|
||||
ln -sfn /proc/self/fd/0 /dev/stdin
|
||||
ln -sfn /proc/self/fd/1 /dev/stdout
|
||||
ln -sfn /proc/self/fd/2 /dev/stderr
|
||||
mkdir -p /etc/systemd
|
||||
ln -sfn @linkUnits@ /etc/systemd/network
|
||||
mkdir -p /etc/udev
|
||||
ln -sfn @udevRules@ /etc/udev/rules.d
|
||||
mkdir -p /dev/.mdadm
|
||||
systemd-udevd --daemon
|
||||
udevadm trigger --action=add
|
||||
udevadm settle
|
||||
|
||||
|
||||
# XXX: Use case usb->lvm will still fail, usb->luks->lvm is covered
|
||||
@preLVMCommands@
|
||||
|
||||
info "starting device mapper and LVM..."
|
||||
lvm vgchange -ay
|
||||
|
||||
if test -n "$debug1devices"; then fail; fi
|
||||
|
||||
|
||||
@postDeviceCommands@
|
||||
|
||||
|
||||
# Check the specified file system, if appropriate.
|
||||
checkFS() {
|
||||
local device="$1"
|
||||
local fsType="$2"
|
||||
|
||||
# Only check block devices.
|
||||
if [ ! -b "$device" ]; then return 0; fi
|
||||
|
||||
# Don't check ROM filesystems.
|
||||
if [ "$fsType" = iso9660 -o "$fsType" = udf ]; then return 0; fi
|
||||
|
||||
# Don't check resilient COWs as they validate the fs structures at mount time
|
||||
if [ "$fsType" = btrfs -o "$fsType" = zfs -o "$fsType" = bcachefs ]; then return 0; fi
|
||||
|
||||
# Skip fsck for apfs as the fsck utility does not support repairing the filesystem (no -a option)
|
||||
if [ "$fsType" = apfs ]; then return 0; fi
|
||||
|
||||
# Skip fsck for nilfs2 - not needed by design and no fsck tool for this filesystem.
|
||||
if [ "$fsType" = nilfs2 ]; then return 0; fi
|
||||
|
||||
# Skip fsck for inherently readonly filesystems.
|
||||
if [ "$fsType" = squashfs ]; then return 0; fi
|
||||
|
||||
# If we couldn't figure out the FS type, then skip fsck.
|
||||
if [ "$fsType" = auto ]; then
|
||||
echo 'cannot check filesystem with type "auto"!'
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Device might be already mounted manually
|
||||
# e.g. NBD-device or the host filesystem of the file which contains encrypted root fs
|
||||
if mount | grep -q "^$device on "; then
|
||||
echo "skip checking already mounted $device"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Optionally, skip fsck on journaling filesystems. This option is
|
||||
# a hack - it's mostly because e2fsck on ext3 takes much longer to
|
||||
# recover the journal than the ext3 implementation in the kernel
|
||||
# does (minutes versus seconds).
|
||||
if test -z "@checkJournalingFS@" -a \
|
||||
\( "$fsType" = ext3 -o "$fsType" = ext4 -o "$fsType" = reiserfs \
|
||||
-o "$fsType" = xfs -o "$fsType" = jfs -o "$fsType" = f2fs \)
|
||||
then
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "checking $device..."
|
||||
|
||||
fsck -V -a "$device"
|
||||
fsckResult=$?
|
||||
|
||||
if test $(($fsckResult | 2)) = $fsckResult; then
|
||||
echo "fsck finished, rebooting..."
|
||||
sleep 3
|
||||
reboot -f
|
||||
fi
|
||||
|
||||
if test $(($fsckResult | 4)) = $fsckResult; then
|
||||
echo "$device has unrepaired errors, please fix them manually."
|
||||
fail
|
||||
fi
|
||||
|
||||
if test $fsckResult -ge 8; then
|
||||
echo "fsck on $device failed."
|
||||
fail
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
# Function for mounting a file system.
|
||||
mountFS() {
|
||||
local device="$1"
|
||||
local mountPoint="$2"
|
||||
local options="$3"
|
||||
local fsType="$4"
|
||||
|
||||
if [ "$fsType" = auto ]; then
|
||||
fsType=$(blkid -o value -s TYPE "$device")
|
||||
if [ -z "$fsType" ]; then fsType=auto; fi
|
||||
fi
|
||||
|
||||
# Filter out x- options, which busybox doesn't do yet.
|
||||
local optionsFiltered="$(IFS=,; for i in $options; do if [ "${i:0:2}" != "x-" ]; then echo -n $i,; fi; done)"
|
||||
# Prefix (lower|upper|work)dir with /mnt-root (overlayfs)
|
||||
local optionsPrefixed="$( echo "$optionsFiltered" | sed -E 's#\<(lowerdir|upperdir|workdir)=#\1=/mnt-root#g' )"
|
||||
|
||||
echo "$device /mnt-root$mountPoint $fsType $optionsPrefixed" >> /etc/fstab
|
||||
|
||||
checkFS "$device" "$fsType"
|
||||
|
||||
# Optionally resize the filesystem.
|
||||
case $options in
|
||||
*x-nixos.autoresize*)
|
||||
if [ "$fsType" = ext2 -o "$fsType" = ext3 -o "$fsType" = ext4 ]; then
|
||||
modprobe "$fsType"
|
||||
echo "resizing $device..."
|
||||
e2fsck -fp "$device"
|
||||
resize2fs "$device"
|
||||
elif [ "$fsType" = f2fs ]; then
|
||||
echo "resizing $device..."
|
||||
fsck.f2fs -fp "$device"
|
||||
resize.f2fs "$device"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# Create backing directories for overlayfs
|
||||
if [ "$fsType" = overlay ]; then
|
||||
for i in upper work; do
|
||||
dir="$( echo "$optionsPrefixed" | grep -o "${i}dir=[^,]*" )"
|
||||
mkdir -m 0700 -p "${dir##*=}"
|
||||
done
|
||||
fi
|
||||
|
||||
info "mounting $device on $mountPoint..."
|
||||
|
||||
mkdir -p "/mnt-root$mountPoint"
|
||||
|
||||
# For ZFS and CIFS mounts, retry a few times before giving up.
|
||||
# We do this for ZFS as a workaround for issue NixOS/nixpkgs#25383.
|
||||
local n=0
|
||||
while true; do
|
||||
mount "/mnt-root$mountPoint" && break
|
||||
if [ \( "$fsType" != cifs -a "$fsType" != zfs \) -o "$n" -ge 10 ]; then fail; break; fi
|
||||
echo "retrying..."
|
||||
sleep 1
|
||||
n=$((n + 1))
|
||||
done
|
||||
|
||||
[ "$mountPoint" == "/" ] &&
|
||||
[ -f "/mnt-root/etc/NIXOS_LUSTRATE" ] &&
|
||||
lustrateRoot "/mnt-root"
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
lustrateRoot () {
|
||||
local root="$1"
|
||||
|
||||
echo
|
||||
echo -e "\e[1;33m<<< NixOS is now lustrating the root filesystem (cruft goes to /old-root) >>>\e[0m"
|
||||
echo
|
||||
|
||||
mkdir -m 0755 -p "$root/old-root.tmp"
|
||||
|
||||
echo
|
||||
echo "Moving impurities out of the way:"
|
||||
for d in "$root"/*
|
||||
do
|
||||
[ "$d" == "$root/nix" ] && continue
|
||||
[ "$d" == "$root/boot" ] && continue # Don't render the system unbootable
|
||||
[ "$d" == "$root/old-root.tmp" ] && continue
|
||||
|
||||
mv -v "$d" "$root/old-root.tmp"
|
||||
done
|
||||
|
||||
# Use .tmp to make sure subsequent invokations don't clash
|
||||
mv -v "$root/old-root.tmp" "$root/old-root"
|
||||
|
||||
mkdir -m 0755 -p "$root/etc"
|
||||
touch "$root/etc/NIXOS"
|
||||
|
||||
exec 4< "$root/old-root/etc/NIXOS_LUSTRATE"
|
||||
|
||||
echo
|
||||
echo "Restoring selected impurities:"
|
||||
while read -u 4 keeper; do
|
||||
dirname="$(dirname "$keeper")"
|
||||
mkdir -m 0755 -p "$root/$dirname"
|
||||
cp -av "$root/old-root/$keeper" "$root/$keeper"
|
||||
done
|
||||
|
||||
exec 4>&-
|
||||
}
|
||||
|
||||
|
||||
|
||||
if test -e /sys/power/resume -a -e /sys/power/disk; then
|
||||
if test -n "@resumeDevice@" && waitDevice "@resumeDevice@"; then
|
||||
resumeDev="@resumeDevice@"
|
||||
resumeInfo="$(udevadm info -q property "$resumeDev" )"
|
||||
else
|
||||
for sd in @resumeDevices@; do
|
||||
# Try to detect resume device. According to Ubuntu bug:
|
||||
# https://bugs.launchpad.net/ubuntu/+source/pm-utils/+bug/923326/comments/1
|
||||
# when there are multiple swap devices, we can't know where the hibernate
|
||||
# image will reside. We can check all of them for swsuspend blkid.
|
||||
if waitDevice "$sd"; then
|
||||
resumeInfo="$(udevadm info -q property "$sd")"
|
||||
if [ "$(echo "$resumeInfo" | sed -n 's/^ID_FS_TYPE=//p')" = "swsuspend" ]; then
|
||||
resumeDev="$sd"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if test -n "$resumeDev"; then
|
||||
resumeMajor="$(echo "$resumeInfo" | sed -n 's/^MAJOR=//p')"
|
||||
resumeMinor="$(echo "$resumeInfo" | sed -n 's/^MINOR=//p')"
|
||||
echo "$resumeMajor:$resumeMinor" > /sys/power/resume 2> /dev/null || echo "failed to resume..."
|
||||
fi
|
||||
fi
|
||||
|
||||
# If we have a path to an iso file, find the iso and link it to /dev/root
|
||||
if [ -n "$isoPath" ]; then
|
||||
mkdir -p /findiso
|
||||
|
||||
for delay in 5 10; do
|
||||
blkid | while read -r line; do
|
||||
device=$(echo "$line" | sed 's/:.*//')
|
||||
type=$(echo "$line" | sed 's/.*TYPE="\([^"]*\)".*/\1/')
|
||||
|
||||
mount -t "$type" "$device" /findiso
|
||||
if [ -e "/findiso$isoPath" ]; then
|
||||
ln -sf "/findiso$isoPath" /dev/root
|
||||
break 2
|
||||
else
|
||||
umount /findiso
|
||||
fi
|
||||
done
|
||||
|
||||
sleep "$delay"
|
||||
done
|
||||
fi
|
||||
|
||||
# Try to find and mount the root device.
|
||||
mkdir -p $targetRoot
|
||||
|
||||
exec 3< @fsInfo@
|
||||
|
||||
while read -u 3 mountPoint; do
|
||||
read -u 3 device
|
||||
read -u 3 fsType
|
||||
read -u 3 options
|
||||
|
||||
# !!! Really quick hack to support bind mounts, i.e., where the
|
||||
# "device" should be taken relative to /mnt-root, not /. Assume
|
||||
# that every device that starts with / but doesn't start with /dev
|
||||
# is a bind mount.
|
||||
pseudoDevice=
|
||||
case $device in
|
||||
/dev/*)
|
||||
;;
|
||||
//*)
|
||||
# Don't touch SMB/CIFS paths.
|
||||
pseudoDevice=1
|
||||
;;
|
||||
/*)
|
||||
device=/mnt-root$device
|
||||
;;
|
||||
*)
|
||||
# Not an absolute path; assume that it's a pseudo-device
|
||||
# like an NFS path (e.g. "server:/path").
|
||||
pseudoDevice=1
|
||||
;;
|
||||
esac
|
||||
|
||||
if test -z "$pseudoDevice" && ! waitDevice "$device"; then
|
||||
# If it doesn't appear, try to mount it anyway (and
|
||||
# probably fail). This is a fallback for non-device "devices"
|
||||
# that we don't properly recognise.
|
||||
echo "Timed out waiting for device $device, trying to mount anyway."
|
||||
fi
|
||||
|
||||
# Wait once more for the udev queue to empty, just in case it's
|
||||
# doing something with $device right now.
|
||||
udevadm settle
|
||||
|
||||
# If copytoram is enabled: skip mounting the ISO and copy its content to a tmpfs.
|
||||
if [ -n "$copytoram" ] && [ "$device" = /dev/root ] && [ "$mountPoint" = /iso ]; then
|
||||
fsType=$(blkid -o value -s TYPE "$device")
|
||||
fsSize=$(blockdev --getsize64 "$device" || stat -Lc '%s' "$device")
|
||||
|
||||
mkdir -p /tmp-iso
|
||||
mount -t "$fsType" /dev/root /tmp-iso
|
||||
mountFS tmpfs /iso size="$fsSize" tmpfs
|
||||
|
||||
cp -r /tmp-iso/* /mnt-root/iso/
|
||||
|
||||
umount /tmp-iso
|
||||
rmdir /tmp-iso
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ "$mountPoint" = / ] && [ "$device" = tmpfs ] && [ ! -z "$persistence" ]; then
|
||||
echo persistence...
|
||||
waitDevice "$persistence"
|
||||
echo enabling persistence...
|
||||
mountFS "$persistence" "$mountPoint" "$persistence_opt" "auto"
|
||||
continue
|
||||
fi
|
||||
|
||||
mountFS "$device" "$mountPoint" "$options" "$fsType"
|
||||
done
|
||||
|
||||
exec 3>&-
|
||||
|
||||
|
||||
@postMountCommands@
|
||||
|
||||
|
||||
# Emit a udev rule for /dev/root to prevent systemd from complaining.
|
||||
if [ -e /mnt-root/iso ]; then
|
||||
eval $(udevadm info --export --export-prefix=ROOT_ --device-id-of-file=/mnt-root/iso)
|
||||
else
|
||||
eval $(udevadm info --export --export-prefix=ROOT_ --device-id-of-file=$targetRoot)
|
||||
fi
|
||||
if [ "$ROOT_MAJOR" -a "$ROOT_MINOR" -a "$ROOT_MAJOR" != 0 ]; then
|
||||
mkdir -p /run/udev/rules.d
|
||||
echo 'ACTION=="add|change", SUBSYSTEM=="block", ENV{MAJOR}=="'$ROOT_MAJOR'", ENV{MINOR}=="'$ROOT_MINOR'", SYMLINK+="root"' > /run/udev/rules.d/61-dev-root-link.rules
|
||||
fi
|
||||
|
||||
|
||||
# Stop udevd.
|
||||
udevadm control --exit
|
||||
|
||||
# Reset the logging file descriptors.
|
||||
# Do this just before pkill, which will kill the tee process.
|
||||
exec 1>&$logOutFd 2>&$logErrFd
|
||||
eval "exec $logOutFd>&- $logErrFd>&-"
|
||||
|
||||
# Kill any remaining processes, just to be sure we're not taking any
|
||||
# with us into stage 2. But keep storage daemons like unionfs-fuse.
|
||||
#
|
||||
# Storage daemons are distinguished by an @ in front of their command line:
|
||||
# https://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons/
|
||||
for pid in $(pgrep -v -f '^@'); do
|
||||
# Make sure we don't kill kernel processes, see #15226 and:
|
||||
# http://stackoverflow.com/questions/12213445/identifying-kernel-threads
|
||||
readlink "/proc/$pid/exe" &> /dev/null || continue
|
||||
# Try to avoid killing ourselves.
|
||||
[ $pid -eq $$ ] && continue
|
||||
kill -9 "$pid"
|
||||
done
|
||||
|
||||
if test -n "$debug1mounts"; then fail; fi
|
||||
|
||||
|
||||
# Restore /proc/sys/kernel/modprobe to its original value.
|
||||
echo /sbin/modprobe > /proc/sys/kernel/modprobe
|
||||
|
||||
|
||||
# Start stage 2. `switch_root' deletes all files in the ramfs on the
|
||||
# current root. The path has to be valid in the chroot not outside.
|
||||
if [ ! -e "$targetRoot/$stage2Init" ]; then
|
||||
stage2Check=${stage2Init}
|
||||
while [ "$stage2Check" != "${stage2Check%/*}" ] && [ ! -L "$targetRoot/$stage2Check" ]; do
|
||||
stage2Check=${stage2Check%/*}
|
||||
done
|
||||
if [ ! -L "$targetRoot/$stage2Check" ]; then
|
||||
echo "stage 2 init script ($targetRoot/$stage2Init) not found"
|
||||
fail
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -m 0755 -p $targetRoot/proc $targetRoot/sys $targetRoot/dev $targetRoot/run
|
||||
|
||||
mount --move /proc $targetRoot/proc
|
||||
mount --move /sys $targetRoot/sys
|
||||
mount --move /dev $targetRoot/dev
|
||||
mount --move /run $targetRoot/run
|
||||
|
||||
exec env -i $(type -P switch_root) "$targetRoot" "$stage2Init"
|
||||
|
||||
fail # should never be reached
|
||||
758
nixos/modules/system/boot/stage-1.nix
Normal file
758
nixos/modules/system/boot/stage-1.nix
Normal file
|
|
@ -0,0 +1,758 @@
|
|||
# This module builds the initial ramdisk, which contains an init
|
||||
# script that performs the first stage of booting the system: it loads
|
||||
# the modules necessary to mount the root file system, then calls the
|
||||
# init in the root file system to start the second boot stage.
|
||||
|
||||
{ config, lib, utils, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
udev = config.systemd.package;
|
||||
|
||||
kernel-name = config.boot.kernelPackages.kernel.name or "kernel";
|
||||
|
||||
modulesTree = config.system.modulesTree.override { name = kernel-name + "-modules"; };
|
||||
firmware = config.hardware.firmware;
|
||||
|
||||
|
||||
# Determine the set of modules that we need to mount the root FS.
|
||||
modulesClosure = pkgs.makeModulesClosure {
|
||||
rootModules = config.boot.initrd.availableKernelModules ++ config.boot.initrd.kernelModules;
|
||||
kernel = modulesTree;
|
||||
firmware = firmware;
|
||||
allowMissing = false;
|
||||
};
|
||||
|
||||
|
||||
# The initrd only has to mount `/` or any FS marked as necessary for
|
||||
# booting (such as the FS containing `/nix/store`, or an FS needed for
|
||||
# mounting `/`, like `/` on a loopback).
|
||||
fileSystems = filter utils.fsNeededForBoot config.system.build.fileSystems;
|
||||
|
||||
# Determine whether zfs-mount(8) is needed.
|
||||
zfsRequiresMountHelper = any (fs: lib.elem "zfsutil" fs.options) fileSystems;
|
||||
|
||||
# A utility for enumerating the shared-library dependencies of a program
|
||||
findLibs = pkgs.buildPackages.writeShellScriptBin "find-libs" ''
|
||||
set -euo pipefail
|
||||
|
||||
declare -A seen
|
||||
left=()
|
||||
|
||||
patchelf="${pkgs.buildPackages.patchelf}/bin/patchelf"
|
||||
|
||||
function add_needed {
|
||||
rpath="$($patchelf --print-rpath $1)"
|
||||
dir="$(dirname $1)"
|
||||
for lib in $($patchelf --print-needed $1); do
|
||||
left+=("$lib" "$rpath" "$dir")
|
||||
done
|
||||
}
|
||||
|
||||
add_needed "$1"
|
||||
|
||||
while [ ''${#left[@]} -ne 0 ]; do
|
||||
next=''${left[0]}
|
||||
rpath=''${left[1]}
|
||||
ORIGIN=''${left[2]}
|
||||
left=("''${left[@]:3}")
|
||||
if [ -z ''${seen[$next]+x} ]; then
|
||||
seen[$next]=1
|
||||
|
||||
# Ignore the dynamic linker which for some reason appears as a DT_NEEDED of glibc but isn't in glibc's RPATH.
|
||||
case "$next" in
|
||||
ld*.so.?) continue;;
|
||||
esac
|
||||
|
||||
IFS=: read -ra paths <<< $rpath
|
||||
res=
|
||||
for path in "''${paths[@]}"; do
|
||||
path=$(eval "echo $path")
|
||||
if [ -f "$path/$next" ]; then
|
||||
res="$path/$next"
|
||||
echo "$res"
|
||||
add_needed "$res"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -z "$res" ]; then
|
||||
echo "Couldn't satisfy dependency $next" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
'';
|
||||
|
||||
# Some additional utilities needed in stage 1, like mount, lvm, fsck
|
||||
# etc. We don't want to bring in all of those packages, so we just
|
||||
# copy what we need. Instead of using statically linked binaries,
|
||||
# we just copy what we need from Glibc and use patchelf to make it
|
||||
# work.
|
||||
extraUtils = pkgs.runCommandCC "extra-utils"
|
||||
{ nativeBuildInputs = [pkgs.buildPackages.nukeReferences];
|
||||
allowedReferences = [ "out" ]; # prevent accidents like glibc being included in the initrd
|
||||
}
|
||||
''
|
||||
set +o pipefail
|
||||
|
||||
mkdir -p $out/bin $out/lib
|
||||
ln -s $out/bin $out/sbin
|
||||
|
||||
copy_bin_and_libs () {
|
||||
[ -f "$out/bin/$(basename $1)" ] && rm "$out/bin/$(basename $1)"
|
||||
cp -pdv $1 $out/bin
|
||||
}
|
||||
|
||||
# Copy BusyBox.
|
||||
for BIN in ${pkgs.busybox}/{s,}bin/*; do
|
||||
copy_bin_and_libs $BIN
|
||||
done
|
||||
|
||||
${optionalString zfsRequiresMountHelper ''
|
||||
# Filesystems using the "zfsutil" option are mounted regardless of the
|
||||
# mount.zfs(8) helper, but it is required to ensure that ZFS properties
|
||||
# are used as mount options.
|
||||
#
|
||||
# BusyBox does not use the ZFS helper in the first place.
|
||||
# util-linux searches /sbin/ as last path for helpers (stage-1-init.sh
|
||||
# must symlink it to the store PATH).
|
||||
# Without helper program, both `mount`s silently fails back to internal
|
||||
# code, using default options and effectively ignore security relevant
|
||||
# ZFS properties such as `setuid=off` and `exec=off` (unless manually
|
||||
# duplicated in `fileSystems.*.options`, defeating "zfsutil"'s purpose).
|
||||
copy_bin_and_libs ${pkgs.util-linux}/bin/mount
|
||||
copy_bin_and_libs ${pkgs.zfs}/bin/mount.zfs
|
||||
''}
|
||||
|
||||
# Copy some util-linux stuff.
|
||||
copy_bin_and_libs ${pkgs.util-linux}/sbin/blkid
|
||||
|
||||
# Copy dmsetup and lvm.
|
||||
copy_bin_and_libs ${getBin pkgs.lvm2}/bin/dmsetup
|
||||
copy_bin_and_libs ${getBin pkgs.lvm2}/bin/lvm
|
||||
|
||||
# Add RAID mdadm tool.
|
||||
copy_bin_and_libs ${pkgs.mdadm}/sbin/mdadm
|
||||
copy_bin_and_libs ${pkgs.mdadm}/sbin/mdmon
|
||||
|
||||
# Copy udev.
|
||||
copy_bin_and_libs ${udev}/bin/udevadm
|
||||
copy_bin_and_libs ${udev}/lib/systemd/systemd-sysctl
|
||||
for BIN in ${udev}/lib/udev/*_id; do
|
||||
copy_bin_and_libs $BIN
|
||||
done
|
||||
# systemd-udevd is only a symlink to udevadm these days
|
||||
ln -sf udevadm $out/bin/systemd-udevd
|
||||
|
||||
# Copy modprobe.
|
||||
copy_bin_and_libs ${pkgs.kmod}/bin/kmod
|
||||
ln -sf kmod $out/bin/modprobe
|
||||
|
||||
# Dirty hack to make sure the kernel properly loads modules
|
||||
# such as ext4 on demand (e.g. on a `mount(2)` syscall). This is necessary
|
||||
# because `kmod` isn't linked against `libpthread.so.0` anymore (since
|
||||
# it was merged into `libc.so.6` since version `2.34`), but still needs
|
||||
# to access it for some reason. This is not an issue in stage-1 itself
|
||||
# because of the `LD_LIBRARY_PATH`-variable and anytime later because the rpath of
|
||||
# kmod/modprobe points to glibc's `$out/lib` where `libpthread.so.6` exists.
|
||||
# However, this is a problem when the kernel calls `modprobe` inside
|
||||
# the initial ramdisk because it doesn't know about the
|
||||
# `LD_LIBRARY_PATH` and the rpath was nuked.
|
||||
#
|
||||
# Also, we can't use `makeWrapper` here because `kmod` only does
|
||||
# `modprobe` functionality if `argv[0] == "modprobe"`.
|
||||
cat >$out/bin/modprobe-kernel <<EOF
|
||||
#!$out/bin/ash
|
||||
export LD_LIBRARY_PATH=$out/lib
|
||||
exec $out/bin/modprobe "\$@"
|
||||
EOF
|
||||
chmod +x $out/bin/modprobe-kernel
|
||||
|
||||
# Copy resize2fs if any ext* filesystems are to be resized
|
||||
${optionalString (any (fs: fs.autoResize && (lib.hasPrefix "ext" fs.fsType)) fileSystems) ''
|
||||
# We need mke2fs in the initrd.
|
||||
copy_bin_and_libs ${pkgs.e2fsprogs}/sbin/resize2fs
|
||||
''}
|
||||
|
||||
# Copy multipath.
|
||||
${optionalString config.services.multipath.enable ''
|
||||
copy_bin_and_libs ${config.services.multipath.package}/bin/multipath
|
||||
copy_bin_and_libs ${config.services.multipath.package}/bin/multipathd
|
||||
# Copy lib/multipath manually.
|
||||
cp -rpv ${config.services.multipath.package}/lib/multipath $out/lib
|
||||
''}
|
||||
|
||||
# Copy secrets if needed.
|
||||
#
|
||||
# TODO: move out to a separate script; see #85000.
|
||||
${optionalString (!config.boot.loader.supportsInitrdSecrets)
|
||||
(concatStringsSep "\n" (mapAttrsToList (dest: source:
|
||||
let source' = if source == null then dest else source; in
|
||||
''
|
||||
mkdir -p $(dirname "$out/secrets/${dest}")
|
||||
# Some programs (e.g. ssh) doesn't like secrets to be
|
||||
# symlinks, so we use `cp -L` here to match the
|
||||
# behaviour when secrets are natively supported.
|
||||
cp -Lr ${source'} "$out/secrets/${dest}"
|
||||
''
|
||||
) config.boot.initrd.secrets))
|
||||
}
|
||||
|
||||
${config.boot.initrd.extraUtilsCommands}
|
||||
|
||||
# Copy ld manually since it isn't detected correctly
|
||||
cp -pv ${pkgs.stdenv.cc.libc.out}/lib/ld*.so.? $out/lib
|
||||
|
||||
# Copy all of the needed libraries
|
||||
find $out/bin $out/lib -type f | while read BIN; do
|
||||
echo "Copying libs for executable $BIN"
|
||||
for LIB in $(${findLibs}/bin/find-libs $BIN); do
|
||||
TGT="$out/lib/$(basename $LIB)"
|
||||
if [ ! -f "$TGT" ]; then
|
||||
SRC="$(readlink -e $LIB)"
|
||||
cp -pdv "$SRC" "$TGT"
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
# Strip binaries further than normal.
|
||||
chmod -R u+w $out
|
||||
stripDirs "$STRIP" "lib bin" "-s"
|
||||
|
||||
# Run patchelf to make the programs refer to the copied libraries.
|
||||
find $out/bin $out/lib -type f | while read i; do
|
||||
nuke-refs -e $out $i
|
||||
done
|
||||
|
||||
find $out/bin -type f | while read i; do
|
||||
echo "patching $i..."
|
||||
patchelf --set-interpreter $out/lib/ld*.so.? --set-rpath $out/lib $i || true
|
||||
done
|
||||
|
||||
find $out/lib -type f \! -name 'ld*.so.?' | while read i; do
|
||||
echo "patching $i..."
|
||||
patchelf --set-rpath $out/lib $i
|
||||
done
|
||||
|
||||
if [ -z "${toString (pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform)}" ]; then
|
||||
# Make sure that the patchelf'ed binaries still work.
|
||||
echo "testing patched programs..."
|
||||
$out/bin/ash -c 'echo hello world' | grep "hello world"
|
||||
${if zfsRequiresMountHelper then ''
|
||||
$out/bin/mount -V 1>&1 | grep -q "mount from util-linux"
|
||||
$out/bin/mount.zfs -h 2>&1 | grep -q "Usage: mount.zfs"
|
||||
'' else ''
|
||||
$out/bin/mount --help 2>&1 | grep -q "BusyBox"
|
||||
''}
|
||||
$out/bin/blkid -V 2>&1 | grep -q 'libblkid'
|
||||
$out/bin/udevadm --version
|
||||
$out/bin/dmsetup --version 2>&1 | tee -a log | grep -q "version:"
|
||||
LVM_SYSTEM_DIR=$out $out/bin/lvm version 2>&1 | tee -a log | grep -q "LVM"
|
||||
$out/bin/mdadm --version
|
||||
${optionalString config.services.multipath.enable ''
|
||||
($out/bin/multipath || true) 2>&1 | grep -q 'need to be root'
|
||||
($out/bin/multipathd || true) 2>&1 | grep -q 'need to be root'
|
||||
''}
|
||||
|
||||
${config.boot.initrd.extraUtilsCommandsTest}
|
||||
fi
|
||||
''; # */
|
||||
|
||||
|
||||
# Networkd link files are used early by udev to set up interfaces early.
|
||||
# This must be done in stage 1 to avoid race conditions between udev and
|
||||
# network daemons.
|
||||
linkUnits = pkgs.runCommand "link-units" {
|
||||
allowedReferences = [ extraUtils ];
|
||||
preferLocalBuild = true;
|
||||
} (''
|
||||
mkdir -p $out
|
||||
cp -v ${udev}/lib/systemd/network/*.link $out/
|
||||
'' + (
|
||||
let
|
||||
links = filterAttrs (n: v: hasSuffix ".link" n) config.systemd.network.units;
|
||||
files = mapAttrsToList (n: v: "${v.unit}/${n}") links;
|
||||
in
|
||||
concatMapStringsSep "\n" (file: "cp -v ${file} $out/") files
|
||||
));
|
||||
|
||||
udevRules = pkgs.runCommand "udev-rules" {
|
||||
allowedReferences = [ extraUtils ];
|
||||
preferLocalBuild = true;
|
||||
} ''
|
||||
mkdir -p $out
|
||||
|
||||
cp -v ${udev}/lib/udev/rules.d/60-cdrom_id.rules $out/
|
||||
cp -v ${udev}/lib/udev/rules.d/60-persistent-storage.rules $out/
|
||||
cp -v ${udev}/lib/udev/rules.d/75-net-description.rules $out/
|
||||
cp -v ${udev}/lib/udev/rules.d/80-drivers.rules $out/
|
||||
cp -v ${udev}/lib/udev/rules.d/80-net-setup-link.rules $out/
|
||||
cp -v ${pkgs.lvm2}/lib/udev/rules.d/*.rules $out/
|
||||
${config.boot.initrd.extraUdevRulesCommands}
|
||||
|
||||
for i in $out/*.rules; do
|
||||
substituteInPlace $i \
|
||||
--replace ata_id ${extraUtils}/bin/ata_id \
|
||||
--replace scsi_id ${extraUtils}/bin/scsi_id \
|
||||
--replace cdrom_id ${extraUtils}/bin/cdrom_id \
|
||||
--replace ${pkgs.coreutils}/bin/basename ${extraUtils}/bin/basename \
|
||||
--replace ${pkgs.util-linux}/bin/blkid ${extraUtils}/bin/blkid \
|
||||
--replace ${getBin pkgs.lvm2}/bin ${extraUtils}/bin \
|
||||
--replace ${pkgs.mdadm}/sbin ${extraUtils}/sbin \
|
||||
--replace ${pkgs.bash}/bin/sh ${extraUtils}/bin/sh \
|
||||
--replace ${udev} ${extraUtils}
|
||||
done
|
||||
|
||||
# Work around a bug in QEMU, which doesn't implement the "READ
|
||||
# DISC INFORMATION" SCSI command:
|
||||
# https://bugzilla.redhat.com/show_bug.cgi?id=609049
|
||||
# As a result, `cdrom_id' doesn't print
|
||||
# ID_CDROM_MEDIA_TRACK_COUNT_DATA, which in turn prevents the
|
||||
# /dev/disk/by-label symlinks from being created. We need these
|
||||
# in the NixOS installation CD, so use ID_CDROM_MEDIA in the
|
||||
# corresponding udev rules for now. This was the behaviour in
|
||||
# udev <= 154. See also
|
||||
# http://www.spinics.net/lists/hotplug/msg03935.html
|
||||
substituteInPlace $out/60-persistent-storage.rules \
|
||||
--replace ID_CDROM_MEDIA_TRACK_COUNT_DATA ID_CDROM_MEDIA
|
||||
''; # */
|
||||
|
||||
|
||||
# The init script of boot stage 1 (loading kernel modules for
|
||||
# mounting the root FS).
|
||||
bootStage1 = pkgs.substituteAll {
|
||||
src = ./stage-1-init.sh;
|
||||
|
||||
shell = "${extraUtils}/bin/ash";
|
||||
|
||||
isExecutable = true;
|
||||
|
||||
postInstall = ''
|
||||
echo checking syntax
|
||||
# check both with bash
|
||||
${pkgs.buildPackages.bash}/bin/sh -n $target
|
||||
# and with ash shell, just in case
|
||||
${pkgs.buildPackages.busybox}/bin/ash -n $target
|
||||
'';
|
||||
|
||||
inherit linkUnits udevRules extraUtils modulesClosure;
|
||||
|
||||
inherit (config.boot) resumeDevice;
|
||||
|
||||
inherit (config.system.build) earlyMountScript;
|
||||
|
||||
inherit (config.boot.initrd) checkJournalingFS verbose
|
||||
preLVMCommands preDeviceCommands postDeviceCommands postMountCommands preFailCommands kernelModules;
|
||||
|
||||
resumeDevices = map (sd: if sd ? device then sd.device else "/dev/disk/by-label/${sd.label}")
|
||||
(filter (sd: hasPrefix "/dev/" sd.device && !sd.randomEncryption.enable
|
||||
# Don't include zram devices
|
||||
&& !(hasPrefix "/dev/zram" sd.device)
|
||||
) config.swapDevices);
|
||||
|
||||
fsInfo =
|
||||
let f = fs: [ fs.mountPoint (if fs.device != null then fs.device else "/dev/disk/by-label/${fs.label}") fs.fsType (builtins.concatStringsSep "," fs.options) ];
|
||||
in pkgs.writeText "initrd-fsinfo" (concatStringsSep "\n" (concatMap f fileSystems));
|
||||
|
||||
setHostId = optionalString (config.networking.hostId != null) ''
|
||||
hi="${config.networking.hostId}"
|
||||
${if pkgs.stdenv.isBigEndian then ''
|
||||
echo -ne "\x''${hi:0:2}\x''${hi:2:2}\x''${hi:4:2}\x''${hi:6:2}" > /etc/hostid
|
||||
'' else ''
|
||||
echo -ne "\x''${hi:6:2}\x''${hi:4:2}\x''${hi:2:2}\x''${hi:0:2}" > /etc/hostid
|
||||
''}
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
# The closure of the init script of boot stage 1 is what we put in
|
||||
# the initial RAM disk.
|
||||
initialRamdisk = pkgs.makeInitrd {
|
||||
name = "initrd-${kernel-name}";
|
||||
inherit (config.boot.initrd) compressor compressorArgs prepend;
|
||||
|
||||
contents =
|
||||
[ { object = bootStage1;
|
||||
symlink = "/init";
|
||||
}
|
||||
{ object = pkgs.writeText "mdadm.conf" config.boot.initrd.services.swraid.mdadmConf;
|
||||
symlink = "/etc/mdadm.conf";
|
||||
}
|
||||
{ object = pkgs.runCommand "initrd-kmod-blacklist-ubuntu" {
|
||||
src = "${pkgs.kmod-blacklist-ubuntu}/modprobe.conf";
|
||||
preferLocalBuild = true;
|
||||
} ''
|
||||
target=$out
|
||||
${pkgs.buildPackages.perl}/bin/perl -0pe 's/## file: iwlwifi.conf(.+?)##/##/s;' $src > $out
|
||||
'';
|
||||
symlink = "/etc/modprobe.d/ubuntu.conf";
|
||||
}
|
||||
{ object = config.environment.etc."modprobe.d/nixos.conf".source;
|
||||
symlink = "/etc/modprobe.d/nixos.conf";
|
||||
}
|
||||
{ object = pkgs.kmod-debian-aliases;
|
||||
symlink = "/etc/modprobe.d/debian.conf";
|
||||
}
|
||||
] ++ lib.optionals config.services.multipath.enable [
|
||||
{ object = pkgs.runCommand "multipath.conf" {
|
||||
src = config.environment.etc."multipath.conf".text;
|
||||
preferLocalBuild = true;
|
||||
} ''
|
||||
target=$out
|
||||
printf "$src" > $out
|
||||
substituteInPlace $out \
|
||||
--replace ${config.services.multipath.package}/lib ${extraUtils}/lib
|
||||
'';
|
||||
symlink = "/etc/multipath.conf";
|
||||
}
|
||||
] ++ (lib.mapAttrsToList
|
||||
(symlink: options:
|
||||
{
|
||||
inherit symlink;
|
||||
object = options.source;
|
||||
}
|
||||
)
|
||||
config.boot.initrd.extraFiles);
|
||||
};
|
||||
|
||||
# Script to add secret files to the initrd at bootloader update time
|
||||
initialRamdiskSecretAppender =
|
||||
let
|
||||
compressorExe = initialRamdisk.compressorExecutableFunction pkgs;
|
||||
in pkgs.writeScriptBin "append-initrd-secrets"
|
||||
''
|
||||
#!${pkgs.bash}/bin/bash -e
|
||||
function usage {
|
||||
echo "USAGE: $0 INITRD_FILE" >&2
|
||||
echo "Appends this configuration's secrets to INITRD_FILE" >&2
|
||||
}
|
||||
|
||||
if [ $# -ne 1 ]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$1"x = "--helpx" ]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
${lib.optionalString (config.boot.initrd.secrets == {})
|
||||
"exit 0"}
|
||||
|
||||
export PATH=${pkgs.coreutils}/bin:${pkgs.libarchive}/bin:${pkgs.gzip}/bin:${pkgs.findutils}/bin
|
||||
|
||||
function cleanup {
|
||||
if [ -n "$tmp" -a -d "$tmp" ]; then
|
||||
rm -fR "$tmp"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
tmp=$(mktemp -d ''${TMPDIR:-/tmp}/initrd-secrets.XXXXXXXXXX)
|
||||
|
||||
${lib.concatStringsSep "\n" (mapAttrsToList (dest: source:
|
||||
let source' = if source == null then dest else toString source; in
|
||||
''
|
||||
mkdir -p $(dirname "$tmp/.initrd-secrets/${dest}")
|
||||
cp -a ${source'} "$tmp/.initrd-secrets/${dest}"
|
||||
''
|
||||
) config.boot.initrd.secrets)
|
||||
}
|
||||
|
||||
(cd "$tmp" && find . -print0 | sort -z | bsdtar --uid 0 --gid 0 -cnf - -T - | bsdtar --null -cf - --format=newc @-) | \
|
||||
${compressorExe} ${lib.escapeShellArgs initialRamdisk.compressorArgs} >> "$1"
|
||||
'';
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
|
||||
boot.resumeDevice = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
example = "/dev/sda3";
|
||||
description = ''
|
||||
Device for manual resume attempt during boot. This should be used primarily
|
||||
if you want to resume from file. If left empty, the swap partitions are used.
|
||||
Specify here the device where the file resides.
|
||||
You should also use <varname>boot.kernelParams</varname> to specify
|
||||
<literal><replaceable>resume_offset</replaceable></literal>.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.enable = mkOption {
|
||||
type = types.bool;
|
||||
default = !config.boot.isContainer;
|
||||
defaultText = literalExpression "!config.boot.isContainer";
|
||||
description = ''
|
||||
Whether to enable the NixOS initial RAM disk (initrd). This may be
|
||||
needed to perform some initialisation tasks (like mounting
|
||||
network/encrypted file systems) before continuing the boot process.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.extraFiles = mkOption {
|
||||
default = { };
|
||||
type = types.attrsOf
|
||||
(types.submodule {
|
||||
options = {
|
||||
source = mkOption {
|
||||
type = types.package;
|
||||
description = "The object to make available inside the initrd.";
|
||||
};
|
||||
};
|
||||
});
|
||||
description = ''
|
||||
Extra files to link and copy in to the initrd.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.prepend = mkOption {
|
||||
default = [ ];
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
Other initrd files to prepend to the final initrd we are building.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.checkJournalingFS = mkOption {
|
||||
default = true;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to run <command>fsck</command> on journaling filesystems such as ext3.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.preLVMCommands = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Shell commands to be executed immediately before LVM discovery.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.preDeviceCommands = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Shell commands to be executed before udev is started to create
|
||||
device nodes.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.postDeviceCommands = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Shell commands to be executed immediately after stage 1 of the
|
||||
boot has loaded kernel modules and created device nodes in
|
||||
<filename>/dev</filename>.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.postMountCommands = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Shell commands to be executed immediately after the stage 1
|
||||
filesystems have been mounted.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.preFailCommands = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Shell commands to be executed before the failure prompt is shown.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.extraUtilsCommands = mkOption {
|
||||
internal = true;
|
||||
default = "";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Shell commands to be executed in the builder of the
|
||||
extra-utils derivation. This can be used to provide
|
||||
additional utilities in the initial ramdisk.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.extraUtilsCommandsTest = mkOption {
|
||||
internal = true;
|
||||
default = "";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Shell commands to be executed in the builder of the
|
||||
extra-utils derivation after patchelf has done its
|
||||
job. This can be used to test additional utilities
|
||||
copied in extraUtilsCommands.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.extraUdevRulesCommands = mkOption {
|
||||
internal = true;
|
||||
default = "";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Shell commands to be executed in the builder of the
|
||||
udev-rules derivation. This can be used to add
|
||||
additional udev rules in the initial ramdisk.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.compressor = mkOption {
|
||||
default = (
|
||||
if lib.versionAtLeast config.boot.kernelPackages.kernel.version "5.9"
|
||||
then "zstd"
|
||||
else "gzip"
|
||||
);
|
||||
defaultText = literalDocBook "<literal>zstd</literal> if the kernel supports it (5.9+), <literal>gzip</literal> if not";
|
||||
type = types.either types.str (types.functionTo types.str);
|
||||
description = ''
|
||||
The compressor to use on the initrd image. May be any of:
|
||||
|
||||
<itemizedlist>
|
||||
<listitem><para>The name of one of the predefined compressors, see <filename>pkgs/build-support/kernel/initrd-compressor-meta.nix</filename> for the definitions.</para></listitem>
|
||||
<listitem><para>A function which, given the nixpkgs package set, returns the path to a compressor tool, e.g. <literal>pkgs: "''${pkgs.pigz}/bin/pigz"</literal></para></listitem>
|
||||
<listitem><para>(not recommended, because it does not work when cross-compiling) the full path to a compressor tool, e.g. <literal>"''${pkgs.pigz}/bin/pigz"</literal></para></listitem>
|
||||
</itemizedlist>
|
||||
|
||||
The given program should read data from stdin and write it to stdout compressed.
|
||||
'';
|
||||
example = "xz";
|
||||
};
|
||||
|
||||
boot.initrd.compressorArgs = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr (types.listOf types.str);
|
||||
description = "Arguments to pass to the compressor for the initrd image, or null to use the compressor's defaults.";
|
||||
};
|
||||
|
||||
boot.initrd.secrets = mkOption
|
||||
{ default = {};
|
||||
type = types.attrsOf (types.nullOr types.path);
|
||||
description =
|
||||
''
|
||||
Secrets to append to the initrd. The attribute name is the
|
||||
path the secret should have inside the initrd, the value
|
||||
is the path it should be copied from (or null for the same
|
||||
path inside and out).
|
||||
'';
|
||||
example = literalExpression
|
||||
''
|
||||
{ "/etc/dropbear/dropbear_rsa_host_key" =
|
||||
./secret-dropbear-key;
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.supportedFilesystems = mkOption {
|
||||
default = [ ];
|
||||
example = [ "btrfs" ];
|
||||
type = types.listOf types.str;
|
||||
description = "Names of supported filesystem types in the initial ramdisk.";
|
||||
};
|
||||
|
||||
boot.initrd.verbose = mkOption {
|
||||
default = true;
|
||||
type = types.bool;
|
||||
description =
|
||||
''
|
||||
Verbosity of the initrd. Please note that disabling verbosity removes
|
||||
only the mandatory messages generated by the NixOS scripts. For a
|
||||
completely silent boot, you might also want to set the two following
|
||||
configuration options:
|
||||
|
||||
<itemizedlist>
|
||||
<listitem><para><literal>boot.consoleLogLevel = 0;</literal></para></listitem>
|
||||
<listitem><para><literal>boot.kernelParams = [ "quiet" "udev.log_level=3" ];</literal></para></listitem>
|
||||
</itemizedlist>
|
||||
'';
|
||||
};
|
||||
|
||||
boot.loader.supportsInitrdSecrets = mkOption
|
||||
{ internal = true;
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description =
|
||||
''
|
||||
Whether the bootloader setup runs append-initrd-secrets.
|
||||
If not, any needed secrets must be copied into the initrd
|
||||
and thus added to the store.
|
||||
'';
|
||||
};
|
||||
|
||||
fileSystems = mkOption {
|
||||
type = with lib.types; attrsOf (submodule {
|
||||
options.neededForBoot = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
If set, this file system will be mounted in the initial ramdisk.
|
||||
Note that the file system will always be mounted in the initial
|
||||
ramdisk if its mount point is one of the following:
|
||||
${concatStringsSep ", " (
|
||||
forEach utils.pathsNeededForBoot (i: "<filename>${i}</filename>")
|
||||
)}.
|
||||
'';
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf config.boot.initrd.enable {
|
||||
assertions = [
|
||||
{ assertion = any (fs: fs.mountPoint == "/") fileSystems;
|
||||
message = "The ‘fileSystems’ option does not specify your root file system.";
|
||||
}
|
||||
{ assertion = let inherit (config.boot) resumeDevice; in
|
||||
resumeDevice == "" || builtins.substring 0 1 resumeDevice == "/";
|
||||
message = "boot.resumeDevice has to be an absolute path."
|
||||
+ " Old \"x:y\" style is no longer supported.";
|
||||
}
|
||||
# TODO: remove when #85000 is fixed
|
||||
{ assertion = !config.boot.loader.supportsInitrdSecrets ->
|
||||
all (source:
|
||||
builtins.isPath source ||
|
||||
(builtins.isString source && hasPrefix builtins.storeDir source))
|
||||
(attrValues config.boot.initrd.secrets);
|
||||
message = ''
|
||||
boot.loader.initrd.secrets values must be unquoted paths when
|
||||
using a bootloader that doesn't natively support initrd
|
||||
secrets, e.g.:
|
||||
|
||||
boot.initrd.secrets = {
|
||||
"/etc/secret" = /path/to/secret;
|
||||
};
|
||||
|
||||
Note that this will result in all secrets being stored
|
||||
world-readable in the Nix store!
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
system.build = mkMerge [
|
||||
{ inherit bootStage1 initialRamdiskSecretAppender extraUtils; }
|
||||
|
||||
# generated in nixos/modules/system/boot/systemd/initrd.nix
|
||||
(mkIf (!config.boot.initrd.systemd.enable) { inherit initialRamdisk; })
|
||||
];
|
||||
|
||||
system.requiredKernelConfig = with config.lib.kernelConfig; [
|
||||
(isYes "TMPFS")
|
||||
(isYes "BLK_DEV_INITRD")
|
||||
];
|
||||
|
||||
boot.initrd.supportedFilesystems = map (fs: fs.fsType) fileSystems;
|
||||
};
|
||||
|
||||
imports = [
|
||||
(mkRenamedOptionModule [ "boot" "initrd" "mdadmConf" ] [ "boot" "initrd" "services" "swraid" "mdadmConf" ])
|
||||
];
|
||||
}
|
||||
144
nixos/modules/system/boot/stage-2-init.sh
Executable file
144
nixos/modules/system/boot/stage-2-init.sh
Executable file
|
|
@ -0,0 +1,144 @@
|
|||
#! @shell@
|
||||
|
||||
systemConfig=@systemConfig@
|
||||
|
||||
export HOME=/root PATH="@path@"
|
||||
|
||||
|
||||
if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" != true ]; then
|
||||
# Process the kernel command line.
|
||||
for o in $(</proc/cmdline); do
|
||||
case $o in
|
||||
boot.debugtrace)
|
||||
# Show each command.
|
||||
set -x
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
|
||||
# Print a greeting.
|
||||
echo
|
||||
echo -e "\e[1;32m<<< NixOS Stage 2 >>>\e[0m"
|
||||
echo
|
||||
|
||||
|
||||
# Normally, stage 1 mounts the root filesystem read/writable.
|
||||
# However, in some environments, stage 2 is executed directly, and the
|
||||
# root is read-only. So make it writable here.
|
||||
if [ -z "$container" ]; then
|
||||
mount -n -o remount,rw none /
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
# Likewise, stage 1 mounts /proc, /dev and /sys, so if we don't have a
|
||||
# stage 1, we need to do that here.
|
||||
if [ ! -e /proc/1 ]; then
|
||||
specialMount() {
|
||||
local device="$1"
|
||||
local mountPoint="$2"
|
||||
local options="$3"
|
||||
local fsType="$4"
|
||||
|
||||
# We must not overwrite this mount because it's bind-mounted
|
||||
# from stage 1's /run
|
||||
if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" = true ] && [ "${mountPoint}" = /run ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
install -m 0755 -d "$mountPoint"
|
||||
mount -n -t "$fsType" -o "$options" "$device" "$mountPoint"
|
||||
}
|
||||
source @earlyMountScript@
|
||||
fi
|
||||
|
||||
|
||||
if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" = true ]; then
|
||||
echo "booting system configuration ${systemConfig}"
|
||||
else
|
||||
echo "booting system configuration $systemConfig" > /dev/kmsg
|
||||
fi
|
||||
|
||||
|
||||
# Make /nix/store a read-only bind mount to enforce immutability of
|
||||
# the Nix store. Note that we can't use "chown root:nixbld" here
|
||||
# because users/groups might not exist yet.
|
||||
# Silence chown/chmod to fail gracefully on a readonly filesystem
|
||||
# like squashfs.
|
||||
chown -f 0:30000 /nix/store
|
||||
chmod -f 1775 /nix/store
|
||||
if [ -n "@readOnlyStore@" ]; then
|
||||
if ! [[ "$(findmnt --noheadings --output OPTIONS /nix/store)" =~ ro(,|$) ]]; then
|
||||
if [ -z "$container" ]; then
|
||||
mount --bind /nix/store /nix/store
|
||||
else
|
||||
mount --rbind /nix/store /nix/store
|
||||
fi
|
||||
mount -o remount,ro,bind /nix/store
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" != true ]; then
|
||||
# Use /etc/resolv.conf supplied by systemd-nspawn, if applicable.
|
||||
if [ -n "@useHostResolvConf@" ] && [ -e /etc/resolv.conf ]; then
|
||||
resolvconf -m 1000 -a host </etc/resolv.conf
|
||||
fi
|
||||
|
||||
|
||||
# Log the script output to /dev/kmsg or /run/log/stage-2-init.log.
|
||||
# Only at this point are all the necessary prerequisites ready for these commands.
|
||||
exec {logOutFd}>&1 {logErrFd}>&2
|
||||
if test -w /dev/kmsg; then
|
||||
exec > >(tee -i /proc/self/fd/"$logOutFd" | while read -r line; do
|
||||
if test -n "$line"; then
|
||||
echo "<7>stage-2-init: $line" > /dev/kmsg
|
||||
fi
|
||||
done) 2>&1
|
||||
else
|
||||
mkdir -p /run/log
|
||||
exec > >(tee -i /run/log/stage-2-init.log) 2>&1
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
# Required by the activation script
|
||||
install -m 0755 -d /etc /etc/nixos
|
||||
install -m 01777 -d /tmp
|
||||
|
||||
|
||||
# Run the script that performs all configuration activation that does
|
||||
# not have to be done at boot time.
|
||||
echo "running activation script..."
|
||||
$systemConfig/activate
|
||||
|
||||
|
||||
# Record the boot configuration.
|
||||
ln -sfn "$systemConfig" /run/booted-system
|
||||
|
||||
|
||||
# Run any user-specified commands.
|
||||
@shell@ @postBootCommands@
|
||||
|
||||
|
||||
# Ensure systemd doesn't try to populate /etc, by forcing its first-boot
|
||||
# heuristic off. It doesn't matter what's in /etc/machine-id for this purpose,
|
||||
# and systemd will immediately fill in the file when it starts, so just
|
||||
# creating it is enough. This `: >>` pattern avoids forking and avoids changing
|
||||
# the mtime if the file already exists.
|
||||
: >> /etc/machine-id
|
||||
|
||||
|
||||
# No need to restore the stdout/stderr streams we never redirected and
|
||||
# especially no need to start systemd
|
||||
if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" != true ]; then
|
||||
# Reset the logging file descriptors.
|
||||
exec 1>&$logOutFd 2>&$logErrFd
|
||||
exec {logOutFd}>&- {logErrFd}>&-
|
||||
|
||||
|
||||
# Start systemd in a clean environment.
|
||||
echo "starting systemd..."
|
||||
exec @systemdExecutable@ "$@"
|
||||
fi
|
||||
71
nixos/modules/system/boot/stage-2.nix
Normal file
71
nixos/modules/system/boot/stage-2.nix
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
useHostResolvConf = config.networking.resolvconf.enable && config.networking.useHostResolvConf;
|
||||
|
||||
bootStage2 = pkgs.substituteAll {
|
||||
src = ./stage-2-init.sh;
|
||||
shellDebug = "${pkgs.bashInteractive}/bin/bash";
|
||||
shell = "${pkgs.bash}/bin/bash";
|
||||
inherit (config.boot) systemdExecutable extraSystemdUnitPaths;
|
||||
isExecutable = true;
|
||||
inherit (config.nix) readOnlyStore;
|
||||
inherit useHostResolvConf;
|
||||
inherit (config.system.build) earlyMountScript;
|
||||
path = lib.makeBinPath ([
|
||||
pkgs.coreutils
|
||||
pkgs.util-linux
|
||||
] ++ lib.optional useHostResolvConf pkgs.openresolv);
|
||||
postBootCommands = pkgs.writeText "local-cmds"
|
||||
''
|
||||
${config.boot.postBootCommands}
|
||||
${config.powerManagement.powerUpCommands}
|
||||
'';
|
||||
};
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
|
||||
boot = {
|
||||
|
||||
postBootCommands = mkOption {
|
||||
default = "";
|
||||
example = "rm -f /var/log/messages";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Shell commands to be executed just before systemd is started.
|
||||
'';
|
||||
};
|
||||
|
||||
systemdExecutable = mkOption {
|
||||
default = "/run/current-system/systemd/lib/systemd/systemd";
|
||||
type = types.str;
|
||||
description = ''
|
||||
The program to execute to start systemd.
|
||||
'';
|
||||
};
|
||||
|
||||
extraSystemdUnitPaths = mkOption {
|
||||
default = [];
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
Additional paths that get appended to the SYSTEMD_UNIT_PATH environment variable
|
||||
that can contain mutable unit files.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
config = {
|
||||
|
||||
system.build.bootStage2 = bootStage2;
|
||||
|
||||
};
|
||||
}
|
||||
631
nixos/modules/system/boot/systemd.nix
Normal file
631
nixos/modules/system/boot/systemd.nix
Normal file
|
|
@ -0,0 +1,631 @@
|
|||
{ config, lib, pkgs, utils, ... }:
|
||||
|
||||
with utils;
|
||||
with systemdUtils.unitOptions;
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.systemd;
|
||||
|
||||
systemd = cfg.package;
|
||||
|
||||
inherit (systemdUtils.lib)
|
||||
generateUnits
|
||||
targetToUnit
|
||||
serviceToUnit
|
||||
socketToUnit
|
||||
timerToUnit
|
||||
pathToUnit
|
||||
mountToUnit
|
||||
automountToUnit
|
||||
sliceToUnit;
|
||||
|
||||
upstreamSystemUnits =
|
||||
[ # Targets.
|
||||
"basic.target"
|
||||
"sysinit.target"
|
||||
"sockets.target"
|
||||
"exit.target"
|
||||
"graphical.target"
|
||||
"multi-user.target"
|
||||
"network.target"
|
||||
"network-pre.target"
|
||||
"network-online.target"
|
||||
"nss-lookup.target"
|
||||
"nss-user-lookup.target"
|
||||
"time-sync.target"
|
||||
] ++ optionals cfg.package.withCryptsetup [
|
||||
"cryptsetup.target"
|
||||
"cryptsetup-pre.target"
|
||||
"remote-cryptsetup.target"
|
||||
] ++ [
|
||||
"sigpwr.target"
|
||||
"timers.target"
|
||||
"paths.target"
|
||||
"rpcbind.target"
|
||||
|
||||
# Rescue mode.
|
||||
"rescue.target"
|
||||
"rescue.service"
|
||||
|
||||
# Udev.
|
||||
"systemd-udevd-control.socket"
|
||||
"systemd-udevd-kernel.socket"
|
||||
"systemd-udevd.service"
|
||||
"systemd-udev-settle.service"
|
||||
] ++ (optional (!config.boot.isContainer) "systemd-udev-trigger.service") ++ [
|
||||
# hwdb.bin is managed by NixOS
|
||||
# "systemd-hwdb-update.service"
|
||||
|
||||
# Consoles.
|
||||
"getty.target"
|
||||
"getty-pre.target"
|
||||
"getty@.service"
|
||||
"serial-getty@.service"
|
||||
"console-getty.service"
|
||||
"container-getty@.service"
|
||||
"systemd-vconsole-setup.service"
|
||||
|
||||
# Hardware (started by udev when a relevant device is plugged in).
|
||||
"sound.target"
|
||||
"bluetooth.target"
|
||||
"printer.target"
|
||||
"smartcard.target"
|
||||
|
||||
# Kernel module loading.
|
||||
"systemd-modules-load.service"
|
||||
"kmod-static-nodes.service"
|
||||
"modprobe@.service"
|
||||
|
||||
# Filesystems.
|
||||
"systemd-fsck@.service"
|
||||
"systemd-fsck-root.service"
|
||||
"systemd-remount-fs.service"
|
||||
"systemd-pstore.service"
|
||||
"local-fs.target"
|
||||
"local-fs-pre.target"
|
||||
"remote-fs.target"
|
||||
"remote-fs-pre.target"
|
||||
"swap.target"
|
||||
"dev-hugepages.mount"
|
||||
"dev-mqueue.mount"
|
||||
"sys-fs-fuse-connections.mount"
|
||||
] ++ (optional (!config.boot.isContainer) "sys-kernel-config.mount") ++ [
|
||||
"sys-kernel-debug.mount"
|
||||
|
||||
# Maintaining state across reboots.
|
||||
"systemd-random-seed.service"
|
||||
"systemd-backlight@.service"
|
||||
"systemd-rfkill.service"
|
||||
"systemd-rfkill.socket"
|
||||
|
||||
# Hibernate / suspend.
|
||||
"hibernate.target"
|
||||
"suspend.target"
|
||||
"suspend-then-hibernate.target"
|
||||
"sleep.target"
|
||||
"hybrid-sleep.target"
|
||||
"systemd-hibernate.service"
|
||||
"systemd-hybrid-sleep.service"
|
||||
"systemd-suspend.service"
|
||||
"systemd-suspend-then-hibernate.service"
|
||||
|
||||
# Reboot stuff.
|
||||
"reboot.target"
|
||||
"systemd-reboot.service"
|
||||
"poweroff.target"
|
||||
"systemd-poweroff.service"
|
||||
"halt.target"
|
||||
"systemd-halt.service"
|
||||
"shutdown.target"
|
||||
"umount.target"
|
||||
"final.target"
|
||||
"kexec.target"
|
||||
"systemd-kexec.service"
|
||||
"systemd-update-utmp.service"
|
||||
|
||||
# Password entry.
|
||||
"systemd-ask-password-console.path"
|
||||
"systemd-ask-password-console.service"
|
||||
"systemd-ask-password-wall.path"
|
||||
"systemd-ask-password-wall.service"
|
||||
|
||||
# Slices / containers.
|
||||
"slices.target"
|
||||
] ++ optionals cfg.package.withImportd [
|
||||
"systemd-importd.service"
|
||||
] ++ optionals cfg.package.withMachined [
|
||||
"machine.slice"
|
||||
"machines.target"
|
||||
"systemd-machined.service"
|
||||
] ++ [
|
||||
"systemd-nspawn@.service"
|
||||
|
||||
# Misc.
|
||||
"systemd-sysctl.service"
|
||||
] ++ optionals cfg.package.withTimedated [
|
||||
"dbus-org.freedesktop.timedate1.service"
|
||||
"systemd-timedated.service"
|
||||
] ++ optionals cfg.package.withLocaled [
|
||||
"dbus-org.freedesktop.locale1.service"
|
||||
"systemd-localed.service"
|
||||
] ++ optionals cfg.package.withHostnamed [
|
||||
"dbus-org.freedesktop.hostname1.service"
|
||||
"systemd-hostnamed.service"
|
||||
] ++ [
|
||||
"systemd-exit.service"
|
||||
"systemd-update-done.service"
|
||||
] ++ cfg.additionalUpstreamSystemUnits;
|
||||
|
||||
upstreamSystemWants =
|
||||
[ "sysinit.target.wants"
|
||||
"sockets.target.wants"
|
||||
"local-fs.target.wants"
|
||||
"multi-user.target.wants"
|
||||
"timers.target.wants"
|
||||
];
|
||||
|
||||
proxy_env = config.networking.proxy.envVars;
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
systemd.package = mkOption {
|
||||
default = pkgs.systemd;
|
||||
defaultText = literalExpression "pkgs.systemd";
|
||||
type = types.package;
|
||||
description = "The systemd package.";
|
||||
};
|
||||
|
||||
systemd.units = mkOption {
|
||||
description = "Definition of systemd units.";
|
||||
default = {};
|
||||
type = systemdUtils.types.units;
|
||||
};
|
||||
|
||||
systemd.packages = mkOption {
|
||||
default = [];
|
||||
type = types.listOf types.package;
|
||||
example = literalExpression "[ pkgs.systemd-cryptsetup-generator ]";
|
||||
description = "Packages providing systemd units and hooks.";
|
||||
};
|
||||
|
||||
systemd.targets = mkOption {
|
||||
default = {};
|
||||
type = systemdUtils.types.targets;
|
||||
description = "Definition of systemd target units.";
|
||||
};
|
||||
|
||||
systemd.services = mkOption {
|
||||
default = {};
|
||||
type = systemdUtils.types.services;
|
||||
description = "Definition of systemd service units.";
|
||||
};
|
||||
|
||||
systemd.sockets = mkOption {
|
||||
default = {};
|
||||
type = systemdUtils.types.sockets;
|
||||
description = "Definition of systemd socket units.";
|
||||
};
|
||||
|
||||
systemd.timers = mkOption {
|
||||
default = {};
|
||||
type = systemdUtils.types.timers;
|
||||
description = "Definition of systemd timer units.";
|
||||
};
|
||||
|
||||
systemd.paths = mkOption {
|
||||
default = {};
|
||||
type = systemdUtils.types.paths;
|
||||
description = "Definition of systemd path units.";
|
||||
};
|
||||
|
||||
systemd.mounts = mkOption {
|
||||
default = [];
|
||||
type = systemdUtils.types.mounts;
|
||||
description = ''
|
||||
Definition of systemd mount units.
|
||||
This is a list instead of an attrSet, because systemd mandates the names to be derived from
|
||||
the 'where' attribute.
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.automounts = mkOption {
|
||||
default = [];
|
||||
type = systemdUtils.types.automounts;
|
||||
description = ''
|
||||
Definition of systemd automount units.
|
||||
This is a list instead of an attrSet, because systemd mandates the names to be derived from
|
||||
the 'where' attribute.
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.slices = mkOption {
|
||||
default = {};
|
||||
type = systemdUtils.types.slices;
|
||||
description = "Definition of slice configurations.";
|
||||
};
|
||||
|
||||
systemd.generators = mkOption {
|
||||
type = types.attrsOf types.path;
|
||||
default = {};
|
||||
example = { systemd-gpt-auto-generator = "/dev/null"; };
|
||||
description = ''
|
||||
Definition of systemd generators.
|
||||
For each <literal>NAME = VALUE</literal> pair of the attrSet, a link is generated from
|
||||
<literal>/etc/systemd/system-generators/NAME</literal> to <literal>VALUE</literal>.
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.shutdown = mkOption {
|
||||
type = types.attrsOf types.path;
|
||||
default = {};
|
||||
description = ''
|
||||
Definition of systemd shutdown executables.
|
||||
For each <literal>NAME = VALUE</literal> pair of the attrSet, a link is generated from
|
||||
<literal>/etc/systemd/system-shutdown/NAME</literal> to <literal>VALUE</literal>.
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.defaultUnit = mkOption {
|
||||
default = "multi-user.target";
|
||||
type = types.str;
|
||||
description = "Default unit started when the system boots.";
|
||||
};
|
||||
|
||||
systemd.ctrlAltDelUnit = mkOption {
|
||||
default = "reboot.target";
|
||||
type = types.str;
|
||||
example = "poweroff.target";
|
||||
description = ''
|
||||
Target that should be started when Ctrl-Alt-Delete is pressed.
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.globalEnvironment = mkOption {
|
||||
type = with types; attrsOf (nullOr (oneOf [ str path package ]));
|
||||
default = {};
|
||||
example = { TZ = "CET"; };
|
||||
description = ''
|
||||
Environment variables passed to <emphasis>all</emphasis> systemd units.
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.managerEnvironment = mkOption {
|
||||
type = with types; attrsOf (nullOr (oneOf [ str path package ]));
|
||||
default = {};
|
||||
example = { SYSTEMD_LOG_LEVEL = "debug"; };
|
||||
description = ''
|
||||
Environment variables of PID 1. These variables are
|
||||
<emphasis>not</emphasis> passed to started units.
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.enableCgroupAccounting = mkOption {
|
||||
default = true;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to enable cgroup accounting.
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.enableUnifiedCgroupHierarchy = mkOption {
|
||||
default = true;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to enable the unified cgroup hierarchy (cgroupsv2).
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.extraConfig = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
example = "DefaultLimitCORE=infinity";
|
||||
description = ''
|
||||
Extra config options for systemd. See man systemd-system.conf for
|
||||
available options.
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.sleep.extraConfig = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
example = "HibernateDelaySec=1h";
|
||||
description = ''
|
||||
Extra config options for systemd sleep state logic.
|
||||
See sleep.conf.d(5) man page for available options.
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.additionalUpstreamSystemUnits = mkOption {
|
||||
default = [ ];
|
||||
type = types.listOf types.str;
|
||||
example = [ "debug-shell.service" "systemd-quotacheck.service" ];
|
||||
description = ''
|
||||
Additional units shipped with systemd that shall be enabled.
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.suppressedSystemUnits = mkOption {
|
||||
default = [ ];
|
||||
type = types.listOf types.str;
|
||||
example = [ "systemd-backlight@.service" ];
|
||||
description = ''
|
||||
A list of units to skip when generating system systemd configuration directory. This has
|
||||
priority over upstream units, <option>systemd.units</option>, and
|
||||
<option>systemd.additionalUpstreamSystemUnits</option>. The main purpose of this is to
|
||||
prevent a upstream systemd unit from being added to the initrd with any modifications made to it
|
||||
by other NixOS modules.
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.watchdog.device = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/dev/watchdog";
|
||||
description = ''
|
||||
The path to a hardware watchdog device which will be managed by systemd.
|
||||
If not specified, systemd will default to /dev/watchdog.
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.watchdog.runtimeTime = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "30s";
|
||||
description = ''
|
||||
The amount of time which can elapse before a watchdog hardware device
|
||||
will automatically reboot the system. Valid time units include "ms",
|
||||
"s", "min", "h", "d", and "w".
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.watchdog.rebootTime = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "10m";
|
||||
description = ''
|
||||
The amount of time which can elapse after a reboot has been triggered
|
||||
before a watchdog hardware device will automatically reboot the system.
|
||||
Valid time units include "ms", "s", "min", "h", "d", and "w".
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.watchdog.kexecTime = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "10m";
|
||||
description = ''
|
||||
The amount of time which can elapse when kexec is being executed before
|
||||
a watchdog hardware device will automatically reboot the system. This
|
||||
option should only be enabled if reloadTime is also enabled. Valid
|
||||
time units include "ms", "s", "min", "h", "d", and "w".
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = {
|
||||
|
||||
warnings = concatLists (
|
||||
mapAttrsToList
|
||||
(name: service:
|
||||
let
|
||||
type = service.serviceConfig.Type or "";
|
||||
restart = service.serviceConfig.Restart or "no";
|
||||
hasDeprecated = builtins.hasAttr "StartLimitInterval" service.serviceConfig;
|
||||
in
|
||||
concatLists [
|
||||
(optional (type == "oneshot" && (restart == "always" || restart == "on-success"))
|
||||
"Service '${name}.service' with 'Type=oneshot' cannot have 'Restart=always' or 'Restart=on-success'"
|
||||
)
|
||||
(optional hasDeprecated
|
||||
"Service '${name}.service' uses the attribute 'StartLimitInterval' in the Service section, which is deprecated. See https://github.com/NixOS/nixpkgs/issues/45786."
|
||||
)
|
||||
(optional (service.reloadIfChanged && service.reloadTriggers != [])
|
||||
"Service '${name}.service' has both 'reloadIfChanged' and 'reloadTriggers' set. This is probably not what you want, because 'reloadTriggers' behave the same whay as 'restartTriggers' if 'reloadIfChanged' is set."
|
||||
)
|
||||
]
|
||||
)
|
||||
cfg.services
|
||||
);
|
||||
|
||||
system.build.units = cfg.units;
|
||||
|
||||
system.nssModules = [ systemd.out ];
|
||||
system.nssDatabases = {
|
||||
hosts = (mkMerge [
|
||||
(mkOrder 400 ["mymachines"]) # 400 to ensure it comes before resolve (which is mkBefore'd)
|
||||
(mkOrder 999 ["myhostname"]) # after files (which is 998), but before regular nss modules
|
||||
]);
|
||||
passwd = (mkMerge [
|
||||
(mkAfter [ "systemd" ])
|
||||
]);
|
||||
group = (mkMerge [
|
||||
(mkAfter [ "systemd" ])
|
||||
]);
|
||||
};
|
||||
|
||||
environment.systemPackages = [ systemd ];
|
||||
|
||||
environment.etc = let
|
||||
# generate contents for /etc/systemd/system-${type} from attrset of links and packages
|
||||
hooks = type: links: pkgs.runCommand "system-${type}" {
|
||||
preferLocalBuild = true;
|
||||
packages = cfg.packages;
|
||||
} ''
|
||||
set -e
|
||||
mkdir -p $out
|
||||
for package in $packages
|
||||
do
|
||||
for hook in $package/lib/systemd/system-${type}/*
|
||||
do
|
||||
ln -s $hook $out/
|
||||
done
|
||||
done
|
||||
${concatStrings (mapAttrsToList (exec: target: "ln -s ${target} $out/${exec};\n") links)}
|
||||
'';
|
||||
|
||||
enabledUpstreamSystemUnits = filter (n: ! elem n cfg.suppressedSystemUnits) upstreamSystemUnits;
|
||||
enabledUnits = filterAttrs (n: v: ! elem n cfg.suppressedSystemUnits) cfg.units;
|
||||
|
||||
in ({
|
||||
"systemd/system".source = generateUnits {
|
||||
type = "system";
|
||||
units = enabledUnits;
|
||||
upstreamUnits = enabledUpstreamSystemUnits;
|
||||
upstreamWants = upstreamSystemWants;
|
||||
};
|
||||
|
||||
"systemd/system.conf".text = ''
|
||||
[Manager]
|
||||
ManagerEnvironment=${lib.concatStringsSep " " (lib.mapAttrsToList (n: v: "${n}=${lib.escapeShellArg v}") cfg.managerEnvironment)}
|
||||
${optionalString config.systemd.enableCgroupAccounting ''
|
||||
DefaultCPUAccounting=yes
|
||||
DefaultIOAccounting=yes
|
||||
DefaultBlockIOAccounting=yes
|
||||
DefaultIPAccounting=yes
|
||||
''}
|
||||
DefaultLimitCORE=infinity
|
||||
${optionalString (config.systemd.watchdog.device != null) ''
|
||||
WatchdogDevice=${config.systemd.watchdog.device}
|
||||
''}
|
||||
${optionalString (config.systemd.watchdog.runtimeTime != null) ''
|
||||
RuntimeWatchdogSec=${config.systemd.watchdog.runtimeTime}
|
||||
''}
|
||||
${optionalString (config.systemd.watchdog.rebootTime != null) ''
|
||||
RebootWatchdogSec=${config.systemd.watchdog.rebootTime}
|
||||
''}
|
||||
${optionalString (config.systemd.watchdog.kexecTime != null) ''
|
||||
KExecWatchdogSec=${config.systemd.watchdog.kexecTime}
|
||||
''}
|
||||
|
||||
${config.systemd.extraConfig}
|
||||
'';
|
||||
|
||||
"systemd/sleep.conf".text = ''
|
||||
[Sleep]
|
||||
${config.systemd.sleep.extraConfig}
|
||||
'';
|
||||
|
||||
"systemd/system-generators" = { source = hooks "generators" cfg.generators; };
|
||||
"systemd/system-shutdown" = { source = hooks "shutdown" cfg.shutdown; };
|
||||
});
|
||||
|
||||
services.dbus.enable = true;
|
||||
|
||||
users.users.systemd-network = {
|
||||
uid = config.ids.uids.systemd-network;
|
||||
group = "systemd-network";
|
||||
};
|
||||
users.groups.systemd-network.gid = config.ids.gids.systemd-network;
|
||||
users.users.systemd-resolve = {
|
||||
uid = config.ids.uids.systemd-resolve;
|
||||
group = "systemd-resolve";
|
||||
};
|
||||
users.groups.systemd-resolve.gid = config.ids.gids.systemd-resolve;
|
||||
|
||||
# Target for ‘charon send-keys’ to hook into.
|
||||
users.groups.keys.gid = config.ids.gids.keys;
|
||||
|
||||
systemd.targets.keys =
|
||||
{ description = "Security Keys";
|
||||
unitConfig.X-StopOnReconfiguration = true;
|
||||
};
|
||||
|
||||
systemd.units =
|
||||
mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit n v)) cfg.paths
|
||||
// mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services
|
||||
// mapAttrs' (n: v: nameValuePair "${n}.slice" (sliceToUnit n v)) cfg.slices
|
||||
// mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit n v)) cfg.sockets
|
||||
// mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit n v)) cfg.targets
|
||||
// mapAttrs' (n: v: nameValuePair "${n}.timer" (timerToUnit n v)) cfg.timers
|
||||
// listToAttrs (map
|
||||
(v: let n = escapeSystemdPath v.where;
|
||||
in nameValuePair "${n}.mount" (mountToUnit n v)) cfg.mounts)
|
||||
// listToAttrs (map
|
||||
(v: let n = escapeSystemdPath v.where;
|
||||
in nameValuePair "${n}.automount" (automountToUnit n v)) cfg.automounts);
|
||||
|
||||
# Environment of PID 1
|
||||
systemd.managerEnvironment = {
|
||||
# Doesn't contain systemd itself - everything works so it seems to use the compiled-in value for its tools
|
||||
PATH = lib.makeBinPath config.system.fsPackages;
|
||||
LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive";
|
||||
TZDIR = "/etc/zoneinfo";
|
||||
# If SYSTEMD_UNIT_PATH ends with an empty component (":"), the usual unit load path will be appended to the contents of the variable
|
||||
SYSTEMD_UNIT_PATH = lib.mkIf (config.boot.extraSystemdUnitPaths != []) "${builtins.concatStringsSep ":" config.boot.extraSystemdUnitPaths}:";
|
||||
};
|
||||
|
||||
|
||||
system.requiredKernelConfig = map config.lib.kernelConfig.isEnabled
|
||||
[ "DEVTMPFS" "CGROUPS" "INOTIFY_USER" "SIGNALFD" "TIMERFD" "EPOLL" "NET"
|
||||
"SYSFS" "PROC_FS" "FHANDLE" "CRYPTO_USER_API_HASH" "CRYPTO_HMAC"
|
||||
"CRYPTO_SHA256" "DMIID" "AUTOFS4_FS" "TMPFS_POSIX_ACL"
|
||||
"TMPFS_XATTR" "SECCOMP"
|
||||
];
|
||||
|
||||
# Generate timer units for all services that have a ‘startAt’ value.
|
||||
systemd.timers =
|
||||
mapAttrs (name: service:
|
||||
{ wantedBy = [ "timers.target" ];
|
||||
timerConfig.OnCalendar = service.startAt;
|
||||
})
|
||||
(filterAttrs (name: service: service.enable && service.startAt != []) cfg.services);
|
||||
|
||||
# Some overrides to upstream units.
|
||||
systemd.services."systemd-backlight@".restartIfChanged = false;
|
||||
systemd.services."systemd-fsck@".restartIfChanged = false;
|
||||
systemd.services."systemd-fsck@".path = [ config.system.path ];
|
||||
systemd.services.systemd-random-seed.restartIfChanged = false;
|
||||
systemd.services.systemd-remount-fs.restartIfChanged = false;
|
||||
systemd.services.systemd-update-utmp.restartIfChanged = false;
|
||||
systemd.services.systemd-udev-settle.restartIfChanged = false; # Causes long delays in nixos-rebuild
|
||||
systemd.targets.local-fs.unitConfig.X-StopOnReconfiguration = true;
|
||||
systemd.targets.remote-fs.unitConfig.X-StopOnReconfiguration = true;
|
||||
systemd.targets.network-online.wantedBy = [ "multi-user.target" ];
|
||||
systemd.services.systemd-importd.environment = proxy_env;
|
||||
systemd.services.systemd-pstore.wantedBy = [ "sysinit.target" ]; # see #81138
|
||||
|
||||
# Don't bother with certain units in containers.
|
||||
systemd.services.systemd-remount-fs.unitConfig.ConditionVirtualization = "!container";
|
||||
systemd.services.systemd-random-seed.unitConfig.ConditionVirtualization = "!container";
|
||||
|
||||
# Increase numeric PID range (set directly instead of copying a one-line file from systemd)
|
||||
# https://github.com/systemd/systemd/pull/12226
|
||||
boot.kernel.sysctl."kernel.pid_max" = mkIf pkgs.stdenv.is64bit (lib.mkDefault 4194304);
|
||||
|
||||
boot.kernelParams = optional (!cfg.enableUnifiedCgroupHierarchy) "systemd.unified_cgroup_hierarchy=0";
|
||||
|
||||
services.logrotate.settings = {
|
||||
"/var/log/btmp" = mapAttrs (_: mkDefault) {
|
||||
frequency = "monthly";
|
||||
rotate = 1;
|
||||
create = "0660 root ${config.users.groups.utmp.name}";
|
||||
minsize = "1M";
|
||||
};
|
||||
"/var/log/wtmp" = mapAttrs (_: mkDefault) {
|
||||
frequency = "monthly";
|
||||
rotate = 1;
|
||||
create = "0664 root ${config.users.groups.utmp.name}";
|
||||
minsize = "1M";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# FIXME: Remove these eventually.
|
||||
imports =
|
||||
[ (mkRenamedOptionModule [ "boot" "systemd" "sockets" ] [ "systemd" "sockets" ])
|
||||
(mkRenamedOptionModule [ "boot" "systemd" "targets" ] [ "systemd" "targets" ])
|
||||
(mkRenamedOptionModule [ "boot" "systemd" "services" ] [ "systemd" "services" ])
|
||||
(mkRenamedOptionModule [ "jobs" ] [ "systemd" "services" ])
|
||||
(mkRemovedOptionModule [ "systemd" "generator-packages" ] "Use systemd.packages instead.")
|
||||
];
|
||||
}
|
||||
57
nixos/modules/system/boot/systemd/coredump.nix
Normal file
57
nixos/modules/system/boot/systemd/coredump.nix
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
{ config, lib, pkgs, utils, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.systemd.coredump;
|
||||
systemd = config.systemd.package;
|
||||
in {
|
||||
options = {
|
||||
systemd.coredump.enable = mkOption {
|
||||
default = true;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether core dumps should be processed by
|
||||
<command>systemd-coredump</command>. If disabled, core dumps
|
||||
appear in the current directory of the crashing process.
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.coredump.extraConfig = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
example = "Storage=journal";
|
||||
description = ''
|
||||
Extra config options for systemd-coredump. See coredump.conf(5) man page
|
||||
for available options.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
systemd.additionalUpstreamSystemUnits = [
|
||||
"systemd-coredump.socket"
|
||||
"systemd-coredump@.service"
|
||||
];
|
||||
|
||||
environment.etc = {
|
||||
"systemd/coredump.conf".text =
|
||||
''
|
||||
[Coredump]
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
|
||||
# install provided sysctl snippets
|
||||
"sysctl.d/50-coredump.conf".source = "${systemd}/example/sysctl.d/50-coredump.conf";
|
||||
"sysctl.d/50-default.conf".source = "${systemd}/example/sysctl.d/50-default.conf";
|
||||
};
|
||||
|
||||
users.users.systemd-coredump = {
|
||||
uid = config.ids.uids.systemd-coredump;
|
||||
group = "systemd-coredump";
|
||||
};
|
||||
users.groups.systemd-coredump = {};
|
||||
|
||||
boot.kernel.sysctl."kernel.core_pattern" = mkIf (!cfg.enable) "core";
|
||||
};
|
||||
}
|
||||
36
nixos/modules/system/boot/systemd/initrd-secrets.nix
Normal file
36
nixos/modules/system/boot/systemd/initrd-secrets.nix
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
{
|
||||
config = lib.mkIf (config.boot.initrd.enable && config.boot.initrd.systemd.enable) {
|
||||
# Copy secrets into the initrd if they cannot be appended
|
||||
boot.initrd.systemd.contents = lib.mkIf (!config.boot.loader.supportsInitrdSecrets)
|
||||
(lib.mapAttrs' (dest: source: lib.nameValuePair "/.initrd-secrets/${dest}" { source = if source == null then dest else source; }) config.boot.initrd.secrets);
|
||||
|
||||
# Copy secrets to their respective locations
|
||||
boot.initrd.systemd.services.initrd-nixos-copy-secrets = lib.mkIf (config.boot.initrd.secrets != {}) {
|
||||
description = "Copy secrets into place";
|
||||
# Run as early as possible
|
||||
wantedBy = [ "sysinit.target" ];
|
||||
before = [ "cryptsetup-pre.target" ];
|
||||
unitConfig.DefaultDependencies = false;
|
||||
|
||||
# We write the secrets to /.initrd-secrets and move them because this allows
|
||||
# secrets to be written to /run. If we put the secret directly to /run and
|
||||
# drop this service, we'd mount the /run tmpfs over the secret, making it
|
||||
# invisible in stage 2.
|
||||
script = ''
|
||||
for secret in $(cd /.initrd-secrets; find . -type f); do
|
||||
mkdir -p "$(dirname "/$secret")"
|
||||
cp "/.initrd-secrets/$secret" "/$secret"
|
||||
done
|
||||
'';
|
||||
|
||||
unitConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
};
|
||||
# The script needs this
|
||||
boot.initrd.systemd.extraBin.find = "${pkgs.findutils}/bin/find";
|
||||
};
|
||||
}
|
||||
502
nixos/modules/system/boot/systemd/initrd.nix
Normal file
502
nixos/modules/system/boot/systemd/initrd.nix
Normal file
|
|
@ -0,0 +1,502 @@
|
|||
{ lib, config, utils, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
inherit (utils) systemdUtils escapeSystemdPath;
|
||||
inherit (systemdUtils.lib)
|
||||
generateUnits
|
||||
pathToUnit
|
||||
serviceToUnit
|
||||
sliceToUnit
|
||||
socketToUnit
|
||||
targetToUnit
|
||||
timerToUnit
|
||||
mountToUnit
|
||||
automountToUnit;
|
||||
|
||||
|
||||
cfg = config.boot.initrd.systemd;
|
||||
|
||||
# Copied from fedora
|
||||
upstreamUnits = [
|
||||
"basic.target"
|
||||
"ctrl-alt-del.target"
|
||||
"emergency.service"
|
||||
"emergency.target"
|
||||
"final.target"
|
||||
"halt.target"
|
||||
"initrd-cleanup.service"
|
||||
"initrd-fs.target"
|
||||
"initrd-parse-etc.service"
|
||||
"initrd-root-device.target"
|
||||
"initrd-root-fs.target"
|
||||
"initrd-switch-root.service"
|
||||
"initrd-switch-root.target"
|
||||
"initrd.target"
|
||||
"kexec.target"
|
||||
"kmod-static-nodes.service"
|
||||
"local-fs-pre.target"
|
||||
"local-fs.target"
|
||||
"multi-user.target"
|
||||
"paths.target"
|
||||
"poweroff.target"
|
||||
"reboot.target"
|
||||
"rescue.service"
|
||||
"rescue.target"
|
||||
"rpcbind.target"
|
||||
"shutdown.target"
|
||||
"sigpwr.target"
|
||||
"slices.target"
|
||||
"sockets.target"
|
||||
"swap.target"
|
||||
"sysinit.target"
|
||||
"sys-kernel-config.mount"
|
||||
"syslog.socket"
|
||||
"systemd-ask-password-console.path"
|
||||
"systemd-ask-password-console.service"
|
||||
"systemd-fsck@.service"
|
||||
"systemd-halt.service"
|
||||
"systemd-hibernate-resume@.service"
|
||||
"systemd-journald-audit.socket"
|
||||
"systemd-journald-dev-log.socket"
|
||||
"systemd-journald.service"
|
||||
"systemd-journald.socket"
|
||||
"systemd-kexec.service"
|
||||
"systemd-modules-load.service"
|
||||
"systemd-poweroff.service"
|
||||
"systemd-reboot.service"
|
||||
"systemd-sysctl.service"
|
||||
"systemd-tmpfiles-setup-dev.service"
|
||||
"systemd-tmpfiles-setup.service"
|
||||
"timers.target"
|
||||
"umount.target"
|
||||
|
||||
# TODO: Networking
|
||||
# "network-online.target"
|
||||
# "network-pre.target"
|
||||
# "network.target"
|
||||
# "nss-lookup.target"
|
||||
# "nss-user-lookup.target"
|
||||
# "remote-fs-pre.target"
|
||||
# "remote-fs.target"
|
||||
] ++ cfg.additionalUpstreamUnits;
|
||||
|
||||
upstreamWants = [
|
||||
"sysinit.target.wants"
|
||||
];
|
||||
|
||||
enabledUpstreamUnits = filter (n: ! elem n cfg.suppressedUnits) upstreamUnits;
|
||||
enabledUnits = filterAttrs (n: v: ! elem n cfg.suppressedUnits) cfg.units;
|
||||
jobScripts = concatLists (mapAttrsToList (_: unit: unit.jobScripts or []) (filterAttrs (_: v: v.enable) cfg.services));
|
||||
|
||||
stage1Units = generateUnits {
|
||||
type = "initrd";
|
||||
units = enabledUnits;
|
||||
upstreamUnits = enabledUpstreamUnits;
|
||||
inherit upstreamWants;
|
||||
inherit (cfg) packages package;
|
||||
};
|
||||
|
||||
fileSystems = filter utils.fsNeededForBoot config.system.build.fileSystems;
|
||||
|
||||
fstab = pkgs.writeText "initrd-fstab" (lib.concatMapStringsSep "\n"
|
||||
({ fsType, mountPoint, device, options, autoFormat, autoResize, ... }@fs: let
|
||||
opts = options ++ optional autoFormat "x-systemd.makefs" ++ optional autoResize "x-systemd.growfs";
|
||||
in "${device} /sysroot${mountPoint} ${fsType} ${lib.concatStringsSep "," opts}") fileSystems);
|
||||
|
||||
needMakefs = lib.any (fs: fs.autoFormat) fileSystems;
|
||||
needGrowfs = lib.any (fs: fs.autoResize) fileSystems;
|
||||
|
||||
kernel-name = config.boot.kernelPackages.kernel.name or "kernel";
|
||||
modulesTree = config.system.modulesTree.override { name = kernel-name + "-modules"; };
|
||||
firmware = config.hardware.firmware;
|
||||
# Determine the set of modules that we need to mount the root FS.
|
||||
modulesClosure = pkgs.makeModulesClosure {
|
||||
rootModules = config.boot.initrd.availableKernelModules ++ config.boot.initrd.kernelModules;
|
||||
kernel = modulesTree;
|
||||
firmware = firmware;
|
||||
allowMissing = false;
|
||||
};
|
||||
|
||||
initrdBinEnv = pkgs.buildEnv {
|
||||
name = "initrd-bin-env";
|
||||
paths = map getBin cfg.initrdBin;
|
||||
pathsToLink = ["/bin" "/sbin"];
|
||||
postBuild = concatStringsSep "\n" (mapAttrsToList (n: v: "ln -s '${v}' $out/bin/'${n}'") cfg.extraBin);
|
||||
};
|
||||
|
||||
initialRamdisk = pkgs.makeInitrdNG {
|
||||
name = "initrd-${kernel-name}";
|
||||
inherit (config.boot.initrd) compressor compressorArgs prepend;
|
||||
|
||||
contents = map (path: { object = path; symlink = ""; }) (subtractLists cfg.suppressedStorePaths cfg.storePaths)
|
||||
++ mapAttrsToList (_: v: { object = v.source; symlink = v.target; }) (filterAttrs (_: v: v.enable) cfg.contents);
|
||||
};
|
||||
|
||||
in {
|
||||
options.boot.initrd.systemd = {
|
||||
enable = mkEnableOption ''systemd in initrd.
|
||||
|
||||
Note: This is in very early development and is highly
|
||||
experimental. Most of the features NixOS supports in initrd are
|
||||
not yet supported by the intrd generated with this option.
|
||||
'';
|
||||
|
||||
package = (mkPackageOption pkgs "systemd" {
|
||||
default = "systemdStage1";
|
||||
}) // {
|
||||
visible = false;
|
||||
};
|
||||
|
||||
contents = mkOption {
|
||||
description = "Set of files that have to be linked into the initrd";
|
||||
example = literalExpression ''
|
||||
{
|
||||
"/etc/hostname".text = "mymachine";
|
||||
}
|
||||
'';
|
||||
visible = false;
|
||||
default = {};
|
||||
type = utils.systemdUtils.types.initrdContents;
|
||||
};
|
||||
|
||||
storePaths = mkOption {
|
||||
description = ''
|
||||
Store paths to copy into the initrd as well.
|
||||
'';
|
||||
type = with types; listOf (oneOf [ singleLineStr package ]);
|
||||
default = [];
|
||||
};
|
||||
|
||||
extraBin = mkOption {
|
||||
description = ''
|
||||
Tools to add to /bin
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
umount = ''${pkgs.util-linux}/bin/umount;
|
||||
}
|
||||
'';
|
||||
type = types.attrsOf types.path;
|
||||
default = {};
|
||||
};
|
||||
|
||||
suppressedStorePaths = mkOption {
|
||||
description = ''
|
||||
Store paths specified in the storePaths option that
|
||||
should not be copied.
|
||||
'';
|
||||
type = types.listOf types.singleLineStr;
|
||||
default = [];
|
||||
};
|
||||
|
||||
emergencyAccess = mkOption {
|
||||
type = with types; oneOf [ bool singleLineStr ];
|
||||
visible = false;
|
||||
description = ''
|
||||
Set to true for unauthenticated emergency access, and false for
|
||||
no emergency access.
|
||||
|
||||
Can also be set to a hashed super user password to allow
|
||||
authenticated access to the emergency mode.
|
||||
'';
|
||||
default = false;
|
||||
};
|
||||
|
||||
initrdBin = mkOption {
|
||||
type = types.listOf types.package;
|
||||
default = [];
|
||||
visible = false;
|
||||
description = ''
|
||||
Packages to include in /bin for the stage 1 emergency shell.
|
||||
'';
|
||||
};
|
||||
|
||||
additionalUpstreamUnits = mkOption {
|
||||
default = [ ];
|
||||
type = types.listOf types.str;
|
||||
visible = false;
|
||||
example = [ "debug-shell.service" "systemd-quotacheck.service" ];
|
||||
description = ''
|
||||
Additional units shipped with systemd that shall be enabled.
|
||||
'';
|
||||
};
|
||||
|
||||
suppressedUnits = mkOption {
|
||||
default = [ ];
|
||||
type = types.listOf types.str;
|
||||
example = [ "systemd-backlight@.service" ];
|
||||
visible = false;
|
||||
description = ''
|
||||
A list of units to skip when generating system systemd configuration directory. This has
|
||||
priority over upstream units, <option>boot.initrd.systemd.units</option>, and
|
||||
<option>boot.initrd.systemd.additionalUpstreamUnits</option>. The main purpose of this is to
|
||||
prevent a upstream systemd unit from being added to the initrd with any modifications made to it
|
||||
by other NixOS modules.
|
||||
'';
|
||||
};
|
||||
|
||||
units = mkOption {
|
||||
description = "Definition of systemd units.";
|
||||
default = {};
|
||||
visible = false;
|
||||
type = systemdUtils.types.units;
|
||||
};
|
||||
|
||||
packages = mkOption {
|
||||
default = [];
|
||||
visible = false;
|
||||
type = types.listOf types.package;
|
||||
example = literalExpression "[ pkgs.systemd-cryptsetup-generator ]";
|
||||
description = "Packages providing systemd units and hooks.";
|
||||
};
|
||||
|
||||
targets = mkOption {
|
||||
default = {};
|
||||
visible = false;
|
||||
type = systemdUtils.types.initrdTargets;
|
||||
description = "Definition of systemd target units.";
|
||||
};
|
||||
|
||||
services = mkOption {
|
||||
default = {};
|
||||
type = systemdUtils.types.initrdServices;
|
||||
visible = false;
|
||||
description = "Definition of systemd service units.";
|
||||
};
|
||||
|
||||
sockets = mkOption {
|
||||
default = {};
|
||||
type = systemdUtils.types.initrdSockets;
|
||||
visible = false;
|
||||
description = "Definition of systemd socket units.";
|
||||
};
|
||||
|
||||
timers = mkOption {
|
||||
default = {};
|
||||
type = systemdUtils.types.initrdTimers;
|
||||
visible = false;
|
||||
description = "Definition of systemd timer units.";
|
||||
};
|
||||
|
||||
paths = mkOption {
|
||||
default = {};
|
||||
type = systemdUtils.types.initrdPaths;
|
||||
visible = false;
|
||||
description = "Definition of systemd path units.";
|
||||
};
|
||||
|
||||
mounts = mkOption {
|
||||
default = [];
|
||||
type = systemdUtils.types.initrdMounts;
|
||||
visible = false;
|
||||
description = ''
|
||||
Definition of systemd mount units.
|
||||
This is a list instead of an attrSet, because systemd mandates the names to be derived from
|
||||
the 'where' attribute.
|
||||
'';
|
||||
};
|
||||
|
||||
automounts = mkOption {
|
||||
default = [];
|
||||
type = systemdUtils.types.automounts;
|
||||
visible = false;
|
||||
description = ''
|
||||
Definition of systemd automount units.
|
||||
This is a list instead of an attrSet, because systemd mandates the names to be derived from
|
||||
the 'where' attribute.
|
||||
'';
|
||||
};
|
||||
|
||||
slices = mkOption {
|
||||
default = {};
|
||||
type = systemdUtils.types.slices;
|
||||
visible = false;
|
||||
description = "Definition of slice configurations.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf (config.boot.initrd.enable && cfg.enable) {
|
||||
system.build = { inherit initialRamdisk; };
|
||||
|
||||
boot.initrd.availableKernelModules = [ "autofs4" ]; # systemd needs this for some features
|
||||
|
||||
boot.initrd.systemd = {
|
||||
initrdBin = [pkgs.bash pkgs.coreutils cfg.package.kmod cfg.package] ++ config.system.fsPackages;
|
||||
extraBin = {
|
||||
less = "${pkgs.less}/bin/less";
|
||||
mount = "${cfg.package.util-linux}/bin/mount";
|
||||
umount = "${cfg.package.util-linux}/bin/umount";
|
||||
};
|
||||
|
||||
contents = {
|
||||
"/init".source = "${cfg.package}/lib/systemd/systemd";
|
||||
"/etc/systemd/system".source = stage1Units;
|
||||
|
||||
"/etc/systemd/system.conf".text = ''
|
||||
[Manager]
|
||||
DefaultEnvironment=PATH=/bin:/sbin ${optionalString (isBool cfg.emergencyAccess && cfg.emergencyAccess) "SYSTEMD_SULOGIN_FORCE=1"}
|
||||
'';
|
||||
|
||||
"/etc/fstab".source = fstab;
|
||||
|
||||
"/lib/modules".source = "${modulesClosure}/lib/modules";
|
||||
"/lib/firmware".source = "${modulesClosure}/lib/firmware";
|
||||
|
||||
"/etc/modules-load.d/nixos.conf".text = concatStringsSep "\n" config.boot.initrd.kernelModules;
|
||||
|
||||
"/etc/passwd".source = "${pkgs.fakeNss}/etc/passwd";
|
||||
"/etc/shadow".text = "root:${if isBool cfg.emergencyAccess then "!" else cfg.emergencyAccess}:::::::";
|
||||
|
||||
"/bin".source = "${initrdBinEnv}/bin";
|
||||
"/sbin".source = "${initrdBinEnv}/sbin";
|
||||
|
||||
"/etc/sysctl.d/nixos.conf".text = "kernel.modprobe = /sbin/modprobe";
|
||||
"/etc/modprobe.d/systemd.conf".source = "${cfg.package}/lib/modprobe.d/systemd.conf";
|
||||
"/etc/modprobe.d/ubuntu.conf".source = pkgs.runCommand "initrd-kmod-blacklist-ubuntu" { } ''
|
||||
${pkgs.buildPackages.perl}/bin/perl -0pe 's/## file: iwlwifi.conf(.+?)##/##/s;' $src > $out
|
||||
'';
|
||||
"/etc/modprobe.d/debian.conf".source = pkgs.kmod-debian-aliases;
|
||||
|
||||
};
|
||||
|
||||
storePaths = [
|
||||
# systemd tooling
|
||||
"${cfg.package}/lib/systemd/systemd-fsck"
|
||||
(lib.mkIf needGrowfs "${cfg.package}/lib/systemd/systemd-growfs")
|
||||
"${cfg.package}/lib/systemd/systemd-hibernate-resume"
|
||||
"${cfg.package}/lib/systemd/systemd-journald"
|
||||
(lib.mkIf needMakefs "${cfg.package}/lib/systemd/systemd-makefs")
|
||||
"${cfg.package}/lib/systemd/systemd-modules-load"
|
||||
"${cfg.package}/lib/systemd/systemd-remount-fs"
|
||||
"${cfg.package}/lib/systemd/systemd-shutdown"
|
||||
"${cfg.package}/lib/systemd/systemd-sulogin-shell"
|
||||
"${cfg.package}/lib/systemd/systemd-sysctl"
|
||||
|
||||
# generators
|
||||
"${cfg.package}/lib/systemd/system-generators/systemd-debug-generator"
|
||||
"${cfg.package}/lib/systemd/system-generators/systemd-fstab-generator"
|
||||
"${cfg.package}/lib/systemd/system-generators/systemd-gpt-auto-generator"
|
||||
"${cfg.package}/lib/systemd/system-generators/systemd-hibernate-resume-generator"
|
||||
"${cfg.package}/lib/systemd/system-generators/systemd-run-generator"
|
||||
|
||||
# utilities needed by systemd
|
||||
"${cfg.package.util-linux}/bin/mount"
|
||||
"${cfg.package.util-linux}/bin/umount"
|
||||
"${cfg.package.util-linux}/bin/sulogin"
|
||||
|
||||
# so NSS can look up usernames
|
||||
"${pkgs.glibc}/lib/libnss_files.so.2"
|
||||
] ++ jobScripts;
|
||||
|
||||
targets.initrd.aliases = ["default.target"];
|
||||
units =
|
||||
mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit n v)) cfg.paths
|
||||
// mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services
|
||||
// mapAttrs' (n: v: nameValuePair "${n}.slice" (sliceToUnit n v)) cfg.slices
|
||||
// mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit n v)) cfg.sockets
|
||||
// mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit n v)) cfg.targets
|
||||
// mapAttrs' (n: v: nameValuePair "${n}.timer" (timerToUnit n v)) cfg.timers
|
||||
// listToAttrs (map
|
||||
(v: let n = escapeSystemdPath v.where;
|
||||
in nameValuePair "${n}.mount" (mountToUnit n v)) cfg.mounts)
|
||||
// listToAttrs (map
|
||||
(v: let n = escapeSystemdPath v.where;
|
||||
in nameValuePair "${n}.automount" (automountToUnit n v)) cfg.automounts);
|
||||
|
||||
# The unit in /run/systemd/generator shadows the unit in
|
||||
# /etc/systemd/system, but will still apply drop-ins from
|
||||
# /etc/systemd/system/foo.service.d/
|
||||
#
|
||||
# We need IgnoreOnIsolate, otherwise the Requires dependency of
|
||||
# a mount unit on its makefs unit causes it to be unmounted when
|
||||
# we isolate for switch-root. Use a dummy package so that
|
||||
# generateUnits will generate drop-ins instead of unit files.
|
||||
packages = [(pkgs.runCommand "dummy" {} ''
|
||||
mkdir -p $out/etc/systemd/system
|
||||
touch $out/etc/systemd/system/systemd-{makefs,growfs}@.service
|
||||
'')];
|
||||
services."systemd-makefs@" = lib.mkIf needMakefs { unitConfig.IgnoreOnIsolate = true; };
|
||||
services."systemd-growfs@" = lib.mkIf needGrowfs { unitConfig.IgnoreOnIsolate = true; };
|
||||
|
||||
services.initrd-nixos-activation = {
|
||||
after = [ "initrd-fs.target" ];
|
||||
requiredBy = [ "initrd.target" ];
|
||||
unitConfig.AssertPathExists = "/etc/initrd-release";
|
||||
serviceConfig.Type = "oneshot";
|
||||
description = "NixOS Activation";
|
||||
|
||||
script = /* bash */ ''
|
||||
set -uo pipefail
|
||||
export PATH="/bin:${cfg.package.util-linux}/bin"
|
||||
|
||||
# Figure out what closure to boot
|
||||
closure=
|
||||
for o in $(< /proc/cmdline); do
|
||||
case $o in
|
||||
init=*)
|
||||
IFS== read -r -a initParam <<< "$o"
|
||||
closure="$(dirname "''${initParam[1]}")"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Sanity check
|
||||
if [ -z "''${closure:-}" ]; then
|
||||
echo 'No init= parameter on the kernel command line' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# If we are not booting a NixOS closure (e.g. init=/bin/sh),
|
||||
# we don't know what root to prepare so we don't do anything
|
||||
if ! [ -x "/sysroot$closure/prepare-root" ]; then
|
||||
echo "NEW_INIT=''${initParam[1]}" > /etc/switch-root.conf
|
||||
echo "$closure does not look like a NixOS installation - not activating"
|
||||
exit 0
|
||||
fi
|
||||
echo 'NEW_INIT=' > /etc/switch-root.conf
|
||||
|
||||
|
||||
# We need to propagate /run for things like /run/booted-system
|
||||
# and /run/current-system.
|
||||
mkdir -p /sysroot/run
|
||||
mount --bind /run /sysroot/run
|
||||
|
||||
# Initialize the system
|
||||
export IN_NIXOS_SYSTEMD_STAGE1=true
|
||||
exec chroot /sysroot $closure/prepare-root
|
||||
'';
|
||||
};
|
||||
|
||||
# This will either call systemctl with the new init as the last parameter (which
|
||||
# is the case when not booting a NixOS system) or with an empty string, causing
|
||||
# systemd to bypass its verification code that checks whether the next file is a systemd
|
||||
# and using its compiled-in value
|
||||
services.initrd-switch-root.serviceConfig = {
|
||||
EnvironmentFile = "-/etc/switch-root.conf";
|
||||
ExecStart = [
|
||||
""
|
||||
''systemctl --no-block switch-root /sysroot "''${NEW_INIT}"''
|
||||
];
|
||||
};
|
||||
|
||||
services.panic-on-fail = {
|
||||
wantedBy = ["emergency.target"];
|
||||
unitConfig = {
|
||||
DefaultDependencies = false;
|
||||
ConditionKernelCommandLine = [
|
||||
"|boot.panic_on_fail"
|
||||
"|stage1panic"
|
||||
];
|
||||
};
|
||||
script = ''
|
||||
echo c > /proc/sysrq-trigger
|
||||
'';
|
||||
serviceConfig.Type = "oneshot";
|
||||
};
|
||||
};
|
||||
|
||||
boot.kernelParams = lib.mkIf (config.boot.resumeDevice != "") [ "resume=${config.boot.resumeDevice}" ];
|
||||
};
|
||||
}
|
||||
131
nixos/modules/system/boot/systemd/journald.nix
Normal file
131
nixos/modules/system/boot/systemd/journald.nix
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.journald;
|
||||
in {
|
||||
options = {
|
||||
services.journald.console = mkOption {
|
||||
default = "";
|
||||
type = types.str;
|
||||
description = "If non-empty, write log messages to the specified TTY device.";
|
||||
};
|
||||
|
||||
services.journald.rateLimitInterval = mkOption {
|
||||
default = "30s";
|
||||
type = types.str;
|
||||
description = ''
|
||||
Configures the rate limiting interval that is applied to all
|
||||
messages generated on the system. This rate limiting is applied
|
||||
per-service, so that two services which log do not interfere with
|
||||
each other's limit. The value may be specified in the following
|
||||
units: s, min, h, ms, us. To turn off any kind of rate limiting,
|
||||
set either value to 0.
|
||||
|
||||
See <option>services.journald.rateLimitBurst</option> for important
|
||||
considerations when setting this value.
|
||||
'';
|
||||
};
|
||||
|
||||
services.journald.rateLimitBurst = mkOption {
|
||||
default = 10000;
|
||||
type = types.int;
|
||||
description = ''
|
||||
Configures the rate limiting burst limit (number of messages per
|
||||
interval) that is applied to all messages generated on the system.
|
||||
This rate limiting is applied per-service, so that two services
|
||||
which log do not interfere with each other's limit.
|
||||
|
||||
Note that the effective rate limit is multiplied by a factor derived
|
||||
from the available free disk space for the journal as described on
|
||||
<link xlink:href="https://www.freedesktop.org/software/systemd/man/journald.conf.html">
|
||||
journald.conf(5)</link>.
|
||||
|
||||
Note that the total amount of logs stored is limited by journald settings
|
||||
such as <literal>SystemMaxUse</literal>, which defaults to a 4 GB cap.
|
||||
|
||||
It is thus recommended to compute what period of time that you will be
|
||||
able to store logs for when an application logs at full burst rate.
|
||||
With default settings for log lines that are 100 Bytes long, this can
|
||||
amount to just a few hours.
|
||||
'';
|
||||
};
|
||||
|
||||
services.journald.extraConfig = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
example = "Storage=volatile";
|
||||
description = ''
|
||||
Extra config options for systemd-journald. See man journald.conf
|
||||
for available options.
|
||||
'';
|
||||
};
|
||||
|
||||
services.journald.enableHttpGateway = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to enable the HTTP gateway to the journal.
|
||||
'';
|
||||
};
|
||||
|
||||
services.journald.forwardToSyslog = mkOption {
|
||||
default = config.services.rsyslogd.enable || config.services.syslog-ng.enable;
|
||||
defaultText = literalExpression "services.rsyslogd.enable || services.syslog-ng.enable";
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to forward log messages to syslog.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
systemd.additionalUpstreamSystemUnits = [
|
||||
"systemd-journald.socket"
|
||||
"systemd-journald@.socket"
|
||||
"systemd-journald-varlink@.socket"
|
||||
"systemd-journald.service"
|
||||
"systemd-journald@.service"
|
||||
"systemd-journal-flush.service"
|
||||
"systemd-journal-catalog-update.service"
|
||||
] ++ (optional (!config.boot.isContainer) "systemd-journald-audit.socket") ++ [
|
||||
"systemd-journald-dev-log.socket"
|
||||
"syslog.socket"
|
||||
] ++ optionals cfg.enableHttpGateway [
|
||||
"systemd-journal-gatewayd.socket"
|
||||
"systemd-journal-gatewayd.service"
|
||||
];
|
||||
|
||||
environment.etc = {
|
||||
"systemd/journald.conf".text = ''
|
||||
[Journal]
|
||||
Storage=persistent
|
||||
RateLimitInterval=${cfg.rateLimitInterval}
|
||||
RateLimitBurst=${toString cfg.rateLimitBurst}
|
||||
${optionalString (cfg.console != "") ''
|
||||
ForwardToConsole=yes
|
||||
TTYPath=${cfg.console}
|
||||
''}
|
||||
${optionalString (cfg.forwardToSyslog) ''
|
||||
ForwardToSyslog=yes
|
||||
''}
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
};
|
||||
|
||||
users.groups.systemd-journal.gid = config.ids.gids.systemd-journal;
|
||||
users.users.systemd-journal-gateway.uid = config.ids.uids.systemd-journal-gateway;
|
||||
users.users.systemd-journal-gateway.group = "systemd-journal-gateway";
|
||||
users.groups.systemd-journal-gateway.gid = config.ids.gids.systemd-journal-gateway;
|
||||
|
||||
systemd.sockets.systemd-journal-gatewayd.wantedBy =
|
||||
optional cfg.enableHttpGateway "sockets.target";
|
||||
|
||||
systemd.services.systemd-journal-flush.restartIfChanged = false;
|
||||
systemd.services.systemd-journald.restartTriggers = [ config.environment.etc."systemd/journald.conf".source ];
|
||||
systemd.services.systemd-journald.stopIfChanged = false;
|
||||
systemd.services."systemd-journald@".restartTriggers = [ config.environment.etc."systemd/journald.conf".source ];
|
||||
systemd.services."systemd-journald@".stopIfChanged = false;
|
||||
};
|
||||
}
|
||||
117
nixos/modules/system/boot/systemd/logind.nix
Normal file
117
nixos/modules/system/boot/systemd/logind.nix
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
{ config, lib, pkgs, utils, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.logind;
|
||||
|
||||
logindHandlerType = types.enum [
|
||||
"ignore" "poweroff" "reboot" "halt" "kexec" "suspend"
|
||||
"hibernate" "hybrid-sleep" "suspend-then-hibernate" "lock"
|
||||
];
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.logind.extraConfig = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
example = "IdleAction=lock";
|
||||
description = ''
|
||||
Extra config options for systemd-logind. See
|
||||
<link xlink:href="https://www.freedesktop.org/software/systemd/man/logind.conf.html">
|
||||
logind.conf(5)</link> for available options.
|
||||
'';
|
||||
};
|
||||
|
||||
services.logind.killUserProcesses = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Specifies whether the processes of a user should be killed
|
||||
when the user logs out. If true, the scope unit corresponding
|
||||
to the session and all processes inside that scope will be
|
||||
terminated. If false, the scope is "abandoned" (see
|
||||
<link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.scope.html#">
|
||||
systemd.scope(5)</link>), and processes are not killed.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
See <link xlink:href="https://www.freedesktop.org/software/systemd/man/logind.conf.html#KillUserProcesses=">logind.conf(5)</link>
|
||||
for more details.
|
||||
'';
|
||||
};
|
||||
|
||||
services.logind.lidSwitch = mkOption {
|
||||
default = "suspend";
|
||||
example = "ignore";
|
||||
type = logindHandlerType;
|
||||
|
||||
description = ''
|
||||
Specifies what to be done when the laptop lid is closed.
|
||||
'';
|
||||
};
|
||||
|
||||
services.logind.lidSwitchDocked = mkOption {
|
||||
default = "ignore";
|
||||
example = "suspend";
|
||||
type = logindHandlerType;
|
||||
|
||||
description = ''
|
||||
Specifies what to be done when the laptop lid is closed
|
||||
and another screen is added.
|
||||
'';
|
||||
};
|
||||
|
||||
services.logind.lidSwitchExternalPower = mkOption {
|
||||
default = cfg.lidSwitch;
|
||||
defaultText = literalExpression "services.logind.lidSwitch";
|
||||
example = "ignore";
|
||||
type = logindHandlerType;
|
||||
|
||||
description = ''
|
||||
Specifies what to do when the laptop lid is closed and the system is
|
||||
on external power. By default use the same action as specified in
|
||||
services.logind.lidSwitch.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
systemd.additionalUpstreamSystemUnits = [
|
||||
"systemd-logind.service"
|
||||
"autovt@.service"
|
||||
"systemd-user-sessions.service"
|
||||
] ++ optionals config.systemd.package.withImportd [
|
||||
"dbus-org.freedesktop.import1.service"
|
||||
] ++ optionals config.systemd.package.withMachined [
|
||||
"dbus-org.freedesktop.machine1.service"
|
||||
] ++ [
|
||||
"dbus-org.freedesktop.login1.service"
|
||||
"user@.service"
|
||||
"user-runtime-dir@.service"
|
||||
];
|
||||
|
||||
environment.etc = {
|
||||
"systemd/logind.conf".text = ''
|
||||
[Login]
|
||||
KillUserProcesses=${if cfg.killUserProcesses then "yes" else "no"}
|
||||
HandleLidSwitch=${cfg.lidSwitch}
|
||||
HandleLidSwitchDocked=${cfg.lidSwitchDocked}
|
||||
HandleLidSwitchExternalPower=${cfg.lidSwitchExternalPower}
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
};
|
||||
|
||||
# Restarting systemd-logind breaks X11
|
||||
# - upstream commit: https://cgit.freedesktop.org/xorg/xserver/commit/?id=dc48bd653c7e101
|
||||
# - systemd announcement: https://github.com/systemd/systemd/blob/22043e4317ecd2bc7834b48a6d364de76bb26d91/NEWS#L103-L112
|
||||
# - this might be addressed in the future by xorg
|
||||
#systemd.services.systemd-logind.restartTriggers = [ config.environment.etc."systemd/logind.conf".source ];
|
||||
systemd.services.systemd-logind.restartIfChanged = false;
|
||||
systemd.services.systemd-logind.stopIfChanged = false;
|
||||
|
||||
# The user-runtime-dir@ service is managed by systemd-logind we should not touch it or else we break the users' sessions.
|
||||
systemd.services."user-runtime-dir@".stopIfChanged = false;
|
||||
systemd.services."user-runtime-dir@".restartIfChanged = false;
|
||||
};
|
||||
}
|
||||
133
nixos/modules/system/boot/systemd/nspawn.nix
Normal file
133
nixos/modules/system/boot/systemd/nspawn.nix
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
{ config, lib, pkgs, utils, ...}:
|
||||
|
||||
with utils.systemdUtils.unitOptions;
|
||||
with utils.systemdUtils.lib;
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.systemd.nspawn;
|
||||
|
||||
checkExec = checkUnitConfig "Exec" [
|
||||
(assertOnlyFields [
|
||||
"Boot" "ProcessTwo" "Parameters" "Environment" "User" "WorkingDirectory"
|
||||
"PivotRoot" "Capability" "DropCapability" "NoNewPrivileges" "KillSignal"
|
||||
"Personality" "MachineId" "PrivateUsers" "NotifyReady" "SystemCallFilter"
|
||||
"LimitCPU" "LimitFSIZE" "LimitDATA" "LimitSTACK" "LimitCORE" "LimitRSS"
|
||||
"LimitNOFILE" "LimitAS" "LimitNPROC" "LimitMEMLOCK" "LimitLOCKS"
|
||||
"LimitSIGPENDING" "LimitMSGQUEUE" "LimitNICE" "LimitRTPRIO" "LimitRTTIME"
|
||||
"OOMScoreAdjust" "CPUAffinity" "Hostname" "ResolvConf" "Timezone"
|
||||
"LinkJournal" "Ephemeral" "AmbientCapability"
|
||||
])
|
||||
(assertValueOneOf "Boot" boolValues)
|
||||
(assertValueOneOf "ProcessTwo" boolValues)
|
||||
(assertValueOneOf "NotifyReady" boolValues)
|
||||
];
|
||||
|
||||
checkFiles = checkUnitConfig "Files" [
|
||||
(assertOnlyFields [
|
||||
"ReadOnly" "Volatile" "Bind" "BindReadOnly" "TemporaryFileSystem"
|
||||
"Overlay" "OverlayReadOnly" "PrivateUsersChown" "BindUser"
|
||||
"Inaccessible" "PrivateUserOwnership"
|
||||
])
|
||||
(assertValueOneOf "ReadOnly" boolValues)
|
||||
(assertValueOneOf "Volatile" (boolValues ++ [ "state" ]))
|
||||
(assertValueOneOf "PrivateUsersChown" boolValues)
|
||||
(assertValueOneOf "PrivateUserOwnership" [ "off" "chown" "map" "auto" ])
|
||||
];
|
||||
|
||||
checkNetwork = checkUnitConfig "Network" [
|
||||
(assertOnlyFields [
|
||||
"Private" "VirtualEthernet" "VirtualEthernetExtra" "Interface" "MACVLAN"
|
||||
"IPVLAN" "Bridge" "Zone" "Port"
|
||||
])
|
||||
(assertValueOneOf "Private" boolValues)
|
||||
(assertValueOneOf "VirtualEthernet" boolValues)
|
||||
];
|
||||
|
||||
instanceOptions = {
|
||||
options = sharedOptions // {
|
||||
execConfig = mkOption {
|
||||
default = {};
|
||||
example = { Parameters = "/bin/sh"; };
|
||||
type = types.addCheck (types.attrsOf unitOption) checkExec;
|
||||
description = ''
|
||||
Each attribute in this set specifies an option in the
|
||||
<literal>[Exec]</literal> section of this unit. See
|
||||
<citerefentry><refentrytitle>systemd.nspawn</refentrytitle>
|
||||
<manvolnum>5</manvolnum></citerefentry> for details.
|
||||
'';
|
||||
};
|
||||
|
||||
filesConfig = mkOption {
|
||||
default = {};
|
||||
example = { Bind = [ "/home/alice" ]; };
|
||||
type = types.addCheck (types.attrsOf unitOption) checkFiles;
|
||||
description = ''
|
||||
Each attribute in this set specifies an option in the
|
||||
<literal>[Files]</literal> section of this unit. See
|
||||
<citerefentry><refentrytitle>systemd.nspawn</refentrytitle>
|
||||
<manvolnum>5</manvolnum></citerefentry> for details.
|
||||
'';
|
||||
};
|
||||
|
||||
networkConfig = mkOption {
|
||||
default = {};
|
||||
example = { Private = false; };
|
||||
type = types.addCheck (types.attrsOf unitOption) checkNetwork;
|
||||
description = ''
|
||||
Each attribute in this set specifies an option in the
|
||||
<literal>[Network]</literal> section of this unit. See
|
||||
<citerefentry><refentrytitle>systemd.nspawn</refentrytitle>
|
||||
<manvolnum>5</manvolnum></citerefentry> for details.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
instanceToUnit = name: def:
|
||||
let base = {
|
||||
text = ''
|
||||
[Exec]
|
||||
${attrsToSection def.execConfig}
|
||||
|
||||
[Files]
|
||||
${attrsToSection def.filesConfig}
|
||||
|
||||
[Network]
|
||||
${attrsToSection def.networkConfig}
|
||||
'';
|
||||
} // def;
|
||||
in base // { unit = makeUnit name base; };
|
||||
|
||||
in {
|
||||
|
||||
options = {
|
||||
|
||||
systemd.nspawn = mkOption {
|
||||
default = {};
|
||||
type = with types; attrsOf (submodule instanceOptions);
|
||||
description = "Definition of systemd-nspawn configurations.";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config =
|
||||
let
|
||||
units = mapAttrs' (n: v: let nspawnFile = "${n}.nspawn"; in nameValuePair nspawnFile (instanceToUnit nspawnFile v)) cfg;
|
||||
in
|
||||
mkMerge [
|
||||
(mkIf (cfg != {}) {
|
||||
environment.etc."systemd/nspawn".source = mkIf (cfg != {}) (generateUnits {
|
||||
allowCollisions = false;
|
||||
type = "nspawn";
|
||||
inherit units;
|
||||
upstreamUnits = [];
|
||||
upstreamWants = [];
|
||||
});
|
||||
})
|
||||
{
|
||||
systemd.targets.multi-user.wants = [ "machines.target" ];
|
||||
}
|
||||
];
|
||||
}
|
||||
58
nixos/modules/system/boot/systemd/shutdown.nix
Normal file
58
nixos/modules/system/boot/systemd/shutdown.nix
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
{ config, lib, utils, pkgs, ... }: let
|
||||
|
||||
cfg = config.systemd.shutdownRamfs;
|
||||
|
||||
ramfsContents = let
|
||||
storePaths = map (p: "${p}\n") cfg.storePaths;
|
||||
contents = lib.mapAttrsToList (_: v: "${v.source}\n${v.target}") (lib.filterAttrs (_: v: v.enable) cfg.contents);
|
||||
in pkgs.writeText "shutdown-ramfs-contents" (lib.concatStringsSep "\n" (storePaths ++ contents));
|
||||
|
||||
in {
|
||||
options.systemd.shutdownRamfs = {
|
||||
enable = lib.mkEnableOption "pivoting back to an initramfs for shutdown" // { default = true; };
|
||||
contents = lib.mkOption {
|
||||
description = "Set of files that have to be linked into the shutdown ramfs";
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
"/lib/systemd/system-shutdown/zpool-sync-shutdown".source = writeShellScript "zpool" "exec ''${zfs}/bin/zpool sync"
|
||||
}
|
||||
'';
|
||||
type = utils.systemdUtils.types.initrdContents;
|
||||
};
|
||||
|
||||
storePaths = lib.mkOption {
|
||||
description = ''
|
||||
Store paths to copy into the shutdown ramfs as well.
|
||||
'';
|
||||
type = lib.types.listOf lib.types.singleLineStr;
|
||||
default = [];
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
systemd.shutdownRamfs.contents."/shutdown".source = "${config.systemd.package}/lib/systemd/systemd-shutdown";
|
||||
systemd.shutdownRamfs.storePaths = [pkgs.runtimeShell "${pkgs.coreutils}/bin"];
|
||||
|
||||
systemd.services.generate-shutdown-ramfs = {
|
||||
description = "Generate shutdown ramfs";
|
||||
wantedBy = [ "shutdown.target" ];
|
||||
before = [ "shutdown.target" ];
|
||||
unitConfig = {
|
||||
DefaultDependencies = false;
|
||||
ConditionFileIsExecutable = [
|
||||
"!/run/initramfs/shutdown"
|
||||
];
|
||||
};
|
||||
|
||||
path = [pkgs.util-linux pkgs.makeInitrdNGTool];
|
||||
serviceConfig.Type = "oneshot";
|
||||
script = ''
|
||||
mkdir -p /run/initramfs
|
||||
if ! mountpoint -q /run/initramfs; then
|
||||
mount -t tmpfs tmpfs /run/initramfs
|
||||
fi
|
||||
make-initrd-ng ${ramfsContents} /run/initramfs
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
120
nixos/modules/system/boot/systemd/tmpfiles.nix
Normal file
120
nixos/modules/system/boot/systemd/tmpfiles.nix
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
{ config, lib, pkgs, utils, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.systemd.tmpfiles;
|
||||
systemd = config.systemd.package;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
systemd.tmpfiles.rules = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
example = [ "d /tmp 1777 root root 10d" ];
|
||||
description = ''
|
||||
Rules for creation, deletion and cleaning of volatile and temporary files
|
||||
automatically. See
|
||||
<citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||
for the exact format.
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.tmpfiles.packages = mkOption {
|
||||
type = types.listOf types.package;
|
||||
default = [];
|
||||
example = literalExpression "[ pkgs.lvm2 ]";
|
||||
apply = map getLib;
|
||||
description = ''
|
||||
List of packages containing <command>systemd-tmpfiles</command> rules.
|
||||
|
||||
All files ending in .conf found in
|
||||
<filename><replaceable>pkg</replaceable>/lib/tmpfiles.d</filename>
|
||||
will be included.
|
||||
If this folder does not exist or does not contain any files an error will be returned instead.
|
||||
|
||||
If a <filename>lib</filename> output is available, rules are searched there and only there.
|
||||
If there is no <filename>lib</filename> output it will fall back to <filename>out</filename>
|
||||
and if that does not exist either, the default output will be used.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
systemd.additionalUpstreamSystemUnits = [
|
||||
"systemd-tmpfiles-clean.service"
|
||||
"systemd-tmpfiles-clean.timer"
|
||||
"systemd-tmpfiles-setup.service"
|
||||
"systemd-tmpfiles-setup-dev.service"
|
||||
];
|
||||
|
||||
systemd.additionalUpstreamUserUnits = [
|
||||
"systemd-tmpfiles-clean.service"
|
||||
"systemd-tmpfiles-clean.timer"
|
||||
"systemd-tmpfiles-setup.service"
|
||||
];
|
||||
|
||||
environment.etc = {
|
||||
"tmpfiles.d".source = (pkgs.symlinkJoin {
|
||||
name = "tmpfiles.d";
|
||||
paths = map (p: p + "/lib/tmpfiles.d") cfg.packages;
|
||||
postBuild = ''
|
||||
for i in $(cat $pathsPath); do
|
||||
(test -d "$i" && test $(ls "$i"/*.conf | wc -l) -ge 1) || (
|
||||
echo "ERROR: The path '$i' from systemd.tmpfiles.packages contains no *.conf files."
|
||||
exit 1
|
||||
)
|
||||
done
|
||||
'' + concatMapStrings (name: optionalString (hasPrefix "tmpfiles.d/" name) ''
|
||||
rm -f $out/${removePrefix "tmpfiles.d/" name}
|
||||
'') config.system.build.etc.passthru.targets;
|
||||
}) + "/*";
|
||||
};
|
||||
|
||||
systemd.tmpfiles.packages = [
|
||||
# Default tmpfiles rules provided by systemd
|
||||
(pkgs.runCommand "systemd-default-tmpfiles" {} ''
|
||||
mkdir -p $out/lib/tmpfiles.d
|
||||
cd $out/lib/tmpfiles.d
|
||||
|
||||
ln -s "${systemd}/example/tmpfiles.d/home.conf"
|
||||
ln -s "${systemd}/example/tmpfiles.d/journal-nocow.conf"
|
||||
ln -s "${systemd}/example/tmpfiles.d/static-nodes-permissions.conf"
|
||||
ln -s "${systemd}/example/tmpfiles.d/systemd.conf"
|
||||
ln -s "${systemd}/example/tmpfiles.d/systemd-nologin.conf"
|
||||
ln -s "${systemd}/example/tmpfiles.d/systemd-nspawn.conf"
|
||||
ln -s "${systemd}/example/tmpfiles.d/systemd-tmp.conf"
|
||||
ln -s "${systemd}/example/tmpfiles.d/tmp.conf"
|
||||
ln -s "${systemd}/example/tmpfiles.d/var.conf"
|
||||
ln -s "${systemd}/example/tmpfiles.d/x11.conf"
|
||||
'')
|
||||
# User-specified tmpfiles rules
|
||||
(pkgs.writeTextFile {
|
||||
name = "nixos-tmpfiles.d";
|
||||
destination = "/lib/tmpfiles.d/00-nixos.conf";
|
||||
text = ''
|
||||
# This file is created automatically and should not be modified.
|
||||
# Please change the option ‘systemd.tmpfiles.rules’ instead.
|
||||
|
||||
${concatStringsSep "\n" cfg.rules}
|
||||
'';
|
||||
})
|
||||
];
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /nix/var 0755 root root - -"
|
||||
"L+ /nix/var/nix/gcroots/booted-system 0755 root root - /run/booted-system"
|
||||
"d /run/lock 0755 root root - -"
|
||||
"d /var/db 0755 root root - -"
|
||||
"L /etc/mtab - - - - ../proc/mounts"
|
||||
"L /var/lock - - - - ../run/lock"
|
||||
# Boot-time cleanup
|
||||
"R! /etc/group.lock - - - - -"
|
||||
"R! /etc/passwd.lock - - - - -"
|
||||
"R! /etc/shadow.lock - - - - -"
|
||||
"R! /etc/mtab* - - - - -"
|
||||
"R! /nix/var/nix/gcroots/tmp - - - - -"
|
||||
"R! /nix/var/nix/temproots - - - - -"
|
||||
];
|
||||
};
|
||||
}
|
||||
153
nixos/modules/system/boot/systemd/user.nix
Normal file
153
nixos/modules/system/boot/systemd/user.nix
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
{ config, lib, pkgs, utils, ... }:
|
||||
with utils;
|
||||
with systemdUtils.unitOptions;
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.systemd.user;
|
||||
|
||||
systemd = config.systemd.package;
|
||||
|
||||
inherit
|
||||
(systemdUtils.lib)
|
||||
makeUnit
|
||||
generateUnits
|
||||
targetToUnit
|
||||
serviceToUnit
|
||||
socketToUnit
|
||||
timerToUnit
|
||||
pathToUnit;
|
||||
|
||||
upstreamUserUnits = [
|
||||
"app.slice"
|
||||
"background.slice"
|
||||
"basic.target"
|
||||
"bluetooth.target"
|
||||
"default.target"
|
||||
"exit.target"
|
||||
"graphical-session-pre.target"
|
||||
"graphical-session.target"
|
||||
"paths.target"
|
||||
"printer.target"
|
||||
"session.slice"
|
||||
"shutdown.target"
|
||||
"smartcard.target"
|
||||
"sockets.target"
|
||||
"sound.target"
|
||||
"systemd-exit.service"
|
||||
"timers.target"
|
||||
"xdg-desktop-autostart.target"
|
||||
] ++ config.systemd.additionalUpstreamUserUnits;
|
||||
in {
|
||||
options = {
|
||||
systemd.user.extraConfig = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
example = "DefaultCPUAccounting=yes";
|
||||
description = ''
|
||||
Extra config options for systemd user instances. See man systemd-user.conf for
|
||||
available options.
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.user.units = mkOption {
|
||||
description = "Definition of systemd per-user units.";
|
||||
default = {};
|
||||
type = systemdUtils.types.units;
|
||||
};
|
||||
|
||||
systemd.user.paths = mkOption {
|
||||
default = {};
|
||||
type = systemdUtils.types.paths;
|
||||
description = "Definition of systemd per-user path units.";
|
||||
};
|
||||
|
||||
systemd.user.services = mkOption {
|
||||
default = {};
|
||||
type = systemdUtils.types.services;
|
||||
description = "Definition of systemd per-user service units.";
|
||||
};
|
||||
|
||||
systemd.user.slices = mkOption {
|
||||
default = {};
|
||||
type = systemdUtils.types.slices;
|
||||
description = "Definition of systemd per-user slice units.";
|
||||
};
|
||||
|
||||
systemd.user.sockets = mkOption {
|
||||
default = {};
|
||||
type = systemdUtils.types.sockets;
|
||||
description = "Definition of systemd per-user socket units.";
|
||||
};
|
||||
|
||||
systemd.user.targets = mkOption {
|
||||
default = {};
|
||||
type = systemdUtils.types.targets;
|
||||
description = "Definition of systemd per-user target units.";
|
||||
};
|
||||
|
||||
systemd.user.timers = mkOption {
|
||||
default = {};
|
||||
type = systemdUtils.types.timers;
|
||||
description = "Definition of systemd per-user timer units.";
|
||||
};
|
||||
|
||||
systemd.additionalUpstreamUserUnits = mkOption {
|
||||
default = [];
|
||||
type = types.listOf types.str;
|
||||
example = [];
|
||||
description = ''
|
||||
Additional units shipped with systemd that should be enabled for per-user systemd instances.
|
||||
'';
|
||||
internal = true;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
systemd.additionalUpstreamSystemUnits = [
|
||||
"user.slice"
|
||||
];
|
||||
|
||||
environment.etc = {
|
||||
"systemd/user".source = generateUnits {
|
||||
type = "user";
|
||||
inherit (cfg) units;
|
||||
upstreamUnits = upstreamUserUnits;
|
||||
upstreamWants = [];
|
||||
};
|
||||
|
||||
"systemd/user.conf".text = ''
|
||||
[Manager]
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.user.units =
|
||||
mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit n v)) cfg.paths
|
||||
// mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services
|
||||
// mapAttrs' (n: v: nameValuePair "${n}.slice" (sliceToUnit n v)) cfg.slices
|
||||
// mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit n v)) cfg.sockets
|
||||
// mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit n v)) cfg.targets
|
||||
// mapAttrs' (n: v: nameValuePair "${n}.timer" (timerToUnit n v)) cfg.timers;
|
||||
|
||||
# Generate timer units for all services that have a ‘startAt’ value.
|
||||
systemd.user.timers =
|
||||
mapAttrs (name: service: {
|
||||
wantedBy = ["timers.target"];
|
||||
timerConfig.OnCalendar = service.startAt;
|
||||
})
|
||||
(filterAttrs (name: service: service.startAt != []) cfg.services);
|
||||
|
||||
# Provide the systemd-user PAM service, required to run systemd
|
||||
# user instances.
|
||||
security.pam.services.systemd-user =
|
||||
{ # Ensure that pam_systemd gets included. This is special-cased
|
||||
# in systemd to provide XDG_RUNTIME_DIR.
|
||||
startSession = true;
|
||||
};
|
||||
|
||||
# Some overrides to upstream units.
|
||||
systemd.services."user@".restartIfChanged = false;
|
||||
systemd.services.systemd-user-sessions.restartIfChanged = false; # Restart kills all active sessions.
|
||||
};
|
||||
}
|
||||
86
nixos/modules/system/boot/timesyncd.nix
Normal file
86
nixos/modules/system/boot/timesyncd.nix
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
{
|
||||
|
||||
options = {
|
||||
|
||||
services.timesyncd = {
|
||||
enable = mkOption {
|
||||
default = !config.boot.isContainer;
|
||||
defaultText = literalExpression "!config.boot.isContainer";
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Enables the systemd NTP client daemon.
|
||||
'';
|
||||
};
|
||||
servers = mkOption {
|
||||
default = config.networking.timeServers;
|
||||
defaultText = literalExpression "config.networking.timeServers";
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
The set of NTP servers from which to synchronise.
|
||||
'';
|
||||
};
|
||||
extraConfig = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
example = ''
|
||||
PollIntervalMaxSec=180
|
||||
'';
|
||||
description = ''
|
||||
Extra config options for systemd-timesyncd. See
|
||||
<link xlink:href="https://www.freedesktop.org/software/systemd/man/timesyncd.conf.html">
|
||||
timesyncd.conf(5)</link> for available options.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf config.services.timesyncd.enable {
|
||||
|
||||
systemd.additionalUpstreamSystemUnits = [ "systemd-timesyncd.service" ];
|
||||
|
||||
systemd.services.systemd-timesyncd = {
|
||||
wantedBy = [ "sysinit.target" ];
|
||||
aliases = [ "dbus-org.freedesktop.timesync1.service" ];
|
||||
restartTriggers = [ config.environment.etc."systemd/timesyncd.conf".source ];
|
||||
};
|
||||
|
||||
environment.etc."systemd/timesyncd.conf".text = ''
|
||||
[Time]
|
||||
NTP=${concatStringsSep " " config.services.timesyncd.servers}
|
||||
${config.services.timesyncd.extraConfig}
|
||||
'';
|
||||
|
||||
users.users.systemd-timesync = {
|
||||
uid = config.ids.uids.systemd-timesync;
|
||||
group = "systemd-timesync";
|
||||
};
|
||||
users.groups.systemd-timesync.gid = config.ids.gids.systemd-timesync;
|
||||
|
||||
system.activationScripts.systemd-timesyncd-migration =
|
||||
# workaround an issue of systemd-timesyncd not starting due to upstream systemd reverting their dynamic users changes
|
||||
# - https://github.com/NixOS/nixpkgs/pull/61321#issuecomment-492423742
|
||||
# - https://github.com/systemd/systemd/issues/12131
|
||||
mkIf (versionOlder config.system.stateVersion "19.09") ''
|
||||
if [ -L /var/lib/systemd/timesync ]; then
|
||||
rm /var/lib/systemd/timesync
|
||||
mv /var/lib/private/systemd/timesync /var/lib/systemd/timesync
|
||||
fi
|
||||
'';
|
||||
system.activationScripts.systemd-timesyncd-init-clock =
|
||||
# Ensure that we have some stored time to prevent systemd-timesyncd to
|
||||
# resort back to the fallback time.
|
||||
# If the file doesn't exist we assume that our current system clock is
|
||||
# good enough to provide an initial value.
|
||||
''
|
||||
if ! [ -f /var/lib/systemd/timesync/clock ]; then
|
||||
test -d /var/lib/systemd/timesync || mkdir -p /var/lib/systemd/timesync
|
||||
touch /var/lib/systemd/timesync/clock
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
}
|
||||
64
nixos/modules/system/boot/tmp.nix
Normal file
64
nixos/modules/system/boot/tmp.nix
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.boot;
|
||||
in
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
boot.cleanTmpDir = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to delete all files in <filename>/tmp</filename> during boot.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.tmpOnTmpfs = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to mount a tmpfs on <filename>/tmp</filename> during boot.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.tmpOnTmpfsSize = mkOption {
|
||||
type = types.oneOf [ types.str types.types.ints.positive ];
|
||||
default = "50%";
|
||||
description = ''
|
||||
Size of tmpfs in percentage.
|
||||
Percentage is defined by systemd.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = {
|
||||
|
||||
# When changing remember to update /tmp mount in virtualisation/qemu-vm.nix
|
||||
systemd.mounts = mkIf cfg.tmpOnTmpfs [
|
||||
{
|
||||
what = "tmpfs";
|
||||
where = "/tmp";
|
||||
type = "tmpfs";
|
||||
mountConfig.Options = concatStringsSep "," [ "mode=1777"
|
||||
"strictatime"
|
||||
"rw"
|
||||
"nosuid"
|
||||
"nodev"
|
||||
"size=${toString cfg.tmpOnTmpfsSize}" ];
|
||||
}
|
||||
];
|
||||
|
||||
systemd.tmpfiles.rules = optional config.boot.cleanTmpDir "D! /tmp 1777 root root";
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue