_ssr_mackup_declared_paths:
- Fix wrong subdir: apps/ → applications/ (coverage was completely broken)
- Use Mackup's bundled Python interpreter (not system python3)
- Track xdg_configuration_files sections separately and prefix paths
with .config/ so paths like gh/config.yml → .config/gh/config.yml
are properly recognized as covering .config/gh
_is_declared:
- Use `command grep` to bypass colour aliases
- Replace grep -E regex (dot-escaping issues) with bash glob: == "$p/"*
- Applied to both lib/mackup.sh and bin/ssr-mackup
_SSR_MACKUP_CURATED:
- Remove entries now correctly detected as built-in covered:
.claude (claude-code.cfg), .codex, .docker, .config/gh (github-cli xdg)
- Keep .cursor (built-in covers Library/Application Support, not .cursor/)
_SSR_MACKUP_SELECTIVE:
- Remove entries for removed curated items
ssr mackup scan:
- Confirm each new item individually (y/n per candidate)
- Confirm each stale removal individually
- Show resolved sub-paths per item before asking
Co-authored-by: Cursor <cursoragent@cursor.com>
ssr mackup scan now only suggests NEW files — it never overwrites an
existing ssr-*.cfg. Once created, the file belongs to the user who
edits it directly in ~/.mackup/ to customise which paths are backed up.
- _ssr_mackup_write_cfg: returns early if file already exists
- scan: drops UPD logic; only shows NEW suggestions
- _ssr_mackup_resolve_paths: removed SSR_MACKUP_SELECTIVE_PATHS support
(ssr.conf config variable removed — edit the cfg files instead)
- Scan output: hints to edit files directly after creation
Co-authored-by: Cursor <cursoragent@cursor.com>
Mackup with engine=file_system already uses symlinks for all backed-up
apps (/Users/osurke/.docker → <cloud>/Mackup/mackup/.docker). The copy PHASE
fails when the source dir contains sockets or dangling symlinks.
For dirs like .claude that have a volatile debug/ subdir, the clean fix
is to list only the stable sub-paths in the Mackup cfg rather than the
whole directory — debug/ is never touched.
_SSR_MACKUP_SELECTIVE: built-in overrides for .claude, .cursor, .codex,
.vscode, .factory — lists only config files (settings.json, CLAUDE.md,
skills, plugins, agents, …), skipping logs/, debug/, etc.
_ssr_mackup_resolve_paths(): resolves the [configuration_files] entries
for a given item using the selective table, falling back to the whole dir.
ssr mackup scan now shows '6 sub-path(s) of .claude/' in the plan
instead of '.claude/' to make the selective behaviour visible.
Configurable via SSR_MACKUP_SELECTIVE_PATHS in ssr.conf.
Co-authored-by: Cursor <cursoragent@cursor.com>
Instead of silently writing ~/.mackup/ssr-*.cfg on every sync, generation
is now an explicit opt-in command: ssr mackup scan
ssr mackup scan — dry-run: shows NEW/UPD/DEL changes, prompts before writing
ssr mackup list — list existing generated files
ssr mackup clean — remove all generated files (with confirmation)
ssr sync no longer calls generate_custom_cfg automatically.
Completions updated for zsh, bash, fish.
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds a simple config file (~/.config/ssr/extra-installers.txt) where
users can list installer URLs for apps not available on Homebrew or MAS
(e.g. EasyMCP Desktop, internal tools).
lib/installers.sh:
- Supports .dmg (mount → cp .app or run .pkg), .pkg, .zip
- Skips entries already present in /Applications
- Non-fatal: logs warnings and continues on failure
ssr sync: copies extra-installers.txt to <backup_dir> alongside Brewfile
ssr restore: new `extra_installers` resumable step runs after shell_deps
Format: optional-name <url> (# comments and blank lines ignored)
Example entry:
EasyMCP https://github.com/Adobe-AIFoundations/easymcp/.../EasyMCP-Desktop-3.0.3-arm64.dmg
Co-authored-by: Cursor <cursoragent@cursor.com>
After mackup restores dotfiles, ~/.zshrc may reference tools (Oh My Zsh,
nvm, pyenv, rustup, …) that haven't been installed yet, causing errors on
first shell login.
lib/shell.sh:
- Defines 13 built-in frameworks with detect + install commands
- Scans ~/.zshrc / ~/.bashrc etc. for references before installing
(avoids installing things the user doesn't actually use)
- SSR_SHELL_DEPS_EXTRA in ssr.conf for custom user additions
bin/ssr-restore:
- Adds `shell_deps` as a resumable step after mackup_restore
- Sources ~/.config/ssr/post-restore.sh if present (user hook)
config/post-restore.sh.example:
- Template with commented examples (OMZ plugins, npm globals, SSH keys)
Co-authored-by: Cursor <cursoragent@cursor.com>
Before each `ssr sync`, scan $HOME for dot-dirs/files not yet present
in the Mackup backup directory and write them into
~/.mackup/ssr-custom.cfg. This covers tools like .claude, .cursor,
.easymcp, .codex, .factory, and any future dot-dir without requiring
the user to write manual Mackup app definitions.
Curated list handles common developer tools; auto-discovery adds the
rest while skipping caches, credential dirs (.ssh, .gnupg, .aws,
.azure) and other volatile state.
Configurable via:
SSR_MACKUP_AUTODISCOVER=true (opt-out with false)
SSR_MACKUP_EXTRA_PATHS="..." (space-separated $HOME-relative paths)
Co-authored-by: Cursor <cursoragent@cursor.com>
Mackup restores com.apple.dock.plist and other UI preference files, but
the running Dock/Finder processes don't reload them automatically. Add
ssr::macos::restart_ui (killall cfprefsd Dock Finder SystemUIServer + 2s
settle) called immediately after the mackup restore step so the Dock
layout and Finder settings take effect without requiring a logout.
Co-authored-by: Cursor <cursoragent@cursor.com>
lib/icloud.sh (new):
ssr::icloud::prefetch <dir> — spawn background open -g requests for
all .icloud stubs; returns immediately so other work can run in parallel.
ssr::icloud::wait <dir> [sec] — poll until stubs are gone or timeout
(default 180s). Logs progress every 5s.
ssr::icloud::keep_local <dir> — after sync, removes
com.apple.icloud.evictable xattr from every real file + updates
access-time (LRU delay), preventing iCloud from re-creating stubs
before the next run. Best-effort (no public pin API on macOS CLI).
bin/ssr-sync:
- prefetch Mackup folder immediately after cloud is available (before
brew update/upgrade) so downloads proceed in the background.
- ssr::icloud::wait called just before mackup backup.
- ssr::icloud::keep_local called after sync completes.
- SSR_ICLOUD_WAIT_TIMEOUT (default 180) and SSR_ICLOUD_KEEP_LOCAL
(default true) config toggles added.
bin/ssr-restore:
- prefetch Mackup folder right after cloud is validated, before the
brew bundle install step, maximising download time available.
config/ssr.conf.example:
Added SSR_ICLOUD_WAIT_TIMEOUT and SSR_ICLOUD_KEEP_LOCAL keys.
Co-authored-by: Cursor <cursoragent@cursor.com>
lib/brew.sh:
Set MAS_NO_AUTO_INDEX=1 before brew bundle install to suppress the
per-app mdimport call that mas otherwise runs after each App Store
installation. Run one mdimport /Applications batch pass afterwards.
lib/restore-state.sh (new):
Lightweight key=value state file (~/.local/state/ssr/restore-state).
ssr::rs::init / ssr::rs::run / ssr::rs::set/get / ssr::rs::error /
ssr::rs::report — tracks step outcomes and accumulated errors.
bin/ssr-restore:
--resume flag: skip steps already marked ok in state file.
Each step (brew_install, brew_restore, mackup_restore, macos_defaults,
prefs_import) wrapped in ssr::rs::run; errors accumulate without abort.
Formatted report printed at end of every run.
bin/ssr-status:
ssr status restore — full restore step report + error log
ssr status backup — Brewfile lines, unmanaged apps, prefs snapshot
ssr status — general view now shows one-line last-restore
summary and hint to run 'ssr status restore'
Co-authored-by: Cursor <cursoragent@cursor.com>
lib/brew.sh:
- Before running the Homebrew installer, check `sudo -n true`. If sudo
is not available, print a clear actionable error message with the
manual install command and exit instead of running NONINTERACTIVE=1
and crashing with "Need sudo access ... Administrator!" mid-way.
bin/ssr-restore:
- Drop the unconditional "Configure iCloud / cloud drive?" prompt.
Instead check whether the cloud directory is already mounted; if it
is, proceed silently (the common case on an already-configured Mac).
The prompt only appears when the directory is missing, i.e. iCloud
is not yet signed in or the drive is not mounted — with an
appropriate message explaining what to do.
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds a round-trip mechanism for macOS system preferences that are not
covered by Homebrew or Mackup (Dock layout, Finder options, global
keyboard shortcuts, input sources, Spaces config, menu-bar clock,
NSGlobalDomain).
sync: `ssr sync` exports each configured domain with `defaults export`
and writes one plist per domain to <backup_dir>/macos-prefs/. Export is
soft-fail per domain so one bad domain doesn't abort the sync. Gated by
SSR_SNAPSHOT_MACOS_PREFS=true (default on).
restore: `ssr restore` replays the snapshot with `defaults import` after
the declarative config/macos-defaults.sh runs, so the snapshot is the
last writer and wins for accepted domains. Before importing, the restore
detects domains that both the snapshot and macos-defaults.sh touch and
prompts interactively:
Domain `com.apple.dock`:
Snapshot will OVERRIDE values set by macos-defaults.sh.
[A]ccept snapshot / [r]eject (keep declarative)? [A/r]:
Non-TTY (launchd, CI) falls back to SSR_PREFS_CONFLICT_DEFAULT (default
"accept"). SSR_ASSUME_YES=true skips all prompts. After import, Dock,
Finder, SystemUIServer and cfprefsd are restarted once.
New config keys: SSR_SNAPSHOT_MACOS_PREFS, SSR_MACOS_PREF_DOMAINS,
SSR_MACOS_PREF_DOMAINS_EXTRA, SSR_PREFS_CONFLICT_DEFAULT, SSR_ASSUME_YES.
ssr-status surfaces the snapshotted-domain count.
README documents the snapshot flow, default domain table, conflict UX,
and the five new config keys.
Co-authored-by: Cursor <cursoragent@cursor.com>
`ssr sync` now scans /Applications (and ~/Applications, configurable via
SSR_APP_DIRS) for top-level .app bundles that are neither Mac App Store
installs (Contents/_MASReceipt/receipt) nor Homebrew Cask installs, and
writes <backup_dir>/unmanaged-apps.md alongside the Brewfile. This closes
a long-standing gap in the restore story: such apps need manual reinstall
on a fresh machine, and were previously invisible to the sync workflow.
Cask detection runs fully offline against the local Caskroom and unions
four complementary strategies:
1. Caskroom walk for .app symlinks/bundles under
$(brew --caskroom)/<cask>/<version>/.
2. `app "Foo.app"` declarations parsed from .rb formula receipts.
3. `name "Foo"` field from .rb receipts (handles pkg-based casks like
Microsoft Edge, Teams, Zoom, Google Drive, etc., that never drop
a .app under the caskroom).
4. `"name":["Foo"]` field from modern .json receipts (multi-line aware).
For each unmanaged app the report includes version and bundle id from the
Info.plist plus a best-effort installer link, in preference order:
1. Sparkle SUFeedURL → derive the vendor homepage from the URL host.
2. Bundle-id prefix match against a curated vendor map (Adobe,
Microsoft, Docker, Cursor, Anthropic, OpenAI, JetBrains,
GitKraken, Signal, Zoom, Telegram, Slack, Synology, Jabra, ...).
3. DuckDuckGo search fallback (`<App name> macOS download`).
Toggles in ssr.conf.example: SSR_SCAN_UNMANAGED_APPS=true (default),
SSR_APP_DIRS=/Applications:~/Applications.
ssr-status gains a one-line summary referencing the report file.
Co-authored-by: Cursor <cursoragent@cursor.com>
- bin/ssr-schedule: use `launchctl bootstrap`/`bootout` (modern API) with
legacy `load -w` fallback. The previous `load -w`-only flow silently
no-op'd on macOS 10.15+ in some configurations, leaving `ssr status`
reporting `inactive` after a successful `ssr schedule on`.
- bin/ssr-status: query the service via `launchctl print`/`list <label>`
for consistent active/inactive detection.
- lib/mackup.sh: pre-materialize iCloud placeholders via `brctl download`
and retry `mackup -f backup` up to three times to dodge the recurring
`OSError [Errno 66] Directory not empty` race between mackup's rmtree
and iCloud's filesystem provider. Soft-fails so the rest of `ssr sync`
still runs even if mackup never settles; logs actionable hints.
Co-authored-by: Cursor <cursoragent@cursor.com>
- bin/ssr-update: fast-forward `git pull` on the clone with safety guards
(dirty tree, detached HEAD, divergent history all abort).
- Re-renders & reloads the launchd plist when launchd/ or bin/ssr* changed
AND the schedule is currently active, so the cron picks up any plist
template updates automatically.
- Reports VERSION delta, commit log, and new config keys introduced in
config/ssr.conf.example so users can opt-in via `ssr config edit`.
- `--dry-run` shows would-be changes without pulling or touching launchd.
- Wired into bin/ssr dispatcher + README updated (Updating section, layout
entry, command help line).
Co-authored-by: Cursor <cursoragent@cursor.com>