Commit Graph

13 Commits

Author SHA1 Message Date
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 371dea1851 feat: auto-detect and install missing shell frameworks after restore
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>
2026-05-12 10:19:32 +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 c330f96f9c fix: restart Dock/Finder/SystemUIServer after mackup restore
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>
2026-05-12 09:51:34 +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 634487b9d8 feat: restore state/resume/report + single Spotlight index after mas installs
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>
2026-05-12 09:39:48 +02:00
Oliver Surke 8489180d51 Fix ssr restore: skip iCloud prompt when available, bail early on missing sudo
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>
2026-05-11 20:51:18 +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 a4465f37c6 Improve schedule and mackup reliability
- 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>
2026-05-11 18:02:31 +02:00
Oliver Surke c1848f66e8 Add shell auto-completion for zsh, bash, and fish
- completions/_ssr            zsh #compdef completion using _arguments/_values
- completions/ssr.bash        bash complete -F function (works with bash-completion@2)
- completions/ssr.fish        fish completions with __ssr_needs_subcmd predicates
- bin/ssr-completions         install/uninstall/status subcommand
                              - auto-detects $SHELL, --shell zsh|bash|fish|all
                              - symlinks into user-scoped paths (no sudo):
                                ~/.zsh/completions/_ssr
                                ~/.local/share/bash-completion/completions/ssr
                                ~/.config/fish/completions/ssr.fish
                              - prints fpath/bash-completion setup hints if not configured
- bin/ssr                     dispatcher knows `completions`; help line added
- bin/ssr-install             offers completion install after schedule prompt
- README                      Shell completions section, command list, layout entry

Verified:
- zsh -n  on _ssr        OK; autoload registers function
- bash -n on ssr.bash    OK; `complete -p ssr` returns the registration after source
- end-to-end install/status/uninstall against an ephemeral $HOME

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:22 +02:00
Oliver Surke 9f745178b3 Add ssr update subcommand for self-update
- 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>
2026-05-11 15:38:11 +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