From 06aeeed62ff05946871c7125befcb4563efd71a9 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Wed, 5 Nov 2025 12:19:54 -0600 Subject: [PATCH] Revert "tmpfiles: add option to purge rules' targets on change" This reverts commit b4350d54c2ec735bc841eb0f583eb889c8159fe9. Signed-off-by: Austin Horstman --- modules/misc/tmpfiles.nix | 166 ++++++------------ modules/programs/glab.nix | 2 +- .../standalone/rclone/sops-nix.nix | 2 +- tests/modules/misc/tmpfiles/basic-rules.nix | 6 +- tests/modules/misc/tmpfiles/default.nix | 1 - .../tmpfiles/escaped-argument-warning.nix | 4 +- .../misc/tmpfiles/rules-with-purging.nix | 28 --- 7 files changed, 58 insertions(+), 151 deletions(-) delete mode 100644 tests/modules/misc/tmpfiles/rules-with-purging.nix diff --git a/modules/misc/tmpfiles.nix b/modules/misc/tmpfiles.nix index 87b87c937..e5d21a6e9 100644 --- a/modules/misc/tmpfiles.nix +++ b/modules/misc/tmpfiles.nix @@ -89,46 +89,6 @@ let } ); - attrsWith' = placeholder: elemType: lib.types.attrsWith { inherit elemType placeholder; }; - - nonEmptyAttrsWith' = - placeholder: elemType: - let - attrs = lib.types.addCheck (attrsWith' placeholder elemType) (s: s != { }); - in - attrs - // { - name = "nonEmptyAttrsOf"; - description = "non-empty ${attrs.description}"; - emptyValue = { }; # no .value attribute, meaning there is not empty value - substSubModules = m: nonEmptyAttrsWith' placeholder (elemType.substSubModules m); - }; - - configSubmodule = lib.types.submodule { - options.rules = lib.mkOption { - description = "The rules contained in this configuration."; - example = { - "%C".d = { - mode = "0755"; - user = "alice"; - group = "alice"; - age = "4 weeks"; - }; - }; - type = nonEmptyAttrsWith' "path" (nonEmptyAttrsWith' "tmpfiles-type" ruleSubmodule); - }; - options.purgeOnChange = lib.mkOption { - description = '' - Whether the rules that are marked for purging, will automatically - be purged when the set of rules changes. - - See {manpage}`systemd-tmpfiles(8)` for details about purging. - ''; - type = lib.types.bool; - default = false; - }; - }; - modulePrefix = [ "systemd" "user" @@ -140,9 +100,6 @@ let mkConfigFile = name: rules: - { - suffix ? [ name ], - }: let escapeArgument = lib.strings.escapeC [ "\t" @@ -158,7 +115,7 @@ let { text = '' # This file was generated by Home Manager and should not be modified. - # Please change the option '${lib.showAttrPath (modulePrefix ++ suffix)}' instead. + # Please change the option '${lib.showAttrPath (modulePrefix ++ [ name ])}' instead. ${lib.pipe rules [ (lib.mapAttrs (_path: lib.attrValues)) (lib.mapAttrsToList (path: map (mkRule path))) @@ -171,13 +128,6 @@ let ''; }; - nonPurgedConfigs = lib.filterAttrs (_name: config: !config.purgeOnChange) cfg.settings; - purgedConfigs = lib.filterAttrs (_name: config: config.purgeOnChange) cfg.settings; - - # WARNING: When changing this path, the next home-manager generation will - # not find and the rules of the old generation that are subject to purging. - purgedRulesConfigName = "purge-on-change"; - in { meta.maintainers = with lib.maintainers; [ @@ -200,7 +150,7 @@ in persistent files. ''; example = { - cache.rules."%C".d = { + cache."%C".d = { mode = "0755"; user = "alice"; group = "alice"; @@ -208,70 +158,58 @@ in }; }; default = { }; - type = attrsWith' "config-name" configSubmodule; + type = + let + attrsWith' = placeholder: elemType: lib.types.attrsWith { inherit elemType placeholder; }; + nonEmptyAttrsWith' = + placeholder: elemType: + let + attrs = lib.types.addCheck (attrsWith' placeholder elemType) (s: s != { }); + in + attrs + // { + name = "nonEmptyAttrsOf"; + description = "non-empty ${attrs.description}"; + emptyValue = { }; # no .value attribute, meaning there is not empty value + substSubModules = m: nonEmptyAttrsWith' placeholder (elemType.substSubModules m); + }; + in + attrsWith' "config-name" ( + nonEmptyAttrsWith' "path" (nonEmptyAttrsWith' "tmpfiles-type" ruleSubmodule) + ); }; - config = lib.mkMerge [ + config = lib.mkIf (cfg.settings != { }) { + assertions = [ + (lib.hm.assertions.assertPlatform "systemd.user.tmpfiles" pkgs lib.platforms.linux) + ]; - (lib.mkIf pkgs.stdenv.hostPlatform.isLinux { - # The activation script must be enabled unconditionally in order to - # guarantee that the old rules are purged even if the new set of rules - # is empty, i.e. `cfg.rulesToPurgeOnChange == [ ]`. - home.activation.purgeTmpfiles = lib.hm.dag.entryAfter [ "writeBoundary" ] ( - let - relativeXdgConfigHome = lib.strings.removePrefix "${config.home.homeDirectory}/" config.xdg.configHome; - configPath = "home-files/${relativeXdgConfigHome}/${purgedRulesConfigName}"; - in - '' - if [[ -v oldGenPath && -f $oldGenPath/${configPath} ]] && - diff -q $oldGenPath/${configPath} $newGenPath/${configPath} &>/dev/null; then - verboseEcho "Purge old tmpfiles" - run ${pkgs.systemd}/bin/systemd-tmpfiles --user --purge ''${DRY_RUN:+--dry-run} $oldGenPath/${configPath} - fi - '' - ); - }) + warnings = lib.flatten ( + lib.mapAttrsToListRecursive ( + path: value: + lib.optional + (lib.last path == "argument" && lib.match ''.*\\([nrt]|x[0-9A-Fa-f]{2}).*'' value != null) + '' + The '${lib.showAttrPath (modulePrefix ++ path)}' option + appears to contain escape sequences, which will be escaped again. + Unescape them if this is not intended. The assigned string is: + "${value}" + '' + ) cfg.settings + ); - (lib.mkIf (cfg.settings != { }) { - assertions = [ - (lib.hm.assertions.assertPlatform "systemd.user.tmpfiles" pkgs lib.platforms.linux) - ]; - - warnings = lib.flatten ( - lib.mapAttrsToListRecursive ( - path: value: - lib.optional - (lib.last path == "argument" && lib.match ''.*\\([nrt]|x[0-9A-Fa-f]{2}).*'' value != null) - '' - The '${lib.showAttrPath (modulePrefix ++ path)}' option - appears to contain escape sequences, which will be escaped again. - Unescape them if this is not intended. The assigned string is: - "${value}" - '' - ) cfg.settings - ); - - xdg.configFile = { - "systemd/user/basic.target.wants/systemd-tmpfiles-setup.service".source = - "${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-setup.service"; - "systemd/user/systemd-tmpfiles-setup.service".source = - "${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-setup.service"; - "systemd/user/timers.target.wants/systemd-tmpfiles-clean.timer".source = - "${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-clean.timer"; - "systemd/user/systemd-tmpfiles-clean.service".source = - "${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-clean.service"; - } - // lib.mapAttrs' ( - name: config: lib.nameValuePair (mkFileName name) (mkConfigFile name config.rules { }) - ) nonPurgedConfigs - // lib.optionalAttrs (purgedConfigs != { }) { - ${mkFileName purgedRulesConfigName} = - let - purgedConfigsMerged = lib.foldl' lib.recursiveUpdate { } (lib.attrValues purgedConfigs); - in - mkConfigFile purgedRulesConfigName purgedConfigsMerged.rules { suffix = [ ]; }; - }; - }) - - ]; + xdg.configFile = { + "systemd/user/basic.target.wants/systemd-tmpfiles-setup.service".source = + "${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-setup.service"; + "systemd/user/systemd-tmpfiles-setup.service".source = + "${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-setup.service"; + "systemd/user/timers.target.wants/systemd-tmpfiles-clean.timer".source = + "${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-clean.timer"; + "systemd/user/systemd-tmpfiles-clean.service".source = + "${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-clean.service"; + } + // lib.mapAttrs' ( + name: rules: lib.nameValuePair (mkFileName name) (mkConfigFile name rules) + ) cfg.settings; + }; } diff --git a/modules/programs/glab.nix b/modules/programs/glab.nix index 16ff2a86f..1aac3c004 100644 --- a/modules/programs/glab.nix +++ b/modules/programs/glab.nix @@ -37,7 +37,7 @@ in # Use `systemd-tmpfiles` since glab requires its configuration file to have # mode 0600. systemd.user.tmpfiles.settings.glab = lib.mkIf (cfg.settings != { }) { - rules."${config.xdg.configHome}/glab-cli/config.yml" = { + "${config.xdg.configHome}/glab-cli/config.yml" = { "C+".argument = yaml.generate "glab-config" cfg.settings; z.mode = "0600"; }; diff --git a/tests/integration/standalone/rclone/sops-nix.nix b/tests/integration/standalone/rclone/sops-nix.nix index fc676505e..a7baf0614 100644 --- a/tests/integration/standalone/rclone/sops-nix.nix +++ b/tests/integration/standalone/rclone/sops-nix.nix @@ -28,7 +28,7 @@ in uid = 1000; }; - systemd.tmpfiles.settings.age.rules."/home/alice/age-key".f = { + systemd.tmpfiles.settings.age."/home/alice/age-key".f = { mode = "400"; user = "alice"; group = "users"; diff --git a/tests/modules/misc/tmpfiles/basic-rules.nix b/tests/modules/misc/tmpfiles/basic-rules.nix index 68b00f87b..30a451737 100644 --- a/tests/modules/misc/tmpfiles/basic-rules.nix +++ b/tests/modules/misc/tmpfiles/basic-rules.nix @@ -2,8 +2,8 @@ imports = [ ./common-stubs.nix ]; systemd.user.tmpfiles.settings = { - cache.rules."%C".d.age = "4 weeks"; - myTool.rules."%h/.config/myTool.conf"."f+" = { + cache."%C".d.age = "4 weeks"; + myTool."%h/.config/myTool.conf"."f+" = { mode = "0644"; user = "alice"; group = "users"; @@ -12,8 +12,6 @@ }; nmt.script = '' - assertPathNotExists home-files/.config/user-tmpfiles.d/home-manager-purge-on-change.conf - cacheRulesFile=home-files/.config/user-tmpfiles.d/home-manager-cache.conf assertFileExists $cacheRulesFile assertFileRegex $cacheRulesFile "^'d' '%C' '-' '-' '-' '4 weeks' $" diff --git a/tests/modules/misc/tmpfiles/default.nix b/tests/modules/misc/tmpfiles/default.nix index 608796fe9..1caa5fc7e 100644 --- a/tests/modules/misc/tmpfiles/default.nix +++ b/tests/modules/misc/tmpfiles/default.nix @@ -1,7 +1,6 @@ { tmpfiles-no-rules = ./no-rules.nix; tmpfiles-basic-rules = ./basic-rules.nix; - tmpfiles-rules-with-purging = ./rules-with-purging.nix; tmpfiles-escaped-argument-warning = ./escaped-argument-warning.nix; } diff --git a/tests/modules/misc/tmpfiles/escaped-argument-warning.nix b/tests/modules/misc/tmpfiles/escaped-argument-warning.nix index 0a7441b7e..9284de3c8 100644 --- a/tests/modules/misc/tmpfiles/escaped-argument-warning.nix +++ b/tests/modules/misc/tmpfiles/escaped-argument-warning.nix @@ -1,11 +1,11 @@ { imports = [ ./common-stubs.nix ]; - systemd.user.tmpfiles.settings.foo.rules.path.f.argument = "my\\x20unescaped\\x20config"; + systemd.user.tmpfiles.settings.foo.path.f.argument = "my\\x20unescaped\\x20config"; test.asserts.warnings.expected = [ '' - The 'systemd.user.tmpfiles.settings.foo.rules.path.f.argument' option + The 'systemd.user.tmpfiles.settings.foo.path.f.argument' option appears to contain escape sequences, which will be escaped again. Unescape them if this is not intended. The assigned string is: "my\x20unescaped\x20config" diff --git a/tests/modules/misc/tmpfiles/rules-with-purging.nix b/tests/modules/misc/tmpfiles/rules-with-purging.nix deleted file mode 100644 index a453bfcb6..000000000 --- a/tests/modules/misc/tmpfiles/rules-with-purging.nix +++ /dev/null @@ -1,28 +0,0 @@ -{ - imports = [ ./common-stubs.nix ]; - - systemd.user.tmpfiles.settings = { - cache.rules."%C".d.age = "4 weeks"; - myTool = { - rules = { - "%h/.config/myTool.conf"."f+".argument = "my_config"; - "%h/.config/myToolPurged.conf"."f+$".argument = "my_config_purged"; - }; - purgeOnChange = true; - }; - }; - - nmt.script = '' - cacheRulesFile=home-files/.config/user-tmpfiles.d/home-manager-cache.conf - assertFileExists $cacheRulesFile - assertFileRegex $cacheRulesFile "^'d' '%C' '-' '-' '-' '4 weeks' $" - - assertPathNotExists home-files/.config/user-tmpfiles.d/home-manager-myTool.conf - myToolRulesFile=home-files/.config/user-tmpfiles.d/home-manager-purge-on-change.conf - assertFileExists $myToolRulesFile - assertFileRegex $myToolRulesFile \ - "^'f+' '%h/.config/myTool.conf' '-' '-' '-' '-' my_config$" - assertFileRegex $myToolRulesFile \ - "^'f+$' '%h/.config/myToolPurged.conf' '-' '-' '-' '-' my_config_purged$" - ''; -}