Initial: Docker-Compose-Stack für Rapport Self-Hosting
Komplettes Bundle für eigene Rapport-Instanz: - Postgres mit Supabase-Extensions + Init-Script für Standard-Rollen - GoTrue (Auth) mit konfigurierbarem SMTP für Passwort-Reset-Mails - PostgREST (REST-API) - Realtime (Postgres-Changes für Live-Sync) - Storage-API (Bilder/Quittungen) - Kong als API-Gateway - Rapport-Frontend als Multi-Stage-Build (zieht Sources aus dem App-Repo) Plus: - scripts/sync-migrations.sh: holt SQL aus dem App-Repo - .env.example mit allen Pflicht-Secrets + optionalen SMTP-Werten - nginx.conf mit SPA-Routing - README mit Setup-Anleitung (Linux + macOS-Colima) - LICENSE (AGPL-3.0) Sync mit App-Repo: scripts/sync-migrations.sh holt die Migrations-SQL via git clone und legt sie nach volumes/db/init/migrations/. Bei jedem Rapport-Update erneut ausführen. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,35 @@
|
|||||||
|
# Kopiere nach .env und ersetze die markierten Werte.
|
||||||
|
# Niemals committen — .env steht in .gitignore.
|
||||||
|
|
||||||
|
# ═══ Pflicht: Secrets ═══
|
||||||
|
# Zufallswerte generieren mit: openssl rand -hex 32
|
||||||
|
POSTGRES_PASSWORD=CHANGE-ME-mindestens-32-zufällige-zeichen
|
||||||
|
JWT_SECRET=CHANGE-ME-mindestens-32-zufällige-zeichen
|
||||||
|
ANON_KEY=CHANGE-ME-aus-jwt-secret-abgeleitet
|
||||||
|
SERVICE_ROLE_KEY=CHANGE-ME-aus-jwt-secret-abgeleitet
|
||||||
|
|
||||||
|
# ═══ Pflicht: URLs ═══
|
||||||
|
# Wenn nur LAN: SITE_URL=http://192.168.1.50:8080 (oder rapport.local:8080)
|
||||||
|
# Wenn extern via Reverse-Proxy: SITE_URL=https://app.rapport.studio.ch
|
||||||
|
SITE_URL=http://localhost:8080
|
||||||
|
API_EXTERNAL_URL=http://localhost:8000
|
||||||
|
|
||||||
|
# ═══ Optional: Ports ═══
|
||||||
|
APP_PORT=8080
|
||||||
|
KONG_HTTP_PORT=8000
|
||||||
|
KONG_HTTPS_PORT=8443
|
||||||
|
DB_PORT=5432
|
||||||
|
|
||||||
|
# ═══ Optional: Rapport-Frontend-Version ═══
|
||||||
|
# Wenn Custom-Build: setze RAPPORT_APP_TAG auf die gewünschte Version
|
||||||
|
RAPPORT_APP_TAG=0.8.2
|
||||||
|
|
||||||
|
# ═══ Optional: Email für Passwort-Reset etc. ═══
|
||||||
|
# Wenn leer: Mails landen lokal in Inbucket (Test-Mailserver, Port 9000)
|
||||||
|
# Für Production: echten SMTP-Server angeben
|
||||||
|
SMTP_HOST=
|
||||||
|
SMTP_PORT=587
|
||||||
|
SMTP_USER=
|
||||||
|
SMTP_PASS=
|
||||||
|
SMTP_SENDER_NAME=Rapport
|
||||||
|
SMTP_ADMIN_EMAIL=admin@rapport.studio.ch
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
volumes/db/data/
|
||||||
|
volumes/db/init/migrations/
|
||||||
|
volumes/storage/
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Multi-Stage Build für das Rapport-Frontend.
|
||||||
|
# Holt die App-Sources via git, baut sie mit Node, packt das fertige dist/
|
||||||
|
# in einen schlanken nginx-Container.
|
||||||
|
#
|
||||||
|
# Steuerung über Build-Args:
|
||||||
|
# --build-arg RAPPORT_APP_TAG=0.8.2 Version aus dem App-Repo
|
||||||
|
# --build-arg SUPABASE_URL=https://... Public-API-URL (kommt in den Build)
|
||||||
|
# --build-arg SUPABASE_ANON_KEY=... Public Anon-Key (kommt in den Build)
|
||||||
|
|
||||||
|
FROM node:20-alpine AS builder
|
||||||
|
ARG RAPPORT_APP_TAG=main
|
||||||
|
ARG SUPABASE_URL
|
||||||
|
ARG SUPABASE_ANON_KEY
|
||||||
|
ARG REPO_URL=https://git.kgva.ch/karim/RAPPORT.git
|
||||||
|
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
WORKDIR /build
|
||||||
|
RUN git clone --branch "${RAPPORT_APP_TAG}" --depth 1 "${REPO_URL}" app
|
||||||
|
WORKDIR /build/app
|
||||||
|
|
||||||
|
# .env.production wird zur Build-Zeit ausgewertet → Werte landen im Bundle
|
||||||
|
RUN if [ -n "$SUPABASE_URL" ]; then \
|
||||||
|
printf "VITE_SUPABASE_URL=%s\nVITE_SUPABASE_ANON_KEY=%s\n" "$SUPABASE_URL" "$SUPABASE_ANON_KEY" > .env.production; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
RUN npm install --no-audit --no-fund && npm run build
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────────────────
|
||||||
|
FROM nginx:alpine
|
||||||
|
COPY --from=builder /build/app/dist /usr/share/nginx/html
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
EXPOSE 80
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s CMD wget -q --spider http://localhost/ || exit 1
|
||||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
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.
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# rapport-server
|
||||||
|
|
||||||
|
Self-Hosting-Stack für [Rapport](https://git.kgva.ch/karim/RAPPORT) — die Studio-Management-Software für Architekturbüros.
|
||||||
|
|
||||||
|
Dieses Repo enthält alles, um Rapport auf eigenem Server (Linux-VM, NAS, Mac Mini) zu hosten:
|
||||||
|
|
||||||
|
- **Postgres** (Datenbank)
|
||||||
|
- **GoTrue** (Auth — Email-Login, Passwort-Reset, …)
|
||||||
|
- **PostgREST** (REST-API auf der DB)
|
||||||
|
- **Realtime** (Live-Sync zwischen Geräten)
|
||||||
|
- **Storage** (Bilder, Quittungen)
|
||||||
|
- **Kong** (API-Gateway)
|
||||||
|
- **Rapport-Frontend** (nginx mit dem React-Build)
|
||||||
|
|
||||||
|
Alles als Docker-Compose. Komplett Open-Source.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Voraussetzungen
|
||||||
|
|
||||||
|
| OS | Container-Runtime |
|
||||||
|
|---|---|
|
||||||
|
| **Linux** (Ubuntu 22.04+, Debian 12+, …) | Docker Engine + Compose v2 |
|
||||||
|
| **macOS** (Mac Mini etc.) | [Colima](https://github.com/abiosoft/colima) + Docker CLI — **vollständig Open-Source** |
|
||||||
|
|
||||||
|
> Auf macOS funktioniert auch OrbStack oder Docker Desktop, beide sind aber proprietär. Colima ist die OSS-Alternative und für Selfhost ausreichend.
|
||||||
|
|
||||||
|
Plus: ein erreichbarer DNS-Name (für TLS) — z.B. `rapport.studio.ch`. Optional: Nginx Proxy Manager oder Caddy als Reverse-Proxy für SSL.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### 1. Repo klonen + Frontend-Sources holen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.kgva.ch/karim/rapport-server.git
|
||||||
|
cd rapport-server
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. `.env` erstellen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
In `.env` müssen mindestens diese drei Werte ersetzt werden:
|
||||||
|
- `POSTGRES_PASSWORD` — Datenbank-Passwort (mind. 32 Zeichen zufällig)
|
||||||
|
- `JWT_SECRET` — JWT-Signatur-Secret (mind. 32 Zeichen zufällig)
|
||||||
|
- `SITE_URL` — die öffentliche URL deiner Rapport-Instanz (z.B. `https://app.rapport.studio.ch`)
|
||||||
|
|
||||||
|
Zufallswerte generieren:
|
||||||
|
```bash
|
||||||
|
openssl rand -hex 32 # für POSTGRES_PASSWORD und JWT_SECRET
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Migrations holen
|
||||||
|
|
||||||
|
Die SQL-Migrations stammen aus dem App-Repo. Einmal initial holen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/sync-migrations.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
(Bei späteren Rapport-Updates `sync-migrations.sh` erneut ausführen, dann `docker compose down && docker compose up -d`.)
|
||||||
|
|
||||||
|
### 4. Stack starten
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Erststart dauert ~1 Minute (Postgres initialisiert sich, Migrations laufen, Container starten).
|
||||||
|
|
||||||
|
### 5. Health-Check
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
Alle Container sollten `healthy` zeigen.
|
||||||
|
|
||||||
|
Frontend ist erreichbar auf `http://localhost:8080` — direkt im Browser öffnen oder über deinen Reverse-Proxy auf eine Domain mappen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Reverse-Proxy + HTTPS
|
||||||
|
|
||||||
|
Empfohlen: **Nginx Proxy Manager** (OSS, Web-UI, Let's-Encrypt automatisch) oder **Caddy** (config-as-code, vollautomatisch).
|
||||||
|
|
||||||
|
Beispiel Caddy:
|
||||||
|
|
||||||
|
```caddy
|
||||||
|
app.rapport.studio.ch {
|
||||||
|
reverse_proxy localhost:8080
|
||||||
|
}
|
||||||
|
|
||||||
|
api.rapport.studio.ch {
|
||||||
|
reverse_proxy localhost:8000 # Kong-Gateway
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In `.env` dann `SITE_URL=https://app.rapport.studio.ch` und `API_EXTERNAL_URL=https://api.rapport.studio.ch` setzen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Updates
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git pull
|
||||||
|
./scripts/sync-migrations.sh # falls neue Migrations
|
||||||
|
docker compose pull # neueste Container-Versionen
|
||||||
|
docker compose up -d # neu starten
|
||||||
|
```
|
||||||
|
|
||||||
|
Daten bleiben erhalten (Volume `postgres-data` wird nicht angetastet).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Backup
|
||||||
|
|
||||||
|
Komplettes Postgres-Dump:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec -T db pg_dumpall -U postgres > backup-$(date +%Y%m%d).sql
|
||||||
|
```
|
||||||
|
|
||||||
|
Empfohlen: per `cron` täglich + auf externe Disk/S3/Backblaze sichern.
|
||||||
|
|
||||||
|
Storage (Quittungen, Logos):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec storage tar -czf - /var/lib/storage > storage-$(date +%Y%m%d).tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Lizenz
|
||||||
|
|
||||||
|
GNU AGPL-3.0-or-later — identisch zur Rapport-App.
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
# Rapport-Server — Komplettstack für Self-Hosting.
|
||||||
|
#
|
||||||
|
# Bevor `docker compose up`:
|
||||||
|
# 1. cp .env.example .env und Secrets ersetzen
|
||||||
|
# 2. ./scripts/sync-migrations.sh (holt SQL aus dem App-Repo)
|
||||||
|
#
|
||||||
|
# Dann:
|
||||||
|
# docker compose up -d
|
||||||
|
#
|
||||||
|
# Erststart dauert ~1 Min (Postgres bootet, Migrations laufen, Health-Checks).
|
||||||
|
|
||||||
|
services:
|
||||||
|
# ════════════════════════════════════════════════════════════════════════
|
||||||
|
# Postgres — Datenbank mit Supabase-Extensions
|
||||||
|
# ════════════════════════════════════════════════════════════════════════
|
||||||
|
db:
|
||||||
|
image: supabase/postgres:15.8.1.020
|
||||||
|
container_name: rapport-db
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
POSTGRES_DB: postgres
|
||||||
|
JWT_SECRET: ${JWT_SECRET}
|
||||||
|
JWT_EXP: 3600
|
||||||
|
volumes:
|
||||||
|
- postgres-data:/var/lib/postgresql/data
|
||||||
|
- ./volumes/db/init/migrations:/docker-entrypoint-initdb.d/migrations:ro
|
||||||
|
- ./volumes/db/init/00-init.sh:/docker-entrypoint-initdb.d/00-init.sh:ro
|
||||||
|
ports:
|
||||||
|
- "${DB_PORT:-5432}:5432"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "pg_isready", "-U", "postgres"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
|
||||||
|
# ════════════════════════════════════════════════════════════════════════
|
||||||
|
# GoTrue — Auth (Email-Login, Passwort-Reset, Magic-Links)
|
||||||
|
# ════════════════════════════════════════════════════════════════════════
|
||||||
|
auth:
|
||||||
|
image: supabase/gotrue:v2.158.1
|
||||||
|
container_name: rapport-auth
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
GOTRUE_API_HOST: 0.0.0.0
|
||||||
|
GOTRUE_API_PORT: 9999
|
||||||
|
API_EXTERNAL_URL: ${API_EXTERNAL_URL}
|
||||||
|
GOTRUE_DB_DRIVER: postgres
|
||||||
|
GOTRUE_DB_DATABASE_URL: postgres://supabase_auth_admin:${POSTGRES_PASSWORD}@db:5432/postgres
|
||||||
|
GOTRUE_SITE_URL: ${SITE_URL}
|
||||||
|
GOTRUE_URI_ALLOW_LIST: ${SITE_URL},${SITE_URL}/
|
||||||
|
GOTRUE_DISABLE_SIGNUP: "false"
|
||||||
|
GOTRUE_JWT_ADMIN_ROLES: service_role
|
||||||
|
GOTRUE_JWT_AUD: authenticated
|
||||||
|
GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
|
||||||
|
GOTRUE_JWT_EXP: 3600
|
||||||
|
GOTRUE_JWT_SECRET: ${JWT_SECRET}
|
||||||
|
GOTRUE_EXTERNAL_EMAIL_ENABLED: "true"
|
||||||
|
GOTRUE_MAILER_AUTOCONFIRM: "true"
|
||||||
|
GOTRUE_SMTP_HOST: ${SMTP_HOST:-}
|
||||||
|
GOTRUE_SMTP_PORT: ${SMTP_PORT:-587}
|
||||||
|
GOTRUE_SMTP_USER: ${SMTP_USER:-}
|
||||||
|
GOTRUE_SMTP_PASS: ${SMTP_PASS:-}
|
||||||
|
GOTRUE_SMTP_ADMIN_EMAIL: ${SMTP_ADMIN_EMAIL:-admin@rapport.local}
|
||||||
|
GOTRUE_SMTP_SENDER_NAME: ${SMTP_SENDER_NAME:-Rapport}
|
||||||
|
|
||||||
|
# ════════════════════════════════════════════════════════════════════════
|
||||||
|
# PostgREST — REST-API auf der Datenbank
|
||||||
|
# ════════════════════════════════════════════════════════════════════════
|
||||||
|
rest:
|
||||||
|
image: postgrest/postgrest:v12.2.0
|
||||||
|
container_name: rapport-rest
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
PGRST_DB_URI: postgres://authenticator:${POSTGRES_PASSWORD}@db:5432/postgres
|
||||||
|
PGRST_DB_SCHEMAS: public,storage
|
||||||
|
PGRST_DB_ANON_ROLE: anon
|
||||||
|
PGRST_JWT_SECRET: ${JWT_SECRET}
|
||||||
|
PGRST_DB_USE_LEGACY_GUCS: "false"
|
||||||
|
PGRST_APP_SETTINGS_JWT_SECRET: ${JWT_SECRET}
|
||||||
|
PGRST_APP_SETTINGS_JWT_EXP: 3600
|
||||||
|
|
||||||
|
# ════════════════════════════════════════════════════════════════════════
|
||||||
|
# Realtime — Postgres-Changes-Broadcast für Live-Sync
|
||||||
|
# ════════════════════════════════════════════════════════════════════════
|
||||||
|
realtime:
|
||||||
|
image: supabase/realtime:v2.30.34
|
||||||
|
container_name: rapport-realtime
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
PORT: 4000
|
||||||
|
DB_HOST: db
|
||||||
|
DB_PORT: 5432
|
||||||
|
DB_USER: supabase_admin
|
||||||
|
DB_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
DB_NAME: postgres
|
||||||
|
DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime'
|
||||||
|
DB_ENC_KEY: supabaserealtime
|
||||||
|
API_JWT_SECRET: ${JWT_SECRET}
|
||||||
|
SECRET_KEY_BASE: ${JWT_SECRET}
|
||||||
|
ERL_AFLAGS: -proto_dist inet_tcp
|
||||||
|
ENABLE_TAILSCALE: "false"
|
||||||
|
DNS_NODES: "''"
|
||||||
|
command: >
|
||||||
|
sh -c "/app/bin/migrate && /app/bin/realtime eval 'Realtime.Release.seeds(Realtime.Repo)' && /app/bin/server"
|
||||||
|
|
||||||
|
# ════════════════════════════════════════════════════════════════════════
|
||||||
|
# Storage — Object-Storage für Bilder (Quittungen, Logos)
|
||||||
|
# ════════════════════════════════════════════════════════════════════════
|
||||||
|
storage:
|
||||||
|
image: supabase/storage-api:v1.11.13
|
||||||
|
container_name: rapport-storage
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
rest:
|
||||||
|
condition: service_started
|
||||||
|
environment:
|
||||||
|
ANON_KEY: ${ANON_KEY}
|
||||||
|
SERVICE_KEY: ${SERVICE_ROLE_KEY}
|
||||||
|
POSTGREST_URL: http://rest:3000
|
||||||
|
PGRST_JWT_SECRET: ${JWT_SECRET}
|
||||||
|
DATABASE_URL: postgres://supabase_storage_admin:${POSTGRES_PASSWORD}@db:5432/postgres
|
||||||
|
FILE_SIZE_LIMIT: 52428800
|
||||||
|
STORAGE_BACKEND: file
|
||||||
|
FILE_STORAGE_BACKEND_PATH: /var/lib/storage
|
||||||
|
TENANT_ID: stub
|
||||||
|
REGION: stub
|
||||||
|
GLOBAL_S3_BUCKET: stub
|
||||||
|
volumes:
|
||||||
|
- storage-data:/var/lib/storage
|
||||||
|
|
||||||
|
# ════════════════════════════════════════════════════════════════════════
|
||||||
|
# Kong — API-Gateway: bündelt alle Services unter einer URL
|
||||||
|
# ════════════════════════════════════════════════════════════════════════
|
||||||
|
kong:
|
||||||
|
image: kong:2.8.1
|
||||||
|
container_name: rapport-kong
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- auth
|
||||||
|
- rest
|
||||||
|
- storage
|
||||||
|
- realtime
|
||||||
|
environment:
|
||||||
|
KONG_DATABASE: "off"
|
||||||
|
KONG_DECLARATIVE_CONFIG: /var/lib/kong/kong.yml
|
||||||
|
KONG_DNS_ORDER: LAST,A,CNAME
|
||||||
|
KONG_PLUGINS: request-transformer,cors,key-auth,acl,basic-auth
|
||||||
|
volumes:
|
||||||
|
- ./kong.yml:/var/lib/kong/kong.yml:ro
|
||||||
|
ports:
|
||||||
|
- "${KONG_HTTP_PORT:-8000}:8000"
|
||||||
|
- "${KONG_HTTPS_PORT:-8443}:8443"
|
||||||
|
|
||||||
|
# ════════════════════════════════════════════════════════════════════════
|
||||||
|
# Rapport Frontend — die React-App hinter nginx
|
||||||
|
# ════════════════════════════════════════════════════════════════════════
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.app
|
||||||
|
args:
|
||||||
|
RAPPORT_APP_TAG: ${RAPPORT_APP_TAG:-main}
|
||||||
|
SUPABASE_URL: ${API_EXTERNAL_URL}
|
||||||
|
SUPABASE_ANON_KEY: ${ANON_KEY}
|
||||||
|
image: rapport-app:${RAPPORT_APP_TAG:-latest}
|
||||||
|
container_name: rapport-app
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${APP_PORT:-8080}:80"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres-data:
|
||||||
|
storage-data:
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# Kong-Konfiguration für den Rapport-Server-Stack.
|
||||||
|
# Routet alle Supabase-API-Pfade (/auth/v1, /rest/v1, /storage/v1, /realtime/v1)
|
||||||
|
# durch dieselbe URL, damit das Frontend nur eine Adresse kennt.
|
||||||
|
|
||||||
|
_format_version: "2.1"
|
||||||
|
_transform: true
|
||||||
|
|
||||||
|
services:
|
||||||
|
- name: auth-v1
|
||||||
|
url: http://auth:9999/
|
||||||
|
routes:
|
||||||
|
- name: auth-v1-route
|
||||||
|
strip_path: true
|
||||||
|
paths:
|
||||||
|
- /auth/v1/
|
||||||
|
plugins:
|
||||||
|
- name: cors
|
||||||
|
|
||||||
|
- name: rest-v1
|
||||||
|
url: http://rest:3000/
|
||||||
|
routes:
|
||||||
|
- name: rest-v1-route
|
||||||
|
strip_path: true
|
||||||
|
paths:
|
||||||
|
- /rest/v1/
|
||||||
|
plugins:
|
||||||
|
- name: cors
|
||||||
|
|
||||||
|
- name: storage-v1
|
||||||
|
url: http://storage:5000/
|
||||||
|
routes:
|
||||||
|
- name: storage-v1-route
|
||||||
|
strip_path: true
|
||||||
|
paths:
|
||||||
|
- /storage/v1/
|
||||||
|
plugins:
|
||||||
|
- name: cors
|
||||||
|
|
||||||
|
- name: realtime-v1
|
||||||
|
url: http://realtime:4000/socket/
|
||||||
|
protocol: http
|
||||||
|
routes:
|
||||||
|
- name: realtime-v1-route
|
||||||
|
strip_path: true
|
||||||
|
paths:
|
||||||
|
- /realtime/v1/
|
||||||
|
plugins:
|
||||||
|
- name: cors
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# Hash-versionierte Assets — langer Cache OK
|
||||||
|
location /assets/ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# SPA-Fallback: unbekannte Pfade → index.html (React-Routing-kompatibel)
|
||||||
|
location / {
|
||||||
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||||
|
try_files $uri /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
}
|
||||||
Executable
+30
@@ -0,0 +1,30 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Holt die aktuellen Postgres-Migrations aus dem RAPPORT-App-Repo.
|
||||||
|
#
|
||||||
|
# Aufruf einmal bei Setup, danach nach jedem Rapport-Update.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
REPO_URL="${RAPPORT_REPO_URL:-https://git.kgva.ch/karim/RAPPORT}"
|
||||||
|
TAG="${RAPPORT_APP_TAG:-main}"
|
||||||
|
TMPDIR=$(mktemp -d)
|
||||||
|
trap "rm -rf $TMPDIR" EXIT
|
||||||
|
|
||||||
|
echo "→ Hole Migrations aus $REPO_URL @ $TAG"
|
||||||
|
git clone --branch "$TAG" --depth 1 --quiet "$REPO_URL" "$TMPDIR/app"
|
||||||
|
|
||||||
|
if [ ! -d "$TMPDIR/app/supabase/migrations" ]; then
|
||||||
|
echo "✗ Migrations-Verzeichnis nicht gefunden im App-Repo." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf volumes/db/init/migrations
|
||||||
|
mkdir -p volumes/db/init/migrations
|
||||||
|
cp "$TMPDIR/app/supabase/migrations/"*.sql volumes/db/init/migrations/
|
||||||
|
|
||||||
|
COUNT=$(ls volumes/db/init/migrations/*.sql | wc -l | tr -d ' ')
|
||||||
|
echo "✓ $COUNT Migrations nach volumes/db/init/migrations/ kopiert"
|
||||||
|
echo
|
||||||
|
echo "Nächster Schritt: docker compose up -d (oder neustart wenn schon läuft)"
|
||||||
Executable
+64
@@ -0,0 +1,64 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Postgres-Init-Script — läuft beim ersten Start des db-Containers.
|
||||||
|
#
|
||||||
|
# 1. Legt die Supabase-Standard-Rollen an (anon, authenticated, service_role,
|
||||||
|
# supabase_auth_admin, supabase_storage_admin, authenticator).
|
||||||
|
# Diese referenzieren die in den Rapport-Migrations definierten Policies.
|
||||||
|
# 2. Wendet alle Rapport-Migrations aus ./migrations/ in alphabetischer
|
||||||
|
# Reihenfolge an.
|
||||||
|
#
|
||||||
|
# Nach diesem Script ist die DB einsatzbereit.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "→ Supabase-Standard-Rollen anlegen…"
|
||||||
|
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname postgres <<-EOSQL
|
||||||
|
-- Standard-Rollen (idempotent)
|
||||||
|
do \$\$ begin
|
||||||
|
if not exists (select 1 from pg_roles where rolname = 'anon') then
|
||||||
|
create role anon nologin noinherit;
|
||||||
|
end if;
|
||||||
|
if not exists (select 1 from pg_roles where rolname = 'authenticated') then
|
||||||
|
create role authenticated nologin noinherit;
|
||||||
|
end if;
|
||||||
|
if not exists (select 1 from pg_roles where rolname = 'service_role') then
|
||||||
|
create role service_role nologin noinherit bypassrls;
|
||||||
|
end if;
|
||||||
|
if not exists (select 1 from pg_roles where rolname = 'authenticator') then
|
||||||
|
execute format('create role authenticator noinherit login password %L', current_setting('rapport.postgres_password', true));
|
||||||
|
end if;
|
||||||
|
if not exists (select 1 from pg_roles where rolname = 'supabase_auth_admin') then
|
||||||
|
execute format('create role supabase_auth_admin login password %L', current_setting('rapport.postgres_password', true));
|
||||||
|
end if;
|
||||||
|
if not exists (select 1 from pg_roles where rolname = 'supabase_storage_admin') then
|
||||||
|
execute format('create role supabase_storage_admin login password %L', current_setting('rapport.postgres_password', true));
|
||||||
|
end if;
|
||||||
|
if not exists (select 1 from pg_roles where rolname = 'supabase_admin') then
|
||||||
|
execute format('create role supabase_admin superuser login password %L', current_setting('rapport.postgres_password', true));
|
||||||
|
end if;
|
||||||
|
end \$\$;
|
||||||
|
|
||||||
|
grant anon to authenticator;
|
||||||
|
grant authenticated to authenticator;
|
||||||
|
grant service_role to authenticator;
|
||||||
|
|
||||||
|
-- auth-Schema (für GoTrue)
|
||||||
|
create schema if not exists auth authorization supabase_auth_admin;
|
||||||
|
|
||||||
|
-- storage-Schema (für Storage-Service)
|
||||||
|
create schema if not exists storage authorization supabase_storage_admin;
|
||||||
|
|
||||||
|
-- pgcrypto + andere Extensions
|
||||||
|
create extension if not exists pgcrypto;
|
||||||
|
create extension if not exists "uuid-ossp";
|
||||||
|
EOSQL
|
||||||
|
|
||||||
|
echo "→ Rapport-Migrations applizieren…"
|
||||||
|
for f in /docker-entrypoint-initdb.d/migrations/*.sql; do
|
||||||
|
if [ -f "$f" ]; then
|
||||||
|
echo " → $(basename "$f")"
|
||||||
|
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname postgres -f "$f"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "✓ DB-Initialisierung abgeschlossen."
|
||||||
Reference in New Issue
Block a user