push sheeet
Some checks failed
Periodic Merges (6h) / master → staging-nixos (push) Failing after 12m50s
Periodic Merges (6h) / master → staging-next (push) Failing after 12m54s
Periodic Merges (24h) / merge-base(master,staging) → haskell-updates (push) Failing after 11m54s
Periodic Merges (6h) / staging-next → staging (push) Failing after 12m13s
Periodic Merges (24h) / staging-next-25.05 → staging-25.05 (push) Failing after 13m24s
Periodic Merges (24h) / release-25.05 → staging-next-25.05 (push) Failing after 14m28s

This commit is contained in:
Dark Steveneq
2025-10-09 14:15:47 +02:00
commit 646b892680
49168 changed files with 5897842 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
{
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 {manpage}`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";
})
];
}

View File

@@ -0,0 +1,61 @@
{ config, lib, ... }:
let
cfg = config.boot.initrd.systemd.dmVerity;
in
{
options = {
boot.initrd.systemd.dmVerity = {
enable = lib.mkEnableOption "dm-verity" // {
description = ''
Mount verity-protected block devices in the initrd.
Enabling this option allows to use `systemd-veritysetup` and
`systemd-veritysetup-generator` in the initrd.
'';
};
};
};
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = config.boot.initrd.systemd.enable;
message = ''
'boot.initrd.systemd.dmVerity.enable' requires 'boot.initrd.systemd.enable' to be enabled.
'';
}
];
boot.initrd = {
availableKernelModules = [
"dm_mod"
"dm_verity"
];
# dm-verity needs additional udev rules from LVM to work.
services.lvm.enable = true;
# The additional targets and store paths allow users to integrate verity-protected devices
# through the systemd tooling.
systemd = {
additionalUpstreamUnits = [
"veritysetup-pre.target"
"veritysetup.target"
"remote-veritysetup.target"
];
storePaths = [
"${config.boot.initrd.systemd.package}/lib/systemd/systemd-veritysetup"
"${config.boot.initrd.systemd.package}/lib/systemd/system-generators/systemd-veritysetup-generator"
];
};
};
};
meta.maintainers = with lib.maintainers; [
msanft
nikstur
willibutz
];
}

View File

@@ -0,0 +1,32 @@
{
lib,
config,
pkgs,
...
}:
let
cfg = config.boot.initrd.systemd;
in
{
options = {
boot.initrd.systemd.fido2.enable = lib.mkEnableOption "systemd FIDO2 support" // {
default = cfg.package.withFido2;
defaultText = lib.literalExpression "config.boot.initrd.systemd.package.withFido2";
};
};
config = lib.mkIf cfg.fido2.enable {
boot.initrd.services.udev.packages = [
# TODO: Add a better way to include upstream rules files.
(pkgs.runCommand "udev-fido2" { } ''
mkdir -p $out/lib/udev/rules.d/
cp ${cfg.package}/lib/udev/rules.d/60-fido-id.rules $out/lib/udev/rules.d/60-fido-id.rules
'')
];
boot.initrd.systemd.storePaths = [
"${pkgs.systemd}/lib/udev/fido_id"
"${cfg.package}/lib/cryptsetup/libcryptsetup-token-systemd-fido2.so"
"${pkgs.libfido2}/lib/libfido2.so.1"
];
};
}

View File

@@ -0,0 +1,94 @@
{
config,
lib,
utils,
...
}:
let
cfg = config.services.homed;
in
{
options.services.homed = {
enable = lib.mkEnableOption "systemd home area/user account manager";
promptOnFirstBoot =
lib.mkEnableOption ''
interactively prompting for user creation on first boot
''
// {
default = true;
};
settings.Home = lib.mkOption {
default = { };
type = lib.types.submodule {
freeformType = lib.types.attrsOf utils.systemdUtils.unitOptions.unitOption;
};
example = {
DefaultStorage = "luks";
DefaultFileSystemType = "btrfs";
};
description = ''
Options for systemd-homed. See {manpage}`homed.conf(5)` man page for
available options.
'';
};
};
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = config.services.nscd.enable;
message = ''
systemd-homed requires the use of the systemd nss module.
services.nscd.enable must be set to true.
'';
}
];
systemd.additionalUpstreamSystemUnits = [
"systemd-homed.service"
"systemd-homed-activate.service"
"systemd-homed-firstboot.service"
];
# homed exposes SSH public keys and other user metadata using userdb
services.userdbd = {
enable = true;
enableSSHSupport = lib.mkDefault config.services.openssh.enable;
};
# Enable creation and mounting of LUKS home areas with all filesystems
# supported by systemd-homed.
boot.supportedFilesystems = [
"btrfs"
"ext4"
"xfs"
];
environment.etc."systemd/homed.conf".text = ''
[Home]
${utils.systemdUtils.lib.attrsToSection cfg.settings.Home}
'';
systemd.services = {
systemd-homed = {
# These packages are required to manage home areas with LUKS storage
path = config.system.fsPackages;
aliases = [ "dbus-org.freedesktop.home1.service" ];
wantedBy = [ "multi-user.target" ];
};
systemd-homed-activate = {
wantedBy = [ "systemd-homed.service" ];
};
systemd-homed-firstboot = {
enable = cfg.promptOnFirstBoot;
wantedBy = [ "systemd-homed.service" ];
};
};
};
}

View File

@@ -0,0 +1,51 @@
{
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";
};
}

View File

@@ -0,0 +1,765 @@
{
lib,
options,
config,
utils,
pkgs,
...
}:
with lib;
let
inherit (utils) systemdUtils escapeSystemdPath;
inherit (systemdUtils.unitOptions) unitOption;
inherit (systemdUtils.lib)
generateUnits
pathToUnit
serviceToUnit
sliceToUnit
socketToUnit
targetToUnit
timerToUnit
mountToUnit
automountToUnit
settingsToSections
;
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"
"modprobe@.service"
"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"
"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";
initrdBinEnv = pkgs.buildEnv {
name = "initrd-bin-env";
paths = map getBin cfg.initrdBin;
pathsToLink = [
"/bin"
"/sbin"
];
# Make sure sbin and bin have the same contents, and add extraBin
postBuild = ''
find $out/bin -maxdepth 1 -type l -print0 | xargs --null cp --no-dereference --no-clobber -t $out/sbin/
find $out/sbin -maxdepth 1 -type l -print0 | xargs --null cp --no-dereference --no-clobber -t $out/bin/
${concatStringsSep "\n" (
mapAttrsToList (n: v: ''
ln -sf '${v}' $out/bin/'${n}'
ln -sf '${v}' $out/sbin/'${n}'
'') cfg.extraBin
)}
'';
};
initialRamdisk = pkgs.makeInitrdNG {
name = "initrd-${kernel-name}";
inherit (config.boot.initrd) compressor compressorArgs prepend;
contents = lib.filter (
{ source, enable, ... }: (!lib.elem source cfg.suppressedStorePaths) && enable
) cfg.storePaths;
};
in
{
imports = [
(lib.mkRemovedOptionModule [ "boot" "initrd" "systemd" "strip" ] ''
The option to strip ELF files in initrd has been removed.
It only saved ~1MiB of initramfs size, but caused a few issues
like unloadable kernel modules.
'')
(lib.mkRemovedOptionModule [
"boot"
"initrd"
"systemd"
"extraConfig"
] "Use boot.initrd.systemd.settings.Manager instead.")
];
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.
'';
};
settings.Manager = mkOption {
default = { };
defaultText = lib.literalExpression ''
{
DefaultEnvironment = "PATH=/bin:/sbin";
}
'';
type = lib.types.submodule {
freeformType = types.attrsOf unitOption;
};
example = {
WatchdogDevice = "/dev/watchdog";
RuntimeWatchdogSec = "30s";
RebootWatchdogSec = "10min";
KExecWatchdogSec = "5min";
};
description = ''
Options for the global systemd service manager used in initrd. See {manpage}`systemd-system.conf(5)` man page
for available options.
'';
};
managerEnvironment = mkOption {
type =
with types;
attrsOf (
nullOr (oneOf [
str
path
package
])
);
default = { };
defaultText = ''
{
PATH = "/bin:/sbin";
}
'';
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/machine-id".source = /etc/machine-id;
}
'';
default = { };
type = utils.systemdUtils.types.initrdContents;
};
storePaths = mkOption {
description = ''
Store paths to copy into the initrd as well.
'';
type = utils.systemdUtils.types.initrdStorePath;
default = [ ];
};
extraBin = mkOption {
description = ''
Tools to add to /bin
'';
example = literalExpression ''
{
umount = ''${pkgs.util-linux}/bin/umount;
}
'';
type = types.attrsOf types.path;
default = { };
};
suppressedStorePaths = mkOption {
description = ''
Store paths specified in the storePaths option that
should not be copied.
'';
type = types.listOf types.singleLineStr;
default = [ ];
};
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 or
null for no emergency access.
Can also be set to a hashed super user password to allow
authenticated access to the emergency mode.
For emergency access after initrd, use `${options.systemd.enableEmergencyMode}` instead.
'';
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.";
};
};
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.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
]
++ lib.optional (config.system.build.kernel.config.isYes "MODULES") cfg.package.kmod;
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";
settings.Manager.ManagerEnvironment = lib.concatStringsSep " " (
lib.mapAttrsToList (n: v: "${n}=${lib.escapeShellArg v}") cfg.managerEnvironment
);
settings.Manager.DefaultEnvironment = "PATH=/bin:/sbin";
contents = {
"/init".source = "${cfg.package}/lib/systemd/systemd";
"/etc/systemd/system".source = stage1Units;
"/etc/systemd/system.conf".text = settingsToSections cfg.settings;
# 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 =
let
ea = cfg.emergencyAccess;
access = ea != null && !(isBool ea && !ea);
passwd = if isString ea then ea else "";
in
"root:${if access then passwd else "*"}:::::::";
"/bin".source = "${initrdBinEnv}/bin";
"/sbin".source = "${initrdBinEnv}/sbin";
"/usr/bin".source = "${initrdBinEnv}/bin";
"/usr/sbin".source = "${initrdBinEnv}/sbin";
"/etc/os-release".source = config.boot.initrd.osRelease;
"/etc/initrd-release".source = config.boot.initrd.osRelease;
# For systemd-journald's _HOSTNAME field; needs to be set early, cannot be backfilled.
"/etc/hostname".text = config.networking.hostName;
}
// optionalAttrs (config.environment.etc ? "modprobe.d/nixos.conf") {
"/etc/modprobe.d/nixos.conf".source = config.environment.etc."modprobe.d/nixos.conf".source;
}
// optionalAttrs (with config.system.build.kernel.config; isSet "MODULES" -> isYes "MODULES") {
"/lib".source = "${config.system.build.modulesClosure}/lib";
"/etc/modules-load.d/nixos.conf".text = concatStringsSep "\n" config.boot.initrd.kernelModules;
"/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.kmod-blacklist-ubuntu}/modprobe.conf";
"/etc/modprobe.d/debian.conf".source = pkgs.kmod-debian-aliases;
};
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"
"${cfg.package}/lib/systemd/systemd-sysroot-fstab-check"
# 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 services generated with writeShellScript and friends
pkgs.runtimeShell
# some tools like xfs still want the sh symlink
"${pkgs.bashNonInteractive}/bin"
# so NSS can look up usernames
"${pkgs.glibc}/lib/libnss_files.so.2"
# Resolving sysroot symlinks without code exec
"${config.system.nixos-init.package}/bin/chroot-realpath"
# Find the etc paths
"${config.system.nixos-init.package}/bin/find-etc"
]
++ lib.optionals config.system.nixos-init.enable [
"${config.system.nixos-init.package}/bin/initrd-init"
]
++ jobScripts
++ map (c: builtins.removeAttrs c [ "text" ]) (builtins.attrValues cfg.contents);
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
);
services.initrd-find-nixos-closure = lib.mkIf (!config.system.nixos-init.enable) {
description = "Find NixOS closure";
unitConfig = {
RequiresMountsFor = "/sysroot/nix/store";
DefaultDependencies = false;
};
before = [
"initrd.target"
"shutdown.target"
];
conflicts = [ "shutdown.target" ];
requiredBy = [ "initrd.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = # bash
''
set -uo pipefail
export PATH="/bin:${
lib.makeBinPath [
cfg.package.util-linux
config.system.nixos-init.package
]
}"
# 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-realpath /sysroot "$closure")"
# Assume the directory containing the init script is the closure.
closure="$(dirname "$closure")"
ln --symbolic "$closure" /nixos-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.
mounts = [
{
where = "/sysroot/run";
what = "/run";
options = "rbind";
unitConfig = {
# See the comment on the mount unit for /run/etc-metadata
DefaultDependencies = false;
};
requiredBy = [ "initrd-fs.target" ];
before = [ "initrd-fs.target" ];
}
];
services.initrd-nixos-activation = lib.mkIf (!config.system.nixos-init.enable) {
after = [ "initrd-switch-root.target" ];
requiredBy = [ "initrd-switch-root.service" ];
before = [ "initrd-switch-root.service" ];
unitConfig.DefaultDependencies = false;
unitConfig = {
AssertPathExists = "/etc/initrd-release";
RequiresMountsFor = [
"/sysroot/run"
];
};
serviceConfig.Type = "oneshot";
description = "NixOS Activation";
script = # bash
''
set -uo pipefail
export PATH="/bin:${cfg.package.util-linux}/bin"
closure="$(realpath /nixos-closure)"
# Initialize the system
export IN_NIXOS_SYSTEMD_STAGE1=true
exec chroot /sysroot "$closure/prepare-root"
'';
};
services.initrd-switch-root =
if config.system.nixos-init.enable then
{
path = [
cfg.package
cfg.package.util-linux
config.system.nixos-init.package
];
environment = {
FIRMWARE = "${config.hardware.firmware}/lib/firmware";
MODPROBE_BINARY = "${pkgs.kmod}/bin/modprobe";
NIX_STORE_MOUNT_OPTS = lib.concatStringsSep "," config.boot.nixStoreMountOpts;
}
// lib.optionalAttrs (config.environment.usrbinenv != null) {
ENV_BINARY = config.environment.usrbinenv;
}
// lib.optionalAttrs (config.environment.binsh != null) {
SH_BINARY = config.environment.binsh;
};
serviceConfig = {
ExecStart = [
""
"${config.system.nixos-init.package}/bin/initrd-init"
];
};
}
else
# 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
{
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"
];
};
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.coreutils}/bin/echo c";
StandardOutput = "file:/proc/sysrq-trigger";
};
};
};
};
}

View File

@@ -0,0 +1,147 @@
{
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)
];
};
};
}

View File

@@ -0,0 +1,174 @@
{
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;
};
}

View File

@@ -0,0 +1,116 @@
{
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;
};
}

View File

@@ -0,0 +1,168 @@
{
config,
lib,
pkgs,
utils,
...
}:
let
cfg = config.services.journald;
in
{
imports = [
(lib.mkRenamedOptionModule
[ "services" "journald" "enableHttpGateway" ]
[ "services" "journald" "gateway" "enable" ]
)
];
options = {
services.journald.console = lib.mkOption {
default = "";
type = lib.types.str;
description = "If non-empty, write log messages to the specified TTY device.";
};
services.journald.rateLimitInterval = lib.mkOption {
default = "30s";
type = lib.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 = lib.mkOption {
default = "persistent";
type = lib.types.enum [
"persistent"
"volatile"
"auto"
"none"
];
description = ''
Controls where to store journal data. See
{manpage}`journald.conf(5)` for further information.
'';
};
services.journald.rateLimitBurst = lib.mkOption {
default = 10000;
type = lib.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
{manpage}`journald.conf(5)`.
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.audit = lib.mkOption {
default = null;
type = lib.types.nullOr lib.types.bool;
description = ''
If enabled systemd-journald will turn on auditing on start-up.
If disabled it will turn it off. If unset it will neither enable nor disable it, leaving the previous state unchanged.
NixOS defaults to leaving this unset as enabling audit without auditd running leads to spamming /dev/kmesg with random messages
and if you enable auditd then auditd is responsible for turning auditing on.
If you want to have audit logs in journald and do not mind audit logs also ending up in /dev/kmesg you can set this option to true.
If you want to for some ununderstandable reason disable auditing if auditd enabled it then you can set this option to false.
It is of NixOS' opinion that setting this to false is definitely the wrong thing to do - but it's an option.
'';
};
services.journald.extraConfig = lib.mkOption {
default = "";
type = lib.types.lines;
example = "Storage=volatile";
description = ''
Extra config options for systemd-journald. See {manpage}`journald.conf(5)`
for available options.
'';
};
services.journald.forwardToSyslog = lib.mkOption {
default = config.services.rsyslogd.enable || config.services.syslog-ng.enable;
defaultText = lib.literalExpression "services.rsyslogd.enable || services.syslog-ng.enable";
type = lib.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"
"systemd-journald-sync@.service"
"systemd-journald-audit.socket"
"systemd-journald-dev-log.socket"
"syslog.socket"
];
systemd.sockets.systemd-journald-audit.wantedBy = [
"systemd-journald.service"
"sockets.target"
];
environment.etc = {
"systemd/journald.conf".text = ''
[Journal]
Storage=${cfg.storage}
RateLimitInterval=${cfg.rateLimitInterval}
RateLimitBurst=${toString cfg.rateLimitBurst}
${lib.optionalString (cfg.console != "") ''
ForwardToConsole=yes
TTYPath=${cfg.console}
''}
${lib.optionalString (cfg.forwardToSyslog) ''
ForwardToSyslog=yes
''}
Audit=${utils.systemdUtils.lib.toOption cfg.audit}
${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;
};
}

View File

@@ -0,0 +1,108 @@
{
config,
lib,
utils,
...
}:
{
options.services.logind = {
settings.Login = lib.mkOption {
description = ''
Settings option for systemd-logind.
See {manpage}`logind.conf(5)` for available options.
'';
type = lib.types.submodule {
freeformType = lib.types.attrsOf utils.systemdUtils.unitOptions.unitOption;
options.KillUserProcesses = lib.mkOption {
default = false;
type = lib.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 {manpage}`systemd.scope(5)`),
and processes are not killed.
See {manpage}`logind.conf(5)` for more details.
Defaulted to false in nixpkgs because many tools that rely on
persistent user processeslike `tmux`, `screen`, `mosh`, `VNC`,
`nohup`, and more would break by the systemd-default behavior.
'';
};
};
default = { };
example = {
KillUserProcesses = false;
HandleLidSwitch = "ignore";
};
};
};
config = {
systemd.additionalUpstreamSystemUnits = [
"systemd-logind.service"
"autovt@.service"
"systemd-user-sessions.service"
]
++ lib.optionals config.systemd.package.withImportd [
"dbus-org.freedesktop.import1.service"
]
++ lib.optionals config.systemd.package.withMachined [
"dbus-org.freedesktop.machine1.service"
]
++ lib.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 =
utils.systemdUtils.lib.settingsToSections config.services.logind.settings;
# 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;
};
imports =
let
settingsRename =
old: new:
lib.mkRenamedOptionModule
[ "services" "logind" old ]
[ "services" "logind" "settings" "Login" new ];
in
[
(lib.mkRemovedOptionModule [
"services"
"logind"
"extraConfig"
] "Use services.logind.settings.Login instead.")
(settingsRename "killUserProcesses" "KillUserProcesses")
(settingsRename "powerKey" "HandlePowerKey")
(settingsRename "powerKeyLongPress" "HandlePowerKeyLongPress")
(settingsRename "rebootKey" "HandleRebootKey")
(settingsRename "rebootKeyLongPress" "HandleRebootKeyLongPress")
(settingsRename "suspendKey" "HandleSuspendKey")
(settingsRename "suspendKeyLongPress" "HandleSuspendKeyLongPress")
(settingsRename "hibernateKey" "HandleHibernateKey")
(settingsRename "hibernateKeyLongPress" "HandleHibernateKeyLongPress")
(settingsRename "lidSwitch" "HandleLidSwitch")
(settingsRename "lidSwitchExternalPower" "HandleLidSwitchExternalPower")
(settingsRename "lidSwitchDocked" "HandleLidSwitchDocked")
];
}

View File

@@ -0,0 +1,208 @@
{
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" ];
systemd.services."systemd-nspawn@".environment = {
SYSTEMD_NSPAWN_UNIFIED_HIERARCHY = mkDefault "1";
};
}
];
}

View File

@@ -0,0 +1,89 @@
{
config,
lib,
utils,
...
}:
let
cfg = config.systemd.oomd;
in
{
imports = [
(lib.mkRenamedOptionModule
[ "systemd" "oomd" "enableUserServices" ]
[ "systemd" "oomd" "enableUserSlices" ]
)
(lib.mkRenamedOptionModule [ "systemd" "oomd" "extraConfig" ] [ "systemd" "oomd" "settings" "OOM" ])
];
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";
settings.OOM = lib.mkOption {
description = ''
Settings option for systemd-oomd.
See {manpage}`oomd.conf(5)` for available options.
'';
type = lib.types.submodule {
freeformType = lib.types.attrsOf utils.systemdUtils.unitOptions.unitOption;
};
default = { };
example = {
DefaultMemoryPressureLimit = "60%";
};
};
};
config = lib.mkIf cfg.enable {
systemd.additionalUpstreamSystemUnits = [
"systemd-oomd.service"
"systemd-oomd.socket"
];
systemd.services.systemd-oomd.after = [
"swap.target" # TODO: drop after systemd v258
"systemd-sysusers.service" # TODO: drop after systemd v257.8
];
systemd.services.systemd-oomd.wantedBy = [ "multi-user.target" ];
environment.etc."systemd/oomd.conf".text = utils.systemdUtils.lib.settingsToSections cfg.settings;
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";
};
};
}

View File

@@ -0,0 +1,220 @@
{
config,
lib,
pkgs,
utils,
...
}:
let
cfg = config.systemd.repart;
initrdCfg = config.boot.initrd.systemd.repart;
format = pkgs.formats.ini { listsAsDuplicateKeys = true; };
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";
};
empty = lib.mkOption {
type = lib.types.enum [
"refuse"
"allow"
"require"
"force"
"create"
];
description = ''
Controls how to operate on empty devices that contain no partition table yet.
See {manpage}`systemd-repart(8)` for details.
'';
example = "require";
default = "refuse";
};
discard = lib.mkOption {
type = lib.types.bool;
description = ''
Controls whether to issue the BLKDISCARD I/O control command on the
space taken up by any added partitions or on the space in between them.
Usually, it's a good idea to issue this request since it tells the underlying
hardware that the covered blocks shall be considered empty, improving performance.
See {manpage}`systemd-repart(8)` for details.
'';
default = true;
};
extraArgs = lib.mkOption {
description = ''
Extra command-line arguments to pass to systemd-repart.
See {manpage}`systemd-repart(8)` for all available options.
'';
type = lib.types.listOf lib.types.str;
default = [ ];
};
};
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
(listOf str)
])
);
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 {manpage}`repart.d(5)`
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 \
--empty=${initrdCfg.empty} \
--discard=${lib.boolToString initrdCfg.discard} \
${utils.escapeSystemdExecArgs initrdCfg.extraArgs} \
${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 ];
}

View File

@@ -0,0 +1,80 @@
{
config,
lib,
utils,
pkgs,
...
}:
let
cfg = config.systemd.shutdownRamfs;
ramfsContents = pkgs.writeText "shutdown-ramfs-contents.json" (builtins.toJSON cfg.storePaths);
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 = utils.systemdUtils.types.initrdStorePath;
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"
]
++ map (c: builtins.removeAttrs c [ "text" ]) (builtins.attrValues cfg.contents);
systemd.mounts = [
{
what = "tmpfs";
where = "/run/initramfs";
type = "tmpfs";
options = "mode=0700";
}
];
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";
};
};
};
}

View File

@@ -0,0 +1,159 @@
{
config,
lib,
pkgs,
utils,
...
}:
let
cfg = config.systemd.sysupdate;
format = pkgs.formats.ini { listToValue = toString; };
# TODO: Switch back to using utils.systemdUtils.lib.definitions once
# https://github.com/systemd/systemd/pull/38187 is resolved. Also ensure
# utils.systemdUtils.lib.definitions is capable of setting a custom file
# suffix.
sysupdateTransfers = lib.mapAttrs' (name: value: {
name = "sysupdate.d/${name}.transfer";
value.source = format.generate "${name}.transfer" value;
}) 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 {manpage}`systemd-sysupdate(8)` 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 {manpage}`sysupdate.d(5)`
for all available options.
'';
};
};
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = config.systemd.package.withSysupdate;
message = "Cannot enable systemd-sysupdate with systemd package not built with sysupdate support";
}
];
systemd.additionalUpstreamSystemUnits = [
"systemd-sysupdate.service"
"systemd-sysupdate.timer"
"systemd-sysupdate-reboot.service"
"systemd-sysupdate-reboot.timer"
"systemd-sysupdated.service"
];
systemd.services.systemd-sysupdated.aliases = [ "dbus-org.freedesktop.sysupdate1.service" ];
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 = sysupdateTransfers;
};
meta.maintainers = with lib.maintainers; [
nikstur
jmbaur
];
}

View File

@@ -0,0 +1,209 @@
{
config,
lib,
pkgs,
utils,
...
}:
let
cfg = config.systemd.sysusers;
userCfg = config.users;
systemUsers = lib.filterAttrs (_username: opts: opts.enable && !opts.isNormalUser) userCfg.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}''
) systemUsers
)}
# 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
)}
'';
immutableEtc = config.system.etc.overlay.enable && !config.system.etc.overlay.mutable;
# The location of the password files when using an immutable /etc.
immutablePasswordFilesLocation = "/var/lib/nixos/etc";
passwordFilesLocation = if immutableEtc then immutablePasswordFilesLocation else "/etc";
# The filenames created by systemd-sysusers.
passwordFiles = [
"passwd"
"group"
"shadow"
"gshadow"
];
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";
}
]
++ (lib.mapAttrsToList (username: opts: {
assertion = opts.enable -> !opts.isNormalUser;
message = "${username} is a normal user. systemd-sysusers doesn't create normal users, only system users.";
}) userCfg.users)
++ lib.mapAttrsToList (username: opts: {
assertion =
(opts.password == opts.initialPassword || opts.password == null)
&& (opts.hashedPassword == opts.initialHashedPassword || opts.hashedPassword == null);
message = "user '${username}' uses password or hashedPassword. systemd-sysupdate only supports initial passwords. It'll never update your passwords.";
}) systemUsers;
systemd = {
# 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") systemUsers);
# 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) systemUsers);
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);
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 = {
# When we have an immutable /etc we cannot write the files directly
# to /etc so we write it to a different directory and symlink them
# into /etc.
#
# We need to explicitly list the config file, otherwise
# systemd-sysusers cannot find it when we also pass another flag.
ExecStart = lib.mkIf immutableEtc [
""
"${config.systemd.package}/bin/systemd-sysusers --root ${builtins.dirOf immutablePasswordFilesLocation} /etc/sysusers.d/00-nixos.conf"
];
# Make the source files writable before executing sysusers.
ExecStartPre = lib.mkIf (!userCfg.mutableUsers) (
lib.map (file: "-${pkgs.util-linux}/bin/umount ${passwordFilesLocation}/${file}") passwordFiles
);
# Make the source files read-only after sysusers has finished.
ExecStartPost = lib.mkIf (!userCfg.mutableUsers) (
lib.map (
file:
"${pkgs.util-linux}/bin/mount --bind -o ro ${passwordFilesLocation}/${file} ${passwordFilesLocation}/${file}"
) passwordFiles
);
LoadCredential = lib.mapAttrsToList (
username: opts: "passwd.hashed-password.${username}:${opts.hashedPasswordFile}"
) (lib.filterAttrs (_username: opts: opts.hashedPasswordFile != null) systemUsers);
SetCredential =
(lib.mapAttrsToList (
username: opts: "passwd.hashed-password.${username}:${opts.initialHashedPassword}"
) (lib.filterAttrs (_username: opts: opts.initialHashedPassword != null) systemUsers))
++ (lib.mapAttrsToList (
username: opts: "passwd.plaintext-password.${username}:${opts.initialPassword}"
) (lib.filterAttrs (_username: opts: opts.initialPassword != null) systemUsers));
};
};
};
environment.etc = lib.mkMerge [
{
"sysusers.d".source = sysusersConfig;
}
# Statically create the symlinks to immutablePasswordFilesLocation when
# using an immutable /etc because we will not be able to do it at
# runtime!
(lib.mkIf immutableEtc (
lib.listToAttrs (
lib.map (
file:
lib.nameValuePair file {
source = "${immutablePasswordFilesLocation}/${file}";
mode = "direct-symlink";
}
) passwordFiles
)
))
];
};
meta.maintainers = with lib.maintainers; [ nikstur ];
}

View File

@@ -0,0 +1,427 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
cfg = config.systemd.tmpfiles;
initrdCfg = config.boot.initrd.systemd.tmpfiles;
systemd = config.systemd.package;
attrsWith' =
placeholder: elemType:
types.attrsWith {
inherit elemType placeholder;
};
escapeArgument = lib.strings.escapeC [
"\t"
"\n"
"\r"
" "
"\\"
];
settingsOption = {
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 = attrsWith' "config-name" (
attrsWith' "path" (
attrsWith' "tmpfiles-type" (
types.submodule (
{ name, config, ... }:
{
options.type = mkOption {
type = types.str;
default = name;
defaultText = "tmpfiles-type";
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:
{manpage}`tmpfiles.d(5)`
'';
};
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:
{manpage}`tmpfiles.d(5)`
'';
};
}
)
)
)
);
};
# generates a single entry for a tmpfiles.d rule
settingsEntryToRule = path: entry: ''
'${entry.type}' '${path}' '${entry.mode}' '${entry.user}' '${entry.group}' '${entry.age}' ${escapeArgument entry.argument}
'';
# generates a list of tmpfiles.d rules from the attrs (paths) under tmpfiles.settings.<name>
pathsToRules = mapAttrsToList (
path: types: concatStrings (mapAttrsToList (_type: settingsEntryToRule path) types)
);
mkRuleFileContent = paths: concatStrings (pathsToRules paths);
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 settingsOption;
boot.initrd.systemd.tmpfiles.settings = mkOption (
settingsOption
// {
description = ''
Similar to {option}`systemd.tmpfiles.settings` but the rules are
only applied by systemd-tmpfiles before `initrd-switch-root.target`.
See {manpage}`bootup(7)`.
'';
}
);
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 = {
warnings =
let
paths = lib.filter (path: path != null && lib.hasPrefix "/etc/tmpfiles.d/" path) (
map (path: path.target) config.boot.initrd.systemd.storePaths
);
in
lib.optional (lib.length paths > 0) (
lib.concatStringsSep " " [
"Files inside /etc/tmpfiles.d in the initrd need to be created with"
"boot.initrd.systemd.tmpfiles.settings."
"Creating them by hand using boot.initrd.systemd.contents or"
"boot.initrd.systemd.storePaths will lead to errors in the future."
"Found these problematic files: ${lib.concatStringsSep ", " paths}"
]
)
++ (lib.flatten (
lib.mapAttrsToList (
name: paths:
lib.mapAttrsToList (
path: entries:
lib.mapAttrsToList (
type': entry:
lib.optional (lib.match ''.*\\([nrt]|x[0-9A-Fa-f]{2}).*'' entry.argument != null) (
lib.concatStringsSep " " [
"The argument option of ${name}.${type'}.${path} appears to"
"contain escape sequences, which will be escaped again."
"Unescape them if this is not intended: \"${entry.argument}\""
]
)
) entries
) paths
) cfg.settings
));
systemd.additionalUpstreamSystemUnits = [
"systemd-tmpfiles-clean.service"
"systemd-tmpfiles-clean.timer"
"systemd-tmpfiles-setup-dev-early.service"
"systemd-tmpfiles-setup-dev.service"
"systemd-tmpfiles-setup.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"
];
RestrictSUIDSGID = false;
};
};
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;
})
+ "/*";
"mtab" = {
mode = "direct-symlink";
source = "/proc/mounts";
};
};
systemd.tmpfiles.packages = [
# Default tmpfiles rules provided by systemd
(pkgs.runCommand "systemd-default-tmpfiles" { } ''
mkdir -p $out/lib/tmpfiles.d
cd $out/lib/tmpfiles.d
ln -s "${systemd}/example/tmpfiles.d/home.conf"
ln -s "${systemd}/example/tmpfiles.d/journal-nocow.conf"
ln -s "${systemd}/example/tmpfiles.d/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" (mkRuleFileContent paths)
) cfg.settings);
systemd.tmpfiles.rules = [
"d /run/lock 0755 root root - -"
"d /var/db 0755 root root - -"
"L /var/lock - - - - ../run/lock"
]
++ lib.optionals config.nix.enable [
"d /nix/var 0755 root root - -"
"L+ /nix/var/nix/gcroots/booted-system 0755 root root - /run/booted-system"
]
# Boot-time cleanup
++ [
"R! /etc/group.lock - - - - -"
"R! /etc/passwd.lock - - - - -"
"R! /etc/shadow.lock - - - - -"
]
++ lib.optionals config.nix.enable [
"R! /nix/var/nix/gcroots/tmp - - - - -"
"R! /nix/var/nix/temproots - - - - -"
];
boot.initrd.systemd = {
additionalUpstreamUnits = [
"systemd-tmpfiles-setup-dev-early.service"
"systemd-tmpfiles-setup-dev.service"
"systemd-tmpfiles-setup.service"
];
# override to exclude the prefix /sysroot, because it is not necessarily set up when the unit starts
services.systemd-tmpfiles-setup.serviceConfig = {
ExecStart = [
""
"systemd-tmpfiles --create --remove --boot --exclude-prefix=/dev --exclude-prefix=/sysroot"
];
};
# sets up files under the prefix /sysroot, after the hierarchy is available and before nixos activation
services.systemd-tmpfiles-setup-sysroot = {
description = "Create Volatile Files and Directories in the Real Root";
after = [ "initrd-fs.target" ];
before = [
"initrd.target"
"shutdown.target"
"initrd-switch-root.target"
];
conflicts = [
"shutdown.target"
"initrd-switch-root.target"
];
wantedBy = [ "initrd.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = "systemd-tmpfiles --create --remove --boot --exclude-prefix=/dev --prefix=/sysroot";
SuccessExitStatus = [ "DATAERR CANTCREAT" ];
ImportCredential = [
"tmpfiles.*"
"login.motd"
"login.issue"
"network.hosts"
"ssh.authorized_keys.root"
];
};
unitConfig = {
DefaultDependencies = false;
RefuseManualStop = true;
};
};
contents."/etc/tmpfiles.d" = mkIf (initrdCfg.settings != { }) {
source = pkgs.linkFarm "initrd-tmpfiles.d" (
mapAttrsToList (name: paths: {
name = "${name}.conf";
path = pkgs.writeText "${name}.conf" (mkRuleFileContent paths);
}) initrdCfg.settings
);
};
};
};
}

View File

@@ -0,0 +1,81 @@
{
lib,
config,
pkgs,
...
}:
{
meta.maintainers = [ lib.maintainers.elvishjerricco ];
imports = [
(lib.mkRenamedOptionModule
[
"boot"
"initrd"
"systemd"
"enableTpm2"
]
[
"boot"
"initrd"
"systemd"
"tpm2"
"enable"
]
)
];
options = {
systemd.tpm2.enable = lib.mkEnableOption "systemd TPM2 support" // {
default = config.systemd.package.withTpm2Units;
defaultText = "systemd.package.withTpm2Units";
};
boot.initrd.systemd.tpm2.enable = lib.mkEnableOption "systemd initrd TPM2 support" // {
default = config.boot.initrd.systemd.package.withTpm2Units;
defaultText = "boot.initrd.systemd.package.withTpm2Units";
};
};
# TODO: pcrphase, pcrextend, pcrfs, pcrmachine
config = lib.mkMerge [
# Stage 2
(
let
cfg = config.systemd;
in
lib.mkIf cfg.tpm2.enable {
systemd.additionalUpstreamSystemUnits = [
"tpm2.target"
"systemd-tpm2-setup-early.service"
"systemd-tpm2-setup.service"
];
}
)
# Stage 1
(
let
cfg = config.boot.initrd.systemd;
in
lib.mkIf (cfg.enable && cfg.tpm2.enable) {
boot.initrd.systemd.additionalUpstreamUnits = [
"tpm2.target"
"systemd-tpm2-setup-early.service"
];
boot.initrd.availableKernelModules = [
"tpm-tis"
]
++ lib.optional (
!(pkgs.stdenv.hostPlatform.isRiscV64 || pkgs.stdenv.hostPlatform.isArmv7)
) "tpm-crb";
boot.initrd.systemd.storePaths = [
pkgs.tpm2-tss
"${cfg.package}/lib/systemd/systemd-tpm2-setup"
"${cfg.package}/lib/systemd/system-generators/systemd-tpm2-generator"
];
}
)
];
}

View File

@@ -0,0 +1,269 @@
{
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"
"capsule@.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 = {
enable =
(mkEnableOption "systemd user units systemd-tmpfiles-setup.service and systemd-tmpfiles-clean.timer")
// {
default = true;
example = false;
};
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;
systemd.user.timers = {
# enable systemd user tmpfiles
systemd-tmpfiles-clean.wantedBy = optional cfg.tmpfiles.enable "timers.target";
}
# Generate timer units for all services that have a startAt value.
// (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.enable "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
'';
};
}

View File

@@ -0,0 +1,84 @@
{ config, lib, ... }:
let
cfg = config.services.userdbd;
# List of system users that will be incorrectly treated as regular/normal
# users by userdb.
highSystemUsers = lib.filter (
user: user.enable && user.isSystemUser && (lib.defaultTo 0 user.uid) >= 1000 && user.uid != 65534
) (lib.attrValues config.users.users);
in
{
options.services.userdbd = {
enable = lib.mkEnableOption ''
the systemd JSON user/group record lookup service
'';
enableSSHSupport = lib.mkEnableOption ''
exposing OpenSSH public keys defined in userdb. Be aware that this
enables modifying public keys at runtime, either by users managed by
{option}`services.homed`, or globally via drop-in files
'';
silenceHighSystemUsers = lib.mkOption {
type = lib.types.bool;
default = false;
example = true;
description = "Silence warning about system users with high UIDs.";
visible = false;
};
};
config = lib.mkIf cfg.enable {
assertions = lib.singleton {
assertion = cfg.enableSSHSupport -> config.security.enableWrappers;
message = "OpenSSH userdb integration requires security wrappers.";
};
warnings = lib.optional (lib.length highSystemUsers > 0 && !cfg.silenceHighSystemUsers) ''
The following system users have UIDs higher than 1000:
${lib.concatLines (lib.map (user: user.name) highSystemUsers)}
These users will be recognized by systemd-userdb as "regular" users, not
"system" users. This will affect programs that query regular users, such
as systemd-homed, which will not run the first boot user creation flow,
as regular users already exist.
To fix this issue, please remove or redefine these system users to have
UIDs below 1000. For Nix build users, it's possible to adjust the base
build user ID using the `ids.uids.nixbld` option, however care must be
taken to avoid collisions with UIDs of other services. Alternatively, you
may enable the `auto-allocate-uids` experimental feature and option in
the Nix configuration to avoid creating these users, however please note
that this option is experimental and subject to change.
Alternatively, to acknowledge and silence this warning, set
`services.userdbd.silenceHighSystemUsers` to true.
'';
systemd.additionalUpstreamSystemUnits = [
"systemd-userdbd.socket"
"systemd-userdbd.service"
];
systemd.sockets.systemd-userdbd.wantedBy = [ "sockets.target" ];
# OpenSSH requires AuthorizedKeysCommand to be owned only by root.
# Referencing `userdbctl` directly from the Nix store won't work, as
# `/nix/store` is owned by the `nixbld` group.
security.wrappers = lib.mkIf cfg.enableSSHSupport {
userdbctl = {
owner = "root";
group = "root";
source = lib.getExe' config.systemd.package "userdbctl";
};
};
services.openssh = lib.mkIf cfg.enableSSHSupport {
authorizedKeysCommand = "/run/wrappers/bin/userdbctl ssh-authorized-keys %u";
authorizedKeysCommandUser = "root";
};
};
}