diff --git a/nixos/modules/services/system/nix-daemon.nix b/nixos/modules/services/system/nix-daemon.nix index ef740b9ba8d94..bceaf82798fc7 100644 --- a/nixos/modules/services/system/nix-daemon.nix +++ b/nixos/modules/services/system/nix-daemon.nix @@ -62,6 +62,102 @@ in ]; }) (lib.mkRemovedOptionModule [ "nix" "daemonNiceLevel" ] "Consider nix.daemonCPUSchedPolicy instead.") + { + # Unprivileged Nix daemon + config = lib.mkIf (cfg.daemonUser != "root") { + assertions = [ + { + message = '' + The Nix daemon cannot run as the root group when not running as the root user. + ''; + assertion = cfg.daemonGroup != "root"; + } + { + message = '' + Nix must have the `local-overlay-store` experimental feature when not running as the root user. + ''; + assertion = lib.elem "local-overlay-store" cfg.settings.experimental-features; + } + { + message = '' + Nix must have the `auto-allocate-uids` experimental feature when not running as the root user. + ''; + assertion = lib.elem "auto-allocate-uids" cfg.settings.experimental-features; + } + ]; + + nix.settings = { + sandbox = true; + + auto-allocate-uids = true; + + # No such group would exist within the sandbox, so chowning to it would fail + build-users-group = ""; + + # Default settings from Nix, we need to specify them here to use them in nix code though + start-id = lib.mkDefault (832 * 1024 * 1024); + id-count = lib.mkDefault (128 * 65536); + }; + + systemd.services.nix-daemon = { + # Nix assumes it should use `daemon` if it isn't root, so we have to set `NIX_REMOTE` anyway + environment.NIX_REMOTE = "local?use-roots-daemon=true"; + serviceConfig = { + User = cfg.daemonUser; + Group = cfg.daemonGroup; + + # Empty string needed to disable old Exec + ExecStart = [ + "" + "${nixPackage}/libexec/nix-nswrapper ${toString cfg.settings.start-id} ${toString cfg.settings.id-count} ${nixPackage}/bin/nix-daemon --daemon" + ]; + }; + }; + + # We can't remount rw while unprivileged + boot.nixStoreMountOpts = [ + "nodev" + "nosuid" + ]; + + users.users."${cfg.daemonUser}" = { + subUidRanges = [ + { + startUid = cfg.settings.start-id; + count = cfg.settings.id-count; + } + ]; + subGidRanges = [ + { + startGid = cfg.settings.start-id; + count = cfg.settings.id-count; + } + ]; + }; + + systemd.tmpfiles.rules = [ + "d /nix/store 0755 ${config.nix.daemonUser} ${config.nix.daemonGroup} - -" + "Z /nix/var 0755 ${config.nix.daemonUser} ${config.nix.daemonGroup} - -" + "d /nix/var/nix/builds 0755 ${config.nix.daemonUser} ${config.nix.daemonGroup} 7d -" + "d /nix/var/nix/daemon-socket 0755 ${config.nix.daemonUser} ${config.nix.daemonGroup} - -" + "d /nix/var/nix/gc-roots-socket 0755 ${config.nix.daemonUser} ${config.nix.daemonGroup} - -" + ]; + + systemd.services.nix-roots-daemon = { + serviceConfig.ExecStart = "${config.nix.package.out}/bin/nix --extra-experimental-features nix-command store roots-daemon"; + }; + systemd.sockets.nix-roots-daemon = { + wantedBy = [ + "nix-daemon.service" + ]; + listenStreams = [ "/nix/var/nix/gc-roots-socket/socket" ]; + unitConfig = { + ConditionPathIsReadWrite = "/nix/var/nix/gc-roots-socket"; + RequiresMountsFor = "/nix/store"; + }; + }; + }; + } ]; ###### interface @@ -88,6 +184,24 @@ in ''; }; + daemonUser = lib.mkOption { + type = lib.types.str; + default = "root"; + description = '' + User to use to run the Nix daemon. + If this is not "root" then the Nix daemon will set several settings to preserve functionality. + When setting this option, you must also set `nix.daemonGroup`. + ''; + }; + + daemonGroup = lib.mkOption { + type = lib.types.str; + default = "root"; + description = '' + Group to use to run the Nix daemon. + ''; + }; + daemonCPUSchedPolicy = lib.mkOption { type = lib.types.enum [ "other" @@ -192,7 +306,8 @@ in systemd.packages = [ nixPackage ]; - systemd.tmpfiles.packages = [ nixPackage ]; + # The upstream Nix tmpfiles.d file assumes the daemon runs as root + systemd.tmpfiles.packages = lib.mkIf (cfg.daemonUser == "root") [ nixPackage ]; systemd.sockets.nix-daemon.wantedBy = [ "sockets.target" ]; @@ -200,7 +315,9 @@ in path = [ nixPackage config.programs.ssh.package - ]; + ] + # For running "newuidmap" + ++ lib.optional (cfg.daemonUser != "root") "/run/wrappers"; environment = cfg.envVars