Files
praxis/scripts/proxmox/lxc-deploy.sh
T
Praxis CI d32e4d487e feat(P01): SLICE-05+06+07 — firstboot hook, install-service, deploy orchestrator
SLICE-05 (devops-engineer): firstboot-hook.sh — installs Docker inside CT
  (apt: docker.io, docker-compose-v2, git, curl), clones praxis repo from
  Gitea (GITEA_TOKEN baked into snippet per G-101 fix), runs install-service.sh
SLICE-06 (devops-engineer): install-service.sh — creates praxis user, writes
  /etc/praxis/server.env from lxc.environment vars (G-103: all 16 env vars),
  installs praxis.service systemd unit (Type=simple, ExecStartPre=docker compose
  build, ExecStart=docker compose up, TimeoutStartSec=600 per RESEARCH Q8)
SLICE-07 (devops-engineer): lxc-deploy.sh orchestrator — stage snippet →
  clone → config → start → health-check, idempotent (--recreate/--reconfigure),
  rollback on failure, auto VMID allocation (D-027)

REQ-DEPLOY-06, 09, 10, 11 covered.

---ci---
project: praxis
phase: 1
milestone: v0.2
status: execute
slice: 05-07
wave: 3
---/ci---
2026-08-01 14:19:05 +00:00

154 lines
5.8 KiB
Bash
Executable File

