depot/third_party/nixpkgs/nixos/modules/system/boot/systemd/tmpfiles.nix

359 lines
12 KiB
Nix
Raw Normal View History

{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.systemd.tmpfiles;
initrdCfg = config.boot.initrd.systemd.tmpfiles;
systemd = config.systemd.package;
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 = 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>
'';
};
}))));
};
# generates a single entry for a tmpfiles.d rule
settingsEntryToRule = path: entry: ''
'${entry.type}' '${path}' '${entry.mode}' '${entry.user}' '${entry.group}' '${entry.age}' ${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}"
]);
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"
];
};
};
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-nixos-activation.service"
"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);
};
};
};
}