Recipes two or more intents share. Each runs after the Config command.
Remote check — the remote is the configured repo AND GitHub says it is private
Push runs it before any other step and again before the push. Status runs it inside a subshell, ( … ), so a STOP line becomes the report's remote warning and the rest of the report still runs. In Push, any STOP line ends the intent: no pull, no commit, no push.
cd "$DOTFILES_DIR" || { echo "STOP: no dotfiles clone at $DOTFILES_DIR"; exit 1; }
REMOTE=$(git remote get-url origin) || { echo "STOP: the clone has no origin remote"; exit 1; }
[ "$REMOTE" = "$DOTFILES_REPO" ] || { echo "STOP: origin is $REMOTE, expected $DOTFILES_REPO"; exit 1; }
PRIVATE=$(gh repo view "$REMOTE" --json isPrivate -q .isPrivate) || { echo "STOP: cannot read the repo's visibility from GitHub (gh auth status?)"; exit 1; }
[ "$PRIVATE" = "true" ] || { echo "STOP: $REMOTE is not private (isPrivate=$PRIVATE)"; exit 1; }
echo "OK: origin is $REMOTE, private"
The visibility check is the one that matters: the equality line only proves origin matches the configured URL, not that the repo is private. gh repo view accepts the SSH, HTTPS, or OWNER/REPO form of the remote.
Compare local and repo — tracked files only
Run from $DOTFILES_DIR. Prints one line per .zshrc or tracked .config file that is missing at home or differs from the clone; no output means they match. Never diff -qr ~/.config/ .config/: the repo tracks a subset, so a whole-directory diff lists every app's data and never comes back clean.
{ echo .zshrc; git ls-files .config; } | while IFS= read -r f; do
if [ ! -e "$HOME/$f" ]; then echo "missing locally: $f"
elif ! cmp -s "$HOME/$f" "$f"; then echo "differs: $f"; fi
done
Backup copy — Backup, and Restore before it overwrites
Sets $timestamp and $backup_dir, copies ~/.zshrc and the DOTFILES_CONFIG_DIRS directories, and leaves secret files out. It sets $backup_ok to 1 only when every copy succeeded; Restore overwrites nothing unless $backup_ok is 1.
timestamp=$(date +%Y%m%d_%H%M%S)
backup_dir=$DOTFILES_BACKUPS/$timestamp
backup_ok=1
mkdir -p "$backup_dir/.config" || backup_ok=0
if [ -f ~/.zshrc ]; then
if cp ~/.zshrc "$backup_dir/.zshrc"; then echo "✅ Backed up .zshrc"; else echo "❌ .zshrc backup failed"; backup_ok=0; fi
fi
# Backup the directories named in Config.md (not entire .config to save space).
# $(printf ...) word-splits in zsh and bash alike; a bare $DOTFILES_CONFIG_DIRS is one word in zsh.
for dir in $(printf '%s' "$DOTFILES_CONFIG_DIRS"); do
if [ -d ~/.config/$dir ]; then
# No secrets in a backup: a tool's directory can hold its own .env or token
# files, and every backup would be another plaintext copy.
rsync -a --exclude='.env' --exclude='.env.*' --exclude='*.oauth' --exclude='*oauth*.json' \
--exclude='*token*.json' --exclude='client_secret*.json' --exclude='credentials*' \
--exclude='auth.json' ~/.config/$dir "$backup_dir/.config/" \
&& echo "✅ Backed up .config/$dir" || { echo "❌ .config/$dir backup failed"; backup_ok=0; }
fi
done
if [ "$backup_ok" = 1 ]; then echo "Backup complete at: $backup_dir"; else echo "❌ Backup INCOMPLETE at: $backup_dir. Do not restore or overwrite anything."; fi
Restore from a backup — rollback
The one rollback recipe; Restore and Backup point here. It overwrites local files, so it follows the same rule as Restore: a fresh backup of the current files first, then the user's yes.
- Pick the backup to roll back to, before taking the fresh one (which becomes the newest):
ls -lt "$DOTFILES_BACKUPS"/
backup=$DOTFILES_BACKUPS/YYYYMMDD_HHMMSS # the chosen timestamp
[ -d "$backup" ] || { echo "STOP: no backup at $backup"; exit 1; }
- Run the Backup copy above, so the current files can be recovered from
$backup_dir.
- Show the user
$backup (the source), $backup_dir (the fresh backup) and the files that will be overwritten (.zshrc and each DOTFILES_CONFIG_DIRS directory present under $backup/.config), and wait for their yes.
- Only after that yes:
[ "$backup_ok" = 1 ] || { echo "STOP: the fresh backup is incomplete; nothing restored"; exit 1; }
[ -f "$backup/.zshrc" ] && cp "$backup/.zshrc" ~/.zshrc
# Only the configured directories: the fresh backup covers those and nothing else.
for dir in $(printf '%s' "$DOTFILES_CONFIG_DIRS"); do
[ -d "$backup/.config/$dir" ] && cp -R "$backup/.config/$dir" ~/.config/
done
echo "Rolled back from $backup; previous files saved at $backup_dir"
Reloading in the agent's shell changes nothing for the user. Tell them to run exec zsh (or source ~/.zshrc) in their own terminal.