From f43102b71ac87b37d68501c525b38e5efd1fb237 Mon Sep 17 00:00:00 2001 From: karim Date: Sun, 31 May 2026 13:42:30 +0200 Subject: [PATCH] =?UTF-8?q?Initial:=20RAPPORT-STACK=20=E2=80=94=20All-in-O?= =?UTF-8?q?ne=20Compose=20(Supabase=20+=20Website=20+=20HOST)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ein 'docker compose up' bringt die komplette Hosting-Plattform hoch: - include ../SERVER-CONTAINER (db/auth/rest/realtime/storage/kong/app) - host-db: eigene Postgres für RAPPORT-HOST - host: Node-Backend + gebündelte Hugo-Website (Dockerfile.host, multi-stage) provisioniert Kunden-Instanzen über Kong in den Supabase-Stack Eine .env für lokal UND Hetzner (Domains/Keys per Env). host-Image baut + läuft verifiziert: Website (/,/hosting/,/login/,/admin/) + API + E2E-Flow (register→checkout→admin) aus dem Container. Co-Authored-By: Claude Opus 4.8 --- .env.example | 66 +++++++++++++++++++++++++++++++++++++++ .gitignore | 3 ++ Dockerfile.host | 31 ++++++++++++++++++ README.md | 69 ++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 78 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 247 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 Dockerfile.host create mode 100644 README.md create mode 100644 docker-compose.yml diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..07d3d05 --- /dev/null +++ b/.env.example @@ -0,0 +1,66 @@ +# ───────────────────────────────────────────────────────────────────────────── +# RAPPORT-STACK — eine Konfiguration für lokal UND Hetzner. +# Kopiere nach .env und ersetze die CHANGE-ME-Werte. .env niemals committen. +# +# Secrets generieren: openssl rand -hex 32 +# ANON_KEY / SERVICE_ROLE_KEY aus JWT_SECRET ableiten: +# cd ../SERVER-CONTAINER && node scripts/generate-keys.mjs $JWT_SECRET +# ───────────────────────────────────────────────────────────────────────────── + +# ═══ Rapport-Backend (Supabase-Stack) ═══ +POSTGRES_PASSWORD=CHANGE-ME-min-32-zeichen +JWT_SECRET=CHANGE-ME-min-32-zeichen +ANON_KEY=CHANGE-ME-aus-jwt-secret +SERVICE_ROLE_KEY=CHANGE-ME-aus-jwt-secret + +# URLs des Rapport-Stacks +# Lokal: SITE_URL=http://localhost:8080 · API_EXTERNAL_URL=http://localhost:8000 +# Prod: SITE_URL=https://app.rapport.studio · API_EXTERNAL_URL=https://api.rapport.studio +SITE_URL=http://localhost:8080 +API_EXTERNAL_URL=http://localhost:8000 + +# Ports des Rapport-Stacks +APP_PORT=8080 +KONG_HTTP_PORT=8000 +KONG_HTTPS_PORT=8443 +DB_PORT=5432 + +# Rapport-Frontend-Image-Tag (aus Gitea-Registry) +RAPPORT_APP_TAG=main + +# SMTP (leer = Test-Mailserver / Mails landen lokal) +SMTP_HOST= +SMTP_PORT=587 +SMTP_USER= +SMTP_PASS= +SMTP_SENDER_NAME=Rapport +SMTP_ADMIN_EMAIL=admin@rapport.local + +# ═══ RAPPORT-HOST (Hosting-Plattform) ═══ +HOST_PORT=8787 +# Lokal: http://localhost:8787 · Prod: https://host.rapport.studio +PUBLIC_BASE_URL=http://localhost:8787 + +# Eigenes JWT-Secret für HOST-Kundenkonten (NICHT das Stack-JWT_SECRET). +HOST_JWT_SECRET=CHANGE-ME-min-32-zeichen +# Passwort für den Betreiber-Bereich /admin. +ADMIN_PASSWORD=CHANGE-ME-admin-passwort + +# Eigene HOST-Datenbank (Container-intern; Default reicht meist). +HOST_DB_USER=rapport_host +HOST_DB_PASSWORD=rapport_host +HOST_DB_NAME=rapport_host + +# Provisioning in den Rapport-Stack. Default greift containerintern auf Kong zu. +RAPPORT_API_URL=http://kong:8000 +# URL-Vorlage für die fertige Kunden-Instanz ({slug} wird ersetzt). +# Lokal: http://localhost:8080/?studio={slug} +# Prod: https://app.rapport.studio/?studio={slug} +RAPPORT_INSTANCE_URL_TEMPLATE=http://localhost:8080/?studio={slug} + +# ═══ Stripe (optional; solange CHANGE-ME → MOCK-Modus, kein echtes Geld) ═══ +STRIPE_SECRET_KEY=sk_test_CHANGE-ME +STRIPE_WEBHOOK_SECRET=whsec_CHANGE-ME +STRIPE_PRICE_SOLO=price_CHANGE-ME +STRIPE_PRICE_STUDIO=price_CHANGE-ME +STRIPE_PRICE_BUSINESS=price_CHANGE-ME diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7f2bec0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.env +*.log +.DS_Store diff --git a/Dockerfile.host b/Dockerfile.host new file mode 100644 index 0000000..247de9b --- /dev/null +++ b/Dockerfile.host @@ -0,0 +1,31 @@ +# RAPPORT-HOST-Image — bündelt das Node-Backend MIT der gebauten Hugo-Website. +# Haupt-Build-Kontext: RAPPORT-HOST. Zusatz-Kontext `website`: RAPPORT-WEBSITE. + +# ── Stage 1: Hugo-Website bauen ────────────────────────────────────────────── +FROM hugomods/hugo:exts AS web +WORKDIR /src +COPY --from=website . /src +# Frisch bauen (alte public/resources ignorieren), Ausgabe nach /public. +RUN rm -rf public resources && hugo --gc --baseURL / --destination /public + +# ── Stage 2: Node-Backend ──────────────────────────────────────────────────── +FROM node:20-alpine +WORKDIR /app + +# Nur Server-Dependencies installieren (Layer-Caching). +COPY server/package.json server/package-lock.json* ./server/ +RUN cd server && npm install --omit=dev --no-audit --no-fund + +# Backend-Code + die gebaute Website. +COPY server ./server +COPY package.json ./ +COPY --from=web /public ./website-public + +# Das Backend liest WEBSITE_PUBLIC_DIR; hier auf die gebündelte Website zeigen. +ENV WEBSITE_PUBLIC_DIR=/app/website-public +ENV PORT=8787 +EXPOSE 8787 + +# Beim Start: HOST-Schema migrieren, dann Server. (host-db ist via compose +# depends_on healthy.) +CMD ["sh", "-c", "node server/migrate.js && node server/index.js"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..6b6f5cd --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +# RAPPORT-STACK + +> Alles-in-einem: der komplette RAPPORT-Hosting-Betrieb als **ein** Compose-Stack. +> Ein `docker compose up` startet Backend, Rapport-Frontend, Marketing-Website, +> Login/Konto und die Betreiber-Plattform. + +## Was drin ist + +| Schicht | Herkunft | Container | +|---|---|---| +| Rapport-Backend (Supabase) | `../SERVER-CONTAINER` (via `include`) | db, auth, rest, realtime, storage, kong | +| Rapport-Frontend | `../SERVER-CONTAINER` | app | +| HOST-Datenbank | dieses Repo | host-db | +| Marketing + Login + Konto + Admin + API | `../RAPPORT-WEBSITE` (gebaut) + `../RAPPORT-HOST` (Node) | host | + +Der `host`-Container bündelt die **gebaute Hugo-Website** und das **Node-Backend** +in einem Image (siehe `Dockerfile.host`). Er provisioniert Kunden-Instanzen über +Kong in den Supabase-Stack. + +> **Voraussetzung:** Die drei Schwester-Repos liegen neben diesem +> (`~/RAPPORT/SERVER-CONTAINER`, `~/RAPPORT/RAPPORT-WEBSITE`, `~/RAPPORT/RAPPORT-HOST`) +> — der Build referenziert sie als Build-Kontexte. + +## Start (lokal) + +```bash +cp .env.example .env +# Secrets setzen: openssl rand -hex 32 für POSTGRES_PASSWORD/JWT_SECRET/HOST_JWT_SECRET +# ANON_KEY + SERVICE_ROLE_KEY: cd ../SERVER-CONTAINER && node scripts/generate-keys.mjs +# Rapport-Migrations einmal holen: cd ../SERVER-CONTAINER && ./scripts/sync-migrations.sh + +docker compose up -d +docker compose ps # alle healthy? +``` + +Erreichbar: +- **Hosting-Plattform** (Marketing/Login/Konto/Admin): http://localhost:8787 +- **Rapport-App** (Kunden-Instanzen): http://localhost:8080 +- **Rapport-API** (Kong): http://localhost:8000 + +Admin-Bereich: http://localhost:8787/admin/ (Passwort = `ADMIN_PASSWORD`). + +## Produktion (Hetzner) + +Gleicher Stack, andere `.env`: +- Domains in `SITE_URL`, `API_EXTERNAL_URL`, `PUBLIC_BASE_URL`, + `RAPPORT_INSTANCE_URL_TEMPLATE` +- echte `STRIPE_*`-Keys +- davor ein Reverse-Proxy (Caddy/Nginx Proxy Manager) für TLS auf + `host.…` (→ 8787) und `app.…` (→ 8080) + +## Aufbau + +``` +RAPPORT-STACK/ +├── docker-compose.yml include SERVER-CONTAINER + host-db + host +├── Dockerfile.host Hugo-Website-Build + Node-Backend (multi-stage) +├── .env.example eine Config für lokal + Hetzner +└── README.md +``` + +## Hinweise + +- **host** baut bei `docker compose build` die Website frisch aus + `../RAPPORT-WEBSITE`. Nach Website-Änderungen: `docker compose build host`. +- Bei Schema-/Migrations-Änderungen im App-Repo: + `cd ../SERVER-CONTAINER && ./scripts/sync-migrations.sh && docker compose up -d` +- Lizenz: dieses Repo bündelt AGPL-Komponenten (Rapport) UND das proprietäre + RAPPORT-HOST. Das Compose-Setup selbst ist Infrastruktur-Glue. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9593b63 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,78 @@ +# ───────────────────────────────────────────────────────────────────────────── +# RAPPORT-STACK — Alles-in-einem: Supabase-Backend + Rapport-Frontend + +# RAPPORT-WEBSITE (Marketing/Login) + RAPPORT-HOST (Hosting-Plattform). +# +# Ein Befehl bringt die komplette Hosting-Plattform hoch: +# cp .env.example .env # Secrets setzen (siehe Kommentare dort) +# docker compose up -d +# +# Läuft lokal UND auf Hetzner — Unterschiede nur über .env (Domains, Keys). +# ───────────────────────────────────────────────────────────────────────────── + +# Der komplette Supabase-Stack (db, auth, rest, realtime, storage, kong, app) +# wird aus dem Schwester-Repo wiederverwendet — nicht dupliziert. +include: + - path: ../SERVER-CONTAINER/docker-compose.yml + +services: + # ════════════════════════════════════════════════════════════════════════ + # host-db — eigene Postgres für RAPPORT-HOST (Konten/Abos/Instanzen). + # Bewusst GETRENNT von der Rapport-Kunden-DB (db). + # ════════════════════════════════════════════════════════════════════════ + host-db: + image: postgres:16-alpine + container_name: rapport-host-db + restart: unless-stopped + environment: + POSTGRES_USER: ${HOST_DB_USER:-rapport_host} + POSTGRES_PASSWORD: ${HOST_DB_PASSWORD:-rapport_host} + POSTGRES_DB: ${HOST_DB_NAME:-rapport_host} + volumes: + - host-db-data:/var/lib/postgresql/data + healthcheck: + test: ["CMD", "pg_isready", "-U", "${HOST_DB_USER:-rapport_host}"] + interval: 5s + timeout: 5s + retries: 10 + + # ════════════════════════════════════════════════════════════════════════ + # host — RAPPORT-HOST Node-Backend, das ZUGLEICH die gebaute RAPPORT-WEBSITE + # ausliefert (Marketing + Login + Konto + Admin). Provisioniert Kunden- + # Instanzen in den Supabase-Stack über Kong. + # ════════════════════════════════════════════════════════════════════════ + host: + build: + # Haupt-Kontext = RAPPORT-HOST; die Website kommt als benannter Zusatz- + # Kontext rein (so liegen beide Repos im selben Build, ohne Duplikat). + context: ../RAPPORT-HOST + dockerfile: ../RAPPORT-STACK/Dockerfile.host + additional_contexts: + website: ../RAPPORT-WEBSITE + container_name: rapport-host + restart: unless-stopped + depends_on: + host-db: + condition: service_healthy + kong: + condition: service_started + environment: + PORT: 8787 + PUBLIC_BASE_URL: ${PUBLIC_BASE_URL:-http://localhost:8787} + JWT_SECRET: ${HOST_JWT_SECRET} + ADMIN_PASSWORD: ${ADMIN_PASSWORD} + DATABASE_URL: postgres://${HOST_DB_USER:-rapport_host}:${HOST_DB_PASSWORD:-rapport_host}@host-db:5432/${HOST_DB_NAME:-rapport_host} + # Provisioning in den Rapport-Stack (über Kong, service_role = der Stack-Key) + RAPPORT_API_URL: ${RAPPORT_API_URL:-http://kong:8000} + RAPPORT_SERVICE_KEY: ${SERVICE_ROLE_KEY} + RAPPORT_INSTANCE_URL_TEMPLATE: ${RAPPORT_INSTANCE_URL_TEMPLATE:-http://localhost:8080/?studio={slug}} + # Stripe (optional; CHANGE-ME → MOCK-Modus) + STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY:-} + STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET:-} + STRIPE_PRICE_SOLO: ${STRIPE_PRICE_SOLO:-} + STRIPE_PRICE_STUDIO: ${STRIPE_PRICE_STUDIO:-} + STRIPE_PRICE_BUSINESS: ${STRIPE_PRICE_BUSINESS:-} + ports: + - "${HOST_PORT:-8787}:8787" + +volumes: + host-db-data: