Files
System-Sync-Restore/README.md
T
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

8.5 KiB
Raw Blame History

ssr — System Sync & Restore for macOS

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.
  • 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.

Successor to the original system_sync.sh / system_restore.sh. Same intent, modernized tooling, modular, idempotent, scheduled by default.


Requirements

  • macOS (Apple Silicon or Intel)
  • Bash 3.2+ (preinstalled)
  • Network access for the initial Homebrew install
  • A cloud drive folder you control (iCloud Drive is the default)

Install

git clone https://git.surke.de/osurke/system-sync-restore.git ~/.ssr
cd ~/.ssr
./install.sh

install.sh will:

  1. Symlink bin/ssr into ~/.local/bin (override with SSR_PREFIX).
  2. Seed ~/.config/ssr/ssr.conf from config/ssr.conf.example (chmod 600).
  3. Optionally enable the scheduled daily sync.

If ~/.local/bin is not on your PATH, add it to your shell rc:

export PATH="$HOME/.local/bin:$PATH"

Usage

ssr install              First-time install: link binary, seed config, schedule sync
ssr sync                 One-shot backup → cloud
ssr restore [Brewfile]   Restore the machine; Brewfile defaults to the cloud copy
ssr update [--dry-run]   Pull latest ssr code, refresh launchd if needed
ssr schedule on|off      Enable/disable the daily launchd job
ssr config show|edit     Show or edit the active config
ssr status               Show config, cloud target, last sync, schedule state
ssr version              Print version

First backup

ssr sync

Output structure in the cloud drive:

<cloud root>/
├── BACKUP/
│   └── <hostname> - <hardware-uuid>/
│       ├── Brewfile
│       └── Brewfile.bak
└── Mackup/
    └── … (managed by mackup)

Restore on a new machine

git clone https://git.surke.de/osurke/system-sync-restore.git ~/.ssr
cd ~/.ssr && ./install.sh
ssr restore

By default ssr restore picks the Brewfile from <cloud>/BACKUP/<host> - <uuid>/Brewfile. To restore from a different machine's snapshot, pass an explicit path:

ssr restore "$HOME/Library/Mobile Documents/com~apple~CloudDocs/BACKUP/oldhost - ABCD-1234/Brewfile"

Configuration

Config lives in ~/.config/ssr/ssr.conf (override with --config or $SSR_CONFIG). The file is sourced by bash but only recognised KEY=VALUE lines are accepted; anything else causes ssr to abort.

Key Default Purpose
SSR_CLOUD_PROVIDER icloud icloud|dropbox|googledrive|onedrive|custom
SSR_CLOUD_PATH Absolute path; only used when provider is custom
SSR_BACKUP_SUBDIR BACKUP Subfolder for per-host backups
SSR_MACKUP_SUBDIR Mackup Subfolder used by mackup
SSR_BREW_UPGRADE true Run brew upgrade during sync
SSR_BREW_CLEANUP true Run brew cleanup after sync
SSR_DISABLE_GATEKEEPER false Run spctl --global-disable during restore (security trade-off)
SSR_OPEN_CASKS false Open every installed cask after restore for first-run config
SSR_MACOS_DEFAULTS repo's config/macos-defaults.sh Override path to your own defaults script
SSR_LAUNCHD_LABEL de.surke.ssr.sync launchd job label
SSR_SCHEDULE_HOUR 9 Daily run hour (023, local time)
SSR_SCHEDULE_MINUTE 0 Daily run minute
SSR_PREFIX $HOME/.local/bin Install prefix for the ssr symlink
SSR_STATE_DIR $HOME/.local/state/ssr Internal state directory
SSR_LOG_DIR $HOME/.local/state/ssr/logs launchd log directory

Cloud provider paths resolved automatically

Provider Resolved root
icloud ~/Library/Mobile Documents/com~apple~CloudDocs
dropbox ~/Library/CloudStorage/Dropbox* (fallback ~/Dropbox)
googledrive ~/Library/CloudStorage/GoogleDrive-*
onedrive ~/Library/CloudStorage/OneDrive-*
custom Value of SSR_CLOUD_PATH