#!/bin/sh
# Praxis — Orchestrator: deploy praxis to a Proxmox LXC container.
#
# Adapted from coreci/scripts/proxmox/lxc-deploy.sh.
# Sequence: stage snippet → clone template → configure CT → start →
# health-check → rollback on failure.
#
# Required env (see .env.example + ~/coreci/.ciagent/.env.secrets):
# PROXMOX_API_URL — https://proxmox:8006/api2/json
# PROXMOX_API_TOKEN — USER@REALM!TOKENID=SECRET
# PROXMOX_NODE — target node name
# PROXMOX_STORAGE — storage holding the template
# PROXMOX_TEMPLATE_VOLID — local:vztmpl/debian-12-template.tar.zst
# GITEA_TOKEN — bearer token for the private Gitea repo
# (baked into the firstboot snippet by stage-snippet.sh)
#
# Optional env:
# PROXMOX_LXC_VMID — target CT VMID (default: auto-allocate via pve_nextid)
# PRAXIS_VERSION — git ref to deploy (default: main)
# PRAXIS_PORT — server HTTP port (default: 8789)
# PRAXIS_HEALTH_URL — override health-check URL
# PROXMOX_MEMORY_MB — CT memory limit (default: 4096)
# PROXMOX_TLS_SKIP_VERIFY— accept self-signed certs (default: false)
# DEEPGRAM_API_KEY — voice-service key (optional, may be empty)
# CARTESIA_API_KEY — voice-service key (optional, may be empty)
# OLLAMA_API_KEY — voice-service key (optional, may be empty)
#
# Flags:
# --recreate — rollback.sh (stop + destroy) then full redeploy
# --reconfigure — re-PUT lxc-config.sh + restart (no clone)
#
# Exit: 0 on successful deploy, 1 on failure (with rollback attempted)
set -eu
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=api.sh disable=SC1091
. "${SCRIPT_DIR}/api.sh"
# shellcheck source=ct-exists.sh disable=SC1091
. "${SCRIPT_DIR}/ct-exists.sh"
# shellcheck source=timing.sh disable=SC1091
. "${SCRIPT_DIR}/timing.sh"
pve_env PROXMOX_API_URL PROXMOX_API_TOKEN PROXMOX_NODE \
PROXMOX_STORAGE PROXMOX_TEMPLATE_VOLID GITEA_TOKEN
# ── Flag parsing ───────────────────────────────────────────────────
recreate=0
reconfigure=0
for arg in "$@"; do
case "$arg" in
--recreate) recreate=1 ;;
--reconfigure) reconfigure=1 ;;
*) echo "deploy: unknown argument: $arg" >&2; exit 2 ;;
esac
done
# Step 0: stage the first-boot hookscript to Proxmox snippet storage.
# G-101 FIX: stage-snippet.sh bakes GITEA_TOKEN into the snippet.
hookscript_volid="${PROXMOX_STORAGE:-local}:snippets/praxis-firstboot.sh"
existing=$(pve_get "/nodes/${PROXMOX_NODE}/storage/${PROXMOX_STORAGE:-local}/content" 2>/dev/null | jq -r --arg v "$hookscript_volid" '.[]? | select(.volid==$v) | .volid' 2>/dev/null || true)
if [ -n "$existing" ]; then
echo "deploy: hookscript snippet ${hookscript_volid} already staged — skipping upload" >&2
else
"${SCRIPT_DIR}/stage-snippet.sh"
fi
# Resolve target VMID (D-027: auto-allocate by default).
vmid="${PROXMOX_LXC_VMID:-auto}"
if [ "$vmid" = "auto" ]; then
vmid=$(pve_nextid)
echo "deploy: auto-allocated VMID ${vmid}" >&2
else
echo "deploy: using configured VMID ${vmid}" >&2
fi
# Trap: rollback on any failure (mirrors coreci pattern).
deploy_failed=0
skip_rollback=0
trap 'deploy_failed=1' INT TERM
cleanup() {
rc=$?
if [ "$skip_rollback" -ne 1 ] && { [ "$deploy_failed" -ne 0 ] || [ "$rc" -ne 0 ]; }; then
echo "deploy: FAILED (rc=${rc}) — rolling back VMID ${vmid}" >&2
"${SCRIPT_DIR}/rollback.sh" "$vmid" 2>&1 || true
fi
}
trap cleanup EXIT
# ── Idempotency: detect existing CT before clone ──────────────────
if ct_exists "$vmid"; then
echo "deploy: VMID ${vmid} already exists — checking health" >&2
ct_healthy=0
if ct_running "$vmid"; then
if PRAXIS_HEALTH_TIMEOUT="${IDEMPOTENCY_HEALTH_TIMEOUT:-30}" \
"${SCRIPT_DIR}/health-check.sh" "$vmid" 2>/dev/null; then
ct_healthy=1
fi
fi
if [ "$ct_healthy" -eq 1 ]; then
echo "deploy: VMID ${vmid} already running + healthy — skipping clone/config/start (idempotent re-deploy)" >&2
skip_provision=1
elif [ "$reconfigure" -eq 1 ]; then
echo "deploy: VMID ${vmid} exists but unhealthy — --reconfigure: re-PUT config + restart" >&2
skip_rollback=1
timing_start reconfigure
"${SCRIPT_DIR}/lxc-config.sh" "$vmid"
"${SCRIPT_DIR}/lxc-start.sh" "$vmid"
timing_end reconfigure
timing_start health
"${SCRIPT_DIR}/health-check.sh" "$vmid"
timing_end health
skip_provision=1
elif [ "$recreate" -eq 1 ]; then
echo "deploy: VMID ${vmid} exists but unhealthy — --recreate: rollback + redeploy" >&2
"${SCRIPT_DIR}/rollback.sh" "$vmid"
skip_provision=0
else
echo "deploy: ERROR — VMID ${vmid} exists but is unhealthy." >&2
echo "deploy: Use --recreate to rollback + redeploy, or --reconfigure to update config + restart." >&2
echo "deploy: No action taken (the existing CT was left intact for inspection)." >&2
skip_rollback=1
exit 1
fi
else
skip_provision=0
fi
if [ "${skip_provision:-0}" -eq 0 ]; then
# Step 1: Clone the template
timing_start clone
"${SCRIPT_DIR}/lxc-clone.sh" "$vmid"
timing_end clone
# Step 2: Configure the CT
timing_start config
"${SCRIPT_DIR}/lxc-config.sh" "$vmid"
timing_end config
# Step 3: Start the CT
timing_start start
"${SCRIPT_DIR}/lxc-start.sh" "$vmid"
timing_end start
# Step 4: Health-check (G-104: 600s timeout for Docker build)
timing_start health
"${SCRIPT_DIR}/health-check.sh" "$vmid"
timing_end health
fi
deploy_failed=0
echo "deploy: praxis deployed successfully to VMID ${vmid}" >&2
printf 'VMID=%s\n' "$vmid"