Files
RAPPORT-SERVER-PROXMOX-LXC/rapport-stack-lxc.sh
T
karim 90fb096ae5 feat: rapport-stack-lxc.sh — All-in-One-Stack auf Proxmox
Deployt RAPPORT-STACK (Supabase + Rapport-Frontend + Marketing-Website +
RAPPORT-HOST + Admin-Cockpit) in einen LXC. Klont alle 4 Repos nach
/opt/rapport (private HOST/STACK via GITEA_TOKEN), generiert Secrets +
ANON/SERVICE-Keys, setzt LAN-URLs, baut + startet den Stack.

Workflow wie openbureau: /opt/rapport/update.sh = git pull (alle Repos) +
sync-migrations + docker compose up -d --build → Änderungen live.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 14:34:48 +02:00

207 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────────────────
# RAPPORT-STACK — Proxmox-LXC-Installer (All-in-One: Supabase + Website + HOST)
#
# Läuft auf der PROXMOX-VE-HOST-SHELL. Baut einen unprivilegierten Debian-12-LXC
# mit Docker, klont die vier Rapport-Repos, generiert Secrets + JWT-Keys, setzt
# die LAN-URLs und fährt den kompletten Stack hoch. Danach Arbeits-Workflow wie
# bei openbureau: pct enter <id> → cd /opt/rapport && ./update.sh
# (git pull in allen Repos + Rebuild + Restart → Änderungen sind live).
#
# GITEA_TOKEN=xxxx bash -c "$(curl -fsSL http://git.kgva.ch/karim/RAPPORT-SERVER-PROXMOX-LXC/raw/branch/main/rapport-stack-lxc.sh)"
#
# GITEA_TOKEN ist PFLICHT: RAPPORT-HOST und RAPPORT-STACK sind private Repos.
# Token erstellen: Gitea → Einstellungen → Anwendungen → Token generieren
# (Scope: read:repository genügt).
# ─────────────────────────────────────────────────────────────────────────────
set -euo pipefail
VERSION="0.1.0"
# ═══ Konfiguration (per Env-Var überschreibbar) ═════════════════════════════
CTID="${CTID:-$(pvesh get /cluster/nextid)}"
CT_HOSTNAME="${CT_HOSTNAME:-rapport-stack}"
DISK_GB="${DISK_GB:-30}" # Supabase-Images + Build brauchen Platz
CORES="${CORES:-4}"
RAM_MB="${RAM_MB:-8192}" # Supabase + HOST + Build, min. 6 GB
SWAP_MB="${SWAP_MB:-2048}"
BRIDGE="${BRIDGE:-vmbr0}"
STORAGE="${STORAGE:-local-lvm}"
TMPL_STORAGE="${TMPL_STORAGE:-local}"
NET_IP="${NET_IP:-dhcp}" # dhcp ODER z.B. 192.168.1.60/24
NET_GW="${NET_GW:-}" # Pflicht bei statischer IP
PASSWORD="${PASSWORD:-}" # root-PW im Container (leer = kein Login)
TEMPLATE="${TEMPLATE:-debian-12-standard}"
GITEA="${GITEA:-https://git.kgva.ch}"
GITEA_USER="${GITEA_USER:-karim}"
GITEA_TOKEN="${GITEA_TOKEN:-}"
RAPPORT_APP_TAG="${RAPPORT_APP_TAG:-main}"
GN='\033[0;32m'; YW='\033[0;33m'; RD='\033[0;31m'; BL='\033[1;34m'; CL='\033[0m'
msg() { echo -e "${GN}${CL} $*"; }
info() { echo -e "${YW}${CL} $*"; }
die() { echo -e "${RD}${CL} $*" >&2; exit 1; }
echo -e "${BL}RAPPORT-STACK — Proxmox-LXC-Installer v${VERSION}${CL}"
# ═══ Vorbedingungen ═════════════════════════════════════════════════════════
[[ $EUID -eq 0 ]] || die "Bitte als root auf der Proxmox-Host-Shell ausführen."
command -v pct >/dev/null || die "pct nicht gefunden — Proxmox-VE-Host erwartet."
command -v pveam >/dev/null || die "pveam nicht gefunden — Proxmox-VE-Host erwartet."
[[ -n "$GITEA_TOKEN" ]] || die "GITEA_TOKEN fehlt. Private Repos (HOST/STACK) brauchen ihn.
Beispiel: GITEA_TOKEN=xxxx bash -c \"\$(curl -fsSL …/rapport-stack-lxc.sh)\""
# ═══ 1 · Debian-Template ════════════════════════════════════════════════════
info "Suche Debian-12-Template …"
pveam update >/dev/null 2>&1 || true
TMPL_FILE="$(pveam available --section system | awk -v t="$TEMPLATE" '$2 ~ t {print $2}' | sort -V | tail -n1)"
[[ -n "$TMPL_FILE" ]] || die "Kein Template '$TEMPLATE*' verfügbar."
if ! pveam list "$TMPL_STORAGE" 2>/dev/null | grep -q "$TMPL_FILE"; then
info "Lade Template $TMPL_FILE"; pveam download "$TMPL_STORAGE" "$TMPL_FILE"
fi
TMPL_REF="${TMPL_STORAGE}:vztmpl/${TMPL_FILE}"
msg "Template: $TMPL_REF"
# ═══ 2 · Netzwerk ═══════════════════════════════════════════════════════════
if [[ "$NET_IP" == "dhcp" ]]; then
NETCFG="name=eth0,bridge=${BRIDGE},ip=dhcp"
else
[[ -n "$NET_GW" ]] || die "Bei statischer IP (NET_IP=$NET_IP) muss NET_GW gesetzt sein."
NETCFG="name=eth0,bridge=${BRIDGE},ip=${NET_IP},gw=${NET_GW}"
fi
# ═══ 3 · Container erstellen ════════════════════════════════════════════════
info "Erstelle LXC #${CTID} (${CT_HOSTNAME}) …"
CREATE_ARGS=(
"$CTID" "$TMPL_REF"
--hostname "$CT_HOSTNAME"
--cores "$CORES" --memory "$RAM_MB" --swap "$SWAP_MB"
--rootfs "${STORAGE}:${DISK_GB}"
--net0 "$NETCFG"
--features "nesting=1,keyctl=1"
--unprivileged 1 --onboot 1 --ostype debian
)
[[ -n "$PASSWORD" ]] && CREATE_ARGS+=(--password "$PASSWORD")
pct create "${CREATE_ARGS[@]}"
pct start "$CTID"
msg "Container #${CTID} läuft."
info "Warte auf Netzwerk …"
for _ in $(seq 1 30); do
pct exec "$CTID" -- getent hosts deb.debian.org >/dev/null 2>&1 && break; sleep 2
done
# ═══ 4 · Docker + Node + git ════════════════════════════════════════════════
info "Installiere Docker + Node + git (kann Minuten dauern) …"
pct exec "$CTID" -- bash -c '
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq ca-certificates curl git openssl nodejs >/dev/null
curl -fsSL https://get.docker.com | sh >/dev/null 2>&1
systemctl enable --now docker >/dev/null 2>&1 || true
'
msg "Docker + Node installiert."
# ═══ 5 · Repos klonen ═══════════════════════════════════════════════════════
# Token-URL nur für die privaten Repos; public über klare URL.
AUTH="${GITEA_USER}:${GITEA_TOKEN}@${GITEA#https://}"
info "Klone Repos nach /opt/rapport …"
pct exec "$CTID" -- bash -c "
set -euo pipefail
rm -rf /opt/rapport && mkdir -p /opt/rapport && cd /opt/rapport
git clone --depth 1 '${GITEA}/${GITEA_USER}/RAPPORT-SERVER.git' SERVER-CONTAINER
git clone --depth 1 '${GITEA}/${GITEA_USER}/RAPPORT-WEBSITE.git' RAPPORT-WEBSITE
git clone --depth 1 'https://${AUTH}/${GITEA_USER}/RAPPORT-HOST.git' RAPPORT-HOST
git clone --depth 1 'https://${AUTH}/${GITEA_USER}/RAPPORT-STACK.git' RAPPORT-STACK
"
msg "Repos geklont."
# ═══ 6 · IP ermitteln ═══════════════════════════════════════════════════════
info "Ermittle Container-IP …"
CT_IP=""
for _ in $(seq 1 15); do
CT_IP="$(pct exec "$CTID" -- hostname -I 2>/dev/null | awk '{print $1}')"
[[ -n "$CT_IP" ]] && break; sleep 2
done
[[ -n "$CT_IP" ]] || die "Konnte keine Container-IP ermitteln."
msg "Container-IP: $CT_IP"
# ═══ 7 · .env generieren (Secrets + Keys + LAN-URLs) ════════════════════════
info "Generiere Secrets + JWT-Keys, setze LAN-URLs …"
pct exec "$CTID" -- env CT_IP="$CT_IP" APP_TAG="$RAPPORT_APP_TAG" bash -c '
set -euo pipefail
cd /opt/rapport/RAPPORT-STACK
cp -n .env.example .env
PWD_DB=$(openssl rand -hex 32)
JWT=$(openssl rand -hex 32)
HJWT=$(openssl rand -hex 32)
ADMINPW=$(openssl rand -hex 12)
# ANON/SERVICE-Keys aus JWT_SECRET ableiten (Skript liegt in SERVER-CONTAINER)
KEYS=$(node /opt/rapport/SERVER-CONTAINER/scripts/generate-keys.mjs "$JWT" 2>/dev/null)
ANON=$(echo "$KEYS" | sed -n "s|^ANON_KEY=||p")
SVC=$(echo "$KEYS" | sed -n "s|^SERVICE_ROLE_KEY=||p")
[ -n "$ANON" ] && [ -n "$SVC" ] || { echo "Key-Generierung fehlgeschlagen" >&2; exit 1; }
set_kv() { sed -i "s|^$1=.*|$1=$2|" .env; }
set_kv POSTGRES_PASSWORD "$PWD_DB"
set_kv JWT_SECRET "$JWT"
set_kv ANON_KEY "$ANON"
set_kv SERVICE_ROLE_KEY "$SVC"
set_kv HOST_JWT_SECRET "$HJWT"
set_kv ADMIN_PASSWORD "$ADMINPW"
set_kv RAPPORT_APP_TAG "$APP_TAG"
set_kv SITE_URL "http://${CT_IP}:8080"
set_kv API_EXTERNAL_URL "http://${CT_IP}:8000"
set_kv PUBLIC_BASE_URL "http://${CT_IP}:8787"
set_kv RAPPORT_INSTANCE_URL_TEMPLATE "http://${CT_IP}:8080/?studio={slug}"
chmod 600 .env
echo "ADMIN_PASSWORD=$ADMINPW" > /opt/rapport/ADMIN_CREDENTIALS.txt
'
msg ".env erzeugt (Secrets zufällig, Keys passend)."
# ═══ 8 · DB-Migrations holen ════════════════════════════════════════════════
info "Hole Rapport-DB-Migrations …"
pct exec "$CTID" -- bash -c "cd /opt/rapport/SERVER-CONTAINER && bash scripts/sync-migrations.sh"
msg "Migrations synchronisiert."
# ═══ 9 · update.sh im Container hinterlegen (Workflow git pull + restart) ════
info "Lege /opt/rapport/update.sh an …"
pct exec "$CTID" -- bash -c 'cat > /opt/rapport/update.sh <<'\''EOF'\''
#!/usr/bin/env bash
# RAPPORT-STACK aktualisieren: git pull in allen Repos + Rebuild + Restart.
set -euo pipefail
cd /opt/rapport
for d in SERVER-CONTAINER RAPPORT-WEBSITE RAPPORT-HOST RAPPORT-STACK; do
echo "→ git pull $d"; git -C "$d" pull --ff-only
done
echo "→ Migrations synchronisieren"
( cd SERVER-CONTAINER && bash scripts/sync-migrations.sh )
echo "→ Rebuild + Restart"
cd RAPPORT-STACK
docker compose up -d --build
docker compose ps
EOF
chmod +x /opt/rapport/update.sh'
msg "update.sh bereit."
# ═══ 10 · Stack hochfahren ══════════════════════════════════════════════════
info "Starte Stack (Images pullen + host bauen, Erststart dauert mehrere Min) …"
pct exec "$CTID" -- bash -c "cd /opt/rapport/RAPPORT-STACK && docker compose up -d --build"
# ═══ Fertig ═════════════════════════════════════════════════════════════════
ADMINPW="$(pct exec "$CTID" -- bash -c 'sed -n "s|^ADMIN_PASSWORD=||p" /opt/rapport/ADMIN_CREDENTIALS.txt' 2>/dev/null || true)"
echo
msg "RAPPORT-STACK läuft in LXC #${CTID} (${CT_HOSTNAME})"
echo -e " ${GN}Hosting-Plattform:${CL} http://${CT_IP}:8787 (Marketing/Login/Konto)"
echo -e " ${GN}Admin-Cockpit:${CL} http://${CT_IP}:8787/admin/ PW: ${ADMINPW}"
echo -e " ${GN}Rapport-App:${CL} http://${CT_IP}:8080"
echo -e " ${GN}Rapport-API:${CL} http://${CT_IP}:8000"
echo
info "Updaten: pct exec ${CTID} -- bash -c 'cd /opt/rapport && ./update.sh'"
info "Status: pct exec ${CTID} -- bash -c 'cd /opt/rapport/RAPPORT-STACK && docker compose ps'"
info "Shell: pct enter ${CTID}"