d32e4d487e
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---
154 lines
5.8 KiB
Bash
Executable File
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" |