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

221 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```bash
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:
```bash
export PATH="$HOME/.local/bin:$PATH"
```
---
## Usage
```text
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
```bash
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
```bash
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:
```bash
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
```bash
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:
```bash
tail -f ~/.local/state/ssr/logs/sync.err.log
```
Show launchd-level details:
```bash
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 `# 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
```bash
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](LICENSE).