diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d87b76368..f6da4675c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,8 +39,8 @@ jobs: nix_path: nixpkgs=channel:${{ env.NIXPKGS_BRANCH }} - name: Install channels run: | - nix-channel --add https://nixos.org/channels/${{ env.NIXPKGS_BRANCH }} nixpkgs - nix-channel --update + sudo nix-channel --add https://nixos.org/channels/${{ env.NIXPKGS_BRANCH }} nixpkgs + sudo nix-channel --update - name: Install nix-darwin run: | sudo mkdir -p /etc/nix-darwin @@ -61,7 +61,7 @@ jobs: /" \ /etc/nix-darwin/configuration.nix - nix run .#darwin-rebuild -- switch \ + sudo nix run .#darwin-rebuild -- switch \ -I darwin=. \ -I darwin-config=/etc/nix-darwin/configuration.nix - name: Switch to new configuration @@ -72,17 +72,17 @@ jobs: "s/pkgs.vim/pkgs.hello/" \ /etc/nix-darwin/configuration.nix - darwin-rebuild switch + sudo darwin-rebuild switch hello - name: Test uninstallation of nix-darwin run: | # We need to specify `--extra-experimental-features` because `experimental-features` is set by # `cachix/install-nix-action` but not by our default config above - nix run .#darwin-uninstaller \ + sudo nix run .#darwin-uninstaller \ --extra-experimental-features "nix-command flakes" \ --override-input nixpkgs nixpkgs/${{ env.NIXPKGS_BRANCH }} - nix run .#darwin-uninstaller.tests.uninstaller \ + sudo nix run .#darwin-uninstaller.tests.uninstaller \ --extra-experimental-features "nix-command flakes" \ --override-input nixpkgs nixpkgs/${{ env.NIXPKGS_BRANCH }} @@ -112,7 +112,7 @@ jobs: 's/nixpkgs.hostPlatform = "aarch64-darwin";/nixpkgs.hostPlatform = "'$(nix eval --expr builtins.currentSystem --impure --raw)'";/' \ flake.nix popd - nix run .#darwin-rebuild -- switch \ + sudo nix run .#darwin-rebuild -- switch \ --override-input nix-darwin . \ --override-input nixpkgs nixpkgs/${{ env.NIXPKGS_BRANCH }} - name: Switch to new configuration @@ -123,12 +123,12 @@ jobs: "s/pkgs.vim/pkgs.hello/" \ /etc/nix-darwin/flake.nix - darwin-rebuild switch \ + sudo darwin-rebuild switch \ --override-input nix-darwin . \ --override-input nixpkgs nixpkgs/${{ env.NIXPKGS_BRANCH }} hello - name: Test uninstallation of nix-darwin run: | - nix run .#darwin-uninstaller --override-input nixpkgs nixpkgs/${{ env.NIXPKGS_BRANCH }} - nix run .#darwin-uninstaller.tests.uninstaller --override-input nixpkgs nixpkgs/${{ env.NIXPKGS_BRANCH }} + sudo nix run .#darwin-uninstaller --override-input nixpkgs nixpkgs/${{ env.NIXPKGS_BRANCH }} + sudo nix run .#darwin-uninstaller.tests.uninstaller --override-input nixpkgs nixpkgs/${{ env.NIXPKGS_BRANCH }} diff --git a/CHANGELOG b/CHANGELOG index 74591effe..58a6b4af0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,30 @@ +2025-01-30 +- Previously, some nix-darwin options applied to the user running + `darwin-rebuild`. As part of a long‐term migration to make + nix-darwin focus on system‐wide activation and support first‐class + multi‐user setups, all system activation now runs as `root`, and + these options instead apply to the `system.primaryUser` user. + + You will get an evaluation error if you are using any options to + which this applies. + + To continue using these options, set `system.primaryUser` to the name + of the user you have been using to run `darwin-rebuild`. In the long + run, this setting will be deprecated and removed after all the + functionality it is relevant for has been adjusted to allow + specifying the relevant user separately, moved under the + `users.users.*` namespace, or migrated to Home Manager. + + Accordingly, `darwin-rebuild` must now be run as root, the + `system.activationScripts.{extraUserActivation,preUserActivation, + postUserActivation}` settings have been removed, and all activation + scripts are now executed as `root` – be careful if you override any + of them. + + If you run into any unexpected issues with the migration, please + open an issue at + and include as much information as possible. + 2025-01-29 - There is now a `nix.enable` toggle to disable management of the Nix installation. Nix installation management has been made more diff --git a/README.md b/README.md index 20447decc..658d19b38 100644 --- a/README.md +++ b/README.md @@ -98,9 +98,9 @@ Unlike NixOS, `nix-darwin` does not have an installer, you can just run `darwin- ```bash # To use Nixpkgs unstable: -nix run nix-darwin/master#darwin-rebuild -- switch +sudo nix run nix-darwin/master#darwin-rebuild -- switch # To use Nixpkgs 24.11: -nix run nix-darwin/nix-darwin-24.11#darwin-rebuild -- switch +sudo nix run nix-darwin/nix-darwin-24.11#darwin-rebuild -- switch ``` ### Step 3. Using `nix-darwin` @@ -108,7 +108,7 @@ nix run nix-darwin/nix-darwin-24.11#darwin-rebuild -- switch After installing, you can run `darwin-rebuild` to apply changes to your system: ```bash -darwin-rebuild switch +sudo darwin-rebuild switch ``` #### Using flake inputs @@ -155,7 +155,7 @@ To install `nix-darwin`, you can just run `darwin-rebuild switch` to install nix ```bash nix-build '' -A darwin-rebuild -./result/bin/darwin-rebuild switch -I darwin-config=/etc/nix-darwin/configuration.nix +sudo ./result/bin/darwin-rebuild switch -I darwin-config=/etc/nix-darwin/configuration.nix ``` ### Step 4. Using `nix-darwin` @@ -163,7 +163,7 @@ nix-build '' -A darwin-rebuild After installing, you can run `darwin-rebuild` to apply changes to your system: ```bash -darwin-rebuild switch +sudo darwin-rebuild switch ``` ### Step 5. Updating `nix-darwin` @@ -186,13 +186,13 @@ The documentation is also available as manpages by running `man 5 configuration. To run the latest version of the uninstaller, you can run the following command: ``` -nix --extra-experimental-features "nix-command flakes" run nix-darwin#darwin-uninstaller +sudo nix --extra-experimental-features "nix-command flakes" run nix-darwin#darwin-uninstaller ``` If that command doesn't work for you, you can try the locally installed uninstaller: ``` -darwin-uninstaller +sudo darwin-uninstaller ``` ## Tests @@ -218,7 +218,7 @@ flag can also be used to override darwin-config or nixpkgs, for more information on the `-I` flag look at the nix-build [manpage](https://nixos.org/manual/nix/stable/command-ref/nix-build.html). ```bash -darwin-rebuild switch -I darwin=. +sudo darwin-rebuild switch -I darwin=. ``` If you're adding a module, please add yourself to `meta.maintainers`, for example diff --git a/modules/environment/default.nix b/modules/environment/default.nix index 377a9594d..b4b658df7 100644 --- a/modules/environment/default.nix +++ b/modules/environment/default.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ options, config, lib, pkgs, ... }: with lib; @@ -75,7 +75,7 @@ in else if config.system.stateVersion >= 6 then "/etc/nix-darwin/configuration.nix" else - "$HOME/.nixpkgs/darwin-configuration.nix"; + "${config.system.primaryUserHome}/.nixpkgs/darwin-configuration.nix"; defaultText = literalExpression '' if config.nixpkgs.flake.setNixPath then # Don’t set this for flake‐based systems. @@ -83,7 +83,7 @@ in else if config.system.stateVersion >= 6 then "/etc/nix-darwin/configuration.nix" else - "$HOME/.nixpkgs/darwin-configuration.nix" + "''${config.system.primaryUserHome}/.nixpkgs/darwin-configuration.nix" ''; description = '' The path of the darwin configuration.nix used to configure the system, @@ -175,6 +175,16 @@ in config = { + # This is horrible, sorry. + system.requiresPrimaryUser = mkIf ( + config.nix.enable + && !config.nixpkgs.flake.setNixPath + && config.system.stateVersion < 6 + && options.environment.darwinConfig.highestPrio == (mkOptionDefault {}).priority + ) [ + "environment.darwinConfig" + ]; + environment.systemPath = mkMerge [ [ (makeBinPath cfg.profiles) ] (mkOrder 1200 [ "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" ]) diff --git a/modules/examples/lnl.nix b/modules/examples/lnl.nix index 8dff10cc5..f8153b3db 100644 --- a/modules/examples/lnl.nix +++ b/modules/examples/lnl.nix @@ -1,6 +1,8 @@ { config, lib, inputs, pkgs, ... }: { + system.primaryUser = "lnl"; + system.defaults.NSGlobalDomain.AppleKeyboardUIMode = 3; system.defaults.NSGlobalDomain.ApplePressAndHoldEnabled = false; system.defaults.NSGlobalDomain.InitialKeyRepeat = 10; diff --git a/modules/homebrew.nix b/modules/homebrew.nix index 10764fbba..c78d41fe2 100644 --- a/modules/homebrew.nix +++ b/modules/homebrew.nix @@ -559,6 +559,17 @@ in This module also provides a few options for modifying how Homebrew commands behave when you manually invoke them, under [](#opt-homebrew.global)''; + user = mkOption { + type = types.str; + default = config.system.primaryUser; + defaultText = literalExpression "config.system.primaryUser"; + description = '' + The user that owns the Homebrew installation. In most cases + this should be the normal user account that you installed + Homebrew as. + ''; + }; + brewPrefix = mkOption { type = types.str; default = if pkgs.stdenv.hostPlatform.isAarch64 then "/opt/homebrew/bin" else "/usr/local/bin"; @@ -764,6 +775,10 @@ in (mkIf (options.homebrew.autoUpdate.isDefined || options.homebrew.cleanup.isDefined) "The `homebrew' module no longer upgrades outdated formulae and apps by default during `nix-darwin' system activation. To enable upgrading, set `homebrew.onActivation.upgrade = true'.") ]; + system.requiresPrimaryUser = mkIf (cfg.enable && options.homebrew.user.highestPrio == (mkOptionDefault {}).priority) [ + "homebrew.enable" + ]; + homebrew.brews = optional (cfg.whalebrews != [ ]) "whalebrew"; @@ -786,6 +801,9 @@ in echo >&2 "Homebrew bundle..." if [ -f "${cfg.brewPrefix}/brew" ]; then PATH="${cfg.brewPrefix}:${lib.makeBinPath [ pkgs.mas ]}:$PATH" \ + sudo \ + --user=${escapeShellArg cfg.user} \ + --set-home \ ${cfg.onActivation.brewBundleCmd} else echo -e "\e[1;31merror: Homebrew is not installed, skipping...\e[0m" >&2 diff --git a/modules/launchd/default.nix b/modules/launchd/default.nix index 64b6af70a..cfd022f3d 100644 --- a/modules/launchd/default.nix +++ b/modules/launchd/default.nix @@ -170,7 +170,16 @@ in launchd.user.agents = mkOption { default = {}; - type = types.attrsOf (types.submodule serviceOptions); + type = types.attrsOf (types.submodule [ + serviceOptions + ({ name, ... }: { + options.managedBy = lib.mkOption { + type = lib.types.str; + internal = true; + default = lib.showOption [ "launchd" "user" "agents" name ]; + }; + }) + ]); description = '' Definition of per-user launchd agents. @@ -187,6 +196,18 @@ in config = { + system.requiresPrimaryUser = + lib.map ( + name: + lib.showOption [ + "launchd" + "user" + "envVariables" + name + ] + ) (attrNames cfg.user.envVariables) + ++ lib.map ({ managedBy, ... }: managedBy) (attrValues cfg.user.agents); + environment.launchAgents = mapAttrs' toEnvironmentText cfg.agents; environment.launchDaemons = mapAttrs' toEnvironmentText cfg.daemons; diff --git a/modules/module-list.nix b/modules/module-list.nix index d01bbdb90..026a348b8 100644 --- a/modules/module-list.nix +++ b/modules/module-list.nix @@ -10,6 +10,7 @@ ./security/sudo.nix ./system ./system/base.nix + ./system/primary-user.nix ./system/checks.nix ./system/activation-scripts.nix ./system/applications.nix diff --git a/modules/nix/default.nix b/modules/nix/default.nix index e5d0801dc..540fbc569 100644 --- a/modules/nix/default.nix +++ b/modules/nix/default.nix @@ -825,11 +825,18 @@ in # Not in NixOS module nix.nixPath = mkIf (config.system.stateVersion < 2) (mkDefault [ - "darwin=$HOME/.nix-defexpr/darwin" - "darwin-config=$HOME/.nixpkgs/darwin-configuration.nix" + "darwin=${config.system.primaryUserHome}/.nix-defexpr/darwin" + "darwin-config=${config.system.primaryUserHome}/.nixpkgs/darwin-configuration.nix" "/nix/var/nix/profiles/per-user/root/channels" ]); + system.requiresPrimaryUser = mkIf ( + config.system.stateVersion < 2 + && options.nix.nixPath.highestPrio == (mkDefault {}).priotity + ) [ + "nix.nixPath" + ]; + # Set up the environment variables for running Nix. environment.variables = cfg.envVars // { NIX_PATH = cfg.nixPath; }; @@ -869,7 +876,7 @@ in # # TODO: Maybe this could use a more general file placement mechanism # to express that we want it deleted and know only one hash? - system.activationScripts.etcChecks.text = mkAfter '' + system.activationScripts.checks.text = mkAfter '' nixCustomConfKnownSha256Hashes=( # v0.33.0 6787fade1cf934f82db554e78e1fc788705c2c5257fddf9b59bdd963ca6fec63 diff --git a/modules/nix/nix-darwin.nix b/modules/nix/nix-darwin.nix index 677acbe55..a22e3e033 100644 --- a/modules/nix/nix-darwin.nix +++ b/modules/nix/nix-darwin.nix @@ -5,6 +5,7 @@ let inherit (config.system) profile; inherit (config.environment) systemPath; nixPath = lib.optionalString config.nix.enable (lib.concatStringsSep ":" config.nix.nixPath); + nixPackage = if config.nix.enable then config.nix.package else null; }; darwin-uninstaller = pkgs.callPackage ../../pkgs/darwin-uninstaller { }; diff --git a/modules/services/activate-system/default.nix b/modules/services/activate-system/default.nix index df0b48e4b..cb0fd12fe 100644 --- a/modules/services/activate-system/default.nix +++ b/modules/services/activate-system/default.nix @@ -1,5 +1,35 @@ { config, lib, pkgs, ... }: +let + activationPath = + lib.makeBinPath ( + [ + pkgs.gnugrep + pkgs.coreutils + ] ++ lib.optionals config.nix.enable [ config.nix.package ] + ) + + lib.optionalString (!config.nix.enable) '' + $( + # If `nix.enable` is off, there might be an unmanaged Nix + # installation (say in `/nix/var/nix/profiles/default`) that + # activation scripts (such as Home Manager) want to find on the + # `$PATH`. Search for it directly to avoid polluting the + # activation script environment with everything on the + # `environment.systemPath`. + if nixEnvPath=$( + PATH="${config.environment.systemPath}" command -v nix-env + ); then + printf ':' + ${lib.getExe' pkgs.coreutils "dirname"} -- "$( + ${lib.getExe' pkgs.coreutils "readlink"} \ + --canonicalize-missing \ + -- "$nixEnvPath" + )" + fi + )'' + + ":/usr/bin:/bin:/usr/sbin:/sbin"; +in + { imports = [ (lib.mkRemovedOptionModule [ "services" "activate-system" "enable" ] "The `activate-system` service is now always enabled as it is necessary for a working `nix-darwin` setup.") @@ -10,7 +40,17 @@ script = '' set -e set -o pipefail - export PATH="${pkgs.gnugrep}/bin:${pkgs.coreutils}/bin:@out@/sw/bin:/usr/bin:/bin:/usr/sbin:/sbin" + + PATH="${activationPath}" + + export PATH + export USER=root + export LOGNAME=root + export HOME=~root + export MAIL=/var/mail/root + export SHELL=$BASH + export LANG=C + export LC_CTYPE=UTF-8 systemConfig=$(cat ${config.system.profile}/systemConfig) @@ -25,7 +65,7 @@ ln -sfn /run/current-system /nix/var/nix/gcroots/current-system fi - ${config.system.activationScripts.etcChecks.text} + ${config.system.activationScripts.checks.text} ${config.system.activationScripts.etc.text} ${config.system.activationScripts.keyboard.text} ''; diff --git a/modules/services/aerospace/default.nix b/modules/services/aerospace/default.nix index 3080579d2..539e3a9a1 100644 --- a/modules/services/aerospace/default.nix +++ b/modules/services/aerospace/default.nix @@ -253,6 +253,7 @@ in KeepAlive = true; RunAtLoad = true; }; + managedBy = "services.aerospace.enable"; }; } ); diff --git a/modules/services/chunkwm.nix b/modules/services/chunkwm.nix index 354288a0d..6d8393e0f 100644 --- a/modules/services/chunkwm.nix +++ b/modules/services/chunkwm.nix @@ -126,6 +126,7 @@ in serviceConfig.RunAtLoad = true; serviceConfig.KeepAlive = true; serviceConfig.ProcessType = "Interactive"; + managedBy = "services.chunkwm.enable"; }; }; diff --git a/modules/services/emacs.nix b/modules/services/emacs.nix index ec98950ba..5e145e0e7 100644 --- a/modules/services/emacs.nix +++ b/modules/services/emacs.nix @@ -49,6 +49,7 @@ in { RunAtLoad = true; KeepAlive = true; }; + managedBy = "services.emacs.enable"; }; }; diff --git a/modules/services/ipfs.nix b/modules/services/ipfs.nix index e7cdb746e..a628fcafb 100644 --- a/modules/services/ipfs.nix +++ b/modules/services/ipfs.nix @@ -64,6 +64,7 @@ in StandardErrorPath = cfg.logFile; EnvironmentVariables = {} // (optionalAttrs (cfg.ipfsPath != null) { IPFS_PATH = cfg.ipfsPath; }); }; + managedBy = "services.ipfs.enable"; }; }; } diff --git a/modules/services/jankyborders/default.nix b/modules/services/jankyborders/default.nix index cb7ab1ebd..61b560c04 100644 --- a/modules/services/jankyborders/default.nix +++ b/modules/services/jankyborders/default.nix @@ -162,6 +162,7 @@ in { ++ (optionalArg "order" cfg.order); serviceConfig.KeepAlive = true; serviceConfig.RunAtLoad = true; + managedBy = "services.jankyborders.enable"; }; }; } diff --git a/modules/services/karabiner-elements/default.nix b/modules/services/karabiner-elements/default.nix index 8be2ddff6..3a2cee1b8 100644 --- a/modules/services/karabiner-elements/default.nix +++ b/modules/services/karabiner-elements/default.nix @@ -84,6 +84,7 @@ in "${parentAppDir}/.Karabiner-VirtualHIDDevice-Manager.app/Contents/MacOS/Karabiner-VirtualHIDDevice-Manager" "activate" ]; serviceConfig.RunAtLoad = true; + managedBy = "services.karabiner-elements.enable"; }; # We need this to run every reboot as /run gets nuked so we can't put this @@ -105,6 +106,7 @@ in ]; serviceConfig.Label = "org.pqrs.karabiner.karabiner_session_monitor"; serviceConfig.KeepAlive = true; + managedBy = "services.karabiner-elements.enable"; }; environment.userLaunchAgents."org.pqrs.karabiner.agent.karabiner_grabber.plist".source = "${cfg.package}/Library/LaunchAgents/org.pqrs.karabiner.agent.karabiner_grabber.plist"; diff --git a/modules/services/khd/default.nix b/modules/services/khd/default.nix index 7594baffe..a09abab0f 100644 --- a/modules/services/khd/default.nix +++ b/modules/services/khd/default.nix @@ -57,6 +57,8 @@ in SockType = "dgram"; SockFamily = "IPv4"; }; + + managedBy = "services.khd.enable"; }; }; diff --git a/modules/services/kwm/default.nix b/modules/services/kwm/default.nix index 5fb6c5635..f7d35f19f 100644 --- a/modules/services/kwm/default.nix +++ b/modules/services/kwm/default.nix @@ -47,6 +47,7 @@ in SockType = "dgram"; SockFamily = "IPv4"; }; + managedBy = "services.kwm.enable"; }; }; diff --git a/modules/services/lorri.nix b/modules/services/lorri.nix index c4e1acee2..2d023af68 100644 --- a/modules/services/lorri.nix +++ b/modules/services/lorri.nix @@ -38,6 +38,7 @@ in ]; environment.systemPackages = [ pkgs.lorri ]; + launchd.user.agents.lorri = { command = with pkgs; "${lorri}/bin/lorri daemon"; path = with pkgs; [ config.nix.package git gnutar gzip ]; @@ -49,6 +50,7 @@ in StandardErrorPath = cfg.logFile; EnvironmentVariables = { NIX_PATH = "nixpkgs=" + toString pkgs.path; }; }; + managedBy = "services.lorri.enable"; }; }; } diff --git a/modules/services/mail/offlineimap.nix b/modules/services/mail/offlineimap.nix index 81c8bdbb2..75dd261b7 100644 --- a/modules/services/mail/offlineimap.nix +++ b/modules/services/mail/offlineimap.nix @@ -56,6 +56,7 @@ in { serviceConfig.StartInterval = cfg.startInterval; serviceConfig.StandardErrorPath = "/var/log/offlineimap.log"; serviceConfig.StandardOutPath = "/var/log/offlineimap.log"; + managedBy = "services.offlineimap.enable"; }; }; } diff --git a/modules/services/mopidy.nix b/modules/services/mopidy.nix index be3c05e14..241628a1d 100644 --- a/modules/services/mopidy.nix +++ b/modules/services/mopidy.nix @@ -41,6 +41,7 @@ in serviceConfig.Program = "${cfg.package}/bin/mopidy"; serviceConfig.RunAtLoad = true; serviceConfig.KeepAlive = true; + managedBy = "services.mopidy.enable"; }; }) (mkIf cfg.mediakeys.enable { @@ -48,6 +49,7 @@ in serviceConfig.Program = "${cfg.package}/bin/mpdkeys"; serviceConfig.RunAtLoad = true; serviceConfig.KeepAlive = true; + managedBy = "services.mopidy.mediakeys.enable"; }; }) ]; diff --git a/modules/services/postgresql/default.nix b/modules/services/postgresql/default.nix index 64dfad460..fab025ce1 100644 --- a/modules/services/postgresql/default.nix +++ b/modules/services/postgresql/default.nix @@ -363,6 +363,7 @@ in serviceConfig.EnvironmentVariables = { PGDATA = cfg.dataDir; }; + managedBy = "services.postgresql.enable"; }; }; diff --git a/modules/services/privoxy/default.nix b/modules/services/privoxy/default.nix index b3147232b..e40da54cc 100644 --- a/modules/services/privoxy/default.nix +++ b/modules/services/privoxy/default.nix @@ -61,6 +61,7 @@ in ${cfg.package}/bin/privoxy /etc/privoxy-config ''; serviceConfig.KeepAlive = true; + managedBy = "services.privoxy.enable"; }; }; } diff --git a/modules/services/redis/default.nix b/modules/services/redis/default.nix index ccacd3b5e..c442b0a20 100644 --- a/modules/services/redis/default.nix +++ b/modules/services/redis/default.nix @@ -67,6 +67,7 @@ in launchd.user.agents.redis = { command = "${cfg.package}/bin/redis-server /etc/redis.conf"; serviceConfig.KeepAlive = true; + managedBy = "services.redis.enable"; }; environment.etc."redis.conf".text = '' diff --git a/modules/services/sketchybar/default.nix b/modules/services/sketchybar/default.nix index c29eec275..1d096cb05 100644 --- a/modules/services/sketchybar/default.nix +++ b/modules/services/sketchybar/default.nix @@ -54,6 +54,7 @@ in ++ optionals (cfg.config != "") [ "--config" "${configFile}" ]; serviceConfig.KeepAlive = true; serviceConfig.RunAtLoad = true; + managedBy = "services.sketchybar.enable"; }; }; } diff --git a/modules/services/skhd/default.nix b/modules/services/skhd/default.nix index 1f5d0cf65..9a0c1d629 100644 --- a/modules/services/skhd/default.nix +++ b/modules/services/skhd/default.nix @@ -40,6 +40,8 @@ in ++ optionals (cfg.skhdConfig != "") [ "-c" "/etc/skhdrc" ]; serviceConfig.KeepAlive = true; serviceConfig.ProcessType = "Interactive"; + + managedBy = "services.skhd.enable"; }; }; diff --git a/modules/services/spacebar/default.nix b/modules/services/spacebar/default.nix index a56dac537..7aa3c09f8 100644 --- a/modules/services/spacebar/default.nix +++ b/modules/services/spacebar/default.nix @@ -69,6 +69,8 @@ in serviceConfig.EnvironmentVariables = { PATH = "${cfg.package}/bin:${config.environment.systemPath}"; }; + + managedBy = "services.spacebar.enable"; }; }; } diff --git a/modules/services/spotifyd.nix b/modules/services/spotifyd.nix index 612bae13a..a70ba6cfe 100644 --- a/modules/services/spotifyd.nix +++ b/modules/services/spotifyd.nix @@ -58,6 +58,7 @@ in RunAtLoad = true; ThrottleInterval = 30; }; + managedBy = "services.spotifyd.enable"; }; }; } diff --git a/modules/services/synapse-bt.nix b/modules/services/synapse-bt.nix index d85a2cd0f..f93cdf14f 100644 --- a/modules/services/synapse-bt.nix +++ b/modules/services/synapse-bt.nix @@ -66,6 +66,7 @@ in command = "${cfg.package}/bin/synapse --config ${configFile}"; serviceConfig.KeepAlive = true; serviceConfig.RunAtLoad = true; + managedBy = "services.synapse-bt.enable"; }; }; diff --git a/modules/services/synergy/default.nix b/modules/services/synergy/default.nix index 2a9e088ca..679424a56 100644 --- a/modules/services/synergy/default.nix +++ b/modules/services/synergy/default.nix @@ -130,6 +130,7 @@ in serviceConfig.KeepAlive = true; serviceConfig.RunAtLoad = cfg.client.autoStart; serviceConfig.ProcessType = "Interactive"; + managedBy = "services.synergy.client.enable"; }; }) @@ -145,6 +146,7 @@ in serviceConfig.KeepAlive = true; serviceConfig.RunAtLoad = cfg.server.autoStart; serviceConfig.ProcessType = "Interactive"; + managedBy = "services.synergy.server.enable"; }; }) ]; diff --git a/modules/services/trezord.nix b/modules/services/trezord.nix index 8da05f342..5d5229835 100644 --- a/modules/services/trezord.nix +++ b/modules/services/trezord.nix @@ -42,6 +42,7 @@ in { KeepAlive = true; RunAtLoad = true; }; + managedBy = "services.trezord.enable"; }; }; } diff --git a/modules/services/yabai/default.nix b/modules/services/yabai/default.nix index fe9d3f96f..ae14ae452 100644 --- a/modules/services/yabai/default.nix +++ b/modules/services/yabai/default.nix @@ -85,6 +85,8 @@ in serviceConfig.EnvironmentVariables = { PATH = "${cfg.package}/bin:${config.environment.systemPath}"; }; + + managedBy = "services.yabai.enable"; }; }) diff --git a/modules/system/activation-scripts.nix b/modules/system/activation-scripts.nix index c8ad20adc..23f102436 100644 --- a/modules/system/activation-scripts.nix +++ b/modules/system/activation-scripts.nix @@ -14,10 +14,12 @@ let }; activationPath = - lib.makeBinPath [ - pkgs.gnugrep - pkgs.coreutils - ] + lib.makeBinPath ( + [ + pkgs.gnugrep + pkgs.coreutils + ] ++ lib.optionals config.nix.enable [ config.nix.package ] + ) + lib.optionalString (!config.nix.enable) '' $( # If `nix.enable` is off, there might be an unmanaged Nix @@ -37,8 +39,7 @@ let )" fi )'' - + ":@out@/sw/bin:/usr/bin:/bin:/usr/sbin:/sbin"; - + + ":/usr/bin:/bin:/usr/sbin:/sbin"; in { @@ -62,24 +63,58 @@ in config = { + assertions = + map + (userActivationOption: { + assertion = !config.system.activationScripts ? ${userActivationOption}; + message = '' + The `system.activationScripts.${userActivationOption}` option has + been removed, as all activation now takes place as `root`. Please + restructure your custom activation scripts appropriately, + potentially using `sudo` if you need to run commands as a user. + ''; + }) + [ + "extraUserActivation" + "preUserActivation" + "postUserActivation" + ]; + system.activationScripts.script.text = '' - #! ${stdenv.shell} + #!/usr/bin/env -i ${stdenv.shell} + # shellcheck shell=bash + # shellcheck disable=SC2096 + set -e set -o pipefail PATH="${activationPath}" + export PATH + export USER=root + export LOGNAME=root + export HOME=~root + export MAIL=/var/mail/root + export SHELL=$BASH + export LANG=C + export LC_CTYPE=UTF-8 systemConfig=@out@ # Ensure a consistent umask. umask 0022 + cd / + + if [[ $(id -u) -ne 0 ]]; then + printf >&2 '\e[1;31merror: `activate` must be run as root\e[0m\n' + exit 2 + fi + ${cfg.activationScripts.preActivation.text} - # We run `etcChecks` again just in case someone runs `activate` - # directly without `activate-user`. - ${cfg.activationScripts.etcChecks.text} + ${cfg.activationScripts.checks.text} + ${cfg.activationScripts.createRun.text} ${cfg.activationScripts.extraActivation.text} ${cfg.activationScripts.groups.text} ${cfg.activationScripts.users.text} @@ -88,7 +123,9 @@ in ${cfg.activationScripts.patches.text} ${cfg.activationScripts.etc.text} ${cfg.activationScripts.defaults.text} + ${cfg.activationScripts.userDefaults.text} ${cfg.activationScripts.launchd.text} + ${cfg.activationScripts.userLaunchd.text} ${cfg.activationScripts.nix-daemon.text} ${cfg.activationScripts.time.text} ${cfg.activationScripts.networking.text} @@ -96,6 +133,7 @@ in ${cfg.activationScripts.keyboard.text} ${cfg.activationScripts.fonts.text} ${cfg.activationScripts.nvram.text} + ${cfg.activationScripts.homebrew.text} ${cfg.activationScripts.postActivation.text} @@ -111,48 +149,11 @@ in fi ''; - # FIXME: activationScripts.checks should be system level - system.activationScripts.userScript.text = '' - #! ${stdenv.shell} - set -e - set -o pipefail - - PATH="${activationPath}" - export PATH - - systemConfig=@out@ - - _status=0 - trap "_status=1" ERR - - # Ensure a consistent umask. - umask 0022 - - ${cfg.activationScripts.preUserActivation.text} - - # This should be running at the system level, but as user activation runs first - # we run it here with sudo - ${cfg.activationScripts.createRun.text} - ${cfg.activationScripts.checks.text} - ${cfg.activationScripts.etcChecks.text} - ${cfg.activationScripts.extraUserActivation.text} - ${cfg.activationScripts.userDefaults.text} - ${cfg.activationScripts.userLaunchd.text} - ${cfg.activationScripts.homebrew.text} - - ${cfg.activationScripts.postUserActivation.text} - - exit $_status - ''; - # Extra activation scripts, that can be customized by users # don't use this unless you know what you are doing. system.activationScripts.extraActivation.text = mkDefault ""; system.activationScripts.preActivation.text = mkDefault ""; system.activationScripts.postActivation.text = mkDefault ""; - system.activationScripts.extraUserActivation.text = mkDefault ""; - system.activationScripts.preUserActivation.text = mkDefault ""; - system.activationScripts.postUserActivation.text = mkDefault ""; }; } diff --git a/modules/system/applications.nix b/modules/system/applications.nix index 9dd876657..a2277c7a0 100644 --- a/modules/system/applications.nix +++ b/modules/system/applications.nix @@ -30,12 +30,15 @@ in [ -L "$1" ] && [ "''${link#*-}" = 'system-applications/Applications' ] } - # Clean up for links created at the old location in HOME - if ourLink ~/Applications; then - rm ~/Applications - elif ourLink ~/Applications/'Nix Apps'; then - rm ~/Applications/'Nix Apps' - fi + ${lib.optionalString (config.system.primaryUser != null) '' + # Clean up for links created at the old location in HOME + # TODO: Remove this in 25.11. + if ourLink ~${config.system.primaryUser}/Applications; then + rm ~${config.system.primaryUser}/Applications + elif ourLink ~${config.system.primaryUser}/Applications/'Nix Apps'; then + rm ~${config.system.primaryUser}/Applications/'Nix Apps' + fi + ''} if [ ! -e '/Applications/Nix Apps' ] \ || ourLink '/Applications/Nix Apps'; then diff --git a/modules/system/base.nix b/modules/system/base.nix index 40c3699b2..1bdef63f3 100644 --- a/modules/system/base.nix +++ b/modules/system/base.nix @@ -4,26 +4,26 @@ system.activationScripts.createRun.text = '' if [[ $(stat -c '%a' /etc/synthetic.conf) != "644" ]]; then echo "fixing permissions on /etc/synthetic.conf..." - sudo chmod 644 /etc/synthetic.conf + chmod 644 /etc/synthetic.conf fi if [[ $(grep -c '^run\b' /etc/synthetic.conf) -gt 1 ]]; then echo "found duplicate run entries in /etc/synthetic.conf, removing..." - sudo sed -i "" -e '/^run\tprivate\/var\/run$/d' /etc/synthetic.conf + sed -i "" -e '/^run\tprivate\/var\/run$/d' /etc/synthetic.conf fi if ! grep -q '^run\b' /etc/synthetic.conf 2>/dev/null; then echo "setting up /run via /etc/synthetic.conf..." - printf 'run\tprivate/var/run\n' | sudo tee -a /etc/synthetic.conf >/dev/null + printf 'run\tprivate/var/run\n' | tee -a /etc/synthetic.conf >/dev/null fi - sudo /System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util -t || true + /System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util -t || true if [[ ! -L /run ]]; then printf >&2 'error: apfs.util failed to symlink /run, aborting activation\n' printf >&2 'To create a symlink from /run to /var/run, please run:\n' printf >&2 '\n' - printf >&2 "$ printf 'run\tprivate/var/run\n' | sudo tee -a /etc/synthetic.conf\n" + printf >&2 "$ printf 'run\tprivate/var/run\n' | tee -a /etc/synthetic.conf\n" printf >&2 '$ sudo /System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util -t\n' printf >&2 '\n' printf >&2 'The current contents of /etc/synthetic.conf is:\n' diff --git a/modules/system/checks.nix b/modules/system/checks.nix index 6afe796cb..ce33e10cc 100644 --- a/modules/system/checks.nix +++ b/modules/system/checks.nix @@ -31,6 +31,18 @@ let fi ''; + primaryUser = '' + primaryUser=${escapeShellArg config.system.primaryUser} + if ! id -- "$primaryUser" >/dev/null 2>&1; then + printf >&2 '\e[1;31merror: primary user `%s` does not exist, aborting activation\e[0m\n' \ + "$primaryUser" + printf >&2 'Please ensure that `system.primaryUser` is set to the name of an\n' + printf >&2 'existing user. Usually this should be the user you have been using to\n' + printf >&2 'run `darwin-rebuild`.\n' + exit 2 + fi + ''; + determinate = '' if [[ -e /usr/local/bin/determinate-nixd ]]; then printf >&2 '\e[1;31merror: Determinate detected, aborting activation\e[0m\n' @@ -150,49 +162,70 @@ let ''; nixPath = '' - nixPath=${concatMapStringsSep ":" escapeDoubleQuote config.nix.nixPath}:$HOME/.nix-defexpr/channels + findPathEntry() { + NIX_PATH=${concatMapStringsSep ":" escapeDoubleQuote config.nix.nixPath} \ + nix-instantiate --find-file "$@" >/dev/null + } - darwinConfig=$(NIX_PATH=$nixPath nix-instantiate --find-file darwin-config) || true - if ! test -e "$darwinConfig"; then - echo "error: Changed but target does not exist, aborting activation" >&2 - echo "Create ''${darwinConfig:-/etc/nix-darwin/configuration.nix} or set environment.darwinConfig:" >&2 - echo >&2 - echo " environment.darwinConfig = \"$(nix-instantiate --find-file darwin-config 2> /dev/null || echo '***')\";" >&2 - echo >&2 - echo "And rebuild using (only required once)" >&2 - echo "$ darwin-rebuild switch -I \"darwin-config=$(nix-instantiate --find-file darwin-config 2> /dev/null || echo '***')\"" >&2 - echo >&2 - echo >&2 - exit 2 + if ! findPathEntry darwin-config; then + printf >&2 '\e[1;31merror: can’t find ``, aborting activation\e[0m\n' + printf >&2 'Make sure that %s exists,\n' \ + ${escapeDoubleQuote ( + if config.environment.darwinConfig == null then + "the \\`\\` entry in `nix.nixPath`" + else + "\\`${config.environment.darwinConfig}\\`" + )} + printf >&2 'or else set `environment.darwinConfig` to the correct path to your\n' + printf >&2 '`configuration.nix` file.\n' + printf >&2 '\n' + printf >&2 'The setting should not reference `$HOME`, as `root` now needs to be\n' + printf >&2 'able to find your configuration. If you previously used `$HOME` in\n' + printf >&2 'your `environment.darwinConfig` path, please replace it with the\n' + printf >&2 'full path to your home directory.\n' + exit 2 fi - darwinPath=$(NIX_PATH=$nixPath nix-instantiate --find-file darwin) || true - if ! test -e "$darwinPath"; then - echo "error: Changed but target does not exist, aborting activation" >&2 - echo "Add the darwin repo as a channel or set nix.nixPath:" >&2 - echo "$ sudo nix-channel --add https://github.com/LnL7/nix-darwin/archive/master.tar.gz darwin" >&2 - echo "$ sudo nix-channel --update" >&2 - echo >&2 - echo "or set" >&2 - echo >&2 - echo " nix.nixPath = [ \"darwin=$(nix-instantiate --find-file darwin 2> /dev/null || echo '***')\" ];" >&2 - echo >&2 - exit 2 - fi + checkChannel() { + if findPathEntry "$1"; then + return + fi - nixpkgsPath=$(NIX_PATH=$nixPath nix-instantiate --find-file nixpkgs) || true - if ! test -e "$nixpkgsPath"; then - echo "error: Changed but target does not exist, aborting activation" >&2 - echo "Add a nixpkgs channel or set nix.nixPath:" >&2 - echo "$ sudo nix-channel --add http://nixos.org/channels/nixpkgs-unstable nixpkgs" >&2 - echo "$ sudo nix-channel --update" >&2 - echo >&2 - echo "or set" >&2 - echo >&2 - echo " nix.nixPath = [ \"nixpkgs=$(nix-instantiate --find-file nixpkgs 2> /dev/null || echo '***')\" ];" >&2 - echo >&2 - exit 2 - fi + printf >&2 '\e[1;31merror: can’t find `<%s>`, aborting activation\e[0m\n' \ + "$1" + printf >&2 'The most likely reason for this is that the channel is owned\n' + printf >&2 'by your user. This no longer works now that nix-darwin has moved over\n' + printf >&2 'to `root`‐based activation.\n' + printf >&2 '\n' + printf >&2 'You can check your current channels with:\n' + printf >&2 '\n' + printf >&2 ' $ sudo nix-channel --list\n' + printf >&2 ' nixpkgs https://nixos.org/channels/NIXPKGS-BRANCH\n' + printf >&2 ' darwin https://github.com/LnL7/nix-darwin/archive/NIX-DARWIN-BRANCH.tar.gz\n' + printf >&2 ' …\n' + printf >&2 ' $ nix-channel --list\n' + printf >&2 ' …\n' + printf >&2 '\n' + printf >&2 'You should see `darwin` and `nixpkgs` in `sudo nix-channel --list`.\n' + printf >&2 'If `darwin` or `nixpkgs` are present in `nix-channel --list` (without\n' + printf >&2 '`sudo`), you should delete them with `nix-channel --remove NAME`.\n' + printf >&2 '\n' + printf >&2 'You can then fix your channels like this:\n' + printf >&2 '\n' + printf >&2 ' $ sudo nix-channel --add https://nixos.org/channels/NIXPKGS-BRANCH nixpkgs\n' + printf >&2 ' $ sudo nix-channel --add https://github.com/LnL7/nix-darwin/archive/NIX-DARWIN-BRANCH.tar.gz darwin\n' + printf >&2 ' $ sudo nix-channel --update\n' + printf >&2 '\n' + printf >&2 'After that, activating your system again should work correctly. If it\n' + printf >&2 'doesn’t, please open an issue at\n' + printf >&2 ' and include as much\n' + printf >&2 'information as possible.\n' + exit 2 + } + + checkChannel nixpkgs + + checkChannel darwin ''; # TODO: Remove this a couple years down the line when we can assume @@ -275,6 +308,7 @@ in system.checks.text = mkMerge [ (mkIf cfg.verifyMacOSVersion macOSVersion) + (mkIf (config.system.primaryUser != null) primaryUser) (mkIf config.nix.enable determinate) (mkIf cfg.verifyBuildUsers preSequoiaBuildUsers) (mkIf cfg.verifyBuildUsers buildGroupID) diff --git a/modules/system/default.nix b/modules/system/default.nix index 8351dcc66..29a477eba 100644 --- a/modules/system/default.nix +++ b/modules/system/default.nix @@ -95,7 +95,51 @@ in nativeBuildInputs = [ pkgs.shellcheck ]; activationScript = cfg.activationScripts.script.text; - activationUserScript = cfg.activationScripts.userScript.text; + + # This is for compatibility with older `darwin-rebuild`s and + # third‐party deployment tools. + # + # TODO: Remove this in 25.11. + activationUserScript = '' + #! ${pkgs.stdenv.shell} + # nix-darwin: deprecated + + # Hack to handle upgrades. + if + [[ -e /run/current-system/activate-user ]] \ + && ! grep -q '^# nix-darwin: deprecated$' \ + /run/current-system/activate-user + then + exit + fi + + printf >&2 '\e[1;31mwarning: `activate-user` is deprecated and will be removed in 25.11\e[0m\n' + printf >&2 'This is usually due to the use of a non‐standard activation/deployment\n' + printf >&2 'tool. If you maintain one of these tools, our advice is:\n' + printf >&2 '\n' + printf >&2 ' You can identify a post‐user‐activation configuration by the absence\n' + printf >&2 ' of `activate-user` or the second line of the script being\n' + printf >&2 ' `# nix-darwin: deprecated`.\n' + printf >&2 '\n' + printf >&2 ' We recommend running `$systemConfig/sw/bin/darwin-rebuild activate`\n' + printf >&2 ' to activate built configurations; for a pre‐user‐activation\n' + printf >&2 ' configuration this should be run as a normal user, and for a\n' + printf >&2 ' post‐user‐activation configuration it should be run as `root`.\n' + printf >&2 '\n' + printf >&2 ' If you can’t or don’t want to use `darwin-rebuild activate`, then you\n' + printf >&2 ' should skip running `activate-user` for post‐user‐activation\n' + printf >&2 ' configurations and continue running `activate` as `root`.\n' + printf >&2 '\n' + printf >&2 ' In 25.11, `darwin-rebuild` will stop running `activate-user` and this\n' + printf >&2 ' transition script will be deleted; you should be able to safely\n' + printf >&2 ' remove all related logic by then.\n' + printf >&2 '\n' + printf >&2 'Otherwise, you should report this to the deployment tool developers. If\n' + printf >&2 'you don’t use a third‐party deployment tool, please open a bug report\n' + printf >&2 'at and include as much\n' + printf >&2 'detail about your setup as possible.\n' + ''; + inherit (cfg) darwinLabel; darwinVersionJson = (pkgs.formats.json {}).generate "darwin-version.json" ( @@ -131,7 +175,6 @@ in unset activationScript echo "$activationUserScript" > $out/activate-user - substituteInPlace $out/activate-user --subst-var out chmod u+x $out/activate-user unset activationUserScript diff --git a/modules/system/defaults-write.nix b/modules/system/defaults-write.nix index a00b0e427..4b32bf3db 100644 --- a/modules/system/defaults-write.nix +++ b/modules/system/defaults-write.nix @@ -1,4 +1,4 @@ -{ config, lib, ... }: +{ options, config, lib, ... }: with lib; @@ -9,6 +9,10 @@ let "defaults write ${domain} '${key}' $'${strings.escape [ "'" ] (generators.toPlist { } value)}'"; defaultsToList = domain: attrs: mapAttrsToList (writeDefault domain) (filterAttrs (n: v: v != null) attrs); + userDefaultsToList = domain: attrs: map + (cmd: "sudo --user=${escapeShellArg config.system.primaryUser} -- ${cmd}") + (defaultsToList domain attrs); + # Filter out options to not pass through # dock has alias options that we need to ignore dockFiltered = (builtins.removeAttrs cfg.dock ["expose-group-by-app"]); @@ -18,28 +22,28 @@ let loginwindow = defaultsToList "/Library/Preferences/com.apple.loginwindow" cfg.loginwindow; smb = defaultsToList "/Library/Preferences/SystemConfiguration/com.apple.smb.server" cfg.smb; SoftwareUpdate = defaultsToList "/Library/Preferences/com.apple.SoftwareUpdate" cfg.SoftwareUpdate; + CustomSystemPreferences = flatten (mapAttrsToList (name: value: defaultsToList name value) cfg.CustomSystemPreferences); # userDefaults - GlobalPreferences = defaultsToList ".GlobalPreferences" cfg.".GlobalPreferences"; - LaunchServices = defaultsToList "com.apple.LaunchServices" cfg.LaunchServices; - NSGlobalDomain = defaultsToList "-g" cfg.NSGlobalDomain; - menuExtraClock = defaultsToList "com.apple.menuextra.clock" cfg.menuExtraClock; - dock = defaultsToList "com.apple.dock" dockFiltered; - finder = defaultsToList "com.apple.finder" cfg.finder; - hitoolbox = defaultsToList "com.apple.HIToolbox" cfg.hitoolbox; - magicmouse = defaultsToList "com.apple.AppleMultitouchMouse" cfg.magicmouse; - magicmouseBluetooth = defaultsToList "com.apple.driver.AppleMultitouchMouse.mouse" cfg.magicmouse; - screencapture = defaultsToList "com.apple.screencapture" cfg.screencapture; - screensaver = defaultsToList "com.apple.screensaver" cfg.screensaver; - spaces = defaultsToList "com.apple.spaces" cfg.spaces; - trackpad = defaultsToList "com.apple.AppleMultitouchTrackpad" cfg.trackpad; - trackpadBluetooth = defaultsToList "com.apple.driver.AppleBluetoothMultitouch.trackpad" cfg.trackpad; - universalaccess = defaultsToList "com.apple.universalaccess" cfg.universalaccess; - ActivityMonitor = defaultsToList "com.apple.ActivityMonitor" cfg.ActivityMonitor; - WindowManager = defaultsToList "com.apple.WindowManager" cfg.WindowManager; - controlcenter = defaultsToList "~/Library/Preferences/ByHost/com.apple.controlcenter" cfg.controlcenter; - CustomUserPreferences = flatten (mapAttrsToList (name: value: defaultsToList name value) cfg.CustomUserPreferences); - CustomSystemPreferences = flatten (mapAttrsToList (name: value: defaultsToList name value) cfg.CustomSystemPreferences); + GlobalPreferences = userDefaultsToList ".GlobalPreferences" cfg.".GlobalPreferences"; + LaunchServices = userDefaultsToList "com.apple.LaunchServices" cfg.LaunchServices; + NSGlobalDomain = userDefaultsToList "-g" cfg.NSGlobalDomain; + menuExtraClock = userDefaultsToList "com.apple.menuextra.clock" cfg.menuExtraClock; + dock = userDefaultsToList "com.apple.dock" dockFiltered; + finder = userDefaultsToList "com.apple.finder" cfg.finder; + hitoolbox = userDefaultsToList "com.apple.HIToolbox" cfg.hitoolbox; + magicmouse = userDefaultsToList "com.apple.AppleMultitouchMouse" cfg.magicmouse; + magicmouseBluetooth = userDefaultsToList "com.apple.driver.AppleMultitouchMouse.mouse" cfg.magicmouse; + screencapture = userDefaultsToList "com.apple.screencapture" cfg.screencapture; + screensaver = userDefaultsToList "com.apple.screensaver" cfg.screensaver; + spaces = userDefaultsToList "com.apple.spaces" cfg.spaces; + trackpad = userDefaultsToList "com.apple.AppleMultitouchTrackpad" cfg.trackpad; + trackpadBluetooth = userDefaultsToList "com.apple.driver.AppleBluetoothMultitouch.trackpad" cfg.trackpad; + universalaccess = userDefaultsToList "com.apple.universalaccess" cfg.universalaccess; + ActivityMonitor = userDefaultsToList "com.apple.ActivityMonitor" cfg.ActivityMonitor; + WindowManager = userDefaultsToList "com.apple.WindowManager" cfg.WindowManager; + controlcenter = userDefaultsToList "~${config.system.primaryUser}/Library/Preferences/ByHost/com.apple.controlcenter" cfg.controlcenter; + CustomUserPreferences = flatten (mapAttrsToList (name: value: userDefaultsToList name value) cfg.CustomUserPreferences); mkIfLists = list: mkIf (any (attrs: attrs != [ ]) list); @@ -57,6 +61,30 @@ in else types.float.check x; }; + system.requiresPrimaryUser = concatMap + (scope: mapAttrsToList + (name: value: mkIf (value != null) (showOption [ "system" "defaults" scope name ])) + (if scope == "dock" then dockFiltered else cfg.${scope})) + [ + "CustomUserPreferences" + ".GlobalPreferences" + "LaunchServices" + "NSGlobalDomain" + "menuExtraClock" + "dock" + "finder" + "hitoolbox" + "magicmouse" + "screencapture" + "screensaver" + "spaces" + "trackpad" + "universalaccess" + "ActivityMonitor" + "WindowManager" + "controlcenter" + ]; + system.activationScripts.defaults.text = mkIfLists [ alf loginwindow @@ -122,11 +150,8 @@ in ${concatStringsSep "\n" controlcenter} ${optionalString (length dock > 0) '' - # Only restart Dock if current user is logged in - if pgrep -xu $UID Dock >/dev/null; then - echo >&2 "restarting Dock..." - killall Dock || true - fi + echo >&2 "restarting Dock..." + killall -qu ${escapeShellArg config.system.primaryUser} Dock || true ''} ''; diff --git a/modules/system/etc.nix b/modules/system/etc.nix index bc60bef90..c9cae7c6d 100644 --- a/modules/system/etc.nix +++ b/modules/system/etc.nix @@ -39,7 +39,7 @@ in '') etc} ''; - system.activationScripts.etcChecks.text = '' + system.activationScripts.checks.text = mkAfter '' declare -A etcSha256Hashes=( ${concatMapStringsSep "\n " (attr: diff --git a/modules/system/launchd.nix b/modules/system/launchd.nix index c578dec34..787c7c284 100644 --- a/modules/system/launchd.nix +++ b/modules/system/launchd.nix @@ -11,8 +11,8 @@ let mkTextDerivation = pkgs.writeText; }; - launchdVariables = mapAttrsToList (name: value: '' - launchctl setenv ${name} '${value}' + launchdVariables = prefix: mapAttrsToList (name: value: '' + ${prefix} launchctl setenv ${name} '${value}' ''); launchdActivation = basedir: target: '' @@ -31,19 +31,21 @@ let fi ''; - userLaunchdActivation = target: '' - if ! diff ${cfg.build.launchd}/user/Library/LaunchAgents/${target} ~/Library/LaunchAgents/${target} &> /dev/null; then - if test -f ~/Library/LaunchAgents/${target}; then + userLaunchdActivation = target: let + user = lib.escapeShellArg config.system.primaryUser; + in '' + if ! diff ${cfg.build.launchd}/user/Library/LaunchAgents/${target} ~${user}/Library/LaunchAgents/${target} &> /dev/null; then + if test -f ~${user}/Library/LaunchAgents/${target}; then echo "reloading user service $(basename ${target} .plist)" >&2 - launchctl unload ~/Library/LaunchAgents/${target} || true + sudo --user=${user} -- launchctl unload ~${user}/Library/LaunchAgents/${target} || true else echo "creating user service $(basename ${target} .plist)" >&2 fi - if test -L ~/Library/LaunchAgents/${target}; then - rm ~/Library/LaunchAgents/${target} + if test -L ~${user}/Library/LaunchAgents/${target}; then + sudo --user=${user} -- rm ~${user}/Library/LaunchAgents/${target} fi - cp -f '${cfg.build.launchd}/user/Library/LaunchAgents/${target}' ~/Library/LaunchAgents/${target} - launchctl load -w ~/Library/LaunchAgents/${target} + sudo --user=${user} -- cp -f '${cfg.build.launchd}/user/Library/LaunchAgents/${target}' ~${user}/Library/LaunchAgents/${target} + sudo --user=${user} -- launchctl load -w ~${user}/Library/LaunchAgents/${target} fi ''; @@ -100,7 +102,7 @@ in # Set up launchd services in /Library/LaunchAgents and /Library/LaunchDaemons echo "setting up launchd services..." >&2 - ${concatStringsSep "\n" (launchdVariables config.launchd.envVariables)} + ${concatStringsSep "\n" (launchdVariables "" config.launchd.envVariables)} ${concatMapStringsSep "\n" (attr: launchdActivation "LaunchAgents" attr.target) launchAgents} ${concatMapStringsSep "\n" (attr: launchdActivation "LaunchDaemons" attr.target) launchDaemons} @@ -132,14 +134,16 @@ in done ''; - system.activationScripts.userLaunchd.text = '' + system.activationScripts.userLaunchd.text = let + user = lib.escapeShellArg config.system.primaryUser; + in mkIf (config.launchd.user.envVariables != { } || userLaunchAgents != [ ]) '' # Set up user launchd services in ~/Library/LaunchAgents echo "setting up user launchd services..." - ${concatStringsSep "\n" (launchdVariables config.launchd.user.envVariables)} + ${concatStringsSep "\n" (launchdVariables "sudo --user=${user} --" config.launchd.user.envVariables)} ${optionalString (builtins.length userLaunchAgents > 0) '' - mkdir -p ~/Library/LaunchAgents + sudo --user=${user} -- mkdir -p ~${user}/Library/LaunchAgents ''} ${concatMapStringsSep "\n" (attr: userLaunchdActivation attr.target) userLaunchAgents} @@ -149,9 +153,9 @@ in if [[ ! -e "${cfg.build.launchd}/user/Library/LaunchAgents/$f" ]]; then echo "removing user service $(basename "$f" .plist)" >&2 - launchctl unload ~/Library/LaunchAgents/"$f" || true - if [[ -e ~/Library/LaunchAgents/"$f" ]]; then - rm -f ~/Library/LaunchAgents/"$f" + sudo --user=${user} -- launchctl unload ~${user}/Library/LaunchAgents/"$f" || true + if [[ -e ~${user}/Library/LaunchAgents/"$f" ]]; then + sudo --user=${user} -- rm -f ~${user}/Library/LaunchAgents/"$f" fi fi done diff --git a/modules/system/primary-user.nix b/modules/system/primary-user.nix new file mode 100644 index 000000000..0944580b6 --- /dev/null +++ b/modules/system/primary-user.nix @@ -0,0 +1,67 @@ +{ + lib, + options, + config, + ... +}: + +{ + options = { + system.primaryUser = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + The user used for options that previously applied to the user + running `darwin-rebuild`. + + This is a transition mechanism as nix-darwin reorganizes its + options and will eventually be unnecessary and removed. + ''; + }; + + system.primaryUserHome = lib.mkOption { + internal = true; + type = lib.types.str; + default = + config.users.users.${config.system.primaryUser}.home or "/Users/${config.system.primaryUser}"; + }; + + system.requiresPrimaryUser = lib.mkOption { + internal = true; + type = lib.types.listOf lib.types.str; + default = [ ]; + }; + }; + + config = { + assertions = [ + { + assertion = config.system.primaryUser == null -> config.system.requiresPrimaryUser == [ ]; + message = '' + Previously, some nix-darwin options applied to the user running + `darwin-rebuild`. As part of a long‐term migration to make + nix-darwin focus on system‐wide activation and support first‐class + multi‐user setups, all system activation now runs as `root`, and + these options instead apply to the `system.primaryUser` user. + + You currently have the following primary‐user‐requiring options set: + + ${lib.concatMapStringsSep "\n" (name: "* `${name}`") ( + lib.sort (name1: name2: name1 < name2) config.system.requiresPrimaryUser + )} + + To continue using these options, set `system.primaryUser` to the name + of the user you have been using to run `darwin-rebuild`. In the long + run, this setting will be deprecated and removed after all the + functionality it is relevant for has been adjusted to allow + specifying the relevant user separately, moved under the + `users.users.*` namespace, or migrated to Home Manager. + + If you run into any unexpected issues with the migration, please + open an issue at + and include as much information as possible. + ''; + } + ]; + }; +} diff --git a/modules/users/default.nix b/modules/users/default.nix index bbfd0d163..1926983b6 100644 --- a/modules/users/default.nix +++ b/modules/users/default.nix @@ -105,6 +105,16 @@ in assertion = !builtins.elem "root" deletedUsers; message = "Remove `root` from `users.knownUsers` if you no longer want nix-darwin to manage it."; } + { + assertion = + config.system.primaryUser != null + -> !builtins.elem config.system.primaryUser deletedUsers; + message = '' + Refusing to delete the primary user. Remove + `${config.system.primaryUser}` from `users.knownUsers` if + you no longer want nix-darwin to manage it. + ''; + } ] ++ flatten (flip mapAttrsToList cfg.users (name: user: map (shell: { assertion = let @@ -140,13 +150,12 @@ in # NOTE: We put this in `system.checks` as we want this to run first to avoid partial activations # however currently that runs at user level activation as that runs before system level activation - # TODO: replace `$USER` with `$SUDO_USER` when system.checks runs from system level system.checks.text = mkIf (builtins.length (createdUsers ++ deletedUsers) > 0) (mkAfter '' ensurePerms() { homeDirectory=$(dscl . -read /Users/nobody NFSHomeDirectory) homeDirectory=''${homeDirectory#NFSHomeDirectory: } - if ! sudo dscl . -change /Users/nobody NFSHomeDirectory "$homeDirectory" "$homeDirectory" &> /dev/null; then + if ! dscl . -change /Users/nobody NFSHomeDirectory "$homeDirectory" "$homeDirectory" &> /dev/null; then if [[ "$(launchctl managername)" != Aqua ]]; then printf >&2 '\e[1;31merror: users cannot be %s over SSH without Full Disk Access, aborting activation\e[0m\n' "$2" printf >&2 'The user %s could not be %s as `darwin-rebuild` was not executed with Full Disk Access over SSH.\n' "$1" "$2" @@ -167,7 +176,7 @@ in # and we can reset it to ensure the user gets another prompt tccutil reset SystemPolicySysAdminFiles > /dev/null - if ! sudo dscl . -change /Users/nobody NFSHomeDirectory "$homeDirectory" "$homeDirectory" &> /dev/null; then + if ! dscl . -change /Users/nobody NFSHomeDirectory "$homeDirectory" "$homeDirectory" &> /dev/null; then printf >&2 '\e[1;31merror: permission denied when trying to %s user %s, aborting activation\e[0m\n' "$2" "$1" printf >&2 '`darwin-rebuild` requires permissions to administrate your computer,\n' printf >&2 'please accept the dialog that pops up.\n' @@ -218,12 +227,6 @@ in u=$(id -u ${name} 2> /dev/null) || true if [ -n "$u" ]; then if [ "$u" -gt 501 ]; then - # TODO: add `darwin.primaryUser` as well - if [[ ${name} == "$USER" ]]; then - printf >&2 '\e[1;31merror: refusing to delete the user calling `darwin-rebuild` (%s), aborting activation\e[0m\n', ${name} - exit 1 - fi - ensurePerms ${name} delete fi fi diff --git a/pkgs/darwin-uninstaller/configuration.nix b/pkgs/darwin-uninstaller/configuration.nix index ce6be6ca0..391f9a1e6 100644 --- a/pkgs/darwin-uninstaller/configuration.nix +++ b/pkgs/darwin-uninstaller/configuration.nix @@ -1,4 +1,4 @@ -{ lib, pkgs, ... }: +{ lib, config, pkgs, ... }: with lib; @@ -15,13 +15,17 @@ with lib; # Restore any unmanaged `nix-daemon`. nix.enable = false; - system.activationScripts.postUserActivation.text = mkAfter '' - nix-channel --remove darwin || true - ''; - system.activationScripts.postActivation.text = mkAfter '' nix-channel --remove darwin || true + ${lib.optionalString (config.system.primaryUser != null) '' + sudo \ + --user=${lib.escapeShellArg config.system.primaryUser} \ + --set-home \ + -- nix-channel --remove darwin \ + || true + ''} + if [[ -L /Applications/Nix\ Apps ]]; then rm /Applications/Nix\ Apps fi diff --git a/pkgs/darwin-uninstaller/default.nix b/pkgs/darwin-uninstaller/default.nix index 658991b15..2fc5cc6f9 100644 --- a/pkgs/darwin-uninstaller/default.nix +++ b/pkgs/darwin-uninstaller/default.nix @@ -53,16 +53,16 @@ in writeShellApplication { ${uninstallSystem.system}/sw/bin/darwin-rebuild activate if [[ -L /run/current-system ]]; then - sudo rm /run/current-system + rm /run/current-system fi if [[ -L /run ]]; then if [[ -e /etc/synthetic.conf ]]; then - sudo sed -i -E '/^run[[:space:]]/d' /etc/synthetic.conf - sudo /System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util -t &>/dev/null || true + sed -i -E '/^run[[:space:]]/d' /etc/synthetic.conf + /System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util -t &>/dev/null || true echo >&2 "NOTE: the /run symlink will be removed on reboot" else - sudo rm /run + rm /run fi fi diff --git a/pkgs/nix-tools/darwin-rebuild.sh b/pkgs/nix-tools/darwin-rebuild.sh index 8f207a7a3..8824e6155 100644 --- a/pkgs/nix-tools/darwin-rebuild.sh +++ b/pkgs/nix-tools/darwin-rebuild.sh @@ -2,6 +2,12 @@ set -e set -o pipefail +if [[ $(id -u) -eq 0 ]]; then + # On macOS, `sudo(8)` preserves `$HOME` by default, which causes Nix + # to output warnings. + HOME=~root +fi + export PATH=@path@ export NIX_PATH=${NIX_PATH:-@nixPath@} @@ -22,12 +28,6 @@ showSyntax() { exit 1 } -sudo() { - # We use `env` before our command to ensure the preserved PATH gets checked - # when trying to resolve the command to execute - command sudo -H --preserve-env=PATH --preserve-env=SSH_CONNECTION env "$@" -} - # Parse the command line. origArgs=("$@") extraMetadataFlags=() @@ -142,6 +142,11 @@ done if [ -z "$action" ]; then showSyntax; fi +if [[ $action =~ ^switch|activate|rollback|check$ && $(id -u) -ne 0 ]]; then + printf >&2 '%s: system activation must now be run as root\n' "$0" + exit 1 +fi + flakeFlags=(--extra-experimental-features 'nix-command flakes') # Use /etc/nix-darwin/flake.nix if it exists. It can be a symlink to the @@ -193,11 +198,7 @@ if [ "$action" = switch ] || [ "$action" = build ] || [ "$action" = check ] || [ fi if [ "$action" = list ] || [ "$action" = rollback ]; then - if [ "$USER" != root ] && [ ! -w $(dirname "$profile") ]; then - sudo nix-env -p "$profile" "${extraProfileFlags[@]}" - else - nix-env -p "$profile" "${extraProfileFlags[@]}" - fi + nix-env -p "$profile" "${extraProfileFlags[@]}" fi if [ "$action" = rollback ]; then @@ -210,22 +211,37 @@ fi if [ -z "$systemConfig" ]; then exit 0; fi -if [ "$action" = switch ]; then - if [ "$USER" != root ] && [ ! -w $(dirname "$profile") ]; then - sudo nix-env -p "$profile" --set "$systemConfig" +# TODO: Remove this backwards‐compatibility hack in 25.11. + +if + [[ -x $systemConfig/activate-user ]] \ + && ! grep -q '^# nix-darwin: deprecated$' "$systemConfig/activate-user" +then + hasActivateUser=1 +else + hasActivateUser= +fi + +runActivateUser() { + if [[ -n $SUDO_USER ]]; then + sudo --user="$SUDO_USER" --set-home -- "$systemConfig/activate-user" else - nix-env -p "$profile" --set "$systemConfig" + printf >&2 \ + '%s: $SUDO_USER not set, can’t run legacy `activate-user` script\n' \ + "$0" + exit 1 fi +} + +if [ "$action" = switch ]; then + nix-env -p "$profile" --set "$systemConfig" fi if [ "$action" = switch ] || [ "$action" = activate ] || [ "$action" = rollback ]; then - "$systemConfig/activate-user" - - if [ "$USER" != root ]; then - sudo "$systemConfig/activate" - else - "$systemConfig/activate" + if [[ -n $hasActivateUser ]]; then + runActivateUser fi + "$systemConfig/activate" fi if [ "$action" = changelog ]; then @@ -234,5 +250,9 @@ fi if [ "$action" = check ]; then export checkActivation=1 - "$systemConfig/activate-user" + if [[ -n $hasActivateUser ]]; then + runActivateUser + else + "$systemConfig/activate" + fi fi diff --git a/pkgs/nix-tools/default.nix b/pkgs/nix-tools/default.nix index 8d6b89b7a..a5414cbdb 100644 --- a/pkgs/nix-tools/default.nix +++ b/pkgs/nix-tools/default.nix @@ -21,6 +21,7 @@ "/usr/sbin" "/sbin" ] +, nixPackage ? null , # This should be kept in sync with the default `nix.nixPath`. nixPath ? lib.concatStringsSep ":" [ "darwin-config=/etc/nix-darwin/configuration.nix" @@ -29,7 +30,7 @@ }: let - extraPath = lib.makeBinPath [ coreutils jq git ]; + extraPath = lib.makeBinPath [ coreutils jq git nixPackage ]; writeProgram = name: env: src: substituteAll ({ diff --git a/tests/activation-scripts.nix b/tests/activation-scripts.nix index e7d08569d..70c4245dd 100644 --- a/tests/activation-scripts.nix +++ b/tests/activation-scripts.nix @@ -1,10 +1,6 @@ { config, pkgs, ... }: { - system.activationScripts.preUserActivation.text = "echo hook preUserActivation"; - system.activationScripts.extraUserActivation.text = "echo hook extraUserActivation"; - system.activationScripts.postUserActivation.text = "echo hook postUserActivation"; - system.activationScripts.preActivation.text = "echo hook preActivation"; system.activationScripts.extraActivation.text = "echo hook extraActivation"; system.activationScripts.postActivation.text = "echo hook postActivation"; @@ -14,11 +10,6 @@ awk '/echo hook / {i++ ; print i " => " $0}' "$2" | grep "$1" } - echo checking activation hooks in /activate-user >&2 - countHooks "1 => echo hook preUserActivation" ${config.out}/activate-user - countHooks "2 => echo hook extraUserActivation" ${config.out}/activate-user - countHooks "3 => echo hook postUserActivation" ${config.out}/activate-user - echo checking activation hooks in /activate >&2 countHooks "1 => echo hook preActivation" ${config.out}/activate countHooks "2 => echo hook extraActivation" ${config.out}/activate diff --git a/tests/fixtures/system-defaults-write/activate.txt b/tests/fixtures/system-defaults-write/system.txt similarity index 100% rename from tests/fixtures/system-defaults-write/activate.txt rename to tests/fixtures/system-defaults-write/system.txt diff --git a/tests/fixtures/system-defaults-write/activate-user.txt b/tests/fixtures/system-defaults-write/user.txt similarity index 57% rename from tests/fixtures/system-defaults-write/activate-user.txt rename to tests/fixtures/system-defaults-write/user.txt index d93321ef5..1cc99da06 100644 --- a/tests/fixtures/system-defaults-write/activate-user.txt +++ b/tests/fixtures/system-defaults-write/user.txt @@ -1,251 +1,251 @@ -defaults write -g 'AppleEnableMouseSwipeNavigateWithScrolls' $' +sudo --user=test-defaults-user -- defaults write -g 'AppleEnableMouseSwipeNavigateWithScrolls' $' ' -defaults write -g 'AppleEnableSwipeNavigateWithScrolls' $' +sudo --user=test-defaults-user -- defaults write -g 'AppleEnableSwipeNavigateWithScrolls' $' ' -defaults write -g 'AppleFontSmoothing' $' +sudo --user=test-defaults-user -- defaults write -g 'AppleFontSmoothing' $' 1 ' -defaults write -g 'AppleICUForce24HourTime' $' +sudo --user=test-defaults-user -- defaults write -g 'AppleICUForce24HourTime' $' ' -defaults write -g 'AppleKeyboardUIMode' $' +sudo --user=test-defaults-user -- defaults write -g 'AppleKeyboardUIMode' $' 3 ' -defaults write -g 'ApplePressAndHoldEnabled' $' +sudo --user=test-defaults-user -- defaults write -g 'ApplePressAndHoldEnabled' $' ' -defaults write -g 'AppleScrollerPagingBehavior' $' +sudo --user=test-defaults-user -- defaults write -g 'AppleScrollerPagingBehavior' $' ' -defaults write -g 'AppleShowAllExtensions' $' +sudo --user=test-defaults-user -- defaults write -g 'AppleShowAllExtensions' $' ' -defaults write -g 'AppleShowAllFiles' $' +sudo --user=test-defaults-user -- defaults write -g 'AppleShowAllFiles' $' ' -defaults write -g 'AppleShowScrollBars' $' +sudo --user=test-defaults-user -- defaults write -g 'AppleShowScrollBars' $' Always ' -defaults write -g 'AppleSpacesSwitchOnActivate' $' +sudo --user=test-defaults-user -- defaults write -g 'AppleSpacesSwitchOnActivate' $' ' -defaults write -g 'AppleWindowTabbingMode' $' +sudo --user=test-defaults-user -- defaults write -g 'AppleWindowTabbingMode' $' always ' -defaults write -g 'InitialKeyRepeat' $' +sudo --user=test-defaults-user -- defaults write -g 'InitialKeyRepeat' $' 10 ' -defaults write -g 'KeyRepeat' $' +sudo --user=test-defaults-user -- defaults write -g 'KeyRepeat' $' 1 ' -defaults write -g 'NSAutomaticCapitalizationEnabled' $' +sudo --user=test-defaults-user -- defaults write -g 'NSAutomaticCapitalizationEnabled' $' ' -defaults write -g 'NSAutomaticDashSubstitutionEnabled' $' +sudo --user=test-defaults-user -- defaults write -g 'NSAutomaticDashSubstitutionEnabled' $' ' -defaults write -g 'NSAutomaticInlinePredictionEnabled' $' +sudo --user=test-defaults-user -- defaults write -g 'NSAutomaticInlinePredictionEnabled' $' ' -defaults write -g 'NSAutomaticPeriodSubstitutionEnabled' $' +sudo --user=test-defaults-user -- defaults write -g 'NSAutomaticPeriodSubstitutionEnabled' $' ' -defaults write -g 'NSAutomaticQuoteSubstitutionEnabled' $' +sudo --user=test-defaults-user -- defaults write -g 'NSAutomaticQuoteSubstitutionEnabled' $' ' -defaults write -g 'NSAutomaticSpellingCorrectionEnabled' $' +sudo --user=test-defaults-user -- defaults write -g 'NSAutomaticSpellingCorrectionEnabled' $' ' -defaults write -g 'NSAutomaticWindowAnimationsEnabled' $' +sudo --user=test-defaults-user -- defaults write -g 'NSAutomaticWindowAnimationsEnabled' $' ' -defaults write -g 'NSDisableAutomaticTermination' $' +sudo --user=test-defaults-user -- defaults write -g 'NSDisableAutomaticTermination' $' ' -defaults write -g 'NSDocumentSaveNewDocumentsToCloud' $' +sudo --user=test-defaults-user -- defaults write -g 'NSDocumentSaveNewDocumentsToCloud' $' ' -defaults write -g 'NSNavPanelExpandedStateForSaveMode' $' +sudo --user=test-defaults-user -- defaults write -g 'NSNavPanelExpandedStateForSaveMode' $' ' -defaults write -g 'NSNavPanelExpandedStateForSaveMode2' $' +sudo --user=test-defaults-user -- defaults write -g 'NSNavPanelExpandedStateForSaveMode2' $' ' -defaults write -g 'NSScrollAnimationEnabled' $' +sudo --user=test-defaults-user -- defaults write -g 'NSScrollAnimationEnabled' $' ' -defaults write -g 'NSTableViewDefaultSizeMode' $' +sudo --user=test-defaults-user -- defaults write -g 'NSTableViewDefaultSizeMode' $' 2 ' -defaults write -g 'NSTextShowsControlCharacters' $' +sudo --user=test-defaults-user -- defaults write -g 'NSTextShowsControlCharacters' $' ' -defaults write -g 'NSUseAnimatedFocusRing' $' +sudo --user=test-defaults-user -- defaults write -g 'NSUseAnimatedFocusRing' $' ' -defaults write -g 'NSWindowResizeTime' $' +sudo --user=test-defaults-user -- defaults write -g 'NSWindowResizeTime' $' 0.010000 ' -defaults write -g 'NSWindowShouldDragOnGesture' $' +sudo --user=test-defaults-user -- defaults write -g 'NSWindowShouldDragOnGesture' $' ' -defaults write -g 'PMPrintingExpandedStateForPrint' $' +sudo --user=test-defaults-user -- defaults write -g 'PMPrintingExpandedStateForPrint' $' ' -defaults write -g 'PMPrintingExpandedStateForPrint2' $' +sudo --user=test-defaults-user -- defaults write -g 'PMPrintingExpandedStateForPrint2' $' ' -defaults write -g 'com.apple.keyboard.fnState' $' +sudo --user=test-defaults-user -- defaults write -g 'com.apple.keyboard.fnState' $' ' -defaults write -g 'com.apple.mouse.tapBehavior' $' +sudo --user=test-defaults-user -- defaults write -g 'com.apple.mouse.tapBehavior' $' 1 ' -defaults write -g 'com.apple.springing.delay' $' +sudo --user=test-defaults-user -- defaults write -g 'com.apple.springing.delay' $' 0.000000 ' -defaults write -g 'com.apple.springing.enabled' $' +sudo --user=test-defaults-user -- defaults write -g 'com.apple.springing.enabled' $' ' -defaults write -g 'com.apple.swipescrolldirection' $' +sudo --user=test-defaults-user -- defaults write -g 'com.apple.swipescrolldirection' $' ' -defaults write -g 'com.apple.trackpad.enableSecondaryClick' $' +sudo --user=test-defaults-user -- defaults write -g 'com.apple.trackpad.enableSecondaryClick' $' ' -defaults write -g 'com.apple.trackpad.trackpadCornerClickBehavior' $' +sudo --user=test-defaults-user -- defaults write -g 'com.apple.trackpad.trackpadCornerClickBehavior' $' 1 ' -defaults write .GlobalPreferences 'com.apple.sound.beep.sound' $' +sudo --user=test-defaults-user -- defaults write .GlobalPreferences 'com.apple.sound.beep.sound' $' /System/Library/Sounds/Funk.aiff ' -defaults write com.apple.menuextra.clock 'FlashDateSeparators' $' +sudo --user=test-defaults-user -- defaults write com.apple.menuextra.clock 'FlashDateSeparators' $' ' -defaults write com.apple.menuextra.clock 'Show24Hour' $' +sudo --user=test-defaults-user -- defaults write com.apple.menuextra.clock 'Show24Hour' $' ' -defaults write com.apple.menuextra.clock 'ShowDate' $' +sudo --user=test-defaults-user -- defaults write com.apple.menuextra.clock 'ShowDate' $' 2 ' -defaults write com.apple.menuextra.clock 'ShowDayOfWeek' $' +sudo --user=test-defaults-user -- defaults write com.apple.menuextra.clock 'ShowDayOfWeek' $' ' -defaults write com.apple.dock 'appswitcher-all-displays' $' +sudo --user=test-defaults-user -- defaults write com.apple.dock 'appswitcher-all-displays' $' ' -defaults write com.apple.dock 'autohide-delay' $' +sudo --user=test-defaults-user -- defaults write com.apple.dock 'autohide-delay' $' 0.240000 ' -defaults write com.apple.dock 'expose-group-apps' $' +sudo --user=test-defaults-user -- defaults write com.apple.dock 'expose-group-apps' $' ' -defaults write com.apple.dock 'orientation' $' +sudo --user=test-defaults-user -- defaults write com.apple.dock 'orientation' $' left ' -defaults write com.apple.dock 'persistent-apps' $' +sudo --user=test-defaults-user -- defaults write com.apple.dock 'persistent-apps' $' @@ -319,7 +319,7 @@ defaults write com.apple.dock 'persistent-apps' $' ' -defaults write com.apple.dock 'persistent-others' $' +sudo --user=test-defaults-user -- defaults write com.apple.dock 'persistent-others' $' @@ -353,134 +353,134 @@ defaults write com.apple.dock 'persistent-others' $' ' -defaults write com.apple.dock 'scroll-to-open' $' +sudo --user=test-defaults-user -- defaults write com.apple.dock 'scroll-to-open' $' ' -defaults write com.apple.finder 'AppleShowAllExtensions' $' +sudo --user=test-defaults-user -- defaults write com.apple.finder 'AppleShowAllExtensions' $' ' -defaults write com.apple.finder 'AppleShowAllFiles' $' +sudo --user=test-defaults-user -- defaults write com.apple.finder 'AppleShowAllFiles' $' ' -defaults write com.apple.finder 'CreateDesktop' $' +sudo --user=test-defaults-user -- defaults write com.apple.finder 'CreateDesktop' $' ' -defaults write com.apple.finder 'FXDefaultSearchScope' $' +sudo --user=test-defaults-user -- defaults write com.apple.finder 'FXDefaultSearchScope' $' SCcf ' -defaults write com.apple.finder 'FXEnableExtensionChangeWarning' $' +sudo --user=test-defaults-user -- defaults write com.apple.finder 'FXEnableExtensionChangeWarning' $' ' -defaults write com.apple.finder 'FXPreferredViewStyle' $' +sudo --user=test-defaults-user -- defaults write com.apple.finder 'FXPreferredViewStyle' $' Flwv ' -defaults write com.apple.finder 'FXRemoveOldTrashItems' $' +sudo --user=test-defaults-user -- defaults write com.apple.finder 'FXRemoveOldTrashItems' $' ' -defaults write com.apple.finder 'NewWindowTarget' $' +sudo --user=test-defaults-user -- defaults write com.apple.finder 'NewWindowTarget' $' PfLo ' -defaults write com.apple.finder 'NewWindowTargetPath' $' +sudo --user=test-defaults-user -- defaults write com.apple.finder 'NewWindowTargetPath' $' file:///Library/Apple ' -defaults write com.apple.finder 'QuitMenuItem' $' +sudo --user=test-defaults-user -- defaults write com.apple.finder 'QuitMenuItem' $' ' -defaults write com.apple.finder 'ShowExternalHardDrivesOnDesktop' $' +sudo --user=test-defaults-user -- defaults write com.apple.finder 'ShowExternalHardDrivesOnDesktop' $' ' -defaults write com.apple.finder 'ShowHardDrivesOnDesktop' $' +sudo --user=test-defaults-user -- defaults write com.apple.finder 'ShowHardDrivesOnDesktop' $' ' -defaults write com.apple.finder 'ShowMountedServersOnDesktop' $' +sudo --user=test-defaults-user -- defaults write com.apple.finder 'ShowMountedServersOnDesktop' $' ' -defaults write com.apple.finder 'ShowPathbar' $' +sudo --user=test-defaults-user -- defaults write com.apple.finder 'ShowPathbar' $' ' -defaults write com.apple.finder 'ShowRemovableMediaOnDesktop' $' +sudo --user=test-defaults-user -- defaults write com.apple.finder 'ShowRemovableMediaOnDesktop' $' ' -defaults write com.apple.finder 'ShowStatusBar' $' +sudo --user=test-defaults-user -- defaults write com.apple.finder 'ShowStatusBar' $' ' -defaults write com.apple.finder '_FXShowPosixPathInTitle' $' +sudo --user=test-defaults-user -- defaults write com.apple.finder '_FXShowPosixPathInTitle' $' ' -defaults write com.apple.finder '_FXSortFoldersFirst' $' +sudo --user=test-defaults-user -- defaults write com.apple.finder '_FXSortFoldersFirst' $' ' -defaults write com.apple.finder '_FXSortFoldersFirstOnDesktop' $' +sudo --user=test-defaults-user -- defaults write com.apple.finder '_FXSortFoldersFirstOnDesktop' $' ' -defaults write com.apple.HIToolbox 'AppleFnUsageType' $' +sudo --user=test-defaults-user -- defaults write com.apple.HIToolbox 'AppleFnUsageType' $' 2 ' -defaults write com.apple.screencapture 'include-date' $' +sudo --user=test-defaults-user -- defaults write com.apple.screencapture 'include-date' $' ' -defaults write com.apple.screencapture 'location' $' +sudo --user=test-defaults-user -- defaults write com.apple.screencapture 'location' $' /tmp ' -defaults write com.apple.screencapture 'target' $' +sudo --user=test-defaults-user -- defaults write com.apple.screencapture 'target' $' file ' -defaults write com.apple.screensaver 'askForPassword' $' +sudo --user=test-defaults-user -- defaults write com.apple.screensaver 'askForPassword' $' ' -defaults write com.apple.screensaver 'askForPasswordDelay' $' +sudo --user=test-defaults-user -- defaults write com.apple.screensaver 'askForPasswordDelay' $' 5 @@ -488,62 +488,62 @@ defaults write com.apple.screensaver 'askForPasswordDelay' $' +sudo --user=test-defaults-user -- defaults write com.apple.universalaccess 'closeViewScrollWheelToggle' $' ' -defaults write com.apple.universalaccess 'closeViewZoomFollowsFocus' $' +sudo --user=test-defaults-user -- defaults write com.apple.universalaccess 'closeViewZoomFollowsFocus' $' ' -defaults write com.apple.universalaccess 'mouseDriverCursorSize' $' +sudo --user=test-defaults-user -- defaults write com.apple.universalaccess 'mouseDriverCursorSize' $' 1.500000 ' -defaults write com.apple.universalaccess 'reduceMotion' $' +sudo --user=test-defaults-user -- defaults write com.apple.universalaccess 'reduceMotion' $' ' -defaults write com.apple.universalaccess 'reduceTransparency' $' +sudo --user=test-defaults-user -- defaults write com.apple.universalaccess 'reduceTransparency' $' ' -defaults write com.apple.ActivityMonitor 'IconType' $' +sudo --user=test-defaults-user -- defaults write com.apple.ActivityMonitor 'IconType' $' 3 ' -defaults write com.apple.ActivityMonitor 'OpenMainWindow' $' +sudo --user=test-defaults-user -- defaults write com.apple.ActivityMonitor 'OpenMainWindow' $' ' -defaults write com.apple.ActivityMonitor 'ShowCategory' $' +sudo --user=test-defaults-user -- defaults write com.apple.ActivityMonitor 'ShowCategory' $' 103 ' -defaults write com.apple.ActivityMonitor 'SortColumn' $' +sudo --user=test-defaults-user -- defaults write com.apple.ActivityMonitor 'SortColumn' $' CPUUsage ' -defaults write com.apple.ActivityMonitor 'SortDirection' $' +sudo --user=test-defaults-user -- defaults write com.apple.ActivityMonitor 'SortDirection' $' 0 ' -defaults write NSGlobalDomain 'TISRomanSwitchState' $' +sudo --user=test-defaults-user -- defaults write NSGlobalDomain 'TISRomanSwitchState' $' 1 ' -defaults write com.apple.Safari 'NSUserKeyEquivalents' $' +sudo --user=test-defaults-user -- defaults write com.apple.Safari 'NSUserKeyEquivalents' $' @@ -551,102 +551,102 @@ defaults write com.apple.Safari 'NSUserKeyEquivalents' $'@^q ' -defaults write com.apple.Safari 'com.apple.Safari.ContentPageGroupIdentifier.WebKit2DeveloperExtrasEnabled' $' +sudo --user=test-defaults-user -- defaults write com.apple.Safari 'com.apple.Safari.ContentPageGroupIdentifier.WebKit2DeveloperExtrasEnabled' $' ' -defaults write com.apple.WindowManager 'AppWindowGroupingBehavior' $' +sudo --user=test-defaults-user -- defaults write com.apple.WindowManager 'AppWindowGroupingBehavior' $' ' -defaults write com.apple.WindowManager 'AutoHide' $' +sudo --user=test-defaults-user -- defaults write com.apple.WindowManager 'AutoHide' $' ' -defaults write com.apple.WindowManager 'EnableStandardClickToShowDesktop' $' +sudo --user=test-defaults-user -- defaults write com.apple.WindowManager 'EnableStandardClickToShowDesktop' $' ' -defaults write com.apple.WindowManager 'EnableTiledWindowMargins' $' +sudo --user=test-defaults-user -- defaults write com.apple.WindowManager 'EnableTiledWindowMargins' $' ' -defaults write com.apple.WindowManager 'EnableTilingByEdgeDrag' $' +sudo --user=test-defaults-user -- defaults write com.apple.WindowManager 'EnableTilingByEdgeDrag' $' ' -defaults write com.apple.WindowManager 'EnableTilingOptionAccelerator' $' +sudo --user=test-defaults-user -- defaults write com.apple.WindowManager 'EnableTilingOptionAccelerator' $' ' -defaults write com.apple.WindowManager 'EnableTopTilingByEdgeDrag' $' +sudo --user=test-defaults-user -- defaults write com.apple.WindowManager 'EnableTopTilingByEdgeDrag' $' ' -defaults write com.apple.WindowManager 'GloballyEnabled' $' +sudo --user=test-defaults-user -- defaults write com.apple.WindowManager 'GloballyEnabled' $' ' -defaults write com.apple.WindowManager 'HideDesktop' $' +sudo --user=test-defaults-user -- defaults write com.apple.WindowManager 'HideDesktop' $' ' -defaults write com.apple.WindowManager 'StageManagerHideWidgets' $' +sudo --user=test-defaults-user -- defaults write com.apple.WindowManager 'StageManagerHideWidgets' $' ' -defaults write com.apple.WindowManager 'StandardHideDesktopIcons' $' +sudo --user=test-defaults-user -- defaults write com.apple.WindowManager 'StandardHideDesktopIcons' $' ' -defaults write com.apple.WindowManager 'StandardHideWidgets' $' +sudo --user=test-defaults-user -- defaults write com.apple.WindowManager 'StandardHideWidgets' $' ' -defaults write ~/Library/Preferences/ByHost/com.apple.controlcenter 'AirDrop' $' +sudo --user=test-defaults-user -- defaults write ~test-defaults-user/Library/Preferences/ByHost/com.apple.controlcenter 'AirDrop' $' 18 ' -defaults write ~/Library/Preferences/ByHost/com.apple.controlcenter 'BatteryShowPercentage' $' +sudo --user=test-defaults-user -- defaults write ~test-defaults-user/Library/Preferences/ByHost/com.apple.controlcenter 'BatteryShowPercentage' $' ' -defaults write ~/Library/Preferences/ByHost/com.apple.controlcenter 'Bluetooth' $' +sudo --user=test-defaults-user -- defaults write ~test-defaults-user/Library/Preferences/ByHost/com.apple.controlcenter 'Bluetooth' $' 18 ' -defaults write ~/Library/Preferences/ByHost/com.apple.controlcenter 'Display' $' +sudo --user=test-defaults-user -- defaults write ~test-defaults-user/Library/Preferences/ByHost/com.apple.controlcenter 'Display' $' 24 ' -defaults write ~/Library/Preferences/ByHost/com.apple.controlcenter 'FocusModes' $' +sudo --user=test-defaults-user -- defaults write ~test-defaults-user/Library/Preferences/ByHost/com.apple.controlcenter 'FocusModes' $' 24 ' -defaults write ~/Library/Preferences/ByHost/com.apple.controlcenter 'NowPlaying' $' +sudo --user=test-defaults-user -- defaults write ~test-defaults-user/Library/Preferences/ByHost/com.apple.controlcenter 'NowPlaying' $' 18 ' -defaults write ~/Library/Preferences/ByHost/com.apple.controlcenter 'Sound' $' +sudo --user=test-defaults-user -- defaults write ~test-defaults-user/Library/Preferences/ByHost/com.apple.controlcenter 'Sound' $' 24 diff --git a/tests/homebrew.nix b/tests/homebrew.nix index d7fdeabc7..65ece0256 100644 --- a/tests/homebrew.nix +++ b/tests/homebrew.nix @@ -15,6 +15,8 @@ in { homebrew.enable = true; + homebrew.user = "test-homebrew-user"; + # Examples taken from https://github.com/Homebrew/homebrew-bundle homebrew.taps = [ "homebrew/cask" diff --git a/tests/launchd-daemons.nix b/tests/launchd-daemons.nix index 59e35aa68..6fe0d0f4a 100644 --- a/tests/launchd-daemons.nix +++ b/tests/launchd-daemons.nix @@ -1,6 +1,8 @@ { config, pkgs, ... }: { + system.primaryUser = "test-launchd-user"; + launchd.daemons.foo.command = "foo"; launchd.agents.bar.command = "bar"; launchd.user.agents.baz.command = "baz"; @@ -9,9 +11,9 @@ echo "checking launchd load in /activate" >&2 grep "launchctl load .* '/Library/LaunchDaemons/org.nixos.foo.plist" ${config.out}/activate grep "launchctl load .* '/Library/LaunchAgents/org.nixos.bar.plist" ${config.out}/activate - echo "checking launchd load in /activate-user" >&2 - grep "launchctl load .* ~/Library/LaunchAgents/org.nixos.baz.plist" ${config.out}/activate-user - echo "checking LaunchAgents creation /activate-user" >&2 - grep "mkdir -p ~/Library/LaunchAgents" ${config.out}/activate-user + echo "checking launchd user agent load in /activate" >&2 + grep "sudo --user=test-launchd-user -- launchctl load .* ~test-launchd-user/Library/LaunchAgents/org.nixos.baz.plist" ${config.out}/activate + echo "checking LaunchAgents creation /activate" >&2 + grep "sudo --user=test-launchd-user -- mkdir -p ~test-launchd-user/Library/LaunchAgents" ${config.out}/activate ''; } diff --git a/tests/networking-hostname.nix b/tests/networking-hostname.nix index 9e8c6fddc..7875178c3 100644 --- a/tests/networking-hostname.nix +++ b/tests/networking-hostname.nix @@ -9,6 +9,5 @@ grep "scutil --set ComputerName 'EVE’s MacBook Pro'" ${config.out}/activate grep "scutil --set LocalHostName ${lib.escapeShellArg "EVE"}" ${config.out}/activate grep "scutil --set HostName ${lib.escapeShellArg "EVE"}" ${config.out}/activate - echo checking defaults write in ${config.out}/activate-user >&2 ''; } diff --git a/tests/networking-shell-escape.nix b/tests/networking-shell-escape.nix index da399b18f..8cd2a4570 100644 --- a/tests/networking-shell-escape.nix +++ b/tests/networking-shell-escape.nix @@ -10,6 +10,5 @@ grep "scutil --set ComputerName '"\""Quotey McQuote's Macbook Pro"\""'" ${config.out}/activate grep "scutil --set LocalHostName '"\""Quotey-McQuote's-Macbook-Pro"\""'" ${config.out}/activate grep "scutil --set HostName "'"\""Quotey-McQuote's-Macbook-Pro"\""'" ${config.out}/activate - echo checking defaults write in ${config.out}/activate-user >&2 ''; } diff --git a/tests/nix-enable.nix b/tests/nix-enable.nix index e052aa2f4..bf84d556d 100644 --- a/tests/nix-enable.nix +++ b/tests/nix-enable.nix @@ -12,5 +12,9 @@ printf >&2 'checking for late‐bound Nix lookup in /activate\n' grep nixEnvPath= ${config.out}/activate + + printf >&2 'checking for late‐bound Nix lookup in activation service\n' + script=$(cat ${config.out}/Library/LaunchDaemons/org.nixos.activate-system.plist | awk -F'[< ]' '$6 ~ "^/nix/store/.*" {print $6}') + grep nixEnvPath= "$script" ''; } diff --git a/tests/services-aerospace.nix b/tests/services-aerospace.nix index 088c92d95..87f7b6c44 100644 --- a/tests/services-aerospace.nix +++ b/tests/services-aerospace.nix @@ -5,6 +5,8 @@ let in { + system.primaryUser = "test-aerospace-user"; + services.aerospace.enable = true; services.aerospace.package = aerospace; services.aerospace.settings = { diff --git a/tests/services-jankyborders.nix b/tests/services-jankyborders.nix index 5bde078e0..7718c0d8a 100644 --- a/tests/services-jankyborders.nix +++ b/tests/services-jankyborders.nix @@ -7,6 +7,8 @@ let in { + system.primaryUser = "test-jankyborders-user"; + services.jankyborders.enable = true; services.jankyborders.package = jankyborders; services.jankyborders.width = 5.0; diff --git a/tests/services-lorri.nix b/tests/services-lorri.nix index 7d3015240..c7935e10a 100644 --- a/tests/services-lorri.nix +++ b/tests/services-lorri.nix @@ -16,6 +16,8 @@ let expectedNixPath = "${"nixpkgs=" + toString pkgs.path}"; in { + system.primaryUser = "test-lorri-user"; + services.lorri.enable = true; test = '' PATH=${ diff --git a/tests/services-offlineimap.nix b/tests/services-offlineimap.nix index a88e186f9..aa41c010b 100644 --- a/tests/services-offlineimap.nix +++ b/tests/services-offlineimap.nix @@ -7,6 +7,8 @@ let in { + system.primaryUser = "test-offlineimap-user"; + services.offlineimap.enable = true; services.offlineimap.package = offlineimap; services.offlineimap.runQuick = true; diff --git a/tests/services-privoxy.nix b/tests/services-privoxy.nix index f6c16a423..76e3646bd 100644 --- a/tests/services-privoxy.nix +++ b/tests/services-privoxy.nix @@ -7,6 +7,8 @@ let in { + system.primaryUser = "test-privoxy-user"; + services.privoxy.enable = true; services.privoxy.package = privoxy; services.privoxy.config = "forward / ."; diff --git a/tests/services-redis.nix b/tests/services-redis.nix index a46916b70..ab1e1ee58 100644 --- a/tests/services-redis.nix +++ b/tests/services-redis.nix @@ -7,6 +7,8 @@ let in { + system.primaryUser = "test-redis-user"; + services.redis.enable = true; services.redis.package = redis; services.redis.extraConfig = '' diff --git a/tests/services-skhd.nix b/tests/services-skhd.nix index 427894020..4851c8c23 100644 --- a/tests/services-skhd.nix +++ b/tests/services-skhd.nix @@ -7,6 +7,8 @@ let in { + system.primaryUser = "test-skhd-user"; + services.skhd.enable = true; services.skhd.package = skhd; services.skhd.skhdConfig = "alt + shift - r : chunkc quit"; diff --git a/tests/services-spacebar.nix b/tests/services-spacebar.nix index 79257fafa..96a7f7a80 100644 --- a/tests/services-spacebar.nix +++ b/tests/services-spacebar.nix @@ -7,6 +7,8 @@ let in { + system.primaryUser = "test-spacebar-user"; + services.spacebar.enable = true; services.spacebar.package = spacebar; services.spacebar.config = { background_color = "0xff202020"; }; diff --git a/tests/services-spotifyd.nix b/tests/services-spotifyd.nix index 956e6a995..651d65c1b 100644 --- a/tests/services-spotifyd.nix +++ b/tests/services-spotifyd.nix @@ -7,6 +7,8 @@ let in { + system.primaryUser = "test-spotify-user"; + services.spotifyd.enable = true; services.spotifyd.package = spotifyd; diff --git a/tests/services-synapse-bt.nix b/tests/services-synapse-bt.nix index 7d50dafb6..2f024c2f1 100644 --- a/tests/services-synapse-bt.nix +++ b/tests/services-synapse-bt.nix @@ -7,6 +7,8 @@ let in { + system.primaryUser = "test-synapse-bt-user"; + services.synapse-bt.enable = true; services.synapse-bt.package = synapse-bt; diff --git a/tests/services-synergy.nix b/tests/services-synergy.nix index 9d3d6f14b..a8c222f4b 100644 --- a/tests/services-synergy.nix +++ b/tests/services-synergy.nix @@ -7,6 +7,8 @@ let in { + system.primaryUser = "test-synergy-user"; + services.synergy.package = synergy; services.synergy.client.enable = true; diff --git a/tests/services-yabai.nix b/tests/services-yabai.nix index 48f369c43..9bdaadf57 100644 --- a/tests/services-yabai.nix +++ b/tests/services-yabai.nix @@ -7,6 +7,8 @@ let in { + system.primaryUser = "test-yabai-user"; + services.yabai.enable = true; services.yabai.package = yabai; services.yabai.config = { focus_follows_mouse = "autoraise"; }; diff --git a/tests/system-defaults-write.nix b/tests/system-defaults-write.nix index 35ff85322..b4b5b7b2d 100644 --- a/tests/system-defaults-write.nix +++ b/tests/system-defaults-write.nix @@ -1,6 +1,8 @@ { config, pkgs, lib, ... }: { + system.primaryUser = "test-defaults-user"; + imports = [ { system.defaults.CustomUserPreferences = { @@ -137,18 +139,18 @@ system.defaults.controlcenter.NowPlaying = true; test = lib.strings.concatMapStringsSep "\n" (x: '' - echo >&2 "checking defaults write in /${x}" + echo >&2 "checking ${x} defaults write in /activate" ${pkgs.python3}/bin/python3 <