vuls: init at 0.27.0
[NixPkgs.git] / nixos / modules / services / system / userborn.nix
blob07e8be34266ea9cec7b298d716c0c10b0455aa77
2   utils,
3   config,
4   lib,
5   pkgs,
6   ...
7 }:
9 let
11   cfg = config.services.userborn;
12   userCfg = config.users;
14   userbornConfig = {
15     groups = lib.mapAttrsToList (username: opts: {
16       inherit (opts) name gid members;
17     }) config.users.groups;
19     users = lib.mapAttrsToList (username: opts: {
20       inherit (opts)
21         name
22         uid
23         group
24         description
25         home
26         password
27         hashedPassword
28         hashedPasswordFile
29         initialPassword
30         initialHashedPassword
31         ;
32       isNormal = opts.isNormalUser;
33       shell = utils.toShellPath opts.shell;
34     }) config.users.users;
35   };
37   userbornConfigJson = pkgs.writeText "userborn.json" (builtins.toJSON userbornConfig);
39   immutableEtc = config.system.etc.overlay.enable && !config.system.etc.overlay.mutable;
40   # The filenames created by userborn.
41   passwordFiles = [
42     "group"
43     "passwd"
44     "shadow"
45   ];
50   options.services.userborn = {
52     enable = lib.mkEnableOption "userborn";
54     package = lib.mkPackageOption pkgs "userborn" { };
56     passwordFilesLocation = lib.mkOption {
57       type = lib.types.str;
58       default = if immutableEtc then "/var/lib/nixos" else "/etc";
59       defaultText = lib.literalExpression ''if immutableEtc then "/var/lib/nixos" else "/etc"'';
60       description = ''
61         The location of the original password files.
63         If this is not `/etc`, the files are symlinked from this location to `/etc`.
65         The primary motivation for this is an immutable `/etc`, where we cannot
66         write the files directly to `/etc`.
68         However this an also serve other use cases, e.g. when `/etc` is on a `tmpfs`.
69       '';
70     };
72   };
74   config = lib.mkIf cfg.enable {
76     assertions = [
77       {
78         assertion = !(config.systemd.sysusers.enable && cfg.enable);
79         message = "You cannot use systemd-sysusers and Userborn at the same time";
80       }
81       {
82         assertion = config.system.activationScripts.users == "";
83         message = "system.activationScripts.users has to be empty to use userborn";
84       }
85       {
86         assertion = immutableEtc -> (cfg.passwordFilesLocation != "/etc");
87         message = "When `system.etc.overlay.mutable = false`, `services.userborn.passwordFilesLocation` cannot be set to `/etc`";
88       }
89     ];
91     system.activationScripts.users = lib.mkForce "";
92     system.activationScripts.hashes = lib.mkForce "";
94     systemd = {
96       # Create home directories, do not create /var/empty even if that's a user's
97       # home.
98       tmpfiles.settings.home-directories = lib.mapAttrs' (
99         username: opts:
100         lib.nameValuePair (toString opts.home) {
101           d = {
102             mode = opts.homeMode;
103             user = username;
104             inherit (opts) group;
105           };
106         }
107       ) (lib.filterAttrs (_username: opts: opts.createHome && opts.home != "/var/empty") userCfg.users);
109       services.userborn = {
110         wantedBy = [ "sysinit.target" ];
111         requiredBy = [ "sysinit-reactivation.target" ];
112         after = [
113           "systemd-remount-fs.service"
114           "systemd-tmpfiles-setup-dev-early.service"
115         ];
116         before = [
117           "systemd-tmpfiles-setup-dev.service"
118           "sysinit.target"
119           "shutdown.target"
120           "sysinit-reactivation.target"
121         ];
122         conflicts = [ "shutdown.target" ];
123         restartTriggers = [
124           userbornConfigJson
125           cfg.passwordFilesLocation
126         ];
127         # This way we don't have to re-declare all the dependencies to other
128         # services again.
129         aliases = [ "systemd-sysusers.service" ];
131         unitConfig = {
132           Description = "Manage Users and Groups";
133           DefaultDependencies = false;
134         };
136         serviceConfig = {
137           Type = "oneshot";
138           RemainAfterExit = true;
139           TimeoutSec = "90s";
141           ExecStart = "${lib.getExe cfg.package} ${userbornConfigJson} ${cfg.passwordFilesLocation}";
143           ExecStartPre = lib.mkMerge [
144             (lib.mkIf (!config.system.etc.overlay.mutable) [
145               "${pkgs.coreutils}/bin/mkdir -p ${cfg.passwordFilesLocation}"
146             ])
148             # Make the source files writable before executing userborn.
149             (lib.mkIf (!userCfg.mutableUsers) (
150               lib.map (file: "-${pkgs.util-linux}/bin/umount ${cfg.passwordFilesLocation}/${file}") passwordFiles
151             ))
152           ];
154           # Make the source files read-only after userborn has finished.
155           ExecStartPost = lib.mkIf (!userCfg.mutableUsers) (
156             lib.map (
157               file:
158               "${pkgs.util-linux}/bin/mount --bind -o ro ${cfg.passwordFilesLocation}/${file} ${cfg.passwordFilesLocation}/${file}"
159             ) passwordFiles
160           );
161         };
162       };
163     };
165     # Statically create the symlinks to passwordFilesLocation when they're not
166     # inside /etc because we will not be able to do it at runtime in case of an
167     # immutable /etc!
168     environment.etc = lib.mkIf (cfg.passwordFilesLocation != "/etc") (
169       lib.listToAttrs (
170         lib.map (
171           file:
172           lib.nameValuePair file {
173             source = "${cfg.passwordFilesLocation}/${file}";
174             mode = "direct-symlink";
175           }
176         ) passwordFiles
177       )
178     );
179   };
181   meta.maintainers = with lib.maintainers; [ nikstur ];