Opinionated shell scripts to back up dotfiles and secrets to this repo and to bootstrap a new Mac from it.
Keep this repository private. It contains SOPS-encrypted material that is still sensitive, plus plaintext config that can reveal hosts, tooling, and habits after restore.
Configuration lives in init.sh (sourced by backup.sh and restore.sh).
DOTFILES— copied intobackup/(files and directories under$HOME).~/.config— rsync’d intobackup/.config/, with excludes listed inbackup.sh(CONFIG_RSYNC_EXCLUDES).SOPS— each path under$HOMEis encrypted with SOPS + age intosops/(single files aspath.enc; directories are one.encper file, mirroring the tree). Thesops/tree is not deleted on each backup: existing ciphertext is reused when the plaintext file’s SHA-256 matchessops/.plaintext-sha256, so Git does not pick up spurious ciphertext churn from re-encrypting unchanged files.backup/nonbrew-applications.txt— paths of.appbundles in/Applicationsand~/Applicationsthat do not resolve under Homebrew Caskroom/Cellar and are notcom.apple.*. Built byrecord_nonbrew_applicationsinmodules/nonbrew-apps.sh;restore.shcallsshow_nonbrew_applicationsafter SOPS. Heuristic only.
Secrets and paths currently in SOPS include things like .ssh, .netrc, kube/docker config, .claude.json, .cursor/mcp.json, ACME dirs under .acme.sh, .secrets, .zsh_history, etc. Adjust the arrays in init.sh to match what you want.
- Encrypt (backup):
backup.shuses--agewith eitherSOPS_AGE_RECIPIENTininit.sh(yourage1…public key) or, if that is empty,op read "$SOPS_AGE_KEY_RECIPIENT_PATH". - Decrypt (restore):
modules/sops.shreads the age secret key withop read "$SOPS_AGE_KEY_PASSWORD_PATH"and setsSOPS_AGE_KEYforsops -d.
The 1Password item fields are documented next to SOPS_AGE_KEY_*_PATH in init.sh.
You need the 1Password CLI (op) signed in for backup encrypt and restore decrypt.
backup.sh and restore.sh require PWD to be the repository root (they source init.sh, which checks this).
cd /path/to/macos-personal
./backup.sh # refresh backup/ and sops/
./backup.sh --force-sops # re-encrypt every SOPS file (ignore hash registry)
./backup.sh --init-sops-registry # hash plaintext only; write sops/.plaintext-sha256 and exit (no backup, no encrypt)
./restore.sh # install tooling and restore home + decrypt sopsSOPS hash registry: sops/.plaintext-sha256 is a sorted TSV (path-under-home<TAB>sha256). It is safe to commit in a private repo; it does not contain secrets, but fixed paths plus hashes can still hint what exists—treat like the rest of the repo.
Bootstrap: After cloning or changing this logic, run ./backup.sh --init-sops-registry once (from repo root) so the registry matches your current plaintext files; then normal ./backup.sh can skip re-encryption where ciphertext already exists and plaintext is unchanged.
Or use make from anywhere:
make -C /path/to/macos-personal backup
make -C /path/to/macos-personal restore
make -C /path/to/macos-personal brew-cleanup # optional; see belowRunning make with no target runs help and prints available targets (it does not run backup).
make brew-cleanup runs brew bundle cleanup --force against backup/Brewfile, uninstalling formulae and casks that are not listed there. Anything you still use must appear in that file or it will be removed. Run brew bundle check --file=backup/Brewfile first if you want to see drift without uninstalling.
- Install Xcode Command Line Tools — open Terminal and run:
Follow the prompt to download and install. This provides
xcode-select --install
git,make, compilers, and other prerequisites that Homebrew and the restore scripts depend on. - Clone this repo (private) and
cdinto it. - Sign in to 1Password and run
op signin(or use app integration) soop readworks for restore. - Run
./restore.shormake restorefrom the repo root. - Complete any GUI installer steps (e.g. Docker Desktop, 1Password).
- Restart the terminal (restore prints this as well).
Order of operations in restore.sh: oh-my-zsh (and Powerlevel10k) → dotfiles / .config → Homebrew + Brewfile → macOS defaults → devbox → SOPS decrypt.
- Dock: autohide, Recents off
- Finder: extensions visible, hidden files, path bar, list view, folders on top, search scoped to current folder
- Spaces / Mission Control: group windows by app
From the repo root, with op authenticated and Homebrew available (for brew bundle dump):
./backup.sh
# or: make backupTo change what is included, edit init.sh (DOTFILES, SOPS) and backup.sh (CONFIG_RSYNC_EXCLUDES). You can also edit backup/Brewfile by hand or trim it after brew bundle dump. If you add a new SOPS path and the corresponding .enc is missing, backup encrypts it once; the registry is rewritten each run with current hashes.