at master 14 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8with lib; 9 10let 11 cfg = config.systemd.tmpfiles; 12 initrdCfg = config.boot.initrd.systemd.tmpfiles; 13 systemd = config.systemd.package; 14 15 attrsWith' = 16 placeholder: elemType: 17 types.attrsWith { 18 inherit elemType placeholder; 19 }; 20 21 escapeArgument = lib.strings.escapeC [ 22 "\t" 23 "\n" 24 "\r" 25 " " 26 "\\" 27 ]; 28 29 settingsOption = { 30 description = '' 31 Declare systemd-tmpfiles rules to create, delete, and clean up volatile 32 and temporary files and directories. 33 34 Even though the service is called `*tmp*files` you can also create 35 persistent files. 36 ''; 37 example = { 38 "10-mypackage" = { 39 "/var/lib/my-service/statefolder".d = { 40 mode = "0755"; 41 user = "root"; 42 group = "root"; 43 }; 44 }; 45 }; 46 default = { }; 47 type = attrsWith' "config-name" ( 48 attrsWith' "path" ( 49 attrsWith' "tmpfiles-type" ( 50 types.submodule ( 51 { name, config, ... }: 52 { 53 options.type = mkOption { 54 type = types.str; 55 default = name; 56 defaultText = "tmpfiles-type"; 57 example = "d"; 58 description = '' 59 The type of operation to perform on the file. 60 61 The type consists of a single letter and optionally one or more 62 modifier characters. 63 64 Please see the upstream documentation for the available types and 65 more details: 66 {manpage}`tmpfiles.d(5)` 67 ''; 68 }; 69 options.mode = mkOption { 70 type = types.str; 71 default = "-"; 72 example = "0755"; 73 description = '' 74 The file access mode to use when creating this file or directory. 75 ''; 76 }; 77 options.user = mkOption { 78 type = types.str; 79 default = "-"; 80 example = "root"; 81 description = '' 82 The user of the file. 83 84 This may either be a numeric ID or a user/group name. 85 86 If omitted or when set to `"-"`, the user and group of the user who 87 invokes systemd-tmpfiles is used. 88 ''; 89 }; 90 options.group = mkOption { 91 type = types.str; 92 default = "-"; 93 example = "root"; 94 description = '' 95 The group of the file. 96 97 This may either be a numeric ID or a user/group name. 98 99 If omitted or when set to `"-"`, the user and group of the user who 100 invokes systemd-tmpfiles is used. 101 ''; 102 }; 103 options.age = mkOption { 104 type = types.str; 105 default = "-"; 106 example = "10d"; 107 description = '' 108 Delete a file when it reaches a certain age. 109 110 If a file or directory is older than the current time minus the age 111 field, it is deleted. 112 113 If set to `"-"` no automatic clean-up is done. 114 ''; 115 }; 116 options.argument = mkOption { 117 type = types.str; 118 default = ""; 119 example = ""; 120 description = '' 121 An argument whose meaning depends on the type of operation. 122 123 Please see the upstream documentation for the meaning of this 124 parameter in different situations: 125 {manpage}`tmpfiles.d(5)` 126 ''; 127 }; 128 } 129 ) 130 ) 131 ) 132 ); 133 }; 134 135 # generates a single entry for a tmpfiles.d rule 136 settingsEntryToRule = path: entry: '' 137 '${entry.type}' '${path}' '${entry.mode}' '${entry.user}' '${entry.group}' '${entry.age}' ${escapeArgument entry.argument} 138 ''; 139 140 # generates a list of tmpfiles.d rules from the attrs (paths) under tmpfiles.settings.<name> 141 pathsToRules = mapAttrsToList ( 142 path: types: concatStrings (mapAttrsToList (_type: settingsEntryToRule path) types) 143 ); 144 145 mkRuleFileContent = paths: concatStrings (pathsToRules paths); 146in 147{ 148 options = { 149 systemd.tmpfiles.rules = mkOption { 150 type = types.listOf types.str; 151 default = [ ]; 152 example = [ "d /tmp 1777 root root 10d" ]; 153 description = '' 154 Rules for creation, deletion and cleaning of volatile and temporary files 155 automatically. See 156 {manpage}`tmpfiles.d(5)` 157 for the exact format. 158 ''; 159 }; 160 161 systemd.tmpfiles.settings = mkOption settingsOption; 162 163 boot.initrd.systemd.tmpfiles.settings = mkOption ( 164 settingsOption 165 // { 166 description = '' 167 Similar to {option}`systemd.tmpfiles.settings` but the rules are 168 only applied by systemd-tmpfiles before `initrd-switch-root.target`. 169 170 See {manpage}`bootup(7)`. 171 ''; 172 } 173 ); 174 175 systemd.tmpfiles.packages = mkOption { 176 type = types.listOf types.package; 177 default = [ ]; 178 example = literalExpression "[ pkgs.lvm2 ]"; 179 apply = map getLib; 180 description = '' 181 List of packages containing {command}`systemd-tmpfiles` rules. 182 183 All files ending in .conf found in 184 {file}`«pkg»/lib/tmpfiles.d` 185 will be included. 186 If this folder does not exist or does not contain any files an error will be returned instead. 187 188 If a {file}`lib` output is available, rules are searched there and only there. 189 If there is no {file}`lib` output it will fall back to {file}`out` 190 and if that does not exist either, the default output will be used. 191 ''; 192 }; 193 }; 194 195 config = { 196 warnings = 197 let 198 paths = lib.filter (path: path != null && lib.hasPrefix "/etc/tmpfiles.d/" path) ( 199 map (path: path.target) config.boot.initrd.systemd.storePaths 200 ); 201 in 202 lib.optional (lib.length paths > 0) ( 203 lib.concatStringsSep " " [ 204 "Files inside /etc/tmpfiles.d in the initrd need to be created with" 205 "boot.initrd.systemd.tmpfiles.settings." 206 "Creating them by hand using boot.initrd.systemd.contents or" 207 "boot.initrd.systemd.storePaths will lead to errors in the future." 208 "Found these problematic files: ${lib.concatStringsSep ", " paths}" 209 ] 210 ) 211 ++ (lib.flatten ( 212 lib.mapAttrsToList ( 213 name: paths: 214 lib.mapAttrsToList ( 215 path: entries: 216 lib.mapAttrsToList ( 217 type': entry: 218 lib.optional (lib.match ''.*\\([nrt]|x[0-9A-Fa-f]{2}).*'' entry.argument != null) ( 219 lib.concatStringsSep " " [ 220 "The argument option of ${name}.${type'}.${path} appears to" 221 "contain escape sequences, which will be escaped again." 222 "Unescape them if this is not intended: \"${entry.argument}\"" 223 ] 224 ) 225 ) entries 226 ) paths 227 ) cfg.settings 228 )); 229 230 systemd.additionalUpstreamSystemUnits = [ 231 "systemd-tmpfiles-clean.service" 232 "systemd-tmpfiles-clean.timer" 233 "systemd-tmpfiles-setup-dev-early.service" 234 "systemd-tmpfiles-setup-dev.service" 235 "systemd-tmpfiles-setup.service" 236 ]; 237 238 systemd.additionalUpstreamUserUnits = [ 239 "systemd-tmpfiles-clean.service" 240 "systemd-tmpfiles-clean.timer" 241 "systemd-tmpfiles-setup.service" 242 ]; 243 244 # Allow systemd-tmpfiles to be restarted by switch-to-configuration. This 245 # service is not pulled into the normal boot process. It only exists for 246 # switch-to-configuration. 247 # 248 # This needs to be a separate unit because it does not execute 249 # systemd-tmpfiles with `--boot` as that is supposed to only be executed 250 # once at boot time. 251 # 252 # Keep this aligned with the upstream `systemd-tmpfiles-setup.service` unit. 253 systemd.services."systemd-tmpfiles-resetup" = { 254 description = "Re-setup tmpfiles on a system that is already running."; 255 256 requiredBy = [ "sysinit-reactivation.target" ]; 257 after = [ 258 "local-fs.target" 259 "systemd-sysusers.service" 260 "systemd-journald.service" 261 ]; 262 before = [ 263 "sysinit-reactivation.target" 264 "shutdown.target" 265 ]; 266 conflicts = [ "shutdown.target" ]; 267 restartTriggers = [ config.environment.etc."tmpfiles.d".source ]; 268 269 unitConfig.DefaultDependencies = false; 270 271 serviceConfig = { 272 Type = "oneshot"; 273 RemainAfterExit = true; 274 ExecStart = "systemd-tmpfiles --create --remove --exclude-prefix=/dev"; 275 SuccessExitStatus = "DATAERR CANTCREAT"; 276 ImportCredential = [ 277 "tmpfiles.*" 278 "loging.motd" 279 "login.issue" 280 "network.hosts" 281 "ssh.authorized_keys.root" 282 ]; 283 RestrictSUIDSGID = false; 284 }; 285 }; 286 287 environment.etc = { 288 "tmpfiles.d".source = 289 (pkgs.symlinkJoin { 290 name = "tmpfiles.d"; 291 paths = map (p: p + "/lib/tmpfiles.d") cfg.packages; 292 postBuild = '' 293 for i in $(cat $pathsPath); do 294 (test -d "$i" && test $(ls "$i"/*.conf | wc -l) -ge 1) || ( 295 echo "ERROR: The path '$i' from systemd.tmpfiles.packages contains no *.conf files." 296 exit 1 297 ) 298 done 299 '' 300 + concatMapStrings ( 301 name: 302 optionalString (hasPrefix "tmpfiles.d/" name) '' 303 rm -f $out/${removePrefix "tmpfiles.d/" name} 304 '' 305 ) config.system.build.etc.passthru.targets; 306 }) 307 + "/*"; 308 "mtab" = { 309 mode = "direct-symlink"; 310 source = "/proc/mounts"; 311 }; 312 }; 313 314 systemd.tmpfiles.packages = [ 315 # Default tmpfiles rules provided by systemd 316 (pkgs.runCommand "systemd-default-tmpfiles" { } '' 317 mkdir -p $out/lib/tmpfiles.d 318 cd $out/lib/tmpfiles.d 319 320 ln -s "${systemd}/example/tmpfiles.d/home.conf" 321 ln -s "${systemd}/example/tmpfiles.d/journal-nocow.conf" 322 ln -s "${systemd}/example/tmpfiles.d/portables.conf" 323 ln -s "${systemd}/example/tmpfiles.d/static-nodes-permissions.conf" 324 ln -s "${systemd}/example/tmpfiles.d/systemd.conf" 325 ln -s "${systemd}/example/tmpfiles.d/systemd-nologin.conf" 326 ln -s "${systemd}/example/tmpfiles.d/systemd-nspawn.conf" 327 ln -s "${systemd}/example/tmpfiles.d/systemd-tmp.conf" 328 ln -s "${systemd}/example/tmpfiles.d/tmp.conf" 329 ln -s "${systemd}/example/tmpfiles.d/var.conf" 330 ln -s "${systemd}/example/tmpfiles.d/x11.conf" 331 '') 332 # User-specified tmpfiles rules 333 (pkgs.writeTextFile { 334 name = "nixos-tmpfiles.d"; 335 destination = "/lib/tmpfiles.d/00-nixos.conf"; 336 text = '' 337 # This file is created automatically and should not be modified. 338 # Please change the option systemd.tmpfiles.rules instead. 339 340 ${concatStringsSep "\n" cfg.rules} 341 ''; 342 }) 343 ] 344 ++ (mapAttrsToList ( 345 name: paths: pkgs.writeTextDir "lib/tmpfiles.d/${name}.conf" (mkRuleFileContent paths) 346 ) cfg.settings); 347 348 systemd.tmpfiles.rules = [ 349 "d /run/lock 0755 root root - -" 350 "d /var/db 0755 root root - -" 351 "L /var/lock - - - - ../run/lock" 352 ] 353 ++ lib.optionals config.nix.enable [ 354 "d /nix/var 0755 root root - -" 355 "L+ /nix/var/nix/gcroots/booted-system 0755 root root - /run/booted-system" 356 ] 357 # Boot-time cleanup 358 ++ [ 359 "R! /etc/group.lock - - - - -" 360 "R! /etc/passwd.lock - - - - -" 361 "R! /etc/shadow.lock - - - - -" 362 ] 363 ++ lib.optionals config.nix.enable [ 364 "R! /nix/var/nix/gcroots/tmp - - - - -" 365 "R! /nix/var/nix/temproots - - - - -" 366 ]; 367 368 boot.initrd.systemd = { 369 additionalUpstreamUnits = [ 370 "systemd-tmpfiles-setup-dev-early.service" 371 "systemd-tmpfiles-setup-dev.service" 372 "systemd-tmpfiles-setup.service" 373 ]; 374 375 # override to exclude the prefix /sysroot, because it is not necessarily set up when the unit starts 376 services.systemd-tmpfiles-setup.serviceConfig = { 377 ExecStart = [ 378 "" 379 "systemd-tmpfiles --create --remove --boot --exclude-prefix=/dev --exclude-prefix=/sysroot" 380 ]; 381 }; 382 383 # sets up files under the prefix /sysroot, after the hierarchy is available and before nixos activation 384 services.systemd-tmpfiles-setup-sysroot = { 385 description = "Create Volatile Files and Directories in the Real Root"; 386 after = [ "initrd-fs.target" ]; 387 before = [ 388 "initrd.target" 389 "shutdown.target" 390 "initrd-switch-root.target" 391 ]; 392 conflicts = [ 393 "shutdown.target" 394 "initrd-switch-root.target" 395 ]; 396 wantedBy = [ "initrd.target" ]; 397 serviceConfig = { 398 Type = "oneshot"; 399 RemainAfterExit = true; 400 ExecStart = "systemd-tmpfiles --create --remove --boot --exclude-prefix=/dev --prefix=/sysroot"; 401 SuccessExitStatus = [ "DATAERR CANTCREAT" ]; 402 ImportCredential = [ 403 "tmpfiles.*" 404 "login.motd" 405 "login.issue" 406 "network.hosts" 407 "ssh.authorized_keys.root" 408 ]; 409 }; 410 unitConfig = { 411 DefaultDependencies = false; 412 RefuseManualStop = true; 413 }; 414 415 }; 416 417 contents."/etc/tmpfiles.d" = mkIf (initrdCfg.settings != { }) { 418 source = pkgs.linkFarm "initrd-tmpfiles.d" ( 419 mapAttrsToList (name: paths: { 420 name = "${name}.conf"; 421 path = pkgs.writeText "${name}.conf" (mkRuleFileContent paths); 422 }) initrdCfg.settings 423 ); 424 }; 425 }; 426 }; 427}