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.
|
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.
|
- **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`, restores `mackup` configs, applies macOS defaults.
|
- **Restore** (`ssr restore`): installs Homebrew, replays the `Brewfile`, runs `mackup link`, applies macOS defaults.
|
||||||
- **Scheduled sync**: native macOS `launchd` agent (daily by default).
|
- **Scheduled sync**: native macOS `launchd` agent (daily by default).
|
||||||
- **Cloud storage**: iCloud Drive, Dropbox, Google Drive, OneDrive, or any custom path — selectable via config.
|
- **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.
|
- **No secrets**: all state lives in your cloud drive; the repo only contains code and config templates.
|
||||||
|
|||||||
@@ -27,14 +27,14 @@ Usage:
|
|||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
install First-time install: links binary, seeds config, schedules sync.
|
install First-time install: links binary, seeds config, schedules sync.
|
||||||
sync Run a backup: brew bundle dump + mackup backup → cloud.
|
sync Run a backup: brew bundle dump + mackup link install → cloud.
|
||||||
restore [Brewfile] Restore a machine: brew bundle + mackup restore + macOS defaults.
|
restore [Brewfile] Restore a machine: brew bundle + mackup link + macOS defaults.
|
||||||
update [--dry-run] Pull latest ssr code, refresh launchd if needed.
|
update [--dry-run] Pull latest ssr code, refresh launchd if needed.
|
||||||
schedule on|off Enable/disable the scheduled launchd sync job.
|
schedule on|off Enable/disable the scheduled launchd sync job.
|
||||||
config show|edit Print or edit the active config file.
|
config show|edit Print or edit the active config file.
|
||||||
completions Install or remove shell completions (zsh/bash/fish).
|
completions Install or remove shell completions (zsh/bash/fish).
|
||||||
status Show last sync time, cloud target, and scheduled state.
|
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.
|
version Print version.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|||||||
+31
-5
@@ -2,10 +2,11 @@
|
|||||||
# ssr-mackup — Manage auto-generated Mackup app definitions for dot-dirs.
|
# ssr-mackup — Manage auto-generated Mackup app definitions for dot-dirs.
|
||||||
#
|
#
|
||||||
# Subcommands:
|
# Subcommands:
|
||||||
# scan Dry-run: show which ssr-*.cfg files would be created / updated,
|
# scan Per-item prompts for new ~/.mackup/ssr-*.cfg files.
|
||||||
# then ask for confirmation before writing anything.
|
|
||||||
# list List existing ~/.mackup/ssr-*.cfg files with the path they cover.
|
# list List existing ~/.mackup/ssr-*.cfg files with the path they cover.
|
||||||
# clean Remove all generated ssr-*.cfg files (prompts first).
|
# 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
|
set -euo pipefail
|
||||||
SSR_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
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.
|
by Mackup's built-in app definitions.
|
||||||
|
|
||||||
Subcommands:
|
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.
|
list List existing generated files.
|
||||||
clean Remove all generated ssr-*.cfg 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)
|
Run 'ssr mackup scan' when new dot-dirs appear; commit ~/.mackup/ssr-*.cfg
|
||||||
and commit the generated files alongside your dotfiles.
|
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
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,6 +251,25 @@ cmd_clean() {
|
|||||||
ssr::ok "Removed ${#found[@]} file(s)."
|
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
|
# Dispatch
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -254,6 +279,7 @@ case "$1" in
|
|||||||
scan) cmd_scan ;;
|
scan) cmd_scan ;;
|
||||||
list) cmd_list ;;
|
list) cmd_list ;;
|
||||||
clean) cmd_clean ;;
|
clean) cmd_clean ;;
|
||||||
|
unlink) cmd_unlink ;;
|
||||||
-h|--help) usage ;;
|
-h|--help) usage ;;
|
||||||
*) ssr::err "Unknown subcommand: $1"; usage; exit 1 ;;
|
*) ssr::err "Unknown subcommand: $1"; usage; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
+2
-1
@@ -64,7 +64,8 @@ _ssr() {
|
|||||||
_values 'mackup action' \
|
_values 'mackup action' \
|
||||||
'scan[Suggest and confirm ssr-*.cfg files]' \
|
'scan[Suggest and confirm ssr-*.cfg files]' \
|
||||||
'list[List generated 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
|
esac
|
||||||
;;
|
;;
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ _ssr() {
|
|||||||
COMPREPLY=( $(compgen -W "install uninstall status" -- "$cur") )
|
COMPREPLY=( $(compgen -W "install uninstall status" -- "$cur") )
|
||||||
;;
|
;;
|
||||||
mackup)
|
mackup)
|
||||||
COMPREPLY=( $(compgen -W "scan list clean" -- "$cur") )
|
COMPREPLY=( $(compgen -W "scan list clean unlink" -- "$cur") )
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
COMPREPLY=()
|
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 config' -a 'show edit path'
|
||||||
complete -c ssr -n '__ssr_using_subcmd completions' -a 'install uninstall status'
|
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_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_BACKUP_SUBDIR=BACKUP
|
||||||
SSR_MACKUP_SUBDIR=Mackup
|
SSR_MACKUP_SUBDIR=Mackup
|
||||||
|
|
||||||
# Auto-discover uncovered dot-dirs in $HOME and add them to a generated
|
# Optional iTerm2 race workaround: pre-symlink DynamicProfiles → Mackup cloud
|
||||||
# ~/.mackup/ssr-custom.cfg before each backup. Set to "false" to opt out.
|
# before `mackup link install`. Default is off — enabling can break iTerm if
|
||||||
SSR_MACKUP_AUTODISCOVER=true
|
# 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.
|
# Subdirs (relative to $HOME) to delete before each mackup backup attempt.
|
||||||
# Handles transient dirs with dangling symlinks or sockets (debug sessions,
|
# 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)
|
# Docker / VM infrastructure (large, or require special handling)
|
||||||
.vagrant .minikube .kube .helm
|
.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
|
# Sync-service data directories — never back up a sync engine inside iCloud
|
||||||
.dropbox # Dropbox data; backing up to iCloud would be circular
|
.dropbox # Dropbox data; backing up to iCloud would be circular
|
||||||
.OneDrive # OneDrive data directory
|
.OneDrive # OneDrive data directory
|
||||||
@@ -375,19 +364,21 @@ ssr::mackup::backup() {
|
|||||||
[[ -n "$mackup_root" ]] && ssr::mackup::_materialize_icloud "$mackup_root"
|
[[ -n "$mackup_root" ]] && ssr::mackup::_materialize_icloud "$mackup_root"
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Pre-symlink: paths managed by apps that recreate their directory almost
|
# Optional pre-symlink (OFF by default): iTerm2 can recreate
|
||||||
# instantly after deletion, making Mackup's delete→symlink sequence lose the
|
# DynamicProfiles/ so fast that `mackup link install` loses the race.
|
||||||
# race. We pre-create the symlink → cloud BEFORE running `link install`.
|
# Forcing a symlink here can break iTerm if the cloud copy is wrong or
|
||||||
# Mackup detects the existing correct symlink and skips the path entirely.
|
# 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).
|
# Format: home-relative path (same as in the Mackup app definition).
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
local -a _presymlink=(
|
local -a _presymlink=()
|
||||||
# iTerm2 recreates DynamicProfiles within milliseconds of deletion.
|
case "${SSR_MACKUP_PRESYMLINK:-}" in
|
||||||
# Pre-symlinking ensures Mackup skips it; iTerm2 reads/writes through
|
1|true|yes|on)
|
||||||
# the symlink normally (creating new profiles goes straight to iCloud).
|
_presymlink=(.config/iterm2/AppSupport/DynamicProfiles)
|
||||||
.config/iterm2/AppSupport/DynamicProfiles
|
;;
|
||||||
)
|
esac
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Light preclean — remove transient directories so their ephemeral contents
|
# 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 ~/"
|
ssr::ok "mackup link completed in ${elapsed}s — symlinks created in ~/"
|
||||||
fi
|
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