diff --git a/README.md b/README.md index a38443f..49e9ef6 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/bin/ssr b/bin/ssr index 6614f5d..74babcc 100755 --- a/bin/ssr +++ b/bin/ssr @@ -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: diff --git a/bin/ssr-mackup b/bin/ssr-mackup index e137536..924d3c3 100755 --- a/bin/ssr-mackup +++ b/bin/ssr-mackup @@ -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 diff --git a/completions/_ssr b/completions/_ssr index f019e65..a4493f5 100644 --- a/completions/_ssr +++ b/completions/_ssr @@ -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 ;; diff --git a/completions/ssr.bash b/completions/ssr.bash index 59537fa..1adbc35 100644 --- a/completions/ssr.bash +++ b/completions/ssr.bash @@ -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=() diff --git a/completions/ssr.fish b/completions/ssr.fish index 963619d..08c5959 100644 --- a/completions/ssr.fish +++ b/completions/ssr.fish @@ -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' diff --git a/config/ssr.conf.example b/config/ssr.conf.example index 59249bb..abd12e9 100644 --- a/config/ssr.conf.example +++ b/config/ssr.conf.example @@ -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, diff --git a/lib/mackup.sh b/lib/mackup.sh index 1608264..c90b4f1 100644 --- a/lib/mackup.sh +++ b/lib/mackup.sh @@ -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 +}