Compare commits

..

21 commits

Author SHA1 Message Date
Sam
6a7fdcd583
readme: Use bullet-list for documentation location (#1703)
Some checks are pending
Test / test-stable (push) Waiting to run
Test / install-against-stable (push) Waiting to run
Test / install-flake (push) Waiting to run
Update website / Build (push) Waiting to run
Update website / Deploy (push) Blocked by required conditions
2026-02-19 17:08:02 +00:00
Sean Gilligan
ebe39ab3fa readme: Use bullet-list for documentation location
This will make it much easier to find the online documentation at
a quick glance, as well as making it easier to see the commands
for viewing locally.
2026-02-17 18:03:42 -08:00
Sam
aff4c008ce
etc: support experimental official Nix installer (#1702) 2026-02-17 23:45:16 +00:00
Michael Hoang
6d789c5a41 etc: support experimental official Nix installer 2026-02-17 23:53:32 +01:00
Sam
6c5a56295d
modules/homebrew: comprehensive module refresh (#1692) 2026-02-12 19:01:55 +00:00
Malo Bourgon
a43b4091db
modules/homebrew: add CHANGELOG entry for module refresh 2026-02-12 10:24:39 -08:00
Malo Bourgon
e0ffd55e7a
modules/homebrew: fix typos and improve option descriptions 2026-02-12 10:24:39 -08:00
Malo Bourgon
c68f5d1387
modules/homebrew: add onActivation.cleanup "check" mode
Closes #1032

Add `"check"` to the `onActivation.cleanup` enum. When set, nix-darwin runs
`brew bundle cleanup` during system checks to detect Homebrew packages that
are installed but not present in the generated Brewfile. If extra packages
are found, activation fails with a list of them and remediation steps.

Unlike `"uninstall"` and `"zap"`, the `"check"` mode never removes packages
-- it only reports. This runs during both `darwin-rebuild check` and
`darwin-rebuild switch`, matching the behavior of all other system checks.
2026-02-12 10:24:39 -08:00
Malo Bourgon
ca6f8609c3
modules/homebrew: add shell integration options
Add `enableBashIntegration`, `enableFishIntegration`, and
`enableZshIntegration` options that evaluate `brew shellenv` to set up
Homebrew's environment and shell completions. This automates the
boilerplate that every nix-darwin Homebrew user currently writes manually.

All three shells use `interactiveShellInit`, consistent with direnv and
home-manager conventions. Fish additionally sets up completions paths in
the same hook.
2026-02-12 10:24:39 -08:00
Malo Bourgon
8c29e146dd
modules/homebrew: replace brewPrefix with prefix
Closes #596

`homebrew.brewPrefix` defaulted to the bin directory (`/opt/homebrew/bin`),
not the actual Homebrew prefix (`/opt/homebrew`). This misled users into
writing `${config.homebrew.brewPrefix}/bin`, producing the broken path
`/opt/homebrew/bin/bin`.

Replace it with `homebrew.prefix`, which has correct semantics matching
`brew --prefix`. The old `brewPrefix` option is removed using
`mkRemovedOptionModule`, which catches both users who set the option and
users who read it in custom code. A warning also fires if the new `prefix`
value ends with `/bin`, catching users who copy the old value verbatim.
2026-02-10 09:26:54 -08:00
Malo Bourgon
24531016d8
modules/homebrew: deprecate homebrew.global.lockfiles
Homebrew Bundle removed lockfile support in Homebrew 4.4.0 (Oct 2024):
the `--no-lock` CLI flag, the `HOMEBREW_BUNDLE_NO_LOCK` env var, and
the `no_lock` parameter in `installer.rb` are all dead code. Setting
`homebrew.global.lockfiles` has had no effect on current Homebrew
versions.

- Replace the `lockfiles` option with a hidden stub (matching `noLock`)
- Replace the `noLock` hard assertion with a shared deprecation warning
  for both options
- Stop setting `HOMEBREW_BUNDLE_NO_LOCK` in `environment.variables`
- Remove the lockfiles paragraph from the `brewfile` option description
2026-02-10 09:26:54 -08:00
Malo Bourgon
65cfcebaa2
modules/homebrew: remove homebrew.whalebrews option
Whalebrew support was fully removed from Homebrew Bundle in
Homebrew 4.7.0 (Nov 2025). A `whalebrew` entry in a Brewfile now
raises `RuntimeError: Invalid Brewfile: undefined method 'whalebrew'`,
breaking the entire `brew bundle` invocation.

Use `mkRemovedOptionModule` so that existing configs get a clear
warning instead of an undefined-option error. Also removes the
auto-addition of `"whalebrew"` to `homebrew.brews` and the Brewfile
generation for Docker containers.
2026-02-10 09:26:54 -08:00
Malo Bourgon
3479b795aa
modules/homebrew: add homebrew.cargoPackages option
Add support for `cargo "pkg"` entries in the generated Brewfile. Homebrew
Bundle supports installing Rust crates via `cargo install`; the `rust`
formula is automatically installed if not already present.
2026-02-10 09:24:45 -08:00
Malo Bourgon
cbe4a600d4
modules/homebrew: add homebrew.goPackages option
Add support for `go "pkg"` entries in the generated Brewfile. Homebrew
Bundle supports installing Go packages via `go install`; the `go`
formula is automatically installed if not already present.
2026-02-10 09:24:17 -08:00
Malo Bourgon
c65c24c87c
modules/homebrew: add postinstall option for brews and casks
Both `brew bundle` formula and cask installers support a `postinstall`
option -- a shell command to run after the package is installed or
upgraded. The command only executes when the package actually changed,
not on every `brew bundle` run.

Examples from the Homebrew docs added to the `homebrew.brews` and
`homebrew.casks` option examples and tests.
2026-02-10 09:22:16 -08:00
Malo Bourgon
a3fd89f1bb
modules/homebrew: add link: :overwrite support
Homebrew supports `link: :overwrite` which runs `brew link --overwrite`,
force-overwriting existing symlinks. Extract the existing
`restart_service` special-case logic into a reusable helper
(`mkBrewfileLineBoolOrSymbolString`) for options that can be either a
bool or a Ruby symbol in the Brewfile.
2026-02-10 09:22:15 -08:00
Malo Bourgon
36815b4852
modules/homebrew: add restart_service "always" support
Homebrew supports restart_service: :always which restarts the service
on every brew bundle run, even if the formula wasn't changed.
2026-02-10 09:22:15 -08:00
Michael Hoang
7c952d9a52
Add support for installing vscode extensions via brew (#1222) 2026-02-10 15:13:34 +00:00
Frank Chiarulli Jr.
fdbfb1dc1b add support for installing vscode extensions via brew 2026-02-09 21:59:25 -05:00
Sam
0d7874ef7e
modules/homebrew: remove duplicates from brewfile (#1689) 2026-02-04 05:49:06 +00:00
Josh Gibbs
53dd29f381
remove duplicates from brewfile 2026-02-03 21:22:21 -08:00
13 changed files with 385 additions and 111 deletions

View file

@ -1,3 +1,32 @@
2026-02-10
- Major changes to `homebrew` module
`homebrew.brewPrefix` was renamed to `homebrew.prefix`, and its semantics
changed: the old option pointed to the bin directory (e.g.,
`/opt/homebrew/bin`), while the new option points to the Homebrew prefix
(e.g., `/opt/homebrew`), matching `brew --prefix`.
`homebrew.whalebrews` was removed. Whalebrew support was removed from
Homebrew Bundle in Homebrew 4.7.0 (Nov 2025), so `whalebrew` entries in a
Brewfile now cause `brew bundle` to fail.
`homebrew.global.lockfiles` and `homebrew.global.noLock` no longer have any
effect. Homebrew Bundle removed lockfile support in Homebrew 4.4.0 (Oct 2024).
`homebrew.onActivation.cleanup` now supports a `"check"` mode, which checks
for unlisted packages and aborts activation if any are found, without
removing them.
Shell integration options were added: `homebrew.enableBashIntegration`,
`homebrew.enableFishIntegration`, and `homebrew.enableZshIntegration`.
New Brewfile entry types were added: `homebrew.goPackages`,
`homebrew.cargoPackages`, and `homebrew.vscode`.
New options were added for brews: `postinstall`, `link = "overwrite"`, and
`restart_service = "always"`. The `postinstall` option was also added for
casks.
2025-01-30
- Previously, some nix-darwin options applied to the user running
`darwin-rebuild`. As part of a longterm migration to make

View file

@ -166,9 +166,11 @@ sudo nix-channel --update
## Documentation
`darwin-help` will open up a local copy of the reference documentation, it can also be found online [here](https://nix-darwin.github.io/nix-darwin/manual/index.html).
The reference documentation is available:
The documentation is also available as manpages by running `man 5 configuration.nix`.
* Online: [nix-darwin reference](https://nix-darwin.github.io/nix-darwin/manual/index.html)
* Locally in your browser via the `darwin-help` command
* As a manual page via `man 5 configuration.nix`
## Uninstalling

View file

@ -0,0 +1,7 @@
# Set up Nix only on SSH connections
# See: https://github.com/DeterminateSystems/nix-installer/pull/714
if [ -e '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' ] && [ -n "${SSH_CONNECTION:-}" ] && [ "${SHLVL:-0}" -eq 1 ]; then
. '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh'
fi
# End Nix

View file

@ -0,0 +1,3 @@
# Written by https://github.com/NixOS/nix-installer
# The contents below are based on options specified at installation time.

View file

@ -0,0 +1,10 @@
# Generated by https://github.com/NixOS/nix-installer
# See `/nix/nix-installer --version` for the version details.
extra-experimental-features = nix-command flakes
always-allow-substitutes = true
bash-prompt-prefix = (nix:$name)\040
max-jobs = auto
extra-nix-path = nixpkgs=flake:nixpkgs
!include nix.custom.conf

View file

@ -12,7 +12,7 @@ let
mkBrewfileSectionString = heading: entries: optionalString (entries != [ ]) ''
# ${heading}
${concatMapStringsSep "\n" (v: v.brewfileLine or v) entries}
${concatStringsSep "\n" (unique (map (v: v.brewfileLine or v) entries))}
'';
@ -28,9 +28,23 @@ let
mkBrewfileLineOptionsListString = attrs:
concatStringsSep ", " (mapAttrsToList (n: v: "${n}: ${v}") attrs);
# Renders a Brewfile option that can be either a bool or a Ruby symbol (e.g. `:overwrite`).
mkBrewfileLineBoolOrSymbolString = name: config: sCfg:
optionalString (hasAttr name sCfg) (
", ${name}: " + (
if isBool config.${name} then sCfg.${name}
else ":${config.${name}}"
)
);
# Option and submodule helper functions ----------------------------------------------------------
mkShellIntegrationOption = shell: mkEnableOption ''
Homebrew ${shell} shell integration, which sets up Homebrew's environment
and shell completions
'';
mkNullOrBoolOption = args: mkOption (args // {
type = types.nullOr types.bool;
default = null;
@ -62,29 +76,36 @@ let
onActivationOptions = { config, ... }: {
options = {
cleanup = mkOption {
type = types.enum [ "none" "uninstall" "zap" ];
type = types.enum [ "none" "check" "uninstall" "zap" ];
default = "none";
example = "uninstall";
description = ''
This option manages what happens to formulae installed by Homebrew, that aren't present in
This option manages what happens to packages installed by Homebrew that aren't present in
the Brewfile generated by this module, during {command}`nix-darwin` system
activation.
When set to `"none"` (the default), formulae not present in the generated
When set to `"none"` (the default), packages not present in the generated
Brewfile are left installed.
When set to `"check"`, {command}`nix-darwin` verifies during system activation that no
Homebrew packages (taps, formulae, casks, etc.) are installed that aren't present in the
generated Brewfile. If extra packages are found, activation fails with a list of them.
Note that when this check fails during {command}`darwin-rebuild switch`, the entire
system activation is aborted and no other configuration changes will be applied until
the issue is resolved.
When set to `"uninstall"`, {command}`nix-darwin` invokes
{command}`brew bundle [install]` with the {command}`--cleanup` flag. This
uninstalls all formulae not listed in generated Brewfile, i.e.,
{command}`brew uninstall` is run for those formulae.
uninstalls all packages not listed in the generated Brewfile, i.e.,
{command}`brew uninstall` is run for those packages.
When set to `"zap"`, {command}`nix-darwin` invokes
{command}`brew bundle [install]` with the {command}`--cleanup --zap`
flags. This uninstalls all formulae not listed in the generated Brewfile, and if the
formula is a cask, removes all files associated with that cask. In other words,
{command}`brew uninstall --zap` is run for all those formulae.
flags. This uninstalls all packages not listed in the generated Brewfile, and if the
package is a cask, removes all files associated with that cask. In other words,
{command}`brew uninstall --zap` is run for all those packages.
If you plan on exclusively using {command}`nix-darwin` to manage formulae
If you plan on exclusively using {command}`nix-darwin` to manage packages
installed by Homebrew, you probably want to set this option to
`"uninstall"` or `"zap"`.
'';
@ -97,7 +118,7 @@ let
{command}`nix-darwin` system activation. The default is `false`
so that repeated invocations of {command}`darwin-rebuild switch` are idempotent.
Note that Homebrew auto-updates when it's been more then 5 minutes since it last updated.
Note that Homebrew auto-updates when it's been more than 5 minutes since it last updated.
Although auto-updating is disabled by default during system activation, note that Homebrew
will auto-update when you manually invoke certain Homebrew commands. To modify this
@ -155,14 +176,6 @@ let
Whether to enable Homebrew to automatically use the Brewfile that this module generates in
the Nix store, when you manually invoke {command}`brew bundle`.
Enabling this option will change the default value of
[](#opt-homebrew.global.lockfiles) to `false` since, with
this option enabled, {command}`brew bundle [install]` will default to using the
Brewfile that this module generates in the Nix store, unless you explicitly point it at
another Brewfile using the `--file` flag. As a result, it will try to
write the lockfile in the Nix store, and complain that it can't (though the command will
run successfully regardless).
Implementation note: when enabled, this option sets the
`HOMEBREW_BUNDLE_FILE` environment variable to the path of the Brewfile
that this module generates in the Nix store, by adding it to
@ -178,7 +191,7 @@ let
{command}`brew tap`, and {command}`brew bundle [install]`.
Note that Homebrew auto-updates when you manually invoke commands like the ones mentioned
above if it's been more then 5 minutes since it last updated.
above if it's been more than 5 minutes since it last updated.
You may want to consider disabling this option if you have
[](#opt-homebrew.onActivation.upgrade) enabled, and
@ -191,31 +204,13 @@ let
[](#opt-environment.variables).
'';
};
lockfiles = mkOption {
type = types.bool;
default = !config.brewfile;
defaultText = literalExpression "!config.homebrew.global.brewfile";
description = ''
Whether to enable Homebrew to generate lockfiles when you manually invoke
{command}`brew bundle [install]`.
This option will default to `false` if
[](#opt-homebrew.global.brewfile) is enabled since, with that option enabled,
{command}`brew bundle [install]` will default to using the Brewfile that this
module generates in the Nix store, unless you explicitly point it at another Brewfile
using the `--file` flag. As a result, it will try to write the
lockfile in the Nix store, and complain that it can't (though the command will run
successfully regardless).
Implementation note: when disabled, this option sets the
`HOMEBREW_BUNDLE_NO_LOCK` environment variable, by adding it to
[](#opt-environment.variables).
'';
};
# The `noLock` option was replaced by `lockfiles`. Due to `homebrew.global` being a submodule,
# we can't use `mkRemovedOptionModule`, so we leave this option definition here, and trigger
# and error message with an assertion below if it's set by the user.
# `noLock` was the original option; `lockfiles` replaced it (with inverted semantics).
# Both are now dead: Homebrew Bundle removed lockfile support in Homebrew 4.4.0
# (Oct 2024), so the `HOMEBREW_BUNDLE_NO_LOCK` env var and `--no-lock` CLI flag are
# ignored. We keep both definitions with null defaults to detect explicit user
# configuration and emit a warning below. We can't use `mkRemovedOptionModule` because
# `homebrew.global` is a submodule.
lockfiles = mkOption { visible = false; default = null; };
noLock = mkOption { visible = false; default = null; };
homebrewEnvironmentVariables = mkInternalOption { type = types.attrs; };
@ -225,7 +220,6 @@ let
homebrewEnvironmentVariables = {
HOMEBREW_BUNDLE_FILE = mkIf config.brewfile "${brewfileFile}";
HOMEBREW_NO_AUTO_UPDATE = mkIf (!config.autoUpdate) "1";
HOMEBREW_BUNDLE_NO_LOCK = mkIf (!config.lockfiles) "1";
};
};
};
@ -253,6 +247,10 @@ let
description = ''
Whether to auto-update the tap even if it is not hosted on GitHub. By default, only taps
hosted on GitHub are auto-updated (for performance reasons).
Note: Homebrew Bundle accepts this option in Brewfile syntax but may silently ignore it
during installation. See [the Homebrew Bundle source](https://github.com/Homebrew/brew/tree/master/Library/Homebrew/bundle)
for current behavior.
'';
};
@ -385,19 +383,34 @@ let
};
require_sha = mkNullOrBoolOption {
description = ''
Whether to require cask(s) to have a checksum.
Whether to require casks to have a checksum.
Homebrew's default is `false`.
'';
};
no_quarantine = mkNullOrBoolOption {
description = "Whether to disable quarantining of downloads.";
description = ''
Whether to disable quarantining of downloads.
Note: this option is deprecated in Homebrew and may be removed in a
future release. See [Homebrew/brew#20755](https://github.com/Homebrew/brew/issues/20755).
Homebrew's default is `false`.
'';
};
no_binaries = mkNullOrBoolOption {
description = "Whether to disable linking of helper executables.";
description = ''
Whether to disable linking of helper executables.
Homebrew's default is `false`.
'';
};
ignore_dependencies = mkNullOrBoolOption {
description = "Ignore casks dependencies in case you manage them extrenally";
description = ''
Whether to ignore cask dependencies, e.g., when you manage them externally.
Homebrew's default is `false`.
'';
};
brewfileLine = mkInternalOption { type = types.nullOr types.str; };
@ -424,7 +437,7 @@ let
type = with types; nullOr (listOf str);
default = null;
description = ''
Arguments flags to pass to {command}`brew install`. Values should not include the
Argument flags to pass to {command}`brew install`. Values should not include the
leading `"--"`.
'';
};
@ -437,12 +450,14 @@ let
'';
};
restart_service = mkOption {
type = with types; nullOr (either bool (enum [ "changed" ]));
type = with types; nullOr (either bool (enum [ "changed" "always" ]));
default = null;
description = ''
Whether to run {command}`brew services restart` for the formula and register it to
launch at login (or boot). If set to `"changed"`, the service will only
be restarted on version changes.
be restarted when the formula is newly installed or upgraded. If set to
`"always"`, the service will be restarted on every {command}`brew bundle`
run, even if nothing changed.
Homebrew's default is `false`.
'';
@ -450,19 +465,34 @@ let
start_service = mkNullOrBoolOption {
description = ''
Whether to run {command}`brew services start` for the formula and register it to
launch at login (or boot).
launch at login (or boot). Unlike {option}`restart_service`, this only starts
the service if it is not currently running, without restarting an already-running
service.
Homebrew's default is `false`.
'';
};
link = mkNullOrBoolOption {
link = mkOption {
type = with types; nullOr (either bool (enum [ "overwrite" ]));
default = null;
description = ''
Whether to link the formula to the Homebrew prefix. When this option is
`null`, Homebrew will use it's default behavior which is to link the
formula if it's currently unlinked and not keg-only, and to unlink the formula if it's
currently linked and keg-only.
Whether to link the formula to the Homebrew prefix. When set to `"overwrite"`,
existing symlinks will be overwritten ({command}`brew link --overwrite`). When this
option is `null`, Homebrew will use its default behavior, which is to link the formula
if it's currently unlinked and not keg-only, and to unlink the formula if it's currently
linked and keg-only.
'';
};
postinstall = mkNullOrStrOption {
description = ''
A shell command to run after the formula is installed or upgraded. The command is passed
to the system shell and only executes when the formula actually changed (was freshly
installed or upgraded), not on every {command}`brew bundle` run.
'';
};
# `version_file` is intentionally not exposed: it writes the installed version to a file
# path relative to the `brew bundle` working directory, which is not meaningful during
# nix-darwin system activation.
brewfileLine = mkInternalOption { type = types.nullOr types.str; };
};
@ -470,20 +500,14 @@ let
config =
let
sCfg = mkProcessedSubmodConfig config;
sCfgSubset = removeAttrs sCfg [ "name" "restart_service" ];
sCfgSubset = removeAttrs sCfg [ "name" "restart_service" "link" ];
in
{
brewfileLine =
"brew ${sCfg.name}"
+ optionalString (sCfgSubset != { }) ", ${mkBrewfileLineOptionsListString sCfgSubset}"
# We need to handle the `restart_service` option seperately since it can be either a bool
# or `:changed` in the Brewfile.
+ optionalString (sCfg ? restart_service) (
", restart_service: " + (
if isBool config.restart_service then sCfg.restart_service
else ":${config.restart_service}"
)
);
+ mkBrewfileLineBoolOrSymbolString "link" config sCfg
+ mkBrewfileLineBoolOrSymbolString "restart_service" config sCfg;
};
};
@ -510,6 +534,13 @@ let
itself.
'';
};
postinstall = mkNullOrStrOption {
description = ''
A shell command to run after the cask is installed or upgraded. The command is passed to
the system shell and only executes when the cask was actually installed or upgraded, not
on every {command}`brew bundle` run.
'';
};
brewfileLine = mkInternalOption { type = types.nullOr types.str; };
};
@ -533,21 +564,26 @@ in
imports = [
(mkRenamedOptionModule [ "homebrew" "autoUpdate" ] [ "homebrew" "onActivation" "autoUpdate" ])
(mkRenamedOptionModule [ "homebrew" "cleanup" ] [ "homebrew" "onActivation" "cleanup" ])
(mkRemovedOptionModule [ "homebrew" "brewPrefix" ] "`homebrew.brewPrefix` has been renamed to `homebrew.prefix` and its semantics changed: the old option pointed to the bin directory (e.g., `/opt/homebrew/bin`), while the new option points to the Homebrew prefix (e.g., `/opt/homebrew`), matching `brew --prefix`. Please replace `homebrew.brewPrefix` with `homebrew.prefix`, removing the trailing `/bin` if present.")
(mkRemovedOptionModule [ "homebrew" "whalebrews" ] "Whalebrew support was removed from Homebrew Bundle in Homebrew 4.7.0 (Nov 2025). `whalebrew` entries in a Brewfile now cause `brew bundle` to fail. Please manage Whalebrew images directly using the `whalebrew` CLI.")
];
options.homebrew = {
enable = mkEnableOption ''
{command}`nix-darwin` to manage installing/updating/upgrading Homebrew taps, formulae,
and casks, as well as Mac App Store apps and Docker containers, using Homebrew Bundle.
casks, Mac App Store apps, Visual Studio Code extensions, Go packages, and Cargo
crates using Homebrew Bundle.
Note that enabling this option does not install Homebrew, see the Homebrew
[website](https://brew.sh) for installation instructions.
Use the [](#opt-homebrew.brews), [](#opt-homebrew.casks),
[](#opt-homebrew.masApps), and [](#opt-homebrew.whalebrews) options
to list the Homebrew formulae, casks, Mac App Store apps, and Docker containers you'd like to
install. Use the [](#opt-homebrew.taps) option, to make additional formula
repositories available to Homebrew. This module uses those options (along with the
[](#opt-homebrew.masApps), [](#opt-homebrew.vscode),
[](#opt-homebrew.goPackages), and [](#opt-homebrew.cargoPackages) options to list
the Homebrew formulae, casks, Mac App Store apps, Visual Studio Code extensions,
Go packages, and Cargo crates you'd like to install. Use the
[](#opt-homebrew.taps) option, to make additional formula repositories available to
Homebrew. This module uses those options (along with the
[](#opt-homebrew.caskArgs) options) to generate a Brewfile that
{command}`nix-darwin` passes to the {command}`brew bundle` command during
system activation.
@ -572,20 +608,27 @@ in
'';
};
brewPrefix = mkOption {
prefix = mkOption {
type = types.str;
default = if pkgs.stdenv.hostPlatform.isAarch64 then "/opt/homebrew/bin" else "/usr/local/bin";
default = if pkgs.stdenv.hostPlatform.isAarch64 then "/opt/homebrew" else "/usr/local";
defaultText = literalExpression ''
if pkgs.stdenv.hostPlatform.isAarch64 then "/opt/homebrew/bin"
else "/usr/local/bin"
if pkgs.stdenv.hostPlatform.isAarch64 then "/opt/homebrew"
else "/usr/local"
'';
description = ''
The path prefix where the {command}`brew` executable is located. This will be set to
the correct value based on your system's platform, and should only need to be changed if you
manually installed Homebrew in a non-standard location.
The Homebrew prefix directory, i.e., the value that {command}`brew --prefix` returns.
The default is automatically set based on your system's platform, and should only need
to be changed if you manually installed Homebrew in a non-standard location.
'';
};
# These default to `false` (unlike direnv, which defaults to `true`) because existing users
# likely already have `brew shellenv` in their dotfiles, and enabling by default would cause
# duplicate evaluation.
enableBashIntegration = mkShellIntegrationOption "Bash";
enableFishIntegration = mkShellIntegrationOption "Fish";
enableZshIntegration = mkShellIntegrationOption "Zsh";
onActivation = mkOption {
type = types.submodule onActivationOptions;
default = { };
@ -648,6 +691,8 @@ in
description = ''
Whether to always upgrade casks listed in [](#opt-homebrew.casks) regardless
of whether it's unversioned or it updates itself.
Homebrew's default is `false`.
'';
};
@ -674,6 +719,12 @@ in
link = true;
conflicts_with = [ "mysql" ];
}
# `brew install`, run a post-install command on version changes
{
name = "postgresql@16";
postinstall = "\''${HOMEBREW_PREFIX}/opt/postgresql@16/bin/postgres -D \''${HOMEBREW_PREFIX}/var/postgresql@16";
}
]
'';
description = ''
@ -705,6 +756,12 @@ in
name = "opera";
greedy = true;
}
# `brew install --cask`, run a post-install command on install or upgrade
{
name = "google-cloud-sdk";
postinstall = "\''${HOMEBREW_PREFIX}/bin/gcloud components update";
}
]
'';
description = ''
@ -739,21 +796,47 @@ in
'';
};
whalebrews = mkOption {
vscode = mkOption {
type = with types; listOf str;
default = [ ];
example = [ "whalebrew/wget" ];
example = [ "golang.go" ];
description = ''
List of Docker images to install using {command}`whalebrew`.
List of Visual Studio Code extensions to install using Homebrew Bundle.
When this option is used, `"whalebrew"` is automatically added to
[](#opt-homebrew.brews).
A compatible editor (Visual Studio Code, VSCodium, Cursor, or VS Code Insiders)
must be available. If none is found, Homebrew will attempt to install
`visual-studio-code` automatically.
For more information on {command}`whalebrew` see:
[github.com/whalebrew/whalebrew](https://github.com/whalebrew/whalebrew).
For more information on {command}`code` see:
[VSCode Extension Marketplace](https://code.visualstudio.com/docs/editor/extension-marketplace).
'';
};
goPackages = mkOption {
type = with types; listOf str;
default = [ ];
example = [ "github.com/charmbracelet/crush" ];
description = ''
List of Go packages to install using {command}`go install`.
Homebrew will automatically install the {command}`go` formula if it is not already
installed.
'';
};
cargoPackages = mkOption {
type = with types; listOf str;
default = [ ];
example = [ "ripgrep" ];
description = ''
List of Rust packages to install using {command}`cargo install`.
Homebrew will automatically install the {command}`rust` formula if it is not already
installed.
'';
};
extraConfig = mkOption {
type = types.lines;
default = "";
@ -766,7 +849,7 @@ in
brewfile = mkInternalOption {
type = types.str;
description = "String reprensentation of the generated Brewfile useful for debugging.";
description = "String representation of the generated Brewfile useful for debugging.";
};
};
@ -775,22 +858,16 @@ in
config = {
assertions = [
# See comment above `homebrew.global.noLock` option declaration for why this is required.
{ assertion = cfg.global.noLock == null; message = "The option `homebrew.global.noLock' was removed, use `homebrew.global.lockfiles' in it's place."; }
];
warnings = [
(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'.")
(mkIf (cfg.global.noLock != null || cfg.global.lockfiles != null) "The options `homebrew.global.noLock' and `homebrew.global.lockfiles' have been deprecated. Homebrew Bundle removed lockfile support in Homebrew 4.4.0 (Oct 2024), so these options no longer have any effect. Please remove them from your configuration.")
(mkIf (hasSuffix "/bin" cfg.prefix) "`homebrew.prefix` should be the Homebrew prefix directory (e.g., `/opt/homebrew`), not the bin directory. The value should match what `brew --prefix` returns. Did you mean to remove the trailing `/bin`?")
];
system.requiresPrimaryUser = mkIf (cfg.enable && options.homebrew.user.highestPrio == (mkOptionDefault {}).priority) [
"homebrew.enable"
];
homebrew.brews =
optional (cfg.whalebrews != [ ]) "whalebrew";
homebrew.brewfile =
"# Created by `nix-darwin`'s `homebrew` module\n\n"
+ mkBrewfileSectionString "Taps" cfg.taps
@ -800,16 +877,72 @@ in
+ mkBrewfileSectionString "Casks" cfg.casks
+ mkBrewfileSectionString "Mac App Store apps"
(mapAttrsToList (n: id: ''mas "${n}", id: ${toString id}'') cfg.masApps)
+ mkBrewfileSectionString "Docker containers" (map (v: ''whalebrew "${v}"'') cfg.whalebrews)
+ mkBrewfileSectionString "Visual Studio Code extensions" (map (v: ''vscode "${v}"'') cfg.vscode)
+ mkBrewfileSectionString "Go packages" (map (v: ''go "${v}"'') cfg.goPackages)
+ mkBrewfileSectionString "Cargo packages" (map (v: ''cargo "${v}"'') cfg.cargoPackages)
+ optionalString (cfg.extraConfig != "") ("# Extra config\n" + cfg.extraConfig);
environment.variables = mkIf cfg.enable cfg.global.homebrewEnvironmentVariables;
programs = mkIf cfg.enable {
bash.interactiveShellInit = mkIf cfg.enableBashIntegration ''
eval "$(${cfg.prefix}/bin/brew shellenv bash)"
if [[ -r "${cfg.prefix}/etc/profile.d/bash_completion.sh" ]]; then
source "${cfg.prefix}/etc/profile.d/bash_completion.sh"
else
for COMPLETION in "${cfg.prefix}/etc/bash_completion.d/"*; do
[[ -r "$COMPLETION" ]] && source "$COMPLETION"
done
fi
'';
zsh.interactiveShellInit = mkIf cfg.enableZshIntegration ''
eval "$(${cfg.prefix}/bin/brew shellenv zsh)"
'';
fish.interactiveShellInit = mkIf cfg.enableFishIntegration ''
eval (${cfg.prefix}/bin/brew shellenv fish)
if test -d "${cfg.prefix}/share/fish/completions"
set -p fish_complete_path "${cfg.prefix}/share/fish/completions"
end
if test -d "${cfg.prefix}/share/fish/vendor_completions.d"
set -p fish_complete_path "${cfg.prefix}/share/fish/vendor_completions.d"
end
'';
};
system.checks.text = mkIf (cfg.enable && cfg.onActivation.cleanup == "check") ''
if [ -f "${cfg.prefix}/bin/brew" ]; then
homebrewCleanupExitCode=0
homebrewCleanupResult=$(PATH="${cfg.prefix}/bin:${lib.makeBinPath [ pkgs.mas ]}:$PATH" \
sudo \
--preserve-env=PATH \
--user=${escapeShellArg cfg.user} \
--set-home \
env HOMEBREW_NO_AUTO_UPDATE=1 \
brew bundle cleanup --file='${brewfileFile}' 2>&1) || homebrewCleanupExitCode=$?
if [ "$homebrewCleanupExitCode" -eq 1 ]; then
printf >&2 '\e[1;31merror: found Homebrew packages not listed in the Brewfile, aborting activation\e[0m\n'
printf >&2 '%s\n' "$homebrewCleanupResult"
printf >&2 '\n'
printf >&2 'To fix this, either:\n'
printf >&2 ' - Add the listed packages to your nix-darwin Homebrew configuration\n'
printf >&2 ' - Remove them by running: brew bundle cleanup --force\n'
printf >&2 ' - Set homebrew.onActivation.cleanup to "uninstall" or "zap"\n'
exit 2
elif [ "$homebrewCleanupExitCode" -ne 0 ]; then
printf >&2 '\e[1;31merror: brew bundle cleanup failed, aborting activation\e[0m\n'
printf >&2 '%s\n' "$homebrewCleanupResult"
exit 2
fi
fi
'';
system.activationScripts.homebrew.text = mkIf cfg.enable ''
# Homebrew Bundle
echo >&2 "Homebrew bundle..."
if [ -f "${cfg.brewPrefix}/brew" ]; then
PATH="${cfg.brewPrefix}:${lib.makeBinPath [ pkgs.mas ]}:$PATH" \
if [ -f "${cfg.prefix}/bin/brew" ]; then
PATH="${cfg.prefix}/bin:${lib.makeBinPath [ pkgs.mas ]}:$PATH" \
sudo \
--preserve-env=PATH \
--user=${escapeShellArg cfg.user} \
@ -821,4 +954,8 @@ in
fi
'';
};
meta.maintainers = [
lib.maintainers.malo or "malo"
];
}

View file

@ -757,6 +757,7 @@ in
"6bb8d6b0dd16b44ee793a9b8382dac76c926e4c16ffb8ddd2bb4884d1ca3f811" # DeterminateSystems Nix installer 0.34.0
"24797ac05542ff8b52910efc77870faa5f9e3275097227ea4e50c430a5f72916" # lix-installer 0.17.1 with flakes
"b027b5cad320b5b8123d9d0db9f815c3f3921596c26dc3c471457098e4d3cc40" # lix-installer 0.17.1 without flakes
"74ee0ae5ad21a1b101617685fd3d001f74a9466d9d763d92eb75b99cc740db91" # experimental official Nix installer 2.33.3
];
environment.etc."nix/registry.json".text = builtins.toJSON {
@ -881,10 +882,12 @@ in
# to express that we want it deleted and know only one hash?
system.activationScripts.checks.text = mkAfter ''
nixCustomConfKnownSha256Hashes=(
# v0.33.0
# DetSys v0.33.0
6787fade1cf934f82db554e78e1fc788705c2c5257fddf9b59bdd963ca6fec63
# v0.34.0
# DetSys v0.34.0
3bd68ef979a42070a44f8d82c205cfd8e8cca425d91253ec2c10a88179bb34aa
# Nix 2.33.3
71f7fdc9f6c9e55ca0f2e6f85137037d660b3224a34d59305e8530ca292bc734
)
if [[ -e /etc/nix/nix.custom.conf ]]; then
nixCustomConfSha256Output=$(shasum -a 256 /etc/nix/nix.custom.conf)

View file

@ -260,6 +260,7 @@ in
environment.etc."zshenv".knownSha256Hashes = [
"d07015be6875f134976fce84c6c7a77b512079c1c5f9594dfa65c70b7968b65f" # DeterminateSystems installer
"4e8f7cb9b699511f4ba5f9d5f8de1c9f5efb5c607de88faf5f58b8b9cb38edbf" # experimental official Nix installer 2.33.3
];
};

View file

@ -252,7 +252,7 @@ let
'';
homebrewInstalled = ''
if [[ ! -f ${escapeShellArg config.homebrew.brewPrefix}/brew && -z "''${INSTALLING_HOMEBREW:-}" ]]; then
if [[ ! -f ${escapeShellArg config.homebrew.prefix}/bin/brew && -z "''${INSTALLING_HOMEBREW:-}" ]]; then
echo "error: Using the homebrew module requires homebrew installed, aborting activation" >&2
echo "Homebrew doesn't seem to be installed. Please install homebrew separately." >&2
echo "You can install homebrew using the following command:" >&2

View file

@ -83,6 +83,8 @@ in {
tests.environment-path = makeTest ./tests/environment-path.nix;
tests.environment-terminfo = makeTest ./tests/environment-terminfo.nix;
tests.homebrew = makeTest ./tests/homebrew.nix;
tests.homebrew-cleanup-check = makeTest ./tests/homebrew-cleanup-check.nix;
tests.homebrew-shell-integration = makeTest ./tests/homebrew-shell-integration.nix;
tests.launchd-daemons = makeTest ./tests/launchd-daemons.nix;
tests.launchd-setenv = makeTest ./tests/launchd-setenv.nix;
tests.networking-firewall = makeTest ./tests/networking-firewall.nix;

View file

@ -0,0 +1,19 @@
{ config, ... }:
{
homebrew.enable = true;
homebrew.user = "test-homebrew-user";
homebrew.onActivation.cleanup = "check";
test = ''
echo "checking that cleanup check is present in system checks" >&2
grep 'brew bundle cleanup --file=' ${config.out}/activate
echo "checking that brew bundle command does not have --cleanup flag" >&2
if echo "${config.homebrew.onActivation.brewBundleCmd}" | grep -F -- '--cleanup' > /dev/null; then
echo "Expected no --cleanup flag in brewBundleCmd"
echo "Actual: ${config.homebrew.onActivation.brewBundleCmd}"
exit 1
fi
'';
}

View file

@ -0,0 +1,29 @@
{ config, ... }:
{
homebrew.enable = true;
programs.bash.enable = true;
programs.zsh.enable = true;
programs.fish.enable = true;
homebrew.user = "test-homebrew-user";
homebrew.enableBashIntegration = true;
homebrew.enableFishIntegration = true;
homebrew.enableZshIntegration = true;
test = ''
echo >&2 "checking bash shell integration in /etc/bashrc"
grep 'brew shellenv bash' ${config.out}/etc/bashrc
echo >&2 "checking bash completions in /etc/bashrc"
grep 'bash_completion' ${config.out}/etc/bashrc
echo >&2 "checking zsh shell integration in /etc/zshrc"
grep 'brew shellenv zsh' ${config.out}/etc/zshrc
echo >&2 "checking fish shell integration in /etc/fish/config.fish"
grep 'brew shellenv fish' ${config.out}/etc/fish/config.fish
echo >&2 "checking fish completions in /etc/fish/config.fish"
grep 'fish_complete_path' ${config.out}/etc/fish/config.fish
'';
}

View file

@ -17,7 +17,7 @@ in
homebrew.user = "test-homebrew-user";
# Examples taken from https://github.com/Homebrew/homebrew-bundle
# Examples adapted from https://docs.brew.sh/Brew-Bundle-and-Brewfile
homebrew.taps = [
"homebrew/cask"
{
@ -41,7 +41,8 @@ in
{
name = "denji/nginx/nginx-full";
args = [ "with-rmtp" ];
restart_service = "changed";
link = "overwrite";
restart_service = "always";
}
{
name = "mysql@5.6";
@ -49,6 +50,10 @@ in
link = true;
conflicts_with = [ "mysql" ];
}
{
name = "postgresql@16";
postinstall = "\${HOMEBREW_PREFIX}/opt/postgresql@16/bin/postgres -D \${HOMEBREW_PREFIX}/var/postgresql@16";
}
];
homebrew.casks = [
@ -61,6 +66,10 @@ in
name = "opera";
greedy = true;
}
{
name = "google-cloud-sdk";
postinstall = "\${HOMEBREW_PREFIX}/bin/gcloud components update";
}
];
homebrew.masApps = {
@ -68,10 +77,19 @@ in
Xcode = 497799835;
};
homebrew.whalebrews = [
"whalebrew/wget"
homebrew.vscode = [
"golang.go"
];
homebrew.goPackages = [
"github.com/charmbracelet/crush"
];
homebrew.cargoPackages = [
"ripgrep"
];
test = ''
bf=${lib.escapeShellArg config.homebrew.brewfile}
@ -85,19 +103,33 @@ in
echo "checking brew entries in Brewfile" >&2
${mkTest "imagemagick" ''brew "imagemagick"''}
${mkTest "denji/nginx/nginx-full" ''brew "denji/nginx/nginx-full", args: ["with-rmtp"], restart_service: :changed''}
${mkTest "denji/nginx/nginx-full" ''brew "denji/nginx/nginx-full", args: ["with-rmtp"], link: :overwrite, restart_service: :always''}
${mkTest "mysql@5.6" ''brew "mysql@5.6", conflicts_with: ["mysql"], link: true, restart_service: true''}
${mkTest "postgresql@16" ''brew "postgresql@16", postinstall: "''${HOMEBREW_PREFIX}/opt/postgresql@16/bin/postgres -D ''${HOMEBREW_PREFIX}/var/postgresql@16"''}
echo "checking cask entries in Brewfile" >&2
${mkTest "google-chrome" ''cask "google-chrome"''}
${mkTest "firefox" ''cask "firefox", args: { appdir: "~/my-apps/Applications" }''}
${mkTest "opera" ''cask "opera", greedy: true''}
${mkTest "google-cloud-sdk" ''cask "google-cloud-sdk", postinstall: "''${HOMEBREW_PREFIX}/bin/gcloud components update"''}
echo "checking mas entries in Brewfile" >&2
${mkTest "1Password for Safari" ''mas "1Password for Safari", id: 1569813296''}
${mkTest "Xcode" ''mas "Xcode", id: 497799835''}
echo "checking whalebrew entries in Brewfile" >&2
${mkTest "whalebrew/wget" ''whalebrew "whalebrew/wget"''}
echo "checking vscode entries in Brewfile" >&2
${mkTest "golang.go" ''vscode "golang.go"''}
echo "checking go entries in Brewfile" >&2
${mkTest "github.com/charmbracelet/crush" ''go "github.com/charmbracelet/crush"''}
echo "checking cargo entries in Brewfile" >&2
${mkTest "ripgrep" ''cargo "ripgrep"''}
echo "checking that shell integration is absent by default" >&2
(! grep 'brew shellenv' ${config.out}/etc/zshrc)
echo "checking that cleanup check is absent by default" >&2
(! grep 'brew bundle cleanup --file=' ${config.out}/activate)
'';
}