diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index a77e025..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml new file mode 100644 index 0000000..d40d1da --- /dev/null +++ b/.github/workflows/install.yml @@ -0,0 +1,111 @@ +name: install + +# Smoke-test that a fresh Mac install still works. +# Runs the brew-only subset of the Brewfile (mas needs an Apple ID, casks +# are slow and irrelevant to CI), stows the dotfiles, then sanity-checks +# that the tooling we rely on (brew, pnpm, eod) is functional. + +on: + push: + branches: [main] + pull_request: + paths: + - Brewfile + - Makefile + - bin/** + - home/** + - macos/** + - scripts/** + - .github/workflows/** + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + install: + runs-on: macos-15 + timeout-minutes: 30 + + steps: + - uses: actions/checkout@v4 + + - name: Show environment + run: | + sw_vers + uname -m + brew --version + which brew + + - name: Brews-only Brewfile (skip mas + cask + vscode + cargo) + run: | + grep -E '^(tap|brew) ' Brewfile > Brewfile.ci + echo "--- Brewfile.ci ---" + cat Brewfile.ci + + - name: brew bundle + run: brew bundle --no-lock --file=Brewfile.ci + + - name: make stow (clear conflicts first) + run: | + # The hosted runner ships some default dotfiles; remove the ones + # we manage so stow doesn't refuse to link. + rm -f ~/.zshrc ~/.gitconfig ~/.gitignore_global ~/.p10k.zsh + rm -f ~/.ssh/config + rm -rf ~/.config/nvim + make stow + + - name: Verify symlinks + run: | + set -x + test -L ~/.zshrc + test -L ~/.gitconfig + test -L ~/.p10k.zsh + test -L ~/.gitignore_global + test -L ~/.ssh/config + test -L ~/.config/nvim/init.lua + + - name: brew works + run: | + brew --version + brew list --formula | head -20 + + - name: node + pnpm work + run: | + node --version + pnpm --version + # Round-trip: pnpm can init a project and install a tiny dep. + mkdir -p /tmp/pnpm-test && cd /tmp/pnpm-test + pnpm init >/dev/null + pnpm add --silent ms >/dev/null + test -f node_modules/ms/package.json + node -e "console.log(require('ms')('2 days'))" + + - name: eod smoke test + run: | + set -x + export DEVELOPER_DIR=/tmp/test-repos + mkdir -p "$DEVELOPER_DIR"/{clean,dirty,ahead} + + # clean repo + git -C "$DEVELOPER_DIR/clean" init -q + git -C "$DEVELOPER_DIR/clean" -c user.email=t@t -c user.name=t commit --allow-empty -q -m initial + + # dirty repo + git -C "$DEVELOPER_DIR/dirty" init -q + touch "$DEVELOPER_DIR/dirty/foo.txt" + + # ahead repo (fake upstream) + git -C "$DEVELOPER_DIR/ahead" init -q + git -C "$DEVELOPER_DIR/ahead" -c user.email=t@t -c user.name=t commit --allow-empty -q -m initial + git -C "$DEVELOPER_DIR/ahead" update-ref refs/remotes/origin/main HEAD + git -C "$DEVELOPER_DIR/ahead" remote add origin /tmp/fake.git + git -C "$DEVELOPER_DIR/ahead" symbolic-ref HEAD refs/heads/main + git -C "$DEVELOPER_DIR/ahead" -c user.email=t@t -c user.name=t commit --allow-empty -q -m local + git -C "$DEVELOPER_DIR/ahead" branch --set-upstream-to=origin/main >/dev/null + + ./bin/eod --no-fetch "$DEVELOPER_DIR" + + - name: make doctor + run: make doctor diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c1d05c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +Brewfile.lock.json diff --git a/Brewfile b/Brewfile index b5343af..cef15b7 100644 --- a/Brewfile +++ b/Brewfile @@ -1,80 +1,88 @@ -tap "homebrew/bundle" -tap "homebrew/services" -tap "mongodb/brew" +# Homebrew bundle file -- the single source of truth for installed apps and CLIs. +# +# Apply with `make brew` (or `brew bundle --file=Brewfile`). +# Dump current state with `make brew-dump`. + +# --------------------------------------------------------------------------------------------------------------------- +# Taps +# --------------------------------------------------------------------------------------------------------------------- +tap "oven-sh/bun" tap "planetscale/tap" -tap "supabase/tap" + +# --------------------------------------------------------------------------------------------------------------------- +# CLI tools and language runtimes (brew) +# --------------------------------------------------------------------------------------------------------------------- +# Shell + dotfile management +brew "bash" brew "bash-completion" +brew "stow" + +# System utilities used by macOS setup scripts +brew "gnu-sed" +brew "gnupg" + +# Core CLI tooling brew "btop" -brew "caddy" -brew "chezmoi" brew "cloc" -brew "cocoapods" -brew "deno" -brew "ffmpeg" -brew "flyctl" brew "fzf" brew "gh" brew "git-delta" brew "lazygit" -brew "lftp" brew "mactop" brew "mas" brew "minicom" brew "mkcert" -brew "mysql-client" brew "neovim" -brew "nvm" -brew "pgsync" -brew "python@3.13" -brew "pipx" -brew "pnpm" -brew "postgresql@17" brew "pv" -brew "python@3.12" -brew "python@3.9" brew "ripgrep" brew "sherlock" brew "sniffnet" brew "sox" -brew "testdisk" brew "tmux" -brew "vercel-cli" brew "wakatime-cli" brew "wget" brew "yt-dlp" brew "zoxide" -brew "mongodb/brew/mongodb-database-tools" -brew "mongodb/brew/mongodb-community" + +# Web / cloud +brew "caddy" +brew "vercel-cli" + +# Media tooling +brew "ffmpeg" + +# Mobile / iOS +brew "cocoapods" + +# Language runtimes +brew "node" +brew "pnpm" + +# Databases +brew "mysql-client" +brew "pgsync" brew "planetscale/tap/pscale" -brew "supabase/tap/supabase" +brew "postgresql@17" + +# --------------------------------------------------------------------------------------------------------------------- +# GUI applications (cask) +# --------------------------------------------------------------------------------------------------------------------- cask "1password" cask "1password-cli" cask "android-platform-tools" cask "android-studio" -cask "anydesk" cask "appcleaner" -cask "azure-data-studio" cask "balenaetcher" -cask "cap" cask "claude-code" -cask "cursor" -cask "cyberduck" -cask "discord" -cask "excalidrawz" cask "expo-orbit" cask "ghostty" -cask "gitbutler" -cask "google-chrome" -cask "grandperspective" -cask "handbrake-app" -cask "iina" cask "imageoptim" cask "keka" +cask "claude" +cask "wispr-flow" cask "kekaexternalhelper" cask "lm-studio" -cask "logi-options+" cask "macs-fan-control" -cask "microsoft-auto-update" cask "microsoft-teams" cask "ngrok" cask "onyx" @@ -82,33 +90,29 @@ cask "openrocket" cask "orbstack" cask "postman" cask "rectangle" -cask "rustdesk" cask "shottr" -cask "sigmaos" cask "sketch" -cask "superhuman" cask "tableplus" cask "tailscale-app" cask "utm" -cask "visual-studio-code" cask "wakatime" cask "whisky" cask "zed" cask "zen" cask "zulu@17" + +# --------------------------------------------------------------------------------------------------------------------- +# Mac App Store (mas) +# Requires being signed into the App Store before `brew bundle` runs. +# --------------------------------------------------------------------------------------------------------------------- mas "1Password for Safari", id: 1569813296 -mas "AB Bounce", id: 6462195757 mas "Albums", id: 1469948986 mas "Amphetamine", id: 937984704 mas "Apple Configurator", id: 1037126344 mas "Broadcasts", id: 1469995354 mas "Compressor", id: 424390742 -mas "Craft", id: 1487937127 mas "Crouton", id: 1461650987 mas "Developer", id: 640199958 -mas "Diffusers", id: 1666309574 -mas "Dona", id: 6748265175 -mas "Dynamic Wallpaper Maker", id: 1453846328 mas "Final Cut Pro", id: 424389933 mas "Flighty", id: 1358823008 mas "GarageBand", id: 682658836 @@ -122,13 +126,8 @@ mas "Microsoft Excel", id: 462058435 mas "Microsoft Outlook", id: 985367838 mas "Microsoft Word", id: 462054704 mas "Motion", id: 434290957 -mas "MusicHarbor", id: 1440405750 -mas "Night Sky", id: 475772902 -mas "Numbers", id: 409203825 mas "OneDrive", id: 823766827 -mas "Pages", id: 409201541 mas "Photomator", id: 1444636541 -mas "PowerWash Simulator", id: 6477445344 mas "Reeder", id: 6475002485 mas "Refined GitHub", id: 1519867270 mas "Shazam", id: 897118787 @@ -142,19 +141,8 @@ mas "Windows App", id: 1295203466 mas "Wipr", id: 1662217862 mas "WireGuard", id: 1451685025 mas "Xcode - 15", id: 497799835 -vscode "biomejs.biome" -vscode "bradlc.vscode-tailwindcss" -vscode "davidanson.vscode-markdownlint" -vscode "expo.vscode-expo-tools" -vscode "github.codespaces" -vscode "github.copilot-chat" -vscode "github.vscode-github-actions" -vscode "mateocerquetella.xcode-12-theme" -vscode "ms-azuretools.vscode-containers" -vscode "ms-vscode-remote.remote-containers" -vscode "pdconsec.vscode-print" -vscode "redhat.vscode-yaml" -vscode "solomonkinard.git-blame" -vscode "wakatime.vscode-wakatime" -vscode "yoavbls.pretty-ts-errors" + +# --------------------------------------------------------------------------------------------------------------------- +# Cargo crates (installed via cargo-bundle plugin) +# --------------------------------------------------------------------------------------------------------------------- cargo "imessage-exporter" diff --git a/Brewfile.lock.json b/Brewfile.lock.json deleted file mode 100644 index eb8fc9c..0000000 --- a/Brewfile.lock.json +++ /dev/null @@ -1,1012 +0,0 @@ -{ - "entries": { - "tap": { - "homebrew/bundle": { - "revision": "6908c482f46130d42065f0a528e749177c2f7418" - }, - "supabase/tap": { - "revision": "f8e9118f68ae0a894e22e3084ec54f610fafa923" - } - }, - "brew": { - "bash-completion": { - "version": "1.3_3", - "bottle": { - "rebuild": 0, - "root_url": "https://ghcr.io/v2/homebrew/core", - "files": { - "arm64_sonoma": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/bash-completion/blobs/sha256:60e79daad9283c5e9f4c814eed837c86aab0b5172c633e7171cbbf26a434bcff", - "sha256": "60e79daad9283c5e9f4c814eed837c86aab0b5172c633e7171cbbf26a434bcff" - }, - "arm64_ventura": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/bash-completion/blobs/sha256:d7902e07973d14daf1bf98d5e3bc5b84beeee977b943c33585cf86d4eaae6e36", - "sha256": "d7902e07973d14daf1bf98d5e3bc5b84beeee977b943c33585cf86d4eaae6e36" - }, - "arm64_monterey": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/bash-completion/blobs/sha256:d7902e07973d14daf1bf98d5e3bc5b84beeee977b943c33585cf86d4eaae6e36", - "sha256": "d7902e07973d14daf1bf98d5e3bc5b84beeee977b943c33585cf86d4eaae6e36" - }, - "arm64_big_sur": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/bash-completion/blobs/sha256:44be13e781914250b3c277ce3672b7a3c45974f80ae8a2b0c55ccf884faf5d6b", - "sha256": "44be13e781914250b3c277ce3672b7a3c45974f80ae8a2b0c55ccf884faf5d6b" - }, - "sonoma": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/bash-completion/blobs/sha256:10c560f8c8058f80450a1d44826e57820d83370dbc3631cf5230a15cc8b8bbdc", - "sha256": "10c560f8c8058f80450a1d44826e57820d83370dbc3631cf5230a15cc8b8bbdc" - }, - "ventura": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/bash-completion/blobs/sha256:1a5cc6b613a97f1a15f87725d8343b4358e56acaa230f7cec64c77d4566a6f80", - "sha256": "1a5cc6b613a97f1a15f87725d8343b4358e56acaa230f7cec64c77d4566a6f80" - }, - "monterey": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/bash-completion/blobs/sha256:1a5cc6b613a97f1a15f87725d8343b4358e56acaa230f7cec64c77d4566a6f80", - "sha256": "1a5cc6b613a97f1a15f87725d8343b4358e56acaa230f7cec64c77d4566a6f80" - }, - "big_sur": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/bash-completion/blobs/sha256:8fe573529e08174b26d4379d92a42a7c38138c712e4e998541e8892fc6a376e7", - "sha256": "8fe573529e08174b26d4379d92a42a7c38138c712e4e998541e8892fc6a376e7" - }, - "catalina": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/bash-completion/blobs/sha256:bd0c84cc6df9d3ff06ac081d85fdcc052b9e63136f4e2aa5fd2f2a0b7f654c84", - "sha256": "bd0c84cc6df9d3ff06ac081d85fdcc052b9e63136f4e2aa5fd2f2a0b7f654c84" - }, - "mojave": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/bash-completion/blobs/sha256:9219c2b46362677e9ae6e19b344b774c3e9f163ae6bf6cf2686da06419aaec89", - "sha256": "9219c2b46362677e9ae6e19b344b774c3e9f163ae6bf6cf2686da06419aaec89" - }, - "high_sierra": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/bash-completion/blobs/sha256:b069be5574bdf6d12fd1fda17c3162467b68165541166d95d1a9474653a63abc", - "sha256": "b069be5574bdf6d12fd1fda17c3162467b68165541166d95d1a9474653a63abc" - }, - "sierra": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/bash-completion/blobs/sha256:58be92ef01d5068f37b1c00af8e9b202bdb409c93121bb0e07dcbb5e55dc3be2", - "sha256": "58be92ef01d5068f37b1c00af8e9b202bdb409c93121bb0e07dcbb5e55dc3be2" - }, - "el_capitan": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/bash-completion/blobs/sha256:58be92ef01d5068f37b1c00af8e9b202bdb409c93121bb0e07dcbb5e55dc3be2", - "sha256": "58be92ef01d5068f37b1c00af8e9b202bdb409c93121bb0e07dcbb5e55dc3be2" - }, - "x86_64_linux": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/bash-completion/blobs/sha256:c34ba8272f8e85e7f453c76e0fee07d8d35831e6b6365588a80ef240f9524e50", - "sha256": "c34ba8272f8e85e7f453c76e0fee07d8d35831e6b6365588a80ef240f9524e50" - } - } - } - }, - "btop": { - "version": "1.3.2", - "bottle": { - "rebuild": 0, - "root_url": "https://ghcr.io/v2/homebrew/core", - "files": { - "arm64_sonoma": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/btop/blobs/sha256:e2d3ce97c7b2dab1c4647631687f4884cb9078fe59d9009ea54d5028c2669703", - "sha256": "e2d3ce97c7b2dab1c4647631687f4884cb9078fe59d9009ea54d5028c2669703" - }, - "arm64_ventura": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/btop/blobs/sha256:ac19df269dc2da0586011c7e93ba0606d178f1f5840afd070c34d8e29404420d", - "sha256": "ac19df269dc2da0586011c7e93ba0606d178f1f5840afd070c34d8e29404420d" - }, - "sonoma": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/btop/blobs/sha256:73b9683780fdc4fcb996644990f37524a424daf4ad6333d23ce9a1ad9eea3281", - "sha256": "73b9683780fdc4fcb996644990f37524a424daf4ad6333d23ce9a1ad9eea3281" - }, - "ventura": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/btop/blobs/sha256:619e5e3fab0c6c5074d94baaa1c37837035731a729d48deba4a1db7bb2ce4ff4", - "sha256": "619e5e3fab0c6c5074d94baaa1c37837035731a729d48deba4a1db7bb2ce4ff4" - }, - "monterey": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/btop/blobs/sha256:5e509f11849bb625bfbd972a8078f9735b70448d5b3d73744d8c88e300651a90", - "sha256": "5e509f11849bb625bfbd972a8078f9735b70448d5b3d73744d8c88e300651a90" - }, - "x86_64_linux": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/btop/blobs/sha256:16e1fa0055f26d4d30a7437fc2c41d6d626271c09fe1e092099d01b58aa340fb", - "sha256": "16e1fa0055f26d4d30a7437fc2c41d6d626271c09fe1e092099d01b58aa340fb" - } - } - } - }, - "chezmoi": { - "version": "2.52.0", - "bottle": { - "rebuild": 0, - "root_url": "https://ghcr.io/v2/homebrew/core", - "files": { - "arm64_sonoma": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/chezmoi/blobs/sha256:4834afdf2217e9e9c7f19ec1442ffb51b7416aba6716618cf31b654b9d15c681", - "sha256": "4834afdf2217e9e9c7f19ec1442ffb51b7416aba6716618cf31b654b9d15c681" - }, - "arm64_ventura": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/chezmoi/blobs/sha256:d85289895d5b7afd2630c21298d505f336d452e06432c98c3f6688293b3a2b15", - "sha256": "d85289895d5b7afd2630c21298d505f336d452e06432c98c3f6688293b3a2b15" - }, - "arm64_monterey": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/chezmoi/blobs/sha256:3837e453158fe40e562c4a7886e46aeae42be015e991fde049c7bef33961a5e5", - "sha256": "3837e453158fe40e562c4a7886e46aeae42be015e991fde049c7bef33961a5e5" - }, - "sonoma": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/chezmoi/blobs/sha256:db3a157120838ca2a5db4c32536308dd783c2d08208f6471ca7e9ea531d6f8c8", - "sha256": "db3a157120838ca2a5db4c32536308dd783c2d08208f6471ca7e9ea531d6f8c8" - }, - "ventura": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/chezmoi/blobs/sha256:e12fd0ecf2eaf99dcfc122e71c9ba8b5b1e76c60dcfe31999fab6bd54a2e6e51", - "sha256": "e12fd0ecf2eaf99dcfc122e71c9ba8b5b1e76c60dcfe31999fab6bd54a2e6e51" - }, - "monterey": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/chezmoi/blobs/sha256:df6d6ea8e10654ef7278838f1a672bf579ee813a20b956fcd52ec6d8ce7abcb1", - "sha256": "df6d6ea8e10654ef7278838f1a672bf579ee813a20b956fcd52ec6d8ce7abcb1" - }, - "x86_64_linux": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/chezmoi/blobs/sha256:b85d19053256ca0953b0fee7369fe4f2b306ccddd42a6a40f9de55535cb849ba", - "sha256": "b85d19053256ca0953b0fee7369fe4f2b306ccddd42a6a40f9de55535cb849ba" - } - } - } - }, - "cocoapods": { - "version": "1.15.2", - "bottle": { - "rebuild": 0, - "root_url": "https://ghcr.io/v2/homebrew/core", - "files": { - "arm64_sonoma": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/cocoapods/blobs/sha256:4f9825eaf19aba42fc19939c47c49b3d4e322d383f5a79ab029167351d5547c6", - "sha256": "4f9825eaf19aba42fc19939c47c49b3d4e322d383f5a79ab029167351d5547c6" - }, - "arm64_ventura": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/cocoapods/blobs/sha256:e1729cced418772a8d8aa810c7cb153ec66db56a9b2cbec3e53adaaa576fb1ba", - "sha256": "e1729cced418772a8d8aa810c7cb153ec66db56a9b2cbec3e53adaaa576fb1ba" - }, - "arm64_monterey": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/cocoapods/blobs/sha256:e8dc98276c80101c70aa14f6e736e1154b988f3b53f6be357b7a76cf776c3b4b", - "sha256": "e8dc98276c80101c70aa14f6e736e1154b988f3b53f6be357b7a76cf776c3b4b" - }, - "sonoma": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/cocoapods/blobs/sha256:981c5baaab7a26258bfccfb7efc7141b9a88912b5fe06197ac07423fc436ed34", - "sha256": "981c5baaab7a26258bfccfb7efc7141b9a88912b5fe06197ac07423fc436ed34" - }, - "ventura": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/cocoapods/blobs/sha256:1cf37b506f2b50d73a26b3b1e1f28009faeb66d6a3ec4671453ac6d360e96cba", - "sha256": "1cf37b506f2b50d73a26b3b1e1f28009faeb66d6a3ec4671453ac6d360e96cba" - }, - "monterey": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/cocoapods/blobs/sha256:fa000679291ddbfa5792e8b04f71c106c4c3325116215ceb76e46f4c8ed7f85d", - "sha256": "fa000679291ddbfa5792e8b04f71c106c4c3325116215ceb76e46f4c8ed7f85d" - }, - "x86_64_linux": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/cocoapods/blobs/sha256:1cad9d3142ee7623d1ca22773dbeefa0e936e224aecef263a1657a91413c9958", - "sha256": "1cad9d3142ee7623d1ca22773dbeefa0e936e224aecef263a1657a91413c9958" - } - } - } - }, - "deno": { - "version": "1.45.5_1", - "bottle": { - "rebuild": 0, - "root_url": "https://ghcr.io/v2/homebrew/core", - "files": { - "arm64_sonoma": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/deno/blobs/sha256:b7721546c2b27ddb503267aa1d47934624ef4b6fbce469a58f82560373858ff2", - "sha256": "b7721546c2b27ddb503267aa1d47934624ef4b6fbce469a58f82560373858ff2" - }, - "arm64_ventura": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/deno/blobs/sha256:9d23dec3e544306fcc4a6f22ec2f63608d40144e57c6a790288d9dd5553d2c8f", - "sha256": "9d23dec3e544306fcc4a6f22ec2f63608d40144e57c6a790288d9dd5553d2c8f" - }, - "arm64_monterey": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/deno/blobs/sha256:096e7402a9f3b1d18ec3a5b6638b0e9a53b86165cd14f69b56cfabb8c7a875b5", - "sha256": "096e7402a9f3b1d18ec3a5b6638b0e9a53b86165cd14f69b56cfabb8c7a875b5" - }, - "sonoma": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/deno/blobs/sha256:57b30e1a4ab84f2d70d369dd2b0c41ab4025b8017417c1a6aa62577ce6f2afb8", - "sha256": "57b30e1a4ab84f2d70d369dd2b0c41ab4025b8017417c1a6aa62577ce6f2afb8" - }, - "ventura": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/deno/blobs/sha256:605c5ce1c5b142cc422f36c76a2f6cc0dcdf238ef351274704fb8bb8e0d29f4a", - "sha256": "605c5ce1c5b142cc422f36c76a2f6cc0dcdf238ef351274704fb8bb8e0d29f4a" - }, - "monterey": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/deno/blobs/sha256:a0c7f0982940b3b71d36106c3222356ea13e9436b793c7ea57f0778af0618776", - "sha256": "a0c7f0982940b3b71d36106c3222356ea13e9436b793c7ea57f0778af0618776" - }, - "x86_64_linux": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/deno/blobs/sha256:6c78e31f84e8c56f1ae9318e88078501acce7e95bc7d2201203267ffc4fdc2d0", - "sha256": "6c78e31f84e8c56f1ae9318e88078501acce7e95bc7d2201203267ffc4fdc2d0" - } - } - } - }, - "fzf": { - "version": "0.54.3", - "bottle": { - "rebuild": 0, - "root_url": "https://ghcr.io/v2/homebrew/core", - "files": { - "arm64_sonoma": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/fzf/blobs/sha256:18acafd043413bfb61c6edef50bb70f9ff42b0748427b1f36e7466f772c54681", - "sha256": "18acafd043413bfb61c6edef50bb70f9ff42b0748427b1f36e7466f772c54681" - }, - "arm64_ventura": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/fzf/blobs/sha256:a04b7d26d831530f6d053ef0adf5513b8eda7030e5a067d4154b7a7bb04e7438", - "sha256": "a04b7d26d831530f6d053ef0adf5513b8eda7030e5a067d4154b7a7bb04e7438" - }, - "arm64_monterey": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/fzf/blobs/sha256:77f351cc6ffa324710c89da3093c18ac8495f57ddf2b948e382ceda744a51aa6", - "sha256": "77f351cc6ffa324710c89da3093c18ac8495f57ddf2b948e382ceda744a51aa6" - }, - "sonoma": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/fzf/blobs/sha256:4807b5b09a1d0af31e75aa3f749acdf84e29275be040953d16060b29b6572e4a", - "sha256": "4807b5b09a1d0af31e75aa3f749acdf84e29275be040953d16060b29b6572e4a" - }, - "ventura": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/fzf/blobs/sha256:e8d6cd2506a64b290525b6ed9641e39344a2de657490b7939e88814287fb12c6", - "sha256": "e8d6cd2506a64b290525b6ed9641e39344a2de657490b7939e88814287fb12c6" - }, - "monterey": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/fzf/blobs/sha256:6e501ed855756c98853a246c68e4b6ee23472365c3dcc1054ce11edc143ccbdf", - "sha256": "6e501ed855756c98853a246c68e4b6ee23472365c3dcc1054ce11edc143ccbdf" - }, - "x86_64_linux": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/fzf/blobs/sha256:6addbb4c0a34e4325df4c754dc79c6544e13149c2bd8f6b813ce543e77b31edb", - "sha256": "6addbb4c0a34e4325df4c754dc79c6544e13149c2bd8f6b813ce543e77b31edb" - } - } - } - }, - "gh": { - "version": "2.54.0", - "bottle": { - "rebuild": 0, - "root_url": "https://ghcr.io/v2/homebrew/core", - "files": { - "arm64_sonoma": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/gh/blobs/sha256:87fd5458d122633c7df0868da975439dab8df13d53ed8089b4ebc20d602d7e9b", - "sha256": "87fd5458d122633c7df0868da975439dab8df13d53ed8089b4ebc20d602d7e9b" - }, - "arm64_ventura": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/gh/blobs/sha256:67110a2e0b88b929fd19e5f96de64664c8bae12d6e07c0bf5a17a89a1225a844", - "sha256": "67110a2e0b88b929fd19e5f96de64664c8bae12d6e07c0bf5a17a89a1225a844" - }, - "arm64_monterey": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/gh/blobs/sha256:aa9727f337894861739bf27dcb5a6234d560e5fc8fa25311b7e1d304b02f1329", - "sha256": "aa9727f337894861739bf27dcb5a6234d560e5fc8fa25311b7e1d304b02f1329" - }, - "sonoma": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/gh/blobs/sha256:02f5f1a4a95c0007e5726e63b2efe4b10ebdcaccd203d160c026d1b3726fcaf1", - "sha256": "02f5f1a4a95c0007e5726e63b2efe4b10ebdcaccd203d160c026d1b3726fcaf1" - }, - "ventura": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/gh/blobs/sha256:16949a188a19f9fdd6580ede3d6b8b5668ec56f8e0e21adfd02bd61f47986094", - "sha256": "16949a188a19f9fdd6580ede3d6b8b5668ec56f8e0e21adfd02bd61f47986094" - }, - "monterey": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/gh/blobs/sha256:0a42fcf5b41349cfb432925c4812adfecfecf9e5ac5e394e89abed75de030c79", - "sha256": "0a42fcf5b41349cfb432925c4812adfecfecf9e5ac5e394e89abed75de030c79" - }, - "x86_64_linux": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/gh/blobs/sha256:fb758d55cdccd4c5a61d3665f7ebfdfd05d8ef3224b4cd282fe6e455b0b0d8dc", - "sha256": "fb758d55cdccd4c5a61d3665f7ebfdfd05d8ef3224b4cd282fe6e455b0b0d8dc" - } - } - } - }, - "mactop": { - "version": "0.1.8", - "bottle": { - "rebuild": 0, - "root_url": "https://ghcr.io/v2/homebrew/core", - "files": { - "arm64_sonoma": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/mactop/blobs/sha256:8285894c895d812c9ac8100025bf4054ef41a4a006f97e9fb38d9567b1c158bd", - "sha256": "8285894c895d812c9ac8100025bf4054ef41a4a006f97e9fb38d9567b1c158bd" - }, - "arm64_ventura": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/mactop/blobs/sha256:eee6a9414367242c091997b966626244b3e8ffc24d86379ca6cf563ffad2858a", - "sha256": "eee6a9414367242c091997b966626244b3e8ffc24d86379ca6cf563ffad2858a" - }, - "arm64_monterey": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/mactop/blobs/sha256:892970e34a50d96aed3564a18bcf69ffe448d71ef4e27b096166284e060a5287", - "sha256": "892970e34a50d96aed3564a18bcf69ffe448d71ef4e27b096166284e060a5287" - } - } - } - }, - "mas": { - "version": "1.8.6", - "bottle": { - "rebuild": 0, - "root_url": "https://ghcr.io/v2/homebrew/core", - "files": { - "arm64_sonoma": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/mas/blobs/sha256:7b11bfefcb43e9a423ff301f7bbc29b0fb86044bf93442f243c5a8a67d8d4869", - "sha256": "7b11bfefcb43e9a423ff301f7bbc29b0fb86044bf93442f243c5a8a67d8d4869" - }, - "arm64_ventura": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/mas/blobs/sha256:e49511dd1283813c4420aec9fc3b3167d18f9fdbb51d82b1e479b628d5312342", - "sha256": "e49511dd1283813c4420aec9fc3b3167d18f9fdbb51d82b1e479b628d5312342" - }, - "arm64_monterey": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/mas/blobs/sha256:379d46e2657be295321f1603dc1df28130ea0b5b264ceb192a9ba488d77c7a98", - "sha256": "379d46e2657be295321f1603dc1df28130ea0b5b264ceb192a9ba488d77c7a98" - }, - "arm64_big_sur": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/mas/blobs/sha256:918a1484de106940f7bebc936e1ded87d7b65652054b09204887ad0651937ec4", - "sha256": "918a1484de106940f7bebc936e1ded87d7b65652054b09204887ad0651937ec4" - }, - "sonoma": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/mas/blobs/sha256:24e3057991ea1eed52eb4a27c0f17d794106770621e5a8bb975477dae135b82d", - "sha256": "24e3057991ea1eed52eb4a27c0f17d794106770621e5a8bb975477dae135b82d" - }, - "ventura": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/mas/blobs/sha256:6ef7788e28c46cdc0f916812f49dfeb1fabf2240a8c36f33ce34bcfb9df1502f", - "sha256": "6ef7788e28c46cdc0f916812f49dfeb1fabf2240a8c36f33ce34bcfb9df1502f" - }, - "monterey": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/mas/blobs/sha256:6b313f2f66d028cb7782c108d6e502ce73ccb9c08fac3bece0b057fcce5c4689", - "sha256": "6b313f2f66d028cb7782c108d6e502ce73ccb9c08fac3bece0b057fcce5c4689" - }, - "big_sur": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/mas/blobs/sha256:50b50f51219143fcb69c730b52b74011a76104f66348ea727d0200f7b375ae25", - "sha256": "50b50f51219143fcb69c730b52b74011a76104f66348ea727d0200f7b375ae25" - }, - "catalina": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/mas/blobs/sha256:d241d3b9156b033f3d2c31684a44de726297e07fd9bd5e3ccc4c36e4f1c3baf3", - "sha256": "d241d3b9156b033f3d2c31684a44de726297e07fd9bd5e3ccc4c36e4f1c3baf3" - } - } - } - }, - "neovim": { - "version": "0.10.1", - "bottle": { - "rebuild": 0, - "root_url": "https://ghcr.io/v2/homebrew/core", - "files": { - "arm64_sonoma": { - "cellar": "/opt/homebrew/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/neovim/blobs/sha256:1c72330a7a7c7a0fd1bc94a7f6ca24ec35791624cca20400c36020a136f0cc60", - "sha256": "1c72330a7a7c7a0fd1bc94a7f6ca24ec35791624cca20400c36020a136f0cc60" - }, - "arm64_ventura": { - "cellar": "/opt/homebrew/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/neovim/blobs/sha256:7bbb635a92be0cc70bb1a03e1554f03a8a4d823730221b6f62a8c7387e55a8e5", - "sha256": "7bbb635a92be0cc70bb1a03e1554f03a8a4d823730221b6f62a8c7387e55a8e5" - }, - "arm64_monterey": { - "cellar": "/opt/homebrew/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/neovim/blobs/sha256:58f575f2eae27d34177889fc2f43dd92f2e5fc2f334d96a46aee4969a6294f90", - "sha256": "58f575f2eae27d34177889fc2f43dd92f2e5fc2f334d96a46aee4969a6294f90" - }, - "sonoma": { - "cellar": "/usr/local/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/neovim/blobs/sha256:8b19ae8a53fe5cd4f655410f7a89a3ce62b3ac69facbb1cb9e6bf7c7e361a7be", - "sha256": "8b19ae8a53fe5cd4f655410f7a89a3ce62b3ac69facbb1cb9e6bf7c7e361a7be" - }, - "ventura": { - "cellar": "/usr/local/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/neovim/blobs/sha256:8e7dafb241ed5e625262fc72e8275b1466bf189b86d1698f5e255f95e25409f8", - "sha256": "8e7dafb241ed5e625262fc72e8275b1466bf189b86d1698f5e255f95e25409f8" - }, - "monterey": { - "cellar": "/usr/local/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/neovim/blobs/sha256:1ccff5bcf6d6dc0285e38b3e1cfa449ba82b86796e34643681f79ed53c47b500", - "sha256": "1ccff5bcf6d6dc0285e38b3e1cfa449ba82b86796e34643681f79ed53c47b500" - }, - "x86_64_linux": { - "cellar": "/home/linuxbrew/.linuxbrew/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/neovim/blobs/sha256:63f1e8c34873378647fe1a0759ae8c465419bacb76ff7276e3bd2eaac5836f77", - "sha256": "63f1e8c34873378647fe1a0759ae8c465419bacb76ff7276e3bd2eaac5836f77" - } - } - } - }, - "nvm": { - "version": "0.40.0", - "bottle": { - "rebuild": 0, - "root_url": "https://ghcr.io/v2/homebrew/core", - "files": { - "all": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/nvm/blobs/sha256:af3f9692df19ac0f8599ea485d68a42b0af097227e4ec8684e2710f36d4443be", - "sha256": "af3f9692df19ac0f8599ea485d68a42b0af097227e4ec8684e2710f36d4443be" - } - } - } - }, - "pv": { - "version": "1.8.12", - "bottle": { - "rebuild": 0, - "root_url": "https://ghcr.io/v2/homebrew/core", - "files": { - "arm64_sonoma": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/pv/blobs/sha256:49322c69f39d78e80431b3ecbaa984a94b41906809fde4c05f5454af2a5aaef7", - "sha256": "49322c69f39d78e80431b3ecbaa984a94b41906809fde4c05f5454af2a5aaef7" - }, - "arm64_ventura": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/pv/blobs/sha256:5d5d03d75411ecec1c57d4d77e51d71cf7c6f6e076b008e281e9e1ff4fa73527", - "sha256": "5d5d03d75411ecec1c57d4d77e51d71cf7c6f6e076b008e281e9e1ff4fa73527" - }, - "arm64_monterey": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/pv/blobs/sha256:047c47b6e7662e5a5e65da526c2942228979a913d19d95d063a30fa3f9828265", - "sha256": "047c47b6e7662e5a5e65da526c2942228979a913d19d95d063a30fa3f9828265" - }, - "sonoma": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/pv/blobs/sha256:dbb3ae11b9e6ec56980c5b11d9b524b68419e841eb6f1c7443e51696cf635c6d", - "sha256": "dbb3ae11b9e6ec56980c5b11d9b524b68419e841eb6f1c7443e51696cf635c6d" - }, - "ventura": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/pv/blobs/sha256:241d0aed8541f63dc26bbd9718b6b956184b4a419309923b2e6727ae70a70d0a", - "sha256": "241d0aed8541f63dc26bbd9718b6b956184b4a419309923b2e6727ae70a70d0a" - }, - "monterey": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/pv/blobs/sha256:1f9c9b78b39aa7d94d0dd459634ea43b49b59396e8d26d6b63a2d02fe6a633fc", - "sha256": "1f9c9b78b39aa7d94d0dd459634ea43b49b59396e8d26d6b63a2d02fe6a633fc" - }, - "x86_64_linux": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/pv/blobs/sha256:c1bf2aa7efad940f801d80ea6eba095dc30db9ef55faaba108d3b0643b864564", - "sha256": "c1bf2aa7efad940f801d80ea6eba095dc30db9ef55faaba108d3b0643b864564" - } - } - } - }, - "tmux": { - "version": "3.4_1", - "bottle": { - "rebuild": 1, - "root_url": "https://ghcr.io/v2/homebrew/core", - "files": { - "arm64_sonoma": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/tmux/blobs/sha256:defd2c5057e1f44cd545dd8c8a79246d860a71d9be88ccbc5e8128ef2ec6f94f", - "sha256": "defd2c5057e1f44cd545dd8c8a79246d860a71d9be88ccbc5e8128ef2ec6f94f" - }, - "arm64_ventura": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/tmux/blobs/sha256:32be1a9082ff54dc7f98f92fb91f72e00f31b9b24e3bc97434ad1a769763c057", - "sha256": "32be1a9082ff54dc7f98f92fb91f72e00f31b9b24e3bc97434ad1a769763c057" - }, - "arm64_monterey": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/tmux/blobs/sha256:8903753c2b5466cb6d28524b5f9582041e0955a0a2280e6e7d269b6068cd84d2", - "sha256": "8903753c2b5466cb6d28524b5f9582041e0955a0a2280e6e7d269b6068cd84d2" - }, - "sonoma": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/tmux/blobs/sha256:963013100e07ffe267686b21f362ad916c37070959b4d8184ac68ed1fdf1693a", - "sha256": "963013100e07ffe267686b21f362ad916c37070959b4d8184ac68ed1fdf1693a" - }, - "ventura": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/tmux/blobs/sha256:59ce7af5006e873f2f1afb464ac9876ec111b28067495510e76c0e6a08760607", - "sha256": "59ce7af5006e873f2f1afb464ac9876ec111b28067495510e76c0e6a08760607" - }, - "monterey": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/tmux/blobs/sha256:c6ec914966f86259aae1d8f77cc50174589013ae03733c27d644ff115269d5ef", - "sha256": "c6ec914966f86259aae1d8f77cc50174589013ae03733c27d644ff115269d5ef" - }, - "x86_64_linux": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/tmux/blobs/sha256:d029ba70c4e7eae66ab6928a71415bc52c4462801e3f1d8834d915b2bfffbe08", - "sha256": "d029ba70c4e7eae66ab6928a71415bc52c4462801e3f1d8834d915b2bfffbe08" - } - } - } - }, - "volta": { - "version": "2.0.0", - "bottle": { - "rebuild": 0, - "root_url": "https://ghcr.io/v2/homebrew/core", - "files": { - "arm64_sonoma": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/volta/blobs/sha256:770e914d04643cd9df8a578c93be0b1d0581279bf609b42b249aa5700e8f8ebc", - "sha256": "770e914d04643cd9df8a578c93be0b1d0581279bf609b42b249aa5700e8f8ebc" - }, - "arm64_ventura": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/volta/blobs/sha256:dd642007cbd682a560f78153f013f94a8ace9f22495ab981a17fdeb36492cd4e", - "sha256": "dd642007cbd682a560f78153f013f94a8ace9f22495ab981a17fdeb36492cd4e" - }, - "arm64_monterey": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/volta/blobs/sha256:4976f15cff289b8fa37f5b92c264dc9d170dc961ed3219f00e61edc06e4d5a19", - "sha256": "4976f15cff289b8fa37f5b92c264dc9d170dc961ed3219f00e61edc06e4d5a19" - }, - "sonoma": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/volta/blobs/sha256:7b8b5f904edca1e8293be0faea549eb962b2fcc4a3da14c1de151a31faff6422", - "sha256": "7b8b5f904edca1e8293be0faea549eb962b2fcc4a3da14c1de151a31faff6422" - }, - "ventura": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/volta/blobs/sha256:26e50ad0f3bad42aaa02cde573c90ff285bcf5af03b2eb012f24632bd46d3c6e", - "sha256": "26e50ad0f3bad42aaa02cde573c90ff285bcf5af03b2eb012f24632bd46d3c6e" - }, - "monterey": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/volta/blobs/sha256:cec8524c49a5a624d2cabef743b8ed1dd2e77447bfd3005fb14056abefc5a8af", - "sha256": "cec8524c49a5a624d2cabef743b8ed1dd2e77447bfd3005fb14056abefc5a8af" - }, - "x86_64_linux": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/volta/blobs/sha256:6167c2a7b8904cd5f7ec6d3955a0d0f8382566ae06788c39146ea5f5b9ba3705", - "sha256": "6167c2a7b8904cd5f7ec6d3955a0d0f8382566ae06788c39146ea5f5b9ba3705" - } - } - } - }, - "wget": { - "version": "1.24.5", - "bottle": { - "rebuild": 0, - "root_url": "https://ghcr.io/v2/homebrew/core", - "files": { - "arm64_sonoma": { - "cellar": "/opt/homebrew/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/wget/blobs/sha256:9befdad158e59763fb0622083974a6252878019702d8c961e1bec3a5f5305339", - "sha256": "9befdad158e59763fb0622083974a6252878019702d8c961e1bec3a5f5305339" - }, - "arm64_ventura": { - "cellar": "/opt/homebrew/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/wget/blobs/sha256:ac4c0330b70dae06eaa8065bfbea78dda277699d1ae8002478017a1bd9cf1908", - "sha256": "ac4c0330b70dae06eaa8065bfbea78dda277699d1ae8002478017a1bd9cf1908" - }, - "arm64_monterey": { - "cellar": "/opt/homebrew/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/wget/blobs/sha256:02313702fc03880f221d60ce4d0b652c8b44fe68c15609329d757d031bce6bc4", - "sha256": "02313702fc03880f221d60ce4d0b652c8b44fe68c15609329d757d031bce6bc4" - }, - "sonoma": { - "cellar": "/usr/local/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/wget/blobs/sha256:034528edb247df85f90997aca6a51ddb988a880af6bb571b8473de1702a887af", - "sha256": "034528edb247df85f90997aca6a51ddb988a880af6bb571b8473de1702a887af" - }, - "ventura": { - "cellar": "/usr/local/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/wget/blobs/sha256:1b7e2f76c90553543a5e25dadf031c6fcfe280f52bf27d89e04006f9d33fd20b", - "sha256": "1b7e2f76c90553543a5e25dadf031c6fcfe280f52bf27d89e04006f9d33fd20b" - }, - "monterey": { - "cellar": "/usr/local/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/wget/blobs/sha256:ffc49a5064a003006e69f51434ac5f7ec4f4019c161ad32fab22c32697db61cd", - "sha256": "ffc49a5064a003006e69f51434ac5f7ec4f4019c161ad32fab22c32697db61cd" - }, - "x86_64_linux": { - "cellar": "/home/linuxbrew/.linuxbrew/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/wget/blobs/sha256:6a4642964fe5c4d1cc8cd3507541736d5b984e34a303a814ef550d4f2f8242f9", - "sha256": "6a4642964fe5c4d1cc8cd3507541736d5b984e34a303a814ef550d4f2f8242f9" - } - } - } - }, - "zoxide": { - "version": "0.9.4", - "bottle": { - "rebuild": 0, - "root_url": "https://ghcr.io/v2/homebrew/core", - "files": { - "arm64_sonoma": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/zoxide/blobs/sha256:f676f976bc36f800fb3e1501dac29b579493d4cfe09217bd9a7a78d7a6289ed3", - "sha256": "f676f976bc36f800fb3e1501dac29b579493d4cfe09217bd9a7a78d7a6289ed3" - }, - "arm64_ventura": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/zoxide/blobs/sha256:1e0633546aacf6a5b61e8f4f3a3267fc2fe20437bdef451523bf6f6280441bc2", - "sha256": "1e0633546aacf6a5b61e8f4f3a3267fc2fe20437bdef451523bf6f6280441bc2" - }, - "arm64_monterey": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/zoxide/blobs/sha256:7a8eb375e99c59cd6f24432d3dd80d4ad7d6e79f43de93c215cfa79f7a27ff33", - "sha256": "7a8eb375e99c59cd6f24432d3dd80d4ad7d6e79f43de93c215cfa79f7a27ff33" - }, - "sonoma": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/zoxide/blobs/sha256:117d3d68c603514fd9c0db890f2cfb6337748b897e15d0f60ff1f80adcf72535", - "sha256": "117d3d68c603514fd9c0db890f2cfb6337748b897e15d0f60ff1f80adcf72535" - }, - "ventura": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/zoxide/blobs/sha256:c57845be5b2e1c2b1c0a1830ea238790be6010ac8471bff993e52e29e3588a38", - "sha256": "c57845be5b2e1c2b1c0a1830ea238790be6010ac8471bff993e52e29e3588a38" - }, - "monterey": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/zoxide/blobs/sha256:599d8d2fcd5f2dddf52ba4815bb310e3344f2b30bae902a6aff8956ef4991f1d", - "sha256": "599d8d2fcd5f2dddf52ba4815bb310e3344f2b30bae902a6aff8956ef4991f1d" - }, - "x86_64_linux": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/zoxide/blobs/sha256:778fb5fd4766a8a149ad721195b626bf7efc8d11da7ae15b9a5d95a2b5a07365", - "sha256": "778fb5fd4766a8a149ad721195b626bf7efc8d11da7ae15b9a5d95a2b5a07365" - } - } - } - }, - "supabase/tap/supabase": { - "version": "1.190.0", - "bottle": false - } - }, - "cask": { - "1password": { - "version": "8.10.40", - "options": { - "full_name": "1password" - } - }, - "1password-cli": { - "version": "2.30.0", - "options": { - "full_name": "1password-cli" - } - }, - "android-studio": { - "version": "2024.1.1.13", - "options": { - "full_name": "android-studio" - } - }, - "appcleaner": { - "version": "3.6.8", - "options": { - "full_name": "appcleaner" - } - }, - "arc": { - "version": "1.55.0,52417", - "options": { - "full_name": "arc" - } - }, - "expo-orbit": { - "version": "1.1.1", - "options": { - "full_name": "expo-orbit" - } - }, - "iina": { - "version": "1.3.5,141", - "options": { - "full_name": "iina" - } - }, - "iterm2": { - "version": "3.5.4", - "options": { - "full_name": "iterm2" - } - }, - "keka": { - "version": "1.4.3", - "options": { - "full_name": "keka" - } - }, - "kekaexternalhelper": { - "version": "1.1.3,1.2.59", - "options": { - "full_name": "kekaexternalhelper" - } - }, - "macs-fan-control": { - "version": "1.5.16", - "options": { - "full_name": "macs-fan-control" - } - }, - "orbstack": { - "version": "1.6.4_17192", - "options": { - "full_name": "orbstack" - } - }, - "raycast": { - "version": "1.81.0", - "options": { - "full_name": "raycast" - } - }, - "shottr": { - "version": "1.7.2", - "options": { - "full_name": "shottr" - } - }, - "stats": { - "version": "2.11.5", - "options": { - "full_name": "stats" - } - }, - "tableplus": { - "version": "6.1.2,568", - "options": { - "full_name": "tableplus" - } - }, - "tailscale": { - "version": "1.70.0", - "options": { - "full_name": "tailscale" - } - }, - "textmate": { - "version": "2.0.23", - "options": { - "full_name": "textmate" - } - }, - "visual-studio-code": { - "version": "1.92.1", - "options": { - "full_name": "visual-studio-code" - } - }, - "zed": { - "version": "0.148.0", - "options": { - "full_name": "zed" - } - }, - "zulu@17": { - "version": "17.0.12,17.52.17", - "options": { - "full_name": "zulu@17" - } - } - }, - "mas": { - "1Password for Safari": { - "id": "1569813296", - "version": "2.27.1" - }, - "Amphetamine": { - "id": "937984704", - "version": "5.3.2" - }, - "Apple Configurator": { - "id": "1037126344", - "version": "2.17" - }, - "Broadcasts": { - "id": "1469995354", - "version": "3.4.2" - }, - "Codye": { - "id": "1516894961", - "version": "2.0.5" - }, - "Compressor": { - "id": "424390742", - "version": "4.8" - }, - "Craft": { - "id": "1487937127", - "version": "2.8.7" - }, - "Denied": { - "id": "909257784", - "version": "2022.1" - }, - "Final Cut Pro": { - "id": "424389933", - "version": "10.8" - }, - "GarageBand": { - "id": "682658836", - "version": "10.4.11" - }, - "GP Calendar": { - "id": "1632951751", - "version": "1.6.2" - }, - "iMovie": { - "id": "408981434", - "version": "10.4.1" - }, - "Keynote": { - "id": "409183694", - "version": "14.1" - }, - "LanScan": { - "id": "472226235", - "version": "7.0.17" - }, - "Logic Pro": { - "id": "634148309", - "version": "11.0.1" - }, - "Microsoft Excel": { - "id": "462058435", - "version": "16.87" - }, - "Microsoft Word": { - "id": "462054704", - "version": "16.87" - }, - "Motion": { - "id": "434290957", - "version": "5.8" - }, - "MusicHarbor": { - "id": "1440405750", - "version": "4.8.4" - }, - "mymind": { - "id": "1532801185", - "version": "1.9340" - }, - "Numbers": { - "id": "409203825", - "version": "14.1" - }, - "OneDrive": { - "id": "823766827", - "version": "24.132.0701" - }, - "Pages": { - "id": "409201541", - "version": "14.1" - }, - "PastePal": { - "id": "1503446680", - "version": "2.16.0" - }, - "Playgrounds": { - "id": "1496833156", - "version": "4.5.1" - }, - "Reeder": { - "id": "1529448980", - "version": "5.4.3" - }, - "Speediness": { - "id": "1596706466", - "version": "1.7.1" - }, - "‎WhatsApp": { - "id": "310633997", - "version": "24.15.77" - } - }, - "vscode": { - "1password.op-vscode": null, - "aaron-bond.better-comments": null, - "arcanis.vscode-zipfs": null, - "bradlc.vscode-tailwindcss": null, - "christian-kohler.npm-intellisense": null, - "davidanson.vscode-markdownlint": null, - "dbaeumer.vscode-eslint": null, - "denoland.vscode-deno": null, - "eamodio.gitlens": null, - "esafirm.kotlin-formatter": null, - "esbenp.prettier-vscode": null, - "expo.vscode-expo-theme": null, - "expo.vscode-expo-tools": null, - "fwcd.kotlin": null, - "github.codespaces": null, - "github.copilot": null, - "github.copilot-chat": null, - "github.github-vscode-theme": null, - "github.remotehub": null, - "github.vscode-github-actions": null, - "github.vscode-pull-request-github": null, - "heybourn.headwind": null, - "mathiasfrohlich.kotlin": null, - "mechatroner.rainbow-csv": null, - "ms-azuretools.vscode-docker": null, - "ms-vscode-remote.remote-containers": null, - "ms-vscode-remote.remote-wsl": null, - "ms-vscode.remote-explorer": null, - "ms-vscode.remote-repositories": null, - "ms-vscode.remote-server": null, - "ms-vscode.vscode-speech": null, - "pdconsec.vscode-print": null, - "prisma.prisma": null, - "pwabuilder.pwa-studio": null, - "redhat.vscode-xml": null, - "rvest.vs-code-prettier-eslint": null, - "supermaven.supermaven": null, - "tamasfe.even-better-toml": null, - "tonybaloney.vscode-pets": null, - "unifiedjs.vscode-mdx": null, - "vscjava.vscode-gradle": null, - "vscode-icons-team.vscode-icons": null, - "vscodevim.vim": null, - "yoavbls.pretty-ts-errors": null - } - }, - "system": { - "macos": { - "sequoia": { - "HOMEBREW_VERSION": "4.3.15", - "HOMEBREW_PREFIX": "/opt/homebrew", - "Homebrew/homebrew-core": "api", - "CLT": "16.0.0.0.1.1722497825", - "Xcode": "16.0", - "macOS": "15.1" - } - } - } -} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..46f37fa --- /dev/null +++ b/Makefile @@ -0,0 +1,80 @@ +# Entry point for the dotfiles. Every target is safe to re-run. +# +# Typical use: +# make install # first-time setup on a fresh Mac +# make update # re-apply everything (idempotent) +# make help # list available targets + +SHELL := /bin/bash +.SHELLFLAGS := -eu -o pipefail -c +.DEFAULT_GOAL := help + +DOTFILES_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) +STOW_PACKAGE := home +TARGET_DIR := $(HOME) +FONT_SRC_DIR := $(DOTFILES_DIR)/fonts +FONT_DEST_DIR := $(HOME)/Library/Fonts + +# ANSI helpers +BOLD := \033[1m +DIM := \033[2m +RST := \033[0m + +.PHONY: help install update bootstrap brew brew-dump stow unstow restow adopt fonts macos dock apps scripts doctor clean-ds-store + +help: ## Show available targets + @printf "$(BOLD)Dotfiles targets$(RST)\n" + @awk 'BEGIN {FS = ":.*##"} /^[a-zA-Z_-]+:.*##/ {printf " $(BOLD)%-12s$(RST) %s\n", $$1, $$2}' $(MAKEFILE_LIST) + @printf "\n$(DIM)Source: $(DOTFILES_DIR)$(RST)\n" + +install: bootstrap brew stow fonts macos apps ## First-time setup on a fresh Mac + @printf "\n$(BOLD)Done.$(RST) Open a new shell to pick up changes.\n" + +update: brew restow fonts macos apps ## Re-apply everything (safe to repeat) + @printf "\n$(BOLD)Updated.$(RST)\n" + +bootstrap: ## Install Xcode CLT and Homebrew if missing + @bash $(DOTFILES_DIR)/scripts/bootstrap.sh + +brew: ## Install/sync packages from Brewfile + brew bundle --file=$(DOTFILES_DIR)/Brewfile + +brew-dump: ## Overwrite Brewfile with current brew state (review the diff!) + brew bundle dump --force --file=$(DOTFILES_DIR)/Brewfile + +stow: ## Symlink home/ into your home directory + stow --dir=$(DOTFILES_DIR) --target=$(TARGET_DIR) --no-folding $(STOW_PACKAGE) + +restow: ## Re-create symlinks (safe to repeat) + stow --dir=$(DOTFILES_DIR) --target=$(TARGET_DIR) --no-folding --restow $(STOW_PACKAGE) + +unstow: ## Remove all dotfile symlinks from your home directory + stow --dir=$(DOTFILES_DIR) --target=$(TARGET_DIR) --no-folding --delete $(STOW_PACKAGE) + +adopt: ## Move existing home-dir files into the repo (use with care, review diff after) + stow --dir=$(DOTFILES_DIR) --target=$(TARGET_DIR) --no-folding --adopt $(STOW_PACKAGE) + +fonts: ## Install bundled fonts into ~/Library/Fonts + @mkdir -p "$(FONT_DEST_DIR)" + @count=0; \ + while IFS= read -r -d '' f; do \ + name=$$(basename "$$f"); \ + if [[ ! -f "$(FONT_DEST_DIR)/$$name" ]]; then \ + cp "$$f" "$(FONT_DEST_DIR)/$$name"; \ + count=$$((count+1)); \ + fi; \ + done < <(find "$(FONT_SRC_DIR)" \( -name '*.otf' -o -name '*.ttf' \) -print0); \ + printf "Fonts: installed %d new file(s) into %s\n" "$$count" "$(FONT_DEST_DIR)" + +macos: ## Apply macOS system defaults (idempotent) + bash $(DOTFILES_DIR)/macos/defaults.sh + DOTFILES_DIR=$(DOTFILES_DIR) bash $(DOTFILES_DIR)/macos/dock.sh + +apps: ## Configure per-app preferences (Amphetamine, Safari, Mail, ...) + bash $(DOTFILES_DIR)/macos/apps.sh + +doctor: ## Diagnose obvious setup problems + @bash $(DOTFILES_DIR)/scripts/doctor.sh + +clean-ds-store: ## Remove .DS_Store files from the repo + @find $(DOTFILES_DIR) -name .DS_Store -not -path '*/.git/*' -print -delete diff --git a/README.md b/README.md index 5bbb299..3c9154b 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,113 @@ -# Welcome to my DotFiles! +# Dotfiles -These are made with [chezmoi](https://www.chezmoi.io/) and are inspired by [this video](https://www.youtube.com/watch?v=-RkANM9FfTM) and [this one](https://www.youtube.com/watch?v=ud7YxC33Z3w) +My personal macOS setup. Brew is the source of truth for every CLI, GUI app, and +plugin. macOS configuration and dotfile symlinks are managed by a small +`Makefile`, with personal scripts in `bin/`. > [!WARNING] -> **If you don't know what these do, don't just install them!** -> These are configuration files for my specific way of working. They setup many things - including 1Password for SSH/Git keys. Just installing these without research *will* result in a headache! +> These are tailored to my workflow (1Password for SSH/Git signing, Ghostty as +> terminal, Powerlevel10k, etc). Don't apply them blindly to your own machine. -## Getting up to speed -The two-liner to install these and get going is this: -```zsh -export GITHUB_USERNAME=limegorilla -sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply $GITHUB_USERNAME +## Quick start + +On a fresh Mac: + +```sh +git clone git@github.com:limegorilla/dotfiles.git ~/.dotfiles +cd ~/.dotfiles +make install ``` + +If you don't have git yet, the bootstrap script will install the Xcode Command +Line Tools first; run `make install` again afterwards. + +On an existing Mac, to re-apply everything safely: + +```sh +dotfiles update # available once .zshrc is sourced +# or, before the alias is loaded: +make -C ~/.dotfiles update +``` + +## Layout + +``` +dotfiles/ +├── Makefile # all entry points (make help for the list) +├── Brewfile # single source of truth for every installed app/CLI +├── home/ # stowed into $HOME (`make stow`) +│ ├── .zshrc +│ ├── .gitconfig +│ ├── .gitignore_global +│ ├── .p10k.zsh +│ ├── .ssh/config +│ └── .config/nvim/ +├── macos/ # idempotent macOS configuration scripts +│ ├── defaults.sh # global system defaults (Finder, firewall, ...) +│ ├── dock.sh # dock layout +│ └── apps.sh # per-app preferences (Safari, Mail, ...) +├── bin/ # personal scripts, added to $PATH via .zshrc +│ └── eod # end-of-day git status across ~/Developer +├── lib/ # shared shell helpers +│ └── dock_operations.sh +├── scripts/ # one-shot helpers used by the Makefile +│ ├── bootstrap.sh # Xcode CLT, Rosetta, Homebrew +│ └── doctor.sh # diagnose setup problems +└── fonts/ # bundled fonts, copied into ~/Library/Fonts +``` + +## Makefile targets + +Run `make help` for the live list. Highlights: + +| Target | What it does | +| --------------- | -------------------------------------------------------- | +| `make install` | Bootstrap, brew, stow, fonts, macos, apps (first run) | +| `make update` | Re-apply everything (safe to repeat on existing Macs) | +| `make brew` | `brew bundle` against the Brewfile | +| `make brew-dump`| Overwrite Brewfile with current brew state (review!) | +| `make stow` | Symlink `home/` into `$HOME` | +| `make restow` | Re-create symlinks (after pulling changes, for example) | +| `make adopt` | Move existing `$HOME` files **into** the repo (careful) | +| `make fonts` | Copy bundled fonts into `~/Library/Fonts` | +| `make macos` | `defaults.sh` + `dock.sh` | +| `make apps` | `apps.sh` | +| `make doctor` | Sanity-check the environment | + +Once `.zshrc` is sourced, the same targets are available via the `dotfiles` +alias (e.g. `dotfiles update`, `dotfiles doctor`). + +## The `bin/` directory + +Anything in `bin/` is on `$PATH` after the shell is sourced. Add a new script by +dropping a file in, marking it executable, and starting it with a useful +shebang (`#!/usr/bin/env bash` for shell, etc.). + +### `eod` — end-of-day report + +Walks every git repository under `~/Developer` and lists anything with +uncommitted changes, unpushed commits, or no upstream branch. Run it before +shutting down for the day: + +```sh +eod # scans ~/Developer +eod ~/code # scan a different tree +``` + +## Conventions + +- **Every package is installed via Brew or the Mac App Store.** Manual installs + (`curl | bash`, `npm i -g`, ...) belong in a script, not in folklore. +- **Every setup script is idempotent.** Re-running `make update` on an existing + Mac should never break the system or require manual cleanup. +- **macOS-only.** No `if linux` branches. If that changes, introduce + `linux/defaults.sh` and split the Makefile targets. + +## Adopting on an existing Mac + +If you already have a `~/.zshrc`, `~/.gitconfig`, etc., `make stow` will refuse +to overwrite them. Two options: + +1. Back them up, delete them, and `make stow`. +2. Run `make adopt` to **move** the existing files into `home/`, then review the + diff and reset anything you don't actually want to track. diff --git a/bin/eod b/bin/eod new file mode 100755 index 0000000..584102e --- /dev/null +++ b/bin/eod @@ -0,0 +1,417 @@ +#!/usr/bin/env bash +# eod -- "end of day" git report. +# +# Walks every git repository under DIRECTORY (default: $DEVELOPER_DIR or +# ~/Developer) and reports anything not yet committed, pushed or pulled. +# Worktree leaves and submodules are skipped (their .git is a file, not a dir). +# Build/dependency directories listed in EOD_SKIP_DIRS are pruned entirely. +# +# Usage: +# eod [DIR] Show a compact table of repos needing attention. +# eod --no-fetch [DIR] Skip `git fetch` (use last-known remote state). +# eod --apply [DIR] Walk through each repo and choose what to do. +# eod -h | --help Show this help. +# +# Environment: +# EOD_SKIP_DIRS Space-separated directory names to prune. Defaults to +# common build/dependency caches that contain checked-out +# git repos (Swift, Node, Go vendor, Xcode, Rust, etc.). + +set -euo pipefail + +# Prefer a UTF-8 locale so multi-byte glyphs (●, ↑, ─, …) render and +# ${#string} counts characters, not bytes. macOS ships en_US.UTF-8. +if [[ -z "${LC_ALL:-}" ]] && locale -a 2>/dev/null | grep -qi '^en_US\.UTF-8$'; then + export LC_ALL=en_US.UTF-8 +fi + +# --------------------------------------------------------------------------- +# Args +# --------------------------------------------------------------------------- +APPLY=0 +FETCH=1 +ROOT="" + +print_help() { + awk '/^# eod/,/^$/' "$0" | sed -E 's/^# ?//' +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --apply) APPLY=1 ;; + --fetch) FETCH=1 ;; + --no-fetch) FETCH=0 ;; + -h|--help) print_help; exit 0 ;; + --) shift; ROOT="${1:-}"; break ;; + -*) echo "eod: unknown flag: $1" >&2; exit 2 ;; + *) ROOT="$1" ;; + esac + shift || true +done + +ROOT="${ROOT:-${DEVELOPER_DIR:-$HOME/Developer}}" +if [[ ! -d "$ROOT" ]]; then + echo "eod: not a directory: $ROOT" >&2 + exit 1 +fi +ROOT="$(cd "$ROOT" && pwd)" + +# Directories to prune. Override via EOD_SKIP_DIRS env var. +DEFAULT_SKIP_DIRS=( + .build # SwiftPM + node_modules # Node + vendor # Go modules / PHP / Ruby + Pods # CocoaPods + DerivedData # Xcode + .terraform # Terraform + .gradle # Gradle + target # Rust / Java + .venv venv # Python virtualenvs + __pycache__ + .next .nuxt .svelte-kit # JS frameworks + .cache +) +IFS=' ' read -r -a SKIP_DIRS <<< "${EOD_SKIP_DIRS:-${DEFAULT_SKIP_DIRS[*]}}" + +# --------------------------------------------------------------------------- +# Colours / UI +# --------------------------------------------------------------------------- +if [[ -t 1 ]]; then + BOLD=$'\e[1m'; DIM=$'\e[2m'; RST=$'\e[0m' + YEL=$'\e[33m'; MAG=$'\e[35m'; CYA=$'\e[36m' + GRN=$'\e[32m'; RED=$'\e[31m' + CLR_LINE=$'\r\e[K' +else + BOLD=""; DIM=""; RST=""; YEL=""; MAG=""; CYA=""; GRN=""; RED=""; CLR_LINE="" +fi + +SPIN=(⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏) + +term_width() { + local cols + cols="${COLUMNS:-$(tput cols 2>/dev/null || echo 80)}" + [[ "$cols" =~ ^[0-9]+$ ]] && echo "$cols" || echo 80 +} + +# Replacement-character-aware middle truncation. Assumes 1-cell-per-char glyphs +# (true for the user-facing paths and branches we display). +truncate_middle() { + local s="$1" max="$2" + if [[ ${#s} -le $max ]]; then + printf '%s' "$s" + else + local keep=$((max - 1)) + local head=$(( keep / 2 )) + local tail=$(( keep - head )) + printf '%s…%s' "${s:0:$head}" "${s: -$tail}" + fi +} + +# Print and clear a "live" status line on the TTY. These must always return +# 0 even when there's no TTY -- they're called under `set -e`. +status() { + if [[ -n "$CLR_LINE" ]]; then + printf '%s%s' "$CLR_LINE" "$1" + fi + return 0 +} + +clear_status() { + if [[ -n "$CLR_LINE" ]]; then + printf '%s' "$CLR_LINE" + fi + return 0 +} + +# --------------------------------------------------------------------------- +# Discover repos +# `.git` as a directory => regular working tree. Worktree leaves and +# submodules have `.git` as a file; `-type d` skips both. +# --------------------------------------------------------------------------- +status "${SPIN[0]} Finding repos in $(truncate_middle "$ROOT" 60)…" + +# Build a find expression that prunes the skip dirs: +# find ROOT \( -type d \( -name A -o -name B ... \) -prune \) -o \ +# \( -name .git -type d -prune -print0 \) +find_expr=( "$ROOT" ) +if [[ ${#SKIP_DIRS[@]} -gt 0 ]]; then + find_expr+=( '(' '-type' 'd' '(' ) + first=1 + for d in "${SKIP_DIRS[@]}"; do + [[ -z "$d" ]] && continue + if [[ $first -eq 1 ]]; then + find_expr+=( '-name' "$d" ) + first=0 + else + find_expr+=( '-o' '-name' "$d" ) + fi + done + find_expr+=( ')' '-prune' ')' '-o' ) +fi +find_expr+=( '(' '-name' '.git' '-type' 'd' '-prune' '-print0' ')' ) + +repos=() +while IFS= read -r -d '' git_dir; do + repos+=("${git_dir%/.git}") +done < <(find "${find_expr[@]}") + +clear_status + +if [[ ${#repos[@]} -eq 0 ]]; then + echo "eod: no git repos found under $ROOT" + exit 0 +fi +IFS=$'\n' repos=($(printf '%s\n' "${repos[@]}" | sort)) +unset IFS + +# --------------------------------------------------------------------------- +# Parallel fetch with live spinner +# --------------------------------------------------------------------------- +if [[ $FETCH -eq 1 ]]; then + status_dir=$(mktemp -d -t eod.XXXXXX) + trap 'rm -rf "$status_dir"' EXIT + + total=${#repos[@]} + + ( + running=0 idx=0 + for repo in "${repos[@]}"; do + idx=$((idx + 1)) + ( + cd "$repo" && git fetch --quiet --all --prune 2>/dev/null || true + touch "$status_dir/$idx" + ) & + running=$((running + 1)) + if [[ $running -ge 10 ]]; then + wait -n 2>/dev/null || wait + running=$((running - 1)) + fi + done + wait + touch "$status_dir/.done" + ) & + + spin_i=0 + while [[ ! -e "$status_dir/.done" ]]; do + done_n=$(find "$status_dir" -maxdepth 1 -type f \! -name '.done' 2>/dev/null | wc -l | tr -d ' ') + status "${SPIN[$spin_i]} Fetching… ${DIM}$done_n/$total${RST}" + spin_i=$(( (spin_i + 1) % ${#SPIN[@]} )) + sleep 0.08 + done + wait + clear_status +fi + +# --------------------------------------------------------------------------- +# Collect state +# --------------------------------------------------------------------------- +names=() +branches=() +upstreams=() +dirty=() +ahead=() +behind=() +needs=() + +idx=0 +for repo in "${repos[@]}"; do + idx=$((idx + 1)) + status "${SPIN[$((idx % ${#SPIN[@]}))]} Analysing… ${DIM}$idx/${#repos[@]}${RST}" + + cd "$repo" + name="${repo#$ROOT/}" + branch="$(git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD 2>/dev/null || echo '?')" + upstream="$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || true)" + + d=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ') + a=0; b=0 + if [[ -n "$upstream" ]]; then + if ! read -r b a < <(git rev-list --left-right --count "@{u}...HEAD" 2>/dev/null); then + b=0; a=0 + fi + fi + + iss=0 + if [[ $d -gt 0 || $a -gt 0 || $b -gt 0 ]]; then + iss=1 + elif [[ -z "$upstream" ]] && git rev-parse HEAD >/dev/null 2>&1; then + iss=1 + fi + + names+=("$name") + branches+=("$branch") + upstreams+=("$upstream") + dirty+=("$d") + ahead+=("$a") + behind+=("$b") + needs+=("$iss") +done +clear_status + +# --------------------------------------------------------------------------- +# Status formatter +# --------------------------------------------------------------------------- +format_status() { # d a b upstream + local d="$1" a="$2" b="$3" up="$4" + local out="" + [[ $d -gt 0 ]] && out+="${YEL}●${d}${RST} " + [[ $a -gt 0 ]] && out+="${MAG}↑${a}${RST} " + [[ $b -gt 0 ]] && out+="${CYA}↓${b}${RST} " + [[ -z "$up" ]] && out+="${DIM}⊘ no upstream${RST} " + [[ -z "$out" ]] && out="${DIM}─${RST}" + printf '%s' "${out% }" +} + +attention=0 +for i in "${!names[@]}"; do + [[ ${needs[i]} -eq 1 ]] && attention=$((attention + 1)) +done + +display_root="${ROOT/#$HOME/~}" + +# --------------------------------------------------------------------------- +# Default mode: table +# --------------------------------------------------------------------------- +if [[ $APPLY -eq 0 ]]; then + if [[ $attention -eq 0 ]]; then + printf "${BOLD}end-of-day${RST} ${DIM}%s${RST}\n" "$display_root" + printf "${GRN}All %d repos caught up.${RST} Have a good evening.\n" "${#repos[@]}" + exit 0 + fi + + # Reserve a sensible chunk of the terminal for the path column. + tw=$(term_width) + max_path_cap=$(( tw - 32 )) # leave room for branch + status + padding + (( max_path_cap < 24 )) && max_path_cap=24 + (( max_path_cap > 80 )) && max_path_cap=80 + max_branch_cap=24 + + # Compute actual column widths from the (possibly truncated) data. + name_w=4 + branch_w=6 + display_names=() + display_branches=() + for i in "${!names[@]}"; do + [[ ${needs[i]} -eq 0 ]] && continue + n=$(truncate_middle "${names[i]}" "$max_path_cap") + b=$(truncate_middle "${branches[i]}" "$max_branch_cap") + display_names+=("$n") + display_branches+=("$b") + (( ${#n} > name_w )) && name_w=${#n} + (( ${#b} > branch_w )) && branch_w=${#b} + done + + printf "${BOLD}end-of-day${RST} ${DIM}%s · %d of %d need attention${RST}\n\n" \ + "$display_root" "$attention" "${#repos[@]}" + + # Headers + rule() { # repeat ─ N times + local n="$1" s="" + while (( n-- > 0 )); do s+="─"; done + printf '%s' "$s" + } + printf " ${DIM}%-${name_w}s %-${branch_w}s %s${RST}\n" "REPO" "BRANCH" "STATUS" + printf " ${DIM}%s %s %s${RST}\n" "$(rule "$name_w")" "$(rule "$branch_w")" "──────" + + j=0 + for i in "${!names[@]}"; do + [[ ${needs[i]} -eq 0 ]] && continue + status_str=$(format_status "${dirty[i]}" "${ahead[i]}" "${behind[i]}" "${upstreams[i]}") + printf " %-${name_w}s ${DIM}%-${branch_w}s${RST} %b\n" \ + "${display_names[$j]}" "${display_branches[$j]}" "$status_str" + j=$((j + 1)) + done + + if [[ $FETCH -eq 0 ]]; then + printf "\n${DIM}Last-known remote state. Drop \`--no-fetch\` to refresh, or \`eod --apply\` to act.${RST}\n" + else + printf "\n${DIM}\`eod --apply\` to act on these.${RST}\n" + fi + exit 0 +fi + +# --------------------------------------------------------------------------- +# --apply mode +# --------------------------------------------------------------------------- +if [[ $attention -eq 0 ]]; then + printf "${GRN}All %d repos caught up. Nothing to do.${RST}\n" "${#repos[@]}" + exit 0 +fi + +current=0 +for i in "${!names[@]}"; do + [[ ${needs[i]} -eq 0 ]] && continue + current=$((current + 1)) + + repo="${repos[i]}" + name="${names[i]}" + branch="${branches[i]}" + upstream="${upstreams[i]}" + d=${dirty[i]}; a=${ahead[i]}; b=${behind[i]} + + cd "$repo" + + printf "\n${BOLD}[%d/%d] %s${RST} ${DIM}(%s)${RST}\n" \ + "$current" "$attention" "$name" "$branch" + printf " %b\n\n" "$(format_status "$d" "$a" "$b" "$upstream")" + + options=() + if [[ -z "$upstream" ]]; then + : + elif [[ $b -gt 0 && $a -eq 0 && $d -eq 0 ]]; then + options+=("u|pull (fast-forward)") + elif [[ $b -gt 0 && $d -gt 0 ]]; then + options+=("t|stash, pull --rebase, pop") + elif [[ $a -gt 0 && $b -gt 0 ]]; then + options+=("r|pull --rebase") + elif [[ $a -gt 0 && $d -eq 0 ]]; then + options+=("p|push") + elif [[ $a -gt 0 && $d -gt 0 ]]; then + options+=("p|push (won't include uncommitted changes)") + fi + + [[ $d -gt 0 ]] && options+=("v|view diff") + [[ $a -gt 0 ]] && options+=("l|view unpushed log") + options+=("s|open shell here") + options+=("k|skip") + options+=("q|quit") + + for opt in "${options[@]}"; do + key="${opt%%|*}"; label="${opt#*|}" + printf " ${BOLD}[%s]${RST} %s\n" "$key" "$label" + done + + printf "\n> " + read -r -n 1 choice || true + echo + + case "$choice" in + u) git pull --ff-only || printf "${RED}pull failed${RST}\n" ;; + p) git push || printf "${RED}push failed${RST}\n" ;; + r) git pull --rebase || printf "${RED}rebase failed; resolve manually${RST}\n" ;; + t) + stash="eod-$(date +%Y%m%d-%H%M%S)" + if git stash push -u -m "$stash" >/dev/null; then + if git pull --rebase; then + git stash pop || printf "${RED}stash pop conflicted; resolve in %s (stash kept)${RST}\n" "$name" + else + printf "${RED}rebase failed; your changes are stashed as '%s'${RST}\n" "$stash" + fi + else + printf "${DIM}nothing to stash; pulling anyway…${RST}\n" + git pull --rebase || printf "${RED}rebase failed${RST}\n" + fi + ;; + v) git diff || true ;; + l) git --no-pager log "@{u}..HEAD" --oneline ;; + s) + printf "${DIM}-- shell in %s; exit to continue --${RST}\n" "$name" + "${SHELL:-/bin/zsh}" -i || true + ;; + k|"") printf "${DIM}skipped${RST}\n" ;; + q) printf "${DIM}stopped at %s${RST}\n" "$name"; exit 0 ;; + *) printf "${DIM}'%s' is not a choice; skipped${RST}\n" "$choice" ;; + esac +done + +printf "\n${GRN}Done.${RST} Have a good evening.\n" diff --git a/dot_config/iterm2/private_sockets/.keep b/dot_config/iterm2/private_sockets/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/dot_config/iterm2/symlink_AppSupport b/dot_config/iterm2/symlink_AppSupport deleted file mode 100644 index 92c5be6..0000000 --- a/dot_config/iterm2/symlink_AppSupport +++ /dev/null @@ -1 +0,0 @@ -/Users/liamdoyle/Library/Application Support/iTerm2 diff --git a/fonts/.DS_Store b/fonts/.DS_Store deleted file mode 100644 index 033033a..0000000 Binary files a/fonts/.DS_Store and /dev/null differ diff --git a/dot_config/nvim/dot_stylua.toml b/home/.config/nvim/.stylua.toml similarity index 100% rename from dot_config/nvim/dot_stylua.toml rename to home/.config/nvim/.stylua.toml diff --git a/dot_config/nvim/init.lua b/home/.config/nvim/init.lua similarity index 100% rename from dot_config/nvim/init.lua rename to home/.config/nvim/init.lua diff --git a/dot_config/nvim/lazy-lock.json b/home/.config/nvim/lazy-lock.json similarity index 100% rename from dot_config/nvim/lazy-lock.json rename to home/.config/nvim/lazy-lock.json diff --git a/dot_config/nvim/lua/custom/plugins/init.lua b/home/.config/nvim/lua/custom/plugins/init.lua similarity index 100% rename from dot_config/nvim/lua/custom/plugins/init.lua rename to home/.config/nvim/lua/custom/plugins/init.lua diff --git a/dot_config/nvim/lua/kickstart/health.lua b/home/.config/nvim/lua/kickstart/health.lua similarity index 100% rename from dot_config/nvim/lua/kickstart/health.lua rename to home/.config/nvim/lua/kickstart/health.lua diff --git a/dot_config/nvim/lua/kickstart/plugins/autopairs.lua b/home/.config/nvim/lua/kickstart/plugins/autopairs.lua similarity index 100% rename from dot_config/nvim/lua/kickstart/plugins/autopairs.lua rename to home/.config/nvim/lua/kickstart/plugins/autopairs.lua diff --git a/dot_config/nvim/lua/kickstart/plugins/debug.lua b/home/.config/nvim/lua/kickstart/plugins/debug.lua similarity index 100% rename from dot_config/nvim/lua/kickstart/plugins/debug.lua rename to home/.config/nvim/lua/kickstart/plugins/debug.lua diff --git a/dot_config/nvim/lua/kickstart/plugins/gitsigns.lua b/home/.config/nvim/lua/kickstart/plugins/gitsigns.lua similarity index 100% rename from dot_config/nvim/lua/kickstart/plugins/gitsigns.lua rename to home/.config/nvim/lua/kickstart/plugins/gitsigns.lua diff --git a/dot_config/nvim/lua/kickstart/plugins/indent_line.lua b/home/.config/nvim/lua/kickstart/plugins/indent_line.lua similarity index 100% rename from dot_config/nvim/lua/kickstart/plugins/indent_line.lua rename to home/.config/nvim/lua/kickstart/plugins/indent_line.lua diff --git a/dot_config/nvim/lua/kickstart/plugins/lint.lua b/home/.config/nvim/lua/kickstart/plugins/lint.lua similarity index 100% rename from dot_config/nvim/lua/kickstart/plugins/lint.lua rename to home/.config/nvim/lua/kickstart/plugins/lint.lua diff --git a/dot_config/nvim/lua/kickstart/plugins/neo-tree.lua b/home/.config/nvim/lua/kickstart/plugins/neo-tree.lua similarity index 100% rename from dot_config/nvim/lua/kickstart/plugins/neo-tree.lua rename to home/.config/nvim/lua/kickstart/plugins/neo-tree.lua diff --git a/dot_gitconfig b/home/.gitconfig similarity index 100% rename from dot_gitconfig rename to home/.gitconfig diff --git a/dot_gitignore_global b/home/.gitignore_global similarity index 100% rename from dot_gitignore_global rename to home/.gitignore_global diff --git a/dot_p10k.zsh b/home/.p10k.zsh similarity index 100% rename from dot_p10k.zsh rename to home/.p10k.zsh diff --git a/dot_ssh/config b/home/.ssh/config similarity index 100% rename from dot_ssh/config rename to home/.ssh/config diff --git a/dot_zshrc b/home/.zshrc similarity index 67% rename from dot_zshrc rename to home/.zshrc index d525edf..d4fac4c 100644 --- a/dot_zshrc +++ b/home/.zshrc @@ -1,17 +1,23 @@ +# --------------------------------------------------------------------------------------------------------------------- +# Dotfiles location -- used by the `dotfiles` alias and to find personal scripts. +# Set DOTFILES_DIR before sourcing this file to override the default. +# --------------------------------------------------------------------------------------------------------------------- +export DOTFILES_DIR="${DOTFILES_DIR:-$HOME/.dotfiles}" + # --------------------------------------------------------------------------------------------------------------------- # Powerlevel10k - Instant Prompt # --------------------------------------------------------------------------------------------------------------------- if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" fi -# Initialization code that may require console input (password prompts, [y/n] -# confirmations, etc.) must go above this block; everything else may go below. # --------------------------------------------------------------------------------------------------------------------- # Homebrew # --------------------------------------------------------------------------------------------------------------------- -if [[ -f "/opt/homebrew/bin/brew" ]] then +if [[ -f "/opt/homebrew/bin/brew" ]]; then eval "$(/opt/homebrew/bin/brew shellenv)" +elif [[ -f "/usr/local/bin/brew" ]]; then + eval "$(/usr/local/bin/brew shellenv)" fi export HOMEBREW_CASK_OPTS=--no-quarantine @@ -19,47 +25,39 @@ export HOMEBREW_CASK_OPTS=--no-quarantine # Zinit # --------------------------------------------------------------------------------------------------------------------- ZINIT_HOME="${XDG_DATA_HOME:-${HOME}/.local/share}/zinit/zinit.git" - -# Download Zinit, if it's not there yet if [ ! -d "$ZINIT_HOME" ]; then mkdir -p "$(dirname $ZINIT_HOME)" git clone https://github.com/zdharma-continuum/zinit.git "$ZINIT_HOME" fi +source "${ZINIT_HOME}/zinit.zsh" # --------------------------------------------------------------------------------------------------------------------- -# Zinit - Source & Plugins +# Zinit - Plugins # --------------------------------------------------------------------------------------------------------------------- -source "${ZINIT_HOME}/zinit.zsh" - -# Only load fancy plugins if not in SSH if [[ -z "$SSH_CONNECTION" && -z "$SSH_TTY" ]]; then - # Add in Powerlevel10k zinit ice depth=1 zinit light romkatv/powerlevel10k [[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh - # Add in zsh plugins zinit light zsh-users/zsh-syntax-highlighting zinit light zsh-users/zsh-completions zinit light zsh-users/zsh-autosuggestions zinit light Aloxaf/fzf-tab else - # Simple prompt for SSH PROMPT='%n@%m:%~ %# ' fi -# Add in snippets zinit snippet OMZP::git zinit snippet OMZP::sudo zinit snippet OMZP::command-not-found -# Load completions autoload -Uz compinit && compinit -eval "$(op completion zsh)"; compdef _op op +command -v op >/dev/null && eval "$(op completion zsh)" && compdef _op op zinit cdreplay -q -# Zshell settings +# --------------------------------------------------------------------------------------------------------------------- +# History # --------------------------------------------------------------------------------------------------------------------- HISTSIZE=5000 HISTFILE=~/.zsh_history @@ -82,40 +80,45 @@ zstyle ':completion:*' menu no zstyle ':fzf-tab:complete:cd:*' fzf-preview 'ls --color $realpath' zstyle ':fzf-tab:complete:__zoxide_z:*' fzf-preview 'ls --color $realpath' +# --------------------------------------------------------------------------------------------------------------------- +# PATH (personal scripts live in $DOTFILES_DIR/bin) +# --------------------------------------------------------------------------------------------------------------------- +[[ -d "$DOTFILES_DIR/bin" ]] && export PATH="$DOTFILES_DIR/bin:$PATH" + # --------------------------------------------------------------------------------------------------------------------- # Aliases # --------------------------------------------------------------------------------------------------------------------- alias ls='ls --color' alias vim='nvim' alias c='clear' -alias ssh="TERM_SIMPLE=1 ssh" +alias ssh='TERM_SIMPLE=1 ssh' -# --------------------------------------------------------------------------------------------------------------------- -# Shell Intergrations -# --------------------------------------------------------------------------------------------------------------------- -eval "$(fzf --zsh)" -eval "$(zoxide init --cmd cd zsh)" +# Manage the dotfiles repo from anywhere: `dotfiles update`, `dotfiles brew`, etc. +alias dotfiles="make -C $DOTFILES_DIR" # --------------------------------------------------------------------------------------------------------------------- -# Chezmoi +# Shell integrations # --------------------------------------------------------------------------------------------------------------------- -command -v chezmoi >/dev/null && . <(chezmoi completion zsh) +command -v fzf >/dev/null && eval "$(fzf --zsh)" +command -v zoxide >/dev/null && eval "$(zoxide init --cmd cd zsh)" # --------------------------------------------------------------------------------------------------------------------- # Orbstack # --------------------------------------------------------------------------------------------------------------------- source ~/.orbstack/shell/init.zsh 2>/dev/null || : -# Additional +# --------------------------------------------------------------------------------------------------------------------- +# Editor +# --------------------------------------------------------------------------------------------------------------------- export EDITOR=nvim -# bun completions -[ -s "$HOME/.bun/_bun" ] && source "$HOME/.bun/_bun" - -# bun +# --------------------------------------------------------------------------------------------------------------------- +# Bun +# --------------------------------------------------------------------------------------------------------------------- export BUN_INSTALL="$HOME/.bun" +[ -s "$HOME/.bun/_bun" ] && source "$HOME/.bun/_bun" -# Added by LM Studio CLI (lms) +# --------------------------------------------------------------------------------------------------------------------- +# Extra PATH entries (Postgres@17, LM Studio, Bun) +# --------------------------------------------------------------------------------------------------------------------- export PATH="/opt/homebrew/opt/postgresql@17/bin:$PATH:$HOME/.lmstudio/bin:$BUN_INSTALL/bin" -# End of LM Studio CLI section - diff --git a/dock_operations.sh b/lib/dock_operations.sh similarity index 100% rename from dock_operations.sh rename to lib/dock_operations.sh diff --git a/macos/apps.sh b/macos/apps.sh new file mode 100755 index 0000000..f2eca31 --- /dev/null +++ b/macos/apps.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# Per-app configuration. Re-runnable. +set -euo pipefail + +if [[ "$(uname -s)" != "Darwin" ]]; then + echo "macos/apps.sh: not running on macOS, skipping." >&2 + exit 0 +fi + +echo "==> Configuring apps" + +# --------------------------------------------------------------------------------------------------------------------- +# Amphetamine +# --------------------------------------------------------------------------------------------------------------------- +killall Amphetamine >/dev/null 2>&1 || true +defaults write com.if.Amphetamine "Start Session At Launch" -bool true + +# --------------------------------------------------------------------------------------------------------------------- +# GPG +# --------------------------------------------------------------------------------------------------------------------- +if command -v gpg-connect-agent >/dev/null; then + gpg-connect-agent reloadagent /bye >/dev/null 2>&1 || true +fi + +# --------------------------------------------------------------------------------------------------------------------- +# Homebrew auto-update (weekly) +# --------------------------------------------------------------------------------------------------------------------- +mkdir -p ~/Library/LaunchAgents +PLIST=~/Library/LaunchAgents/com.github.domt4.homebrew-autoupdate.plist +if [[ ! -f "$PLIST" ]]; then + touch "$PLIST" +fi +if command -v brew >/dev/null && brew commands | grep -q autoupdate; then + brew autoupdate delete >/dev/null 2>&1 || true + brew autoupdate start 604800 --upgrade >/dev/null +fi + +# --------------------------------------------------------------------------------------------------------------------- +# Mail +# --------------------------------------------------------------------------------------------------------------------- +killall Mail >/dev/null 2>&1 || true +# Copy email addresses as `foo@example.com`, not `Foo Bar ` +defaults write com.apple.mail AddressesIncludeNameOnPasteboard -bool false + +# --------------------------------------------------------------------------------------------------------------------- +# Safari (privacy hardening) +# --------------------------------------------------------------------------------------------------------------------- +killall Safari >/dev/null 2>&1 || true +defaults write com.apple.Safari UniversalSearchEnabled -bool false +defaults write com.apple.Safari SuppressSearchSuggestions -bool true +defaults write com.apple.Safari SendDoNotTrackHTTPHeader -bool true +defaults write com.apple.Safari AutoFillFromAddressBook -bool false +defaults write com.apple.Safari AutoFillPasswords -bool false +defaults write com.apple.Safari AutoFillCreditCardData -bool false +defaults write com.apple.Safari AutoFillMiscellaneousForms -bool false +defaults write com.apple.Safari InstallExtensionUpdatesAutomatically -bool true + +# --------------------------------------------------------------------------------------------------------------------- +# Terminal.app +# Configures defaults *without* launching Terminal (AppleScript would). +# Ghostty is the primary terminal -- configure that separately if needed. +# --------------------------------------------------------------------------------------------------------------------- +defaults write com.apple.Terminal "Default Window Settings" -string Basic +defaults write com.apple.Terminal "Startup Window Settings" -string Basic +defaults write com.apple.terminal StringEncodings -array 4 + +# Required so zsh completion doesn't complain about insecure directories +if command -v brew >/dev/null; then + chmod -R go-w "$(brew --prefix)/share" || true +fi + +# --------------------------------------------------------------------------------------------------------------------- +# TextEdit +# --------------------------------------------------------------------------------------------------------------------- +killall TextEdit >/dev/null 2>&1 || true +defaults write com.apple.TextEdit RichText -bool false +defaults write com.apple.TextEdit PlainTextEncoding -int 4 +defaults write com.apple.TextEdit PlainTextEncodingForWrite -int 4 + +echo "==> Apps configured" diff --git a/macos/defaults.sh b/macos/defaults.sh new file mode 100755 index 0000000..8683322 --- /dev/null +++ b/macos/defaults.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# Idempotent macOS system defaults. +# Re-run any time with `make macos`. +set -euo pipefail + +if [[ "$(uname -s)" != "Darwin" ]]; then + echo "macos/defaults.sh: not running on macOS, skipping." >&2 + exit 0 +fi + +echo "==> Applying macOS defaults" + +# Close System Settings so it doesn't overwrite our changes +osascript -e 'tell application "System Settings" to quit' >/dev/null 2>&1 || true +osascript -e 'tell application "System Preferences" to quit' >/dev/null 2>&1 || true + +# --------------------------------------------------------------------------------------------------------------------- +# Global +# --------------------------------------------------------------------------------------------------------------------- +defaults write com.apple.appleseed.FeedbackAssistant Autogather -bool false +defaults write -g NSAutomaticCapitalizationEnabled -bool false +defaults write -g NSAutomaticDashSubstitutionEnabled -bool true +defaults -currentHost write com.apple.ImageCapture disableHotPlug -bool true +defaults write NSGlobalDomain PMPrintingExpandedStateForPrint -bool true +defaults write NSGlobalDomain NSNavPanelExpandedStateForSaveMode -bool true + +# Tap-to-click +defaults write com.apple.driver.AppleBluetoothMultitouch.trackpad Clicking -bool true +defaults -currentHost write NSGlobalDomain com.apple.mouse.tapBehavior -int 1 +defaults write NSGlobalDomain com.apple.mouse.tapBehavior -int 1 + +# --------------------------------------------------------------------------------------------------------------------- +# Security +# --------------------------------------------------------------------------------------------------------------------- +# Enable Touch ID for sudo using /etc/pam.d/sudo_local (survives system updates on macOS 14+). +# Fall back to editing /etc/pam.d/sudo if sudo_local isn't supported. +if [[ -f /etc/pam.d/sudo_local.template ]]; then + if ! sudo grep -q 'pam_tid.so' /etc/pam.d/sudo_local 2>/dev/null; then + echo "Enabling Touch ID for sudo via /etc/pam.d/sudo_local" + sudo cp /etc/pam.d/sudo_local.template /etc/pam.d/sudo_local + sudo sed -i '' 's/^#\(auth.*pam_tid.so\)/\1/' /etc/pam.d/sudo_local + fi +elif ! sudo grep -q 'pam_tid.so' /etc/pam.d/sudo; then + echo "Enabling Touch ID for sudo via /etc/pam.d/sudo" + if ! command -v gsed >/dev/null; then + echo "ERROR: gsed (gnu-sed) is required. Install via 'brew install gnu-sed'." >&2 + exit 1 + fi + sudo gsed -i '2iauth sufficient pam_tid.so' /etc/pam.d/sudo +fi + +# Secure keyboard entry in Terminal-likes (only for apps that are installed) +defaults write -app Terminal SecureKeyboardEntry -bool true +if [[ -d "/Applications/iTerm.app" ]]; then + defaults write -app iTerm SecureKeyboardEntry -bool true +fi + +# Require an administrator password to access system-wide preferences +# https://www.tenable.com/audits/CIS_Apple_macOS_11_v2.0.0_L1 +TMP_PLIST=$(mktemp -t system.preferences.plist) +sudo security authorizationdb read system.preferences > "$TMP_PLIST" +sudo defaults write "$TMP_PLIST" shared -bool false +sudo security authorizationdb write system.preferences < "$TMP_PLIST" +rm -f "$TMP_PLIST" + +# --------------------------------------------------------------------------------------------------------------------- +# Finder +# --------------------------------------------------------------------------------------------------------------------- +defaults write com.apple.Finder AppleShowAllFiles -bool false +defaults write com.apple.finder QLEnableTextSelection -bool true +defaults write com.apple.finder FXDefaultSearchScope -string "SCcf" +defaults write com.apple.finder FXPreferredViewStyle -string "Nlsv" +defaults write com.apple.finder SearchRecentsSavedViewStyle -string "Nlsv" +defaults write com.apple.desktopservices DSDontWriteNetworkStores -bool true +defaults write com.apple.desktopservices DSDontWriteUSBStores -bool true +defaults write com.apple.finder EmptyTrashSecurely -bool true +killall Finder >/dev/null 2>&1 || true + +# --------------------------------------------------------------------------------------------------------------------- +# Firewall +# --------------------------------------------------------------------------------------------------------------------- +sudo defaults write /Library/Preferences/com.apple.alf globalstate -int 1 +sudo defaults write /Library/Preferences/com.apple.alf stealthenabled -int 1 + +# --------------------------------------------------------------------------------------------------------------------- +# Software Update +# --------------------------------------------------------------------------------------------------------------------- +sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticCheckEnabled -bool true +sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticDownload -bool true +sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticallyInstallMacOSUpdates -bool true +sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate ConfigDataInstall -bool true +sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate CriticalUpdateInstall -bool true +sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate ScheduleFrequency -int 1 +defaults write com.apple.commerce AutoUpdate -bool true + +echo "==> macOS defaults applied" diff --git a/macos/dock.sh b/macos/dock.sh new file mode 100755 index 0000000..0f0a5e7 --- /dev/null +++ b/macos/dock.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# Configure the Dock. Re-runnable: clears the dock then rebuilds it deterministically. +set -euo pipefail + +if [[ "$(uname -s)" != "Darwin" ]]; then + echo "macos/dock.sh: not running on macOS, skipping." >&2 + exit 0 +fi + +# Resolve repo dir (so we can source the dock helpers regardless of CWD) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_DIR="${DOTFILES_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}" + +# shellcheck source=../lib/dock_operations.sh +source "$REPO_DIR/lib/dock_operations.sh" + +echo "==> Configuring Dock" + +defaults write com.apple.dock orientation -string "bottom" +defaults write com.apple.dock tilesize -int 43 +defaults write com.apple.dock minimize-to-application -bool false +defaults write com.apple.dock launchanim -bool true +defaults write com.apple.dock show-process-indicators -bool true +defaults write com.apple.dock show-recents -bool false + +clear_dock + +apps=( + '/System/Cryptexes/App/System/Applications/Safari.app' + '/Applications/Visual Studio Code.app' + '/Applications/Ghostty.app' + '/System/Applications/Mail.app' + '/System/Applications/Calendar.app' + '/System/Applications/Music.app' +) +for app in "${apps[@]}"; do + add_app_to_dock "$app" +done + +add_folder_to_dock "$HOME/Downloads" --arrangement 3 --displayAs 0 --showAs 1 +add_folder_to_dock "$HOME/Developer" --arrangement 1 --displayAs 1 --showAs 2 + +killall Dock >/dev/null 2>&1 || true +echo "==> Dock configured" diff --git a/run_once_after_configure-apps-darwin.sh.tmpl b/run_once_after_configure-apps-darwin.sh.tmpl deleted file mode 100644 index 52b50a4..0000000 --- a/run_once_after_configure-apps-darwin.sh.tmpl +++ /dev/null @@ -1,101 +0,0 @@ -#!/bin/bash -{{ if (eq .chezmoi.os "darwin") -}} - -# --------------------------------------------------------------------------------------------------------------------- -# Amphetamine settings -# --------------------------------------------------------------------------------------------------------------------- -killall Amphetamine -# Start session as soon as Amphetamine starts -defaults write com.if.Amphetamine "Start Session At Launch" -bool true - -# # --------------------------------------------------------------------------------------------------------------------- -# # Docker settings -# # --------------------------------------------------------------------------------------------------------------------- -# # Disable automatic updates -# killall Docker -# defaults write com.docker.docker SUAutomaticallyUpdate -bool false -# defaults write com.docker.docker SUEnableAutomaticChecks -bool false -# open -a Docker - -# --------------------------------------------------------------------------------------------------------------------- -# GPG settings -# --------------------------------------------------------------------------------------------------------------------- -gpg-connect-agent reloadagent /bye - -# --------------------------------------------------------------------------------------------------------------------- -# Homebrew settings -# --------------------------------------------------------------------------------------------------------------------- -# https://github.com/Homebrew/homebrew-autoupdate/issues/10 -mkdir -p ~/Library/LaunchAgents -# https://github.com/Homebrew/homebrew-autoupdate/issues/14 -sudo touch ~/Library/LaunchAgents/com.github.domt4.homebrew-autoupdate.plist -sudo chown $(whoami) ~/Library/LaunchAgents/com.github.domt4.homebrew-autoupdate.plist -# Auto-upgrade apps every week -echo "Deleting any autoupdate plist before enabling autoupdate" -brew autoupdate delete && brew autoupdate start 604800 --upgrade - -# --------------------------------------------------------------------------------------------------------------------- -# Mail settings -# --------------------------------------------------------------------------------------------------------------------- -killall Mail -# Copy email addresses as `foo@example.com` instead of `Foo Bar ` in Mail.app -defaults write com.apple.mail AddressesIncludeNameOnPasteboard -bool false - -# --------------------------------------------------------------------------------------------------------------------- -# OneDrive settings -# --------------------------------------------------------------------------------------------------------------------- -# iPhone backups are located in OneDrive -# TODO: Add in Sharepoint shares -# mkdir -p ~/Library/Application\ Support/MobileSync -# ln -s ~/Library/CloudStorage/OneDrive-Personal/iPhone\ backup ~/Library/Application\ Support/MobileSync/Backup - -# --------------------------------------------------------------------------------------------------------------------- -# Safari settings -# --------------------------------------------------------------------------------------------------------------------- -killall Safari -# Privacy: don’t send search queries to Apple -defaults write com.apple.Safari UniversalSearchEnabled -bool false -defaults write com.apple.Safari SuppressSearchSuggestions -bool true -# Privacy: Enable “Do Not Track” -defaults write com.apple.Safari SendDoNotTrackHTTPHeader -bool true -# Disable AutoFill -defaults write com.apple.Safari AutoFillFromAddressBook -bool false -defaults write com.apple.Safari AutoFillPasswords -bool false -defaults write com.apple.Safari AutoFillCreditCardData -bool false -defaults write com.apple.Safari AutoFillMiscellaneousForms -bool false -# Update extensions automatically -defaults write com.apple.Safari InstallExtensionUpdatesAutomatically -bool true - -# --------------------------------------------------------------------------------------------------------------------- -# Terminal settings -# --------------------------------------------------------------------------------------------------------------------- -# Make sure Terminal is using the Basic profile -defaults write com.apple.Terminal "Default Window Settings" -string Basic -defaults write com.apple.Terminal "Startup Window Settings" -string Basic -# Only use UTF-8 -defaults write com.apple.terminal StringEncodings -array 4 -# Set font preferences -# To get the current font name, use: osascript -e "tell application \"Terminal\" to get the font name of window 1" -osascript -e "tell application \"Terminal\" to set font name of settings set \"Basic\" to \"MesloLGLNerdFontComplete-Regular\"" -osascript -e "tell application \"Terminal\" to set font size of settings set \"Basic\" to 18" -# https://docs.brew.sh/Shell-Completion#configuring-completions-in-zsh -chmod -R go-w "$(brew --prefix)/share" - -# --------------------------------------------------------------------------------------------------------------------- -# TextEdit settings -# --------------------------------------------------------------------------------------------------------------------- -killall TextEdit -# Set default TextEdit document format as plain text -defaults write com.apple.TextEdit "RichText" -bool "false" -# Open and save files as UTF-8 -defaults write com.apple.TextEdit PlainTextEncoding -int 4 -defaults write com.apple.TextEdit PlainTextEncodingForWrite -int 4 - -# --------------------------------------------------------------------------------------------------------------------- -# Make chezmoi use Git with SSH -# --------------------------------------------------------------------------------------------------------------------- -cd "{{ .chezmoi.sourceDir }}" -CHEZMOI_SSH_URL=$(git remote get-url origin | sed -Ene's#https://([^/]*)/([^/]*/.*.git)#git@\1:\2#p') -[[ -z $CHEZMOI_SSH_URL ]] || git remote set-url origin $CHEZMOI_SSH_URL - -{{ end -}} \ No newline at end of file diff --git a/run_once_before_1-prepare-system.sh.tmpl b/run_once_before_1-prepare-system.sh.tmpl deleted file mode 100644 index 36de09f..0000000 --- a/run_once_before_1-prepare-system.sh.tmpl +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -# Install Rosetta for Apple Silicon // ARM64 platforms -{{ if (and (eq .chezmoi.os "darwin") (eq .chezmoi.arch "arm64")) }} -echo "INSTALLING ROSETTA - PASSWORD MAY BE REQUIRED" -softwareupdate --install-rosetta --agree-to-license -{{ end }} diff --git a/run_once_before_2-configure-system-darwin.sh.tmpl b/run_once_before_2-configure-system-darwin.sh.tmpl deleted file mode 100644 index 1e6df0b..0000000 --- a/run_once_before_2-configure-system-darwin.sh.tmpl +++ /dev/null @@ -1,142 +0,0 @@ -#!/bin/bash -{{ if (eq .chezmoi.os "darwin") -}} - -# Inspired from https://github.com/politician/dotfiles/blob/main/run_once_before_2-configure-system-darwin.sh.tmpl - -# Force System prefrences to quit -osascript -e 'tell application "System Preferences" to quit' -# Source dock functions -source "./dock_operations.sh" - -# --------------------------------------------------------------------------------------------------------------------- -# Global settings -# --------------------------------------------------------------------------------------------------------------------- - -echo "Modifying macOS Global Settings" - -# Do not autogather large files when submitting a feedback report -defaults write com.apple.appleseed.FeedbackAssistant "Autogather" -bool "false" -# Disable automatic capitalization -defaults write -g NSAutomaticCapitalizationEnabled -bool false -# Enable smart dashes -defaults write -g NSAutomaticDashSubstitutionEnabled -bool true -# Prevent Photos from opening automatically when devices are plugged in -defaults -currentHost write com.apple.ImageCapture disableHotPlug -bool true && killall Photos -# Save/Print modals are auto-expanded by default -defaults write NSGlobalDomain PMPrintingExpandedStateForPrint -bool true -defaults write NSGlobalDomain NSNavPanelExpandedStateForSaveMode -bool true -# Trackpad: enable tap to click for this user and for the login screen -defaults write com.apple.driver.AppleBluetoothMultitouch.trackpad Clicking -bool true -defaults -currentHost write NSGlobalDomain com.apple.mouse.tapBehavior -int 1 -defaults write NSGlobalDomain com.apple.mouse.tapBehavior -int 1 - -# --------------------------------------------------------------------------------------------------------------------- -# Security settings -# --------------------------------------------------------------------------------------------------------------------- -# Enable TouchID for sudo -! grep -q pam_tid.so /etc/pam.d/sudo && sudo gsed -i '2iauth sufficient pam_tid.so' /etc/pam.d/sudo -# Ensure Secure Keyboard Entry in Terminal is enabled -defaults write -app Terminal SecureKeyboardEntry -bool true -defaults write -app iTerm SecureKeyboardEntry -bool true - -# --------------------------------------------------------------------------------------------------------------------- -# AuthorizationDB settings -# The authorizationdb settings cannot be written to directly, so the plist must be exported out to temporary file -# --------------------------------------------------------------------------------------------------------------------- -# Export AuthorizationDB settings to temporary file -sudo security authorizationdb read system.preferences > /tmp/system.preferences.plist - -# Require an administrator password to access system-wide preferences -# https://www.tenable.com/audits/CIS_Apple_macOS_11_v2.0.0_L1 -sudo defaults write /tmp/system.preferences.plist shared -bool false - -# Import AuthorizationDB settings from temporary file -sudo security authorizationdb write system.preferences < /tmp/system.preferences.plist - -# --------------------------------------------------------------------------------------------------------------------- -# Dock settings -# --------------------------------------------------------------------------------------------------------------------- -# Set dock position -defaults write com.apple.dock orientation -string "bottom" -# Set the icon size of Dock items in pixels -defaults write com.apple.dock "tilesize" -int 43 -# Minimize windows into their application’s icon -defaults write com.apple.dock minimize-to-application -bool false -# Enable launch animation -defaults write com.apple.dock launchanim -bool true -# Show indicator lights for open applications in the Dock -defaults write com.apple.dock show-process-indicators -bool true -# Don’t show recent applications in Dock -defaults write com.apple.dock show-recents -bool false -# Clear Dock -clear_dock -# Declare apps to set to dock -declare -a apps=( - '/System/Cryptexes/App/System/Applications/Safari.app' - '/Applications/Visual Studio Code.app' - '/Applications/Ghostty.app' - '/System/Applications/Mail.app' - '/System/Applications/Calendar.app' - '/System/Applications/Music.app' -); -# Add apps to dock -for app in "${apps[@]}"; do - add_app_to_dock "$app" -done -# Add folders to dock (not done in an array as I need to pass options to add_folder_to_dock) -add_folder_to_dock "$HOME/Downloads" --arrangement 3 --displayAs 0 --showAs 1 -add_folder_to_dock "$HOME/Developer" --arrangement 1 --displayAs 1 --showAs 2 -# Kill Dock -killall Dock - -# --------------------------------------------------------------------------------------------------------------------- -# Finder settings -# --------------------------------------------------------------------------------------------------------------------- -# Do not show hidden files -defaults write com.apple.Finder "AppleShowAllFiles" -bool "false" -# Allowing text selection in Quick Look/Preview in Finder by default -defaults write com.apple.finder QLEnableTextSelection -bool true -# When performing a search, search the current folder by default (WHY IS THIS NOT THE DEFAULT?!) -defaults write com.apple.finder FXDefaultSearchScope -string "SCcf" -# Use list view in all Finder windows by default -defaults write com.apple.finder FXPreferredViewStyle -string "Nlsv" -defaults write com.apple.finder SearchRecentsSavedViewStyle -string "Nlsv" -# Disable creation of metadata files on external volumes -defaults write com.apple.desktopservices DSDontWriteNetworkStores -bool true -defaults write com.apple.desktopservices DSDontWriteUSBStores -bool true -# Empty Trash securely by default -defaults write com.apple.finder EmptyTrashSecurely -bool true - -killall Finder - -# --------------------------------------------------------------------------------------------------------------------- -# Firewall settings -# --------------------------------------------------------------------------------------------------------------------- -# Enable firewall -sudo defaults write /Library/Preferences/com.apple.alf globalstate -int 1 -sudo defaults write /Library/Preferences/com.apple.alf stealthenabled -int 1 - -#launchctl unload /System/Library/LaunchAgents/com.apple.alf.useragent.plist -#sudo launchctl unload /System/Library/LaunchDaemons/com.apple.alf.agent.plist -#sudo launchctl load /System/Library/LaunchDaemons/com.apple.alf.agent.plist -#launchctl load /System/Library/LaunchAgents/com.apple.alf.useragent.plist - -# --------------------------------------------------------------------------------------------------------------------- -# Software Update settings -# --------------------------------------------------------------------------------------------------------------------- -# Automatically check for updates -sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticCheckEnabled -bool true -# Download updates automatically in the background -sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticDownload -bool true -# Install macos updates automatically -sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticallyInstallMacOSUpdates -bool true -# Install system data file updates automatically -sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate ConfigDataInstall -bool true -# Install critical security updates automatically -sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate CriticalUpdateInstall -bool true -# Check for software updates daily, not just once per week -sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate ScheduleFrequency -int 1 -# Install app updates automatically -defaults write com.apple.commerce AutoUpdate -bool true - -{{ end -}} diff --git a/run_once_before_3-install-packages-darwin.sh.tmpl b/run_once_before_3-install-packages-darwin.sh.tmpl deleted file mode 100644 index 32bfa9b..0000000 --- a/run_once_before_3-install-packages-darwin.sh.tmpl +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -{{ if (eq .chezmoi.os "darwin") -}} - -# Close App Store apps in case this script updates them (this should be anything mas installs from the brewfile) -killall "1Password for Safari" "Amphetamine" "Apple Configurator" "Broadcasts" "Codye" "Compressor" "Craft" "Final Cut Pro" "GarageBand" "iMovie" "Keynote" "Logic Pro" "Microsoft Excel" "Microsoft Word" "Motion" "MusicHarbor" "Numbers" "OneDrive" "Pages" "Playgrounds" "Reeder" "Speediness" "Xcode" "WhatsApp" - -brew bundle - -{{ end -}} - -# Install latest stable version of Node.js -volta install node - -# Install latest versions of global Node.js packages -npm install -g\ - prettier@latest\ - eslint@latest\ - typescript@latest\ - turbo@latest\ - yarn@latest\ - pnpm@latest \ No newline at end of file diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh new file mode 100755 index 0000000..81ef45f --- /dev/null +++ b/scripts/bootstrap.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Install Xcode Command Line Tools and Homebrew if they're not already present. +# Safe to re-run. +set -euo pipefail + +if [[ "$(uname -s)" != "Darwin" ]]; then + echo "scripts/bootstrap.sh: not running on macOS, skipping." >&2 + exit 0 +fi + +# --------------------------------------------------------------------------------------------------------------------- +# Xcode Command Line Tools +# --------------------------------------------------------------------------------------------------------------------- +if ! xcode-select -p >/dev/null 2>&1; then + echo "==> Installing Xcode Command Line Tools (a GUI prompt will appear)" + xcode-select --install + echo " Re-run 'make install' once the Command Line Tools finish installing." + exit 1 +fi + +# --------------------------------------------------------------------------------------------------------------------- +# Rosetta 2 (Apple Silicon only) +# --------------------------------------------------------------------------------------------------------------------- +if [[ "$(uname -m)" == "arm64" ]]; then + if ! /usr/bin/pgrep -q oahd; then + echo "==> Installing Rosetta 2" + softwareupdate --install-rosetta --agree-to-license + fi +fi + +# --------------------------------------------------------------------------------------------------------------------- +# Homebrew +# --------------------------------------------------------------------------------------------------------------------- +if ! command -v brew >/dev/null; then + echo "==> Installing Homebrew" + NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +fi + +# Add brew to PATH for the current shell (also handled by .zshrc afterwards) +if [[ -x /opt/homebrew/bin/brew ]]; then + eval "$(/opt/homebrew/bin/brew shellenv)" +elif [[ -x /usr/local/bin/brew ]]; then + eval "$(/usr/local/bin/brew shellenv)" +fi + +echo "==> Bootstrap complete" diff --git a/scripts/doctor.sh b/scripts/doctor.sh new file mode 100755 index 0000000..aef903f --- /dev/null +++ b/scripts/doctor.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# Diagnose obvious setup problems. +set -euo pipefail + +PASS="\033[32m✓\033[0m" +FAIL="\033[31m✗\033[0m" +WARN="\033[33m!\033[0m" + +problems=0 + +check() { + local label="$1" + local cmd="$2" + if eval "$cmd" >/dev/null 2>&1; then + printf "$PASS %s\n" "$label" + else + printf "$FAIL %s\n" "$label" + problems=$((problems + 1)) + fi +} + +warn_if_missing() { + local label="$1" + local cmd="$2" + if eval "$cmd" >/dev/null 2>&1; then + printf "$PASS %s\n" "$label" + else + printf "$WARN %s\n" "$label" + fi +} + +echo "Required tools" +check "brew installed" "command -v brew" +check "stow installed" "command -v stow" +check "gsed (gnu-sed) installed" "command -v gsed" +check "gh (GitHub CLI) installed" "command -v gh" +check "nvim installed" "command -v nvim" +check "delta installed" "command -v delta" + +echo +echo "Optional tools" +warn_if_missing "node installed" "command -v node" +warn_if_missing "bun installed" "command -v bun" +warn_if_missing "pnpm installed" "command -v pnpm" +warn_if_missing "op (1Password CLI)" "command -v op" + +echo +echo "Symlinks" +for f in ~/.zshrc ~/.gitconfig ~/.p10k.zsh ~/.gitignore_global ~/.ssh/config ~/.config/nvim/init.lua; do + if [[ -L "$f" ]]; then + printf "$PASS %s -> %s\n" "$f" "$(readlink "$f")" + elif [[ -e "$f" ]]; then + printf "$WARN %s exists but is not a symlink\n" "$f" + else + printf "$FAIL %s missing\n" "$f" + problems=$((problems + 1)) + fi +done + +echo +echo "Filesystem" +warn_if_missing "~/Developer exists" "[[ -d $HOME/Developer ]]" + +echo +if [[ $problems -eq 0 ]]; then + printf "$PASS No problems found.\n" +else + printf "$FAIL %d problem(s) found.\n" "$problems" + exit 1 +fi