feat: central services mit traefik deployen
This commit is contained in:
@@ -1,10 +1,14 @@
|
|||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
|
CENTRAL_API_DOMAIN=services.fedeo.de
|
||||||
|
CENTRAL_ADMIN_DOMAIN=services-admin.fedeo.de
|
||||||
|
CONTACT_EMAIL=admin@example.com
|
||||||
|
POSTGRES_PASSWORD=change-me-postgres-password
|
||||||
DATABASE_URL=postgres://fedeo_push:fedeo_push@localhost:5442/fedeo_push
|
DATABASE_URL=postgres://fedeo_push:fedeo_push@localhost:5442/fedeo_push
|
||||||
API_HOST=0.0.0.0
|
API_HOST=0.0.0.0
|
||||||
API_PORT=4020
|
API_PORT=4020
|
||||||
ADMIN_TOKEN=change-me-admin-token
|
ADMIN_TOKEN=change-me-admin-token
|
||||||
PUSH_SECRET_ENCRYPTION_KEY=change-me-32-byte-minimum-secret
|
PUSH_SECRET_ENCRYPTION_KEY=change-me-32-byte-minimum-secret
|
||||||
PUBLIC_BASE_URL=http://localhost:4020
|
PUBLIC_BASE_URL=https://services.fedeo.de
|
||||||
WEB_PUSH_PUBLIC_KEY=
|
WEB_PUSH_PUBLIC_KEY=
|
||||||
WEB_PUSH_PRIVATE_KEY=
|
WEB_PUSH_PRIVATE_KEY=
|
||||||
IOS_BUNDLE_ID=software.federspiel.fedeo
|
IOS_BUNDLE_ID=software.federspiel.fedeo
|
||||||
@@ -22,5 +26,5 @@ OPENAI_BASE_URL=https://api.openai.com/v1
|
|||||||
GOCARDLESS_BASE_URL=https://bankaccountdata.gocardless.com/api/v2
|
GOCARDLESS_BASE_URL=https://bankaccountdata.gocardless.com/api/v2
|
||||||
GOCARDLESS_SECRET_ID=
|
GOCARDLESS_SECRET_ID=
|
||||||
GOCARDLESS_SECRET_KEY=
|
GOCARDLESS_SECRET_KEY=
|
||||||
NUXT_PUBLIC_API_BASE=http://localhost:4020
|
NUXT_PUBLIC_API_BASE=https://services.fedeo.de
|
||||||
NUXT_ADMIN_TOKEN=change-me-admin-token
|
NUXT_ADMIN_TOKEN=change-me-admin-token
|
||||||
|
|||||||
1
push-server/.gitignore
vendored
1
push-server/.gitignore
vendored
@@ -5,3 +5,4 @@ dist
|
|||||||
.env
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
*.log
|
*.log
|
||||||
|
traefik/letsencrypt/acme.json
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Eigenständiger Stack für zentrale FEDEO-Dienste. Neben Push können Selfhost-I
|
|||||||
- `apps/api`: Fastify API für Admin-Dashboard und Selfhost-Instanzen
|
- `apps/api`: Fastify API für Admin-Dashboard und Selfhost-Instanzen
|
||||||
- `apps/admin`: Nuxt Admin Dashboard mit Nuxt UI
|
- `apps/admin`: Nuxt Admin Dashboard mit Nuxt UI
|
||||||
- `packages/db`: Drizzle Schema und Migrationen für PostgreSQL
|
- `packages/db`: Drizzle Schema und Migrationen für PostgreSQL
|
||||||
- `docker-compose.yml`: lokaler Stack aus Postgres, API und Admin
|
- `docker-compose.yml`: produktiver Stack aus Traefik, Postgres, API und Admin
|
||||||
|
|
||||||
Provider-Schlüssel wie `OPENAI_API_KEY` und die GoCardless-Zugangsdaten liegen ausschließlich in diesem zentralen Stack. Selfhost-Instanzen erhalten nur Instanz-ID und Instanzschlüssel.
|
Provider-Schlüssel wie `OPENAI_API_KEY` und die GoCardless-Zugangsdaten liegen ausschließlich in diesem zentralen Stack. Selfhost-Instanzen erhalten nur Instanz-ID und Instanzschlüssel.
|
||||||
|
|
||||||
@@ -22,22 +22,48 @@ npm run dev:api
|
|||||||
npm run dev:admin
|
npm run dev:admin
|
||||||
```
|
```
|
||||||
|
|
||||||
Für den dauerhaften Betrieb:
|
Lokale Entwicklungsports:
|
||||||
|
|
||||||
```bash
|
|
||||||
cp .env.example .env
|
|
||||||
# .env mit sicheren Zugangsdaten und Provider-Schlüsseln befüllen
|
|
||||||
docker compose up -d --build
|
|
||||||
```
|
|
||||||
|
|
||||||
Der API-Container wendet Datenbankmigrationen vor jedem Start automatisch an. PostgreSQL-Daten liegen im Volume `push_postgres_data`. Für ein öffentliches Deployment müssen API und Admin hinter einem TLS-Reverse-Proxy betrieben und `PUBLIC_API_BASE_URL` auf die öffentliche API-Adresse gesetzt werden.
|
|
||||||
|
|
||||||
Standardports:
|
|
||||||
|
|
||||||
- API: `http://localhost:4020`
|
- API: `http://localhost:4020`
|
||||||
- Admin: `http://localhost:3000` lokal oder `http://localhost:3020` über Docker Compose
|
- Admin: `http://localhost:3000`
|
||||||
- Postgres: `localhost:5442`
|
- Postgres: `localhost:5442`
|
||||||
|
|
||||||
|
## Öffentlicher Betrieb mit Traefik
|
||||||
|
|
||||||
|
Voraussetzungen:
|
||||||
|
|
||||||
|
- Docker Engine mit Docker-Compose-Plugin
|
||||||
|
- freie Ports `80` und `443`
|
||||||
|
- A- beziehungsweise AAAA-Records für API- und Admin-Domain auf den Server
|
||||||
|
|
||||||
|
Der geführte Erststart erzeugt sichere lokale Schlüssel, bereitet den Let’s-Encrypt-Speicher vor und kann den Stack direkt starten:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd push-server
|
||||||
|
chmod +x scripts/setup.sh
|
||||||
|
./scripts/setup.sh --start
|
||||||
|
```
|
||||||
|
|
||||||
|
Standardmäßig werden `services.fedeo.de` für die API und `services-admin.fedeo.de` für das Admin-Dashboard vorgeschlagen. Anschließend müssen die zentralen Provider-Zugangsdaten in `.env` gepflegt werden:
|
||||||
|
|
||||||
|
```env
|
||||||
|
OPENAI_API_KEY=...
|
||||||
|
GOCARDLESS_SECRET_ID=...
|
||||||
|
GOCARDLESS_SECRET_KEY=...
|
||||||
|
```
|
||||||
|
|
||||||
|
Ein Neustart nach Änderungen erfolgt mit:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
docker compose ps
|
||||||
|
docker compose logs -f api
|
||||||
|
```
|
||||||
|
|
||||||
|
Traefik leitet HTTP automatisch auf HTTPS um und bezieht Zertifikate per Let’s Encrypt TLS-Challenge. PostgreSQL besitzt keinen Host-Port und ist nur im internen Docker-Netz erreichbar. API und Admin werden ebenfalls nicht direkt über Host-Ports veröffentlicht.
|
||||||
|
|
||||||
|
Der API-Container wendet Datenbankmigrationen vor jedem Start automatisch an. PostgreSQL-Daten liegen im Volume `push_postgres_data`; Let’s-Encrypt-Daten liegen unter `traefik/letsencrypt`.
|
||||||
|
|
||||||
## Admin-Zugang
|
## Admin-Zugang
|
||||||
|
|
||||||
Das Dashboard nutzt den Wert aus `ADMIN_TOKEN`. Der Token wird im Browser nur lokal gespeichert und als `Authorization: Bearer ...` an die API gesendet.
|
Das Dashboard nutzt den Wert aus `ADMIN_TOKEN`. Der Token wird im Browser nur lokal gespeichert und als `Authorization: Bearer ...` an die API gesendet.
|
||||||
|
|||||||
@@ -1,12 +1,39 @@
|
|||||||
services:
|
services:
|
||||||
|
traefik:
|
||||||
|
image: traefik:v3.3
|
||||||
|
restart: unless-stopped
|
||||||
|
command:
|
||||||
|
- --providers.docker=true
|
||||||
|
- --providers.docker.exposedbydefault=false
|
||||||
|
- --entrypoints.web.address=:80
|
||||||
|
- --entrypoints.web.http.redirections.entrypoint.to=websecure
|
||||||
|
- --entrypoints.web.http.redirections.entrypoint.scheme=https
|
||||||
|
- --entrypoints.websecure.address=:443
|
||||||
|
- --certificatesresolvers.letsencrypt.acme.tlschallenge=true
|
||||||
|
- --certificatesresolvers.letsencrypt.acme.email=${CONTACT_EMAIL}
|
||||||
|
- --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
|
||||||
|
- --ping=true
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
- ./traefik/letsencrypt:/letsencrypt
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "traefik", "healthcheck", "--ping"]
|
||||||
|
interval: 15s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- web
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:17
|
image: postgres:17
|
||||||
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: fedeo_push
|
POSTGRES_DB: fedeo_push
|
||||||
POSTGRES_USER: fedeo_push
|
POSTGRES_USER: fedeo_push
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-fedeo_push}
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-fedeo_push}
|
||||||
ports:
|
|
||||||
- "5442:5432"
|
|
||||||
volumes:
|
volumes:
|
||||||
- push_postgres_data:/var/lib/postgresql/data
|
- push_postgres_data:/var/lib/postgresql/data
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@@ -14,6 +41,8 @@ services:
|
|||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
|
||||||
api:
|
api:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -23,8 +52,6 @@ services:
|
|||||||
env_file: ${CENTRAL_ENV_FILE:-.env}
|
env_file: ${CENTRAL_ENV_FILE:-.env}
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: postgres://fedeo_push:${POSTGRES_PASSWORD:-fedeo_push}@postgres:5432/fedeo_push
|
DATABASE_URL: postgres://fedeo_push:${POSTGRES_PASSWORD:-fedeo_push}@postgres:5432/fedeo_push
|
||||||
ports:
|
|
||||||
- "4020:4020"
|
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
@@ -33,6 +60,16 @@ services:
|
|||||||
interval: 15s
|
interval: 15s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.fedeo-central-api.rule=Host(`${CENTRAL_API_DOMAIN}`)
|
||||||
|
- traefik.http.routers.fedeo-central-api.entrypoints=websecure
|
||||||
|
- traefik.http.routers.fedeo-central-api.tls.certresolver=letsencrypt
|
||||||
|
- traefik.http.services.fedeo-central-api.loadbalancer.server.port=4020
|
||||||
|
- traefik.docker.network=fedeo_central_web
|
||||||
|
networks:
|
||||||
|
- web
|
||||||
|
- internal
|
||||||
|
|
||||||
admin:
|
admin:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -41,12 +78,26 @@ services:
|
|||||||
dockerfile: apps/admin/Dockerfile
|
dockerfile: apps/admin/Dockerfile
|
||||||
env_file: ${CENTRAL_ENV_FILE:-.env}
|
env_file: ${CENTRAL_ENV_FILE:-.env}
|
||||||
environment:
|
environment:
|
||||||
NUXT_PUBLIC_API_BASE: ${PUBLIC_API_BASE_URL:-http://localhost:4020}
|
NUXT_PUBLIC_API_BASE: https://${CENTRAL_API_DOMAIN}
|
||||||
ports:
|
|
||||||
- "3020:3000"
|
|
||||||
depends_on:
|
depends_on:
|
||||||
api:
|
api:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.fedeo-central-admin.rule=Host(`${CENTRAL_ADMIN_DOMAIN}`)
|
||||||
|
- traefik.http.routers.fedeo-central-admin.entrypoints=websecure
|
||||||
|
- traefik.http.routers.fedeo-central-admin.tls.certresolver=letsencrypt
|
||||||
|
- traefik.http.services.fedeo-central-admin.loadbalancer.server.port=3000
|
||||||
|
- traefik.docker.network=fedeo_central_web
|
||||||
|
networks:
|
||||||
|
- web
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
push_postgres_data:
|
push_postgres_data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
web:
|
||||||
|
name: fedeo_central_web
|
||||||
|
internal:
|
||||||
|
name: fedeo_central_internal
|
||||||
|
internal: true
|
||||||
|
|||||||
77
push-server/scripts/setup.sh
Executable file
77
push-server/scripts/setup.sh
Executable file
@@ -0,0 +1,77 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
ENV_FILE="$ROOT_DIR/.env"
|
||||||
|
ENV_EXAMPLE="$ROOT_DIR/.env.example"
|
||||||
|
|
||||||
|
prompt() {
|
||||||
|
local label="$1"
|
||||||
|
local default_value="$2"
|
||||||
|
local value
|
||||||
|
read -r -p "$label [$default_value]: " value
|
||||||
|
printf '%s' "${value:-$default_value}"
|
||||||
|
}
|
||||||
|
|
||||||
|
replace_env() {
|
||||||
|
local key="$1"
|
||||||
|
local value="$2"
|
||||||
|
local escaped
|
||||||
|
escaped="${value//\\/\\\\}"
|
||||||
|
escaped="${escaped//&/\\&}"
|
||||||
|
escaped="${escaped//|/\\|}"
|
||||||
|
if grep -q "^${key}=" "$ENV_FILE"; then
|
||||||
|
sed -i "s|^${key}=.*$|${key}=${escaped}|" "$ENV_FILE"
|
||||||
|
else
|
||||||
|
printf '%s=%s\n' "$key" "$value" >> "$ENV_FILE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
random_secret() {
|
||||||
|
openssl rand -hex "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
command -v docker >/dev/null 2>&1 || { echo "Docker ist nicht installiert." >&2; exit 1; }
|
||||||
|
docker compose version >/dev/null 2>&1 || { echo "Docker Compose Plugin ist nicht installiert." >&2; exit 1; }
|
||||||
|
command -v openssl >/dev/null 2>&1 || { echo "OpenSSL ist nicht installiert." >&2; exit 1; }
|
||||||
|
|
||||||
|
if [[ ! -f "$ENV_FILE" ]]; then
|
||||||
|
cp "$ENV_EXAMPLE" "$ENV_FILE"
|
||||||
|
echo ".env wurde aus .env.example angelegt."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "FEDEO Central Services Setup"
|
||||||
|
echo
|
||||||
|
api_domain="$(prompt "API-Domain" "services.fedeo.de")"
|
||||||
|
admin_domain="$(prompt "Admin-Domain" "services-admin.fedeo.de")"
|
||||||
|
contact_email="$(prompt "E-Mail für Let's Encrypt" "admin@example.com")"
|
||||||
|
|
||||||
|
replace_env "CENTRAL_API_DOMAIN" "$api_domain"
|
||||||
|
replace_env "CENTRAL_ADMIN_DOMAIN" "$admin_domain"
|
||||||
|
replace_env "CONTACT_EMAIL" "$contact_email"
|
||||||
|
replace_env "PUBLIC_BASE_URL" "https://$api_domain"
|
||||||
|
replace_env "NUXT_PUBLIC_API_BASE" "https://$api_domain"
|
||||||
|
|
||||||
|
grep -q '^POSTGRES_PASSWORD=change-me-' "$ENV_FILE" && replace_env "POSTGRES_PASSWORD" "$(random_secret 24)"
|
||||||
|
grep -q '^ADMIN_TOKEN=change-me-' "$ENV_FILE" && replace_env "ADMIN_TOKEN" "$(random_secret 32)"
|
||||||
|
grep -q '^PUSH_SECRET_ENCRYPTION_KEY=change-me-' "$ENV_FILE" && replace_env "PUSH_SECRET_ENCRYPTION_KEY" "$(random_secret 32)"
|
||||||
|
|
||||||
|
mkdir -p "$ROOT_DIR/traefik/letsencrypt"
|
||||||
|
touch "$ROOT_DIR/traefik/letsencrypt/acme.json"
|
||||||
|
chmod 600 "$ROOT_DIR/traefik/letsencrypt/acme.json"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Konfiguration: $ENV_FILE"
|
||||||
|
echo "API: https://$api_domain"
|
||||||
|
echo "Admin: https://$admin_domain"
|
||||||
|
echo
|
||||||
|
echo "Bitte OPENAI_API_KEY sowie GOCARDLESS_SECRET_ID und GOCARDLESS_SECRET_KEY in .env ergänzen."
|
||||||
|
echo "Die DNS-A/AAAA-Records beider Domains müssen auf diesen Server zeigen."
|
||||||
|
|
||||||
|
if [[ "${1:-}" == "--start" ]]; then
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
docker compose up -d --build
|
||||||
|
docker compose ps
|
||||||
|
else
|
||||||
|
echo "Start: cd $ROOT_DIR && docker compose up -d --build"
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user