#!/usr/bin/env bash # --------------------------------------------------------------------------- # Restore a backup produced by scripts/pi-backup.sh. # # Deliberately conservative: # - refuses to run unless the manifest verifies # - moves the current directory aside instead of overwriting it # - never restarts a service # - dry run by default # # Usage: # scripts/pi-restore.sh --from [--only ] [--apply] # # Components: runtime, sessions, workspace-, sessions-, # systemd. Secrets are never restored automatically -- unpack # secrets.tar.gz by hand so that each file lands where you intend. # --------------------------------------------------------------------------- # shellcheck source=lib/common.sh . "$(dirname "${BASH_SOURCE[0]}")/lib/common.sh" FROM="" ONLY="" APPLY=0 while [ "$#" -gt 0 ]; do case "$1" in --from) FROM="${2:?--from needs a path}"; shift 2 ;; --only) ONLY="${2:?--only needs a component}"; shift 2 ;; --apply) APPLY=1; shift ;; -h|--help) sed -n '2,18p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;; *) die "unknown argument: $1" ;; esac done [ -n "$FROM" ] || die "--from is required" [ -d "$FROM" ] || die "no such directory: $FROM" [ -f "$FROM/MANIFEST.sha256" ] || die "no MANIFEST.sha256 in $FROM" head1 "verify $FROM" ( cd "$FROM" && sha256sum -c MANIFEST.sha256 ) || die "manifest verification failed; refusing to restore" ok "manifest verified" STAMP="$(date -u +%Y%m%dT%H%M%SZ)" [ "$APPLY" -eq 1 ] || info "\n${C_DIM}(dry run; pass --apply to write)${C_OFF}" # Move a live path aside rather than overwriting, so a bad restore is reversible. preserve() { local path="$1" [ -e "$path" ] || return 0 local aside="$path.pre-restore-$STAMP" info " preserve $path -> $aside" [ "$APPLY" -eq 1 ] && mv "$path" "$aside" return 0 } extract() { local archive="$1" into="$2" info " extract $(basename "$archive") -> $into" if [ "$APPLY" -eq 1 ]; then install -d -m 700 "$into" tar -xzf "$archive" -C "$into" fi } want() { [ -z "$ONLY" ] || [ "$ONLY" = "$1" ]; } if want runtime && [ -f "$FROM/pi-agent-runtime.tar.gz" ]; then head1 "runtime" preserve "$HOME/.pi/agent" extract "$FROM/pi-agent-runtime.tar.gz" "$HOME/.pi" fi if want sessions && [ -f "$FROM/pi-global-sessions.tar.gz" ]; then head1 "global sessions" extract "$FROM/pi-global-sessions.tar.gz" "$HOME/.pi/agent" fi while IFS= read -r name; do profile="$REPO_ROOT/scenarios/$name/profile.toml" [ -f "$profile" ] || continue if want "workspace-$name" && [ -f "$FROM/workspace-$name.tar.gz" ]; then workspace="$(toml_get "$profile" scenario workspace)" if [ -n "$workspace" ]; then head1 "workspace $name" preserve "$workspace" extract "$FROM/workspace-$name.tar.gz" "$(dirname "$workspace")" fi fi if want "sessions-$name" && [ -f "$FROM/sessions-$name.tar.gz" ]; then session_dir="$(toml_get "$profile" scenario session_dir)" if [ -n "$session_dir" ]; then head1 "sessions $name" preserve "$session_dir" extract "$FROM/sessions-$name.tar.gz" "$(dirname "$session_dir")" fi fi done < <(list_scenarios) if want systemd && [ -f "$FROM/systemd-units.tar.gz" ]; then head1 "systemd units" info " ${C_DIM}not extracted automatically: the archive holds every user unit," info " and most are unrelated to Pi. Unpack and install selectively:${C_OFF}" info " mkdir /tmp/units && tar -xzf $FROM/systemd-units.tar.gz -C /tmp/units" info " install -m 600 /tmp/units/ ~/.config/systemd/user/" info " systemctl --user daemon-reload" fi if [ -f "$FROM/secrets.tar.gz" ]; then head1 "secrets" info " ${C_DIM}not restored automatically. Filenames are path-encoded; unpack and" info " place each file deliberately, then chmod 600:${C_OFF}" info " mkdir -m 700 /tmp/sec && tar -xzf $FROM/secrets.tar.gz -C /tmp/sec && ls /tmp/sec" fi printf '\n' if [ "$APPLY" -eq 1 ]; then ok "restore applied; preserved copies carry the suffix .pre-restore-$STAMP" info "" info "Restart the affected gateways yourself, then check health:" info " systemctl --user restart curator.service pi-memo-telegram.service" info " curl -fsS http://127.0.0.1:8766/api/health" else info "${C_DIM}dry run complete; re-run with --apply${C_OFF}" fi