|
62 | 62 | ]; |
63 | 63 | }) |
64 | 64 | (lib.mkRemovedOptionModule [ "nix" "daemonNiceLevel" ] "Consider nix.daemonCPUSchedPolicy instead.") |
| 65 | + { |
| 66 | + # Unprivileged Nix daemon |
| 67 | + config = lib.mkIf (cfg.daemonUser != "root") { |
| 68 | + assertions = [ |
| 69 | + { |
| 70 | + message = '' |
| 71 | + The Nix daemon cannot run as the root group when not running as the root user. |
| 72 | + ''; |
| 73 | + assertion = cfg.daemonGroup != "root"; |
| 74 | + } |
| 75 | + { |
| 76 | + message = '' |
| 77 | + Nix must have the `local-overlay-store` experimental feature when not running as the root user. |
| 78 | + ''; |
| 79 | + assertion = lib.elem "local-overlay-store" cfg.settings.experimental-features; |
| 80 | + } |
| 81 | + { |
| 82 | + message = '' |
| 83 | + Nix must have the `auto-allocate-uids` experimental feature when not running as the root user. |
| 84 | + ''; |
| 85 | + assertion = lib.elem "auto-allocate-uids" cfg.settings.experimental-features; |
| 86 | + } |
| 87 | + ]; |
| 88 | + |
| 89 | + nix.settings = { |
| 90 | + sandbox = true; |
| 91 | + |
| 92 | + auto-allocate-uids = true; |
| 93 | + |
| 94 | + # No such group would exist within the sandbox, so chowning to it would fail |
| 95 | + build-users-group = ""; |
| 96 | + |
| 97 | + # Default settings from Nix, we need to specify them here to use them in nix code though |
| 98 | + start-id = lib.mkDefault (832 * 1024 * 1024); |
| 99 | + id-count = lib.mkDefault (128 * 65536); |
| 100 | + }; |
| 101 | + |
| 102 | + systemd.services.nix-daemon = { |
| 103 | + # Nix assumes it should use `daemon` if it isn't root, so we have to set `NIX_REMOTE` anyway |
| 104 | + environment.NIX_REMOTE = "local?use-roots-daemon=true"; |
| 105 | + serviceConfig = { |
| 106 | + User = cfg.daemonUser; |
| 107 | + Group = cfg.daemonGroup; |
| 108 | + |
| 109 | + # Empty string needed to disable old Exec |
| 110 | + ExecStart = [ |
| 111 | + "" |
| 112 | + "${nixPackage}/libexec/nix-nswrapper ${toString cfg.settings.start-id} ${toString cfg.settings.id-count} ${nixPackage}/bin/nix-daemon --daemon" |
| 113 | + ]; |
| 114 | + }; |
| 115 | + }; |
| 116 | + |
| 117 | + # We can't remount rw while unprivileged |
| 118 | + boot.nixStoreMountOpts = [ |
| 119 | + "nodev" |
| 120 | + "nosuid" |
| 121 | + ]; |
| 122 | + |
| 123 | + users.users."${cfg.daemonUser}" = { |
| 124 | + subUidRanges = [ |
| 125 | + { |
| 126 | + startUid = cfg.settings.start-id; |
| 127 | + count = cfg.settings.id-count; |
| 128 | + } |
| 129 | + ]; |
| 130 | + subGidRanges = [ |
| 131 | + { |
| 132 | + startGid = cfg.settings.start-id; |
| 133 | + count = cfg.settings.id-count; |
| 134 | + } |
| 135 | + ]; |
| 136 | + }; |
| 137 | + |
| 138 | + systemd.tmpfiles.rules = [ |
| 139 | + "d /nix/store 0755 ${config.nix.daemonUser} ${config.nix.daemonGroup} - -" |
| 140 | + "Z /nix/var 0755 ${config.nix.daemonUser} ${config.nix.daemonGroup} - -" |
| 141 | + "d /nix/var/nix/builds 0755 ${config.nix.daemonUser} ${config.nix.daemonGroup} 7d -" |
| 142 | + "d /nix/var/nix/daemon-socket 0755 ${config.nix.daemonUser} ${config.nix.daemonGroup} - -" |
| 143 | + "d /nix/var/nix/gc-roots-socket 0755 ${config.nix.daemonUser} ${config.nix.daemonGroup} - -" |
| 144 | + ]; |
| 145 | + |
| 146 | + systemd.services.nix-roots-daemon = { |
| 147 | + serviceConfig.ExecStart = "${config.nix.package.out}/bin/nix --extra-experimental-features nix-command store roots-daemon"; |
| 148 | + }; |
| 149 | + systemd.sockets.nix-roots-daemon = { |
| 150 | + wantedBy = [ |
| 151 | + "nix-daemon.service" |
| 152 | + ]; |
| 153 | + listenStreams = [ "/nix/var/nix/gc-roots-socket/socket" ]; |
| 154 | + unitConfig = { |
| 155 | + ConditionPathIsReadWrite = "/nix/var/nix/gc-roots-socket"; |
| 156 | + RequiresMountsFor = "/nix/store"; |
| 157 | + }; |
| 158 | + }; |
| 159 | + }; |
| 160 | + } |
65 | 161 | ]; |
66 | 162 |
|
67 | 163 | ###### interface |
|
88 | 184 | ''; |
89 | 185 | }; |
90 | 186 |
|
| 187 | + daemonUser = lib.mkOption { |
| 188 | + type = lib.types.str; |
| 189 | + default = "root"; |
| 190 | + description = '' |
| 191 | + User to use to run the Nix daemon. |
| 192 | + If this is not "root" then the Nix daemon will set several settings to preserve functionality. |
| 193 | + When setting this option, you must also set `nix.daemonGroup`. |
| 194 | + ''; |
| 195 | + }; |
| 196 | + |
| 197 | + daemonGroup = lib.mkOption { |
| 198 | + type = lib.types.str; |
| 199 | + default = "root"; |
| 200 | + description = '' |
| 201 | + Group to use to run the Nix daemon. |
| 202 | + ''; |
| 203 | + }; |
| 204 | + |
91 | 205 | daemonCPUSchedPolicy = lib.mkOption { |
92 | 206 | type = lib.types.enum [ |
93 | 207 | "other" |
|
192 | 306 |
|
193 | 307 | systemd.packages = [ nixPackage ]; |
194 | 308 |
|
195 | | - systemd.tmpfiles.packages = [ nixPackage ]; |
| 309 | + # The upstream Nix tmpfiles.d file assumes the daemon runs as root |
| 310 | + systemd.tmpfiles.packages = lib.mkIf (cfg.daemonUser == "root") [ nixPackage ]; |
196 | 311 |
|
197 | 312 | systemd.sockets.nix-daemon.wantedBy = [ "sockets.target" ]; |
198 | 313 |
|
199 | 314 | systemd.services.nix-daemon = { |
200 | 315 | path = [ |
201 | 316 | nixPackage |
202 | 317 | config.programs.ssh.package |
203 | | - ]; |
| 318 | + ] |
| 319 | + # For running "newuidmap" |
| 320 | + ++ lib.optional (cfg.daemonUser != "root") "/run/wrappers"; |
204 | 321 |
|
205 | 322 | environment = |
206 | 323 | cfg.envVars |
|
0 commit comments