#!/usr/bin/env bash # --------------------------------------------------------------------------- # Compare tracked configuration against the live installation. # # Run this before every deploy, and after every migration, to prove that the # repository and the host agree. # # Usage: # scripts/pi-diff.sh # runtime + every scenario # scripts/pi-diff.sh runtime # scripts/pi-diff.sh # # Exit status: 0 when everything matches, 1 when anything differs. # --------------------------------------------------------------------------- # shellcheck source=lib/common.sh . "$(dirname "${BASH_SOURCE[0]}")/lib/common.sh" DIFFS=0 diff_runtime() { head1 "runtime (~/.pi/agent)" local live="$HOME/.pi/agent" local tracked="$REPO_ROOT/runtime/agent" diff_file "$tracked/settings.json" "$live/settings.json" "settings.json" || DIFFS=$((DIFFS + 1)) # models.json is rendered from a template, so compare the redacted forms. if [ -f "$live/models.json" ]; then local a b a="$(mktemp)"; b="$(mktemp)" python3 - "$tracked/models.json.template" >"$a" <<'PY' import json, re, sys print(re.sub(r'"apiKey":\s*".*?"', '"apiKey": ""', json.dumps(json.load(open(sys.argv[1])), ensure_ascii=False, indent=2, sort_keys=True))) PY python3 - "$live/models.json" >"$b" <<'PY' import json, re, sys print(re.sub(r'"apiKey":\s*".*?"', '"apiKey": ""', json.dumps(json.load(open(sys.argv[1])), ensure_ascii=False, indent=2, sort_keys=True))) PY if cmp -s "$a" "$b"; then ok "models.json (key-redacted comparison)" else printf '%s models.json (key-redacted comparison)\n' "${C_YELLOW}DIFF${C_OFF}" diff -u --label repo/models.json.template "$a" --label live/models.json "$b" | sed 's/^/ /' DIFFS=$((DIFFS + 1)) fi rm -f "$a" "$b" else warn "live ~/.pi/agent/models.json is absent" DIFFS=$((DIFFS + 1)) fi local rel while IFS= read -r rel; do diff_file "$tracked/$rel" "$live/$rel" "$rel" || DIFFS=$((DIFFS + 1)) done < <(cd "$tracked" && find extensions prompts -type f 2>/dev/null | sort) } diff_scenario() { local name="$1" dir dir="$(require_scenario "$name")" local profile="$dir/profile.toml" [ -f "$profile" ] || die "scenario '$name' has no profile.toml" local workspace workspace="$(toml_get "$profile" scenario workspace)" [ -n "$workspace" ] || die "scenario '$name': [scenario].workspace is not set" head1 "$name ($workspace)" if [ ! -d "$dir/workspace" ]; then warn "no tracked workspace/ for '$name'; nothing to compare" return fi if [ ! -d "$workspace" ]; then warn "live workspace $workspace does not exist yet" DIFFS=$((DIFFS + 1)) return fi local rel while IFS= read -r rel; do diff_file "$dir/workspace/$rel" "$workspace/$rel" "$rel" || DIFFS=$((DIFFS + 1)) done < <(cd "$dir/workspace" && find . -type f -printf '%P\n' | sort) # Report live files that the repository does not track, so drift is visible in # both directions. Runtime state and toolchains are expected and are skipped. local untracked=() while IFS= read -r rel; do case "$rel" in .ccgram-uploads/*|*/__pycache__/*|*/.venv/*|.venv/*|*.pyc|*.bak-*|\ telegram-gateway/*|download.html|.pi/launch.json|*.log) continue ;; esac # Vendored shared modules are installed by deploy-scenario.sh and are not in # the scenario's workspace/, so compare them against shared/extensions/ # instead of reporting them as untracked -- otherwise the one file most # likely to drift is the one the diff stays silent about. case "$rel" in .pi/extensions/_shared/*) mod="${rel#.pi/extensions/_shared/}" src="$REPO_ROOT/shared/extensions/$mod" if [ ! -f "$src" ]; then untracked+=("$rel (no longer in shared/extensions)") elif ! diff_file "$src" "$workspace/$rel" "$rel (vendored from shared/extensions)"; then DIFFS=$((DIFFS + 1)) fi continue ;; esac [ -f "$dir/workspace/$rel" ] || untracked+=("$rel") done < <(cd "$workspace" && find . -type f -printf '%P\n' | sort) if [ "${#untracked[@]}" -gt 0 ]; then printf '%sUNTRACKED in live workspace:%s\n' "$C_DIM" "$C_OFF" printf ' %s\n' "${untracked[@]}" fi } TARGET="${1:-}" if [ -z "$TARGET" ]; then diff_runtime while IFS= read -r s; do diff_scenario "$s"; done < <(list_scenarios) elif [ "$TARGET" = "runtime" ]; then diff_runtime else diff_scenario "$TARGET" fi printf '\n' if [ "$DIFFS" -eq 0 ]; then ok "repository and live installation agree" else warn "$DIFFS difference(s) found" fi exit $((DIFFS > 0))