diff options
Diffstat (limited to 'nixpkgs/nixos/modules/system/boot/systemd')
18 files changed, 2778 insertions, 0 deletions
diff --git a/nixpkgs/nixos/modules/system/boot/systemd/coredump.nix b/nixpkgs/nixos/modules/system/boot/systemd/coredump.nix new file mode 100644 index 000000000000..ccf5d449b94a --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/coredump.nix @@ -0,0 +1,78 @@ +{ 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`. 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 = mkMerge [ + + (mkIf cfg.enable { + 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 = + # Fix systemd-coredump error caused by truncation of `kernel.core_pattern` + # when the `systemd` derivation name is too long. This works by substituting + # the path to `systemd` with a symlink that has a constant-length path. + # + # See: https://github.com/NixOS/nixpkgs/issues/213408 + pkgs.substitute { + src = "${systemd}/example/sysctl.d/50-coredump.conf"; + substitutions = [ + "--replace-fail" + "${systemd}" + "${pkgs.symlinkJoin { name = "systemd"; paths = [ systemd ]; }}" + ]; + }; + + "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 = {}; + }) + + (mkIf (!cfg.enable) { + boot.kernel.sysctl."kernel.core_pattern" = mkDefault "core"; + }) + + ]; + +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/homed.nix b/nixpkgs/nixos/modules/system/boot/systemd/homed.nix new file mode 100644 index 000000000000..1fd8846616c9 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/homed.nix @@ -0,0 +1,43 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.homed; +in +{ + options.services.homed.enable = lib.mkEnableOption '' + systemd home area/user account manager + ''; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = config.services.nscd.enable; + message = "systemd-homed requires the use of systemd nss module. services.nscd.enable must be set to true,"; + } + ]; + + systemd.additionalUpstreamSystemUnits = [ + "systemd-homed.service" + "systemd-homed-activate.service" + ]; + + # This is mentioned in homed's [Install] section. + # + # While homed appears to work without it, it's probably better + # to follow upstream recommendations. + services.userdbd.enable = lib.mkDefault true; + + systemd.services = { + systemd-homed = { + # These packages are required to manage encrypted volumes + path = config.system.fsPackages; + aliases = [ "dbus-org.freedesktop.home1.service" ]; + wantedBy = [ "multi-user.target" ]; + }; + + systemd-homed-activate = { + wantedBy = [ "systemd-homed.service" ]; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/initrd-secrets.nix b/nixpkgs/nixos/modules/system/boot/systemd/initrd-secrets.nix new file mode 100644 index 000000000000..d375238aa146 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/initrd-secrets.nix @@ -0,0 +1,37 @@ +{ 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" "shutdown.target" ]; + conflicts = [ "shutdown.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 -o -type l); do + mkdir -p "$(dirname "/$secret")" + cp "/.initrd-secrets/$secret" "/$secret" + done + ''; + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + }; + # The script needs this + boot.initrd.systemd.extraBin.find = "${pkgs.findutils}/bin/find"; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/initrd.nix b/nixpkgs/nixos/modules/system/boot/systemd/initrd.nix new file mode 100644 index 000000000000..6107a2594baf --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/initrd.nix @@ -0,0 +1,593 @@ +{ lib, options, 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; + + upstreamUnits = [ + "basic.target" + "ctrl-alt-del.target" + "debug-shell.service" + "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" + "systemd-bsod.service" + ] ++ 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; + }; + + kernel-name = config.boot.kernelPackages.kernel.name or "kernel"; + # 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 = config.system.modulesTree; + firmware = config.hardware.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 -sf '${v}' $out/bin/'${n}'") cfg.extraBin); + }; + + initialRamdisk = pkgs.makeInitrdNG { + name = "initrd-${kernel-name}"; + inherit (config.boot.initrd) compressor compressorArgs prepend; + inherit (cfg) strip; + + 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" // { + description = '' + Whether to enable systemd in initrd. The unit options such as + {option}`boot.initrd.systemd.services` are the same as their + stage 2 counterparts such as {option}`systemd.services`, + except that `restartTriggers` and `reloadTriggers` are not + supported. + ''; + }; + + package = lib.mkOption { + type = lib.types.package; + default = config.systemd.package; + defaultText = lib.literalExpression "config.systemd.package"; + description = '' + The systemd package to use. + ''; + }; + + extraConfig = mkOption { + default = ""; + type = types.lines; + example = "DefaultLimitCORE=infinity"; + description = '' + Extra config options for systemd. See systemd-system.conf(5) man page + for available options. + ''; + }; + + 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 + *not* passed to started units. + ''; + }; + + contents = mkOption { + description = "Set of files that have to be linked into the initrd"; + example = literalExpression '' + { + "/etc/hostname".text = "mymachine"; + } + ''; + 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 = []; + }; + + strip = mkOption { + description = '' + Whether to completely strip executables and libraries copied to the initramfs. + + Setting this to false may save on the order of 30MiB on the + machine building the system (by avoiding a binutils + reference), at the cost of ~1MiB of initramfs size. This puts + this option firmly in the territory of micro-optimisation. + ''; + type = types.bool; + default = true; + }; + + 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 = []; + }; + + root = lib.mkOption { + type = lib.types.enum [ "fstab" "gpt-auto" ]; + default = "fstab"; + example = "gpt-auto"; + description = '' + Controls how systemd will interpret the root FS in initrd. See + {manpage}`kernel-command-line(7)`. NixOS currently does not + allow specifying the root file system itself this + way. Instead, the `fstab` value is used in order to interpret + the root file system specified with the `fileSystems` option. + ''; + }; + + emergencyAccess = mkOption { + type = with types; oneOf [ bool (nullOr (passwdEntry str)) ]; + 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 = []; + description = '' + Packages to include in /bin for the stage 1 emergency shell. + ''; + }; + + additionalUpstreamUnits = mkOption { + default = [ ]; + type = types.listOf types.str; + 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" ]; + description = '' + A list of units to skip when generating system systemd configuration directory. This has + priority over upstream units, {option}`boot.initrd.systemd.units`, and + {option}`boot.initrd.systemd.additionalUpstreamUnits`. 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 = "shallow"; + type = systemdUtils.types.units; + }; + + packages = mkOption { + default = []; + type = types.listOf types.package; + example = literalExpression "[ pkgs.systemd-cryptsetup-generator ]"; + description = "Packages providing systemd units and hooks."; + }; + + targets = mkOption { + default = {}; + visible = "shallow"; + type = systemdUtils.types.initrdTargets; + description = "Definition of systemd target units."; + }; + + services = mkOption { + default = {}; + type = systemdUtils.types.initrdServices; + visible = "shallow"; + description = "Definition of systemd service units."; + }; + + sockets = mkOption { + default = {}; + type = systemdUtils.types.initrdSockets; + visible = "shallow"; + description = "Definition of systemd socket units."; + }; + + timers = mkOption { + default = {}; + type = systemdUtils.types.initrdTimers; + visible = "shallow"; + description = "Definition of systemd timer units."; + }; + + paths = mkOption { + default = {}; + type = systemdUtils.types.initrdPaths; + visible = "shallow"; + description = "Definition of systemd path units."; + }; + + mounts = mkOption { + default = []; + type = systemdUtils.types.initrdMounts; + visible = "shallow"; + 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 = "shallow"; + 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 = "shallow"; + description = "Definition of slice configurations."; + }; + + enableTpm2 = mkOption { + default = true; + type = types.bool; + description = '' + Whether to enable TPM2 support in the initrd. + ''; + }; + }; + + config = mkIf (config.boot.initrd.enable && cfg.enable) { + assertions = [ + { + assertion = cfg.root == "fstab" -> any (fs: fs.mountPoint == "/") (builtins.attrValues config.fileSystems); + message = "The ‘fileSystems’ option does not specify your root file system."; + } + ] ++ map (name: { + assertion = lib.attrByPath name (throw "impossible") config.boot.initrd == ""; + message = '' + systemd stage 1 does not support 'boot.initrd.${lib.concatStringsSep "." name}'. Please + convert it to analogous systemd units in 'boot.initrd.systemd'. + + Definitions: + ${lib.concatMapStringsSep "\n" ({ file, ... }: " - ${file}") (lib.attrByPath name (throw "impossible") options.boot.initrd).definitionsWithLocations} + ''; + }) [ + [ "preFailCommands" ] + [ "preDeviceCommands" ] + [ "preLVMCommands" ] + [ "postDeviceCommands" ] + [ "postResumeCommands" ] + [ "postMountCommands" ] + [ "extraUdevRulesCommands" ] + [ "extraUtilsCommands" ] + [ "extraUtilsCommandsTest" ] + [ "network" "postCommands" ] + ]; + + system.build = { inherit initialRamdisk; }; + + boot.initrd.availableKernelModules = [ + # systemd needs this for some features + "autofs" + # systemd-cryptenroll + ] ++ lib.optional cfg.enableTpm2 "tpm-tis" + ++ lib.optional (cfg.enableTpm2 && !(pkgs.stdenv.hostPlatform.isRiscV64 || pkgs.stdenv.hostPlatform.isArmv7)) "tpm-crb" + ++ lib.optional cfg.package.withEfi "efivarfs"; + + boot.kernelParams = [ + "root=${config.boot.initrd.systemd.root}" + ] ++ lib.optional (config.boot.resumeDevice != "") "resume=${config.boot.resumeDevice}" + # `systemd` mounts root in initrd as read-only unless "rw" is on the kernel command line. + # For NixOS activation to succeed, we need to have root writable in initrd. + ++ lib.optional (config.boot.initrd.systemd.root == "gpt-auto") "rw"; + + boot.initrd.systemd = { + # bashInteractive is easier to use and also required by debug-shell.service + initrdBin = [pkgs.bashInteractive pkgs.coreutils cfg.package.kmod cfg.package]; + extraBin = { + less = "${pkgs.less}/bin/less"; + mount = "${cfg.package.util-linux}/bin/mount"; + umount = "${cfg.package.util-linux}/bin/umount"; + fsck = "${cfg.package.util-linux}/bin/fsck"; + }; + + managerEnvironment.PATH = "/bin:/sbin"; + + contents = { + "/tmp/.keep".text = "systemd requires the /tmp mount point in the initrd cpio archive"; + "/init".source = "${cfg.package}/lib/systemd/systemd"; + "/etc/systemd/system".source = stage1Units; + + "/etc/systemd/system.conf".text = '' + [Manager] + DefaultEnvironment=PATH=/bin:/sbin + ${cfg.extraConfig} + ManagerEnvironment=${lib.concatStringsSep " " (lib.mapAttrsToList (n: v: "${n}=${lib.escapeShellArg v}") cfg.managerEnvironment)} + ''; + + "/lib".source = "${modulesClosure}/lib"; + + "/etc/modules-load.d/nixos.conf".text = concatStringsSep "\n" config.boot.initrd.kernelModules; + + # We can use either ! or * to lock the root account in the + # console, but some software like OpenSSH won't even allow you + # to log in with an SSH key if you use ! so we use * instead + "/etc/shadow".text = "root:${if isBool cfg.emergencyAccess then optionalString (!cfg.emergencyAccess) "*" 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; + + "/etc/os-release".source = config.boot.initrd.osRelease; + "/etc/initrd-release".source = config.boot.initrd.osRelease; + + } // optionalAttrs (config.environment.etc ? "modprobe.d/nixos.conf") { + "/etc/modprobe.d/nixos.conf".source = config.environment.etc."modprobe.d/nixos.conf".source; + }; + + storePaths = [ + # systemd tooling + "${cfg.package}/lib/systemd/systemd-executor" + "${cfg.package}/lib/systemd/systemd-fsck" + "${cfg.package}/lib/systemd/systemd-hibernate-resume" + "${cfg.package}/lib/systemd/systemd-journald" + "${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" + "${cfg.package}/lib/systemd/systemd-bsod" + + # 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" + + # required for script services, and some tools like xfs still want the sh symlink + "${pkgs.bash}/bin" + + # so NSS can look up usernames + "${pkgs.glibc}/lib/libnss_files.so.2" + ] ++ optionals (cfg.package.withCryptsetup && cfg.enableTpm2) [ + # tpm2 support + "${cfg.package}/lib/cryptsetup/libcryptsetup-token-systemd-tpm2.so" + pkgs.tpm2-tss + ] ++ optionals cfg.package.withCryptsetup [ + # fido2 support + "${cfg.package}/lib/cryptsetup/libcryptsetup-token-systemd-fido2.so" + "${pkgs.libfido2}/lib/libfido2.so.1" + ] ++ jobScripts; + + targets.initrd.aliases = ["default.target"]; + units = + mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit v)) cfg.paths + // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit v)) cfg.services + // mapAttrs' (n: v: nameValuePair "${n}.slice" (sliceToUnit v)) cfg.slices + // mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit v)) cfg.sockets + // mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit v)) cfg.targets + // mapAttrs' (n: v: nameValuePair "${n}.timer" (timerToUnit v)) cfg.timers + // listToAttrs (map + (v: let n = escapeSystemdPath v.where; + in nameValuePair "${n}.mount" (mountToUnit v)) cfg.mounts) + // listToAttrs (map + (v: let n = escapeSystemdPath v.where; + in nameValuePair "${n}.automount" (automountToUnit v)) cfg.automounts); + + # make sure all the /dev nodes are set up + services.systemd-tmpfiles-setup-dev.wantedBy = ["sysinit.target"]; + + 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="''${initParam[1]}" + ;; + esac + done + + # Sanity check + if [ -z "''${closure:-}" ]; then + echo 'No init= parameter on the kernel command line' >&2 + exit 1 + fi + + # Resolve symlinks in the init parameter. We need this for some boot loaders + # (e.g. boot.loader.generationsDir). + closure="$(chroot /sysroot ${pkgs.coreutils}/bin/realpath "$closure")" + + # Assume the directory containing the init script is the closure. + closure="$(dirname "$closure")" + + # 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$(readlink "/sysroot$closure/prepare-root" || echo "$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"; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/journald-gateway.nix b/nixpkgs/nixos/modules/system/boot/systemd/journald-gateway.nix new file mode 100644 index 000000000000..1bcb3f400c61 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/journald-gateway.nix @@ -0,0 +1,135 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.journald.gateway; + + cliArgs = lib.cli.toGNUCommandLineShell { } { + # If either of these are null / false, they are not passed in the command-line + inherit (cfg) cert key trust system user merge; + }; +in +{ + meta.maintainers = [ lib.maintainers.raitobezarius ]; + options.services.journald.gateway = { + enable = lib.mkEnableOption "the HTTP gateway to the journal"; + + port = lib.mkOption { + default = 19531; + type = lib.types.port; + description = '' + The port to listen to. + ''; + }; + + cert = lib.mkOption { + default = null; + type = with lib.types; nullOr str; + description = '' + The path to a file or `AF_UNIX` stream socket to read the server + certificate from. + + The certificate must be in PEM format. This option switches + `systemd-journal-gatewayd` into HTTPS mode and must be used together + with {option}`services.journald.gateway.key`. + ''; + }; + + key = lib.mkOption { + default = null; + type = with lib.types; nullOr str; + description = '' + Specify the path to a file or `AF_UNIX` stream socket to read the + secret server key corresponding to the certificate specified with + {option}`services.journald.gateway.cert` from. + + The key must be in PEM format. + + This key should not be world-readable, and must be readably by the + `systemd-journal-gateway` user. + ''; + }; + + trust = lib.mkOption { + default = null; + type = with lib.types; nullOr str; + description = '' + Specify the path to a file or `AF_UNIX` stream socket to read a CA + certificate from. + + The certificate must be in PEM format. + + Setting this option enforces client certificate checking. + ''; + }; + + system = lib.mkOption { + default = true; + type = lib.types.bool; + description = '' + Serve entries from system services and the kernel. + + This has the same meaning as `--system` for {manpage}`journalctl(1)`. + ''; + }; + + user = lib.mkOption { + default = true; + type = lib.types.bool; + description = '' + Serve entries from services for the current user. + + This has the same meaning as `--user` for {manpage}`journalctl(1)`. + ''; + }; + + merge = lib.mkOption { + default = false; + type = lib.types.bool; + description = '' + Serve entries interleaved from all available journals, including other + machines. + + This has the same meaning as `--merge` option for + {manpage}`journalctl(1)`. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + # This prevents the weird case were disabling "system" and "user" + # actually enables both because the cli flags are not present. + assertion = cfg.system || cfg.user; + message = '' + systemd-journal-gatewayd cannot serve neither "system" nor "user" + journals. + ''; + } + ]; + + systemd.additionalUpstreamSystemUnits = [ + "systemd-journal-gatewayd.socket" + "systemd-journal-gatewayd.service" + ]; + + 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.services.systemd-journal-gatewayd.serviceConfig.ExecStart = [ + # Clear the default command line + "" + "${pkgs.systemd}/lib/systemd/systemd-journal-gatewayd ${cliArgs}" + ]; + + systemd.sockets.systemd-journal-gatewayd = { + wantedBy = [ "sockets.target" ]; + listenStreams = [ + # Clear the default port + "" + (toString cfg.port) + ]; + }; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/journald-remote.nix b/nixpkgs/nixos/modules/system/boot/systemd/journald-remote.nix new file mode 100644 index 000000000000..13674694c144 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/journald-remote.nix @@ -0,0 +1,163 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.journald.remote; + format = pkgs.formats.systemd; + + cliArgs = lib.cli.toGNUCommandLineShell { } { + inherit (cfg) output; + # "-3" specifies the file descriptor from the .socket unit. + "listen-${cfg.listen}" = "-3"; + }; +in +{ + meta.maintainers = [ lib.maintainers.raitobezarius ]; + options.services.journald.remote = { + enable = lib.mkEnableOption "receiving systemd journals from the network"; + + listen = lib.mkOption { + default = "https"; + type = lib.types.enum [ "https" "http" ]; + description = '' + Which protocol to listen to. + ''; + }; + + output = lib.mkOption { + default = "/var/log/journal/remote/"; + type = lib.types.str; + description = '' + The location of the output journal. + + In case the output file is not specified, journal files will be created + underneath the selected directory. Files will be called + {file}`remote-hostname.journal`, where the `hostname` part is the + escaped hostname of the source endpoint of the connection, or the + numerical address if the hostname cannot be determined. + ''; + }; + + port = lib.mkOption { + default = 19532; + type = lib.types.port; + description = '' + The port to listen to. + + Note that this option is used only if + {option}`services.journald.upload.listen` is configured to be either + "https" or "http". + ''; + }; + + settings = lib.mkOption { + default = { }; + + description = '' + Configuration in the journal-remote configuration file. See + {manpage}`journal-remote.conf(5)` for available options. + ''; + + type = lib.types.submodule { + freeformType = format.type; + + options.Remote = { + Seal = lib.mkOption { + default = false; + example = true; + type = lib.types.bool; + description = '' + Periodically sign the data in the journal using Forward Secure + Sealing. + ''; + }; + + SplitMode = lib.mkOption { + default = "host"; + example = "none"; + type = lib.types.enum [ "host" "none" ]; + description = '' + With "host", a separate output file is used, based on the + hostname of the other endpoint of a connection. With "none", only + one output journal file is used. + ''; + }; + + ServerKeyFile = lib.mkOption { + default = "/etc/ssl/private/journal-remote.pem"; + type = lib.types.str; + description = '' + A path to a SSL secret key file in PEM format. + + Note that due to security reasons, `systemd-journal-remote` will + refuse files from the world-readable `/nix/store`. This file + should be readable by the "" user. + + This option can be used with `listen = "https"`. If the path + refers to an `AF_UNIX` stream socket in the file system a + connection is made to it and the key read from it. + ''; + }; + + ServerCertificateFile = lib.mkOption { + default = "/etc/ssl/certs/journal-remote.pem"; + type = lib.types.str; + description = '' + A path to a SSL certificate file in PEM format. + + This option can be used with `listen = "https"`. If the path + refers to an `AF_UNIX` stream socket in the file system a + connection is made to it and the certificate read from it. + ''; + }; + + TrustedCertificateFile = lib.mkOption { + default = "/etc/ssl/ca/trusted.pem"; + type = lib.types.str; + description = '' + A path to a SSL CA certificate file in PEM format, or `all`. + + If `all` is set, then client certificate checking will be + disabled. + + This option can be used with `listen = "https"`. If the path + refers to an `AF_UNIX` stream socket in the file system a + connection is made to it and the certificate read from it. + ''; + }; + }; + }; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.additionalUpstreamSystemUnits = [ + "systemd-journal-remote.service" + "systemd-journal-remote.socket" + ]; + + systemd.services.systemd-journal-remote.serviceConfig.ExecStart = [ + # Clear the default command line + "" + "${pkgs.systemd}/lib/systemd/systemd-journal-remote ${cliArgs}" + ]; + + systemd.sockets.systemd-journal-remote = { + wantedBy = [ "sockets.target" ]; + listenStreams = [ + # Clear the default port + "" + (toString cfg.port) + ]; + }; + + # User and group used by systemd-journal-remote.service + users.groups.systemd-journal-remote = { }; + users.users.systemd-journal-remote = { + isSystemUser = true; + group = "systemd-journal-remote"; + }; + + environment.etc."systemd/journal-remote.conf".source = + format.generate "journal-remote.conf" cfg.settings; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/journald-upload.nix b/nixpkgs/nixos/modules/system/boot/systemd/journald-upload.nix new file mode 100644 index 000000000000..053f886ff5c6 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/journald-upload.nix @@ -0,0 +1,111 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.journald.upload; + format = pkgs.formats.systemd; +in +{ + meta.maintainers = [ lib.maintainers.raitobezarius ]; + options.services.journald.upload = { + enable = lib.mkEnableOption "uploading the systemd journal to a remote server"; + + settings = lib.mkOption { + default = { }; + + description = '' + Configuration for journal-upload. See {manpage}`journal-upload.conf(5)` + for available options. + ''; + + type = lib.types.submodule { + freeformType = format.type; + + options.Upload = { + URL = lib.mkOption { + type = lib.types.str; + example = "https://192.168.1.1"; + description = '' + The URL to upload the journal entries to. + + See the description of `--url=` option in + {manpage}`systemd-journal-upload(8)` for the description of + possible values. + ''; + }; + + ServerKeyFile = lib.mkOption { + type = with lib.types; nullOr str; + example = lib.literalExpression "./server-key.pem"; + # Since systemd-journal-upload uses a DynamicUser, permissions must + # be done using groups + description = '' + SSL key in PEM format. + + In contrary to what the name suggests, this option configures the + client private key sent to the remote journal server. + + This key should not be world-readable, and must be readably by + the `systemd-journal` group. + ''; + default = null; + }; + + ServerCertificateFile = lib.mkOption { + type = with lib.types; nullOr str; + example = lib.literalExpression "./server-ca.pem"; + description = '' + SSL CA certificate in PEM format. + + In contrary to what the name suggests, this option configures the + client certificate sent to the remote journal server. + ''; + default = null; + }; + + TrustedCertificateFile = lib.mkOption { + type = with lib.types; nullOr str; + example = lib.literalExpression "./ca"; + description = '' + SSL CA certificate. + + This certificate will be used to check the remote journal HTTPS + server certificate. + ''; + default = null; + }; + + NetworkTimeoutSec = lib.mkOption { + type = with lib.types; nullOr str; + example = "1s"; + description = '' + When network connectivity to the server is lost, this option + configures the time to wait for the connectivity to get restored. + + If the server is not reachable over the network for the + configured time, `systemd-journal-upload` exits. Takes a value in + seconds (or in other time units if suffixed with "ms", "min", + "h", etc). For details, see {manpage}`systemd.time(5)`. + ''; + default = null; + }; + }; + }; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.additionalUpstreamSystemUnits = [ "systemd-journal-upload.service" ]; + + systemd.services."systemd-journal-upload" = { + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Restart = "always"; + # To prevent flooding the server in case the server is struggling + RestartSec = "3sec"; + }; + }; + + environment.etc."systemd/journal-upload.conf".source = + format.generate "journal-upload.conf" cfg.settings; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/journald.nix b/nixpkgs/nixos/modules/system/boot/systemd/journald.nix new file mode 100644 index 000000000000..f9f05d2b08f4 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/journald.nix @@ -0,0 +1,129 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.journald; +in { + imports = [ + (mkRenamedOptionModule [ "services" "journald" "enableHttpGateway" ] [ "services" "journald" "gateway" "enable" ]) + ]; + + 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` for important + considerations when setting this value. + ''; + }; + + services.journald.storage = mkOption { + default = "persistent"; + type = types.enum [ "persistent" "volatile" "auto" "none" ]; + description = '' + Controls where to store journal data. See + {manpage}`journald.conf(5)` for further information. + ''; + }; + + 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 + [ + journald.conf(5)](https://www.freedesktop.org/software/systemd/man/journald.conf.html). + + Note that the total amount of logs stored is limited by journald settings + such as `SystemMaxUse`, which defaults to 10% the file system size + (capped at max 4GB), and `SystemKeepFree`, which defaults to 15% of the + file system size. + + 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.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" + ]; + + environment.etc = { + "systemd/journald.conf".text = '' + [Journal] + Storage=${cfg.storage} + 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; + + 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; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/logind.nix b/nixpkgs/nixos/modules/system/boot/systemd/logind.nix new file mode 100644 index 000000000000..ed5369c09ccb --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/logind.nix @@ -0,0 +1,205 @@ +{ 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 [logind.conf(5)](https://www.freedesktop.org/software/systemd/man/logind.conf.html) + for available options. + ''; + }; + + 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 [systemd.scope(5)](https://www.freedesktop.org/software/systemd/man/systemd.scope.html#)), + and processes are not killed. + + See [logind.conf(5)](https://www.freedesktop.org/software/systemd/man/logind.conf.html#KillUserProcesses=) + for more details. + ''; + }; + + powerKey = mkOption { + default = "poweroff"; + example = "ignore"; + type = logindHandlerType; + + description = '' + Specifies what to do when the power key is pressed. + ''; + }; + + powerKeyLongPress = mkOption { + default = "ignore"; + example = "reboot"; + type = logindHandlerType; + + description = '' + Specifies what to do when the power key is long-pressed. + ''; + }; + + rebootKey = mkOption { + default = "reboot"; + example = "ignore"; + type = logindHandlerType; + + description = '' + Specifies what to do when the reboot key is pressed. + ''; + }; + + rebootKeyLongPress = mkOption { + default = "poweroff"; + example = "ignore"; + type = logindHandlerType; + + description = '' + Specifies what to do when the reboot key is long-pressed. + ''; + }; + + suspendKey = mkOption { + default = "suspend"; + example = "ignore"; + type = logindHandlerType; + + description = '' + Specifies what to do when the suspend key is pressed. + ''; + }; + + suspendKeyLongPress = mkOption { + default = "hibernate"; + example = "ignore"; + type = logindHandlerType; + + description = '' + Specifies what to do when the suspend key is long-pressed. + ''; + }; + + hibernateKey = mkOption { + default = "hibernate"; + example = "ignore"; + type = logindHandlerType; + + description = '' + Specifies what to do when the hibernate key is pressed. + ''; + }; + + hibernateKeyLongPress = mkOption { + default = "ignore"; + example = "suspend"; + type = logindHandlerType; + + description = '' + Specifies what to do when the hibernate key is long-pressed. + ''; + }; + + lidSwitch = mkOption { + default = "suspend"; + example = "ignore"; + type = logindHandlerType; + + description = '' + Specifies what to do when the laptop lid is closed. + ''; + }; + + 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. + ''; + }; + + lidSwitchDocked = mkOption { + default = "ignore"; + example = "suspend"; + type = logindHandlerType; + + description = '' + Specifies what to do when the laptop lid is closed + and another screen is added. + ''; + }; + }; + + 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" + ] ++ optionals config.systemd.package.withPortabled [ + "dbus-org.freedesktop.portable1.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"} + HandlePowerKey=${cfg.powerKey} + HandlePowerKeyLongPress=${cfg.powerKeyLongPress} + HandleRebootKey=${cfg.rebootKey} + HandleRebootKeyLongPress=${cfg.rebootKeyLongPress} + HandleSuspendKey=${cfg.suspendKey} + HandleSuspendKeyLongPress=${cfg.suspendKeyLongPress} + HandleHibernateKey=${cfg.hibernateKey} + HandleHibernateKeyLongPress=${cfg.hibernateKeyLongPress} + HandleLidSwitch=${cfg.lidSwitch} + HandleLidSwitchExternalPower=${cfg.lidSwitchExternalPower} + HandleLidSwitchDocked=${cfg.lidSwitchDocked} + ${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; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/nspawn.nix b/nixpkgs/nixos/modules/system/boot/systemd/nspawn.nix new file mode 100644 index 000000000000..11fbb88838e1 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/nspawn.nix @@ -0,0 +1,132 @@ +{ 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" "PrivateUsersOwnership" + ]) + (assertValueOneOf "ReadOnly" boolValues) + (assertValueOneOf "Volatile" (boolValues ++ [ "state" ])) + (assertValueOneOf "PrivateUsersChown" boolValues) + (assertValueOneOf "PrivateUsersOwnership" [ "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 = + (getAttrs [ "enable" ] 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 + `[Exec]` section of this unit. See + {manpage}`systemd.nspawn(5)` 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 + `[Files]` section of this unit. See + {manpage}`systemd.nspawn(5)` 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 + `[Network]` section of this unit. See + {manpage}`systemd.nspawn(5)` 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" ]; + } + ]; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/oomd.nix b/nixpkgs/nixos/modules/system/boot/systemd/oomd.nix new file mode 100644 index 000000000000..a2a90e0ceb87 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/oomd.nix @@ -0,0 +1,71 @@ +{ config, lib, ... }: let + + cfg = config.systemd.oomd; + +in { + imports = [ + (lib.mkRenamedOptionModule [ "systemd" "oomd" "enableUserServices" ] [ "systemd" "oomd" "enableUserSlices" ]) + ]; + + options.systemd.oomd = { + enable = lib.mkEnableOption "the `systemd-oomd` OOM killer" // { default = true; }; + + # Fedora enables the first and third option by default. See the 10-oomd-* files here: + # https://src.fedoraproject.org/rpms/systemd/tree/806c95e1c70af18f81d499b24cd7acfa4c36ffd6 + enableRootSlice = lib.mkEnableOption "oomd on the root slice (`-.slice`)"; + enableSystemSlice = lib.mkEnableOption "oomd on the system slice (`system.slice`)"; + enableUserSlices = lib.mkEnableOption "oomd on all user slices (`user@.slice`) and all user owned slices"; + + extraConfig = lib.mkOption { + type = with lib.types; attrsOf (oneOf [ str int bool ]); + default = {}; + example = lib.literalExpression ''{ DefaultMemoryPressureDurationSec = "20s"; }''; + description = '' + Extra config options for `systemd-oomd`. See {command}`man oomd.conf` + for available options. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.additionalUpstreamSystemUnits = [ + "systemd-oomd.service" + "systemd-oomd.socket" + ]; + systemd.services.systemd-oomd.wantedBy = [ "multi-user.target" ]; + + environment.etc."systemd/oomd.conf".text = lib.generators.toINI {} { + OOM = cfg.extraConfig; + }; + + systemd.oomd.extraConfig.DefaultMemoryPressureDurationSec = lib.mkDefault "20s"; # Fedora default + + users.users.systemd-oom = { + description = "systemd-oomd service user"; + group = "systemd-oom"; + isSystemUser = true; + }; + users.groups.systemd-oom = { }; + + systemd.slices."-".sliceConfig = lib.mkIf cfg.enableRootSlice { + ManagedOOMMemoryPressure = "kill"; + ManagedOOMMemoryPressureLimit = lib.mkDefault "80%"; + }; + systemd.slices."system".sliceConfig = lib.mkIf cfg.enableSystemSlice { + ManagedOOMMemoryPressure = "kill"; + ManagedOOMMemoryPressureLimit = lib.mkDefault "80%"; + }; + systemd.slices."user-".sliceConfig = lib.mkIf cfg.enableUserSlices { + ManagedOOMMemoryPressure = "kill"; + ManagedOOMMemoryPressureLimit = lib.mkDefault "80%"; + }; + systemd.user.units."slice" = lib.mkIf cfg.enableUserSlices { + text = '' + [Slice] + ManagedOOMMemoryPressure=kill + ManagedOOMMemoryPressureLimit=80% + ''; + overrideStrategy = "asDropin"; + }; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/repart.nix b/nixpkgs/nixos/modules/system/boot/systemd/repart.nix new file mode 100644 index 000000000000..49db1305bb2b --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/repart.nix @@ -0,0 +1,165 @@ +{ config, lib, pkgs, utils, ... }: + +let + cfg = config.systemd.repart; + initrdCfg = config.boot.initrd.systemd.repart; + + format = pkgs.formats.ini { }; + + definitionsDirectory = utils.systemdUtils.lib.definitions + "repart.d" + format + (lib.mapAttrs (_n: v: { Partition = v; }) cfg.partitions); + + partitionAssertions = lib.mapAttrsToList (fileName: definition: + let + inherit (utils.systemdUtils.lib) GPTMaxLabelLength; + labelLength = builtins.stringLength definition.Label; + in + { + assertion = definition ? Label -> GPTMaxLabelLength >= labelLength; + message = '' + The partition label '${definition.Label}' defined for '${fileName}' is ${toString labelLength} + characters long, but the maximum label length supported by systemd is ${toString GPTMaxLabelLength}. + ''; + } + ) cfg.partitions; +in +{ + options = { + boot.initrd.systemd.repart = { + enable = lib.mkEnableOption "systemd-repart" // { + description = '' + Grow and add partitions to a partition table at boot time in the initrd. + systemd-repart only works with GPT partition tables. + + To run systemd-repart after the initrd, see + `options.systemd.repart.enable`. + ''; + }; + + device = lib.mkOption { + type = with lib.types; nullOr str; + description = '' + The device to operate on. + + If `device == null`, systemd-repart will operate on the device + backing the root partition. So in order to dynamically *create* the + root partition in the initrd you need to set a device. + ''; + default = null; + example = "/dev/vda"; + }; + }; + + systemd.repart = { + enable = lib.mkEnableOption "systemd-repart" // { + description = '' + Grow and add partitions to a partition table. + systemd-repart only works with GPT partition tables. + + To run systemd-repart while in the initrd, see + `options.boot.initrd.systemd.repart.enable`. + ''; + }; + + partitions = lib.mkOption { + type = with lib.types; attrsOf (attrsOf (oneOf [ str int bool ])); + default = { }; + example = { + "10-root" = { + Type = "root"; + }; + "20-home" = { + Type = "home"; + SizeMinBytes = "512M"; + SizeMaxBytes = "2G"; + }; + }; + description = '' + Specify partitions as a set of the names of the definition files as the + key and the partition configuration as its value. The partition + configuration can use all upstream options. See <link + xlink:href="https://www.freedesktop.org/software/systemd/man/repart.d.html"/> + for all available options. + ''; + }; + }; + }; + + config = lib.mkIf (cfg.enable || initrdCfg.enable) { + assertions = [ + { + assertion = initrdCfg.enable -> config.boot.initrd.systemd.enable; + message = '' + 'boot.initrd.systemd.repart.enable' requires 'boot.initrd.systemd.enable' to be enabled. + ''; + } + ] ++ partitionAssertions; + + # systemd-repart uses loopback devices for partition creation + boot.initrd.availableKernelModules = lib.optional initrdCfg.enable "loop"; + + boot.initrd.systemd = lib.mkIf initrdCfg.enable { + additionalUpstreamUnits = [ + "systemd-repart.service" + ]; + + storePaths = [ + "${config.boot.initrd.systemd.package}/bin/systemd-repart" + ]; + + contents."/etc/repart.d".source = definitionsDirectory; + + # Override defaults in upstream unit. + services.systemd-repart = + let + deviceUnit = "${utils.escapeSystemdPath initrdCfg.device}.device"; + in + { + # systemd-repart tries to create directories in /var/tmp by default to + # store large temporary files that benefit from persistence on disk. In + # the initrd, however, /var/tmp does not provide more persistence than + # /tmp, so we re-use it here. + environment."TMPDIR" = "/tmp"; + serviceConfig = { + ExecStart = [ + " " # required to unset the previous value. + # When running in the initrd, systemd-repart by default searches + # for definition files in /sysroot or /sysusr. We tell it to look + # in the initrd itself. + ''${config.boot.initrd.systemd.package}/bin/systemd-repart \ + --definitions=/etc/repart.d \ + --dry-run=no ${lib.optionalString (initrdCfg.device != null) initrdCfg.device} + '' + ]; + }; + # systemd-repart needs to run after /sysroot (or /sysuser, but we + # don't have it) has been mounted because otherwise it cannot + # determine the device (i.e disk) to operate on. If you want to run + # systemd-repart without /sysroot (i.e. to create the root + # partition), you have to explicitly tell it which device to operate + # on. The service then needs to be ordered to run after this device + # is available. + requires = lib.mkIf (initrdCfg.device != null) [ deviceUnit ]; + after = + if initrdCfg.device == null then + [ "sysroot.mount" ] + else + [ deviceUnit ]; + }; + }; + + environment.etc = lib.mkIf cfg.enable { + "repart.d".source = definitionsDirectory; + }; + + systemd = lib.mkIf cfg.enable { + additionalUpstreamSystemUnits = [ + "systemd-repart.service" + ]; + }; + }; + + meta.maintainers = with lib.maintainers; [ nikstur ]; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/shutdown.nix b/nixpkgs/nixos/modules/system/boot/systemd/shutdown.nix new file mode 100644 index 000000000000..5c2525a57b4b --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/shutdown.nix @@ -0,0 +1,66 @@ +{ 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"; + "/etc/initrd-release".source = config.environment.etc.os-release.source; + "/etc/os-release".source = config.environment.etc.os-release.source; + }; + systemd.shutdownRamfs.storePaths = [pkgs.runtimeShell "${pkgs.coreutils}/bin"]; + + systemd.mounts = [{ + what = "tmpfs"; + where = "/run/initramfs"; + type = "tmpfs"; + }]; + + systemd.services.generate-shutdown-ramfs = { + description = "Generate shutdown ramfs"; + wantedBy = [ "shutdown.target" ]; + before = [ "shutdown.target" ]; + unitConfig = { + DefaultDependencies = false; + RequiresMountsFor = "/run/initramfs"; + ConditionFileIsExecutable = [ + "!/run/initramfs/shutdown" + ]; + }; + + serviceConfig = { + Type = "oneshot"; + ProtectSystem = "strict"; + ReadWritePaths = "/run/initramfs"; + ExecStart = "${pkgs.makeInitrdNGTool}/bin/make-initrd-ng ${ramfsContents} /run/initramfs"; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/sysupdate.nix b/nixpkgs/nixos/modules/system/boot/systemd/sysupdate.nix new file mode 100644 index 000000000000..67f0b3b1caad --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/sysupdate.nix @@ -0,0 +1,136 @@ +{ config, lib, pkgs, utils, ... }: + +let + cfg = config.systemd.sysupdate; + + format = pkgs.formats.ini { listToValue = toString; }; + + definitionsDirectory = utils.systemdUtils.lib.definitions + "sysupdate.d" + format + cfg.transfers; +in +{ + options.systemd.sysupdate = { + + enable = lib.mkEnableOption "systemd-sysupdate" // { + description = '' + Atomically update the host OS, container images, portable service + images or other sources. + + If enabled, updates are triggered in regular intervals via a + `systemd.timer` unit. + + Please see + <https://www.freedesktop.org/software/systemd/man/systemd-sysupdate.html> + for more details. + ''; + }; + + timerConfig = utils.systemdUtils.unitOptions.timerOptions.options.timerConfig // { + default = { }; + description = '' + The timer configuration for performing the update. + + By default, the upstream configuration is used: + <https://github.com/systemd/systemd/blob/main/units/systemd-sysupdate.timer> + ''; + }; + + reboot = { + enable = lib.mkEnableOption "automatically rebooting after an update" // { + description = '' + Whether to automatically reboot after an update. + + If set to `true`, the system will automatically reboot via a + `systemd.timer` unit but only after a new version was installed. + + This uses a unit completely separate from the one performing the + update because it is typically advisable to download updates + regularly while the system is up, but delay reboots until the + appropriate time (i.e. typically at night). + + Set this to `false` if you do not want to reboot after an update. This + is useful when you update a container image or another source where + rebooting is not necessary in order to finalize the update. + ''; + }; + + timerConfig = utils.systemdUtils.unitOptions.timerOptions.options.timerConfig // { + default = { }; + description = '' + The timer configuration for rebooting after an update. + + By default, the upstream configuration is used: + <https://github.com/systemd/systemd/blob/main/units/systemd-sysupdate-reboot.timer> + ''; + }; + }; + + transfers = lib.mkOption { + type = with lib.types; attrsOf format.type; + default = { }; + example = { + "10-uki" = { + Transfer = { + ProtectVersion = "%A"; + }; + + Source = { + Type = "url-file"; + Path = "https://download.example.com/"; + MatchPattern = [ "nixos_@v+@l-@d.efi" "nixos_@v+@l.efi" "nixos_@v.efi" ]; + }; + + Target = { + Type = "regular-file"; + Path = "/EFI/Linux"; + PathRelativeTo = "boot"; + MatchPattern = '' + nixos_@v+@l-@d.efi"; \ + nixos_@v+@l.efi \ + nixos_@v.efi + ''; + Mode = "0444"; + TriesLeft = 3; + TriesDone = 0; + InstancesMax = 2; + }; + }; + }; + description = '' + Specify transfers as a set of the names of the transfer files as the + key and the configuration as its value. The configuration can use all + upstream options. See + <https://www.freedesktop.org/software/systemd/man/sysupdate.d.html> + for all available options. + ''; + }; + + }; + + config = lib.mkIf cfg.enable { + + systemd.additionalUpstreamSystemUnits = [ + "systemd-sysupdate.service" + "systemd-sysupdate.timer" + "systemd-sysupdate-reboot.service" + "systemd-sysupdate-reboot.timer" + ]; + + systemd.timers = { + "systemd-sysupdate" = { + wantedBy = [ "timers.target" ]; + timerConfig = cfg.timerConfig; + }; + "systemd-sysupdate-reboot" = lib.mkIf cfg.reboot.enable { + wantedBy = [ "timers.target" ]; + timerConfig = cfg.reboot.timerConfig; + }; + }; + + environment.etc."sysupdate.d".source = definitionsDirectory; + }; + + meta.maintainers = with lib.maintainers; [ nikstur ]; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/sysusers.nix b/nixpkgs/nixos/modules/system/boot/systemd/sysusers.nix new file mode 100644 index 000000000000..476251e14045 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/sysusers.nix @@ -0,0 +1,186 @@ +{ config, lib, pkgs, utils, ... }: + +let + + cfg = config.systemd.sysusers; + userCfg = config.users; + + sysusersConfig = pkgs.writeTextDir "00-nixos.conf" '' + # Type Name ID GECOS Home directory Shell + + # Users + ${lib.concatLines (lib.mapAttrsToList + (username: opts: + let + uid = if opts.uid == null then "/var/lib/nixos/uid/${username}" else toString opts.uid; + in + ''u ${username} ${uid}:${opts.group} "${opts.description}" ${opts.home} ${utils.toShellPath opts.shell}'' + ) + userCfg.users) + } + + # Groups + ${lib.concatLines (lib.mapAttrsToList + (groupname: opts: ''g ${groupname} ${if opts.gid == null then "/var/lib/nixos/gid/${groupname}" else toString opts.gid}'') userCfg.groups) + } + + # Group membership + ${lib.concatStrings (lib.mapAttrsToList + (groupname: opts: (lib.concatMapStrings (username: "m ${username} ${groupname}\n")) opts.members ) userCfg.groups) + } + ''; + + staticSysusersCredentials = pkgs.runCommand "static-sysusers-credentials" { } '' + mkdir $out; cd $out + ${lib.concatLines ( + (lib.mapAttrsToList + (username: opts: "echo -n '${opts.initialHashedPassword}' > 'passwd.hashed-password.${username}'") + (lib.filterAttrs (_username: opts: opts.initialHashedPassword != null) userCfg.users)) + ++ + (lib.mapAttrsToList + (username: opts: "echo -n '${opts.initialPassword}' > 'passwd.plaintext-password.${username}'") + (lib.filterAttrs (_username: opts: opts.initialPassword != null) userCfg.users)) + ++ + (lib.mapAttrsToList + (username: opts: "cat '${opts.hashedPasswordFile}' > 'passwd.hashed-password.${username}'") + (lib.filterAttrs (_username: opts: opts.hashedPasswordFile != null) userCfg.users)) + ) + } + ''; + + staticSysusers = pkgs.runCommand "static-sysusers" + { + nativeBuildInputs = [ pkgs.systemd ]; + } '' + mkdir $out + export CREDENTIALS_DIRECTORY=${staticSysusersCredentials} + systemd-sysusers --root $out ${sysusersConfig}/00-nixos.conf + ''; + +in + +{ + + options = { + + # This module doesn't set it's own user options but reuses the ones from + # users-groups.nix + + systemd.sysusers = { + enable = lib.mkEnableOption "systemd-sysusers" // { + description = '' + If enabled, users are created with systemd-sysusers instead of with + the custom `update-users-groups.pl` script. + + Note: This is experimental. + ''; + }; + }; + + }; + + config = lib.mkIf cfg.enable { + + assertions = [ + { + assertion = config.system.activationScripts.users == ""; + message = "system.activationScripts.users has to be empty to use systemd-sysusers"; + } + { + assertion = config.users.mutableUsers -> config.system.etc.overlay.enable; + message = "config.users.mutableUsers requires config.system.etc.overlay.enable."; + } + ]; + + systemd = lib.mkMerge [ + ({ + + # Create home directories, do not create /var/empty even if that's a user's + # home. + tmpfiles.settings.home-directories = lib.mapAttrs' + (username: opts: lib.nameValuePair opts.home { + d = { + mode = opts.homeMode; + user = username; + group = opts.group; + }; + }) + (lib.filterAttrs (_username: opts: opts.home != "/var/empty") userCfg.users); + + # Create uid/gid marker files for those without an explicit id + tmpfiles.settings.nixos-uid = lib.mapAttrs' + (username: opts: lib.nameValuePair "/var/lib/nixos/uid/${username}" { + f = { + user = username; + }; + }) + (lib.filterAttrs (_username: opts: opts.uid == null) userCfg.users); + + tmpfiles.settings.nixos-gid = lib.mapAttrs' + (groupname: opts: lib.nameValuePair "/var/lib/nixos/gid/${groupname}" { + f = { + group = groupname; + }; + }) + (lib.filterAttrs (_groupname: opts: opts.gid == null) userCfg.groups); + }) + + (lib.mkIf config.users.mutableUsers { + additionalUpstreamSystemUnits = [ + "systemd-sysusers.service" + ]; + + services.systemd-sysusers = { + # Enable switch-to-configuration to restart the service. + unitConfig.ConditionNeedsUpdate = [ "" ]; + requiredBy = [ "sysinit-reactivation.target" ]; + before = [ "sysinit-reactivation.target" ]; + restartTriggers = [ "${config.environment.etc."sysusers.d".source}" ]; + + serviceConfig = { + LoadCredential = lib.mapAttrsToList + (username: opts: "passwd.hashed-password.${username}:${opts.hashedPasswordFile}") + (lib.filterAttrs (_username: opts: opts.hashedPasswordFile != null) userCfg.users); + SetCredential = (lib.mapAttrsToList + (username: opts: "passwd.hashed-password.${username}:${opts.initialHashedPassword}") + (lib.filterAttrs (_username: opts: opts.initialHashedPassword != null) userCfg.users)) + ++ + (lib.mapAttrsToList + (username: opts: "passwd.plaintext-password.${username}:${opts.initialPassword}") + (lib.filterAttrs (_username: opts: opts.initialPassword != null) userCfg.users)) + ; + }; + }; + }) + ]; + + environment.etc = lib.mkMerge [ + (lib.mkIf (!userCfg.mutableUsers) { + "passwd" = { + source = "${staticSysusers}/etc/passwd"; + mode = "0644"; + }; + "group" = { + source = "${staticSysusers}/etc/group"; + mode = "0644"; + }; + "shadow" = { + source = "${staticSysusers}/etc/shadow"; + mode = "0000"; + }; + "gshadow" = { + source = "${staticSysusers}/etc/gshadow"; + mode = "0000"; + }; + }) + + (lib.mkIf userCfg.mutableUsers { + "sysusers.d".source = sysusersConfig; + }) + ]; + + }; + + meta.maintainers = with lib.maintainers; [ nikstur ]; + +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/tmpfiles.nix b/nixpkgs/nixos/modules/system/boot/systemd/tmpfiles.nix new file mode 100644 index 000000000000..93a7f7a2bd27 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/tmpfiles.nix @@ -0,0 +1,260 @@ +{ 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 + {manpage}`tmpfiles.d(5)` + for the exact format. + ''; + }; + + systemd.tmpfiles.settings = mkOption { + description = '' + Declare systemd-tmpfiles rules to create, delete, and clean up volatile + and temporary files and directories. + + Even though the service is called `*tmp*files` you can also create + persistent files. + ''; + example = { + "10-mypackage" = { + "/var/lib/my-service/statefolder".d = { + mode = "0755"; + user = "root"; + group = "root"; + }; + }; + }; + default = {}; + type = types.attrsOf (types.attrsOf (types.attrsOf (types.submodule ({ name, config, ... }: { + options.type = mkOption { + type = types.str; + default = name; + example = "d"; + description = '' + The type of operation to perform on the file. + + The type consists of a single letter and optionally one or more + modifier characters. + + Please see the upstream documentation for the available types and + more details: + <https://www.freedesktop.org/software/systemd/man/tmpfiles.d> + ''; + }; + options.mode = mkOption { + type = types.str; + default = "-"; + example = "0755"; + description = '' + The file access mode to use when creating this file or directory. + ''; + }; + options.user = mkOption { + type = types.str; + default = "-"; + example = "root"; + description = '' + The user of the file. + + This may either be a numeric ID or a user/group name. + + If omitted or when set to `"-"`, the user and group of the user who + invokes systemd-tmpfiles is used. + ''; + }; + options.group = mkOption { + type = types.str; + default = "-"; + example = "root"; + description = '' + The group of the file. + + This may either be a numeric ID or a user/group name. + + If omitted or when set to `"-"`, the user and group of the user who + invokes systemd-tmpfiles is used. + ''; + }; + options.age = mkOption { + type = types.str; + default = "-"; + example = "10d"; + description = '' + Delete a file when it reaches a certain age. + + If a file or directory is older than the current time minus the age + field, it is deleted. + + If set to `"-"` no automatic clean-up is done. + ''; + }; + options.argument = mkOption { + type = types.str; + default = ""; + example = ""; + description = '' + An argument whose meaning depends on the type of operation. + + Please see the upstream documentation for the meaning of this + parameter in different situations: + <https://www.freedesktop.org/software/systemd/man/tmpfiles.d> + ''; + }; + })))); + }; + + 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` rules. + + All files ending in .conf found in + {file}`«pkg»/lib/tmpfiles.d` + will be included. + If this folder does not exist or does not contain any files an error will be returned instead. + + If a {file}`lib` output is available, rules are searched there and only there. + If there is no {file}`lib` output it will fall back to {file}`out` + 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" + ]; + + # Allow systemd-tmpfiles to be restarted by switch-to-configuration. This + # service is not pulled into the normal boot process. It only exists for + # switch-to-configuration. + # + # This needs to be a separate unit because it does not execute + # systemd-tmpfiles with `--boot` as that is supposed to only be executed + # once at boot time. + # + # Keep this aligned with the upstream `systemd-tmpfiles-setup.service` unit. + systemd.services."systemd-tmpfiles-resetup" = { + description = "Re-setup tmpfiles on a system that is already running."; + + requiredBy = [ "sysinit-reactivation.target" ]; + after = [ "local-fs.target" "systemd-sysusers.service" "systemd-journald.service" ]; + before = [ "sysinit-reactivation.target" "shutdown.target" ]; + conflicts = [ "shutdown.target" ]; + restartTriggers = [ config.environment.etc."tmpfiles.d".source ]; + + unitConfig.DefaultDependencies = false; + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = "systemd-tmpfiles --create --remove --exclude-prefix=/dev"; + SuccessExitStatus = "DATAERR CANTCREAT"; + ImportCredential = [ + "tmpfiles.*" + "loging.motd" + "login.issue" + "network.hosts" + "ssh.authorized_keys.root" + ]; + }; + }; + + 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 + + # home.conf creates /srv (which we don't want), and /home, which + # is handled by NixOS anyway. + # ln -s "${systemd}/example/tmpfiles.d/home.conf" + ln -s "${systemd}/example/tmpfiles.d/journal-nocow.conf" + ln -s "${systemd}/example/tmpfiles.d/portables.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} + ''; + }) + ] ++ (mapAttrsToList (name: paths: + pkgs.writeTextDir "lib/tmpfiles.d/${name}.conf" (concatStrings (mapAttrsToList (path: types: + concatStrings (mapAttrsToList (_type: entry: '' + '${entry.type}' '${path}' '${entry.mode}' '${entry.user}' '${entry.group}' '${entry.age}' ${entry.argument} + '') types) + ) paths )) + ) cfg.settings); + + 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 - - - - -" + ]; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/user.nix b/nixpkgs/nixos/modules/system/boot/systemd/user.nix new file mode 100644 index 000000000000..53fca631678c --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/user.nix @@ -0,0 +1,250 @@ +{ 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 + sliceToUnit + 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; + + writeTmpfiles = { rules, user ? null }: + let + suffix = optionalString (user != null) "-${user}"; + in + pkgs.writeTextFile { + name = "nixos-user-tmpfiles.d${suffix}"; + destination = "/etc/xdg/user-tmpfiles.d/00-nixos${suffix}.conf"; + text = '' + # This file is created automatically and should not be modified. + # Please change the options ‘systemd.user.tmpfiles’ instead. + ${concatStringsSep "\n" rules} + ''; + }; +in { + options = { + systemd.user.extraConfig = mkOption { + default = ""; + type = types.lines; + example = "DefaultCPUAccounting=yes"; + description = '' + Extra config options for systemd user instances. See {manpage}`systemd-user.conf(5)` 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.user.tmpfiles = { + rules = mkOption { + type = types.listOf types.str; + default = []; + example = [ "D %C - - - 7d" ]; + description = '' + Global user rules for creation, deletion and cleaning of volatile and + temporary files automatically. See + {manpage}`tmpfiles.d(5)` + for the exact format. + ''; + }; + + users = mkOption { + description = '' + Per-user rules for creation, deletion and cleaning of volatile and + temporary files automatically. + ''; + default = {}; + type = types.attrsOf (types.submodule { + options = { + rules = mkOption { + type = types.listOf types.str; + default = []; + example = [ "D %C - - - 7d" ]; + description = '' + Per-user rules for creation, deletion and cleaning of volatile and + temporary files automatically. See + {manpage}`tmpfiles.d(5)` + for the exact format. + ''; + }; + }; + }); + }; + }; + + systemd.user.generators = mkOption { + type = types.attrsOf types.path; + default = {}; + example = { systemd-gpt-auto-generator = "/dev/null"; }; + description = '' + Definition of systemd generators; see {manpage}`systemd.generator(5)`. + + For each `NAME = VALUE` pair of the attrSet, a link is generated from + `/etc/systemd/user-generators/NAME` to `VALUE`. + ''; + }; + + 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 v)) cfg.paths + // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit v)) cfg.services + // mapAttrs' (n: v: nameValuePair "${n}.slice" (sliceToUnit v)) cfg.slices + // mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit v)) cfg.sockets + // mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit v)) cfg.targets + // mapAttrs' (n: v: nameValuePair "${n}.timer" (timerToUnit 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; + # Disable pam_mount in systemd-user to prevent it from being called + # multiple times during login, because it will prevent pam_mount from + # unmounting the previously mounted volumes. + pamMount = false; + }; + + # Some overrides to upstream units. + systemd.services."user@".restartIfChanged = false; + systemd.services.systemd-user-sessions.restartIfChanged = false; # Restart kills all active sessions. + + # enable systemd user tmpfiles + systemd.user.services.systemd-tmpfiles-setup.wantedBy = + optional + (cfg.tmpfiles.rules != [] || any (cfg': cfg'.rules != []) (attrValues cfg.tmpfiles.users)) + "basic.target"; + + # /run/current-system/sw/etc/xdg is in systemd's $XDG_CONFIG_DIRS so we can + # write the tmpfiles.d rules for everyone there + environment.systemPackages = + optional + (cfg.tmpfiles.rules != []) + (writeTmpfiles { inherit (cfg.tmpfiles) rules; }); + + # /etc/profiles/per-user/$USER/etc/xdg is in systemd's $XDG_CONFIG_DIRS so + # we can write a single user's tmpfiles.d rules there + users.users = + mapAttrs + (user: cfg': { + packages = optional (cfg'.rules != []) (writeTmpfiles { + inherit (cfg') rules; + inherit user; + }); + }) + cfg.tmpfiles.users; + + system.userActivationScripts.tmpfiles = '' + ${config.systemd.package}/bin/systemd-tmpfiles --user --create --remove + ''; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/userdbd.nix b/nixpkgs/nixos/modules/system/boot/systemd/userdbd.nix new file mode 100644 index 000000000000..0161a7c2c681 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/userdbd.nix @@ -0,0 +1,18 @@ +{ config, lib, ... }: + +let + cfg = config.services.userdbd; +in +{ + options.services.userdbd.enable = lib.mkEnableOption '' + the systemd JSON user/group record lookup service + ''; + config = lib.mkIf cfg.enable { + systemd.additionalUpstreamSystemUnits = [ + "systemd-userdbd.socket" + "systemd-userdbd.service" + ]; + + systemd.sockets.systemd-userdbd.wantedBy = [ "sockets.target" ]; + }; +} |