macOS

This is my nix-darwin configuration for macOS (Apple Silicon).

Purpose

This configuration manages my work MacBook, providing a declarative way to configure macOS system settings and install applications via Homebrew.

Hardware

  • Model: MacBook Pro (Mac16,5 / MX2V3LL/A)
  • Chip: Apple M4 Max (14 cores (10 performance + 4 efficiency))
  • Memory: 36 GB
  • Display: Built-in Liquid Retina XDR (3456 x 2234 Retina)
  • macOS: 26.5 (build 25F71)

System Defaults

macOS-specific settings for a better developer experience:

{ pkgs, lib, config, ... }:
{
  system.defaults = {
    # Dock
    dock = {
      autohide = true;
      show-recents = false;
      tilesize = 48;
      orientation = "left";
      wvous-br-corner = 14; # quick note
    };

    # Finder
    finder = {
      AppleShowAllExtensions = true;
      ShowPathbar = true;
      ShowStatusBar = true;
      FXEnableExtensionChangeWarning = false;
      FXPreferredViewStyle = "Nlsv"; # list view
      _FXShowPosixPathInTitle = true;
      _FXSortFoldersFirst = true;
      AppleShowAllFiles = true;
      QuitMenuItem = true;
      NewWindowTarget = "Other";
      NewWindowTargetPath = "file:///Users/gabriel.morin/";
    };

    # Screenshots
    screencapture = {
      location = "~/Pictures/Screenshots";
      type = "png";
      disable-shadow = true;
    };

    # Login
    loginwindow = {
      GuestEnabled = false;
      DisableConsoleAccess = true;
    };

    # Menu bar clock
    menuExtraClock = {
      ShowAMPM = true;
      ShowDate = 0; # when space allows
      ShowDayOfWeek = true;
    };

    # Global settings
    NSGlobalDomain = {
      AppleShowAllExtensions = true;
      AppleInterfaceStyle = "Dark";
      InitialKeyRepeat = 15;
      KeyRepeat = 2;
      NSAutomaticCapitalizationEnabled = false;
      NSAutomaticSpellingCorrectionEnabled = false;
      NSAutomaticDashSubstitutionEnabled = false;
      NSAutomaticQuoteSubstitutionEnabled = false;
      NSAutomaticPeriodSubstitutionEnabled = false;
      NSNavPanelExpandedStateForSaveMode = true;
      NSNavPanelExpandedStateForSaveMode2 = true;
      PMPrintingExpandedStateForPrint = true;
      PMPrintingExpandedStateForPrint2 = true;
    };

    # Trackpad
    trackpad = {
      Clicking = true;
      TrackpadRightClick = true;
    };

    # Show hidden files in NSOpenPanel/NSSavePanel (IDE open dialogs, etc.)
    CustomUserPreferences = {
      "NSGlobalDomain" = {
        AppleShowAllFiles = true;
      };
    };
  };
  
  fonts.packages = with pkgs.nerd-fonts; [
    hack
    jetbrains-mono
  ];

  security.pam.services.sudo_local.touchIdAuth = true;
  system.primaryUser = "gabriel.morin";
  networking.hostName = "LNEGQ7JJR7FWG";
  networking.computerName = "LNEGQ7JJR7FWG";
  system.stateVersion = 6;

  # Skip the userDefaults activation step (~20-25s of `launchctl asuser sudo`
  # PAM overhead per switch) when system.defaults hasn't changed. Fingerprint
  # is the store path of a derivation containing the serialized config; a
  # marker file at /var/lib/nix-darwin/userDefaults.fingerprint records the
  # last applied path. When they match, the block is skipped.
  system.activationScripts.userDefaults.text = lib.mkMerge [
    (lib.mkBefore ''
      __userDefaultsFingerprint=${pkgs.writeText "user-defaults.json"
        (builtins.toJSON {
          inherit (config.system.defaults)
            dock finder screencapture loginwindow menuExtraClock
            NSGlobalDomain trackpad CustomUserPreferences;
        })}
      __userDefaultsMarker=/var/lib/nix-darwin/userDefaults.fingerprint
      if [ "$(readlink "$__userDefaultsMarker" 2>/dev/null)" = "$__userDefaultsFingerprint" ]; then
        echo >&2 "user defaults: unchanged, skipping"
      else
    '')
    (lib.mkAfter ''
        mkdir -p "$(dirname "$__userDefaultsMarker")"
        ln -sfn "$__userDefaultsFingerprint" "$__userDefaultsMarker"
      fi
    '')
  ];
}

