From 945e46fb038eae49acdc55415e89e97dbdc2603c Mon Sep 17 00:00:00 2001 From: karim Date: Sat, 23 May 2026 20:10:54 +0200 Subject: [PATCH] =?UTF-8?q?Initial:=20Docker-Compose-Stack=20f=C3=BCr=20Ra?= =?UTF-8?q?pport=20Self-Hosting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .env.example | 35 +++++++ .gitignore | 7 ++ Dockerfile.app | 33 +++++++ LICENSE | 24 +++++ README.md | 140 ++++++++++++++++++++++++++++ docker-compose.yml | 185 +++++++++++++++++++++++++++++++++++++ kong.yml | 48 ++++++++++ nginx.conf | 23 +++++ scripts/sync-migrations.sh | 30 ++++++ volumes/db/init/00-init.sh | 64 +++++++++++++ 10 files changed, 589 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 Dockerfile.app create mode 100644 LICENSE create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 kong.yml create mode 100644 nginx.conf create mode 100755 scripts/sync-migrations.sh create mode 100755 volumes/db/init/00-init.sh diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..7dfd3d4 --- /dev/null +++ b/.env.example @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f52b1c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.env +.env.local +volumes/db/data/ +volumes/db/init/migrations/ +volumes/storage/ +.DS_Store +*.log diff --git a/Dockerfile.app b/Dockerfile.app new file mode 100644 index 0000000..dce8313 --- /dev/null +++ b/Dockerfile.app @@ -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 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..31467da --- /dev/null +++ b/README.md @@ -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. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..81daf1b --- /dev/null +++ b/docker-compose.yml @@ -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: diff --git a/kong.yml b/kong.yml new file mode 100644 index 0000000..b29b114 --- /dev/null +++ b/kong.yml @@ -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 diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..7dcfa88 --- /dev/null +++ b/nginx.conf @@ -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; +} diff --git a/scripts/sync-migrations.sh b/scripts/sync-migrations.sh new file mode 100755 index 0000000..630f0d1 --- /dev/null +++ b/scripts/sync-migrations.sh @@ -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)" diff --git a/volumes/db/init/00-init.sh b/volumes/db/init/00-init.sh new file mode 100755 index 0000000..fc1e197 --- /dev/null +++ b/volumes/db/init/00-init.sh @@ -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."