diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix index 2fbb37625805..f9750b7263ca 100644 --- a/nixos/modules/config/users-groups.nix +++ b/nixos/modules/config/users-groups.nix @@ -496,6 +496,7 @@ let in filter types.shellPackage.check shells; + lingeringUsers = map (u: u.name) (attrValues (flip filterAttrs cfg.users (n: u: u.linger))); in { imports = [ (mkAliasOptionModuleMD [ "users" "extraUsers" ] [ "users" "users" ]) @@ -695,25 +696,31 @@ in { ''; } else ""; # keep around for backwards compatibility - system.activationScripts.update-lingering = let - lingerDir = "/var/lib/systemd/linger"; - lingeringUsers = map (u: u.name) (attrValues (flip filterAttrs cfg.users (n: u: u.linger))); - lingeringUsersFile = builtins.toFile "lingering-users" - (concatStrings (map (s: "${s}\n") - (sort (a: b: a < b) lingeringUsers))); # this sorting is important for `comm` to work correctly - in stringAfter [ "users" ] '' - if [ -e ${lingerDir} ] ; then + systemd.services.linger-users = lib.mkIf ((builtins.length lingeringUsers) > 0) { + wantedBy = ["multi-user.target"]; + after = ["systemd-logind.service"]; + requires = ["systemd-logind.service"]; + + script = let + lingerDir = "/var/lib/systemd/linger"; + lingeringUsersFile = builtins.toFile "lingering-users" + (concatStrings (map (s: "${s}\n") + (sort (a: b: a < b) lingeringUsers))); # this sorting is important for `comm` to work correctly + in '' + mkdir -vp ${lingerDir} cd ${lingerDir} for user in $(ls); do if ! id "$user" >/dev/null; then - echo "Removing linger for deleted user $user" + echo "Removing linger for missing user $user" rm --force -- "$user" fi done - ls ${lingerDir} | sort | comm -3 -1 ${lingeringUsersFile} - | xargs -r ${pkgs.systemd}/bin/loginctl disable-linger - ls ${lingerDir} | sort | comm -3 -2 ${lingeringUsersFile} - | xargs -r ${pkgs.systemd}/bin/loginctl enable-linger - fi - ''; + ls | sort | comm -3 -1 ${lingeringUsersFile} - | xargs -r ${pkgs.systemd}/bin/loginctl disable-linger + ls | sort | comm -3 -2 ${lingeringUsersFile} - | xargs -r ${pkgs.systemd}/bin/loginctl enable-linger + ''; + + serviceConfig.Type = "oneshot"; + }; # Warn about user accounts with deprecated password hashing schemes # This does not work when the users and groups are created by diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index f713e3bfdc6f..d265ab380fbf 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -899,6 +899,7 @@ in { systemd-sysusers-immutable = runTest ./systemd-sysusers-immutable.nix; systemd-timesyncd = handleTest ./systemd-timesyncd.nix {}; systemd-timesyncd-nscd-dnssec = handleTest ./systemd-timesyncd-nscd-dnssec.nix {}; + systemd-user-linger = handleTest ./systemd-user-linger.nix {}; systemd-user-tmpfiles-rules = handleTest ./systemd-user-tmpfiles-rules.nix {}; systemd-misc = handleTest ./systemd-misc.nix {}; systemd-userdbd = handleTest ./systemd-userdbd.nix {}; diff --git a/nixos/tests/systemd-user-linger.nix b/nixos/tests/systemd-user-linger.nix new file mode 100644 index 000000000000..2c3d71668979 --- /dev/null +++ b/nixos/tests/systemd-user-linger.nix @@ -0,0 +1,39 @@ +import ./make-test-python.nix ( + { lib, ... }: + { + name = "systemd-user-linger"; + + nodes.machine = + { ... }: + { + users.users = { + alice = { + isNormalUser = true; + linger = true; + uid = 1000; + }; + + bob = { + isNormalUser = true; + linger = false; + uid = 10001; + }; + }; + }; + + testScript = + { ... }: + '' + machine.wait_for_file("/var/lib/systemd/linger/alice") + machine.succeed("systemctl status user-1000.slice") + + machine.fail("test -e /var/lib/systemd/linger/bob") + machine.fail("systemctl status user-1001.slice") + + with subtest("missing users have linger purged"): + machine.succeed("touch /var/lib/systemd/linger/missing") + machine.systemctl("restart linger-users") + machine.succeed("test ! -e /var/lib/systemd/linger/missing") + ''; + } +)