1{ config, lib, pkgs, utils, ... }:
2
3with lib;
4
5let
6 cfg = config.systemd.tmpfiles;
7 systemd = config.systemd.package;
8in
9{
10 options = {
11 systemd.tmpfiles.rules = mkOption {
12 type = types.listOf types.str;
13 default = [];
14 example = [ "d /tmp 1777 root root 10d" ];
15 description = ''
16 Rules for creation, deletion and cleaning of volatile and temporary files
17 automatically. See
18 {manpage}`tmpfiles.d(5)`
19 for the exact format.
20 '';
21 };
22
23 systemd.tmpfiles.settings = mkOption {
24 description = ''
25 Declare systemd-tmpfiles rules to create, delete, and clean up volatile
26 and temporary files and directories.
27
28 Even though the service is called `*tmp*files` you can also create
29 persistent files.
30 '';
31 example = {
32 "10-mypackage" = {
33 "/var/lib/my-service/statefolder".d = {
34 mode = "0755";
35 user = "root";
36 group = "root";
37 };
38 };
39 };
40 default = {};
41 type = types.attrsOf (types.attrsOf (types.attrsOf (types.submodule ({ name, config, ... }: {
42 options.type = mkOption {
43 type = types.str;
44 default = name;
45 example = "d";
46 description = ''
47 The type of operation to perform on the file.
48
49 The type consists of a single letter and optionally one or more
50 modifier characters.
51
52 Please see the upstream documentation for the available types and
53 more details:
54 <https://www.freedesktop.org/software/systemd/man/tmpfiles.d>
55 '';
56 };
57 options.mode = mkOption {
58 type = types.str;
59 default = "-";
60 example = "0755";
61 description = ''
62 The file access mode to use when creating this file or directory.
63 '';
64 };
65 options.user = mkOption {
66 type = types.str;
67 default = "-";
68 example = "root";
69 description = ''
70 The user of the file.
71
72 This may either be a numeric ID or a user/group name.
73
74 If omitted or when set to `"-"`, the user and group of the user who
75 invokes systemd-tmpfiles is used.
76 '';
77 };
78 options.group = mkOption {
79 type = types.str;
80 default = "-";
81 example = "root";
82 description = ''
83 The group of the file.
84
85 This may either be a numeric ID or a user/group name.
86
87 If omitted or when set to `"-"`, the user and group of the user who
88 invokes systemd-tmpfiles is used.
89 '';
90 };
91 options.age = mkOption {
92 type = types.str;
93 default = "-";
94 example = "10d";
95 description = ''
96 Delete a file when it reaches a certain age.
97
98 If a file or directory is older than the current time minus the age
99 field, it is deleted.
100
101 If set to `"-"` no automatic clean-up is done.
102 '';
103 };
104 options.argument = mkOption {
105 type = types.str;
106 default = "";
107 example = "";
108 description = ''
109 An argument whose meaning depends on the type of operation.
110
111 Please see the upstream documentation for the meaning of this
112 parameter in different situations:
113 <https://www.freedesktop.org/software/systemd/man/tmpfiles.d>
114 '';
115 };
116 }))));
117 };
118
119 systemd.tmpfiles.packages = mkOption {
120 type = types.listOf types.package;
121 default = [];
122 example = literalExpression "[ pkgs.lvm2 ]";
123 apply = map getLib;
124 description = ''
125 List of packages containing {command}`systemd-tmpfiles` rules.
126
127 All files ending in .conf found in
128 {file}`«pkg»/lib/tmpfiles.d`
129 will be included.
130 If this folder does not exist or does not contain any files an error will be returned instead.
131
132 If a {file}`lib` output is available, rules are searched there and only there.
133 If there is no {file}`lib` output it will fall back to {file}`out`
134 and if that does not exist either, the default output will be used.
135 '';
136 };
137 };
138
139 config = {
140 systemd.additionalUpstreamSystemUnits = [
141 "systemd-tmpfiles-clean.service"
142 "systemd-tmpfiles-clean.timer"
143 "systemd-tmpfiles-setup.service"
144 "systemd-tmpfiles-setup-dev.service"
145 ];
146
147 systemd.additionalUpstreamUserUnits = [
148 "systemd-tmpfiles-clean.service"
149 "systemd-tmpfiles-clean.timer"
150 "systemd-tmpfiles-setup.service"
151 ];
152
153 # Allow systemd-tmpfiles to be restarted by switch-to-configuration. This
154 # service is not pulled into the normal boot process. It only exists for
155 # switch-to-configuration.
156 #
157 # This needs to be a separate unit because it does not execute
158 # systemd-tmpfiles with `--boot` as that is supposed to only be executed
159 # once at boot time.
160 #
161 # Keep this aligned with the upstream `systemd-tmpfiles-setup.service` unit.
162 systemd.services."systemd-tmpfiles-resetup" = {
163 description = "Re-setup tmpfiles on a system that is already running.";
164
165 requiredBy = [ "sysinit-reactivation.target" ];
166 after = [ "local-fs.target" "systemd-sysusers.service" "systemd-journald.service" ];
167 before = [ "sysinit-reactivation.target" "shutdown.target" ];
168 conflicts = [ "shutdown.target" ];
169 restartTriggers = [ config.environment.etc."tmpfiles.d".source ];
170
171 unitConfig.DefaultDependencies = false;
172
173 serviceConfig = {
174 Type = "oneshot";
175 RemainAfterExit = true;
176 ExecStart = "systemd-tmpfiles --create --remove --exclude-prefix=/dev";
177 SuccessExitStatus = "DATAERR CANTCREAT";
178 ImportCredential = [
179 "tmpfiles.*"
180 "loging.motd"
181 "login.issue"
182 "network.hosts"
183 "ssh.authorized_keys.root"
184 ];
185 };
186 };
187
188 environment.etc = {
189 "tmpfiles.d".source = (pkgs.symlinkJoin {
190 name = "tmpfiles.d";
191 paths = map (p: p + "/lib/tmpfiles.d") cfg.packages;
192 postBuild = ''
193 for i in $(cat $pathsPath); do
194 (test -d "$i" && test $(ls "$i"/*.conf | wc -l) -ge 1) || (
195 echo "ERROR: The path '$i' from systemd.tmpfiles.packages contains no *.conf files."
196 exit 1
197 )
198 done
199 '' + concatMapStrings (name: optionalString (hasPrefix "tmpfiles.d/" name) ''
200 rm -f $out/${removePrefix "tmpfiles.d/" name}
201 '') config.system.build.etc.passthru.targets;
202 }) + "/*";
203 };
204
205 systemd.tmpfiles.packages = [
206 # Default tmpfiles rules provided by systemd
207 (pkgs.runCommand "systemd-default-tmpfiles" {} ''
208 mkdir -p $out/lib/tmpfiles.d
209 cd $out/lib/tmpfiles.d
210
211 ln -s "${systemd}/example/tmpfiles.d/home.conf"
212 ln -s "${systemd}/example/tmpfiles.d/journal-nocow.conf"
213 ln -s "${systemd}/example/tmpfiles.d/portables.conf"
214 ln -s "${systemd}/example/tmpfiles.d/static-nodes-permissions.conf"
215 ln -s "${systemd}/example/tmpfiles.d/systemd.conf"
216 ln -s "${systemd}/example/tmpfiles.d/systemd-nologin.conf"
217 ln -s "${systemd}/example/tmpfiles.d/systemd-nspawn.conf"
218 ln -s "${systemd}/example/tmpfiles.d/systemd-tmp.conf"
219 ln -s "${systemd}/example/tmpfiles.d/tmp.conf"
220 ln -s "${systemd}/example/tmpfiles.d/var.conf"
221 ln -s "${systemd}/example/tmpfiles.d/x11.conf"
222 '')
223 # User-specified tmpfiles rules
224 (pkgs.writeTextFile {
225 name = "nixos-tmpfiles.d";
226 destination = "/lib/tmpfiles.d/00-nixos.conf";
227 text = ''
228 # This file is created automatically and should not be modified.
229 # Please change the option ‘systemd.tmpfiles.rules’ instead.
230
231 ${concatStringsSep "\n" cfg.rules}
232 '';
233 })
234 ] ++ (mapAttrsToList (name: paths:
235 pkgs.writeTextDir "lib/tmpfiles.d/${name}.conf" (concatStrings (mapAttrsToList (path: types:
236 concatStrings (mapAttrsToList (_type: entry: ''
237 '${entry.type}' '${path}' '${entry.mode}' '${entry.user}' '${entry.group}' '${entry.age}' ${entry.argument}
238 '') types)
239 ) paths ))
240 ) cfg.settings);
241
242 systemd.tmpfiles.rules = [
243 "d /nix/var 0755 root root - -"
244 "L+ /nix/var/nix/gcroots/booted-system 0755 root root - /run/booted-system"
245 "d /run/lock 0755 root root - -"
246 "d /var/db 0755 root root - -"
247 "L /etc/mtab - - - - ../proc/mounts"
248 "L /var/lock - - - - ../run/lock"
249 # Boot-time cleanup
250 "R! /etc/group.lock - - - - -"
251 "R! /etc/passwd.lock - - - - -"
252 "R! /etc/shadow.lock - - - - -"
253 "R! /etc/mtab* - - - - -"
254 "R! /nix/var/nix/gcroots/tmp - - - - -"
255 "R! /nix/var/nix/temproots - - - - -"
256 ];
257 };
258}