Homebrew

Since many macOS applications aren’t available in nixpkgs, Homebrew is used for GUI apps:

{ lib, config, primaryUser, ... }:
let
  customConfigPath = ./_homebrew-tm.nix;
  hasCustomConfig = builtins.pathExists customConfigPath;
  customConfig = if hasCustomConfig then (import customConfigPath { inherit lib; }) else { homebrew = {}; };
  trustEntry = tap:
    if (tap.clone_target or null) != null then tap.clone_target
    else lib.toLower tap.name;
  trustedTaps = lib.sort (a: b: a < b) (map trustEntry config.homebrew.taps);
  trustJson = builtins.toJSON { trustedtaps = trustedTaps; };
  trustFile = "/Users/${primaryUser}/.homebrew/trust.json";
in
{
  homebrew = {
    enable = true;
    onActivation = {
      autoUpdate = true;
      cleanup = "zap";
      upgrade = true;
      extraFlags = [ "--verbose" ];
    };

    taps = [
      "openfga/tap"
      "databricks/tap"
      "LizardByte/homebrew"
      "peonping/tap"
      "libkrun/krun"
    ] ++ (customConfig.homebrew.taps or []);

    brews = [
      "awscli"
      "databricks/tap/databricks"
      "pulumi"
      "openfga/tap/fga"
      "kafka"
      "ttyd"
      "LizardByte/homebrew/sunshine"
      "PeonPing/tap/peon-ping"
      "jenv"
      "podman"
      "libkrun/krun/krunkit"
    ] ++ (customConfig.homebrew.brews or []);

    # GUI Applications (casks)
    casks = [
      # Terminals
      "ghostty"
      "iterm2"
      "wezterm"

      # Editors & IDEs
      "visual-studio-code"
      "jetbrains-toolbox"

      # Browsers & Communication
      "discord"
      "spotify"

      # Development Tools
      "podman-desktop"
      "dbeaver-community"
      "meld"
      "hoppscotch"
      "jdk-mission-control"
      "zulu@17"
      "zulu@21"

      # System Utilities
      "amethyst"
      "scroll-reverser"
      "stats"

      # Media
      #"gimp"
      "blackhole-2ch"

      # Kubernetes
      "freelens"
    ] ++ (customConfig.homebrew.casks or []);
  };

  system.activationScripts.preActivation.text = lib.mkAfter ''
    echo >&2 "homebrew: writing tap trust file..."
    /usr/bin/install -d -o ${primaryUser} -m 700 "/Users/${primaryUser}/.homebrew"
    printf '%s' ${lib.escapeShellArg trustJson} > "${trustFile}"
    if [ -s "${config.sops.secrets.gitlab_host.path}" ]; then
      /usr/bin/sed -i "" "s|REDACTED_URL|$(cat "${config.sops.secrets.gitlab_host.path}")|g" "${trustFile}"
    else
      echo >&2 "homebrew: WARNING gitlab_host secret not yet provisioned; tm tap trust will use placeholder URL"
    fi
    /usr/sbin/chown ${primaryUser} "${trustFile}"
    chmod 600 "${trustFile}"
  '';
}

Differences from NixOS

Unlike the NixOS hosts, macOS uses:

  • nix-darwin instead of NixOS modules
  • Homebrew for GUI applications (casks)
  • Different system defaults API