diff --git a/flake.nix b/flake.nix index ad5221c..9aadbc4 100644 --- a/flake.nix +++ b/flake.nix @@ -11,26 +11,34 @@ outputs = inputs@{ self, nix-darwin, nixpkgs }: let - configuration = { pkgs, ... }: { + configuration = { pkgs, lib, inputs, ... }: { # List packages installed in system profile. To search by name, run: # $ nix-env -qaP | grep wget environment.systemPackages = [ pkgs.vim ]; - + nix.extraOptions = '' extra-platforms = x86_64-darwin aarch64-darwin ''; # Auto upgrade nix package and the daemon service. services.nix-daemon.enable = true; - services.tailscale = { - enable = true; + services = { + tailscale = { + enable = true; + }; }; - security.pam.enableSudoTouchIdAuth = true; + # Enable the darwin security.pam module for sudo Touch ID authentication. + security = { + pam = { + enableSudoTouchIdAuth = true; + }; + }; - # nix.package = pkgs.nix; + # Enable the nix-darwin module system. + nix.package = pkgs.nix; # Necessary for using flakes on this system. nix.settings.experimental-features = "nix-command flakes"; @@ -47,15 +55,26 @@ system.configurationRevision = self.rev or self.dirtyRev or null; system.defaults = { - dock.autohide = true; - dock.mru-spaces = false; - finder.AppleShowAllExtensions = true; - finder.FXPreferredViewStyle = "clmv"; + dock = { + autohide = true; + mru-spaces = false; + }; + finder = { + AppleShowAllFiles = true; + AppleShowAllExtensions = true; + FXPreferredViewStyle = "clmv"; + }; loginwindow.LoginwindowText = "swaphb-mba"; screencapture.location = "~/Pictures/screenshots"; screensaver.askForPasswordDelay = 10; + trackpad = { + # Click = "click"; + # DragLock = true; + # Dragging = true; + TrackpadThreeFingerDrag = true; + FirstClickThreshold = 1; + }; }; - system.defaults.trackpad.TrackpadThreeFingerDrag = true; # Used for backwards compatibility, please read the changelog before changing. # $ darwin-rebuild changelog system.stateVersion = 4; @@ -86,7 +105,9 @@ # Build darwin flake using: # $ darwin-rebuild build --flake .#swaphb-mba darwinConfigurations."swaphb-mba" = nix-darwin.lib.darwinSystem { - modules = [ configuration ]; + modules = [ + configuration + ]; }; # Expose the package set, including overlays, for convenience. diff --git a/modules/homebrew/default.nix b/modules/homebrew/default.nix new file mode 100644 index 0000000..993dcd8 --- /dev/null +++ b/modules/homebrew/default.nix @@ -0,0 +1,795 @@ +# Created by: https://github.com/malob +{ config, lib, options, pkgs, ... }: + +with lib; + +let + cfg = config.homebrew; + + brewfileFile = pkgs.writeText "Brewfile" cfg.brewfile; + + # Brewfile creation helper functions ------------------------------------------------------------- + + mkBrewfileSectionString = heading: entries: optionalString (entries != [ ]) '' + # ${heading} + ${concatMapStringsSep "\n" (v: v.brewfileLine or v) entries} + + ''; + + mkBrewfileLineValueString = v: + if isInt v then toString v + else if isFloat v then strings.floatToString v + else if isBool v then boolToString v + else if isString v then ''"${v}"'' + else if isAttrs v then "{ ${concatStringsSep ", " (mapAttrsToList (n: v': "${n}: ${mkBrewfileLineValueString v'}") v)} }" + else if isList v then "[${concatMapStringsSep ", " mkBrewfileLineValueString v}]" + else abort "The value: ${generators.toPretty v} is not a valid Brewfile value."; + + mkBrewfileLineOptionsListString = attrs: + concatStringsSep ", " (mapAttrsToList (n: v: "${n}: ${v}") attrs); + + + # Option and submodule helper functions ---------------------------------------------------------- + + mkNullOrBoolOption = args: mkOption (args // { + type = types.nullOr types.bool; + default = null; + }); + + mkNullOrStrOption = args: mkOption (args // { + type = types.nullOr types.str; + default = null; + }); + + mkInternalOption = args: mkOption (args // { + visible = false; + internal = true; + readOnly = true; + }); + + mkProcessedSubmodConfig = attrs: mapAttrs (_: mkBrewfileLineValueString) + (filterAttrsRecursive (n: v: n != "_module" && n != "brewfileLine" && v != null) attrs); + + + # Submodules ------------------------------------------------------------------------------------- + # Option values and descriptions of Brewfile entries are sourced/derived from: + # * `brew` manpage: https://docs.brew.sh/Manpage + # * `brew bundle` source files (at https://github.com/Homebrew/homebrew-bundle/tree/9fffe077f1a5a722ed5bd26a87ed622e8cb64e0c): + # * lib/bundle/dsl.rb + # * lib/bundle/{brew,cask,tap}_installer.rb + # * spec/bundle/{brew,cask,tap}_installer_spec.rb + + onActivationOptions = { config, ... }: { + options = { + cleanup = mkOption { + type = types.enum [ "none" "uninstall" "zap" ]; + default = "none"; + example = "uninstall"; + description = '' + This option manages what happens to formulae 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 + Brewfile are left installed. + + 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. + + 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. + + If you plan on exclusively using {command}`nix-darwin` to manage formulae + installed by Homebrew, you probably want to set this option to + `"uninstall"` or `"zap"`. + ''; + }; + autoUpdate = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable Homebrew to auto-update itself and all formulae during + {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. + + 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 + behavior see [](#opt-homebrew.global.autoUpdate). + + Implementation note: when disabled, this option sets the `HOMEBREW_NO_AUTO_UPDATE` + environment variable when {command}`nix-darwin` invokes {command}`brew bundle [install]` + during system activation. + ''; + }; + upgrade = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable Homebrew to upgrade outdated formulae and Mac App Store apps during + {command}`nix-darwin` system activation. The default is `false` + so that repeated invocations of {command}`darwin-rebuild switch` are idempotent. + + Implementation note: when disabled, {command}`nix-darwin` invokes + {command}`brew bundle [install]` with the {command}`--no-upgrade` flag during system + activation. + ''; + }; + extraFlags = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "--verbose" ]; + description = '' + Extra flags to pass to {command}`brew bundle [install]` during {command}`nix-darwin` + system activation. + ''; + }; + + brewBundleCmd = mkInternalOption { type = types.str; }; + }; + + config = { + brewBundleCmd = concatStringsSep " " ( + optional (!config.autoUpdate) "HOMEBREW_NO_AUTO_UPDATE=1" + ++ [ "brew bundle --file='${brewfileFile}' --no-lock" ] + ++ optional (!config.upgrade) "--no-upgrade" + ++ optional (config.cleanup == "uninstall") "--cleanup" + ++ optional (config.cleanup == "zap") "--cleanup --zap" + ++ config.extraFlags + ); + }; + }; + + globalOptions = { config, ... }: { + options = { + brewfile = mkOption { + type = types.bool; + default = false; + description = '' + 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 + [](#opt-environment.variables). + ''; + }; + autoUpdate = mkOption { + type = types.bool; + default = true; + description = '' + Whether to enable Homebrew to auto-update itself and all formulae when you manually invoke + commands like {command}`brew install`, {command}`brew upgrade`, + {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. + + You may want to consider disabling this option if you have + [](#opt-homebrew.onActivation.upgrade) enabled, and + [](#opt-homebrew.onActivation.autoUpdate) disabled, if you want to ensure that + your installed formulae will only be upgraded during {command}`nix-darwin` system + activation, after you've explicitly run {command}`brew update`. + + Implementation note: when disabled, this option sets the + `HOMEBREW_NO_AUTO_UPDATE` environment variable, by adding it to + [](#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 = mkOption { visible = false; default = null; }; + + homebrewEnvironmentVariables = mkInternalOption { type = types.attrs; }; + }; + + config = { + homebrewEnvironmentVariables = { + HOMEBREW_BUNDLE_FILE = mkIf config.brewfile "${brewfileFile}"; + HOMEBREW_NO_AUTO_UPDATE = mkIf (!config.autoUpdate) "1"; + HOMEBREW_BUNDLE_NO_LOCK = mkIf (!config.lockfiles) "1"; + }; + }; + }; + + tapOptions = { config, ... }: { + options = { + name = mkOption { + type = types.str; + example = "homebrew/cask-fonts"; + description = '' + When {option}`clone_target` is unspecified, this is the name of a formula + repository to tap from GitHub using HTTPS. For example, `"user/repo"` + will tap https://github.com/user/homebrew-repo. + ''; + }; + clone_target = mkNullOrStrOption { + description = '' + Use this option to tap a formula repository from anywhere, using any transport protocol + that {command}`git` handles. When {option}`clone_target` is specified, taps + can be cloned from places other than GitHub and using protocols other than HTTPS, e.g., + SSH, git, HTTP, FTP(S), rsync. + ''; + }; + force_auto_update = mkNullOrBoolOption { + 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). + ''; + }; + + brewfileLine = mkInternalOption { type = types.nullOr types.str; }; + }; + + config = + let + sCfg = mkProcessedSubmodConfig config; + in + { + brewfileLine = + "tap ${sCfg.name}" + + optionalString (sCfg ? clone_target) ", ${sCfg.clone_target}" + + optionalString (sCfg ? force_auto_update) + ", force_auto_update: ${sCfg.force_auto_update}"; + }; + }; + + # Sourced from https://docs.brew.sh/Manpage#global-cask-options + # and valid values for `HOMEBREW_CASK_OPTS`. + caskArgsOptions = { config, ... }: { + options = { + appdir = mkNullOrStrOption { + description = '' + Target location for Applications. + + Homebrew's default is {file}`/Applications`. + ''; + }; + colorpickerdir = mkNullOrStrOption { + description = '' + Target location for Color Pickers. + + Homebrew's default is {file}`~/Library/ColorPickers`. + ''; + }; + prefpanedir = mkNullOrStrOption { + description = '' + Target location for Preference Panes. + + Homebrew's default is {file}`~/Library/PreferencePanes`. + ''; + }; + qlplugindir = mkNullOrStrOption { + description = '' + Target location for QuickLook Plugins. + + Homebrew's default is {file}`~/Library/QuickLook`. + ''; + }; + mdimporterdir = mkNullOrStrOption { + description = '' + Target location for Spotlight Plugins. + + Homebrew's default is {file}`~/Library/Spotlight`. + ''; + }; + dictionarydir = mkNullOrStrOption { + description = '' + Target location for Dictionaries. + + Homebrew's default is {file}`~/Library/Dictionaries`. + ''; + }; + fontdir = mkNullOrStrOption { + description = '' + Target location for Fonts. + + Homebrew's default is {file}`~/Library/Fonts`. + ''; + }; + servicedir = mkNullOrStrOption { + description = '' + Target location for Services. + + Homebrew's default is {file}`~/Library/Services`. + ''; + }; + input_methoddir = mkNullOrStrOption { + description = '' + Target location for Input Methods. + + Homebrew's default is {file}`~/Library/Input Methods`. + ''; + }; + internet_plugindir = mkNullOrStrOption { + description = '' + Target location for Internet Plugins. + + Homebrew's default is {file}`~/Library/Internet Plug-Ins`. + ''; + }; + audio_unit_plugindir = mkNullOrStrOption { + description = '' + Target location for Audio Unit Plugins. + + Homebrew's default is + {file}`~/Library/Audio/Plug-Ins/Components`. + ''; + }; + vst_plugindir = mkNullOrStrOption { + description = '' + Target location for VST Plugins. + + Homebrew's default is {file}`~/Library/Audio/Plug-Ins/VST`. + ''; + }; + vst3_plugindir = mkNullOrStrOption { + description = '' + Target location for VST3 Plugins. + + Homebrew's default is {file}`~/Library/Audio/Plug-Ins/VST3`. + ''; + }; + screen_saverdir = mkNullOrStrOption { + description = '' + Target location for Screen Savers. + + Homebrew's default is {file}`~/Library/Screen Savers`. + ''; + }; + language = mkNullOrStrOption { + description = '' + Comma-separated list of language codes to prefer for cask installation. The first matching + language is used, otherwise it reverts to the cask’s default language. The default value + is the language of your system. + ''; + example = "zh-TW"; + }; + require_sha = mkNullOrBoolOption { + description = '' + Whether to require cask(s) to have a checksum. + + Homebrew's default is `false`. + ''; + }; + no_quarantine = mkNullOrBoolOption { + description = "Whether to disable quarantining of downloads."; + }; + no_binaries = mkNullOrBoolOption { + description = "Whether to disable linking of helper executables."; + }; + + brewfileLine = mkInternalOption { type = types.nullOr types.str; }; + }; + + config = + let + sCfg = mkProcessedSubmodConfig config; + in + { + brewfileLine = + if sCfg == { } then null + else "cask_args ${mkBrewfileLineOptionsListString sCfg}"; + }; + }; + + brewOptions = { config, ... }: { + options = { + name = mkOption { + type = types.str; + description = "The name of the formula to install."; + }; + args = mkOption { + type = with types; nullOr (listOf str); + default = null; + description = '' + Arguments flags to pass to {command}`brew install`. Values should not include the + leading `"--"`. + ''; + }; + conflicts_with = mkOption { + type = with types; nullOr (listOf str); + default = null; + description = '' + List of formulae that should be unlinked and their services stopped (if they are + installed). + ''; + }; + restart_service = mkOption { + type = with types; nullOr (either bool (enum [ "changed" ])); + 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. + + Homebrew's default is `false`. + ''; + }; + start_service = mkNullOrBoolOption { + description = '' + Whether to run {command}`brew services start` for the formula and register it to + launch at login (or boot). + + Homebrew's default is `false`. + ''; + }; + link = mkNullOrBoolOption { + 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. + ''; + }; + + brewfileLine = mkInternalOption { type = types.nullOr types.str; }; + }; + + config = + let + sCfg = mkProcessedSubmodConfig config; + sCfgSubset = removeAttrs sCfg [ "name" "restart_service" ]; + 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}" + ) + ); + }; + }; + + caskOptions = { config, ... }: { + options = { + name = mkOption { + type = types.str; + description = "The name of the cask to install."; + }; + args = mkOption { + type = types.nullOr (types.submodule caskArgsOptions); + default = null; + visible = "shallow"; # so that options from `homebrew.caskArgs` aren't repeated. + description = '' + Arguments passed to {command}`brew install --cask` when installing this cask. See + [](#opt-homebrew.caskArgs) for the available options. + ''; + }; + greedy = mkNullOrBoolOption { + description = '' + Whether to always upgrade this cask regardless of whether it's unversioned or it updates + itself. + ''; + }; + + brewfileLine = mkInternalOption { type = types.nullOr types.str; }; + }; + + config = + let + sCfg = mkProcessedSubmodConfig config; + sCfgSubset = removeAttrs sCfg [ "name" ]; + in + { + brewfileLine = + "cask ${sCfg.name}" + + optionalString (sCfgSubset != { }) ", ${mkBrewfileLineOptionsListString sCfgSubset}"; + }; + }; +in + +{ + # Interface -------------------------------------------------------------------------------------- + + imports = [ + (mkRenamedOptionModule [ "homebrew" "autoUpdate" ] [ "homebrew" "onActivation" "autoUpdate" ]) + (mkRenamedOptionModule [ "homebrew" "cleanup" ] [ "homebrew" "onActivation" "cleanup" ]) + ]; + + 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. + + 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.caskArgs) options) to generate a Brewfile that + {command}`nix-darwin` passes to the {command}`brew bundle` command during + system activation. + + The default configuration of this module prevents Homebrew Bundle from auto-updating Homebrew + and all formulae, as well as upgrading anything that's already installed, so that repeated + invocations of {command}`darwin-rebuild switch` (without any change to the + configuration) are idempotent. You can modify this behavior using the options under + [](#opt-homebrew.onActivation). + + This module also provides a few options for modifying how Homebrew commands behave when + you manually invoke them, under [](#opt-homebrew.global)''; + + brewPrefix = mkOption { + type = types.str; + default = if pkgs.stdenv.hostPlatform.isAarch64 then "/opt/homebrew/bin" else "/usr/local/bin"; + defaultText = literalExpression '' + if pkgs.stdenv.hostPlatform.isAarch64 then "/opt/homebrew/bin" + else "/usr/local/bin" + ''; + 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. + ''; + }; + + onActivation = mkOption { + type = types.submodule onActivationOptions; + default = { }; + description = '' + Options for configuring the behavior of the {command}`brew bundle` command that + {command}`nix-darwin` runs during system activation. + ''; + }; + + global = mkOption { + type = types.submodule globalOptions; + default = { }; + description = '' + Options for configuring the behavior of Homebrew commands when you manually invoke them. + ''; + }; + + taps = mkOption { + type = with types; listOf (coercedTo str (name: { inherit name; }) (submodule tapOptions)); + default = [ ]; + example = literalExpression '' + # Adapted examples from https://github.com/Homebrew/homebrew-bundle#usage + [ + # `brew tap` + "homebrew/cask" + + # `brew tap` with custom Git URL and arguments + { + name = "user/tap-repo"; + clone_target = "https://user@bitbucket.org/user/homebrew-tap-repo.git"; + force_auto_update = true; + } + ] + ''; + description = '' + List of Homebrew formula repositories to tap. + + Taps defined as strings, e.g., `"user/repo"`, are a shorthand for: + + `{ name = "user/repo"; }` + ''; + }; + + caskArgs = mkOption { + type = types.submodule caskArgsOptions; + default = { }; + example = literalExpression '' + { + appdir = "~/Applications"; + require_sha = true; + } + ''; + description = '' + Arguments passed to {command}`brew install --cask` for all casks listed in + [](#opt-homebrew.casks). + ''; + }; + + brews = mkOption { + type = with types; listOf (coercedTo str (name: { inherit name; }) (submodule brewOptions)); + default = [ ]; + example = literalExpression '' + # Adapted examples from https://github.com/Homebrew/homebrew-bundle#usage + [ + # `brew install` + "imagemagick" + + # `brew install --with-rmtp`, `brew services restart` on version changes + { + name = "denji/nginx/nginx-full"; + args = [ "with-rmtp" ]; + restart_service = "changed"; + } + + # `brew install`, always `brew services restart`, `brew link`, `brew unlink mysql` (if it is installed) + { + name = "mysql@5.6"; + restart_service = true; + link = true; + conflicts_with = [ "mysql" ]; + } + ] + ''; + description = '' + List of Homebrew formulae to install. + + Formulae defined as strings, e.g., `"imagemagick"`, are a shorthand for: + + `{ name = "imagemagick"; }` + ''; + }; + + casks = mkOption { + type = with types; listOf (coercedTo str (name: { inherit name; }) (submodule caskOptions)); + default = [ ]; + example = literalExpression '' + # Adapted examples from https://github.com/Homebrew/homebrew-bundle#usage + [ + # `brew install --cask` + "google-chrome" + + # `brew install --cask --appdir=~/my-apps/Applications` + { + name = "firefox"; + args = { appdir = "~/my-apps/Applications"; }; + } + + # always upgrade auto-updated or unversioned cask to latest version even if already installed + { + name = "opera"; + greedy = true; + } + ] + ''; + description = '' + List of Homebrew casks to install. + + Casks defined as strings, e.g., `"google-chrome"`, are a shorthand for: + + `{ name = "google-chrome"; }` + ''; + }; + + masApps = mkOption { + type = types.attrsOf types.ints.positive; + default = { }; + example = literalExpression '' + { + "1Password for Safari" = 1569813296; + Xcode = 497799835; + } + ''; + description = '' + Applications to install from Mac App Store using {command}`mas`. + + When this option is used, `"mas"` is automatically added to + [](#opt-homebrew.brews). + + Note that you need to be signed into the Mac App Store for {command}`mas` to + successfully install and upgrade applications, and that unfortunately apps removed from this + option will not be uninstalled automatically even if + [](#opt-homebrew.onActivation.cleanup) is set to `"uninstall"` + or `"zap"` (this is currently a limitation of Homebrew Bundle). + + For more information on {command}`mas` see: + [github.com/mas-cli/mas](https://github.com/mas-cli/mas). + ''; + }; + + whalebrews = mkOption { + type = with types; listOf str; + default = [ ]; + example = [ "whalebrew/wget" ]; + description = '' + List of Docker images to install using {command}`whalebrew`. + + When this option is used, `"whalebrew"` is automatically added to + [](#opt-homebrew.brews). + + For more information on {command}`whalebrew` see: + [github.com/whalebrew/whalebrew](https://github.com/whalebrew/whalebrew). + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + example = '' + # 'brew cask install' only if '/usr/libexec/java_home --failfast' fails + cask "java" unless system "/usr/libexec/java_home --failfast" + ''; + description = "Extra lines to be added verbatim to the bottom of the generated Brewfile."; + }; + + brewfile = mkInternalOption { + type = types.str; + description = "String reprensentation of the generated Brewfile useful for debugging."; + }; + }; + + + # Implementation --------------------------------------------------------------------------------- + + 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'.") + ]; + + homebrew.brews = + optional (cfg.masApps != { }) "mas" + ++ optional (cfg.whalebrews != [ ]) "whalebrew"; + + homebrew.brewfile = + "# Created by `nix-darwin`'s `homebrew` module\n\n" + + mkBrewfileSectionString "Taps" cfg.taps + + mkBrewfileSectionString "Arguments for all casks" + (optional (cfg.caskArgs.brewfileLine != null) cfg.caskArgs) + + mkBrewfileSectionString "Brews" cfg.brews + + 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) + + optionalString (cfg.extraConfig != "") ("# Extra config\n" + cfg.extraConfig); + + environment.variables = mkIf cfg.enable cfg.global.homebrewEnvironmentVariables; + + system.activationScripts.homebrew.text = mkIf cfg.enable '' + # Homebrew Bundle + echo >&2 "Homebrew bundle..." + if [ -f "${cfg.brewPrefix}/brew" ]; then + PATH="${cfg.brewPrefix}":$PATH ${cfg.onActivation.brewBundleCmd} + else + echo -e "\e[1;31merror: Homebrew is not installed, skipping...\e[0m" >&2 + fi + ''; + }; +} \ No newline at end of file