From 52ee8c57c26a31588c61fe9507ec688248d011ca Mon Sep 17 00:00:00 2001 From: Emily Date: Sat, 11 Jan 2025 15:44:41 +0000 Subject: [PATCH 01/15] primary-user: init --- modules/module-list.nix | 1 + modules/system/checks.nix | 14 ++++++++ modules/system/primary-user.nix | 60 +++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 modules/system/primary-user.nix diff --git a/modules/module-list.nix b/modules/module-list.nix index 15f6f39..c9c09b9 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/system/checks.nix b/modules/system/checks.nix index 47835ac..ca21ad6 100644 --- a/modules/system/checks.nix +++ b/modules/system/checks.nix @@ -31,6 +31,19 @@ let fi ''; + primaryUser = '' + # shellcheck disable=SC2209 + 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' @@ -275,6 +288,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/primary-user.nix b/modules/system/primary-user.nix new file mode 100644 index 0000000..1eb7b29 --- /dev/null +++ b/modules/system/primary-user.nix @@ -0,0 +1,60 @@ +{ + 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.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. + ''; + } + ]; + }; +} From c449918bfbce4211ad2c39a793780b336a737c48 Mon Sep 17 00:00:00 2001 From: Emily Date: Sat, 11 Jan 2025 15:44:41 +0000 Subject: [PATCH 02/15] homebrew: move to system activation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds an optional explicit `homebrew.user` option that allows users to avoid setting `system.primaryUser`, partly as a proof of concept of what the interfaces should look like in the future. Homebrew only officially support one global installation, so a singleton matches upstream’s expectations; in practice, it may be useful for us to nest this into `users.users.*.homebrew` instead, at the expense of being an unsupported setup if used to its full potential. Since that would be a breaking change to the inteface anyway, I think adding `homebrew.user` for now is acceptable. (I think one native Apple Silicon and one Rosetta 2 Homebrew installation – under `/opt/homebrew` and `/usr/local` respectively – may be exceptions to this lack of upstream support, but that would be complicated to support even with `users.users.*.homebrew`.) I’m not entirely sure where in system activation this should go. Probably after the user defaults and launch agents stuff, to match the existing logic in user activation, and I lean towards doing it as late as possible; too early and we might not have the users and groups required to bootstrap a Homebrew installation set up, but as Homebrew installations could be fiddly and fail, doing it in the middle could leave a partially‐activated system. Probably it should be done in a launch agent or something instead, but this is my best guess as to the appropriate place for now. The downside is that activation scripts generally won’t be able to assume that the Homebrew prefix is populated according to the current configuration, but they probably shouldn’t be depending on that anyway? --- modules/homebrew.nix | 18 ++++++++++++++++++ modules/system/activation-scripts.nix | 2 +- tests/homebrew.nix | 2 ++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/modules/homebrew.nix b/modules/homebrew.nix index 10764fb..c78d41f 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/system/activation-scripts.nix b/modules/system/activation-scripts.nix index c8ad20a..40a2ed2 100644 --- a/modules/system/activation-scripts.nix +++ b/modules/system/activation-scripts.nix @@ -96,6 +96,7 @@ in ${cfg.activationScripts.keyboard.text} ${cfg.activationScripts.fonts.text} ${cfg.activationScripts.nvram.text} + ${cfg.activationScripts.homebrew.text} ${cfg.activationScripts.postActivation.text} @@ -138,7 +139,6 @@ in ${cfg.activationScripts.extraUserActivation.text} ${cfg.activationScripts.userDefaults.text} ${cfg.activationScripts.userLaunchd.text} - ${cfg.activationScripts.homebrew.text} ${cfg.activationScripts.postUserActivation.text} diff --git a/tests/homebrew.nix b/tests/homebrew.nix index d7fdeab..65ece02 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" From 7877cba5f5f462067ccd5e9eda3562fe016852d7 Mon Sep 17 00:00:00 2001 From: Emily Date: Sat, 11 Jan 2025 15:44:41 +0000 Subject: [PATCH 03/15] launchd: move `userLaunchd` to system activation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I’m not *completely* certain that this handles user agents correctly. There is a deprecated command, `launchctl asuser`, that executes a command in the Mach bootstrap context of another user`. claims that this is required when loading and unloading user agents, but I haven’t tested this. Our current launchd agent logic is pretty weird and broken already anyway, so unless this actively regresses things I’d lean towards keeping it like this until we can move over entirely to `launchctl bootstrap`/`launchctl kickstart`, which aren’t deprecated and can address individual users directly. Someone should definitely test it more extensively than I have, though. --- modules/examples/lnl.nix | 2 + modules/launchd/default.nix | 23 ++++++++++- modules/services/aerospace/default.nix | 1 + modules/services/chunkwm.nix | 1 + modules/services/emacs.nix | 1 + modules/services/ipfs.nix | 1 + modules/services/jankyborders/default.nix | 1 + .../services/karabiner-elements/default.nix | 2 + modules/services/khd/default.nix | 2 + modules/services/kwm/default.nix | 1 + modules/services/lorri.nix | 2 + modules/services/mail/offlineimap.nix | 1 + modules/services/mopidy.nix | 2 + modules/services/postgresql/default.nix | 1 + modules/services/privoxy/default.nix | 1 + modules/services/redis/default.nix | 1 + modules/services/sketchybar/default.nix | 1 + modules/services/skhd/default.nix | 2 + modules/services/spacebar/default.nix | 2 + modules/services/spotifyd.nix | 1 + modules/services/synapse-bt.nix | 1 + modules/services/synergy/default.nix | 2 + modules/services/trezord.nix | 1 + modules/services/yabai/default.nix | 2 + modules/system/activation-scripts.nix | 2 +- modules/system/launchd.nix | 38 ++++++++++--------- tests/launchd-daemons.nix | 10 +++-- tests/services-aerospace.nix | 2 + tests/services-jankyborders.nix | 2 + tests/services-lorri.nix | 2 + tests/services-offlineimap.nix | 2 + tests/services-privoxy.nix | 2 + tests/services-redis.nix | 2 + tests/services-skhd.nix | 2 + tests/services-spacebar.nix | 2 + tests/services-spotifyd.nix | 2 + tests/services-synapse-bt.nix | 2 + tests/services-synergy.nix | 2 + tests/services-yabai.nix | 2 + 39 files changed, 106 insertions(+), 23 deletions(-) diff --git a/modules/examples/lnl.nix b/modules/examples/lnl.nix index 8dff10c..f8153b3 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/launchd/default.nix b/modules/launchd/default.nix index 64b6af7..cfd022f 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/services/aerospace/default.nix b/modules/services/aerospace/default.nix index 3080579..539e3a9 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 354288a..6d8393e 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 ec98950..5e145e0 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 e7cdb74..a628fca 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 cb7ab1e..61b560c 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 8be2ddf..3a2cee1 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 7594baf..a09abab 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 5fb6c56..f7d35f1 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 c4e1ace..2d023af 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 81c8bdb..75dd261 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 be3c05e..241628a 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 64dfad4..fab025c 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 b314723..e40da54 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 ccacd3b..c442b0a 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 c29eec2..1d096cb 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 1f5d0cf..9a0c1d6 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 a56dac5..7aa3c09 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 612bae1..a70ba6c 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 d85a2cd..f93cdf1 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 2a9e088..679424a 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 8da05f3..5d52298 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 fe9d3f9..ae14ae4 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 40a2ed2..ddcbbe9 100644 --- a/modules/system/activation-scripts.nix +++ b/modules/system/activation-scripts.nix @@ -89,6 +89,7 @@ in ${cfg.activationScripts.etc.text} ${cfg.activationScripts.defaults.text} ${cfg.activationScripts.launchd.text} + ${cfg.activationScripts.userLaunchd.text} ${cfg.activationScripts.nix-daemon.text} ${cfg.activationScripts.time.text} ${cfg.activationScripts.networking.text} @@ -138,7 +139,6 @@ in ${cfg.activationScripts.etcChecks.text} ${cfg.activationScripts.extraUserActivation.text} ${cfg.activationScripts.userDefaults.text} - ${cfg.activationScripts.userLaunchd.text} ${cfg.activationScripts.postUserActivation.text} diff --git a/modules/system/launchd.nix b/modules/system/launchd.nix index c578dec..65c9ba7 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 + launchctl asuser "$(id -u -- ${user})" 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} + launchctl asuser "$(id -u -- ${user})" 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/tests/launchd-daemons.nix b/tests/launchd-daemons.nix index 59e35aa..6fe0d0f 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/services-aerospace.nix b/tests/services-aerospace.nix index 088c92d..87f7b6c 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 5bde078..7718c0d 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 7d30152..c7935e1 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 a88e186..aa41c01 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 f6c16a4..76e3646 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 a46916b..ab1e1ee 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 4278940..4851c8c 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 79257fa..96a7f7a 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 956e6a9..651d65c 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 7d50daf..2f024c2 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 9d3d6f1..a8c222f 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 48f369c..9bdaadf 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"; }; From f47b8062cbcd1b6867e42dfac27d8e91467b553e Mon Sep 17 00:00:00 2001 From: Emily Date: Sat, 11 Jan 2025 15:44:41 +0000 Subject: [PATCH 04/15] defaults: move `userDefaults` to system activation --- modules/system/activation-scripts.nix | 2 +- modules/system/defaults-write.nix | 79 ++++-- .../{activate.txt => system.txt} | 0 .../{activate-user.txt => user.txt} | 224 +++++++++--------- tests/system-defaults-write.nix | 10 +- 5 files changed, 172 insertions(+), 143 deletions(-) rename tests/fixtures/system-defaults-write/{activate.txt => system.txt} (100%) rename tests/fixtures/system-defaults-write/{activate-user.txt => user.txt} (50%) diff --git a/modules/system/activation-scripts.nix b/modules/system/activation-scripts.nix index ddcbbe9..a5cdc24 100644 --- a/modules/system/activation-scripts.nix +++ b/modules/system/activation-scripts.nix @@ -88,6 +88,7 @@ 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} @@ -138,7 +139,6 @@ in ${cfg.activationScripts.checks.text} ${cfg.activationScripts.etcChecks.text} ${cfg.activationScripts.extraUserActivation.text} - ${cfg.activationScripts.userDefaults.text} ${cfg.activationScripts.postUserActivation.text} diff --git a/modules/system/defaults-write.nix b/modules/system/defaults-write.nix index 41911a9..630156b 100644 --- a/modules/system/defaults-write.nix +++ b/modules/system/defaults-write.nix @@ -9,6 +9,12 @@ 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: let + user = escapeShellArg config.system.primaryUser; + in map + (cmd: ''launchctl asuser "$(id -u -- ${user})" sudo --user=${user} -- ${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,29 +24,29 @@ 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; - iCal = defaultsToList "com.apple.iCal" cfg.iCal; - 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; + iCal = userDefaultsToList "com.apple.iCal" cfg.iCal; + 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); @@ -58,6 +64,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 @@ -125,11 +155,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/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 50% rename from tests/fixtures/system-defaults-write/activate-user.txt rename to tests/fixtures/system-defaults-write/user.txt index 9f48668..21820d7 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' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'AppleEnableMouseSwipeNavigateWithScrolls' $' ' -defaults write -g 'AppleEnableSwipeNavigateWithScrolls' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'AppleEnableSwipeNavigateWithScrolls' $' ' -defaults write -g 'AppleFontSmoothing' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'AppleFontSmoothing' $' 1 ' -defaults write -g 'AppleICUForce24HourTime' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'AppleICUForce24HourTime' $' ' -defaults write -g 'AppleKeyboardUIMode' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'AppleKeyboardUIMode' $' 3 ' -defaults write -g 'ApplePressAndHoldEnabled' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'ApplePressAndHoldEnabled' $' ' -defaults write -g 'AppleScrollerPagingBehavior' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'AppleScrollerPagingBehavior' $' ' -defaults write -g 'AppleShowAllExtensions' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'AppleShowAllExtensions' $' ' -defaults write -g 'AppleShowAllFiles' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'AppleShowAllFiles' $' ' -defaults write -g 'AppleShowScrollBars' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'AppleShowScrollBars' $' Always ' -defaults write -g 'AppleSpacesSwitchOnActivate' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'AppleSpacesSwitchOnActivate' $' ' -defaults write -g 'AppleWindowTabbingMode' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'AppleWindowTabbingMode' $' always ' -defaults write -g 'InitialKeyRepeat' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'InitialKeyRepeat' $' 10 ' -defaults write -g 'KeyRepeat' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'KeyRepeat' $' 1 ' -defaults write -g 'NSAutomaticCapitalizationEnabled' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'NSAutomaticCapitalizationEnabled' $' ' -defaults write -g 'NSAutomaticDashSubstitutionEnabled' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'NSAutomaticDashSubstitutionEnabled' $' ' -defaults write -g 'NSAutomaticInlinePredictionEnabled' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'NSAutomaticInlinePredictionEnabled' $' ' -defaults write -g 'NSAutomaticPeriodSubstitutionEnabled' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'NSAutomaticPeriodSubstitutionEnabled' $' ' -defaults write -g 'NSAutomaticQuoteSubstitutionEnabled' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'NSAutomaticQuoteSubstitutionEnabled' $' ' -defaults write -g 'NSAutomaticSpellingCorrectionEnabled' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'NSAutomaticSpellingCorrectionEnabled' $' ' -defaults write -g 'NSAutomaticWindowAnimationsEnabled' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'NSAutomaticWindowAnimationsEnabled' $' ' -defaults write -g 'NSDisableAutomaticTermination' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'NSDisableAutomaticTermination' $' ' -defaults write -g 'NSDocumentSaveNewDocumentsToCloud' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'NSDocumentSaveNewDocumentsToCloud' $' ' -defaults write -g 'NSNavPanelExpandedStateForSaveMode' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'NSNavPanelExpandedStateForSaveMode' $' ' -defaults write -g 'NSNavPanelExpandedStateForSaveMode2' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'NSNavPanelExpandedStateForSaveMode2' $' ' -defaults write -g 'NSScrollAnimationEnabled' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'NSScrollAnimationEnabled' $' ' -defaults write -g 'NSTableViewDefaultSizeMode' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'NSTableViewDefaultSizeMode' $' 2 ' -defaults write -g 'NSTextShowsControlCharacters' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'NSTextShowsControlCharacters' $' ' -defaults write -g 'NSUseAnimatedFocusRing' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'NSUseAnimatedFocusRing' $' ' -defaults write -g 'NSWindowResizeTime' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'NSWindowResizeTime' $' 0.010000 ' -defaults write -g 'NSWindowShouldDragOnGesture' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'NSWindowShouldDragOnGesture' $' ' -defaults write -g 'PMPrintingExpandedStateForPrint' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'PMPrintingExpandedStateForPrint' $' ' -defaults write -g 'PMPrintingExpandedStateForPrint2' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'PMPrintingExpandedStateForPrint2' $' ' -defaults write -g 'com.apple.keyboard.fnState' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'com.apple.keyboard.fnState' $' ' -defaults write -g 'com.apple.mouse.tapBehavior' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'com.apple.mouse.tapBehavior' $' 1 ' -defaults write -g 'com.apple.springing.delay' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'com.apple.springing.delay' $' 0.000000 ' -defaults write -g 'com.apple.springing.enabled' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'com.apple.springing.enabled' $' ' -defaults write -g 'com.apple.swipescrolldirection' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'com.apple.swipescrolldirection' $' ' -defaults write -g 'com.apple.trackpad.enableSecondaryClick' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'com.apple.trackpad.enableSecondaryClick' $' ' -defaults write -g 'com.apple.trackpad.trackpadCornerClickBehavior' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write -g 'com.apple.trackpad.trackpadCornerClickBehavior' $' 1 ' -defaults write .GlobalPreferences 'com.apple.sound.beep.sound' $' +launchctl asuser "$(id -u -- test-defaults-user)" 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' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.menuextra.clock 'FlashDateSeparators' $' ' -defaults write com.apple.menuextra.clock 'Show24Hour' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.menuextra.clock 'Show24Hour' $' ' -defaults write com.apple.menuextra.clock 'ShowDate' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.menuextra.clock 'ShowDate' $' 2 ' -defaults write com.apple.menuextra.clock 'ShowDayOfWeek' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.menuextra.clock 'ShowDayOfWeek' $' ' -defaults write com.apple.dock 'appswitcher-all-displays' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.dock 'appswitcher-all-displays' $' ' -defaults write com.apple.dock 'autohide-delay' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.dock 'autohide-delay' $' 0.240000 ' -defaults write com.apple.dock 'expose-group-apps' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.dock 'expose-group-apps' $' ' -defaults write com.apple.dock 'orientation' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.dock 'orientation' $' left ' -defaults write com.apple.dock 'persistent-apps' $' +launchctl asuser "$(id -u -- test-defaults-user)" 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' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.dock 'persistent-others' $' @@ -353,149 +353,149 @@ defaults write com.apple.dock 'persistent-others' $' ' -defaults write com.apple.dock 'scroll-to-open' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.dock 'scroll-to-open' $' ' -defaults write com.apple.finder 'AppleShowAllExtensions' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.finder 'AppleShowAllExtensions' $' ' -defaults write com.apple.finder 'AppleShowAllFiles' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.finder 'AppleShowAllFiles' $' ' -defaults write com.apple.finder 'CreateDesktop' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.finder 'CreateDesktop' $' ' -defaults write com.apple.finder 'FXDefaultSearchScope' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.finder 'FXDefaultSearchScope' $' SCcf ' -defaults write com.apple.finder 'FXEnableExtensionChangeWarning' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.finder 'FXEnableExtensionChangeWarning' $' ' -defaults write com.apple.finder 'FXPreferredViewStyle' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.finder 'FXPreferredViewStyle' $' Flwv ' -defaults write com.apple.finder 'FXRemoveOldTrashItems' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.finder 'FXRemoveOldTrashItems' $' ' -defaults write com.apple.finder 'NewWindowTarget' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.finder 'NewWindowTarget' $' PfLo ' -defaults write com.apple.finder 'NewWindowTargetPath' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.finder 'NewWindowTargetPath' $' file:///Library/Apple ' -defaults write com.apple.finder 'QuitMenuItem' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.finder 'QuitMenuItem' $' ' -defaults write com.apple.finder 'ShowExternalHardDrivesOnDesktop' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.finder 'ShowExternalHardDrivesOnDesktop' $' ' -defaults write com.apple.finder 'ShowHardDrivesOnDesktop' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.finder 'ShowHardDrivesOnDesktop' $' ' -defaults write com.apple.finder 'ShowMountedServersOnDesktop' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.finder 'ShowMountedServersOnDesktop' $' ' -defaults write com.apple.finder 'ShowPathbar' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.finder 'ShowPathbar' $' ' -defaults write com.apple.finder 'ShowRemovableMediaOnDesktop' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.finder 'ShowRemovableMediaOnDesktop' $' ' -defaults write com.apple.finder 'ShowStatusBar' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.finder 'ShowStatusBar' $' ' -defaults write com.apple.finder '_FXShowPosixPathInTitle' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.finder '_FXShowPosixPathInTitle' $' ' -defaults write com.apple.finder '_FXSortFoldersFirst' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.finder '_FXSortFoldersFirst' $' ' -defaults write com.apple.finder '_FXSortFoldersFirstOnDesktop' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.finder '_FXSortFoldersFirstOnDesktop' $' ' -defaults write com.apple.HIToolbox 'AppleFnUsageType' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.HIToolbox 'AppleFnUsageType' $' 2 ' -defaults write com.apple.iCal 'CalendarSidebarShown' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.iCal 'CalendarSidebarShown' $' ' -defaults write com.apple.iCal 'TimeZone support enabled' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.iCal 'TimeZone support enabled' $' ' -defaults write com.apple.iCal 'first day of week' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.iCal 'first day of week' $' 4 ' -defaults write com.apple.screencapture 'include-date' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.screencapture 'include-date' $' ' -defaults write com.apple.screencapture 'location' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.screencapture 'location' $' /tmp ' -defaults write com.apple.screencapture 'target' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.screencapture 'target' $' file ' -defaults write com.apple.screensaver 'askForPassword' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.screensaver 'askForPassword' $' ' -defaults write com.apple.screensaver 'askForPasswordDelay' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.screensaver 'askForPasswordDelay' $' 5 @@ -503,62 +503,62 @@ defaults write com.apple.screensaver 'askForPasswordDelay' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.universalaccess 'closeViewScrollWheelToggle' $' ' -defaults write com.apple.universalaccess 'closeViewZoomFollowsFocus' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.universalaccess 'closeViewZoomFollowsFocus' $' ' -defaults write com.apple.universalaccess 'mouseDriverCursorSize' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.universalaccess 'mouseDriverCursorSize' $' 1.500000 ' -defaults write com.apple.universalaccess 'reduceMotion' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.universalaccess 'reduceMotion' $' ' -defaults write com.apple.universalaccess 'reduceTransparency' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.universalaccess 'reduceTransparency' $' ' -defaults write com.apple.ActivityMonitor 'IconType' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.ActivityMonitor 'IconType' $' 3 ' -defaults write com.apple.ActivityMonitor 'OpenMainWindow' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.ActivityMonitor 'OpenMainWindow' $' ' -defaults write com.apple.ActivityMonitor 'ShowCategory' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.ActivityMonitor 'ShowCategory' $' 103 ' -defaults write com.apple.ActivityMonitor 'SortColumn' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.ActivityMonitor 'SortColumn' $' CPUUsage ' -defaults write com.apple.ActivityMonitor 'SortDirection' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.ActivityMonitor 'SortDirection' $' 0 ' -defaults write NSGlobalDomain 'TISRomanSwitchState' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write NSGlobalDomain 'TISRomanSwitchState' $' 1 ' -defaults write com.apple.Safari 'NSUserKeyEquivalents' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.Safari 'NSUserKeyEquivalents' $' @@ -566,102 +566,102 @@ defaults write com.apple.Safari 'NSUserKeyEquivalents' $'@^q ' -defaults write com.apple.Safari 'com.apple.Safari.ContentPageGroupIdentifier.WebKit2DeveloperExtrasEnabled' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.Safari 'com.apple.Safari.ContentPageGroupIdentifier.WebKit2DeveloperExtrasEnabled' $' ' -defaults write com.apple.WindowManager 'AppWindowGroupingBehavior' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.WindowManager 'AppWindowGroupingBehavior' $' ' -defaults write com.apple.WindowManager 'AutoHide' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.WindowManager 'AutoHide' $' ' -defaults write com.apple.WindowManager 'EnableStandardClickToShowDesktop' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.WindowManager 'EnableStandardClickToShowDesktop' $' ' -defaults write com.apple.WindowManager 'EnableTiledWindowMargins' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.WindowManager 'EnableTiledWindowMargins' $' ' -defaults write com.apple.WindowManager 'EnableTilingByEdgeDrag' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.WindowManager 'EnableTilingByEdgeDrag' $' ' -defaults write com.apple.WindowManager 'EnableTilingOptionAccelerator' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.WindowManager 'EnableTilingOptionAccelerator' $' ' -defaults write com.apple.WindowManager 'EnableTopTilingByEdgeDrag' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.WindowManager 'EnableTopTilingByEdgeDrag' $' ' -defaults write com.apple.WindowManager 'GloballyEnabled' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.WindowManager 'GloballyEnabled' $' ' -defaults write com.apple.WindowManager 'HideDesktop' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.WindowManager 'HideDesktop' $' ' -defaults write com.apple.WindowManager 'StageManagerHideWidgets' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.WindowManager 'StageManagerHideWidgets' $' ' -defaults write com.apple.WindowManager 'StandardHideDesktopIcons' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.WindowManager 'StandardHideDesktopIcons' $' ' -defaults write com.apple.WindowManager 'StandardHideWidgets' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write com.apple.WindowManager 'StandardHideWidgets' $' ' -defaults write ~/Library/Preferences/ByHost/com.apple.controlcenter 'AirDrop' $' +launchctl asuser "$(id -u -- test-defaults-user)" 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' $' +launchctl asuser "$(id -u -- test-defaults-user)" 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' $' +launchctl asuser "$(id -u -- test-defaults-user)" 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' $' +launchctl asuser "$(id -u -- test-defaults-user)" 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' $' +launchctl asuser "$(id -u -- test-defaults-user)" 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' $' +launchctl asuser "$(id -u -- test-defaults-user)" 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' $' +launchctl asuser "$(id -u -- test-defaults-user)" sudo --user=test-defaults-user -- defaults write ~test-defaults-user/Library/Preferences/ByHost/com.apple.controlcenter 'Sound' $' 24 diff --git a/tests/system-defaults-write.nix b/tests/system-defaults-write.nix index 6b06dd2..9184d2c 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 = { @@ -140,18 +142,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 < Date: Sat, 11 Jan 2025 15:44:41 +0000 Subject: [PATCH 05/15] applications: use `system.primaryUser` for the legacy path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit System activation scripts shouldn’t (and soon won’t be able to) rely on `$HOME` being the primary user’s. --- modules/system/applications.nix | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/system/applications.nix b/modules/system/applications.nix index 9dd8766..a2277c7 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 From bed70a84af8e4bc28e4050cded929505a030195a Mon Sep 17 00:00:00 2001 From: Emily Date: Mon, 13 Jan 2025 23:21:04 +0000 Subject: [PATCH 06/15] {environment,nix}: remove references to `$HOME` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These can’t be relied upon in a post‐user‐activation world. Technically a breaking change, if anyone has their home directory outside of `/Users` or is using `root` for this, but, well, I did my best and these are legacy defaults anyway. --- modules/environment/default.nix | 16 +++++++++++++--- modules/nix/default.nix | 11 +++++++++-- modules/system/primary-user.nix | 7 +++++++ 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/modules/environment/default.nix b/modules/environment/default.nix index 377a959..b4b658d 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/nix/default.nix b/modules/nix/default.nix index e5d0801..6f32651 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; }; diff --git a/modules/system/primary-user.nix b/modules/system/primary-user.nix index 1eb7b29..0944580 100644 --- a/modules/system/primary-user.nix +++ b/modules/system/primary-user.nix @@ -19,6 +19,13 @@ ''; }; + 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; From 0abf01266612d3dd72dbf79597282cf33ad721ca Mon Sep 17 00:00:00 2001 From: Emily Date: Wed, 15 Jan 2025 18:41:10 +0000 Subject: [PATCH 07/15] users: refuse to delete the primary user --- modules/users/default.nix | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/modules/users/default.nix b/modules/users/default.nix index bbfd0d1..25e87d1 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,7 +150,6 @@ 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) @@ -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 From 2ca294741f94d74c9d9bdc7171bda41f9f9c975b Mon Sep 17 00:00:00 2001 From: Emily Date: Sat, 11 Jan 2025 15:44:41 +0000 Subject: [PATCH 08/15] activation-scripts: get rid of user activation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🎉 Closes: #96 --- modules/system/activation-scripts.nix | 55 ++++++++--------------- modules/system/base.nix | 10 ++--- modules/system/default.nix | 47 ++++++++++++++++++- modules/users/default.nix | 6 +-- pkgs/darwin-uninstaller/configuration.nix | 14 +++--- pkgs/nix-tools/darwin-rebuild.sh | 26 ++++++++++- tests/activation-scripts.nix | 9 ---- 7 files changed, 104 insertions(+), 63 deletions(-) diff --git a/modules/system/activation-scripts.nix b/modules/system/activation-scripts.nix index a5cdc24..9ab1b7b 100644 --- a/modules/system/activation-scripts.nix +++ b/modules/system/activation-scripts.nix @@ -62,6 +62,23 @@ 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} set -e @@ -77,9 +94,9 @@ in ${cfg.activationScripts.preActivation.text} - # We run `etcChecks` again just in case someone runs `activate` - # directly without `activate-user`. ${cfg.activationScripts.etcChecks.text} + ${cfg.activationScripts.createRun.text} + ${cfg.activationScripts.checks.text} ${cfg.activationScripts.extraActivation.text} ${cfg.activationScripts.groups.text} ${cfg.activationScripts.users.text} @@ -114,45 +131,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.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/base.nix b/modules/system/base.nix index 40c3699..1bdef63 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/default.nix b/modules/system/default.nix index 8351dcc..29a477e 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/users/default.nix b/modules/users/default.nix index 25e87d1..b491c02 100644 --- a/modules/users/default.nix +++ b/modules/users/default.nix @@ -148,14 +148,12 @@ in users.gids = mkMerge gids; users.uids = mkMerge uids; - # 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 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" @@ -176,7 +174,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' diff --git a/pkgs/darwin-uninstaller/configuration.nix b/pkgs/darwin-uninstaller/configuration.nix index ce6be6c..391f9a1 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/nix-tools/darwin-rebuild.sh b/pkgs/nix-tools/darwin-rebuild.sh index 8f207a7..14c56e5 100644 --- a/pkgs/nix-tools/darwin-rebuild.sh +++ b/pkgs/nix-tools/darwin-rebuild.sh @@ -190,6 +190,7 @@ if [ "$action" = switch ] || [ "$action" = build ] || [ "$action" = check ] || [ -- "$flake#$flakeAttr.system" \ | jq -r '.[0].outputs.out') fi + fi if [ "$action" = list ] || [ "$action" = rollback ]; then @@ -210,6 +211,17 @@ fi if [ -z "$systemConfig" ]; then exit 0; fi +# 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 + if [ "$action" = switch ]; then if [ "$USER" != root ] && [ ! -w $(dirname "$profile") ]; then sudo nix-env -p "$profile" --set "$systemConfig" @@ -219,7 +231,9 @@ if [ "$action" = switch ]; then fi if [ "$action" = switch ] || [ "$action" = activate ] || [ "$action" = rollback ]; then - "$systemConfig/activate-user" + if [[ -n $hasActivateUser ]]; then + "$systemConfig/activate-user" + fi if [ "$USER" != root ]; then sudo "$systemConfig/activate" @@ -234,5 +248,13 @@ fi if [ "$action" = check ]; then export checkActivation=1 - "$systemConfig/activate-user" + if [[ -n $hasActivateUser ]]; then + "$systemConfig/activate-user" + else + if [ "$USER" != root ]; then + sudo "$systemConfig/activate" + else + "$systemConfig/activate" + fi + fi fi diff --git a/tests/activation-scripts.nix b/tests/activation-scripts.nix index e7d0856..70c4245 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 From 40d2a159cc6fd4c171613c51f81232b78be0f9be Mon Sep 17 00:00:00 2001 From: Emily Date: Sat, 11 Jan 2025 15:44:41 +0000 Subject: [PATCH 09/15] tests: remove stray `activate-user` references --- tests/networking-hostname.nix | 1 - tests/networking-shell-escape.nix | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/networking-hostname.nix b/tests/networking-hostname.nix index 93a67bb..14d1ded 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 da399b1..8cd2a45 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 ''; } From 516dbe1fa40548945f18135875ed22228db4ce33 Mon Sep 17 00:00:00 2001 From: Emily Date: Sat, 11 Jan 2025 15:44:41 +0000 Subject: [PATCH 10/15] darwin-rebuild: require running as `root` --- .github/workflows/test.yml | 20 +++++------ README.md | 16 ++++----- pkgs/darwin-uninstaller/default.nix | 8 ++--- pkgs/nix-tools/darwin-rebuild.sh | 56 ++++++++++++++--------------- 4 files changed, 49 insertions(+), 51 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d87b763..f6da467 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/README.md b/README.md index 98e2ba8..fe12ec1 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/pkgs/darwin-uninstaller/default.nix b/pkgs/darwin-uninstaller/default.nix index 658991b..2fc5cc6 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 14c56e5..8824e61 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 @@ -190,15 +195,10 @@ if [ "$action" = switch ] || [ "$action" = build ] || [ "$action" = check ] || [ -- "$flake#$flakeAttr.system" \ | jq -r '.[0].outputs.out') fi - 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 @@ -222,24 +222,26 @@ else hasActivateUser= fi -if [ "$action" = switch ]; then - if [ "$USER" != root ] && [ ! -w $(dirname "$profile") ]; then - sudo nix-env -p "$profile" --set "$systemConfig" +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 if [[ -n $hasActivateUser ]]; then - "$systemConfig/activate-user" - fi - - if [ "$USER" != root ]; then - sudo "$systemConfig/activate" - else - "$systemConfig/activate" + runActivateUser fi + "$systemConfig/activate" fi if [ "$action" = changelog ]; then @@ -249,12 +251,8 @@ fi if [ "$action" = check ]; then export checkActivation=1 if [[ -n $hasActivateUser ]]; then - "$systemConfig/activate-user" + runActivateUser else - if [ "$USER" != root ]; then - sudo "$systemConfig/activate" - else - "$systemConfig/activate" - fi + "$systemConfig/activate" fi fi From 051283a8953d08a7c16de5304e8dba5c4418e02e Mon Sep 17 00:00:00 2001 From: Emily Date: Thu, 30 Jan 2025 00:55:52 +0000 Subject: [PATCH 11/15] {activation-scripts,activate-system}: purify environment again --- modules/services/activate-system/default.nix | 42 +++++++++++++++++++- modules/system/activation-scripts.nix | 33 +++++++++++---- tests/nix-enable.nix | 4 ++ 3 files changed, 71 insertions(+), 8 deletions(-) diff --git a/modules/services/activate-system/default.nix b/modules/services/activate-system/default.nix index df0b48e..f935ee1 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) diff --git a/modules/system/activation-scripts.nix b/modules/system/activation-scripts.nix index 9ab1b7b..646929d 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 { @@ -80,18 +81,36 @@ in ]; 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} ${cfg.activationScripts.etcChecks.text} diff --git a/tests/nix-enable.nix b/tests/nix-enable.nix index e052aa2..bf84d55 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" ''; } From af62c4d1763fd60a2fbd51df7ce56828e4fa309e Mon Sep 17 00:00:00 2001 From: Emily Date: Sat, 11 Jan 2025 15:44:41 +0000 Subject: [PATCH 12/15] checks: make `nixPath` check more helpful --- modules/system/checks.nix | 99 ++++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 39 deletions(-) diff --git a/modules/system/checks.nix b/modules/system/checks.nix index ca21ad6..c093e35 100644 --- a/modules/system/checks.nix +++ b/modules/system/checks.nix @@ -163,49 +163,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/nix-darwin/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/nix-darwin/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/nix-darwin/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 From 7e5c6f7e2156dcece3c30d42d19ce482b8f2a4ff Mon Sep 17 00:00:00 2001 From: Emily Date: Sat, 11 Jan 2025 15:44:41 +0000 Subject: [PATCH 13/15] etc: merge `etcChecks` into `checks` The `activate-system` daemon will now run all the checks, which seems like probably a good idea anyway? --- modules/nix/default.nix | 2 +- modules/services/activate-system/default.nix | 2 +- modules/system/activation-scripts.nix | 1 - modules/system/etc.nix | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/nix/default.nix b/modules/nix/default.nix index 6f32651..540fbc5 100644 --- a/modules/nix/default.nix +++ b/modules/nix/default.nix @@ -876,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/services/activate-system/default.nix b/modules/services/activate-system/default.nix index f935ee1..cb0fd12 100644 --- a/modules/services/activate-system/default.nix +++ b/modules/services/activate-system/default.nix @@ -65,7 +65,7 @@ in 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/system/activation-scripts.nix b/modules/system/activation-scripts.nix index 646929d..023dc3f 100644 --- a/modules/system/activation-scripts.nix +++ b/modules/system/activation-scripts.nix @@ -113,7 +113,6 @@ in ${cfg.activationScripts.preActivation.text} - ${cfg.activationScripts.etcChecks.text} ${cfg.activationScripts.createRun.text} ${cfg.activationScripts.checks.text} ${cfg.activationScripts.extraActivation.text} diff --git a/modules/system/etc.nix b/modules/system/etc.nix index bc60bef..c9cae7c 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: From a0e4dd2af9de4c62319e5ffe5c225d8488e42024 Mon Sep 17 00:00:00 2001 From: Emily Date: Fri, 14 Feb 2025 19:08:27 +0000 Subject: [PATCH 14/15] activation-scripts: move `createRun` after `checks` The checks should no longer depend on `/run`, so this avoids modifying the system before they run. --- modules/system/activation-scripts.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/system/activation-scripts.nix b/modules/system/activation-scripts.nix index 023dc3f..23f1024 100644 --- a/modules/system/activation-scripts.nix +++ b/modules/system/activation-scripts.nix @@ -113,8 +113,8 @@ in ${cfg.activationScripts.preActivation.text} - ${cfg.activationScripts.createRun.text} ${cfg.activationScripts.checks.text} + ${cfg.activationScripts.createRun.text} ${cfg.activationScripts.extraActivation.text} ${cfg.activationScripts.groups.text} ${cfg.activationScripts.users.text} From b9e580c1130307c3aee715956a11824c0d8cdc5e Mon Sep 17 00:00:00 2001 From: Emily Date: Sun, 19 Jan 2025 00:55:21 +0000 Subject: [PATCH 15/15] changelog: document user activation removal --- CHANGELOG | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index e64e013..f6153cc 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