diff options
Diffstat (limited to 'nixos/modules/system')
27 files changed, 876 insertions, 309 deletions
diff --git a/nixos/modules/system/activation/activation-script.nix b/nixos/modules/system/activation/activation-script.nix index 1545bcb8a1f9..41fe7d309a5a 100644 --- a/nixos/modules/system/activation/activation-script.nix +++ b/nixos/modules/system/activation/activation-script.nix @@ -1,7 +1,7 @@ # generate the script used to activate the configuration. -{ config, pkgs, ... }: +{ config, lib, pkgs, ... }: -with pkgs.lib; +with lib; let diff --git a/nixos/modules/system/activation/no-clone.nix b/nixos/modules/system/activation/no-clone.nix index c9ab691ce477..7f4584435266 100644 --- a/nixos/modules/system/activation/no-clone.nix +++ b/nixos/modules/system/activation/no-clone.nix @@ -1,6 +1,6 @@ -{pkgs, ...}: +{ lib, ... }: -with pkgs.lib; +with lib; { boot.loader.grub.device = mkOverride 0 "nodev"; diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl index 91beed1130eb..25b5afe99da4 100644 --- a/nixos/modules/system/activation/switch-to-configuration.pl +++ b/nixos/modules/system/activation/switch-to-configuration.pl @@ -27,7 +27,10 @@ EOF exit 1; } -die "This is not a NixOS installation (/etc/NIXOS is missing)!\n" unless -f "/etc/NIXOS"; +# This is a NixOS installation if it has /etc/NIXOS or a proper +# /etc/os-release. +die "This is not a NixOS installation!\n" unless + -f "/etc/NIXOS" || (read_file("/etc/os-release", err_mode => 'quiet') // "") =~ /ID=nixos/s; openlog("nixos", "", LOG_USER); @@ -96,12 +99,18 @@ sub parseFstab { sub parseUnit { my ($filename) = @_; my $info = {}; - foreach my $line (read_file($filename)) { + parseKeyValues($info, read_file($filename)); + parseKeyValues($info, read_file("${filename}.d/overrides.conf")) if -f "${filename}.d/overrides.conf"; + return $info; +} + +sub parseKeyValues { + my $info = shift; + foreach my $line (@_) { # FIXME: not quite correct. $line =~ /^([^=]+)=(.*)$/ or next; $info->{$1} = $2; } - return $info; } sub boolIsTrue { @@ -109,6 +118,14 @@ sub boolIsTrue { return $s eq "yes" || $s eq "true"; } +# As a fingerprint for determining whether a unit has changed, we use +# its absolute path. If it has an override file, we append *its* +# absolute path as well. +sub fingerprintUnit { + my ($s) = @_; + return abs_path($s) . (-f "${s}.d/overrides.conf" ? " " . abs_path "${s}.d/overrides.conf" : ""); +} + # Stop all services that no longer exist or have changed in the new # configuration. my (@unitsToStop, @unitsToSkip); @@ -125,7 +142,7 @@ while (my ($unit, $state) = each %{$activePrev}) { $baseName =~ s/\.[a-z]*$//; if (-e $prevUnitFile && ($state->{state} eq "active" || $state->{state} eq "activating")) { - if (! -e $newUnitFile) { + if (! -e $newUnitFile || abs_path($newUnitFile) eq "/dev/null") { push @unitsToStop, $unit; } @@ -160,7 +177,7 @@ while (my ($unit, $state) = each %{$activePrev}) { } } - elsif (abs_path($prevUnitFile) ne abs_path($newUnitFile)) { + elsif (fingerprintUnit($prevUnitFile) ne fingerprintUnit($newUnitFile)) { if ($unit eq "sysinit.target" || $unit eq "basic.target" || $unit eq "multi-user.target" || $unit eq "graphical.target") { # Do nothing. These cannot be restarted directly. } elsif ($unit =~ /\.mount$/) { @@ -170,7 +187,10 @@ while (my ($unit, $state) = each %{$activePrev}) { # FIXME: do something? } else { my $unitInfo = parseUnit($newUnitFile); - if (!boolIsTrue($unitInfo->{'X-RestartIfChanged'} // "yes")) { + if (boolIsTrue($unitInfo->{'X-ReloadIfChanged'} // "no")) { + write_file($reloadListFile, { append => 1 }, "$unit\n"); + } + elsif (!boolIsTrue($unitInfo->{'X-RestartIfChanged'} // "yes")) { push @unitsToSkip, $unit; } else { # If this unit is socket-activated, then stop the @@ -319,7 +339,7 @@ if (scalar @restart > 0) { # that are symlinks to other units. We shouldn't start both at the # same time because we'll get a "Failed to add path to set" error from # systemd. -my @start = unique("default.target", "timers.target", split('\n', read_file($startListFile, err_mode => 'quiet') // "")); +my @start = unique("default.target", "timers.target", "sockets.target", split('\n', read_file($startListFile, err_mode => 'quiet') // "")); print STDERR "starting the following units: ", join(", ", sort(@start)), "\n"; $systemdManager->StartUnit($_, "replace") for @start; unlink($startListFile); diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix index 30a529988a96..1600a1fb0104 100644 --- a/nixos/modules/system/activation/top-level.nix +++ b/nixos/modules/system/activation/top-level.nix @@ -1,6 +1,6 @@ -{ config, pkgs, modules, baseModules, ... }: +{ config, lib, pkgs, modules, baseModules, ... }: -with pkgs.lib; +with lib; let @@ -11,7 +11,7 @@ let # you can provide an easy way to boot the same configuration # as you use, but with another kernel # !!! fix this - cloner = inheritParent: list: with pkgs.lib; + cloner = inheritParent: list: map (childConfig: (import ../../../lib/eval-config.nix { inherit baseModules; @@ -68,6 +68,7 @@ let echo -n "$configurationName" > $out/configuration-name echo -n "systemd ${toString config.systemd.package.interfaceVersion}" > $out/init-interface-version echo -n "$nixosVersion" > $out/nixos-version + echo -n "$system" > $out/system mkdir $out/fine-tune childCount=0 diff --git a/nixos/modules/system/boot/kernel.nix b/nixos/modules/system/boot/kernel.nix index 2b075bf6a6d2..b81bcf20f439 100644 --- a/nixos/modules/system/boot/kernel.nix +++ b/nixos/modules/system/boot/kernel.nix @@ -1,6 +1,6 @@ -{ config, pkgs, ... }: +{ config, lib, pkgs, ... }: -with pkgs.lib; +with lib; let @@ -159,7 +159,7 @@ in boot.kernel.sysctl."kernel.printk" = config.boot.consoleLogLevel; - boot.kernelModules = [ "loop" ]; + boot.kernelModules = [ "loop" "configs" ]; boot.initrd.availableKernelModules = [ # Note: most of these (especially the SATA/PATA modules) @@ -203,6 +203,9 @@ in # To wait for SCSI devices to appear. "scsi_wait_scan" + + # Needed by the stage 2 init script. + "rtc_cmos" ]; boot.initrd.kernelModules = @@ -215,37 +218,26 @@ in # Create /etc/modules-load.d/nixos.conf, which is read by # systemd-modules-load.service to load required kernel modules. - # FIXME: ensure that systemd-modules-load.service is restarted if - # this file changes. environment.etc = singleton { target = "modules-load.d/nixos.conf"; source = kernelModulesConf; }; - # Sigh. This overrides systemd's systemd-modules-load.service - # just so we can set a restart trigger. Also make - # multi-user.target pull it in so that it gets started if it - # failed earlier. systemd.services."systemd-modules-load" = - { description = "Load Kernel Modules"; - wantedBy = [ "sysinit.target" "multi-user.target" ]; - before = [ "sysinit.target" "shutdown.target" ]; - conflicts = [ "shutdown.target" ]; - unitConfig = - { DefaultDependencies = false; - ConditionCapability = "CAP_SYS_MODULE"; - }; + { wantedBy = [ "multi-user.target" ]; + restartTriggers = [ kernelModulesConf ]; + environment.MODULE_DIR = "/run/booted-system/kernel-modules/lib/modules"; serviceConfig = - { Type = "oneshot"; - RemainAfterExit = true; - ExecStart = "${config.systemd.package}/lib/systemd/systemd-modules-load"; - # Ignore failed module loads. Typically some of the + { # 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"; }; - restartTriggers = [ kernelModulesConf ]; + }; + + systemd.services.kmod-static-nodes = + { environment.MODULE_DIR = "/run/booted-system/kernel-modules/lib/modules"; }; lib.kernelConfig = { diff --git a/nixos/modules/system/boot/loader/efi.nix b/nixos/modules/system/boot/loader/efi.nix index 7e739173f9a3..241cfc7e836d 100644 --- a/nixos/modules/system/boot/loader/efi.nix +++ b/nixos/modules/system/boot/loader/efi.nix @@ -1,6 +1,6 @@ -{ pkgs, ... }: +{ lib, ... }: -with pkgs.lib; +with lib; { options.boot.loader.efi = { diff --git a/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix b/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix index 9855c8c19dd0..4b5e84f53c1a 100644 --- a/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix +++ b/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix @@ -1,6 +1,6 @@ -{ config, pkgs, ... }: +{ config, lib, pkgs, ... }: -with pkgs.lib; +with lib; let diff --git a/nixos/modules/system/boot/loader/grub/grub.nix b/nixos/modules/system/boot/loader/grub/grub.nix index ef6ff71ed778..a3b09223cbb8 100644 --- a/nixos/modules/system/boot/loader/grub/grub.nix +++ b/nixos/modules/system/boot/loader/grub/grub.nix @@ -1,6 +1,6 @@ -{ config, pkgs, ... }: +{ config, lib, pkgs, ... }: -with pkgs.lib; +with lib; let @@ -133,11 +133,8 @@ in chainloader (hd0,1)+1 # GRUB 2 example - menuentry "Windows7" { - title Windows7 - insmod ntfs - set root='(hd1,1)' - chainloader +1 + menuentry "Windows 7" { + chainloader (hd0,4)+1 } ''; description = '' diff --git a/nixos/modules/system/boot/loader/grub/memtest.nix b/nixos/modules/system/boot/loader/grub/memtest.nix index 80c1a160cfde..94e5a14174b0 100644 --- a/nixos/modules/system/boot/loader/grub/memtest.nix +++ b/nixos/modules/system/boot/loader/grub/memtest.nix @@ -1,33 +1,87 @@ # This module adds Memtest86+ to the GRUB boot menu. -{ config, pkgs, ... }: +{ config, lib, pkgs, ... }: -with pkgs.lib; +with lib; let memtest86 = pkgs.memtest86plus; + cfg = config.boot.loader.grub.memtest86; in { options = { - boot.loader.grub.memtest86 = mkOption { - default = false; - type = types.bool; - description = '' - Make Memtest86+, a memory testing program, available from the - GRUB boot menu. - ''; + boot.loader.grub.memtest86 = { + + enable = mkOption { + default = false; + type = types.bool; + description = '' + Make Memtest86+, a memory testing program, available from the + GRUB boot menu. + ''; + }; + + 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 = mkIf config.boot.loader.grub.memtest86 { + config = mkIf cfg.enable { boot.loader.grub.extraEntries = if config.boot.loader.grub.version == 2 then '' menuentry "Memtest86+" { - linux16 @bootRoot@/memtest.bin + linux16 @bootRoot@/memtest.bin ${toString cfg.params} } '' else diff --git a/nixos/modules/system/boot/loader/gummiboot/gummiboot-builder.py b/nixos/modules/system/boot/loader/gummiboot/gummiboot-builder.py index 9ea224b51f63..db73544181b6 100644 --- a/nixos/modules/system/boot/loader/gummiboot/gummiboot-builder.py +++ b/nixos/modules/system/boot/loader/gummiboot/gummiboot-builder.py @@ -9,7 +9,6 @@ import tempfile import errno def copy_if_not_exists(source, dest): - known_paths.append(dest) if not os.path.exists(dest): shutil.copyfile(source, dest) @@ -38,12 +37,13 @@ def write_loader_conf(generation): print >> f, "default nixos-generation-%d" % (generation) os.rename("@efiSysMountPoint@/loader/loader.conf.tmp", "@efiSysMountPoint@/loader/loader.conf") -def copy_from_profile(generation, name): +def copy_from_profile(generation, name, dry_run=False): store_file_path = os.readlink("%s/%s" % (system_dir(generation), name)) suffix = os.path.basename(store_file_path) store_dir = os.path.basename(os.path.dirname(store_file_path)) efi_file_path = "/efi/nixos/%s-%s.efi" % (store_dir, suffix) - copy_if_not_exists(store_file_path, "@efiSysMountPoint@%s" % (efi_file_path)) + if not dry_run: + copy_if_not_exists(store_file_path, "@efiSysMountPoint@%s" % (efi_file_path)) return efi_file_path def add_entry(generation): @@ -72,6 +72,10 @@ def get_generations(profile): def remove_old_entries(gens): slice_start = len("@efiSysMountPoint@/loader/entries/nixos-generation-") slice_end = -1 * len(".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: gen = int(path[slice_start:slice_end]) @@ -94,7 +98,6 @@ if os.getenv("NIXOS_INSTALL_GRUB") == "1": else: subprocess.check_call(["@gummiboot@/bin/gummiboot", "--path=@efiSysMountPoint@", "--no-variables", "install"]) -known_paths = [] mkdir_p("@efiSysMountPoint@/efi/nixos") mkdir_p("@efiSysMountPoint@/loader/entries") try: @@ -106,9 +109,8 @@ except IOError as e: machine_id = None gens = get_generations("system") +remove_old_entries(gens) for gen in gens: add_entry(gen) if os.readlink(system_dir(gen)) == args.default_config: write_loader_conf(gen) - -remove_old_entries(gens) diff --git a/nixos/modules/system/boot/loader/gummiboot/gummiboot.nix b/nixos/modules/system/boot/loader/gummiboot/gummiboot.nix index 7edc30776379..19c613a7c94c 100644 --- a/nixos/modules/system/boot/loader/gummiboot/gummiboot.nix +++ b/nixos/modules/system/boot/loader/gummiboot/gummiboot.nix @@ -1,6 +1,6 @@ -{ config, pkgs, ... }: +{ config, lib, pkgs, ... }: -with pkgs.lib; +with lib; let cfg = config.boot.loader.gummiboot; diff --git a/nixos/modules/system/boot/loader/init-script/init-script.nix b/nixos/modules/system/boot/loader/init-script/init-script.nix index 4b0fcd85b4b5..3b33d42b4ae4 100644 --- a/nixos/modules/system/boot/loader/init-script/init-script.nix +++ b/nixos/modules/system/boot/loader/init-script/init-script.nix @@ -1,6 +1,6 @@ -{ config, pkgs, ... }: +{ config, lib, pkgs, ... }: -with pkgs.lib; +with lib; let diff --git a/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix index 5bc856c3df0b..d3f32418a64c 100644 --- a/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix +++ b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix @@ -1,6 +1,6 @@ -{ config, pkgs, ... }: +{ config, lib, pkgs, ... }: -with pkgs.lib; +with lib; let diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix index ba357f5d2de3..c923cc49c449 100644 --- a/nixos/modules/system/boot/luksroot.nix +++ b/nixos/modules/system/boot/luksroot.nix @@ -1,11 +1,11 @@ -{ config, pkgs, ... }: +{ config, lib, pkgs, ... }: -with pkgs.lib; +with lib; let luks = config.boot.initrd.luks; - openCommand = { name, device, keyFile, keyFileSize, allowDiscards, ... }: '' + openCommand = { name, device, keyFile, keyFileSize, allowDiscards, yubikey, ... }: '' # Wait for luksRoot to appear, e.g. if on a usb drive. # XXX: copied and adapted from stage-1-init.sh - should be # available as a function. @@ -31,9 +31,161 @@ let fi ''} + open_normally() { + cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} \ + ${optionalString (keyFile != null) "--key-file=${keyFile} ${optionalString (keyFileSize != null) "--keyfile-size=${toString keyFileSize}"}"} + } + + ${optionalString (luks.yubikeySupport && (yubikey != null)) '' + + rbtohex() { + ( od -An -vtx1 | tr -d ' \n' ) + } + + hextorb() { + ( tr '[:lower:]' '[:upper:]' | sed -e 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI' | xargs printf ) + } + + open_yubikey() { + + # Make all of these local to this function + # to prevent their values being leaked + local salt + local iterations + local k_user + local challenge + local response + local k_luks + local opened + local new_salt + local new_iterations + local new_challenge + local new_response + local new_k_luks + + mkdir -p ${yubikey.storage.mountPoint} + mount -t ${yubikey.storage.fsType} ${toString yubikey.storage.device} ${yubikey.storage.mountPoint} + + salt="$(cat ${yubikey.storage.mountPoint}${yubikey.storage.path} | sed -n 1p | tr -d '\n')" + iterations="$(cat ${yubikey.storage.mountPoint}${yubikey.storage.path} | sed -n 2p | tr -d '\n')" + challenge="$(echo -n $salt | openssl-wrap dgst -binary -sha512 | rbtohex)" + response="$(ykchalresp -${toString yubikey.slot} -x $challenge 2>/dev/null)" + + for try in $(seq 3); do + + ${optionalString yubikey.twoFactor '' + echo -n "Enter two-factor passphrase: " + read -s k_user + echo + ''} + + if [ ! -z "$k_user" ]; then + k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString yubikey.keyLength} $iterations $response | rbtohex)" + else + k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $iterations $response | rbtohex)" + fi + + echo -n "$k_luks" | hextorb | cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} --key-file=- + + if [ $? == "0" ]; then + opened=true + break + else + opened=false + echo "Authentication failed!" + fi + done + + if [ "$opened" == false ]; then + umount ${yubikey.storage.mountPoint} + echo "Maximum authentication errors reached" + exit 1 + fi + + echo -n "Gathering entropy for new salt (please enter random keys to generate entropy if this blocks for long)..." + for i in $(seq ${toString yubikey.saltLength}); do + byte="$(dd if=/dev/random bs=1 count=1 2>/dev/null | rbtohex)"; + new_salt="$new_salt$byte"; + echo -n . + done; + echo "ok" + + new_iterations="$iterations" + ${optionalString (yubikey.iterationStep > 0) '' + new_iterations="$(($new_iterations + ${toString yubikey.iterationStep}))" + ''} + + new_challenge="$(echo -n $new_salt | openssl-wrap dgst -binary -sha512 | rbtohex)" + + new_response="$(ykchalresp -${toString yubikey.slot} -x $new_challenge 2>/dev/null)" + + if [ ! -z "$k_user" ]; then + new_k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString yubikey.keyLength} $new_iterations $new_response | rbtohex)" + else + new_k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $new_iterations $new_response | rbtohex)" + fi + + mkdir -p ${yubikey.ramfsMountPoint} + # A ramfs is used here to ensure that the file used to update + # the key slot with cryptsetup will never get swapped out. + # Warning: Do NOT replace with tmpfs! + mount -t ramfs none ${yubikey.ramfsMountPoint} + + echo -n "$new_k_luks" | hextorb > ${yubikey.ramfsMountPoint}/new_key + echo -n "$k_luks" | hextorb | cryptsetup luksChangeKey ${device} --key-file=- ${yubikey.ramfsMountPoint}/new_key + + if [ $? == "0" ]; then + echo -ne "$new_salt\n$new_iterations" > ${yubikey.storage.mountPoint}${yubikey.storage.path} + else + echo "Warning: Could not update LUKS key, current challenge persists!" + fi + + rm -f ${yubikey.ramfsMountPoint}/new_key + umount ${yubikey.ramfsMountPoint} + rm -rf ${yubikey.ramfsMountPoint} + + umount ${yubikey.storage.mountPoint} + } + + ${optionalString (yubikey.gracePeriod > 0) '' + echo -n "Waiting ${toString yubikey.gracePeriod} seconds as grace..." + for i in $(seq ${toString yubikey.gracePeriod}); do + sleep 1 + echo -n . + done + echo "ok" + ''} + + yubikey_missing=true + ykinfo -v 1>/dev/null 2>&1 + if [ $? != "0" ]; then + echo -n "waiting 10 seconds for yubikey to appear..." + for try in $(seq 10); do + sleep 1 + ykinfo -v 1>/dev/null 2>&1 + if [ $? == "0" ]; then + yubikey_missing=false + break + fi + echo -n . + done + echo "ok" + else + yubikey_missing=false + fi + + if [ "$yubikey_missing" == true ]; then + echo "no yubikey found, falling back to non-yubikey open procedure" + open_normally + else + open_yubikey + fi + ''} + # open luksRoot and scan for logical volumes - cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} \ - ${optionalString (keyFile != null) "--key-file=${keyFile} ${optionalString (keyFileSize != null) "--keyfile-size=${toString keyFileSize}"}"} + ${optionalString ((!luks.yubikeySupport) || (yubikey == null)) '' + open_normally + ''} ''; isPreLVM = f: f.preLVM; @@ -139,10 +291,108 @@ in ''; }; - }; + yubikey = mkOption { + default = null; + type = types.nullOr types.optionSet; + description = '' + The options to use for this LUKS device in Yubikey-PBA. + If null (the default), Yubikey-PBA will be disabled for this device. + ''; + options = { + twoFactor = mkOption { + default = true; + type = types.bool; + description = "Whether to use a passphrase and a Yubikey (true), or only a Yubikey (false)"; + }; + + slot = mkOption { + default = 2; + type = types.int; + description = "Which slot on the Yubikey to challenge"; + }; + + saltLength = mkOption { + default = 16; + type = types.int; + description = "Length of the new salt in byte (64 is the effective maximum)"; + }; + + keyLength = mkOption { + default = 64; + type = types.int; + description = "Length of the LUKS slot key derived with PBKDF2 in byte"; + }; + + iterationStep = mkOption { + default = 0; + type = types.int; + description = "How much the iteration count for PBKDF2 is increased at each successful authentication"; + }; + + gracePeriod = mkOption { + default = 2; + type = types.int; + description = "Time in seconds to wait before attempting to find the Yubikey"; + }; + + ramfsMountPoint = mkOption { + default = "/crypt-ramfs"; + type = types.string; + description = "Path where the ramfs used to update the LUKS key will be mounted in stage-1"; + }; + + storage = mkOption { + type = types.optionSet; + description = "Options related to the storing the salt"; + + options = { + device = mkOption { + default = /dev/sda1; + type = types.path; + description = '' + An unencrypted device that will temporarily be mounted in stage-1. + Must contain the current salt to create the challenge for this LUKS device. + ''; + }; + + fsType = mkOption { + default = "vfat"; + type = types.string; + description = "The filesystem of the unencrypted device"; + }; + + mountPoint = mkOption { + default = "/crypt-storage"; + type = types.string; + description = "Path where the unencrypted device will be mounted in stage-1"; + }; + + path = mkOption { + default = "/crypt-storage/default"; + type = types.string; + description = '' + Absolute path of the salt on the unencrypted device with + that device's root directory as "/". + ''; + }; + }; + }; + }; + }; + + }; }; + boot.initrd.luks.yubikeySupport = mkOption { + default = false; + type = types.bool; + description = '' + Enables support for authenticating with a Yubikey on LUKS devices. + See the NixOS wiki for information on how to properly setup a LUKS device + and a Yubikey to work with this feature. + ''; + }; }; config = mkIf (luks.devices != []) { @@ -157,15 +407,48 @@ in # copy the cryptsetup binary and it's dependencies boot.initrd.extraUtilsCommands = '' cp -pdv ${pkgs.cryptsetup}/sbin/cryptsetup $out/bin - # XXX: do we have a function that does this? - for lib in $(ldd $out/bin/cryptsetup |grep '=>' |grep /nix/store/ |cut -d' ' -f3); do - cp -pdvn $lib $out/lib - cp -pvn $(readlink -f $lib) $out/lib - done + + cp -pdv ${pkgs.libgcrypt}/lib/libgcrypt*.so.* $out/lib + cp -pdv ${pkgs.libgpgerror}/lib/libgpg-error*.so.* $out/lib + cp -pdv ${pkgs.cryptsetup}/lib/libcryptsetup*.so.* $out/lib + cp -pdv ${pkgs.popt}/lib/libpopt*.so.* $out/lib + + ${optionalString luks.yubikeySupport '' + cp -pdv ${pkgs.ykpers}/bin/ykchalresp $out/bin + cp -pdv ${pkgs.ykpers}/bin/ykinfo $out/bin + cp -pdv ${pkgs.openssl}/bin/openssl $out/bin + + cc -O3 -I${pkgs.openssl}/include -L${pkgs.openssl}/lib ${./pbkdf2-sha512.c} -o $out/bin/pbkdf2-sha512 -lcrypto + strip -s $out/bin/pbkdf2-sha512 + + cp -pdv ${pkgs.libusb1}/lib/libusb*.so.* $out/lib + cp -pdv ${pkgs.ykpers}/lib/libykpers*.so.* $out/lib + cp -pdv ${pkgs.libyubikey}/lib/libyubikey*.so.* $out/lib + cp -pdv ${pkgs.openssl}/lib/libssl*.so.* $out/lib + cp -pdv ${pkgs.openssl}/lib/libcrypto*.so.* $out/lib + + mkdir -p $out/etc/ssl + cp -pdv ${pkgs.openssl}/etc/ssl/openssl.cnf $out/etc/ssl + + cat > $out/bin/openssl-wrap <<EOF +#!$out/bin/sh +EOF + chmod +x $out/bin/openssl-wrap + ''} ''; boot.initrd.extraUtilsCommandsTest = '' $out/bin/cryptsetup --version + ${optionalString luks.yubikeySupport '' + $out/bin/ykchalresp -V + $out/bin/ykinfo -V + cat > $out/bin/openssl-wrap <<EOF +#!$out/bin/sh +export OPENSSL_CONF=$out/etc/ssl/openssl.cnf +$out/bin/openssl "\$@" +EOF + $out/bin/openssl-wrap version + ''} ''; boot.initrd.preLVMCommands = concatMapStrings openCommand preLVM; diff --git a/nixos/modules/system/boot/modprobe.nix b/nixos/modules/system/boot/modprobe.nix index 027a7ac99d51..7b214cd1e1f5 100644 --- a/nixos/modules/system/boot/modprobe.nix +++ b/nixos/modules/system/boot/modprobe.nix @@ -1,6 +1,6 @@ -{ config, pkgs, ... }: +{ config, lib, pkgs, ... }: -with pkgs.lib; +with lib; { @@ -68,7 +68,10 @@ with pkgs.lib; config = mkIf (!config.boot.isContainer) { - environment.etc = singleton + environment.etc = [ + { source = "${pkgs.kmod-blacklist-ubuntu}/modprobe.conf"; + target = "modprobe.d/ubuntu.conf"; + } { source = pkgs.writeText "modprobe.conf" '' ${flip concatMapStrings config.boot.blacklistedKernelModules (name: '' @@ -77,26 +80,11 @@ with pkgs.lib; ${config.boot.extraModprobeConfig} ''; target = "modprobe.d/nixos.conf"; - }; + } + ]; environment.systemPackages = [ config.system.sbin.modprobe pkgs.kmod ]; - boot.blacklistedKernelModules = - [ # This module is for debugging and generates gigantic amounts - # of log output, so it should never be loaded automatically. - "evbug" - - # This module causes ALSA to occassionally select the wrong - # default sound device, and is little more than an annoyance - # on modern machines. - "snd_pcsp" - - # The cirrusfb module prevents X11 from starting. FIXME: - # Ubuntu blacklists all framebuffer devices because they're - # "buggy" and cause suspend problems. Maybe we should too? - "cirrusfb" - ]; - system.activationScripts.modprobe = '' # Allow the kernel to find our wrapped modprobe (which searches diff --git a/nixos/modules/system/boot/pbkdf2-sha512.c b/nixos/modules/system/boot/pbkdf2-sha512.c new file mode 100644 index 000000000000..b40c383ac023 --- /dev/null +++ b/nixos/modules/system/boot/pbkdf2-sha512.c @@ -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; +} \ No newline at end of file diff --git a/nixos/modules/system/boot/shutdown.nix b/nixos/modules/system/boot/shutdown.nix index 44cadcd64a76..68bc936c5b0b 100644 --- a/nixos/modules/system/boot/shutdown.nix +++ b/nixos/modules/system/boot/shutdown.nix @@ -1,6 +1,6 @@ -{ config, pkgs, ... }: +{ config, lib, pkgs, ... }: -with pkgs.lib; +with lib; { diff --git a/nixos/modules/system/boot/stage-1-init.sh b/nixos/modules/system/boot/stage-1-init.sh index 1f65026b5def..216937a619b1 100644 --- a/nixos/modules/system/boot/stage-1-init.sh +++ b/nixos/modules/system/boot/stage-1-init.sh @@ -14,7 +14,7 @@ fail() { # in an interactive shell. cat <<EOF -An error occured in stage 1 of the boot process, which must mount the +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: @@ -139,8 +139,6 @@ mkdir -p /dev/.mdadm systemd-udevd --daemon udevadm trigger --action=add udevadm settle || true -modprobe scsi_wait_scan || true -udevadm settle || true # Load boot-time keymap before any LVM/LUKS initialization @@ -320,6 +318,10 @@ while read -u 3 mountPoint; do echo -n "waiting for device $device to appear..." for try in $(seq 1 20); 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 $device; then break; fi echo -n "." done diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix index 8ed3aecb6911..c38d33c45d6e 100644 --- a/nixos/modules/system/boot/stage-1.nix +++ b/nixos/modules/system/boot/stage-1.nix @@ -3,9 +3,9 @@ # 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, pkgs, ... }: +{ config, lib, pkgs, ... }: -with pkgs.lib; +with lib; let @@ -74,7 +74,7 @@ let cp -v ${pkgs.lvm2}/sbin/dmsetup $out/bin/dmsetup cp -v ${pkgs.lvm2}/sbin/lvm $out/bin/lvm cp -v ${pkgs.lvm2}/lib/libdevmapper.so.*.* $out/lib - cp -v ${pkgs.systemd}/lib/libsystemd-daemon.so.* $out/lib + cp -v ${pkgs.systemd}/lib/libsystemd.so.* $out/lib # Add RAID mdadm tool. cp -v ${pkgs.mdadm}/sbin/mdadm $out/bin/mdadm diff --git a/nixos/modules/system/boot/stage-2-init.sh b/nixos/modules/system/boot/stage-2-init.sh index 2fadd3de1f0f..a64c6cdfa191 100644 --- a/nixos/modules/system/boot/stage-2-init.sh +++ b/nixos/modules/system/boot/stage-2-init.sh @@ -82,7 +82,7 @@ done # More special file systems, initialise required directories. mkdir -m 0755 /dev/shm -mount -t tmpfs -o "rw,nosuid,nodev,size=@devShmSize@" tmpfs /dev/shm +mount -t tmpfs -o "rw,nosuid,nodev,size=@devShmSize@" none /dev/shm mkdir -m 0755 -p /dev/pts [ -e /proc/bus/usb ] && mount -t usbfs none /proc/bus/usb # UML doesn't have USB by default mkdir -m 01777 -p /tmp @@ -96,28 +96,14 @@ mkdir -m 0755 -p /etc/nixos # Miscellaneous boot time cleanup. rm -rf /var/run /var/lock -rm -f /etc/resolv.conf -touch /etc/resolv.conf rm -f /etc/{group,passwd,shadow}.lock if test -n "@cleanTmpDir@"; then echo -n "cleaning \`/tmp'..." find /tmp -maxdepth 1 -mindepth 1 -print0 | xargs -0r rm -rf --one-file-system echo " done" -else - # Get rid of ICE locks... - rm -rf /tmp/.ICE-unix fi -# ... and ensure that it's owned by root. -mkdir -m 1777 /tmp/.ICE-unix - -# This is a good time to clean up /nix/var/nix/chroots. Doing an `rm -# -rf' on it isn't safe in general because it can contain bind mounts -# to /nix/store and other places. But after rebooting these are all -# gone, of course. -rm -rf /nix/var/nix/chroots # recreated in activate-configuration.sh - # Also get rid of temporary GC roots. rm -rf /nix/var/nix/gcroots/tmp /nix/var/nix/temproots @@ -131,6 +117,15 @@ if ! mountpoint -q /run; then mount -t tmpfs -o "mode=0755,size=@runSize@" none /run fi +# Create a ramfs on /run/keys to hold secrets that shouldn't be +# written to disk (generally used for NixOps, harmless elsewhere). +if ! mountpoint -q /run/keys; then + rm -rf /run/keys + mkdir -m 0750 /run/keys + chown 0:96 /run/keys + mount -t ramfs none /run/keys +fi + mkdir -m 0755 -p /run/lock @@ -146,12 +141,32 @@ if test -n "$resumeDevice"; then fi +# Use /etc/resolv.conf supplied by systemd-nspawn, if applicable. +if [ -n "@useHostResolvConf@" -a -e /etc/resolv.conf ]; then + cat /etc/resolv.conf | resolvconf -m 1000 -a host +else + touch /etc/resolv.conf +fi + + +# Create /var/setuid-wrappers as a tmpfs. +rm -rf /var/setuid-wrappers +mkdir -m 0755 -p /var/setuid-wrappers +mount -t tmpfs -o "mode=0755" none /var/setuid-wrappers + + # Run the script that performs all configuration activation that does # not have to be done at boot time. echo "running activation script..." $systemConfig/activate +# Restore the system time from the hardware clock. We do this after +# running the activation script to be sure that /etc/localtime points +# at the current time zone. +hwclock --hctosys + + # Record the boot configuration. ln -sfn "$systemConfig" /run/booted-system diff --git a/nixos/modules/system/boot/stage-2.nix b/nixos/modules/system/boot/stage-2.nix index aa0d7e0c138b..f53c3b8b8e70 100644 --- a/nixos/modules/system/boot/stage-2.nix +++ b/nixos/modules/system/boot/stage-2.nix @@ -1,6 +1,6 @@ -{ config, pkgs, ... }: +{ config, lib, pkgs, ... }: -with pkgs.lib; +with lib; let @@ -19,11 +19,13 @@ let isExecutable = true; inherit (config.boot) devShmSize runSize cleanTmpDir; inherit (config.nix) readOnlyStore; + inherit (config.networking) useHostResolvConf; ttyGid = config.ids.gids.tty; path = [ pkgs.coreutils pkgs.utillinux pkgs.sysvtools + pkgs.openresolv ] ++ (optional config.boot.cleanTmpDir pkgs.findutils) ++ optional config.nix.readOnlyStore readonlyMountpoint; postBootCommands = pkgs.writeText "local-cmds" @@ -79,6 +81,7 @@ in ''; }; + # FIXME: should replace this with something that uses systemd-tmpfiles. cleanTmpDir = mkOption { type = types.bool; default = false; diff --git a/nixos/modules/system/boot/systemd-unit-options.nix b/nixos/modules/system/boot/systemd-unit-options.nix index c0518599f17a..a6183c47eb1b 100644 --- a/nixos/modules/system/boot/systemd-unit-options.nix +++ b/nixos/modules/system/boot/systemd-unit-options.nix @@ -1,6 +1,6 @@ -{ config, pkgs }: +{ config, lib }: -with pkgs.lib; +with lib; let @@ -28,7 +28,7 @@ let in rec { - unitOptions = { + sharedOptions = { enable = mkOption { default = true; @@ -41,6 +41,37 @@ in rec { ''; }; + requiredBy = mkOption { + default = []; + type = types.listOf types.string; + description = "Units that require (i.e. depend on and need to go down with) this unit."; + }; + + wantedBy = mkOption { + default = []; + type = types.listOf types.string; + description = "Units that want (i.e. depend on) this unit."; + }; + + }; + + concreteUnitOptions = sharedOptions // { + + text = mkOption { + type = types.nullOr types.str; + default = null; + description = "Text of this systemd unit."; + }; + + unit = mkOption { + internal = true; + description = "The generated unit."; + }; + + }; + + commonUnitOptions = sharedOptions // { + description = mkOption { default = ""; type = types.str; @@ -109,18 +140,6 @@ in rec { ''; }; - requiredBy = mkOption { - default = []; - type = types.listOf types.str; - description = "Units that require (i.e. depend on and need to go down with) this unit."; - }; - - wantedBy = mkOption { - default = []; - type = types.listOf types.str; - description = "Units that want (i.e. depend on) this unit."; - }; - unitConfig = mkOption { default = {}; example = { RequiresMountsFor = "/data"; }; @@ -135,6 +154,7 @@ in rec { restartTriggers = mkOption { default = []; + type = types.listOf types.unspecified; description = '' An arbitrary list of items such as derivations. If any item in the list changes between reconfigurations, the service will @@ -145,7 +165,7 @@ in rec { }; - serviceOptions = unitOptions // { + serviceOptions = commonUnitOptions // { environment = mkOption { default = {}; @@ -236,6 +256,17 @@ in rec { ''; }; + reloadIfChanged = mkOption { + type = types.bool; + default = false; + description = '' + Whether the service should be reloaded during a NixOS + configuration switch if its definition has changed. If + enabled, the value of <option>restartIfChanged</option> is + ignored. + ''; + }; + stopIfChanged = mkOption { type = types.bool; default = true; @@ -268,7 +299,7 @@ in rec { }; - socketOptions = unitOptions // { + socketOptions = commonUnitOptions // { listenStreams = mkOption { default = []; @@ -295,7 +326,7 @@ in rec { }; - timerOptions = unitOptions // { + timerOptions = commonUnitOptions // { timerConfig = mkOption { default = {}; @@ -314,7 +345,24 @@ in rec { }; - mountOptions = unitOptions // { + pathOptions = commonUnitOptions // { + + pathConfig = mkOption { + default = {}; + example = { PathChanged = "/some/path"; Unit = "changedpath.service"; }; + type = types.attrsOf unitOption; + description = '' + Each attribute in this set specifies an option in the + <literal>[Path]</literal> section of the unit. See + <citerefentry><refentrytitle>systemd.path</refentrytitle> + <manvolnum>5</manvolnum></citerefentry> for details. + ''; + }; + + }; + + + mountOptions = commonUnitOptions // { what = mkOption { example = "/dev/sda1"; @@ -358,7 +406,7 @@ in rec { }; }; - automountOptions = unitOptions // { + automountOptions = commonUnitOptions // { where = mkOption { example = "/mnt"; @@ -382,4 +430,6 @@ in rec { }; }; + targetOptions = commonUnitOptions; + } diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix index b575deb24b7b..6c6adab66e7c 100644 --- a/nixos/modules/system/boot/systemd.nix +++ b/nixos/modules/system/boot/systemd.nix @@ -1,8 +1,8 @@ -{ config, pkgs, utils, ... }: +{ config, lib, pkgs, utils, ... }: -with pkgs.lib; +with lib; with utils; -with import ./systemd-unit-options.nix { inherit config pkgs; }; +with import ./systemd-unit-options.nix { inherit config lib; }; let @@ -11,29 +11,26 @@ let systemd = cfg.package; makeUnit = name: unit: - pkgs.runCommand "unit" { preferLocalBuild = true; inherit (unit) text; } - ((if !unit.enable then '' - mkdir -p $out - ln -s /dev/null $out/${name} - '' else if unit.linkTarget != null then '' - mkdir -p $out - ln -s ${unit.linkTarget} $out/${name} - '' else if unit.text != null then '' - mkdir -p $out - echo -n "$text" > $out/${name} - '' else "") + optionalString (unit.extraConfig != {}) '' - mkdir -p $out/${name}.d - ${concatStringsSep "\n" (mapAttrsToList (n: v: "echo -n \"${v}\" > $out/${name}.d/${n}") unit.extraConfig)} - ''); - - upstreamUnits = + if unit.enable then + pkgs.runCommand "unit" { preferLocalBuild = true; inherit (unit) text; } + '' + mkdir -p $out + echo -n "$text" > $out/${name} + '' + else + pkgs.runCommand "unit" { preferLocalBuild = true; } + '' + mkdir -p $out + ln -s /dev/null $out/${name} + ''; + + upstreamSystemUnits = [ # Targets. "basic.target" "sysinit.target" "sockets.target" "graphical.target" "multi-user.target" - "getty.target" "network.target" "network-online.target" "nss-lookup.target" @@ -43,6 +40,7 @@ let "sigpwr.target" "timers.target" "paths.target" + "rpcbind.target" # Rescue mode. "rescue.target" @@ -55,6 +53,13 @@ let "systemd-udev-settle.service" "systemd-udev-trigger.service" + # Consoles. + "getty.target" + "getty@.service" + "serial-getty@.service" + "container-getty@.service" + "systemd-vconsole-setup.service" + # Hardware (started by udev when a relevant device is plugged in). "sound.target" "bluetooth.target" @@ -67,12 +72,15 @@ let #"systemd-vconsole-setup.service" "systemd-user-sessions.service" "dbus-org.freedesktop.login1.service" + "dbus-org.freedesktop.machine1.service" "user@.service" # Journal. "systemd-journald.socket" "systemd-journald.service" "systemd-journal-flush.service" + "systemd-journal-gatewayd.socket" + "systemd-journal-gatewayd.service" "syslog.socket" # SysV init compatibility. @@ -80,7 +88,8 @@ let "systemd-initctl.service" # Kernel module loading. - #"systemd-modules-load.service" + "systemd-modules-load.service" + "kmod-static-nodes.service" # Filesystems. "systemd-fsck@.service" @@ -93,10 +102,16 @@ let "swap.target" "dev-hugepages.mount" "dev-mqueue.mount" + "proc-sys-fs-binfmt_misc.mount" "sys-fs-fuse-connections.mount" "sys-kernel-config.mount" "sys-kernel-debug.mount" + # Maintaining state across reboots. + "systemd-random-seed.service" + "systemd-backlight@.service" + "systemd-rfkill@.service" + # Hibernate / suspend. "hibernate.target" "suspend.target" @@ -121,12 +136,30 @@ let "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" + "-.slice" + "system.slice" + "user.slice" + "machine.slice" + "systemd-machined.service" + + # Temporary file creation / cleanup. + "systemd-tmpfiles-clean.service" + "systemd-tmpfiles-clean.timer" + "systemd-tmpfiles-setup.service" + "systemd-tmpfiles-setup-dev.service" + + # Misc. + "systemd-sysctl.service" ] ++ optionals cfg.enableEmergencyMode [ @@ -134,16 +167,26 @@ let "emergency.service" ]; - upstreamWants = + upstreamSystemWants = [ #"basic.target.wants" "sysinit.target.wants" "sockets.target.wants" "local-fs.target.wants" "multi-user.target.wants" - "shutdown.target.wants" "timers.target.wants" ]; + upstreamUserUnits = + [ "basic.target" + "default.target" + "exit.target" + "paths.target" + "shutdown.target" + "sockets.target" + "systemd-exit.service" + "timers.target" + ]; + makeJobScript = name: text: let x = pkgs.writeTextFile { name = "unit-script"; executable = true; destination = "/bin/${name}"; inherit text; }; in "${x}/bin/${name}"; @@ -151,15 +194,23 @@ let unitConfig = { name, config, ... }: { config = { unitConfig = - { Requires = concatStringsSep " " config.requires; - Wants = concatStringsSep " " config.wants; - After = concatStringsSep " " config.after; - Before = concatStringsSep " " config.before; - BindsTo = concatStringsSep " " config.bindsTo; - PartOf = concatStringsSep " " config.partOf; - Conflicts = concatStringsSep " " config.conflicts; - "X-Restart-Triggers" = toString config.restartTriggers; - } // optionalAttrs (config.description != "") { + optionalAttrs (config.requires != []) + { Requires = toString config.requires; } + // optionalAttrs (config.wants != []) + { Wants = toString config.wants; } + // optionalAttrs (config.after != []) + { After = toString config.after; } + // optionalAttrs (config.before != []) + { Before = toString config.before; } + // optionalAttrs (config.bindsTo != []) + { BindsTo = toString config.bindsTo; } + // optionalAttrs (config.partOf != []) + { PartOf = toString config.partOf; } + // optionalAttrs (config.conflicts != []) + { Conflicts = toString config.conflicts; } + // optionalAttrs (config.restartTriggers != []) + { X-Restart-Triggers = toString config.restartTriggers; } + // optionalAttrs (config.description != "") { Description = config.description; }; }; @@ -244,6 +295,11 @@ let (if isList value then value else [value])) as)); + commonUnitText = def: '' + [Unit] + ${attrsToSection def.unitConfig} + ''; + targetToUnit = name: def: { inherit (def) wantedBy requiredBy enable; text = @@ -255,15 +311,16 @@ let serviceToUnit = name: def: { inherit (def) wantedBy requiredBy enable; - text = + text = commonUnitText def + '' - [Unit] - ${attrsToSection def.unitConfig} - [Service] ${let env = cfg.globalEnvironment // def.environment; in concatMapStrings (n: "Environment=\"${n}=${getAttr n env}\"\n") (attrNames env)} - ${optionalString (!def.restartIfChanged) "X-RestartIfChanged=false"} + ${if def.reloadIfChanged then '' + X-ReloadIfChanged=true + '' else if !def.restartIfChanged then '' + X-RestartIfChanged=false + '' else ""} ${optionalString (!def.stopIfChanged) "X-StopIfChanged=false"} ${attrsToSection def.serviceConfig} ''; @@ -271,11 +328,8 @@ let socketToUnit = name: def: { inherit (def) wantedBy requiredBy enable; - text = + text = commonUnitText def + '' - [Unit] - ${attrsToSection def.unitConfig} - [Socket] ${attrsToSection def.socketConfig} ${concatStringsSep "\n" (map (s: "ListenStream=${s}") def.listenStreams)} @@ -284,23 +338,26 @@ let timerToUnit = name: def: { inherit (def) wantedBy requiredBy enable; - text = + text = commonUnitText def + '' - [Unit] - ${attrsToSection def.unitConfig} - [Timer] ${attrsToSection def.timerConfig} ''; }; - mountToUnit = name: def: + pathToUnit = name: def: { inherit (def) wantedBy requiredBy enable; - text = + text = commonUnitText def + '' - [Unit] - ${attrsToSection def.unitConfig} + [Path] + ${attrsToSection def.pathConfig} + ''; + }; + mountToUnit = name: def: + { inherit (def) wantedBy requiredBy enable; + text = commonUnitText def + + '' [Mount] ${attrsToSection def.mountConfig} ''; @@ -308,70 +365,99 @@ let automountToUnit = name: def: { inherit (def) wantedBy requiredBy enable; - text = + text = commonUnitText def + '' - [Unit] - ${attrsToSection def.unitConfig} - [Automount] ${attrsToSection def.automountConfig} ''; }; - units = pkgs.runCommand "units" { preferLocalBuild = true; } - '' + generateUnits = type: units: upstreamUnits: upstreamWants: + pkgs.runCommand "${type}-units" { preferLocalBuild = true; } '' mkdir -p $out + + # Copy the upstream systemd units we're interested in. for i in ${toString upstreamUnits}; do - fn=${systemd}/example/systemd/system/$i + fn=${systemd}/example/systemd/${type}/$i if ! [ -e $fn ]; then echo "missing $fn"; false; fi if [ -L $fn ]; then - cp -pd $fn $out/ + target="$(readlink "$fn")" + if [ ''${target:0:3} = ../ ]; then + ln -s "$(readlink -f "$fn")" $out/ + else + cp -pd $fn $out/ + fi else ln -s $fn $out/ fi done + # Copy .wants links, but only those that point to units that + # we're interested in. for i in ${toString upstreamWants}; do - fn=${systemd}/example/systemd/system/$i + fn=${systemd}/example/systemd/${type}/$i if ! [ -e $fn ]; then echo "missing $fn"; false; fi x=$out/$(basename $fn) mkdir $x for i in $fn/*; do y=$x/$(basename $i) cp -pd $i $y - if ! [ -e $y ]; then rm -v $y; fi + if ! [ -e $y ]; then rm $y; fi done done - for i in ${toString (mapAttrsToList (n: v: v.unit) cfg.units)}; do - ln -fs $i/* $out/ + # Symlink all units provided listed in systemd.packages. + for i in ${toString cfg.packages}; do + files=$(echo $i/etc/systemd/${type}/* $i/lib/systemd/${type}/*) + if [ -n "$files" ]; then + ln -s $files $out/ + fi done - for i in ${toString cfg.packages}; do - ln -s $i/etc/systemd/system/* $out/ + # Symlink all units defined by systemd.units. If these are also + # provided by systemd or systemd.packages, then add them as + # <unit-name>.d/overrides.conf, which makes them extend the + # upstream unit. + for i in ${toString (mapAttrsToList (n: v: v.unit) units)}; do + fn=$(basename $i/*) + if [ -e $out/$fn ]; then + if [ "$(readlink -f $i/$fn)" = /dev/null ]; then + ln -sfn /dev/null $out/$fn + else + mkdir $out/$fn.d + ln -s $i/$fn $out/$fn.d/overrides.conf + fi + else + ln -fs $i/$fn $out/ + fi done + # Created .wants and .requires symlinks from the wantedBy and + # requiredBy options. ${concatStrings (mapAttrsToList (name: unit: concatMapStrings (name2: '' mkdir -p $out/'${name2}.wants' ln -sfn '../${name}' $out/'${name2}.wants'/ - '') unit.wantedBy) cfg.units)} + '') unit.wantedBy) units)} ${concatStrings (mapAttrsToList (name: unit: concatMapStrings (name2: '' mkdir -p $out/'${name2}.requires' ln -sfn '../${name}' $out/'${name2}.requires'/ - '') unit.requiredBy) cfg.units)} + '') unit.requiredBy) units)} - ln -s ${cfg.defaultUnit} $out/default.target + ${optionalString (type == "system") '' + # Stupid misc. symlinks. + ln -s ${cfg.defaultUnit} $out/default.target - ln -s rescue.target $out/kbrequest.target + ln -s rescue.target $out/kbrequest.target - mkdir -p $out/getty.target.wants/ - ln -s ../autovt@tty1.service $out/getty.target.wants/ + mkdir -p $out/getty.target.wants/ + ln -s ../autovt@tty1.service $out/getty.target.wants/ - ln -s ../local-fs.target ../remote-fs.target ../network.target ../nss-lookup.target \ - ../nss-user-lookup.target ../swap.target $out/multi-user.target.wants/ + ln -s ../local-fs.target ../remote-fs.target ../network.target ../nss-lookup.target \ + ../nss-user-lookup.target ../swap.target $out/multi-user.target.wants/ + ''} ''; # */ in @@ -393,55 +479,9 @@ in default = {}; type = types.attrsOf types.optionSet; options = { name, config, ... }: - { options = { - text = mkOption { - type = types.nullOr types.str; - default = null; - description = "Text of this systemd unit."; - }; - enable = mkOption { - default = true; - type = types.bool; - description = '' - If set to false, this unit will be a symlink to - /dev/null. This is primarily useful to prevent specific - template instances (e.g. <literal>serial-getty@ttyS0</literal>) - from being started. - ''; - }; - requiredBy = mkOption { - default = []; - type = types.listOf types.string; - description = "Units that require (i.e. depend on and need to go down with) this unit."; - }; - wantedBy = mkOption { - default = []; - type = types.listOf types.string; - description = "Units that want (i.e. depend on) this unit."; - }; - unit = mkOption { - internal = true; - description = "The generated unit."; - }; - linkTarget = mkOption { - default = null; - description = "The file to symlink this target to."; - type = types.nullOr types.path; - }; - extraConfig = mkOption { - default = {}; - example = { "foo@1.conf" = "X-RestartIfChanged=false"; }; - type = types.attrsOf types.lines; - description = '' - Extra files to be appended to the configuration for the unit. - This can be used to override configuration for a unit provided - by systemd or another package, or to override only a single instance - of a template unit. - ''; - }; - }; + { options = concreteUnitOptions; config = { - unit = makeUnit name config; + unit = mkDefault (makeUnit name config); }; }; }; @@ -455,7 +495,7 @@ in systemd.targets = mkOption { default = {}; type = types.attrsOf types.optionSet; - options = [ unitOptions unitConfig ]; + options = [ targetOptions unitConfig ]; description = "Definition of systemd target units."; }; @@ -480,6 +520,13 @@ in description = "Definition of systemd timer units."; }; + systemd.paths = mkOption { + default = {}; + type = types.attrsOf types.optionSet; + options = [ pathOptions unitConfig ]; + description = "Definition of systemd path units."; + }; + systemd.mounts = mkOption { default = []; type = types.listOf types.optionSet; @@ -567,6 +614,14 @@ in ''; }; + services.journald.enableHttpGateway = mkOption { + default = false; + type = types.bool; + description = '' + Whether to enable the HTTP gateway to the journal. + ''; + }; + services.logind.extraConfig = mkOption { default = ""; type = types.lines; @@ -590,6 +645,41 @@ in ''; }; + systemd.tmpfiles.rules = mkOption { + type = types.listOf types.str; + default = []; + example = [ "d /tmp 1777 root root 10d" ]; + description = '' + Rules for creating and cleaning up temporary files + automatically. See + <citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry> + for the exact format. You should not use this option to create + files required by systemd services, since there is no + guarantee that <command>systemd-tmpfiles</command> runs when + the system is reconfigured using + <command>nixos-rebuild</command>. + ''; + }; + + systemd.user.units = mkOption { + description = "Definition of systemd per-user units."; + default = {}; + type = types.attrsOf types.optionSet; + options = { name, config, ... }: + { options = concreteUnitOptions; + config = { + unit = mkDefault (makeUnit name config); + }; + }; + }; + + systemd.user.services = mkOption { + default = {}; + type = types.attrsOf types.optionSet; + options = [ serviceOptions unitConfig serviceConfig ]; + description = "Definition of systemd per-user service units."; + }; + }; @@ -597,11 +687,20 @@ in config = { - system.build.units = units; + assertions = mapAttrsToList (name: service: { + assertion = service.serviceConfig.Type or "" == "oneshot" -> service.serviceConfig.Restart or "no" == "no"; + message = "${name}: Type=oneshot services must have Restart=no"; + }) cfg.services; + + system.build.units = cfg.units; environment.systemPackages = [ systemd ]; - environment.etc."systemd/system".source = units; + environment.etc."systemd/system".source = + generateUnits "system" cfg.units upstreamSystemUnits upstreamSystemWants; + + environment.etc."systemd/user".source = + generateUnits "user" cfg.user.units upstreamUserUnits []; environment.etc."systemd/system.conf".text = '' @@ -645,8 +744,11 @@ in ''; # Target for ‘charon send-keys’ to hook into. + users.extraGroups.keys.gid = config.ids.gids.keys; + systemd.targets.keys = { description = "Security Keys"; + unitConfig.X-StopOnReconfiguration = true; }; systemd.units = @@ -654,6 +756,7 @@ in // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services // mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit n v)) cfg.sockets // mapAttrs' (n: v: nameValuePair "${n}.timer" (timerToUnit n v)) cfg.timers + // mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit n v)) cfg.paths // listToAttrs (map (v: let n = escapeSystemdPath v.where; in nameValuePair "${n}.mount" (mountToUnit n v)) cfg.mounts) @@ -661,6 +764,9 @@ in (v: let n = escapeSystemdPath v.where; in nameValuePair "${n}.automount" (automountToUnit n v)) cfg.automounts); + systemd.user.units = + mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.user.services; + system.requiredKernelConfig = map config.lib.kernelConfig.isEnabled [ "CGROUPS" "AUTOFS4_FS" "DEVTMPFS" ]; @@ -673,6 +779,8 @@ in }; users.extraGroups.systemd-journal.gid = config.ids.gids.systemd-journal; + users.extraUsers.systemd-journal-gateway.uid = config.ids.uids.systemd-journal-gateway; + users.extraGroups.systemd-journal-gateway.gid = config.ids.gids.systemd-journal-gateway; # Generate timer units for all services that have a ‘startAt’ value. systemd.timers = @@ -682,43 +790,25 @@ in }) (filterAttrs (name: service: service.startAt != "") cfg.services); - # FIXME: These are borrowed from upstream systemd. - systemd.services."systemd-update-utmp" = - { description = "Update UTMP about System Reboot/Shutdown"; - wantedBy = [ "sysinit.target" ]; - after = [ "systemd-remount-fs.service" ]; - before = [ "sysinit.target" "shutdown.target" ]; - conflicts = [ "shutdown.target" ]; - unitConfig = { - DefaultDependencies = false; - RequiresMountsFor = "/var/log"; - }; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - ExecStart = "${systemd}/lib/systemd/systemd-update-utmp reboot"; - ExecStop = "${systemd}/lib/systemd/systemd-update-utmp shutdown"; - }; - restartIfChanged = false; - }; + systemd.sockets.systemd-journal-gatewayd.wantedBy = + optional config.services.journald.enableHttpGateway "sockets.target"; - systemd.services."systemd-random-seed" = - { description = "Load/Save Random Seed"; - wantedBy = [ "sysinit.target" "multi-user.target" ]; - after = [ "systemd-remount-fs.service" ]; - before = [ "sysinit.target" "shutdown.target" ]; - conflicts = [ "shutdown.target" ]; - unitConfig = { - DefaultDependencies = false; - RequiresMountsFor = "/var/lib"; - }; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - ExecStart = "${systemd}/lib/systemd/systemd-random-seed load"; - ExecStop = "${systemd}/lib/systemd/systemd-random-seed save"; - }; + # 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; }; + environment.etc."tmpfiles.d/x11.conf".source = "${systemd}/example/tmpfiles.d/x11.conf"; + + environment.etc."tmpfiles.d/nixos.conf".text = + '' + # This file is created automatically and should not be modified. + # Please change the option ‘systemd.tmpfiles.rules’ instead. + ${concatStringsSep "\n" cfg.tmpfiles.rules} + ''; + }; } diff --git a/nixos/modules/system/etc/etc.nix b/nixos/modules/system/etc/etc.nix index a8f0a59b6fa9..22d55a9e246c 100644 --- a/nixos/modules/system/etc/etc.nix +++ b/nixos/modules/system/etc/etc.nix @@ -1,8 +1,8 @@ # Management of static files in /etc. -{ config, pkgs, ... }: +{ config, lib, pkgs, ... }: -with pkgs.lib; +with lib; let @@ -19,6 +19,8 @@ let sources = map (x: x.source) etc'; targets = map (x: x.target) etc'; modes = map (x: x.mode) etc'; + uids = map (x: x.uid) etc'; + gids = map (x: x.gid) etc'; }; in @@ -87,6 +89,24 @@ in ''; }; + uid = mkOption { + default = 0; + type = types.int; + description = '' + UID of created file. Only takes affect when the file is + copied (that is, the mode is not 'symlink'). + ''; + }; + + gid = mkOption { + default = 0; + type = types.int; + description = '' + GID of created file. Only takes affect when the file is + copied (that is, the mode is not 'symlink'). + ''; + }; + }; config = { diff --git a/nixos/modules/system/etc/make-etc.sh b/nixos/modules/system/etc/make-etc.sh index 7cf68db9ddce..60d4ba1301a3 100644 --- a/nixos/modules/system/etc/make-etc.sh +++ b/nixos/modules/system/etc/make-etc.sh @@ -6,6 +6,8 @@ set -f sources_=($sources) targets_=($targets) modes_=($modes) +uids_=($uids) +gids_=($gids) set +f for ((i = 0; i < ${#targets_[@]}; i++)); do @@ -35,6 +37,8 @@ for ((i = 0; i < ${#targets_[@]}; i++)); do if test "${modes_[$i]}" != symlink; then echo "${modes_[$i]}" > $out/etc/$target.mode + echo "${uids_[$i]}" > $out/etc/$target.uid + echo "${gids_[$i]}" > $out/etc/$target.gid fi fi diff --git a/nixos/modules/system/etc/setup-etc.pl b/nixos/modules/system/etc/setup-etc.pl index 4b79dbaab89e..8ba9a370b27a 100644 --- a/nixos/modules/system/etc/setup-etc.pl +++ b/nixos/modules/system/etc/setup-etc.pl @@ -60,7 +60,15 @@ sub link { if ($mode eq "direct-symlink") { atomicSymlink readlink("$static/$fn"), $target or warn; } else { + open UID, "<$_.uid"; + my $uid = <UID>; chomp $uid; + close UID; + open GID, "<$_.gid"; + my $gid = <GID>; chomp $gid; + close GID; + copy "$static/$fn", "$target.tmp" or warn; + chown int($uid), int($gid), "$target.tmp" or warn; chmod oct($mode), "$target.tmp" or warn; rename "$target.tmp", $target or warn; } diff --git a/nixos/modules/system/upstart/upstart.nix b/nixos/modules/system/upstart/upstart.nix index aa5c8dfe64b2..5c0461304072 100644 --- a/nixos/modules/system/upstart/upstart.nix +++ b/nixos/modules/system/upstart/upstart.nix @@ -1,7 +1,7 @@ -{ config, pkgs, ... }: +{ config, lib, pkgs, ... }: -with pkgs.lib; -with import ../boot/systemd-unit-options.nix { inherit config pkgs; }; +with lib; +with import ../boot/systemd-unit-options.nix { inherit config lib; }; let @@ -93,7 +93,7 @@ let if job.daemonType == "fork" || job.daemonType == "daemon" then { Type = "forking"; GuessMainPID = true; } else if job.daemonType == "none" then { } else throw "invalid daemon type `${job.daemonType}'") - // optionalAttrs (!job.task && job.respawn) + // optionalAttrs (!job.task && !(job.script == "" && job.exec == "") && job.respawn) { Restart = "always"; } // optionalAttrs job.task { Type = "oneshot"; RemainAfterExit = false; }; |