1{ config, lib, pkgs, utils, ... }:
2
3let
4
5 cfg = config.systemd.sysusers;
6 userCfg = config.users;
7
8 sysusersConfig = pkgs.writeTextDir "00-nixos.conf" ''
9 # Type Name ID GECOS Home directory Shell
10
11 # Users
12 ${lib.concatLines (lib.mapAttrsToList
13 (username: opts:
14 let
15 uid = if opts.uid == null then "-" else toString opts.uid;
16 in
17 ''u ${username} ${uid}:${opts.group} "${opts.description}" ${opts.home} ${utils.toShellPath opts.shell}''
18 )
19 userCfg.users)
20 }
21
22 # Groups
23 ${lib.concatLines (lib.mapAttrsToList
24 (groupname: opts: ''g ${groupname} ${if opts.gid == null then "-" else toString opts.gid}'') userCfg.groups)
25 }
26
27 # Group membership
28 ${lib.concatStrings (lib.mapAttrsToList
29 (groupname: opts: (lib.concatMapStrings (username: "m ${username} ${groupname}\n")) opts.members ) userCfg.groups)
30 }
31 '';
32
33 staticSysusersCredentials = pkgs.runCommand "static-sysusers-credentials" { } ''
34 mkdir $out; cd $out
35 ${lib.concatLines (
36 (lib.mapAttrsToList
37 (username: opts: "echo -n '${opts.initialHashedPassword}' > 'passwd.hashed-password.${username}'")
38 (lib.filterAttrs (_username: opts: opts.initialHashedPassword != null) userCfg.users))
39 ++
40 (lib.mapAttrsToList
41 (username: opts: "echo -n '${opts.initialPassword}' > 'passwd.plaintext-password.${username}'")
42 (lib.filterAttrs (_username: opts: opts.initialPassword != null) userCfg.users))
43 ++
44 (lib.mapAttrsToList
45 (username: opts: "cat '${opts.hashedPasswordFile}' > 'passwd.hashed-password.${username}'")
46 (lib.filterAttrs (_username: opts: opts.hashedPasswordFile != null) userCfg.users))
47 )
48 }
49 '';
50
51 staticSysusers = pkgs.runCommand "static-sysusers"
52 {
53 nativeBuildInputs = [ pkgs.systemd ];
54 } ''
55 mkdir $out
56 export CREDENTIALS_DIRECTORY=${staticSysusersCredentials}
57 systemd-sysusers --root $out ${sysusersConfig}/00-nixos.conf
58 '';
59
60in
61
62{
63
64 options = {
65
66 # This module doesn't set it's own user options but reuses the ones from
67 # users-groups.nix
68
69 systemd.sysusers = {
70 enable = lib.mkEnableOption "systemd-sysusers" // {
71 description = ''
72 If enabled, users are created with systemd-sysusers instead of with
73 the custom `update-users-groups.pl` script.
74
75 Note: This is experimental.
76 '';
77 };
78 };
79
80 };
81
82 config = lib.mkIf cfg.enable {
83
84 assertions = [
85 {
86 assertion = config.system.activationScripts.users == "";
87 message = "system.activationScripts.users has to be empty to use systemd-sysusers";
88 }
89 {
90 assertion = config.users.mutableUsers -> config.system.etc.overlay.enable;
91 message = "config.users.mutableUsers requires config.system.etc.overlay.enable.";
92 }
93 ];
94
95 systemd = lib.mkMerge [
96 ({
97
98 # Create home directories, do not create /var/empty even if that's a user's
99 # home.
100 tmpfiles.settings.home-directories = lib.mapAttrs'
101 (username: opts: lib.nameValuePair opts.home {
102 d = {
103 mode = opts.homeMode;
104 user = username;
105 group = opts.group;
106 };
107 })
108 (lib.filterAttrs (_username: opts: opts.home != "/var/empty") userCfg.users);
109 })
110
111 (lib.mkIf config.users.mutableUsers {
112 additionalUpstreamSystemUnits = [
113 "systemd-sysusers.service"
114 ];
115
116 services.systemd-sysusers = {
117 # Enable switch-to-configuration to restart the service.
118 unitConfig.ConditionNeedsUpdate = [ "" ];
119 requiredBy = [ "sysinit-reactivation.target" ];
120 before = [ "sysinit-reactivation.target" ];
121 restartTriggers = [ "${config.environment.etc."sysusers.d".source}" ];
122
123 serviceConfig = {
124 LoadCredential = lib.mapAttrsToList
125 (username: opts: "passwd.hashed-password.${username}:${opts.hashedPasswordFile}")
126 (lib.filterAttrs (_username: opts: opts.hashedPasswordFile != null) userCfg.users);
127 SetCredential = (lib.mapAttrsToList
128 (username: opts: "passwd.hashed-password.${username}:${opts.initialHashedPassword}")
129 (lib.filterAttrs (_username: opts: opts.initialHashedPassword != null) userCfg.users))
130 ++
131 (lib.mapAttrsToList
132 (username: opts: "passwd.plaintext-password.${username}:${opts.initialPassword}")
133 (lib.filterAttrs (_username: opts: opts.initialPassword != null) userCfg.users))
134 ;
135 };
136 };
137 })
138 ];
139
140 environment.etc = lib.mkMerge [
141 (lib.mkIf (!userCfg.mutableUsers) {
142 "passwd" = {
143 source = "${staticSysusers}/etc/passwd";
144 mode = "0644";
145 };
146 "group" = {
147 source = "${staticSysusers}/etc/group";
148 mode = "0644";
149 };
150 "shadow" = {
151 source = "${staticSysusers}/etc/shadow";
152 mode = "0000";
153 };
154 "gshadow" = {
155 source = "${staticSysusers}/etc/gshadow";
156 mode = "0000";
157 };
158 })
159
160 (lib.mkIf userCfg.mutableUsers {
161 "sysusers.d".source = sysusersConfig;
162 })
163 ];
164
165 };
166
167 meta.maintainers = with lib.maintainers; [ nikstur ];
168
169}