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>
7.2 KiB
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): dumpsBrewfileand runsmackup backupto the configured cloud directory. - Restore (
ssr restore): installs Homebrew, replays theBrewfile, restoresmackupconfigs, applies macOS defaults. - Scheduled sync: native macOS
launchdagent (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:
- Symlink
bin/ssrinto~/.local/bin(override withSSR_PREFIX). - Seed
~/.config/ssr/ssr.conffromconfig/ssr.conf.example(chmod 600). - 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 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 (0–23, 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
Project layout
.
├── install.sh # Thin wrapper around bin/ssr-install
├── bin/
│ ├── ssr # CLI dispatcher
│ ├── ssr-install # Local install + scheduling
│ ├── 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
# commentsandKEY=VALUElines are allowed. Foreign content aborts execution. chmod 600is applied tossr.confand the rendered launchd plist.SSR_DISABLE_GATEKEEPER=trueis 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.