fix(mackup): safer defaults + recovery after broken symlink sync
- Default-off SSR_MACKUP_PRESYMLINK for DynamicProfiles pre-symlink (was risking empty/wrong iCloud trees and breaking iTerm2). - Remove incorrect skip entries: built-in cfgs use different paths than ~/.zsh, ~/.vscode*, ~/.iterm2 — skipping hid real overlap from scan only but the presymlink change was the main footgun. - Add `ssr mackup unlink` → `mackup -f link uninstall` to copy configs back from Mackup storage and drop symlinks (official undo). - Docs: ssr.conf.example, README sync/restore wording, completions. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,8 +2,8 @@
|
||||
|
||||
Reproducible macOS setup in one command. `ssr` snapshots your Homebrew bundle and application configs to a cloud drive on a schedule, and can rebuild a fresh Mac from that snapshot.
|
||||
|
||||
- **Backup** (`ssr sync`): dumps `Brewfile` and runs `mackup backup` to the configured cloud directory.
|
||||
- **Restore** (`ssr restore`): installs Homebrew, replays the `Brewfile`, restores `mackup` configs, applies macOS defaults.
|
||||
- **Backup** (`ssr sync`): dumps `Brewfile` and runs `mackup link install` to the configured cloud directory (symlink-based).
|
||||
- **Restore** (`ssr restore`): installs Homebrew, replays the `Brewfile`, runs `mackup link`, applies macOS defaults.
|
||||
- **Scheduled sync**: native macOS `launchd` agent (daily by default).
|
||||
- **Cloud storage**: iCloud Drive, Dropbox, Google Drive, OneDrive, or any custom path — selectable via config.
|
||||
- **No secrets**: all state lives in your cloud drive; the repo only contains code and config templates.
|
||||
|
||||
@@ -27,14 +27,14 @@ Usage:
|
||||
|
||||
Commands:
|
||||
install First-time install: links binary, seeds config, schedules sync.
|
||||
sync Run a backup: brew bundle dump + mackup backup → cloud.
|
||||
restore [Brewfile] Restore a machine: brew bundle + mackup restore + macOS defaults.
|
||||
sync Run a backup: brew bundle dump + mackup link install → cloud.
|
||||
restore [Brewfile] Restore a machine: brew bundle + mackup link + macOS defaults.
|
||||
update [--dry-run] Pull latest ssr code, refresh launchd if needed.
|
||||
schedule on|off Enable/disable the scheduled launchd sync job.
|
||||
config show|edit Print or edit the active config file.
|
||||
completions Install or remove shell completions (zsh/bash/fish).
|
||||
status Show last sync time, cloud target, and scheduled state.
|
||||
mackup scan|list|clean Manage auto-generated Mackup app definitions.
|
||||
mackup scan|list|clean|unlink Mackup ssr-*.cfg helpers + symlink revert.
|
||||
version Print version.
|
||||
|
||||
Options:
|
||||
|
||||
+36
-10
@@ -2,10 +2,11 @@
|
||||
# ssr-mackup — Manage auto-generated Mackup app definitions for dot-dirs.
|
||||
#
|
||||
# Subcommands:
|
||||
# scan Dry-run: show which ssr-*.cfg files would be created / updated,
|
||||
# then ask for confirmation before writing anything.
|
||||
# list List existing ~/.mackup/ssr-*.cfg files with the path they cover.
|
||||
# clean Remove all generated ssr-*.cfg files (prompts first).
|
||||
# scan Per-item prompts for new ~/.mackup/ssr-*.cfg files.
|
||||
# list List existing ~/.mackup/ssr-*.cfg files with the path they cover.
|
||||
# clean Remove all generated ssr-*.cfg files (prompts first).
|
||||
# unlink Run `mackup link uninstall` — copy configs back from Mackup
|
||||
# storage and remove symlinks (fixes broken iTerm / shell paths).
|
||||
|
||||
set -euo pipefail
|
||||
SSR_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
@@ -27,12 +28,17 @@ Manage auto-generated Mackup app definitions for dot-dirs not covered
|
||||
by Mackup's built-in app definitions.
|
||||
|
||||
Subcommands:
|
||||
scan Show suggested ~/.mackup/ssr-*.cfg files and confirm before writing.
|
||||
scan Suggest new ~/.mackup/ssr-*.cfg files (confirm each item).
|
||||
list List existing generated files.
|
||||
clean Remove all generated ssr-*.cfg files.
|
||||
unlink Revert Mackup symlinks (copy from cloud → real files in ~).
|
||||
|
||||
Run 'ssr mackup scan' once after install (or whenever new dot-dirs appear)
|
||||
and commit the generated files alongside your dotfiles.
|
||||
Run 'ssr mackup scan' when new dot-dirs appear; commit ~/.mackup/ssr-*.cfg
|
||||
with your dotfiles if you use them.
|
||||
|
||||
If Terminal/iTerm or other apps break after sync, run:
|
||||
ssr mackup unlink
|
||||
That runs Mackup's official undo for symlink-based installs.
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -245,15 +251,35 @@ cmd_clean() {
|
||||
ssr::ok "Removed ${#found[@]} file(s)."
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# unlink — mackup link uninstall (recovery)
|
||||
# ---------------------------------------------------------------------------
|
||||
cmd_unlink() {
|
||||
cat <<'EOF'
|
||||
This runs: mackup -f link uninstall
|
||||
|
||||
Mackup copies each managed path from your Mackup storage folder back into
|
||||
$HOME and removes the symlinks. Use when settings broke after `ssr sync` /
|
||||
`mackup link install` (e.g. iTerm2, shell init files pointing at iCloud).
|
||||
|
||||
After unlink, apps use normal local files again. Run `ssr sync` only when
|
||||
you are ready to re-apply symlink-based backup.
|
||||
EOF
|
||||
printf '\n'
|
||||
ssr::confirm "Run mackup link uninstall now?" 120 || { printf 'Aborted.\n'; return 0; }
|
||||
ssr::mackup::link_uninstall && ssr::ok "mackup link uninstall finished."
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Dispatch
|
||||
# ---------------------------------------------------------------------------
|
||||
[[ $# -gt 0 ]] || { usage; exit 1; }
|
||||
|
||||
case "$1" in
|
||||
scan) cmd_scan ;;
|
||||
list) cmd_list ;;
|
||||
clean) cmd_clean ;;
|
||||
scan) cmd_scan ;;
|
||||
list) cmd_list ;;
|
||||
clean) cmd_clean ;;
|
||||
unlink) cmd_unlink ;;
|
||||
-h|--help) usage ;;
|
||||
*) ssr::err "Unknown subcommand: $1"; usage; exit 1 ;;
|
||||
esac
|
||||
|
||||
+2
-1
@@ -64,7 +64,8 @@ _ssr() {
|
||||
_values 'mackup action' \
|
||||
'scan[Suggest and confirm ssr-*.cfg files]' \
|
||||
'list[List generated ssr-*.cfg files]' \
|
||||
'clean[Remove all generated ssr-*.cfg files]'
|
||||
'clean[Remove all generated ssr-*.cfg files]' \
|
||||
'unlink[mackup link uninstall — revert symlinks]'
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
|
||||
@@ -61,7 +61,7 @@ _ssr() {
|
||||
COMPREPLY=( $(compgen -W "install uninstall status" -- "$cur") )
|
||||
;;
|
||||
mackup)
|
||||
COMPREPLY=( $(compgen -W "scan list clean" -- "$cur") )
|
||||
COMPREPLY=( $(compgen -W "scan list clean unlink" -- "$cur") )
|
||||
;;
|
||||
*)
|
||||
COMPREPLY=()
|
||||
|
||||
@@ -70,4 +70,4 @@ complete -c ssr -n '__ssr_using_subcmd schedule' -a 'on off status'
|
||||
complete -c ssr -n '__ssr_using_subcmd config' -a 'show edit path'
|
||||
complete -c ssr -n '__ssr_using_subcmd completions' -a 'install uninstall status'
|
||||
complete -c ssr -n __ssr_needs_subcmd -a mackup -d 'Manage Mackup app definitions'
|
||||
complete -c ssr -n '__ssr_using_subcmd mackup' -a 'scan list clean'
|
||||
complete -c ssr -n '__ssr_using_subcmd mackup' -a 'scan list clean unlink'
|
||||
|
||||
@@ -16,9 +16,10 @@ SSR_CLOUD_PROVIDER=icloud
|
||||
SSR_BACKUP_SUBDIR=BACKUP
|
||||
SSR_MACKUP_SUBDIR=Mackup
|
||||
|
||||
# Auto-discover uncovered dot-dirs in $HOME and add them to a generated
|
||||
# ~/.mackup/ssr-custom.cfg before each backup. Set to "false" to opt out.
|
||||
SSR_MACKUP_AUTODISCOVER=true
|
||||
# Optional iTerm2 race workaround: pre-symlink DynamicProfiles → Mackup cloud
|
||||
# before `mackup link install`. Default is off — enabling can break iTerm if
|
||||
# the cloud copy is incomplete. Prefer `ssr mackup unlink` to recover.
|
||||
# SSR_MACKUP_PRESYMLINK=1
|
||||
|
||||
# Subdirs (relative to $HOME) to delete before each mackup backup attempt.
|
||||
# Handles transient dirs with dangling symlinks or sockets (debug sessions,
|
||||
|
||||
+22
-21
@@ -80,17 +80,6 @@ _SSR_MACKUP_SKIP=(
|
||||
# Docker / VM infrastructure (large, or require special handling)
|
||||
.vagrant .minikube .kube .helm
|
||||
|
||||
# Dot-dirs that belong to apps already covered by a Mackup built-in,
|
||||
# even when the built-in covers a different path (e.g. Library/Preferences
|
||||
# or Library/Application Support) rather than the dot-dir itself.
|
||||
# Adding them here prevents duplicate / redundant ssr-*.cfg suggestions.
|
||||
.iterm2 # iterm2.cfg covers Library/Preferences + .config/iterm2
|
||||
.vscode # vscode.cfg covers Library/Application Support/Code/User
|
||||
.vscode-insiders # vscode-insiders.cfg covers Library/Application Support/Code - Insiders
|
||||
.zsh # zsh.cfg covers .zshrc, .zshenv, .zprofile, .zlogin, .zlogout
|
||||
.bash # bash.cfg covers .bashrc, .bash_profile, .bash_logout etc.
|
||||
.fish # fish.cfg covers .config/fish
|
||||
|
||||
# Sync-service data directories — never back up a sync engine inside iCloud
|
||||
.dropbox # Dropbox data; backing up to iCloud would be circular
|
||||
.OneDrive # OneDrive data directory
|
||||
@@ -375,19 +364,21 @@ ssr::mackup::backup() {
|
||||
[[ -n "$mackup_root" ]] && ssr::mackup::_materialize_icloud "$mackup_root"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Pre-symlink: paths managed by apps that recreate their directory almost
|
||||
# instantly after deletion, making Mackup's delete→symlink sequence lose the
|
||||
# race. We pre-create the symlink → cloud BEFORE running `link install`.
|
||||
# Mackup detects the existing correct symlink and skips the path entirely.
|
||||
# Optional pre-symlink (OFF by default): iTerm2 can recreate
|
||||
# DynamicProfiles/ so fast that `mackup link install` loses the race.
|
||||
# Forcing a symlink here can break iTerm if the cloud copy is wrong or
|
||||
# empty — only enable after you trust the Mackup cloud tree.
|
||||
#
|
||||
# export SSR_MACKUP_PRESYMLINK=1 # in ssr.conf or the shell
|
||||
#
|
||||
# Format: home-relative path (same as in the Mackup app definition).
|
||||
# ---------------------------------------------------------------------------
|
||||
local -a _presymlink=(
|
||||
# iTerm2 recreates DynamicProfiles within milliseconds of deletion.
|
||||
# Pre-symlinking ensures Mackup skips it; iTerm2 reads/writes through
|
||||
# the symlink normally (creating new profiles goes straight to iCloud).
|
||||
.config/iterm2/AppSupport/DynamicProfiles
|
||||
)
|
||||
local -a _presymlink=()
|
||||
case "${SSR_MACKUP_PRESYMLINK:-}" in
|
||||
1|true|yes|on)
|
||||
_presymlink=(.config/iterm2/AppSupport/DynamicProfiles)
|
||||
;;
|
||||
esac
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Light preclean — remove transient directories so their ephemeral contents
|
||||
@@ -510,3 +501,13 @@ ssr::mackup::restore() {
|
||||
ssr::ok "mackup link completed in ${elapsed}s — symlinks created in ~/"
|
||||
fi
|
||||
}
|
||||
|
||||
# Reverse `mackup link` / `link install`: copy each managed path from the
|
||||
# Mackup storage tree back into $HOME and remove the symlinks. Use when apps
|
||||
# (e.g. iTerm2) misbehave after symlink-based sync or iCloud produced a bad
|
||||
# tree. Invoked by `ssr mackup unlink` (interactive confirm).
|
||||
ssr::mackup::link_uninstall() {
|
||||
ssr::mackup::ensure_installed
|
||||
ssr::log "mackup link uninstall (revert symlinks → local files)"
|
||||
mackup -f link uninstall
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user