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>
This commit is contained in:
2026-05-11 15:05:17 +02:00
commit 539a8fc28f
23 changed files with 1171 additions and 0 deletions
Executable
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
# ssr — System Sync & Restore CLI dispatcher.
# Resolves the repository root, sources libs, and forwards to a subcommand.
set -euo pipefail
# Resolve script directory even when invoked via a symlink (e.g. /usr/local/bin/ssr).
SOURCE="${BASH_SOURCE[0]}"
while [[ -L "$SOURCE" ]]; do
DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
SOURCE="$(readlink "$SOURCE")"
[[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE"
done
SSR_BIN_DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
SSR_ROOT="$(cd "$SSR_BIN_DIR/.." && pwd)"
export SSR_ROOT SSR_BIN_DIR
# shellcheck source=../lib/common.sh
source "$SSR_ROOT/lib/common.sh"
usage() {
cat <<EOF
ssr — System Sync & Restore for macOS
Usage:
ssr <command> [options]
Commands:
install First-time install: links binary, seeds config, schedules sync.
sync Run a backup: brew bundle dump + mackup backup → cloud.
restore [Brewfile] Restore a machine: brew bundle + mackup restore + macOS defaults.
schedule on|off Enable/disable the scheduled launchd sync job.
config show|edit Print or edit the active config file.
status Show last sync time, cloud target, and scheduled state.
version Print version.
Options:
-c, --config PATH Use a specific config file (default: ~/.config/ssr/ssr.conf).
-h, --help Show this help.
Environment:
SSR_CONFIG Same as --config.
Docs: see README.md in the repository root.
EOF
}
# Parse global flags.
while [[ $# -gt 0 ]]; do
case "$1" in
-c|--config) export SSR_CONFIG="$2"; shift 2 ;;
-h|--help) usage; exit 0 ;;
--) shift; break ;;
-*) ssr::die "Unknown global flag: $1 (use -h for help)" ;;
*) break ;;
esac
done
[[ $# -gt 0 ]] || { usage; exit 1; }
cmd="$1"; shift
case "$cmd" in
install|sync|restore|schedule|config|status)
target="$SSR_BIN_DIR/ssr-$cmd"
[[ -x "$target" ]] || ssr::die "Subcommand not executable: $target"
exec "$target" "$@"
;;
version|-v|--version)
cat "$SSR_ROOT/VERSION" 2>/dev/null || echo "dev"
;;
help|-h|--help) usage ;;
*) ssr::err "Unknown command: $cmd"; usage; exit 1 ;;
esac
Executable
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# ssr-config — show or edit the active configuration file.
set -euo pipefail
SSR_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck source=../lib/common.sh
source "$SSR_ROOT/lib/common.sh"
cfg="${SSR_CONFIG:-$HOME/.config/ssr/ssr.conf}"
case "${1:-show}" in
show)
if [[ -f "$cfg" ]]; then
echo "# $cfg"
cat "$cfg"
else
ssr::warn "No config found at $cfg"
echo "Run: ssr install to seed one from the example."
exit 1
fi
;;
edit)
ssr::ensure_dir "$(dirname "$cfg")"
if [[ ! -f "$cfg" ]]; then
cp "$SSR_ROOT/config/ssr.conf.example" "$cfg"
chmod 600 "$cfg"
fi
"${EDITOR:-vi}" "$cfg"
;;
path)
echo "$cfg"
;;
*)
ssr::die "Usage: ssr config [show|edit|path]"
;;
esac
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
# ssr-install — local install: symlink CLI, seed config, optionally schedule.
set -euo pipefail
SSR_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck source=../lib/common.sh
source "$SSR_ROOT/lib/common.sh"
ssr::require_macos
PREFIX="${SSR_PREFIX:-$HOME/.local/bin}"
CONFIG_DIR="${SSR_CONFIG_DIR:-$HOME/.config/ssr}"
CONFIG_FILE="$CONFIG_DIR/ssr.conf"
ssr::log "Installing ssr"
ssr::log " prefix: $PREFIX"
ssr::log " config: $CONFIG_FILE"
ssr::ensure_dir "$PREFIX"
ssr::ensure_dir "$CONFIG_DIR"
# Symlink the main CLI so `ssr` works from anywhere.
ln -sfn "$SSR_ROOT/bin/ssr" "$PREFIX/ssr"
ssr::ok "Linked $PREFIX/ssr → $SSR_ROOT/bin/ssr"
# Seed config from the example (do not overwrite an existing user config).
if [[ ! -f "$CONFIG_FILE" ]]; then
cp "$SSR_ROOT/config/ssr.conf.example" "$CONFIG_FILE"
chmod 600 "$CONFIG_FILE"
ssr::ok "Seeded $CONFIG_FILE (chmod 600)"
else
ssr::log "Config already exists, leaving untouched: $CONFIG_FILE"
fi
# Path hint if PREFIX is not on PATH.
case ":$PATH:" in
*":$PREFIX:"*) ;;
*) ssr::warn "$PREFIX is not on your PATH — add it to your shell rc:"
echo " export PATH=\"$PREFIX:\$PATH\"" ;;
esac
# Offer to enable the scheduled sync.
if ssr::confirm "Enable daily scheduled sync (launchd)?" 30; then
"$SSR_ROOT/bin/ssr-schedule" on
fi
ssr::ok "Install complete. Run: ssr status"
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
# ssr-restore — restore brew packages, mackup data, macOS defaults.
set -euo pipefail
SSR_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck source=../lib/common.sh
source "$SSR_ROOT/lib/common.sh"
# shellcheck source=../lib/cloud.sh
source "$SSR_ROOT/lib/cloud.sh"
# shellcheck source=../lib/brew.sh
source "$SSR_ROOT/lib/brew.sh"
# shellcheck source=../lib/mackup.sh
source "$SSR_ROOT/lib/mackup.sh"
# shellcheck source=../lib/macos.sh
source "$SSR_ROOT/lib/macos.sh"
ssr::require_macos
ssr::load_config
usage() {
cat <<EOF
Usage: ssr restore [BREWFILE]
If BREWFILE is omitted, the latest cloud backup is used:
<cloud>/BACKUP/<host> - <uuid>/Brewfile
EOF
}
[[ "${1:-}" == "-h" || "${1:-}" == "--help" ]] && { usage; exit 0; }
ssr::log "ssr restore starting"
ssr::cloud::ensure_available
backup_dir="$(ssr::cloud::backup_dir)"
mackup_dir="$(ssr::cloud::mackup_dir)"
brewfile="${1:-$backup_dir/Brewfile}"
[[ -f "$brewfile" ]] || ssr::die "Brewfile not found: $brewfile"
ssr::log "Using Brewfile: $brewfile"
ssr::confirm "Configure iCloud / cloud drive and proceed with restore?" 60 \
|| ssr::die "Aborted by user"
ssr::macos::disable_gatekeeper
ssr::macos::install_rosetta
ssr::brew::ensure_installed
ssr::brew::update
ssr::brew::restore "$brewfile"
ssr::mackup::ensure_installed
ssr::mackup::configure "$mackup_dir"
ssr::mackup::restore
ssr::macos::open_installed_casks
ssr::macos::apply_defaults
ssr::ok "ssr restore completed"
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# ssr-schedule — manage the launchd scheduled sync job.
set -euo pipefail
SSR_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck source=../lib/common.sh
source "$SSR_ROOT/lib/common.sh"
ssr::require_macos
ssr::load_config
LABEL="${SSR_LAUNCHD_LABEL:-de.surke.ssr.sync}"
PLIST_SRC="$SSR_ROOT/launchd/${LABEL}.plist"
PLIST_DST="$HOME/Library/LaunchAgents/${LABEL}.plist"
LOG_DIR="${SSR_LOG_DIR:-$HOME/.local/state/ssr/logs}"
INTERVAL_HOUR="${SSR_SCHEDULE_HOUR:-9}"
INTERVAL_MIN="${SSR_SCHEDULE_MINUTE:-0}"
SSR_BIN="$SSR_ROOT/bin/ssr"
render_plist() {
ssr::ensure_dir "$LOG_DIR"
ssr::ensure_dir "$(dirname "$PLIST_DST")"
# Substitute placeholders from the template into the user's LaunchAgents dir.
sed \
-e "s|@@LABEL@@|${LABEL}|g" \
-e "s|@@SSR_BIN@@|${SSR_BIN}|g" \
-e "s|@@LOG_DIR@@|${LOG_DIR}|g" \
-e "s|@@HOUR@@|${INTERVAL_HOUR}|g" \
-e "s|@@MINUTE@@|${INTERVAL_MIN}|g" \
"$PLIST_SRC" > "$PLIST_DST"
chmod 600 "$PLIST_DST"
}
cmd_on() {
[[ -f "$PLIST_SRC" ]] || ssr::die "launchd template missing: $PLIST_SRC"
render_plist
# Reload to pick up changes if it was already loaded.
launchctl unload "$PLIST_DST" 2>/dev/null || true
launchctl load -w "$PLIST_DST"
ssr::ok "Scheduled sync enabled (${INTERVAL_HOUR}:$(printf '%02d' "$INTERVAL_MIN") daily) → $PLIST_DST"
}
cmd_off() {
if [[ -f "$PLIST_DST" ]]; then
launchctl unload "$PLIST_DST" 2>/dev/null || true
rm -f "$PLIST_DST"
ssr::ok "Scheduled sync disabled"
else
ssr::log "Scheduled sync was not installed"
fi
}
cmd_status() {
if [[ -f "$PLIST_DST" ]] && launchctl list | grep -q "$LABEL"; then
ssr::ok "Scheduled sync: active (label=$LABEL)"
else
ssr::log "Scheduled sync: inactive"
fi
}
case "${1:-status}" in
on|enable) cmd_on ;;
off|disable) cmd_off ;;
status) cmd_status ;;
*) ssr::die "Usage: ssr schedule [on|off|status]" ;;
esac
Executable
+38
View File
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
# ssr-status — print current state: config, cloud target, last sync, schedule.
set -euo pipefail
SSR_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck source=../lib/common.sh
source "$SSR_ROOT/lib/common.sh"
# shellcheck source=../lib/cloud.sh
source "$SSR_ROOT/lib/cloud.sh"
ssr::load_config
cfg="${SSR_CONFIG:-$HOME/.config/ssr/ssr.conf}"
provider="${SSR_CLOUD_PROVIDER:-icloud}"
echo "ssr status"
echo " repo: $SSR_ROOT"
echo " config: $cfg $([[ -f "$cfg" ]] && echo '(present)' || echo '(missing)')"
echo " provider: $provider"
if cloud_dir="$(ssr::cloud::resolve_dir 2>/dev/null)"; then
echo " cloud dir: $cloud_dir $([[ -d "$cloud_dir" ]] && echo '(ok)' || echo '(unreachable)')"
echo " backup dir: $(ssr::cloud::backup_dir 2>/dev/null || echo 'n/a')"
fi
state_dir="${SSR_STATE_DIR:-$HOME/.local/state/ssr}"
if [[ -f "$state_dir/last-sync" ]]; then
echo " last sync: $(cat "$state_dir/last-sync")"
else
echo " last sync: never"
fi
label="${SSR_LAUNCHD_LABEL:-de.surke.ssr.sync}"
if launchctl list 2>/dev/null | grep -q "$label"; then
echo " schedule: active ($label)"
else
echo " schedule: inactive"
fi
Executable
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
# ssr-sync — back up Homebrew + Mackup state to the configured cloud dir.
set -euo pipefail
SSR_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck source=../lib/common.sh
source "$SSR_ROOT/lib/common.sh"
# shellcheck source=../lib/cloud.sh
source "$SSR_ROOT/lib/cloud.sh"
# shellcheck source=../lib/brew.sh
source "$SSR_ROOT/lib/brew.sh"
# shellcheck source=../lib/mackup.sh
source "$SSR_ROOT/lib/mackup.sh"
ssr::require_macos
ssr::load_config
ssr::log "ssr sync starting"
ssr::syslog "sync starting"
ssr::cloud::ensure_available
backup_dir="$(ssr::cloud::backup_dir)"
mackup_dir="$(ssr::cloud::mackup_dir)"
ssr::ensure_dir "$backup_dir"
ssr::ensure_dir "$mackup_dir"
ssr::brew::ensure_installed
ssr::mackup::ensure_installed
ssr::mackup::configure "$mackup_dir"
ssr::brew::update
ssr::brew::upgrade
ssr::mackup::backup
ssr::brew::dump "$backup_dir"
ssr::brew::cleanup
# Record sync time so `ssr status` can report freshness.
state_dir="${SSR_STATE_DIR:-$HOME/.local/state/ssr}"
ssr::ensure_dir "$state_dir"
date '+%Y-%m-%dT%H:%M:%S%z' > "$state_dir/last-sync"
ssr::ok "ssr sync completed → $backup_dir"
ssr::syslog "sync completed"