#!/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 → 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." # GITEA_TOKEN ist OPTIONAL: solange alle Repos public sind, klont es ohne. # Falls RAPPORT-HOST/RAPPORT-STACK wieder privat sind, Token setzen. # ═══ 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 ═══════════════════════════════════════════════════════ # Klon-Präfix: mit Token (falls gesetzt, für private Repos), sonst plain. if [[ -n "$GITEA_TOKEN" ]]; then BASE="https://${GITEA_USER}:${GITEA_TOKEN}@${GITEA#https://}/${GITEA_USER}" else BASE="${GITEA}/${GITEA_USER}" fi 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 '${BASE}/RAPPORT-SERVER.git' SERVER-CONTAINER git clone --depth 1 '${BASE}/RAPPORT-WEBSITE.git' RAPPORT-WEBSITE git clone --depth 1 '${BASE}/RAPPORT-HOST.git' RAPPORT-HOST git clone --depth 1 '${BASE}/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}"