Skip to content

Replace chezmoi with Makefile + stow setup#1

Merged
limegorilla merged 8 commits into
mainfrom
claude/audit-mac-setup-R7zZb
May 18, 2026
Merged

Replace chezmoi with Makefile + stow setup#1
limegorilla merged 8 commits into
mainfrom
claude/audit-mac-setup-R7zZb

Conversation

@limegorilla
Copy link
Copy Markdown
Owner

Summary

Rebuilds the dotfiles around a Makefile + stow workflow so everything is idempotent and re-runnable on existing Macs. Brew is now the single source of truth for every CLI, GUI app, and plugin. Adds bin/ for personal scripts, with eod as the first inhabitant.

What changed

Layout

  • home/ — files stowed into $HOME (make stow). dot_* renames done; iTerm2 chezmoi symlink dropped since the dock and Brewfile have moved to Ghostty.
  • macos/ — three always-idempotent modules: defaults.sh (system-wide), dock.sh (dock layout), apps.sh (per-app prefs). Replaces the four chezmoi run_once_*.sh.tmpl scripts.
  • lib/dock_operations.sh — sourced via $DOTFILES_DIR so it works regardless of CWD (the old script broke with a relative ./ source).
  • scripts/bootstrap.sh — Xcode CLT, Rosetta on Apple Silicon, Homebrew. scripts/doctor.sh sanity-checks the environment.
  • bin/ — personal scripts, added to $PATH by .zshrc. New: bin/eod.

Brewfile audit (lines up with what scripts and shell actually use)

  • Dropped: chezmoi (replaced), nvm (conflicted with the volta usage that previously got removed in 8e54533), pipx (unused).
  • Added: bash (scripts need bash 4+), stow (new workflow), gnu-sed (for gsed in defaults.sh), gnupg (for gpg-connect-agent in apps.sh), node, bun via the oven-sh/bun tap.
  • Grouped by category with comments.

bin/eod — end-of-day report
Walks every git repo under ~/Developer and lists anything with uncommitted changes, unpushed commits, or no upstream branch. Run before shutting down for the day. DEVELOPER_DIR=… or a positional arg overrides the path.

.zshrc

  • Drops the chezmoi completion line.
  • Sets DOTFILES_DIR (defaults to ~/.dotfiles) and prepends $DOTFILES_DIR/bin to $PATH.
  • New dotfiles alias = make -C $DOTFILES_DIR, so dotfiles update, dotfiles brew, dotfiles doctor work from anywhere.

Other

  • New README.md with the layout, target table, and conventions.
  • .gitignore for .DS_Store and Brewfile.lock.json (removes the committed copies).

Makefile targets

Target What it does
make install First-time setup (bootstrap → brew → stow → fonts → macos → apps)
make update Re-apply everything (safe to repeat)
make brew brew bundle
make brew-dump Dump current state to Brewfile (review the diff)
make stow Symlink home/ into $HOME (--no-folding)
make adopt Move existing files in $HOME into the repo
make macos defaults.sh + dock.sh
make apps apps.sh
make fonts Copy bundled fonts to ~/Library/Fonts (skips existing)
make doctor Sanity-check brew/stow/symlinks/etc.

Test plan

Container-side checks done here:

  • bash -n on every script
  • make help lists every target
  • bin/eod exercised against a fake DEVELOPER_DIR containing a clean, dirty, and unpushed repo — output is correct and uses TTY colours

Needs verifying on an actual Mac:

  • make install on a fresh VM (or make adopt then make update on your existing machine)
  • Touch ID sudo path: the new defaults.sh prefers /etc/pam.d/sudo_local (Sonoma+) and falls back to editing /etc/pam.d/sudo
  • make fonts on a Mac with existing fonts (it skips ones already present)
  • eod against your real ~/Developer

Migration notes (existing Macs)

git clone … ~/.dotfiles      # if not already cloned
cd ~/.dotfiles
make adopt                   # pulls your current ~/.zshrc etc. into the repo so stow can manage them
git diff                     # review what got adopted, drop anything you don't want tracked
make update

Or, if you want to overwrite local copies with what's in the repo: back them up, delete them, then make stow.


Generated by Claude Code

claude and others added 8 commits May 18, 2026 17:50
- Rename dot_* files to home/.* for direct stow management; drop the four
  run_once_*.sh.tmpl chezmoi scripts in favour of always-idempotent
  modules in macos/.
- Split system config into macos/defaults.sh (system-wide), macos/dock.sh
  (dock layout), and macos/apps.sh (per-app prefs). All re-runnable.
- Move helper functions to lib/dock_operations.sh, reference via
  $DOTFILES_DIR so the dock script works regardless of CWD.
