#!/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" # shellcheck source=../lib/apps.sh source "$SSR_ROOT/lib/apps.sh" # shellcheck source=../lib/macos-prefs.sh source "$SSR_ROOT/lib/macos-prefs.sh" # shellcheck source=../lib/icloud.sh source "$SSR_ROOT/lib/icloud.sh" # shellcheck source=../lib/installers.sh source "$SSR_ROOT/lib/installers.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" # Kick off async iCloud download for the Mackup folder immediately so that # files have maximum time to download while brew update/upgrade runs. # ssr::icloud::wait is called just before mackup backup below. ssr::icloud::prefetch "$mackup_dir" ssr::brew::ensure_installed ssr::mackup::ensure_installed ssr::mackup::configure "$mackup_dir" ssr::brew::update ssr::brew::upgrade # Wait for iCloud downloads triggered above before handing off to mackup. # Timeout (seconds) is configurable; default 180s. ssr::icloud::wait "$mackup_dir" "${SSR_ICLOUD_WAIT_TIMEOUT:-180}" # Auto-generate ~/.mackup/ssr-custom.cfg for dot-dirs not yet covered by # Mackup's built-in definitions (e.g. .claude, .cursor, .easymcp, …). if [[ "${SSR_MACKUP_AUTODISCOVER:-true}" == "true" ]]; then ssr::mackup::generate_custom_cfg "$mackup_dir" fi ssr::mackup::backup ssr::brew::dump "$backup_dir" ssr::brew::cleanup # Back up the extra-installers list alongside the Brewfile. ssr::installers::sync_list "$backup_dir" # Surface .app bundles that aren't covered by brew cask or the App Store — # they would otherwise be missed on `ssr restore` and need manual reinstall. if [[ "${SSR_SCAN_UNMANAGED_APPS:-true}" == "true" ]]; then ssr::log "Scanning for unmanaged .app bundles" report_path="$(ssr::apps::write_report "$backup_dir")" # Each row in the Markdown table starts with `| [`; count those. count="$(grep -cE '^\| \[' "$report_path" 2>/dev/null || echo 0)" if [[ "$count" -gt 0 ]]; then ssr::warn "Found $count unmanaged app(s) — manual reinstall on restore:" # Extract the linked app name from each table row for a quick console summary. grep -E '^\| \[' "$report_path" \ | sed -E 's/^\| \[([^]]+)\].*/ - \1/' ssr::log "Full report: $report_path" else ssr::ok "No unmanaged apps detected." fi fi # Snapshot macOS preference domains so they can be restored on a new machine. if [[ "${SSR_SNAPSHOT_MACOS_PREFS:-true}" == "true" ]]; then ssr::log "Snapshotting macOS preferences" ssr::macos::prefs_export "$backup_dir" fi # 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" # Mark Mackup folder files as keep-local so iCloud doesn't evict them # before the next sync run, avoiding slow on-demand downloads next time. if [[ "${SSR_ICLOUD_KEEP_LOCAL:-true}" == "true" ]]; then ssr::icloud::keep_local "$mackup_dir" fi ssr::ok "ssr sync completed → $backup_dir" ssr::syslog "sync completed"