linger-users: Fix strict shell checks, fix for mutable users (#363209)
This commit is contained in:
@@ -385,6 +385,8 @@ and [release notes for v18](https://goteleport.com/docs/changelog/#1800-070325).
|
|||||||
- In all other cases, you'll need to set this option to `true` yourself.
|
- In all other cases, you'll need to set this option to `true` yourself.
|
||||||
- `boot.isNspawnContainer` being `true` implies [](#opt-boot.isContainer) being `true`.
|
- `boot.isNspawnContainer` being `true` implies [](#opt-boot.isContainer) being `true`.
|
||||||
|
|
||||||
|
- `users.users.*.linger` now defaults to `null` rather than `false`, meaning NixOS will not attempt to enable or disable lingering for that user account. In practice, this is unlikely to make a difference for most people, as new users are created without lingering configured, but it means users who use `loginctl` commands to manage lingering imperatively will not have their changes overridden by default. There is a new, related option, `users.manageLingering`, which can be used to prevent NixOS attempting to manage lingering entirely.
|
||||||
|
|
||||||
- Due to [deprecation of gnome-session X11 support](https://blogs.gnome.org/alatiera/2025/06/08/the-x11-session-removal/), `services.desktopManager.pantheon` now defaults to pantheon-wayland session. The X11 session has been removed, see [this issue](https://github.com/elementary/session-settings/issues/91) for details.
|
- Due to [deprecation of gnome-session X11 support](https://blogs.gnome.org/alatiera/2025/06/08/the-x11-session-removal/), `services.desktopManager.pantheon` now defaults to pantheon-wayland session. The X11 session has been removed, see [this issue](https://github.com/elementary/session-settings/issues/91) for details.
|
||||||
|
|
||||||
- `bcachefs` file systems will now use the out-of-tree module for supported kernels. The in-tree module has been removed, and users will need to switch to kernels that support the out-of-tree module.
|
- `bcachefs` file systems will now use the out-of-tree module for supported kernels. The in-tree module has been removed, and users will need to switch to kernels that support the out-of-tree module.
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ let
|
|||||||
any
|
any
|
||||||
attrNames
|
attrNames
|
||||||
attrValues
|
attrValues
|
||||||
|
boolToString
|
||||||
concatMap
|
concatMap
|
||||||
concatMapStringsSep
|
concatMapStringsSep
|
||||||
concatStrings
|
concatStrings
|
||||||
@@ -43,6 +44,7 @@ let
|
|||||||
stringLength
|
stringLength
|
||||||
trace
|
trace
|
||||||
types
|
types
|
||||||
|
versionOlder
|
||||||
xor
|
xor
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -128,6 +130,10 @@ let
|
|||||||
'';
|
'';
|
||||||
|
|
||||||
userOpts =
|
userOpts =
|
||||||
|
let
|
||||||
|
# Pass state version through despite config being overwritten in the inner module
|
||||||
|
inherit (config.system) stateVersion;
|
||||||
|
in
|
||||||
{ name, config, ... }:
|
{ name, config, ... }:
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -455,16 +461,22 @@ let
|
|||||||
};
|
};
|
||||||
|
|
||||||
linger = mkOption {
|
linger = mkOption {
|
||||||
type = types.bool;
|
type = types.nullOr types.bool;
|
||||||
default = false;
|
example = true;
|
||||||
|
default = if versionOlder stateVersion "26.11" then false else null;
|
||||||
|
defaultText = literalExpression "if lib.versionOlder config.system.stateVersion \"25.11\" then false else null";
|
||||||
description = ''
|
description = ''
|
||||||
Whether to enable lingering for this user. If true, systemd user
|
Whether to enable or disable lingering for this user. Without
|
||||||
units will start at boot, rather than starting at login and stopping
|
lingering, user units will not be started until the user logs in,
|
||||||
at logout. This is the declarative equivalent of running
|
and may be stopped on logout depending on the settings in
|
||||||
`loginctl enable-linger` for this user.
|
`logind.conf`.
|
||||||
|
|
||||||
If false, user units will not be started until the user logs in, and
|
By default, NixOS will not manage lingering, new users will default
|
||||||
may be stopped on logout depending on the settings in `logind.conf`.
|
to not lingering, and you can change the linger setting using
|
||||||
|
`loginctl enable-linger` or `loginctl disable-linger`. Setting
|
||||||
|
this option to `true` or `false` is the declarative equivalent of
|
||||||
|
running `loginctl enable-linger` or `loginctl disable-linger`
|
||||||
|
respectively.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -661,8 +673,6 @@ let
|
|||||||
shells = mapAttrsToList (_: u: u.shell) cfg.users;
|
shells = mapAttrsToList (_: u: u.shell) cfg.users;
|
||||||
in
|
in
|
||||||
filter types.shellPackage.check shells;
|
filter types.shellPackage.check shells;
|
||||||
|
|
||||||
lingeringUsers = map (u: u.name) (attrValues (flip filterAttrs cfg.users (n: u: u.linger)));
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
@@ -710,6 +720,13 @@ in
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
users.manageLingering = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Whether to manage whether users linger or not.";
|
||||||
|
example = false;
|
||||||
|
};
|
||||||
|
|
||||||
users.users = mkOption {
|
users.users = mkOption {
|
||||||
default = { };
|
default = { };
|
||||||
type = with types; attrsOf (submodule userOpts);
|
type = with types; attrsOf (submodule userOpts);
|
||||||
@@ -894,32 +911,52 @@ in
|
|||||||
else
|
else
|
||||||
""; # keep around for backwards compatibility
|
""; # keep around for backwards compatibility
|
||||||
|
|
||||||
systemd.services.linger-users = lib.mkIf ((length lingeringUsers) > 0) {
|
systemd.services.linger-users = lib.mkIf cfg.manageLingering {
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
after = [ "systemd-logind.service" ];
|
after = [ "systemd-logind.service" ];
|
||||||
requires = [ "systemd-logind.service" ];
|
requires = [ "systemd-logind.service" ];
|
||||||
|
|
||||||
script =
|
script =
|
||||||
let
|
let
|
||||||
lingerDir = "/var/lib/systemd/linger";
|
lingeringUsers = filterAttrs (n: v: v.linger == true) cfg.users;
|
||||||
lingeringUsersFile = builtins.toFile "lingering-users" (
|
nonLingeringUsers = filterAttrs (n: v: v.linger == false) cfg.users;
|
||||||
concatStrings (map (s: "${s}\n") (sort (a: b: a < b) lingeringUsers))
|
lingeringUserNames = mapAttrsToList (n: v: v.name) lingeringUsers;
|
||||||
); # this sorting is important for `comm` to work correctly
|
nonLingeringUserNames = mapAttrsToList (n: v: v.name) nonLingeringUsers;
|
||||||
in
|
in
|
||||||
''
|
''
|
||||||
mkdir -vp ${lingerDir}
|
${lib.strings.toShellVars { inherit lingeringUserNames nonLingeringUserNames; }}
|
||||||
cd ${lingerDir}
|
|
||||||
for user in $(ls); do
|
user_configured () {
|
||||||
if ! id "$user" >/dev/null; then
|
# Use `id` to check if the user exists rather than checking the
|
||||||
echo "Removing linger for missing user $user"
|
# NixOS configuration, as it may be that the user has been
|
||||||
rm --force -- "$user"
|
# manually configured, which is permitted if users.mutableUsers
|
||||||
fi
|
# is true (the default).
|
||||||
|
id "$1" >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
shopt -s dotglob nullglob
|
||||||
|
for user in *; do
|
||||||
|
if ! user_configured "$user"; then
|
||||||
|
# systemd has this user configured to linger despite them not
|
||||||
|
# existing.
|
||||||
|
echo "Removing linger for missing user $user" >&2
|
||||||
|
rm -- "$user"
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
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
|
if (( ''${#nonLingeringUserNames[*]} > 0 )); then
|
||||||
|
${config.systemd.package}/bin/loginctl disable-linger "''${nonLingeringUserNames[@]}"
|
||||||
|
fi
|
||||||
|
if (( ''${#lingeringUserNames[*]} > 0 )); then
|
||||||
|
${config.systemd.package}/bin/loginctl enable-linger "''${lingeringUserNames[@]}"
|
||||||
|
fi
|
||||||
'';
|
'';
|
||||||
|
|
||||||
serviceConfig.Type = "oneshot";
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
StateDirectory = "systemd/linger";
|
||||||
|
WorkingDirectory = "/var/lib/systemd/linger";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# Warn about user accounts with deprecated password hashing schemes
|
# Warn about user accounts with deprecated password hashing schemes
|
||||||
@@ -1163,6 +1200,22 @@ in
|
|||||||
users.groups.${user.name} = {};
|
users.groups.${user.name} = {};
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
assertion = user.linger != null -> cfg.manageLingering;
|
||||||
|
message = ''
|
||||||
|
users.manageLingering is set to false, but
|
||||||
|
users.users.${user.name}.linger is configured.
|
||||||
|
|
||||||
|
If you want NixOS to manage whether user accounts linger or
|
||||||
|
not, you must set users.manageLingering to true. This is the
|
||||||
|
default setting.
|
||||||
|
|
||||||
|
If you do not want NixOS to manage whether user accounts linger
|
||||||
|
or not, you must set users.users.${user.name}.linger to null.
|
||||||
|
This is the default setting provided system.stateVersion is at
|
||||||
|
least "25.11".
|
||||||
|
'';
|
||||||
|
}
|
||||||
]
|
]
|
||||||
++ (map
|
++ (map
|
||||||
(shell: {
|
(shell: {
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
boot.kexec.enable = lib.mkDefault false;
|
boot.kexec.enable = lib.mkDefault false;
|
||||||
# Relies on bash scripts
|
# Relies on bash scripts
|
||||||
powerManagement.enable = lib.mkDefault false;
|
powerManagement.enable = lib.mkDefault false;
|
||||||
|
users.manageLingering = lib.mkDefault false;
|
||||||
# Relies on the gzip command which depends on bash
|
# Relies on the gzip command which depends on bash
|
||||||
services.logrotate.enable = lib.mkDefault false;
|
services.logrotate.enable = lib.mkDefault false;
|
||||||
|
|
||||||
|
|||||||
@@ -1519,6 +1519,7 @@ in
|
|||||||
systemd-sysusers-password-option-override-ordering = runTest ./systemd-sysusers-password-option-override-ordering.nix;
|
systemd-sysusers-password-option-override-ordering = runTest ./systemd-sysusers-password-option-override-ordering.nix;
|
||||||
systemd-timesyncd-nscd-dnssec = runTest ./systemd-timesyncd-nscd-dnssec.nix;
|
systemd-timesyncd-nscd-dnssec = runTest ./systemd-timesyncd-nscd-dnssec.nix;
|
||||||
systemd-user-linger = runTest ./systemd-user-linger.nix;
|
systemd-user-linger = runTest ./systemd-user-linger.nix;
|
||||||
|
systemd-user-linger-purge = runTest ./systemd-user-linger-purge.nix;
|
||||||
systemd-user-tmpfiles-rules = runTest ./systemd-user-tmpfiles-rules.nix;
|
systemd-user-tmpfiles-rules = runTest ./systemd-user-tmpfiles-rules.nix;
|
||||||
systemd-userdbd = runTest ./systemd-userdbd.nix;
|
systemd-userdbd = runTest ./systemd-userdbd.nix;
|
||||||
systemtap = handleTest ./systemtap.nix { };
|
systemtap = handleTest ./systemtap.nix { };
|
||||||
|
|||||||
37
nixos/tests/systemd-user-linger-purge.nix
Normal file
37
nixos/tests/systemd-user-linger-purge.nix
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# This test checks #418101, where lingering users would not be cleared up if
|
||||||
|
# the configuration is updated to remove lingering from all users.
|
||||||
|
rec {
|
||||||
|
name = "systemd-user-linger-purge";
|
||||||
|
|
||||||
|
nodes.machine = {
|
||||||
|
users.users = {
|
||||||
|
bob = {
|
||||||
|
isNormalUser = true;
|
||||||
|
linger = false;
|
||||||
|
uid = 1001;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript =
|
||||||
|
let
|
||||||
|
uidStrings = builtins.mapAttrs (k: v: builtins.toString v.uid) nodes.machine.users.users;
|
||||||
|
in
|
||||||
|
''
|
||||||
|
machine.fail("test -e /var/lib/systemd/linger/bob")
|
||||||
|
machine.fail("systemctl status user-${uidStrings.bob}.slice")
|
||||||
|
|
||||||
|
with subtest("missing users have linger purged"):
|
||||||
|
machine.succeed("touch /var/lib/systemd/linger/alice")
|
||||||
|
machine.systemctl("restart linger-users")
|
||||||
|
machine.succeed("test ! -e /var/lib/systemd/linger/alice")
|
||||||
|
|
||||||
|
with subtest("mutable users can linger"):
|
||||||
|
machine.succeed("useradd alice")
|
||||||
|
machine.succeed("test ! -e /var/lib/systemd/linger/alice")
|
||||||
|
machine.succeed("loginctl enable-linger alice")
|
||||||
|
machine.succeed("test -e /var/lib/systemd/linger/alice")
|
||||||
|
machine.systemctl("restart linger-users")
|
||||||
|
machine.succeed("test -e /var/lib/systemd/linger/alice")
|
||||||
|
'';
|
||||||
|
}
|
||||||
@@ -1,37 +1,39 @@
|
|||||||
{ lib, ... }:
|
rec {
|
||||||
{
|
|
||||||
name = "systemd-user-linger";
|
name = "systemd-user-linger";
|
||||||
|
|
||||||
nodes.machine =
|
nodes.machine = {
|
||||||
{ ... }:
|
users.users = {
|
||||||
{
|
alice = {
|
||||||
users.users = {
|
isNormalUser = true;
|
||||||
alice = {
|
linger = true;
|
||||||
isNormalUser = true;
|
uid = 1000;
|
||||||
linger = true;
|
};
|
||||||
uid = 1000;
|
|
||||||
};
|
|
||||||
|
|
||||||
bob = {
|
bob = {
|
||||||
isNormalUser = true;
|
isNormalUser = true;
|
||||||
linger = false;
|
linger = false;
|
||||||
uid = 10001;
|
uid = 1001;
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
testScript =
|
testScript =
|
||||||
{ ... }:
|
let
|
||||||
|
uidStrings = builtins.mapAttrs (k: v: builtins.toString v.uid) nodes.machine.users.users;
|
||||||
|
in
|
||||||
''
|
''
|
||||||
machine.wait_for_file("/var/lib/systemd/linger/alice")
|
machine.wait_for_file("/var/lib/systemd/linger/alice")
|
||||||
machine.succeed("systemctl status user-1000.slice")
|
machine.succeed("systemctl status user-${uidStrings.alice}.slice")
|
||||||
|
|
||||||
machine.fail("test -e /var/lib/systemd/linger/bob")
|
machine.fail("test -e /var/lib/systemd/linger/bob")
|
||||||
machine.fail("systemctl status user-1001.slice")
|
machine.fail("systemctl status user-${uidStrings.bob}.slice")
|
||||||
|
|
||||||
with subtest("missing users have linger purged"):
|
with subtest("mutable users can linger"):
|
||||||
machine.succeed("touch /var/lib/systemd/linger/missing")
|
machine.succeed("useradd clare")
|
||||||
|
machine.succeed("test ! -e /var/lib/systemd/linger/clare")
|
||||||
|
machine.succeed("loginctl enable-linger clare")
|
||||||
|
machine.succeed("test -e /var/lib/systemd/linger/clare")
|
||||||
machine.systemctl("restart linger-users")
|
machine.systemctl("restart linger-users")
|
||||||
machine.succeed("test ! -e /var/lib/systemd/linger/missing")
|
machine.succeed("test -e /var/lib/systemd/linger/clare")
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user