mirror of
https://github.com/boxpositron/absolute-dotfiles.git
synced 2026-10-24 08:23:19 +00:00
feat(setup): rewrite setup-mac.sh around Brewfiles with idempotent steps
The old script stopped on a syntax error (unclosed if blocks) and had drifted from the machine: it installed Anaconda, AeroSpace and Oh My Posh, and linked nothing. - Eight named steps (xcode homebrew brew links shell runtimes ai apps) that check what is in place first, run on their own by name, and preview with --dry-run; a failed step is reported while the rest run - Brewfile holds the CLI tools, fonts and terminals, Brewfile.apps the GUI casks and App Store apps; installs never upgrade existing packages and skip apps already installed outside Homebrew - links symlinks the configs and .local/bin scripts, backing up what it replaces, and fetches the nvim submodule only on a fresh clone - Guard .zshrc's ~/.deno/env source for machines without it - Document the steps in the README
This commit is contained in:
@@ -322,7 +322,7 @@ if [ -d "$HOME/neovim/bin" ]; then
|
|||||||
export PATH="$HOME/neovim/bin:$PATH"
|
export PATH="$HOME/neovim/bin:$PATH"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
. "$HOME/.deno/env"
|
[ -f "$HOME/.deno/env" ] && . "$HOME/.deno/env"
|
||||||
|
|
||||||
export NVM_DIR="$HOME/.config/nvm"
|
export NVM_DIR="$HOME/.config/nvm"
|
||||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
|
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
|
||||||
|
|||||||
@@ -0,0 +1,172 @@
|
|||||||
|
# Command-line tools, fonts and terminals. Installed by `./setup-mac.sh brew`.
|
||||||
|
# GUI apps and App Store apps live in Brewfile.apps.
|
||||||
|
# To see what is installed but missing here: brew bundle dump --file=- | diff Brewfile -
|
||||||
|
|
||||||
|
tap "bufbuild/buf"
|
||||||
|
tap "gu1llaum-3/sshm"
|
||||||
|
tap "hashicorp/tap"
|
||||||
|
tap "leoafarias/fvm"
|
||||||
|
tap "mongodb/brew"
|
||||||
|
tap "oven-sh/bun"
|
||||||
|
tap "pbkit/tap"
|
||||||
|
tap "reoclo/tap"
|
||||||
|
tap "teamookla/speedtest"
|
||||||
|
|
||||||
|
# Shell, prompt and terminal tools (.zshrc and .tmux.conf rely on these)
|
||||||
|
brew "zsh-syntax-highlighting"
|
||||||
|
brew "starship"
|
||||||
|
brew "zoxide"
|
||||||
|
brew "direnv"
|
||||||
|
brew "fzf"
|
||||||
|
brew "eza"
|
||||||
|
brew "bat"
|
||||||
|
brew "grc"
|
||||||
|
brew "tmux"
|
||||||
|
brew "zellij"
|
||||||
|
brew "yazi"
|
||||||
|
brew "mosh"
|
||||||
|
brew "openssh"
|
||||||
|
brew "gu1llaum-3/sshm/sshm", trusted: true
|
||||||
|
|
||||||
|
# Everyday CLI
|
||||||
|
brew "coreutils"
|
||||||
|
brew "curl"
|
||||||
|
brew "wget"
|
||||||
|
brew "jq"
|
||||||
|
brew "ripgrep"
|
||||||
|
brew "fd"
|
||||||
|
brew "tree"
|
||||||
|
brew "watch"
|
||||||
|
brew "htop"
|
||||||
|
brew "dust"
|
||||||
|
brew "telnet"
|
||||||
|
brew "stow"
|
||||||
|
brew "sevenzip"
|
||||||
|
brew "gnupg"
|
||||||
|
brew "mas" # App Store installs in Brewfile.apps
|
||||||
|
|
||||||
|
# Git
|
||||||
|
brew "gh"
|
||||||
|
brew "tea"
|
||||||
|
brew "lazygit"
|
||||||
|
brew "git-filter-repo"
|
||||||
|
brew "gitleaks"
|
||||||
|
brew "colordiff"
|
||||||
|
brew "diffstat"
|
||||||
|
|
||||||
|
# Editors and code tools
|
||||||
|
brew "neovim"
|
||||||
|
brew "luarocks"
|
||||||
|
brew "ast-grep"
|
||||||
|
brew "silicon"
|
||||||
|
brew "just"
|
||||||
|
brew "bufbuild/buf/buf", trusted: true
|
||||||
|
brew "pbkit/tap/pbkit", trusted: true
|
||||||
|
|
||||||
|
# Languages and version managers
|
||||||
|
brew "asdf"
|
||||||
|
brew "pyenv"
|
||||||
|
brew "pyenv-virtualenv"
|
||||||
|
brew "python@3.11"
|
||||||
|
brew "python@3.12"
|
||||||
|
brew "ruby", link: false
|
||||||
|
brew "go"
|
||||||
|
brew "deno"
|
||||||
|
brew "oven-sh/bun/bun", trusted: true
|
||||||
|
brew "pnpm"
|
||||||
|
brew "uv"
|
||||||
|
brew "leoafarias/fvm/fvm", trusted: true
|
||||||
|
|
||||||
|
# Python tooling
|
||||||
|
brew "black"
|
||||||
|
brew "ruff"
|
||||||
|
brew "mypy"
|
||||||
|
brew "ipython"
|
||||||
|
brew "python-lsp-server"
|
||||||
|
brew "cookiecutter"
|
||||||
|
|
||||||
|
# Build toolchain and native libraries
|
||||||
|
brew "cmake"
|
||||||
|
brew "gcc"
|
||||||
|
brew "lld"
|
||||||
|
brew "libffi"
|
||||||
|
brew "libraw"
|
||||||
|
brew "librist"
|
||||||
|
brew "libssh"
|
||||||
|
brew "grpc"
|
||||||
|
brew "portaudio"
|
||||||
|
brew "tcl-tk"
|
||||||
|
brew "cryptography"
|
||||||
|
brew "python-setuptools"
|
||||||
|
|
||||||
|
# Containers, CI, cloud and deploy
|
||||||
|
brew "lazydocker"
|
||||||
|
brew "dive"
|
||||||
|
brew "hadolint"
|
||||||
|
brew "trivy"
|
||||||
|
brew "nixpacks"
|
||||||
|
brew "act"
|
||||||
|
brew "actionlint"
|
||||||
|
brew "woodpecker-cli"
|
||||||
|
brew "awscli"
|
||||||
|
brew "doctl"
|
||||||
|
brew "cloudflared"
|
||||||
|
brew "vercel"
|
||||||
|
brew "pscale"
|
||||||
|
brew "hashicorp/tap/terraform", trusted: true
|
||||||
|
brew "bitwarden-cli"
|
||||||
|
brew "reoclo/tap/reoclo", trusted: true
|
||||||
|
|
||||||
|
# Databases, servers and API clients
|
||||||
|
brew "mongosh"
|
||||||
|
brew "mongodb/brew/mongodb-community", trusted: true
|
||||||
|
brew "mysql"
|
||||||
|
brew "mysql-client@8.4"
|
||||||
|
brew "redis"
|
||||||
|
brew "httpd"
|
||||||
|
brew "posting"
|
||||||
|
|
||||||
|
# Media, documents and images
|
||||||
|
brew "ffmpeg"
|
||||||
|
brew "mpv"
|
||||||
|
brew "imagemagick"
|
||||||
|
brew "graphicsmagick"
|
||||||
|
brew "tesseract"
|
||||||
|
brew "ghostscript"
|
||||||
|
brew "poppler"
|
||||||
|
brew "weasyprint"
|
||||||
|
brew "pngquant"
|
||||||
|
brew "potrace"
|
||||||
|
brew "chafa"
|
||||||
|
brew "folderify"
|
||||||
|
|
||||||
|
# Apple and mobile builds
|
||||||
|
brew "xcodegen"
|
||||||
|
brew "xcbeautify"
|
||||||
|
brew "create-dmg"
|
||||||
|
|
||||||
|
# Hardware, installers and disk images
|
||||||
|
brew "arduino-cli"
|
||||||
|
brew "makensis"
|
||||||
|
brew "wimlib"
|
||||||
|
brew "cabextract"
|
||||||
|
brew "cdrtools"
|
||||||
|
|
||||||
|
# Networking
|
||||||
|
brew "tor"
|
||||||
|
brew "teamookla/speedtest/speedtest", trusted: true
|
||||||
|
|
||||||
|
# Fonts, terminals and CLI apps
|
||||||
|
cask "font-fira-code-nerd-font"
|
||||||
|
cask "font-hack-nerd-font"
|
||||||
|
cask "font-symbols-only-nerd-font"
|
||||||
|
cask "ghostty"
|
||||||
|
cask "wezterm"
|
||||||
|
cask "1password-cli"
|
||||||
|
cask "git-credential-manager"
|
||||||
|
cask "ngrok"
|
||||||
|
|
||||||
|
# Go and uv tools
|
||||||
|
go "golang.org/x/tools/gopls"
|
||||||
|
uv "headroom-ai"
|
||||||
|
uv "vectorcode"
|
||||||
+119
@@ -0,0 +1,119 @@
|
|||||||
|
# GUI apps and App Store apps. Installed by `./setup-mac.sh apps`.
|
||||||
|
# Sign in to the App Store first, or the mas entries fail.
|
||||||
|
# Apps already in /Applications that Homebrew did not install are skipped by
|
||||||
|
# setup-mac.sh, so this is safe to run on a Mac that already has them.
|
||||||
|
# Install by hand (no usable cask): Bartender 6 (the cask is Bartender 7),
|
||||||
|
# FileZilla, VMware Fusion and YubiKey Manager.
|
||||||
|
|
||||||
|
tap "glinford/tap"
|
||||||
|
tap "johannesnagl/tap"
|
||||||
|
|
||||||
|
# Development
|
||||||
|
cask "docker-desktop"
|
||||||
|
cask "zed"
|
||||||
|
cask "android-studio"
|
||||||
|
cask "bruno"
|
||||||
|
cask "beekeeper-studio"
|
||||||
|
cask "mongodb-compass"
|
||||||
|
cask "redis-insight"
|
||||||
|
cask "charles"
|
||||||
|
cask "cyberduck"
|
||||||
|
cask "chromium"
|
||||||
|
cask "johannesnagl/tap/showmd", trusted: true
|
||||||
|
|
||||||
|
# AI and agents
|
||||||
|
cask "conductor"
|
||||||
|
cask "claude"
|
||||||
|
cask "opencode-desktop"
|
||||||
|
cask "lm-studio"
|
||||||
|
cask "ollama-app"
|
||||||
|
cask "emdash"
|
||||||
|
cask "open-design"
|
||||||
|
cask "block-buzz"
|
||||||
|
cask "typewhisper"
|
||||||
|
|
||||||
|
# Browsers
|
||||||
|
cask "brave-browser"
|
||||||
|
cask "google-chrome"
|
||||||
|
cask "firefox"
|
||||||
|
cask "librewolf"
|
||||||
|
cask "zen"
|
||||||
|
|
||||||
|
# Productivity
|
||||||
|
cask "1password"
|
||||||
|
cask "raycast"
|
||||||
|
cask "linear"
|
||||||
|
cask "obsidian"
|
||||||
|
cask "ticktick"
|
||||||
|
cask "granola"
|
||||||
|
cask "mailspring"
|
||||||
|
cask "libreoffice"
|
||||||
|
cask "affinity"
|
||||||
|
cask "numi"
|
||||||
|
cask "mmex"
|
||||||
|
|
||||||
|
# Communication
|
||||||
|
cask "discord"
|
||||||
|
cask "element"
|
||||||
|
cask "telegram-desktop"
|
||||||
|
cask "whatsapp"
|
||||||
|
cask "microsoft-teams"
|
||||||
|
|
||||||
|
# Menu bar and system utilities
|
||||||
|
cask "hiddenbar"
|
||||||
|
cask "meetingbar"
|
||||||
|
cask "stats"
|
||||||
|
cask "monitorcontrol"
|
||||||
|
cask "rectangle-pro"
|
||||||
|
cask "appcleaner"
|
||||||
|
cask "latest"
|
||||||
|
cask "glinford/tap/dns-easy-switcher", trusted: true
|
||||||
|
cask "localsend"
|
||||||
|
cask "logi-options+"
|
||||||
|
cask "radio-silence"
|
||||||
|
cask "reikey"
|
||||||
|
cask "yubico-authenticator"
|
||||||
|
cask "protonvpn"
|
||||||
|
cask "teamviewer"
|
||||||
|
cask "balenaetcher"
|
||||||
|
|
||||||
|
# Media and games
|
||||||
|
cask "spotify"
|
||||||
|
cask "vlc"
|
||||||
|
cask "obs"
|
||||||
|
cask "jellyfin-media-player"
|
||||||
|
cask "transmission"
|
||||||
|
cask "steam"
|
||||||
|
cask "minecraft"
|
||||||
|
|
||||||
|
# App Store
|
||||||
|
mas "1Password for Safari", id: 1569813296
|
||||||
|
mas "AdBlock", id: 1402042596
|
||||||
|
mas "Amphetamine", id: 937984704
|
||||||
|
mas "Bitwarden", id: 1352778147
|
||||||
|
mas "Boop", id: 1518425043
|
||||||
|
mas "Color Picker", id: 1545870783
|
||||||
|
mas "DeArrow", id: 6451469297
|
||||||
|
mas "Disk Drill Media Recovery", id: 431224317
|
||||||
|
mas "DuckDuckGo Privacy for Safari", id: 1482920575
|
||||||
|
mas "Fresco", id: 1251572132
|
||||||
|
mas "GarageBand", id: 682658836
|
||||||
|
mas "Gestimer", id: 990588172
|
||||||
|
mas "Keka", id: 470158793
|
||||||
|
mas "Keynote", id: 409183694
|
||||||
|
mas "Keynote Creator Studio", id: 361285480
|
||||||
|
mas "Maccy", id: 1527619437
|
||||||
|
mas "Microsoft Excel", id: 462058435
|
||||||
|
mas "Microsoft PowerPoint", id: 462062816
|
||||||
|
mas "Microsoft Word", id: 462054704
|
||||||
|
mas "Numbers", id: 409203825
|
||||||
|
mas "Numbers Creator Studio", id: 361304891
|
||||||
|
mas "Pages", id: 409201541
|
||||||
|
mas "Pages Creator Studio", id: 361309726
|
||||||
|
mas "Pixelmator Pro", id: 1289583905
|
||||||
|
mas "Slack", id: 803453959
|
||||||
|
mas "SponsorBlock", id: 1573461917
|
||||||
|
mas "Tailscale", id: 1475387142
|
||||||
|
mas "Termius 2", id: 1176074088
|
||||||
|
mas "TextSniper", id: 1528890965
|
||||||
|
mas "Xcode", id: 497799835
|
||||||
@@ -33,13 +33,10 @@ This repository contains personal dotfiles for configuring a complete developmen
|
|||||||
|
|
||||||
Before installing these dotfiles, ensure you have the following:
|
Before installing these dotfiles, ensure you have the following:
|
||||||
|
|
||||||
- **Operating System**: macOS (Darwin platform)
|
- **Operating System**: macOS (Apple Silicon or Intel)
|
||||||
- **Package Manager**: [Homebrew](https://brew.sh/) installed
|
- **Git**: To clone this repository. macOS offers to install it with the Command Line Tools the first time you run `git`
|
||||||
- **Shell**: Zsh (default on modern macOS)
|
|
||||||
- **Git**: Version control system
|
`setup-mac.sh` installs everything else: the Command Line Tools, Homebrew, Node.js, Python, Rust and the rest.
|
||||||
- **Node.js**: Required for many Neovim plugins and LSP servers
|
|
||||||
- **Python**: Required for certain development tools and Neovim plugins
|
|
||||||
- **Rust**: (Optional) Required for some tools like ripgrep, fd, etc.
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -53,17 +50,28 @@ Before installing these dotfiles, ensure you have the following:
|
|||||||
cd ~/dotfiles
|
cd ~/dotfiles
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Run the setup script**:
|
2. **Preview, then run the setup script**:
|
||||||
```bash
|
```bash
|
||||||
|
./setup-mac.sh --dry-run
|
||||||
./setup-mac.sh
|
./setup-mac.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Create symbolic links** (if not handled by setup script):
|
The script is safe to rerun: each step checks what is already in place and only does the missing work. A failed step is reported at the end while the other steps still run. Name steps to run only those, for example `./setup-mac.sh links` after adding a config.
|
||||||
```bash
|
|
||||||
ln -s ~/dotfiles/.config ~/.config
|
| Step | What it does |
|
||||||
ln -s ~/dotfiles/.zshrc ~/.zshrc
|
|------|--------------|
|
||||||
ln -s ~/dotfiles/.tmux.conf ~/.tmux.conf
|
| `xcode` | Installs the Xcode Command Line Tools |
|
||||||
```
|
| `homebrew` | Installs Homebrew and loads it from `~/.zprofile` |
|
||||||
|
| `brew` | Installs the CLI tools, fonts and terminals in `Brewfile` |
|
||||||
|
| `links` | Fetches the Neovim submodule on a fresh clone and symlinks the configs and `.local/bin` scripts into `$HOME`, backing up anything it replaces to `~/.local/state/dotfiles/backups/` |
|
||||||
|
| `shell` | Installs oh-my-zsh and the tmux plugins |
|
||||||
|
| `runtimes` | Node with nvm and the global npm packages, Rust, Python with pyenv, poetry, and Flutter with fvm |
|
||||||
|
| `ai` | Installs omo, Claude Code, OpenCode and Codex, then runs `link-ai-configs.sh` |
|
||||||
|
| `apps` | Installs the GUI and App Store apps in `Brewfile.apps` |
|
||||||
|
|
||||||
|
`brew` and `apps` never upgrade packages that are already installed. `apps` skips apps already in `/Applications` that Homebrew did not install, and its App Store entries need you signed in to the App Store first. To list what is installed but missing from `Brewfile`, run `brew bundle dump --file=- | diff Brewfile -`.
|
||||||
|
|
||||||
|
A few things still need you afterwards, such as signing in to the App Store and the AI tools, and turning on the 1Password SSH agent. The script lists them when it finishes.
|
||||||
|
|
||||||
### Manual Installation
|
### Manual Installation
|
||||||
|
|
||||||
@@ -83,7 +91,7 @@ ln -s ~/dotfiles/.zshrc ~/.zshrc
|
|||||||
|
|
||||||
### AI configuration restore
|
### AI configuration restore
|
||||||
|
|
||||||
After installing Node.js, OpenCode, Claude Code, and OMC, restore the selected AI configuration files separately from the package installer:
|
The `ai` step of `setup-mac.sh` runs this script. To run it on its own after installing Node.js, OpenCode, Claude Code, and OMC:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bash ~/dotfiles/link-ai-configs.sh --dry-run
|
bash ~/dotfiles/link-ai-configs.sh --dry-run
|
||||||
@@ -99,14 +107,7 @@ The script creates individual relative symlinks, skips existing correct links, a
|
|||||||
|
|
||||||
Credentials, `settings.local.json`, plugin installations, unrelated user skills, histories, caches, and runtime state are not copied into this repository or managed by this script. The selected Impeccable, Vercel web-design-guidelines and frontend-workflow skills are managed individually; see [the GPT visual workflow](.config/opencode/README.md) for provenance, verification and rollback. Install other desired plugins and portable skills separately. OpenCode's local MCP servers are referenced through `{env:HOME}`, so the paths are portable, but the servers themselves (headroom, agcanvas, uvx, ast-grep-mcp, portfolio-mcp) must be installed at those locations on a new machine.
|
Credentials, `settings.local.json`, plugin installations, unrelated user skills, histories, caches, and runtime state are not copied into this repository or managed by this script. The selected Impeccable, Vercel web-design-guidelines and frontend-workflow skills are managed individually; see [the GPT visual workflow](.config/opencode/README.md) for provenance, verification and rollback. Install other desired plugins and portable skills separately. OpenCode's local MCP servers are referenced through `{env:HOME}`, so the paths are portable, but the servers themselves (headroom, agcanvas, uvx, ast-grep-mcp, portfolio-mcp) must be installed at those locations on a new machine.
|
||||||
|
|
||||||
The OmO hooks, slash commands and workspace scripts are not linked by the script yet. Link them by hand:
|
The OmO hooks and slash commands (`~/.omo/agent/hooks.json` and `~/.omo/agent/prompts`) and the workspace scripts in `~/.local/bin` are linked by the `links` step of `setup-mac.sh` instead.
|
||||||
|
|
||||||
```bash
|
|
||||||
ln -s ../../dotfiles/.config/omo/hooks.json ~/.omo/agent/hooks.json
|
|
||||||
ln -s ../../dotfiles/.config/omo/prompts ~/.omo/agent/prompts
|
|
||||||
ln -s ../../dotfiles/.local/bin/owt ~/.local/bin/owt
|
|
||||||
ln -s ../../dotfiles/.local/bin/omo-notify ~/.local/bin/omo-notify
|
|
||||||
```
|
|
||||||
|
|
||||||
Restart OpenCode and Claude Code after applying changes. On first use, Claude may ask to approve the shared preferences import. Updates can rewrite generated configuration or replace symlinks: review the live files and repository diff before rerunning the link script, which backs up divergent live files rather than merging them. No plugin versions or model choices are changed by this restore step.
|
Restart OpenCode and Claude Code after applying changes. On first use, Claude may ask to approve the shared preferences import. Updates can rewrite generated configuration or replace symlinks: review the live files and repository diff before rerunning the link script, which backs up divergent live files rather than merging them. No plugin versions or model choices are changed by this restore step.
|
||||||
|
|
||||||
@@ -249,11 +250,7 @@ Custom scripts and binaries are stored in `.local/bin/`:
|
|||||||
- `omo-notify`: macOS notification for OmO's Stop and Notification hooks
|
- `omo-notify`: macOS notification for OmO's Stop and Notification hooks
|
||||||
- `docker-nuke`: Force-restart a wedged Docker Desktop, capturing diagnostics to `~/.local/state/docker-nuke/` first (`--dry-run` to preview, `--no-restart` to leave it down, `--deep` for root helpers)
|
- `docker-nuke`: Force-restart a wedged Docker Desktop, capturing diagnostics to `~/.local/state/docker-nuke/` first (`--dry-run` to preview, `--no-restart` to leave it down, `--deep` for root helpers)
|
||||||
|
|
||||||
Ensure this directory is in your PATH:
|
The `links` step of `setup-mac.sh` links each script into `~/.local/bin`, which `.zshrc` puts on your PATH. `pbcopy` and `pbpaste` are OSC 52 clipboard shims for headless servers, so they are not linked on macOS.
|
||||||
|
|
||||||
```bash
|
|
||||||
export PATH="$HOME/dotfiles/.local/bin:$PATH"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Local Servers
|
### Local Servers
|
||||||
|
|
||||||
|
|||||||
+417
-198
@@ -1,239 +1,458 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
# Set up a Mac from this dotfiles repo. Safe to rerun: every step checks what
|
||||||
|
# is already in place and only does the missing work.
|
||||||
|
#
|
||||||
|
# ./setup-mac.sh run every step in order
|
||||||
|
# ./setup-mac.sh links shell run only the named steps
|
||||||
|
# ./setup-mac.sh --dry-run show what would change without changing it
|
||||||
|
#
|
||||||
|
# Steps: xcode homebrew brew links shell runtimes ai apps
|
||||||
|
# Packages live in Brewfile (CLI tools, fonts, terminals) and Brewfile.apps
|
||||||
|
# (GUI and App Store apps). Written for the bash 3.2 that ships with macOS.
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
echo "Setting up your Mac..."
|
STEPS="xcode homebrew brew links shell runtimes ai apps"
|
||||||
# Check if Homebrew is installed
|
DOTFILES=$(dirname "$(realpath "${BASH_SOURCE[0]}")")
|
||||||
if command -v brew &> /dev/null; then
|
NVM_DIR=$HOME/.config/nvm
|
||||||
echo "Homebrew is installed."
|
NODE_VERSION=22
|
||||||
else
|
PYTHON_VERSION=3.11
|
||||||
echo "Homebrew is not installed."
|
NPM_GLOBALS="oh-my-claude-sisyphus 9router better-sqlite3 sql.js systray2"
|
||||||
echo "Installing Homebrew..."
|
DRY_RUN=false
|
||||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
|
||||||
|
# Repo paths linked into $HOME, as "source:target" when the target differs.
|
||||||
|
# Paths missing from the repo (gitignored local files such as .env) are skipped.
|
||||||
|
# The AI configs are linked separately by link-ai-configs.sh in the ai step.
|
||||||
|
LINKS="
|
||||||
|
.zshrc
|
||||||
|
.tmux.conf
|
||||||
|
.tmux-server.conf
|
||||||
|
.rgignore
|
||||||
|
.withcontextconfig.jsonc
|
||||||
|
.withcontextignore
|
||||||
|
.env
|
||||||
|
.envrc
|
||||||
|
.opencommit
|
||||||
|
AGENTS.md
|
||||||
|
docs
|
||||||
|
local_servers
|
||||||
|
.superpowers
|
||||||
|
.config/nvim
|
||||||
|
.config/ghostty
|
||||||
|
.config/wezterm
|
||||||
|
.config/direnv
|
||||||
|
.config/starship.toml
|
||||||
|
.config/ohmyposh/zen.toml
|
||||||
|
.config/zellij/config.kdl
|
||||||
|
.config/zed/keymap.json
|
||||||
|
.config/zed/settings.json
|
||||||
|
.config/opencode/agent
|
||||||
|
.config/opencode/command
|
||||||
|
.config/omo/hooks.json:.omo/agent/hooks.json
|
||||||
|
.config/omo/prompts:.omo/agent/prompts
|
||||||
|
"
|
||||||
|
|
||||||
|
say() { printf '\n\033[1;34m==> %s\033[0m\n' "$*"; }
|
||||||
|
info() { printf ' %s\n' "$*"; }
|
||||||
|
warn() { printf '\033[1;33m ! %s\033[0m\n' "$*" >&2; }
|
||||||
|
have() { command -v "$1" >/dev/null 2>&1; }
|
||||||
|
|
||||||
|
run() {
|
||||||
|
if $DRY_RUN; then info "would run: $*"; else "$@"; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_remote() {
|
||||||
|
local url=$1 script
|
||||||
|
shift
|
||||||
|
if $DRY_RUN; then
|
||||||
|
info "would run the installer from $url $*"
|
||||||
|
return 0
|
||||||
fi
|
fi
|
||||||
# Check if Git is installed
|
script=$(curl -fsSL "$url") || return 1
|
||||||
|
/bin/bash -c "$script" "$url" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
if command -v git &> /dev/null; then
|
load_brew_env() {
|
||||||
echo "Git is installed."
|
local brew
|
||||||
else
|
for brew in /opt/homebrew/bin/brew /usr/local/bin/brew; do
|
||||||
echo "Git is not installed."
|
if [[ -x $brew ]]; then
|
||||||
echo "Installing Git..."
|
eval "$("$brew" shellenv)"
|
||||||
brew install git
|
return 0
|
||||||
fi
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
# Check if coreutils is installed
|
describe() {
|
||||||
|
case $1 in
|
||||||
|
xcode) echo "Xcode Command Line Tools" ;;
|
||||||
|
homebrew) echo "Homebrew" ;;
|
||||||
|
brew) echo "CLI tools, fonts and terminals (Brewfile)" ;;
|
||||||
|
links) echo "Dotfile symlinks" ;;
|
||||||
|
shell) echo "oh-my-zsh and tmux plugins" ;;
|
||||||
|
runtimes) echo "Node, Rust, Python and Flutter" ;;
|
||||||
|
ai) echo "AI coding tools and their configs" ;;
|
||||||
|
apps) echo "GUI and App Store apps (Brewfile.apps)" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
if command -v gdate &> /dev/null; then
|
step_xcode() {
|
||||||
echo "coreutils is installed."
|
if xcode-select -p >/dev/null 2>&1; then
|
||||||
else
|
info "Command Line Tools already installed"
|
||||||
echo "coreutils is not installed."
|
return 0
|
||||||
echo "Installing coreutils..."
|
|
||||||
brew install coreutils
|
|
||||||
fi
|
fi
|
||||||
|
run xcode-select --install
|
||||||
# Check if OhMyPosh is installed
|
if ! $DRY_RUN; then
|
||||||
|
info "Finish the Command Line Tools installer window; waiting for it"
|
||||||
if command -v oh-my-posh &> /dev/null; then
|
until xcode-select -p >/dev/null 2>&1; do sleep 5; done
|
||||||
echo "OhMyPosh is installed."
|
|
||||||
else
|
|
||||||
echo "OhMyPosh is not installed."
|
|
||||||
echo "Installing OhMyPosh..."
|
|
||||||
brew install jandedobbeleer/oh-my-posh/oh-my-posh
|
|
||||||
fi
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Check if asdf is installed
|
step_homebrew() {
|
||||||
if command -v asdf &> /dev/null; then
|
local prefix=/usr/local
|
||||||
echo "asdf is installed."
|
if [[ $(uname -m) == arm64 ]]; then prefix=/opt/homebrew; fi
|
||||||
|
if [[ -x $prefix/bin/brew ]]; then
|
||||||
|
info "Homebrew already installed"
|
||||||
else
|
else
|
||||||
echo "asdf is not installed."
|
run_remote https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh
|
||||||
echo "Installing asdf..."
|
|
||||||
brew install asdf
|
|
||||||
|
|
||||||
echo "Adding asdf plugins..."
|
|
||||||
asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git
|
|
||||||
fi
|
fi
|
||||||
|
# Login shells get brew on PATH from .zprofile; .zshrc relies on it.
|
||||||
# Check if NodeJS is installed
|
if grep -qs 'brew shellenv' "$HOME/.zprofile"; then
|
||||||
if command -v node &> /dev/null; then
|
info "Homebrew already loaded by ~/.zprofile"
|
||||||
echo "NodeJS is installed."
|
elif $DRY_RUN; then
|
||||||
|
info "would add Homebrew's shellenv to ~/.zprofile"
|
||||||
else
|
else
|
||||||
echo "NodeJS is not installed."
|
# shellcheck disable=SC2016 # the $(...) is meant for .zprofile, not here
|
||||||
echo "Installing NodeJS..."
|
printf '\n# Homebrew\neval "$(%s/bin/brew shellenv)"\n' "$prefix" >>"$HOME/.zprofile"
|
||||||
brew install node
|
info "added Homebrew's shellenv to ~/.zprofile"
|
||||||
fi
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Check if Neovim is installed
|
# Installs a Brewfile without upgrading what is already installed. In dry-run
|
||||||
|
# mode it lists what is missing instead.
|
||||||
if command -v nvim &> /dev/null; then
|
bundle() {
|
||||||
echo "Neovim is installed."
|
if ! have brew; then
|
||||||
else
|
warn "Homebrew is missing; run the homebrew step first"
|
||||||
echo "Neovim is not installed."
|
return 1
|
||||||
echo "Installing Neovim..."
|
|
||||||
brew install neovim
|
|
||||||
fi
|
fi
|
||||||
|
if $DRY_RUN; then
|
||||||
# Check if WezTerm is installed
|
info "missing from ${1##*/}:"
|
||||||
|
HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_BUNDLE_NO_UPGRADE=1 brew bundle check --file="$1" --verbose 2>&1 |
|
||||||
|
grep -v '^The Brewfile' | sed 's/^/ /' || true
|
||||||
if command -v wezterm &> /dev/null; then
|
return 0
|
||||||
echo "WezTerm is installed."
|
|
||||||
else
|
|
||||||
echo "WezTerm is not installed."
|
|
||||||
echo "Installing WezTerm..."
|
|
||||||
brew install wezterm
|
|
||||||
fi
|
fi
|
||||||
|
HOMEBREW_BUNDLE_NO_UPGRADE=1 brew bundle install --file="$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
step_brew() {
|
||||||
|
bundle "$DOTFILES/Brewfile"
|
||||||
|
}
|
||||||
|
|
||||||
# Ensure fira font is installed
|
BACKUP_DIR=""
|
||||||
|
KEPT=0
|
||||||
|
|
||||||
brew install font-fira-code-nerd-font
|
# Links ~/<target> to the repo's <source>, backing up whatever was there.
|
||||||
|
link() {
|
||||||
# Check if Zsh is installed
|
local src=$DOTFILES/$1 dst=$HOME/$2 parent
|
||||||
|
if [[ ! -e $src ]]; then
|
||||||
if command -v zsh &> /dev/null; then
|
info "skipped ~/$2 ($1 is not in the repo)"
|
||||||
echo "Zsh is installed."
|
return 0
|
||||||
else
|
|
||||||
echo "Zsh is not installed."
|
|
||||||
echo "Installing Zsh..."
|
|
||||||
brew install zsh
|
|
||||||
|
|
||||||
echo "Setting Zsh as default shell..."
|
|
||||||
sudo sh -c "echo $(which zsh) >> /etc/shells"
|
|
||||||
fi
|
fi
|
||||||
|
if [[ $src -ef $dst ]]; then
|
||||||
# Check if Generic Colorizer is installed
|
KEPT=$((KEPT + 1))
|
||||||
|
return 0
|
||||||
if command -v grc &> /dev/null; then
|
|
||||||
echo "Generic Colorizer is installed."
|
|
||||||
else
|
|
||||||
echo "Generic Colorizer is not installed."
|
|
||||||
echo "Installing Generic Colorizer..."
|
|
||||||
brew install grc
|
|
||||||
fi
|
fi
|
||||||
|
if $DRY_RUN; then
|
||||||
# Check if bat is installed
|
if [[ -e $dst || -L $dst ]]; then
|
||||||
|
info "would back up ~/$2 and link it to $1"
|
||||||
if command -v bat &> /dev/null; then
|
|
||||||
echo "bat is installed."
|
|
||||||
else
|
else
|
||||||
echo "bat is not installed."
|
info "would link ~/$2 to $1"
|
||||||
echo "Installing bat..."
|
|
||||||
brew install bat
|
|
||||||
fi
|
fi
|
||||||
|
return 0
|
||||||
# Check if eza is installed
|
|
||||||
|
|
||||||
if command -v eza &> /dev/null; then
|
|
||||||
echo "eza is installed."
|
|
||||||
else
|
|
||||||
echo "eza is not installed."
|
|
||||||
echo "Installing eza..."
|
|
||||||
brew install eza
|
|
||||||
fi
|
fi
|
||||||
|
parent=$(dirname "$dst")
|
||||||
# Install global pnpm package
|
mkdir -p "$parent"
|
||||||
if command -v pnpm &> /dev/null; then
|
if [[ -e $dst || -L $dst ]]; then
|
||||||
echo "pnpm is installed."
|
if [[ -z $BACKUP_DIR ]]; then
|
||||||
else
|
BACKUP_DIR=$HOME/.local/state/dotfiles/backups/$(date +%Y-%m-%dT%H-%M-%S)
|
||||||
echo "pnpm is not installed."
|
|
||||||
echo "Installing pnpm..."
|
|
||||||
npm install -g pnpm
|
|
||||||
fi
|
fi
|
||||||
|
mkdir -p "$(dirname "$BACKUP_DIR/$2")"
|
||||||
# Check if fzf is installed
|
mv "$dst" "$BACKUP_DIR/$2"
|
||||||
|
info "backed up ~/$2 to $BACKUP_DIR/$2"
|
||||||
if command -v fzf &> /dev/null; then
|
|
||||||
echo "fzf is installed."
|
|
||||||
else
|
|
||||||
echo "fzf is not installed."
|
|
||||||
echo "Installing fzf..."
|
|
||||||
brew install fzf
|
|
||||||
fi
|
fi
|
||||||
|
ln -s "$(python3 -c 'import os, sys; print(os.path.relpath(sys.argv[1], os.path.realpath(sys.argv[2])))' "$src" "$parent")" "$dst"
|
||||||
|
info "linked ~/$2"
|
||||||
|
}
|
||||||
|
|
||||||
# Check if ripgrep is installed
|
step_links() {
|
||||||
|
# Fetch the nvim config on a fresh clone only. An initialised submodule is
|
||||||
if command -v rg &> /dev/null; then
|
# left alone so whatever branch it has checked out is never reset.
|
||||||
echo "ripgrep is installed."
|
if git -C "$DOTFILES" submodule status .config/nvim | grep -q '^-'; then
|
||||||
else
|
run git -C "$DOTFILES" submodule update --init --recursive .config/nvim
|
||||||
echo "ripgrep is not installed."
|
|
||||||
echo "Installing ripgrep..."
|
|
||||||
brew install ripgrep
|
|
||||||
fi
|
fi
|
||||||
|
local entry script name
|
||||||
|
for entry in $LINKS; do
|
||||||
|
link "${entry%%:*}" "${entry#*:}"
|
||||||
|
done
|
||||||
|
for script in "$DOTFILES"/.local/bin/*; do
|
||||||
|
name=${script##*/}
|
||||||
|
case $name in
|
||||||
|
pbcopy | pbpaste) continue ;; # OSC 52 clipboard shims for headless servers
|
||||||
|
esac
|
||||||
|
if [[ -x $script ]]; then link ".local/bin/$name" ".local/bin/$name"; fi
|
||||||
|
done
|
||||||
|
info "$KEPT links already in place"
|
||||||
|
}
|
||||||
|
|
||||||
# Check if poetry is installed
|
step_shell() {
|
||||||
|
if [[ -d $HOME/.oh-my-zsh ]]; then
|
||||||
if command -v poetry &> /dev/null; then
|
info "oh-my-zsh already installed"
|
||||||
echo "poetry is installed."
|
|
||||||
else
|
else
|
||||||
echo "poetry is not installed."
|
# --keep-zshrc leaves the linked .zshrc alone.
|
||||||
echo "Installing poetry..."
|
run_remote https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh --unattended --keep-zshrc
|
||||||
curl -sSL https://install.python-poetry.org | python3 -
|
|
||||||
fi
|
fi
|
||||||
|
local tpm=$HOME/.tmux/plugins/tpm
|
||||||
# Check if gh is installed
|
if [[ -d $tpm ]]; then
|
||||||
if command -v gh &> /dev/null; then
|
info "tpm already installed"
|
||||||
echo "gh is installed"
|
|
||||||
else
|
else
|
||||||
echo "gh is not installed"
|
run git clone --depth 1 https://github.com/tmux-plugins/tpm "$tpm"
|
||||||
echo "Installing gh with brew..."
|
|
||||||
brew install gh
|
|
||||||
|
|
||||||
# Check if pyenv is installed
|
|
||||||
|
|
||||||
if command -v pyenv &> /dev/null; then
|
|
||||||
echo "pyenv is installed."
|
|
||||||
else
|
|
||||||
echo "pyenv is not installed."
|
|
||||||
echo "Installing pyenv with brew..."
|
|
||||||
brew install pyenv
|
|
||||||
|
|
||||||
# Check if pyenv-virtualenv is installed
|
|
||||||
if command -v pyenv &> /dev/null; then
|
|
||||||
echo "pyenv-virtualenv is installed."
|
|
||||||
else
|
|
||||||
echo "pyenv-virtualenv is not installed."
|
|
||||||
echo "Installing pyenv-virtualenv with brew..."
|
|
||||||
brew install pyenv-virtualenv
|
|
||||||
|
|
||||||
# Check if anaconda is installed
|
|
||||||
|
|
||||||
if command -v conda &> /dev/null; then
|
|
||||||
echo "anaconda is installed."
|
|
||||||
else
|
|
||||||
echo "anaconda is not installed."
|
|
||||||
echo "Installing anaconda..."
|
|
||||||
brew install --cask anaconda
|
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
# Installs the .tmux.conf plugins that are not there yet.
|
||||||
|
run "$tpm/bin/install_plugins"
|
||||||
|
}
|
||||||
|
|
||||||
# Install Flutter version manager
|
install_node() {
|
||||||
|
if [[ -s $NVM_DIR/nvm.sh ]]; then
|
||||||
if command -v fvm &> /dev/null; then
|
info "nvm already installed"
|
||||||
echo "fvm is installed."
|
|
||||||
else
|
else
|
||||||
echo "fvm is not installed."
|
run git clone --quiet https://github.com/nvm-sh/nvm.git "$NVM_DIR"
|
||||||
echo "Installing fvm..."
|
if $DRY_RUN; then
|
||||||
curl -fsSL https://fvm.app/install.sh | bash
|
info "would install Node $NODE_VERSION as the nvm default, plus npm globals: $NPM_GLOBALS"
|
||||||
|
return 0
|
||||||
fi
|
fi
|
||||||
|
git -C "$NVM_DIR" checkout --quiet "$(git -C "$NVM_DIR" describe --abbrev=0 --tags --match 'v[0-9]*' "$(git -C "$NVM_DIR" rev-list --tags --max-count=1)")"
|
||||||
|
fi
|
||||||
# Install SDK manager
|
# nvm does not work under set -eu, so it gets a subshell of its own.
|
||||||
# Check if $HOME/.sdkman exists
|
(
|
||||||
|
set +eu
|
||||||
if [ -d "$HOME/.sdkman" ]; then
|
# shellcheck source=/dev/null
|
||||||
echo "sdkman is installed."
|
. "$NVM_DIR/nvm.sh"
|
||||||
|
if nvm which "$NODE_VERSION" >/dev/null 2>&1; then
|
||||||
|
info "Node $NODE_VERSION already installed"
|
||||||
else
|
else
|
||||||
echo "sdkman is not installed."
|
run nvm install "$NODE_VERSION" || exit 1
|
||||||
echo "Installing sdkman..."
|
|
||||||
curl -s "https://get.sdkman.io" | bash
|
|
||||||
fi
|
fi
|
||||||
|
if [[ $(nvm version default) != "$(nvm version "$NODE_VERSION")" ]]; then
|
||||||
|
run nvm alias default "$NODE_VERSION" || exit 1
|
||||||
# Install Aerospace
|
fi
|
||||||
|
nvm use --silent "$NODE_VERSION" >/dev/null 2>&1 || exit 0 # dry run before Node exists
|
||||||
if command -v aerospace &> /dev/null; then
|
for pkg in $NPM_GLOBALS; do
|
||||||
echo "aerospace is installed."
|
if npm ls -g --depth=0 "$pkg" >/dev/null 2>&1; then
|
||||||
|
info "npm $pkg already installed"
|
||||||
else
|
else
|
||||||
echo "aerospace is not installed."
|
run npm install -g "$pkg" || exit 1
|
||||||
echo "Installing aerospace..."
|
|
||||||
brew install --cask nikitabobko/tap/aerospace
|
|
||||||
fi
|
fi
|
||||||
|
done
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
install_rust() {
|
||||||
|
if [[ -x $HOME/.cargo/bin/rustup ]]; then
|
||||||
|
info "Rust already installed"
|
||||||
|
else
|
||||||
|
run_remote https://sh.rustup.rs -y --no-modify-path
|
||||||
|
fi
|
||||||
|
if [[ -x $HOME/.cargo/bin/cargo-xwin ]]; then
|
||||||
|
info "cargo-xwin already installed"
|
||||||
|
else
|
||||||
|
run "$HOME/.cargo/bin/cargo" install --locked cargo-xwin
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_python() {
|
||||||
|
local current
|
||||||
|
current=$(pyenv global 2>/dev/null || true)
|
||||||
|
if [[ -n $current && $current != system ]]; then
|
||||||
|
info "pyenv global Python already set ($current)"
|
||||||
|
elif $DRY_RUN; then
|
||||||
|
info "would install the latest Python $PYTHON_VERSION with pyenv and make it the global default"
|
||||||
|
else
|
||||||
|
pyenv install --skip-existing "$PYTHON_VERSION"
|
||||||
|
pyenv global "$(pyenv latest "$PYTHON_VERSION")"
|
||||||
|
fi
|
||||||
|
if have poetry; then
|
||||||
|
info "poetry already installed"
|
||||||
|
else
|
||||||
|
run uv tool install poetry
|
||||||
|
fi
|
||||||
|
# .zshrc adds ~/.zfunc to fpath for these completions.
|
||||||
|
if [[ -f $HOME/.zfunc/_poetry ]]; then
|
||||||
|
info "poetry completions already installed"
|
||||||
|
elif $DRY_RUN; then
|
||||||
|
info "would write poetry's zsh completions to ~/.zfunc/_poetry"
|
||||||
|
else
|
||||||
|
mkdir -p "$HOME/.zfunc"
|
||||||
|
poetry completions zsh >"$HOME/.zfunc/_poetry"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_flutter() {
|
||||||
|
if [[ -e $HOME/fvm/default ]]; then
|
||||||
|
info "Flutter already installed (fvm global)"
|
||||||
|
else
|
||||||
|
run fvm install stable
|
||||||
|
run fvm global stable
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
step_runtimes() {
|
||||||
|
install_node
|
||||||
|
install_rust
|
||||||
|
install_python
|
||||||
|
install_flutter
|
||||||
|
}
|
||||||
|
|
||||||
|
step_ai() {
|
||||||
|
if have omo; then info "omo already installed"; else run bun add -g omo-ai; fi
|
||||||
|
if have claude; then
|
||||||
|
info "Claude Code already installed"
|
||||||
|
else
|
||||||
|
run_remote https://claude.ai/install.sh
|
||||||
|
fi
|
||||||
|
if have opencode; then
|
||||||
|
info "OpenCode already installed"
|
||||||
|
else
|
||||||
|
run_remote https://opencode.ai/install --no-modify-path
|
||||||
|
fi
|
||||||
|
if have codex; then info "Codex already installed"; else run brew install --cask codex; fi
|
||||||
|
# link-ai-configs.sh needs Node from the runtimes step.
|
||||||
|
local args=""
|
||||||
|
if $DRY_RUN; then args=--dry-run; fi
|
||||||
|
(
|
||||||
|
set +eu
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$NVM_DIR/nvm.sh"
|
||||||
|
bash "$DOTFILES/link-ai-configs.sh" $args
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prints the casks in a Brewfile whose app is already installed, but not by
|
||||||
|
# Homebrew. Installing those fails with "It seems there is already an App".
|
||||||
|
unmanaged_casks() {
|
||||||
|
local cask casks="" managed json name kind id
|
||||||
|
if ! have jq; then
|
||||||
|
warn "jq is missing, so apps installed outside Homebrew cannot be detected"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
managed=$(brew list --cask)
|
||||||
|
while read -r cask; do
|
||||||
|
if ! grep -qx "${cask##*/}" <<<"$managed"; then casks="$casks $cask"; fi
|
||||||
|
done < <(sed -nE 's/^cask "([^"]+)".*/\1/p' "$1")
|
||||||
|
if [[ -z $casks ]]; then return 0; fi
|
||||||
|
# Casks from taps that are not trusted yet cannot be looked up; on a fresh
|
||||||
|
# Mac there is nothing to skip anyway.
|
||||||
|
# shellcheck disable=SC2086 # one argument per cask
|
||||||
|
if ! json=$(HOMEBREW_NO_AUTO_UPDATE=1 brew info --cask --json=v2 $casks 2>/dev/null); then
|
||||||
|
warn "could not look up the casks, so none are skipped"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
# Each cask yields the app names it installs (source and renamed target),
|
||||||
|
# or, for pkg and installer casks, its display name plus its pkg receipts.
|
||||||
|
jq -r '
|
||||||
|
.casks[] | .token as $name |
|
||||||
|
[.artifacts[] | select(has("app")) | .app | (.[0] | split("/") | last),
|
||||||
|
(.[1]? | objects | .target // empty | split("/") | last)] as $apps |
|
||||||
|
if ($apps | length) > 0 then $apps[] | "\($name)\tapp\t\(.)"
|
||||||
|
else "\($name)\tapp\t\(.name[0]).app",
|
||||||
|
(.artifacts[] | select(has("uninstall")) | .uninstall[] | .pkgutil? // empty
|
||||||
|
| (if type == "array" then .[] else . end) | "\($name)\tpkg\t\(.)")
|
||||||
|
end' <<<"$json" |
|
||||||
|
while IFS=$'\t' read -r name kind id; do
|
||||||
|
if [[ $kind == app && (-e /Applications/$id || -e $HOME/Applications/$id) ]] ||
|
||||||
|
{ [[ $kind == pkg && -n $(pkgutil --pkgs="$id" 2>/dev/null) ]]; }; then
|
||||||
|
echo "$name"
|
||||||
|
fi
|
||||||
|
done | sort -u | tr '\n' ' '
|
||||||
|
}
|
||||||
|
|
||||||
|
step_apps() {
|
||||||
|
local skip
|
||||||
|
if ! have brew; then
|
||||||
|
warn "Homebrew is missing; run the homebrew step first"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
skip=$(unmanaged_casks "$DOTFILES/Brewfile.apps")
|
||||||
|
skip=${skip% }
|
||||||
|
if [[ -n $skip ]]; then
|
||||||
|
info "skipping casks for apps installed outside Homebrew: $skip"
|
||||||
|
fi
|
||||||
|
HOMEBREW_BUNDLE_CASK_SKIP=$skip bundle "$DOTFILES/Brewfile.apps"
|
||||||
|
}
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
sed -n '2,/^$/s/^# \{0,1\}//p' "${BASH_SOURCE[0]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
local arg step status selected="" failed=""
|
||||||
|
for arg in "$@"; do
|
||||||
|
case $arg in
|
||||||
|
-n | --dry-run) DRY_RUN=true ;;
|
||||||
|
-h | --help)
|
||||||
|
usage
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
warn "unknown option: $arg"
|
||||||
|
usage
|
||||||
|
return 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [[ " $STEPS " != *" $arg "* ]]; then
|
||||||
|
warn "unknown step: $arg (steps: $STEPS)"
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
selected="$selected $arg"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
if [[ $(uname -s) != Darwin ]]; then
|
||||||
|
warn "setup-mac.sh only runs on macOS"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [[ -z $selected ]]; then selected=$STEPS; fi
|
||||||
|
|
||||||
|
# Tools from non-Homebrew installers live here; the steps look for them.
|
||||||
|
export PATH="$HOME/.local/bin:$HOME/.bun/bin:$HOME/.opencode/bin:$HOME/.cargo/bin:$PATH"
|
||||||
|
load_brew_env || true
|
||||||
|
if $DRY_RUN; then say "Dry run: nothing will be changed"; fi
|
||||||
|
|
||||||
|
for step in $selected; do
|
||||||
|
say "$step: $(describe "$step")"
|
||||||
|
# Each step runs in a subshell with errexit so it stops at its first
|
||||||
|
# failure. Testing the subshell directly would switch errexit off.
|
||||||
|
(
|
||||||
|
set -e
|
||||||
|
"step_$step"
|
||||||
|
)
|
||||||
|
status=$?
|
||||||
|
if [[ $status -ne 0 ]]; then
|
||||||
|
failed="$failed $step"
|
||||||
|
warn "$step failed; fix the error above, then rerun ./setup-mac.sh $step"
|
||||||
|
fi
|
||||||
|
load_brew_env || true
|
||||||
|
done
|
||||||
|
|
||||||
|
say "Done"
|
||||||
|
if [[ -n $failed ]]; then warn "failed steps:$failed"; fi
|
||||||
|
if ! $DRY_RUN && [[ $selected == "$STEPS" ]]; then
|
||||||
|
info "Steps that need you:"
|
||||||
|
info "- Sign in to the App Store, then rerun ./setup-mac.sh apps for the App Store apps"
|
||||||
|
info "- 1Password: turn on the SSH agent (Settings > Developer); .zshrc uses it for SSH"
|
||||||
|
info "- Sign in: gh auth login, then Claude Code, Codex and OpenCode"
|
||||||
|
info "- Open a new terminal to load the new shell setup"
|
||||||
|
fi
|
||||||
|
[[ -z $failed ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
|
|||||||
Reference in New Issue
Block a user