commit 8c1c804eb1bf7dcb5f8ec4a0f3c5a0676b142d46 Author: karim Date: Sat May 30 12:09:25 2026 +0200 Initial: RAPPORT Server Proxmox-LXC-Installer v0.1.0 Ein-Befehl-Installer: baut einen unprivilegierten Debian-12-LXC mit Docker, klont den SERVER-CONTAINER-Stack, generiert Secrets + JWT-Keys, holt die DB-Migrations und startet den Compose-Stack. Co-Authored-By: Claude Opus 4.8 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9fa88ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +*.log diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a5b4ae0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +Rapport-Server — Self-Hosting-Stack für Rapport +Copyright (C) 2026 Karim Gabriele Varano + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +──────────────────────────────────────────────────────────────────────── + +Full license text: https://www.gnu.org/licenses/agpl-3.0.txt + +The bundled Supabase containers (postgres, gotrue, postgrest, realtime, +storage-api, kong, supabase/postgres image) are distributed under their +own permissive licenses (mostly Apache-2.0 and PostgreSQL License) by the +Supabase team. See https://supabase.com/docs/guides/self-hosting for details. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b9779bb --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ +# RAPPORT-SERVER-PROXMOX-LXC + +> ⚠️ **Status: Alpha.** Baut auf [SERVER-CONTAINER](https://git.kgva.ch/karim/rapport-server) auf, das selbst noch nicht end-to-end läuft (siehe dortige „Bekannte offene Punkte"). Erst zum Testen gedacht, nicht für Produktion. + +Ein-Befehl-Installer, der den Rapport-Self-Hosting-Stack in einen **Proxmox-LXC-Container** deployt. Für Leute, die einen Proxmox-Host haben und Rapport nicht auf dem Mac (SERVER-APP), sondern auf dem Server laufen lassen wollen. + +Der vierte Deployment-Pfad der Rapport-Familie: + +| Repo | Zielgruppe | +|---|---| +| `APP` | Endnutzer (Desktop-Client) | +| `SERVER-APP` | Mac-Mini-Selfhost per Doppelklick (Tauri) | +| `SERVER-CONTAINER` | Docker-Compose von Hand (Tech-affin) | +| **`SERVER-PROXMOX-LXC`** | **Proxmox-Homelab — ein Befehl, fertiger Container** | + +--- + +## Was das Script macht + +Läuft auf der **Proxmox-VE-Host-Shell** und: + +1. Lädt das Debian-12-Template (falls nicht vorhanden) +2. Erstellt einen **unprivilegierten** LXC mit `nesting=1,keyctl=1` (nötig für Docker) +3. Installiert Docker + Node im Container +4. Klont [SERVER-CONTAINER](https://git.kgva.ch/karim/rapport-server) nach `/opt/rapport` +5. Generiert zufällige `POSTGRES_PASSWORD` + `JWT_SECRET` und daraus passende `ANON_KEY` / `SERVICE_ROLE_KEY` +6. Setzt `SITE_URL` / `API_EXTERNAL_URL` auf die LAN-IP des Containers +7. Holt die DB-Migrations (`sync-migrations.sh`) +8. Startet den Stack mit `docker compose up -d` + +--- + +## Benutzung + +Auf der **Proxmox-Host-Shell** (als root): + +```bash +bash -c "$(curl -fsSL https://git.kgva.ch/karim/rapport-server-proxmox-lxc/raw/branch/main/rapport-lxc.sh)" +``` + +Danach erreichbar unter `http://:8080`. + +### Parameter (per Env-Var) + +Alle Defaults lassen sich überschreiben: + +```bash +RAM_MB=8192 DISK_GB=30 CT_HOSTNAME=rapport \ +NET_IP=192.168.1.50/24 NET_GW=192.168.1.1 \ +bash -c "$(curl -fsSL https://git.kgva.ch/karim/rapport-server-proxmox-lxc/raw/branch/main/rapport-lxc.sh)" +``` + +| Var | Default | Bedeutung | +|---|---|---| +| `CTID` | nächste freie ID | Container-ID | +| `CT_HOSTNAME` | `rapport-server` | Hostname | +| `CORES` | `2` | CPU-Kerne | +| `RAM_MB` | `6144` | RAM (min. 4096 empfohlen) | +| `SWAP_MB` | `2048` | Swap | +| `DISK_GB` | `20` | Disk (Supabase-Images sind gross) | +| `BRIDGE` | `vmbr0` | Netzwerk-Bridge | +| `STORAGE` | `local-lvm` | Storage für rootfs | +| `TMPL_STORAGE` | `local` | Storage für Template-Cache | +| `NET_IP` | `dhcp` | `dhcp` oder z.B. `192.168.1.50/24` | +| `NET_GW` | — | Gateway (Pflicht bei statischer IP) | +| `PASSWORD` | — | root-Passwort im Container (leer = kein Login) | +| `REPO_URL` | `…/rapport-server.git` | Quelle des Compose-Stacks | +| `REPO_REF` | `main` | Branch/Tag | + +--- + +## Voraussetzungen + +- Proxmox VE 8.x +- ~20 GB freier Storage, ≥4 GB RAM für den Container +- Internet-Zugang im Container (Docker-Images + Repos pullen) + +--- + +## Verwaltung + +```bash +pct exec -- bash -c 'cd /opt/rapport && docker compose ps' # Status +pct exec -- bash -c 'cd /opt/rapport && docker compose logs -f' # Logs +pct enter # Shell +``` + +Backup (Postgres): + +```bash +pct exec -- bash -c 'cd /opt/rapport && docker compose exec -T db pg_dumpall -U postgres' > backup.sql +``` + +Oder einfach den ganzen LXC per Proxmox-Backup (vzdump) sichern. + +--- + +## Lizenz + +GNU AGPL-3.0-or-later — identisch zur Rapport-Familie. diff --git a/rapport-lxc.sh b/rapport-lxc.sh new file mode 100755 index 0000000..a9bfcd8 --- /dev/null +++ b/rapport-lxc.sh @@ -0,0 +1,186 @@ +#!/usr/bin/env bash +# ───────────────────────────────────────────────────────────────────────────── +# RAPPORT Server — Proxmox-LXC-Installer +# +# Läuft auf der PROXMOX-VE-HOST-SHELL (nicht im Container!). +# Baut einen unprivilegierten Debian-12-LXC, installiert Docker, klont den +# Rapport-Compose-Stack (SERVER-CONTAINER), generiert Secrets + JWT-Keys, +# holt die DB-Migrations, setzt die LAN-URLs und startet den Stack. +# +# bash -c "$(curl -fsSL https://git.kgva.ch/karim/rapport-server-proxmox-lxc/raw/branch/main/rapport-lxc.sh)" +# +# …oder lokal: bash rapport-lxc.sh +# +# Alle Parameter sind per Env-Var überschreibbar, z.B.: +# RAM_MB=8192 NET_IP=192.168.1.50/24 NET_GW=192.168.1.1 bash rapport-lxc.sh +# ───────────────────────────────────────────────────────────────────────────── +set -euo pipefail + +VERSION="0.1.0" + +# ═══ Konfiguration ══════════════════════════════════════════════════════════ +CTID="${CTID:-$(pvesh get /cluster/nextid)}" # freie Container-ID +CT_HOSTNAME="${CT_HOSTNAME:-rapport-server}" +DISK_GB="${DISK_GB:-20}" # Supabase-Images sind gross +CORES="${CORES:-2}" +RAM_MB="${RAM_MB:-6144}" # Postgres + 6 Services, min. 4 GB +SWAP_MB="${SWAP_MB:-2048}" +BRIDGE="${BRIDGE:-vmbr0}" +STORAGE="${STORAGE:-local-lvm}" # Storage für rootfs +TMPL_STORAGE="${TMPL_STORAGE:-local}" # Storage für Template-Cache +NET_IP="${NET_IP:-dhcp}" # dhcp ODER z.B. 192.168.1.50/24 +NET_GW="${NET_GW:-}" # nur bei statischer IP nötig +PASSWORD="${PASSWORD:-}" # root-PW im Container (leer = kein Login) +REPO_URL="${REPO_URL:-https://git.kgva.ch/karim/rapport-server.git}" +REPO_REF="${REPO_REF:-main}" +TEMPLATE="${TEMPLATE:-debian-12-standard}" # pveam-Template-Name (Präfix) + +# ═══ Ausgabe-Helfer ═════════════════════════════════════════════════════════ +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 Server — 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 — läuft das auf einem Proxmox-VE-Host?" +command -v pveam >/dev/null || die "pveam nicht gefunden — Proxmox-VE-Host erwartet." + +# ═══ 1 · Debian-Template sicherstellen ══════════════════════════════════════ +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 (siehe: pveam available)." + +if ! pveam list "$TMPL_STORAGE" 2>/dev/null | grep -q "$TMPL_FILE"; then + info "Lade Template $TMPL_FILE auf $TMPL_STORAGE …" + pveam download "$TMPL_STORAGE" "$TMPL_FILE" +fi +TMPL_REF="${TMPL_STORAGE}:vztmpl/${TMPL_FILE}" +msg "Template: $TMPL_REF" + +# ═══ 2 · Netzwerk-String bauen ══════════════════════════════════════════════ +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 (unprivilegiert + Docker-Features) ══════════════ +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" # Pflicht, damit Docker im unpriv. LXC läuft + --unprivileged 1 + --onboot 1 + --ostype debian +) +[[ -n "$PASSWORD" ]] && CREATE_ARGS+=(--password "$PASSWORD") +pct create "${CREATE_ARGS[@]}" +msg "Container #${CTID} erstellt." + +info "Starte Container …" +pct start "$CTID" + +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 · Basis-Pakete + Docker + Node installieren ══════════════════════════ +# Node wird für scripts/generate-keys.mjs gebraucht (ANON/SERVICE-JWT-Keys). +info "Installiere Basis-Pakete + Docker im Container (kann ein paar 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 · Repo klonen ════════════════════════════════════════════════════════ +info "Klone Rapport-Stack ($REPO_URL @ $REPO_REF) …" +pct exec "$CTID" -- bash -c " + set -euo pipefail + rm -rf /opt/rapport + git clone --branch '$REPO_REF' --depth 1 '$REPO_URL' /opt/rapport +" +msg "Stack nach /opt/rapport geklont." + +# ═══ 6 · Container-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 erzeugen: Secrets, JWT-Keys, LAN-URLs ═════════════════════════ +# Reihenfolge laut SERVER-CONTAINER/README: +# .env aus .env.example → POSTGRES_PASSWORD + JWT_SECRET → ANON/SERVICE-Keys +# (müssen aus DEM JWT_SECRET abgeleitet sein!) → SITE_URL/API_EXTERNAL_URL +info "Generiere Secrets + JWT-Keys, setze LAN-URLs …" +pct exec "$CTID" -- env CT_IP="$CT_IP" bash -c ' + set -euo pipefail + cd /opt/rapport + cp -n .env.example .env + + PW=$(openssl rand -hex 32) + JWT=$(openssl rand -hex 32) + sed -i "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=${PW}|" .env + sed -i "s|^JWT_SECRET=.*|JWT_SECRET=${JWT}|" .env + + # ANON_KEY + SERVICE_ROLE_KEY aus dem JWT_SECRET ableiten + KEYS=$(node scripts/generate-keys.mjs 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; } + grep -q "^ANON_KEY=" .env && sed -i "s|^ANON_KEY=.*|ANON_KEY=${ANON}|" .env || echo "ANON_KEY=${ANON}" >> .env + grep -q "^SERVICE_ROLE_KEY=" .env && sed -i "s|^SERVICE_ROLE_KEY=.*|SERVICE_ROLE_KEY=${SVC}|" .env || echo "SERVICE_ROLE_KEY=${SVC}" >> .env + + # LAN-URLs auf die Container-IP zeigen lassen + sed -i "s|^SITE_URL=.*|SITE_URL=http://${CT_IP}:8080|" .env + sed -i "s|^API_EXTERNAL_URL=.*|API_EXTERNAL_URL=http://${CT_IP}:8000|" .env + + # Standard-Ports (LXC ist dediziert, keine Konflikte wie auf Karims Dev-Mac) + sed -i "s|^APP_PORT=.*|APP_PORT=8080|" .env 2>/dev/null || true + sed -i "s|^KONG_HTTP_PORT=.*|KONG_HTTP_PORT=8000|" .env 2>/dev/null || true + sed -i "s|^DB_PORT=.*|DB_PORT=5432|" .env 2>/dev/null || true + chmod 600 .env +' +msg ".env erzeugt (Secrets zufällig, Keys passend zum JWT_SECRET)." + +# ═══ 8 · DB-Migrations holen ════════════════════════════════════════════════ +info "Hole DB-Migrations aus dem App-Repo …" +pct exec "$CTID" -- bash -c "cd /opt/rapport && bash scripts/sync-migrations.sh" +msg "Migrations synchronisiert." + +# ═══ 9 · Stack hochfahren ═══════════════════════════════════════════════════ +info "Starte Compose-Stack (Images pullen, Erststart ~1-2 Min) …" +pct exec "$CTID" -- bash -c "cd /opt/rapport && docker compose up -d" + +# ═══ Fertig ═════════════════════════════════════════════════════════════════ +echo +msg "RAPPORT Server läuft in LXC #${CTID} (${CT_HOSTNAME})" +echo -e " ${GN}Frontend:${CL} http://${CT_IP}:8080" +echo -e " ${GN}API/Kong:${CL} http://${CT_IP}:8000" +echo -e " ${GN}Postgres:${CL} ${CT_IP}:5432 (nur containerintern; PW in /opt/rapport/.env)" +echo +info "Status: pct exec ${CTID} -- bash -c 'cd /opt/rapport && docker compose ps'" +info "Logs: pct exec ${CTID} -- bash -c 'cd /opt/rapport && docker compose logs -f'" +info "Shell: pct enter ${CTID}"