Files
fleet-dotfiles-template/dot_local/bin/executable_chezmoi-auto-sync.sh
Anthony Cardinale ebccdda936 Initial public release
A chezmoi-based fleet-dotfiles template for macOS workstations:

- Two-way auto-sync via launchd watcher + 5-min puller
- Mesh SSH via modify_authorized_keys driven by .chezmoidata/fleet.yaml
- age-encrypted secrets file
- Bundled Claude Code agentic team (11 agents) + /lite + /lite-sub commands
- Verify-before-claiming Stop hook
- Generic statusline + project-boundary validate-path hook
- Reference launchd plist for cross-fleet task-durations aggregation
  (companion repo: gitea.tojo.team/cardinale/task-durations)
- AGENTS.md walks an agent through the entire setup Q&A interactively
- docs/ covers architecture, security model, fleet onboarding
2026-05-02 17:26:32 -04:00

76 lines
3.0 KiB
Bash

#!/bin/bash
# chezmoi-auto-sync.sh — watches ~/.claude/ and ~/.local/share/chezmoi/docs/ for changes
# and syncs to chezmoi source + git. Called by the launchd WatchPaths agent.
#
# IMPORTANT: Do NOT sync ~/.claude/skills/ here — skills managed by .chezmoiexternal.toml
# (ffmpeg-usage, swift-front, etc.) are cloned from their own repos. Adding them here
# flattens their .git/ dirs into the dotfiles repo and causes conflicts.
set -euo pipefail
LOCKFILE="/tmp/chezmoi-sync.lock"
LOG="/tmp/chezmoi-sync.log"
# Prevent concurrent runs
if [ -f "$LOCKFILE" ]; then
pid=$(cat "$LOCKFILE" 2>/dev/null)
if kill -0 "$pid" 2>/dev/null; then
exit 0
fi
fi
echo $$ > "$LOCKFILE"
trap 'rm -f "$LOCKFILE"' EXIT
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG"
}
# Debounce — wait 2 seconds for batch changes to settle
sleep 2
log "Change detected in watched paths, syncing..."
# Purge .DS_Store files from ~/.claude/ BEFORE chezmoi sees them
# (Finder creates these constantly; they're noise)
find ~/.claude -name ".DS_Store" -delete 2>/dev/null || true
# Pull first to avoid conflicts (rebase local changes on top)
cd "$(chezmoi source-path)"
git pull --rebase --quiet origin main 2>> "$LOG" || {
log "WARNING: git pull --rebase failed, attempting merge"
git rebase --abort 2>/dev/null
git pull --quiet origin main 2>> "$LOG" || log "ERROR: git pull failed"
}
# Remove any .DS_Store from source before adding new files
find "$(chezmoi source-path)" -name ".DS_Store" -o -name "dot_DS_Store" | xargs rm -f 2>/dev/null
# Only sync files we explicitly manage — NOT skills (managed by .chezmoiexternal.toml)
chezmoi add ~/.claude/CLAUDE.md 2>> "$LOG" || true
chezmoi add ~/.claude/agents/ 2>> "$LOG" || true
chezmoi add ~/.claude/settings.json 2>> "$LOG" || true
chezmoi add ~/.claude/commands/ 2>> "$LOG" || true
chezmoi add ~/.claude/hooks/ 2>> "$LOG" || true
chezmoi add ~/.claude/statusline-command.sh 2>> "$LOG" || true
chezmoi add ~/.claude/scripts/task-durations/extract.py 2>> "$LOG" || true
chezmoi add ~/.claude/scripts/task-durations/estimate.sh 2>> "$LOG" || true
chezmoi add ~/.claude/scripts/task-durations/pull-fleet.sh 2>> "$LOG" || true
chezmoi add ~/.local/bin/chezmoi-auto-sync.sh 2>> "$LOG" || true
# Clean up any .DS_Store that chezmoi added
cd "$(chezmoi source-path)"
git rm --cached -r --quiet $(git ls-files --cached | grep -i "DS_Store") 2>/dev/null || true
# Catch any uncommitted changes in the source dir that chezmoi's autoCommit missed
# (e.g., direct edits to ~/.local/share/chezmoi/docs/, which is the same git repo
# but not under chezmoi's "managed" tree). When chezmoi add was a no-op, autoCommit
# doesn't fire, so those edits sit uncommitted without this fallback.
if ! git diff --quiet || ! git diff --cached --quiet || [ -n "$(git ls-files --others --exclude-standard)" ]; then
git add -A 2>> "$LOG"
git commit -m "Auto-sync from $(hostname -s)" 2>> "$LOG" || true
git push 2>> "$LOG" || true
log "Pushed uncommitted source changes (e.g. docs/)"
fi
log "Sync complete"