Scheduled sync

ssr schedule on     # write & load ~/Library/LaunchAgents/<label>.plist
ssr schedule off    # unload & remove
ssr schedule status # check

Logs land in $SSR_LOG_DIR/sync.out.log and sync.err.log. Tail them:

tail -f ~/.local/state/ssr/logs/sync.err.log

Show launchd-level details:

launchctl list | grep ssr
log show --predicate 'process == "ssr"' --last 1h

Updating

Updates happen on three independent layers:

Tool code (this repo). Self-update via ssr update:

ssr update            # git pull --ff-only on the clone, then refresh side-effects
ssr update --dry-run  # show what would change, don't pull

ssr update will:

  • Refuse if the working tree is dirty or HEAD is detached.
  • Fast-forward only — never rewrites history.
  • Re-render and reload the launchd plist if launchd/ or bin/ssr* changed and the schedule is currently active.
  • Print added config keys when config/ssr.conf.example gained new options.

Backup data (cloud drive). Refreshed automatically by the scheduled ssr sync. The previous Brewfile is preserved as Brewfile.bak; deeper history is provided by the cloud provider's built-in version retention (iCloud Drive, Dropbox, …). Trigger an on-demand sync with ssr sync.

Dependencies (Homebrew, mackup). ssr sync runs brew upgrade and brew cleanup when SSR_BREW_UPGRADE=true / SSR_BREW_CLEANUP=true (both default true). Set them to false in ssr.conf to keep snapshots without auto-upgrading installed packages.


Project layout

.
├── install.sh              # Thin wrapper around bin/ssr-install
├── bin/
│   ├── ssr                 # CLI dispatcher
│   ├── ssr-install         # Local install + scheduling
│   ├── ssr-update          # Self-update (git pull + launchd refresh)
│   ├── ssr-sync            # Backup to cloud
│   ├── ssr-restore         # Rebuild a Mac from the backup
│   ├── ssr-schedule        # launchd on/off/status
│   ├── ssr-config          # show/edit/path
│   └── ssr-status          # state summary
├── lib/
│   ├── common.sh           # Logging, validation, config loader
│   ├── cloud.sh            # Provider abstraction
│   ├── brew.sh             # Homebrew install + Brewfile ops
│   ├── mackup.sh           # Mackup install + backup/restore
│   └── macos.sh            # macOS-specific actions (Rosetta, defaults, etc.)
├── config/
│   ├── ssr.conf.example    # Reference user config
│   └── macos-defaults.sh   # Default macOS preferences
├── launchd/
│   └── de.surke.ssr.sync.plist  # Template for the scheduled agent
├── VERSION
├── LICENSE
└── README.md

All dependencies are inside the repository; the only external requirements are macOS itself and curl (for the one-time Homebrew bootstrap).


Security

  • No secrets in this repo. All cloud data lives in the user's cloud drive.
  • The config file is sourced but validated: only # comments and KEY=VALUE lines are allowed. Foreign content aborts execution.
  • chmod 600 is applied to ssr.conf and the rendered launchd plist.
  • SSR_DISABLE_GATEKEEPER=true is opt-in only; off by default.
  • No remote pipes (curl … | bash) except Homebrew's own bootstrap during install.

Uninstall

ssr schedule off
rm -f ~/.local/bin/ssr
rm -rf ~/.config/ssr ~/.local/state/ssr

The cloud data is left untouched.


Migration from the legacy scripts

The old system_sync.sh / system_restore.sh map onto the new commands:

Legacy New
system_sync.sh ssr sync
system_restore.sh <Brewfile> ssr restore <Brewfile>
Manual cron / Lingon entry ssr schedule on
Hard-coded iCloud path SSR_CLOUD_PROVIDER in ssr.conf
Inline .macos block config/macos-defaults.sh (overridable)

License

MIT. See LICENSE.