9 Commits

Author SHA1 Message Date
Oliver Surke 421b802467 fix(mackup): update backup path and enhance configuration handling
- Corrected the backup path in the `_in_backup` function to reflect the new structure under `mackup/`.
- Improved error handling in `ssr::mackup::configure` to skip restore if the configuration file is not updated, providing user feedback.
- Updated `ssr::sync` to conditionally execute backup based on configuration validity, enhancing robustness.
- Revised documentation in configuration examples to clarify the new Mackup directory structure and its implications for users.
2026-05-13 20:17:50 +02:00
Oliver Surke c31338dba9 refactor(mackup): transition to copy-based backup and restore
- Updated `ssr sync` and `ssr restore` commands to use `mackup backup` and `mackup restore`, replacing the previous symlink-based approach.
- Added guidance for users with legacy symlink setups to run `ssr mackup unlink` for recovery.
- Revised documentation in README, completions, and configuration examples to reflect the new copy-based methodology for improved reliability on newer macOS versions.
- Enhanced comments in the codebase to clarify the changes and their implications for users.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 09:17:25 +02:00
Oliver Surke 36d4941c95 feat: ssr mackup scan|list|clean — on-demand cfg generation with confirmation
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>
2026-05-12 13:39:17 +02:00
Oliver Surke 72516b864c feat: download and install extra DMG/PKG/ZIP installers during restore
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>
2026-05-12 10:33:56 +02:00
Oliver Surke 649449c628 feat: auto-generate ~/.mackup/ssr-custom.cfg for uncovered dot-dirs
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>
2026-05-12 10:05:16 +02:00
Oliver Surke 698cbe145a feat: async iCloud prefetch + keep-local for Mackup folder
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>
2026-05-12 09:46:12 +02:00
Oliver Surke 5ed6279b51 Snapshot and restore macOS preference domains
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>
2026-05-11 20:19:49 +02:00
Oliver Surke d681792148 Surface unmanaged .app bundles during sync as Markdown report
`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>
2026-05-11 18:04:30 +02:00
Oliver Surke 539a8fc28f Initial ssr repository
Convert the legacy system_sync.sh and system_restore.sh into a modular
shareable repository:

- bin/ssr CLI dispatcher with install/sync/restore/schedule/config/status
- lib/ split: common, cloud, brew, mackup, macos
- Cloud provider abstraction (icloud, dropbox, googledrive, onedrive, custom)
- launchd template for scheduled daily sync (renderable via sed)
- ssr.conf.example with whitelisted KEY=VALUE config loader
- macos-defaults.sh extracted from inline block, overridable
- install.sh bootstrap symlinks CLI into ~/.local/bin
- README with usage, config table, migration map, security notes
- Original scripts archived under legacy/ for reference

Fixes vs originals: set -euo pipefail, quoted vars, find -print0,
spctl --global-disable (was --master-disable), MACOS=".macos" bug,
zero-width char before ln, Apple Silicon brew shellenv, config injection
hardening.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 15:05:17 +02:00