Files
System-Sync-Restore/lib/restore-state.sh
T
Oliver Surke 634487b9d8 feat: restore state/resume/report + single Spotlight index after mas installs
lib/brew.sh:
  Set MAS_NO_AUTO_INDEX=1 before brew bundle install to suppress the
  per-app mdimport call that mas otherwise runs after each App Store
  installation. Run one mdimport /Applications batch pass afterwards.

lib/restore-state.sh (new):
  Lightweight key=value state file (~/.local/state/ssr/restore-state).
  ssr::rs::init / ssr::rs::run / ssr::rs::set/get / ssr::rs::error /
  ssr::rs::report — tracks step outcomes and accumulated errors.

bin/ssr-restore:
  --resume flag: skip steps already marked ok in state file.
  Each step (brew_install, brew_restore, mackup_restore, macos_defaults,
  prefs_import) wrapped in ssr::rs::run; errors accumulate without abort.
  Formatted report printed at end of every run.

bin/ssr-status:
  ssr status restore — full restore step report + error log
  ssr status backup  — Brewfile lines, unmanaged apps, prefs snapshot
  ssr status         — general view now shows one-line last-restore
                       summary and hint to run 'ssr status restore'

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 09:39:48 +02:00

145 lines
4.7 KiB
Bash
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.
#!/usr/bin/env bash
# lib/restore-state.sh — tracks restore step outcomes and accumulated errors.
#
# State file: $SSR_STATE_DIR/restore-state (key=value, one per line)
# Error log: $SSR_STATE_DIR/restore-errors.log
#
# Step values: ok | warn | fail | skipped | pending
_ssr_rs_state_file() {
printf '%s/restore-state' "${SSR_STATE_DIR:-$HOME/.local/state/ssr}"
}
_ssr_rs_error_file() {
printf '%s/restore-errors.log' "${SSR_STATE_DIR:-$HOME/.local/state/ssr}"
}
# Initialise (or reset) the state file for a new restore run.
# $1 = brewfile path used for this run.
ssr::rs::init() {
local brewfile="${1:-}"
local state_dir="${SSR_STATE_DIR:-$HOME/.local/state/ssr}"
ssr::ensure_dir "$state_dir"
# Archive previous state so `ssr status restore` can show it even after a
# new run starts.
local sf; sf="$(_ssr_rs_state_file)"
[[ -f "$sf" ]] && cp -f "$sf" "${sf}.prev" 2>/dev/null || true
# Truncate error log for the new run.
: > "$(_ssr_rs_error_file)"
# Write fresh state header.
printf 'started=%s\nbrewfile=%s\n' "$(ssr::ts)" "$brewfile" > "$sf"
}
# Set a key in the state file (upsert).
ssr::rs::set() {
local key="$1" val="$2"
local sf; sf="$(_ssr_rs_state_file)"
local tmp; tmp="$(mktemp)"
grep -v "^${key}=" "$sf" 2>/dev/null > "$tmp" || true
printf '%s=%s\n' "$key" "$val" >> "$tmp"
mv -f "$tmp" "$sf"
}
# Get a value from the state file (empty string if absent).
ssr::rs::get() {
local key="$1"
grep "^${key}=" "$(_ssr_rs_state_file)" 2>/dev/null | cut -d= -f2- || true
}
# Append a timestamped error line to the error log.
ssr::rs::error() {
printf '[%s] %s\n' "$(ssr::ts)" "$1" >> "$(_ssr_rs_error_file)"
}
# Run a named restore step.
# Usage: ssr::rs::run <step_key> <description> <function> [args…]
#
# If --resume is active and the step is already 'ok', it is skipped.
# Exit code of the function is captured; non-zero sets step to 'warn' (not
# fatal) and logs the error. The outer script keeps running.
ssr::rs::run() {
local key="$1" desc="$2"; shift 2
local fn="$1"; shift
# Resume: skip steps that already completed successfully.
if [[ "${SSR_RESTORE_RESUME:-false}" == "true" ]]; then
local prev; prev="$(ssr::rs::get "step_${key}")"
if [[ "$prev" == "ok" ]]; then
ssr::log "SKIP [$desc] (already completed in previous run)"
return 0
fi
fi
ssr::log "STEP [$desc]"
ssr::rs::set "step_${key}" "running"
local rc=0
"$fn" "$@" || rc=$?
if [[ $rc -eq 0 ]]; then
ssr::rs::set "step_${key}" "ok"
else
ssr::rs::set "step_${key}" "warn:rc=$rc"
ssr::rs::error "[$desc] exited rc=$rc"
ssr::warn "[$desc] finished with errors (rc=$rc) — continuing restore"
fi
}
# Print a formatted restore report to stdout.
ssr::rs::report() {
local sf; sf="$(_ssr_rs_state_file)"
local ef; ef="$(_ssr_rs_error_file)"
if [[ ! -f "$sf" ]]; then
printf 'No restore state found (%s).\n' "$sf"
return 0
fi
local started brewfile
started="$(grep '^started=' "$sf" | cut -d= -f2-)"
brewfile="$(grep '^brewfile=' "$sf" | cut -d= -f2-)"
printf '\n%s\n' "── Restore Report ──────────────────────────────"
printf ' started : %s\n' "${started:-unknown}"
printf ' brewfile : %s\n' "${brewfile:-(unknown)}"
printf '\n Steps:\n'
local -a step_keys=(brew_install brew_restore mackup_restore macos_defaults prefs_import)
local -A step_labels=(
[brew_install]="Homebrew install"
[brew_restore]="Brew bundle install"
[mackup_restore]="Mackup restore"
[macos_defaults]="macOS defaults"
[prefs_import]="Prefs snapshot import"
)
local key val icon
for key in "${step_keys[@]}"; do
val="$(grep "^step_${key}=" "$sf" 2>/dev/null | cut -d= -f2- || true)"
case "$val" in
ok) icon="✓" ;;
skipped) icon="" ;;
warn*) icon="⚠" ;;
fail*) icon="✗" ;;
running) icon="…" ;;
*) icon="?" ;;
esac
printf ' %s %-28s %s\n' "$icon" "${step_labels[$key]}" "${val:-(not run)}"
done
if [[ -s "$ef" ]]; then
printf '\n Errors:\n'
sed 's/^/ /' "$ef"
else
printf '\n No errors recorded.\n'
fi
printf '%s\n\n' "────────────────────────────────────────────────"
# Resume hint if any step failed.
if grep -qE '^step_[^=]+=(warn|fail)' "$sf" 2>/dev/null; then
printf ' To retry failed steps: ssr restore --resume\n\n'
fi
}