at 25.11-pre 5.5 kB view raw
1{ 2 utils, 3 config, 4 lib, 5 pkgs, 6 ... 7}: 8 9let 10 11 cfg = config.services.userborn; 12 userCfg = config.users; 13 14 userbornConfig = { 15 groups = lib.mapAttrsToList (username: opts: { 16 inherit (opts) name gid members; 17 }) config.users.groups; 18 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 }) (lib.filterAttrs (_: u: u.enable) config.users.users); 35 }; 36 37 userbornConfigJson = pkgs.writeText "userborn.json" (builtins.toJSON userbornConfig); 38 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 ]; 46 47in 48{ 49 50 options.services.userborn = { 51 52 enable = lib.mkEnableOption "userborn"; 53 54 package = lib.mkPackageOption pkgs "userborn" { }; 55 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. 62 63 If this is not `/etc`, the files are symlinked from this location to `/etc`. 64 65 The primary motivation for this is an immutable `/etc`, where we cannot 66 write the files directly to `/etc`. 67 68 However this an also serve other use cases, e.g. when `/etc` is on a `tmpfs`. 69 ''; 70 }; 71 72 }; 73 74 config = lib.mkIf cfg.enable { 75 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 ]; 90 91 system.activationScripts.users = lib.mkForce ""; 92 system.activationScripts.hashes = lib.mkForce ""; 93 94 systemd = { 95 96 # Create home directories, do not create /var/empty even if that's a user's 97 # home. 98 tmpfiles.settings.home-directories = 99 lib.mapAttrs' 100 ( 101 username: opts: 102 lib.nameValuePair (toString opts.home) { 103 d = { 104 mode = opts.homeMode; 105 user = opts.name; 106 inherit (opts) group; 107 }; 108 } 109 ) 110 ( 111 lib.filterAttrs ( 112 _username: opts: opts.enable && opts.createHome && opts.home != "/var/empty" 113 ) userCfg.users 114 ); 115 116 services.userborn = { 117 wantedBy = [ "sysinit.target" ]; 118 requiredBy = [ "sysinit-reactivation.target" ]; 119 after = [ 120 "systemd-remount-fs.service" 121 "systemd-tmpfiles-setup-dev-early.service" 122 ]; 123 before = [ 124 "systemd-tmpfiles-setup-dev.service" 125 "sysinit.target" 126 "shutdown.target" 127 "sysinit-reactivation.target" 128 ]; 129 conflicts = [ "shutdown.target" ]; 130 restartTriggers = [ 131 userbornConfigJson 132 cfg.passwordFilesLocation 133 ]; 134 # This way we don't have to re-declare all the dependencies to other 135 # services again. 136 aliases = [ "systemd-sysusers.service" ]; 137 138 unitConfig = { 139 Description = "Manage Users and Groups"; 140 DefaultDependencies = false; 141 }; 142 143 serviceConfig = { 144 Type = "oneshot"; 145 RemainAfterExit = true; 146 TimeoutSec = "90s"; 147 148 ExecStart = "${lib.getExe cfg.package} ${userbornConfigJson} ${cfg.passwordFilesLocation}"; 149 150 ExecStartPre = lib.mkMerge [ 151 (lib.mkIf (cfg.passwordFilesLocation != "/etc") [ 152 "${pkgs.coreutils}/bin/mkdir -p ${cfg.passwordFilesLocation}" 153 ]) 154 155 # Make the source files writable before executing userborn. 156 (lib.mkIf (!userCfg.mutableUsers) ( 157 lib.map (file: "-${pkgs.util-linux}/bin/umount ${cfg.passwordFilesLocation}/${file}") passwordFiles 158 )) 159 ]; 160 161 # Make the source files read-only after userborn has finished. 162 ExecStartPost = lib.mkIf (!userCfg.mutableUsers) ( 163 lib.map ( 164 file: 165 "${pkgs.util-linux}/bin/mount --bind -o ro ${cfg.passwordFilesLocation}/${file} ${cfg.passwordFilesLocation}/${file}" 166 ) passwordFiles 167 ); 168 }; 169 }; 170 }; 171 172 # Statically create the symlinks to passwordFilesLocation when they're not 173 # inside /etc because we will not be able to do it at runtime in case of an 174 # immutable /etc! 175 environment.etc = lib.mkIf (cfg.passwordFilesLocation != "/etc") ( 176 lib.listToAttrs ( 177 lib.map ( 178 file: 179 lib.nameValuePair file { 180 source = "${cfg.passwordFilesLocation}/${file}"; 181 mode = "direct-symlink"; 182 } 183 ) passwordFiles 184 ) 185 ); 186 }; 187 188 meta.maintainers = with lib.maintainers; [ nikstur ]; 189 190}