1b3617da3b
REVIEW.md: 2 P0 fixed (stale test defaults, sandbox isolation), 8 P1+ flagged for post-hoc review. Verdict: APPROVE_WITH_NOTES. AUDIT.md: 0 critical, 5 warnings. Reconstruction PASS, file discipline PASS, branch hygiene PASS, commit discipline PASS. Verdict: HEALTHY. Doc-drift fixed (REQ statuses → complete). P0 fixes in working tree: 1. lxc-config.bats: aligned stale defaults with production code 2. lxc-deploy.bats: fixed sandbox isolation (HOME redirect) ---ci--- project: praxis phase: 2 milestone: v0.2 status: review ---/ci---
220 lines
9.0 KiB
Bash
220 lines
9.0 KiB
Bash
#!/usr/bin/env bats
|
|
# Bats tests for scripts/proxmox/lxc-config.sh (praxis CT config).
|
|
#
|
|
# Run: bats scripts/proxmox/test/lxc-config.bats
|
|
#
|
|
# lxc-config.sh sets memory + onboot via REST PUT /config (API-token-
|
|
# accepted), then sets hookscript + lxc.environment via SSH to the PVE
|
|
# host (root-only fields rejected by REST). The SSH heredoc sed -i's
|
|
# prior lines then cat >> appends the new ones — idempotent on re-run.
|
|
# These tests exercise the real lxc-config.sh with a mocked api.sh
|
|
# (pve_curl records the PUT) + a mocked ssh that runs the heredoc body
|
|
# locally so sed/cat operate on a sandbox conf file.
|
|
#
|
|
# Praxis v0.2 (vs coreci) key differences asserted here:
|
|
# - hookscript snippet name is "praxis-firstboot.sh" (NOT "coreci-firstboot.sh")
|
|
# - lxc.environment includes PRAXIS_PORT=8789 (NOT CORECI_HTTP_PORT=18080)
|
|
# - lxc.environment includes voice-service vars (DEEPGRAM, CARTESIA, OLLAMA)
|
|
# - memory default 4096 (NOT 2048)
|
|
# - PRAXIS_VERSION, PRAXIS_DB_PATH, PRAXIS_TTS, PRAXIS_SCENARIO present
|
|
|
|
setup() {
|
|
SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
CONFIG="${SCRIPT_DIR}/lxc-config.sh"
|
|
|
|
STUB_DIR="$(mktemp -d)"
|
|
export STUB_DIR
|
|
LOG="${STUB_DIR}/calls.log"
|
|
export CALL_LOG="$LOG"
|
|
: > "$LOG" 2>/dev/null || true
|
|
CONF_FILE="${STUB_DIR}/pve-lxc-200.conf"
|
|
export CONF_FILE
|
|
|
|
# Sandbox: <ROOT>/lxc-config.sh (SCRIPT_DIR) + <ROOT>/api.sh (sourced)
|
|
# + <ROOT>/ssh (mocked) on PATH ahead of /usr/bin.
|
|
ROOT="${STUB_DIR}/root"
|
|
mkdir -p "$ROOT"
|
|
cp "$CONFIG" "${ROOT}/lxc-config.sh"
|
|
|
|
# Mocked api.sh — pve_env validates required env vars (mirrors the
|
|
# real helper so the env-validation path is exercised); pve_curl
|
|
# records method + path + body.
|
|
cat > "${ROOT}/api.sh" <<'ASTUB'
|
|
pve_env() {
|
|
missing=0
|
|
for var in "$@"; do
|
|
eval "val=\"\${${var}:-}\""
|
|
if [ -z "$val" ]; then
|
|
echo "pve_env: $var is required but not set" >&2
|
|
missing=1
|
|
fi
|
|
done
|
|
return "$missing"
|
|
}
|
|
pve_curl() {
|
|
method="$1"; path="$2"; shift 2
|
|
printf '%s\n' "${method} ${path} $*" >> "$CALL_LOG"
|
|
printf '%s\n' "${STUB_PVE_CURL_OUT:-null}"
|
|
}
|
|
pve_tls_insecure() { :; }
|
|
pve_auth_header() { :; }
|
|
ASTUB
|
|
|
|
# Mocked ssh — writes everything after the remote host arg into a
|
|
# script and runs it with sh, so the sed -i + cat >> execute locally
|
|
# against $CONF_FILE (the heredoc references $conf set from
|
|
# $conf_file which the script sets to /etc/pve/lxc/<vmid>.conf — we
|
|
# override that path by rewriting the conf= line to point at our
|
|
# sandbox file). Records the raw heredoc body to $CALL_LOG.
|
|
cat > "${ROOT}/ssh" <<'SSTUB'
|
|
#!/bin/sh
|
|
# ssh [opts] host <remote-script>
|
|
# Drop the opts (-o ...) and the host (root@...); the rest is the script.
|
|
shift # drop -o StrictHostKeyChecking=no
|
|
host="$1"; shift
|
|
remote="$*"
|
|
printf '%s\n' "$remote" >> "$CALL_LOG"
|
|
# Run the remote script locally so sed/cat operate on the sandbox conf.
|
|
# The heredoc sets conf='<path>' then sed -i + cat >> operate on $conf.
|
|
# We rewrite the conf path to point at our sandbox file.
|
|
remote_fixed=$(printf '%s\n' "$remote" | sed "s|/etc/pve/lxc/[0-9]*\.conf|${CONF_FILE}|g")
|
|
sh -c "$remote_fixed"
|
|
SSTUB
|
|
|
|
chmod +x "${ROOT}"/*.sh "${ROOT}/ssh"
|
|
|
|
export PATH="${ROOT}:${PATH}"
|
|
export PROXMOX_API_URL="https://proxmox.test:8006/api2/json"
|
|
export PROXMOX_API_TOKEN="root@pam!test=secret"
|
|
export PROXMOX_NODE="testnode"
|
|
export PROXMOX_STORAGE="local"
|
|
export GITEA_TOKEN="gitea-test-token"
|
|
export PRAXIS_VERSION="v0.2"
|
|
export PRAXIS_PORT="8789"
|
|
}
|
|
|
|
teardown() {
|
|
[ -n "${STUB_DIR:-}" ] && rm -rf "$STUB_DIR"
|
|
}
|
|
|
|
@test "config: REST PUT /nodes/{node}/lxc/{vmid}/config with onboot + memory=4096" {
|
|
run "${ROOT}/lxc-config.sh" 200
|
|
[ "$status" -eq 0 ]
|
|
grep -q '^PUT /nodes/testnode/lxc/200/config onboot=1 memory=4096$' "$LOG"
|
|
# Default memory is 4096 (NOT 2048 — coreci was 2048).
|
|
! grep -q 'memory=2048' "$LOG"
|
|
grep -q 'lxc-config: VMID 200 configured' <<< "$output"
|
|
}
|
|
|
|
@test "config: PROXMOX_MEMORY_MB override → memory field reflects it" {
|
|
PROXMOX_MEMORY_MB=8192 run "${ROOT}/lxc-config.sh" 200
|
|
[ "$status" -eq 0 ]
|
|
grep -q 'PUT /nodes/testnode/lxc/200/config onboot=1 memory=8192' "$LOG"
|
|
}
|
|
|
|
@test "config: SSH appends hookscript=local:snippets/praxis-firstboot.sh (NOT coreci-firstboot.sh)" {
|
|
run "${ROOT}/lxc-config.sh" 200
|
|
[ "$status" -eq 0 ]
|
|
[ -f "$CONF_FILE" ]
|
|
grep -q '^onboot: 1$' "$CONF_FILE"
|
|
grep -q '^hookscript: local:snippets/praxis-firstboot.sh$' "$CONF_FILE"
|
|
# NOT coreci (praxis rebrand).
|
|
! grep -q 'coreci-firstboot.sh' "$CONF_FILE"
|
|
}
|
|
|
|
@test "config: lxc.environment includes PRAXIS_PORT=8789 (NOT CORECI_HTTP_PORT=18080)" {
|
|
run "${ROOT}/lxc-config.sh" 200
|
|
[ "$status" -eq 0 ]
|
|
[ -f "$CONF_FILE" ]
|
|
grep -q '^lxc.environment: PRAXIS_PORT=8789$' "$CONF_FILE"
|
|
# NOT the coreci var name + port.
|
|
! grep -q 'CORECI_HTTP_PORT' "$CONF_FILE"
|
|
! grep -q '18080' "$CONF_FILE"
|
|
}
|
|
|
|
@test "config: lxc.environment includes PRAXIS_VERSION + PRAXIS_DB_PATH + PRAXIS_TTS + PRAXIS_SCENARIO" {
|
|
PRAXIS_DB_PATH=/app/data/praxis.db
|
|
PRAXIS_TTS=deepgram
|
|
PRAXIS_SCENARIO=default
|
|
export PRAXIS_DB_PATH PRAXIS_TTS PRAXIS_SCENARIO
|
|
run "${ROOT}/lxc-config.sh" 200
|
|
[ "$status" -eq 0 ]
|
|
grep -q '^lxc.environment: PRAXIS_VERSION=v0.2$' "$CONF_FILE"
|
|
grep -q '^lxc.environment: PRAXIS_DB_PATH=/app/data/praxis.db$' "$CONF_FILE"
|
|
grep -q '^lxc.environment: PRAXIS_TTS=deepgram$' "$CONF_FILE"
|
|
grep -q '^lxc.environment: PRAXIS_SCENARIO=default$' "$CONF_FILE"
|
|
}
|
|
|
|
@test "config: lxc.environment includes GITEA_TOKEN when set" {
|
|
run "${ROOT}/lxc-config.sh" 200
|
|
[ "$status" -eq 0 ]
|
|
grep -q '^lxc.environment: GITEA_TOKEN=gitea-test-token$' "$CONF_FILE"
|
|
}
|
|
|
|
@test "config: GITEA_TOKEN unset → no GITEA_TOKEN lxc.environment line" {
|
|
run env -u GITEA_TOKEN "${ROOT}/lxc-config.sh" 200
|
|
[ "$status" -eq 0 ]
|
|
[ -f "$CONF_FILE" ]
|
|
grep -q '^hookscript: local:snippets/praxis-firstboot.sh$' "$CONF_FILE"
|
|
! grep -q '^lxc.environment: GITEA_TOKEN=' "$CONF_FILE"
|
|
# The other env lines are still present.
|
|
grep -q '^lxc.environment: PRAXIS_PORT=8789$' "$CONF_FILE"
|
|
}
|
|
|
|
@test "config: lxc.environment includes voice-service vars (DEEPGRAM, CARTESIA, OLLAMA)" {
|
|
DEEPGRAM_API_KEY="dg-key"
|
|
CARTESIA_API_KEY="cart-key"
|
|
OLLAMA_API_KEY="oll-key"
|
|
run env DEEPGRAM_API_KEY="$DEEPGRAM_API_KEY" CARTESIA_API_KEY="$CARTESIA_API_KEY" \
|
|
OLLAMA_API_KEY="$OLLAMA_API_KEY" "${ROOT}/lxc-config.sh" 200
|
|
[ "$status" -eq 0 ]
|
|
grep -q '^lxc.environment: DEEPGRAM_API_KEY=dg-key$' "$CONF_FILE"
|
|
grep -q '^lxc.environment: CARTESIA_API_KEY=cart-key$' "$CONF_FILE"
|
|
grep -q '^lxc.environment: OLLAMA_API_KEY=oll-key$' "$CONF_FILE"
|
|
# Ollama config defaults present (match lxc-config.sh + .env.example).
|
|
grep -q '^lxc.environment: OLLAMA_BASE_URL=https://ollama.com/v1$' "$CONF_FILE"
|
|
grep -q '^lxc.environment: OLLAMA_ROLEPLAY_MODEL=gemma4:cloud$' "$CONF_FILE"
|
|
grep -q '^lxc.environment: OLLAMA_DEBRIEF_MODEL=deepseek-v4-flash:cloud$' "$CONF_FILE"
|
|
# Deepgram defaults present (match lxc-config.sh + .env.example).
|
|
grep -q '^lxc.environment: DEEPGRAM_MODEL=nova-3$' "$CONF_FILE"
|
|
grep -q '^lxc.environment: DEEPGRAM_LANGUAGE=en$' "$CONF_FILE"
|
|
grep -q '^lxc.environment: DEEPGRAM_REGION=na$' "$CONF_FILE"
|
|
}
|
|
|
|
@test "config: voice-service keys default to empty (v0.2 infrastructure-only)" {
|
|
run env -u DEEPGRAM_API_KEY -u CARTESIA_API_KEY -u OLLAMA_API_KEY \
|
|
"${ROOT}/lxc-config.sh" 200
|
|
[ "$status" -eq 0 ]
|
|
# The lines are present but with empty values (v0.2 may ship without
|
|
# the secrets; the CT boots and install-service writes the env file).
|
|
grep -q '^lxc.environment: DEEPGRAM_API_KEY=$' "$CONF_FILE"
|
|
grep -q '^lxc.environment: CARTESIA_API_KEY=$' "$CONF_FILE"
|
|
grep -q '^lxc.environment: OLLAMA_API_KEY=$' "$CONF_FILE"
|
|
}
|
|
|
|
@test "config: idempotent — re-run does not duplicate hookscript/lxc.environment lines" {
|
|
# First run appends the lines.
|
|
"${ROOT}/lxc-config.sh" 200 >/dev/null 2>&1
|
|
# Seed a stale line that the sed should remove (simulates prior state).
|
|
printf 'hookscript: local:snippets/OLD.sh\n' >> "$CONF_FILE"
|
|
# Second run — sed -i removes prior lines, then cat >> appends fresh.
|
|
"${ROOT}/lxc-config.sh" 200 >/dev/null 2>&1
|
|
[ -f "$CONF_FILE" ]
|
|
! grep -q 'OLD.sh' "$CONF_FILE"
|
|
[ "$(grep -c '^hookscript:' "$CONF_FILE")" -eq 1 ]
|
|
[ "$(grep -c '^onboot:' "$CONF_FILE")" -eq 1 ]
|
|
[ "$(grep -c '^lxc.environment: PRAXIS_PORT=' "$CONF_FILE")" -eq 1 ]
|
|
[ "$(grep -c '^lxc.environment: GITEA_TOKEN=' "$CONF_FILE")" -eq 1 ]
|
|
[ "$(grep -c '^lxc.environment: OLLAMA_BASE_URL=' "$CONF_FILE")" -eq 1 ]
|
|
}
|
|
|
|
@test "config: missing VMID arg → exit non-zero (usage)" {
|
|
run "${ROOT}/lxc-config.sh"
|
|
[ "$status" -ne 0 ]
|
|
grep -q 'usage: lxc-config.sh' <<< "$output"
|
|
}
|
|
|
|
@test "config: pve_env fails on missing PROXMOX_API_TOKEN → exit non-zero" {
|
|
run env -u PROXMOX_API_TOKEN "${ROOT}/lxc-config.sh" 200
|
|
[ "$status" -ne 0 ]
|
|
} |