.pi/extensions/_shared/ is installed by deploy-scenario.sh from shared/extensions/ and is not in the scenario's workspace/, so pi-diff listed it as merely "UNTRACKED in live workspace" -- no comparison at all. The file most likely to drift, because it is the one shared between scenarios, was the one the diff had nothing to say about. Now compared against shared/extensions/ and reported like any other file, with a missing source called out separately. Also fixes the counter: the new branch incremented a variable named `changed` while the summary reads `DIFFS`, so a detected difference printed a diff and then claimed the installation agreed, and exited 0. Verified by planting drift: exit 1 and "1 difference(s) found", then 0 after redeploying.
137 lines
4.6 KiB
Bash
Executable File
137 lines
4.6 KiB
Bash
Executable File
#!/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 <scenario>
|
|
#
|
|
# 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": "<X>"',
|
|
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": "<X>"',
|
|
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))
|