Scenarios - memo-inbox: mirrored by copying; the live directory was not moved or modified and the service was not restarted. All four tracked files match byte for byte (pi-diff.sh reports SAME). Marked deploy = "mirror" so deploy-scenario.sh refuses --apply: applying a mirror would invert the direction of truth and could change a service in daily use. - curator: target configuration, not yet deployed. .pi/SYSTEM.md replaces pi's coding-assistant prompt; durable role text is in .pi/APPEND_SYSTEM.md; profile.toml is the single source of truth for the launch contract. - pi-grok: registered only. It is genuinely a coding agent, so the isolation baseline does not apply in full. Corrections to the documentation, found by testing rather than by reading - AGENTS.override.md does NOT block parent-directory context files; it only shadows its own directory. Verified: with an override file in the workspace, a marker in /tmp/AGENTS.md still reached the system prompt. The only effective switch is --no-context-files, so durable role text must live in .pi/APPEND_SYSTEM.md, which is a system-prompt file and unaffected by -nc. Verified end state: no coding-assistant framing, no pi-docs block, own identity and role text present, no parent pollution, only own skills/tools. - PI_CODING_AGENT_DIR isolates settings/models/auth/trust/extensions/skills/ prompts/themes under the agent directory -- stronger than the --no-* flags because it also repoints credentials -- but does NOT cover ~/.agents/skills. Measured: find-skills, modsearch and summarize still leak. So it complements --no-skills rather than replacing it. - --append-system-prompt accepts a file path, which pi-grok relies on. - cwd is what anchors .pi discovery: a probe that forgot cwd silently lost .pi/SYSTEM.md and kept the coding-assistant persona. Tooling (all dry-run by default; none of them restarts a service) - pi-diff.sh: compares tracked config against the live install in both directions, with a key-redacted comparison for models.json - deploy-scenario.sh: installs a workspace and renders profile.toml into .pi/launch.json, then checks that every referenced path exists - deploy-runtime.sh: renders models.json from its template, refusing placeholder or missing keys. Verified byte-identical to the live file - pi-backup.sh / pi-restore.sh: archives outside the repo, sha256 manifest verified before any restore, live paths preserved rather than overwritten Fixed while testing: pi-backup.sh compared the destination against the repo root literally, so a relative --dest ./backups wrote credential archives into the work tree. Now canonicalised with realpath; ./backups, an absolute in-repo path and ./docs/../backups are all refused.
127 lines
4.3 KiB
Bash
Executable File
127 lines
4.3 KiB
Bash
Executable File
#!/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 <dir> [--only <component>] [--apply]
|
|
#
|
|
# Components: runtime, sessions, workspace-<scenario>, sessions-<scenario>,
|
|
# 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 <backup dir> 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/<unit> ~/.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
|