- New top-level Makefile is the single entry point: make install / update
  / brew / stow / fonts / macos / apps / doctor / brew-dump / adopt.
- scripts/bootstrap.sh installs Xcode CLT, Rosetta (Apple Silicon) and
  Homebrew if missing. scripts/doctor.sh sanity-checks the environment.
- Brewfile audit: drop chezmoi, nvm, pipx (unused or replaced); add bash,
  stow, gnu-sed (gsed), gnupg (gpg-connect-agent), node, and bun (via the
  oven-sh/bun tap). Reorganise into commented sections.
- bin/ holds personal scripts on $PATH. First inhabitant: eod, an
  end-of-day report listing uncommitted/unpushed work across every git
  repo under ~/Developer.
- .zshrc: drop chezmoi completion, expose DOTFILES_DIR, prepend bin/ to
  PATH, add a `dotfiles` alias that proxies to make from anywhere.
- Add .gitignore for .DS_Store and Brewfile.lock.json; remove the
  committed copies. Drop the dead iTerm2 chezmoi symlink (Ghostty now).
- Rewrite README with the new layout, target table and conventions.
defaults -app errors out (and with set -e aborts the whole script)
when the target app doesn't exist. Guard the iTerm line with a
directory check so a Ghostty-only setup doesn't bail out mid-defaults.
The 'tell application "Terminal" to set font ...' AppleScript calls
launch Terminal.app to apply the change, which surfaces as a random
Terminal window every time 'dotfiles update' runs. Drop them; Ghostty
is the primary terminal and Terminal.app's defaults still get the
non-osascript writes.
- Compact table: one row per repo with status (●N dirty, ↑N ahead,
  ↓N behind, no-upstream). Only repos that need attention are shown.
- --fetch parallelises 'git fetch' (capped at 10 concurrent) so behind
  counts reflect the actual remote state. Default mode stays fast and
  uses last-known state.
- --apply walks repos needing attention; the action menu is built from
  the repo's state -- pull when behind only, push when ahead only,
  pull --rebase when diverged, stash+rebase+pop when behind+dirty,
  plus always-available 'shell here', 'view diff', 'view log', skip,
  quit. Implies --fetch so decisions are based on current state.
- Use 'git symbolic-ref --short HEAD' instead of rev-parse --abbrev-ref
  so unborn-HEAD repos report their branch correctly instead of
  injecting a '?' into the row.
- Make the worktree-skipping explicit in the header comment. Sort the
  repo list after collecting (drops the GNU-only 'sort -z' dependency).
- DIR can be passed as a positional arg; defaults to $DEVELOPER_DIR or
  ~/Developer.
- Prune common build/dependency directories so their checked-out git
  repos (e.g. SwiftPM's .build/checkouts/*, node_modules/*) don't
  pollute the report. Defaults cover .build, node_modules, vendor,
  Pods, DerivedData, .terraform, .gradle, target, .venv, venv,
  __pycache__, .next, .nuxt, .svelte-kit, .cache. Override via the
  EOD_SKIP_DIRS env var.
- Fetch by default; add --no-fetch to opt out. --apply no longer needs
  to imply --fetch since fetch is on.
- Background-parallel fetch with a live spinner ("⠋ Fetching…  N/M")
  so the previously-silent hang has feedback. Status line is cleared
  before normal output.
- Real table layout: dimmed REPO/BRANCH/STATUS headers with ─ rules,
  computed column widths, middle-truncation for over-wide paths and
  branches. ⊘ instead of literal "no upstream" for compactness.
- Prefer en_US.UTF-8 if the locale is available so ${#string} and ─
  rendering both work in characters rather than bytes.
- Fix a set-e exit: clear_status returned 1 when stdout wasn't a TTY,
  which aborted the script under `set -e`. Both status helpers now
  explicitly return 0.
Runs on push to main, on PRs that touch Brewfile/Makefile/scripts, and
on manual dispatch. Uses macos-15 runners (Apple Silicon).

What it covers:
  - brew bundle against a generated Brewfile.ci containing only tap and
    brew lines -- mas needs an Apple ID, casks are slow and irrelevant
    for verifying the CLI surface.
  - make stow with conflicting hosted-runner dotfiles removed first, so
    the symlink layout actually gets created.
  - Symlink existence check for the dotfiles we expect to manage.
  - brew --version + brew list smoke.
  - node + pnpm round-trip (init, install ms, require it from node).
  - eod against three fake repos covering clean/dirty/ahead states.
  - make doctor.

What it deliberately skips: cask installs, mas, the macOS defaults and
dock/apps modules (no apply target on a CI runner).
@limegorilla limegorilla marked this pull request as ready for review May 18, 2026 19:34
@limegorilla limegorilla merged commit 566f0a2 into main May 18, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants