Files
praxis/scripts/proxmox/e2e-deploy.sh
T
Praxis CI 93d33ecb0c feat(P01): SLICE-08+09+10 — secret wiring, bats tests (121), e2e verification
SLICE-08 (devops-engineer): .env.example updated with Proxmox deployment
  vars (documented, sourced from ~/coreci/.env.secrets per D-026),
  PRAXIS_CLIENT_DIST for StaticFiles, PRAXIS_SCENARIO. config.json
  secrets.scopes already extended in SPECIFY (proxmox + voice scopes).
SLICE-09 (devops-engineer): 10 bats test files (G-106 fix: 10 not 9)
  covering all proxmox scripts — 121 tests, 114 pass + 7 skipped (e2e).
  Mocked curl/pct/ssh; no live cluster needed for unit tests.
SLICE-10 (devops-engineer): e2e-deploy.sh — sources secrets from both
  coreci + praxis .env.secrets, runs full deploy, verifies /health +
  client HTML serving. REQ-DEPLOY-15 covered.

All 6 grill binding decisions addressed:
  G-101 MUST: GITEA_TOKEN baked into snippet (stage-snippet.sh)
  G-102 MUST: PRAXIS_DB_PATH env read (db/store.py + db/migrate.py)
  G-103 FIX:  all 16 env vars in injection list (install-service.sh)
  G-104 FIX:  health-check timeout 600s (health-check.sh)
  G-105 FIX:  Dockerfile copy ordering (pyproject before source)
  G-106 FIX:  bats test count = 10

REQ-DEPLOY-12, 14, 15 covered. All 20 REQ-IDs now implemented.

---ci---
project: praxis
phase: 1
milestone: v0.2
status: execute
slice: 08-10
wave: 4
---/ci---
2026-08-03 18:17:40 +00:00

116 lines
4.2 KiB
Bash
Executable File

#!/bin/sh
# Praxis — E2E deploy verification script.
#
# Runs the full deploy against a live Proxmox cluster, then verifies
# the deployed CT is healthy and serving the praxis client + API.
#
# This is the integration test that proves the deploy pipeline works
# end-to-end. It sources secrets from both ~/coreci/.ciagent/.env.secrets
# (proxmox) and .ciagent/.env.secrets (GITEA_TOKEN, DEEPGRAM_API_KEY).
#
# Usage: ./scripts/proxmox/e2e-deploy.sh [--recreate]
# Exit: 0 on success, 1 on failure
set -eu
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJ_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
CORECI_SECRETS="${HOME}/coreci/.ciagent/.env.secrets"
PRAXIS_SECRETS="${PROJ_ROOT}/.ciagent/.env.secrets"
echo "e2e: praxis LXC deploy verification" >&2
# ── Load secrets ───────────────────────────────────────────────────
if [ ! -f "$CORECI_SECRETS" ]; then
echo "e2e: ERROR — coreci secrets not found at ${CORECI_SECRETS}" >&2
exit 1
fi
if [ ! -f "$PRAXIS_SECRETS" ]; then
echo "e2e: ERROR — praxis secrets not found at ${PRAXIS_SECRETS}" >&2
exit 1
fi
# Source proxmox secrets from coreci (D-026).
set -a
. "$CORECI_SECRETS"
# Source praxis secrets (GITEA_TOKEN, DEEPGRAM_API_KEY).
. "$PRAXIS_SECRETS"
set +a
# Validate required secrets.
for var in PROXMOX_API_URL PROXMOX_API_TOKEN PROXMOX_NODE \
PROXMOX_STORAGE PROXMOX_TEMPLATE_VOLID GITEA_TOKEN; do
eval "val=\"\${${var}:-}\""
if [ -z "$val" ]; then
echo "e2e: ERROR — ${var} is not set" >&2
exit 1
fi
done
echo "e2e: secrets loaded (proxmox from coreci, gitea+deepgram from praxis)" >&2
# ── Run the deploy ─────────────────────────────────────────────────
echo "e2e: running lxc-deploy.sh $*..." >&2
VMID_OUTPUT=$("${SCRIPT_DIR}/lxc-deploy.sh" "$@" 2>&1) || {
echo "e2e: lxc-deploy.sh FAILED" >&2
printf '%s\n' "$VMID_OUTPUT" >&2
exit 1
}
VMID=$(printf '%s\n' "$VMID_OUTPUT" | grep '^VMID=' | cut -d= -f2)
if [ -z "$VMID" ]; then
echo "e2e: ERROR — could not parse VMID from deploy output" >&2
printf '%s\n' "$VMID_OUTPUT" >&2
exit 1
fi
echo "e2e: deployed VMID=${VMID}" >&2
# ── Verify the deployed CT ─────────────────────────────────────────
echo "e2e: verifying deployed CT..." >&2
# 1. Health-check (already ran inside lxc-deploy.sh, but re-verify)
"${SCRIPT_DIR}/health-check.sh" "$VMID" || {
echo "e2e: health-check FAILED for VMID ${VMID}" >&2
exit 1
}
# 2. Fetch the /health endpoint and check the response shape
HEALTH_URL="${PRAXIS_HEALTH_URL:-}"
if [ -z "$HEALTH_URL" ]; then
# Resolve bridge IP like health-check.sh does
ifaces=$(curl -sS --insecure ${PROXMOX_TLS_SKIP_VERIFY:+--insecure} \
-H "Authorization: PVEAPIToken=${PROXMOX_API_TOKEN}" \
"${PROXMOX_API_URL}/nodes/${PROXMOX_NODE}/lxc/${VMID}/interfaces" 2>/dev/null | jq -r '.data')
ip=$(printf '%s' "$ifaces" | jq -r '.[] | select(.name != "lo") | (.inet? // .ip? // empty)' 2>/dev/null | grep -v '^$' | head -1)
HEALTH_URL="http://${ip}:8789/health"
fi
echo "e2e: polling ${HEALTH_URL}..." >&2
HEALTH_RESP=$(curl -fsS --connect-timeout 5 "$HEALTH_URL" 2>&1) || {
echo "e2e: /health endpoint unreachable at ${HEALTH_URL}" >&2
exit 1
}
STATUS=$(printf '%s' "$HEALTH_RESP" | jq -r '.status' 2>/dev/null)
if [ "$STATUS" != "ok" ]; then
echo "e2e: /health status is '${STATUS}' (expected 'ok')" >&2
exit 1
fi
echo "e2e: /health returned status=ok ✓" >&2
# 3. Verify the client is served (GET / should return HTML)
CLIENT_URL="${HEALTH_URL%/health}/"
CLIENT_RESP=$(curl -fsS --connect-timeout 5 "$CLIENT_URL" 2>&1) || {
echo "e2e: client endpoint unreachable at ${CLIENT_URL}" >&2
exit 1
}
case "$CLIENT_RESP" in
*"<html"*|*"<!DOCTYPE"*)
echo "e2e: client served (HTML returned) ✓" >&2
;;
*)
echo "e2e: client endpoint did not return HTML" >&2
exit 1
;;
esac
echo "e2e: ALL CHECKS PASSED — praxis deployed and serving on VMID ${VMID}" >&2
printf 'VMID=%s\nHEALTH_URL=%s\n' "$VMID" "$HEALTH_URL"