My nix-darwin and NixOS config
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: streamline macmini config, add Ghostty, fix SSH/age setup

- Remove redundant media codec brews (covered by ffmpeg in nix packages)
- Remove gaming casks (epic-games, roblox, ea) and libreoffice
- Remove Steam Link (requires Rosetta 2) and OP Auto Clicker from MAS
- Add Ghostty as declarative terminal across all desktop hosts
- Replace Terminal.app with Ghostty in Dock
- Fix SSH keychain launchd agent flag (--apple-use-keychain)
- Update macmini SSH public key
- Update sops ewan age key
- Add verify-ssh-external script for GitHub, Forgejo, Tangled
- Fix check-secrets.sh to not require git and handle dotenv format
- Suppress home-manager manual warnings (HM issue #7935)
- Ensure nix-config is always a git repo with correct remotes on activation

+176 -52
+1 -1
.sops.yaml
··· 16 16 17 17 keys: 18 18 # User key — always included so secrets can be edited from any machine. 19 - - &ewan age1xl8ptkqm03skrdadqgprnez3trrc0k9t0ex052lweewqre2zc9qq7ljm3z 19 + - &ewan age17ulnk7akn9zfwtc87vsexrr809xj6gkkcp2rkez6xtzyrqclpshqfew5wy 20 20 21 21 # Host keys — derived from /etc/ssh/ssh_host_ed25519_key on each machine. 22 22 # sops-nix decrypts at activation time using the host's own key.
+45
home/default.nix
··· 41 41 ] 42 42 ++ lib.optionals (cfg.isDesktop) [ 43 43 ./programs/starship.nix 44 + ./programs/ghostty.nix 44 45 ] 45 46 ++ [ 46 47 ./programs/fastfetch.nix ··· 93 94 }; 94 95 95 96 programs.home-manager.enable = true; 97 + 98 + # Disable the home-manager manual — avoids a known upstream warning about 99 + # options.json referencing store paths without proper context (HM issue #7935). 100 + manual.manpages.enable = false; 101 + manual.html.enable = false; 102 + manual.json.enable = false; 103 + 104 + # ── nix-config git repo ──────────────────────────────────────────────────── 105 + # Ensures ~/.config/nix-config is always a git repo with the correct remotes. 106 + home.activation.nixConfigGitRepo = lib.hm.dag.entryAfter [ "writeBoundary" ] '' 107 + nix_config="${config.home.homeDirectory}/.config/nix-config" 108 + github_remote="git@github.com:ewanc26/nix" 109 + tangled_remote="git@tangled.org:ewancroft.uk/nix" 110 + 111 + if [ ! -d "$nix_config" ]; then 112 + echo "nix-config: directory not found, skipping git setup" 113 + else 114 + cd "$nix_config" 115 + 116 + if [ ! -d ".git" ]; then 117 + $DRY_RUN_CMD ${pkgs.git}/bin/git init 118 + $DRY_RUN_CMD ${pkgs.git}/bin/git checkout -b main 119 + echo "nix-config: initialised git repo" 120 + fi 121 + 122 + current_origin=$(${pkgs.git}/bin/git remote get-url origin 2>/dev/null || echo "") 123 + if [ -z "$current_origin" ]; then 124 + $DRY_RUN_CMD ${pkgs.git}/bin/git remote add origin "$github_remote" 125 + echo "nix-config: added origin -> $github_remote" 126 + elif [ "$current_origin" != "$github_remote" ]; then 127 + $DRY_RUN_CMD ${pkgs.git}/bin/git remote set-url origin "$github_remote" 128 + echo "nix-config: updated origin -> $github_remote" 129 + fi 130 + 131 + current_tangled=$(${pkgs.git}/bin/git remote get-url tangled 2>/dev/null || echo "") 132 + if [ -z "$current_tangled" ]; then 133 + $DRY_RUN_CMD ${pkgs.git}/bin/git remote add tangled "$tangled_remote" 134 + echo "nix-config: added tangled -> $tangled_remote" 135 + elif [ "$current_tangled" != "$tangled_remote" ]; then 136 + $DRY_RUN_CMD ${pkgs.git}/bin/git remote set-url tangled "$tangled_remote" 137 + echo "nix-config: updated tangled -> $tangled_remote" 138 + fi 139 + fi 140 + ''; 96 141 97 142 # ── Nextcloud desktop client ───────────────────────────────────────────── 98 143 # Both activation scripts below patch nextcloud.cfg in-place so that
+49
home/programs/ghostty.nix
··· 1 + # Ghostty terminal — all desktop hosts (Darwin + Linux). 2 + # On Darwin, uses the pre-built ghostty-bin package (avoids building from 3 + # source / Xcode requirement). On Linux, uses the full nixpkgs package. 4 + { 5 + pkgs, 6 + osConfig, 7 + isDarwin, 8 + ... 9 + }: 10 + let 11 + cfg = osConfig.myConfig; 12 + d = cfg.desktop; 13 + in 14 + { 15 + programs.ghostty = { 16 + enable = true; 17 + 18 + package = if isDarwin then pkgs.ghostty-bin else pkgs.ghostty; 19 + 20 + enableZshIntegration = true; 21 + 22 + settings = { 23 + # ── Font ──────────────────────────────────────────────────────────── 24 + font-family = d.monoFontFamily; 25 + font-size = d.monoFontSize; 26 + 27 + # ── Theme ─────────────────────────────────────────────────────────── 28 + # Inline Catppuccin Mocha colours — no external theme file needed 29 + background = "1e1e2e"; # base 30 + foreground = "cdd6f4"; # text 31 + 32 + # ── Window ────────────────────────────────────────────────────────── 33 + window-decoration = if isDarwin then "auto" else "none"; 34 + background-opacity = 0.95; 35 + background-blur-radius = 20; 36 + 37 + # ── Cursor ────────────────────────────────────────────────────────── 38 + cursor-style = "bar"; 39 + cursor-style-blink = true; 40 + 41 + # ── Scrollback ────────────────────────────────────────────────────── 42 + scrollback-limit = 10000; 43 + 44 + # ── Misc ──────────────────────────────────────────────────────────── 45 + confirm-close-surface = false; 46 + copy-on-select = false; 47 + }; 48 + }; 49 + }
+3 -1
home/programs/ssh.nix
··· 93 93 config = { 94 94 ProgramArguments = [ 95 95 "/usr/bin/ssh-add" 96 - "--apple-load-keychain" 96 + "-q" 97 + "--apple-use-keychain" 98 + "/Users/${cfg.user.username}/.ssh/id_ed25519" 97 99 ]; 98 100 RunAtLoad = true; 99 101 StandardOutPath = "/tmp/ssh-add-keychain.log";
+52
home/scripts/verify-ssh-external
··· 1 + #!/usr/bin/env bash 2 + # Check SSH connectivity to external services 3 + # Usage: verify-ssh-external 4 + 5 + set -uo pipefail 6 + 7 + PASS=0 8 + FAIL=0 9 + 10 + check() { 11 + local label="$1" 12 + local host="$2" 13 + local user="$3" 14 + local expected="$4" 15 + 16 + echo -n " $label... " 17 + result=$(ssh -o ConnectTimeout=5 \ 18 + -o BatchMode=yes \ 19 + -o StrictHostKeyChecking=accept-new \ 20 + "${user}@${host}" 2>&1) || true 21 + 22 + if echo "$result" | grep -qi "$expected"; then 23 + echo "✓" 24 + ((PASS++)) 25 + else 26 + echo "✗ ($result)" 27 + ((FAIL++)) 28 + fi 29 + } 30 + 31 + echo 32 + echo "=== External SSH Verification ===" 33 + echo 34 + 35 + check "GitHub (github.com)" "github.com" "git" "successfully authenticated" 36 + check "Forgejo (git.ewancroft.uk)" "git.ewancroft.uk" "git" "successfully authenticated\|welcome" 37 + check "Tangled (tangled.sh)" "tangled.sh" "git" "successfully authenticated\|welcome" 38 + check "Tangled (tangled.org)" "tangled.org" "git" "successfully authenticated\|welcome" 39 + 40 + echo 41 + if [ "$FAIL" -eq 0 ]; then 42 + echo " All $PASS connections OK" 43 + else 44 + echo " $PASS passed, $FAIL failed" 45 + echo 46 + echo " Troubleshooting:" 47 + echo " - Check your key is loaded: ssh-add -l" 48 + echo " - GitHub keys: https://github.com/settings/keys" 49 + echo " - Forgejo keys: https://git.ewancroft.uk/user/settings/keys" 50 + echo " - Tangled keys: https://tangled.sh/settings/keys" 51 + fi 52 + echo
+3 -41
modules/options.nix
··· 736 736 brews = mkOption { 737 737 type = listStr; 738 738 default = [ 739 + # MediaInfo — standalone GUI/CLI media analyser 739 740 "libmediainfo" 740 741 "media-info" 741 742 "libzen" 742 - "aribb24" 743 - "dav1d" 744 - "rav1e" 745 - "svt-av1" 746 - "x264" 747 - "x265" 748 - "xvid" 749 - "webp" 750 - "aom" 751 - "jpeg-xl" 752 - "highway" 753 - "flac" 754 - "lame" 755 - "opus" 756 - "vorbis-tools" 757 - "libsndfile" 758 - "libsamplerate" 759 - "rubberband" 760 - "speex" 761 - "theora" 762 - "mpg123" 763 - "little-cms2" 764 - "leptonica" 765 - "rtmpdump" 766 - "srt" 767 - "librist" 768 - "libmms" 769 - "lzo" 770 - "snappy" 771 - "xxhash" 772 - "yyjson" 773 - "freetds" 774 - "unixodbc" 775 - "summarize" 776 - "goat" 743 + # MAS helper — required for masApps below 777 744 "mas" 778 745 ]; 779 746 }; ··· 801 768 "firefox" 802 769 # Gaming 803 770 "steam" 804 - "epic-games" 805 771 "prismlauncher" 806 - "roblox" 807 - "ea" 808 772 # Virtualisation 809 773 "utm" 810 774 # Networking / remote ··· 823 787 "microsoft-powerpoint" 824 788 "microsoft-teams" 825 789 "microsoft-word" 826 - "libreoffice" 827 790 "nextcloud-vfs" 828 791 ]; 829 792 }; ··· 832 795 default = { 833 796 "Amphetamine" = 937984704; 834 797 "OneDrive" = 823766827; 835 - "OP Auto Clicker" = 6754914118; 836 - "Steam Link" = 1246969117; 798 + # Steam Link removed — requires Rosetta 2, incompatible with Apple Silicon 837 799 "TestFlight" = 899247664; 838 800 "The Unarchiver" = 425424353; 839 801 "WhatsApp" = 310633997;
+1 -1
modules/ssh-keys.nix
··· 1 1 # Authorised SSH public keys for ewan, keyed by machine name. 2 2 # Each host automatically excludes its own key at build time. 3 3 { 4 - macmini = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPcEH7Belx/vpnmuspyAc/3iIAFqtxKGeftG5z5vBsUv git@ewancroft.uk"; 4 + macmini = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGVVr3laZlNUZ+g5DuHEf5VsOpdg52WLK38kBmZzSbJm git@ewancroft.uk"; 5 5 laptop = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAwl17Pm/CCbcCw4pboqP3V3iJTWKHggow0Qpt3vENNZ git@ewancroft.uk"; 6 6 server = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICUA55QI9vU2TCTJobFSkTZ4xnl3s7CXdnARxkh6/QzT git@ewancroft.uk"; 7 7 }
+21 -7
scripts/check-secrets.sh
··· 2 2 # Audits all sops secrets in the nix-config repo. 3 3 # Reports: format, recipient count, whether server key is present, and decryptability. 4 4 5 - cd "$(git rev-parse --show-toplevel)" || exit 1 5 + REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd .. && pwd)" 6 + cd "$REPO_ROOT" 6 7 7 8 SERVER_KEY="age1xvny7h8cahajamj4lz9cew5w0dqlge0yy6tys7szj42grcrl95jqsrutsu" 8 9 PASS=0 ··· 13 14 local expected_format="$2" 14 15 echo "── $file ──────────────────────────────" 15 16 16 - if ! jq empty "$file" 2>/dev/null; then 17 - echo " ✗ Not valid JSON — may be corrupted" 18 - ((FAIL++)); return 17 + # sops metadata is always in a JSON sidecar regardless of file format. 18 + # For dotenv/binary files, extract the sops block from the file directly. 19 + local sops_json 20 + if [[ "$expected_format" == "json" ]]; then 21 + if ! jq empty "$file" 2>/dev/null; then 22 + echo " ✗ Not valid JSON — may be corrupted" 23 + ((FAIL++)); return 24 + fi 25 + sops_json=$(jq '.sops' "$file" 2>/dev/null) 26 + else 27 + # dotenv/binary: sops appends a JSON block at the end after a blank line 28 + sops_json=$(awk '/^sops:/{found=1} found{print}' "$file" 2>/dev/null || true) 29 + if [ -z "$sops_json" ]; then 30 + # fallback: try treating whole file as JSON anyway 31 + sops_json=$(jq '.sops' "$file" 2>/dev/null || echo "{}") 32 + fi 19 33 fi 20 34 21 35 local count 22 - count=$(jq '.sops.age | length' "$file" 2>/dev/null) 36 + count=$(echo "$sops_json" | jq '.age | length' 2>/dev/null || echo "0") 23 37 echo " Recipients: $count" 24 38 25 - if jq -r '.sops.age[].recipient' "$file" 2>/dev/null | grep -q "$SERVER_KEY"; then 39 + if echo "$sops_json" | jq -r '.age[].recipient' 2>/dev/null | grep -q "$SERVER_KEY"; then 26 40 echo " ✓ Server key present" 27 41 else 28 42 echo " ✗ Server key MISSING" ··· 30 44 fi 31 45 32 46 local modified 33 - modified=$(jq -r '.sops.lastmodified' "$file" 2>/dev/null) 47 + modified=$(echo "$sops_json" | jq -r '.lastmodified' 2>/dev/null || echo "unknown") 34 48 echo " Last modified: $modified" 35 49 36 50 local plaintext
+1 -1
settings/darwin/default.nix
··· 55 55 "/Applications/Spotify.app" 56 56 "/Applications/Firefox.app" 57 57 # ── System ───────────────────────────────────────────────────── 58 - "/System/Applications/Utilities/Terminal.app" 58 + "/Users/ewan/Applications/Home Manager Apps/Ghostty.app" 59 59 ]; 60 60 }; 61 61