25 KiB
Prod-Rollout HiveGrid — Runbook (dedizierter Hetzner-Account)
Umsetzung von AD-24 (Prod unter eigenem Hetzner-Account, self-contained). Stand 2026-06-19. Go-Gates: Jeder Schritt mit 🟥 ist kostenwirksam / schwer reversibel und wird vor dem
createeinzeln mit dem Owner (Raffael) bestätigt. 🟩 = read-only / vorbereitend.
0. Voraussetzungen (🟩 vorhanden)
- Hetzner-API-Token (Prod):
~/.cli-tokens/hcloud-hivegrid-prod— verifiziert (Account leer). - S3 (Prod):
~/.cli-tokens/prod-s3-bucket-hetzner-credentials— Auth verifiziert (Projekt p15040939, fsn1), Bucket noch nicht angelegt. - Cloudflare (Zone
hivegrid.de):~/.cli-tokens/cloudflare-token(Global Key + Email →X-Auth-Email/X-Auth-Key). - Gitea/CI: Deploy-Pipeline
.gitea/workflows/deploy.yml(Promote-Pfad → Prod). - Entscheidungen: Ingress = Cloudflare-Proxy + Coolify/Traefik (kein Hetzner-LB). Region fsn1, x86 cx-Linie.
1. Netzwerk-Fundament (🟥 Provisionierung, aber günstig/reversibel)
- SSH-Key im Prod-Account hinterlegen (aktuell 0) — Public-Key des Deploy-/Admin-Keys.
- Private Network (Hetzner Cloud Network), z.B.
10.0.0.0/16, Subnetz fsn1. - Firewall:
- eingehend
80,443/tcp→ control/web/api (bzw. nur über Cloudflare-IP-Ranges, wenn proxied). 22/tcpSSH nur von Admin-/Bastion-IP.db+services: kein eingehender Public-Traffic, nur über das Private Network.
- eingehend
2. Server provisionieren (🟥 kostenwirksam — Go pro Batch)
Alle in fsn1, im Private Network, mit Firewall + SSH-Key. cx-Linie (x86):
| VM | Typ | Specs | Zweck |
|---|---|---|---|
hivegrid-control |
cx33 | 4/8/80 | Coolify + Orchestrierung/Bastion |
hivegrid-web |
cx33 | 4/8/80 | Web (SSR) |
hivegrid-api |
cx33 | 4/8/80 | API |
hivegrid-worker |
cx43 | 8/16/160 | 5 Worker-Queue-Gruppen + Meili + ClamAV + mCaptcha |
hivegrid-db |
cx33 | 4/8/80 | Postgres + PgBouncer + Redis |
- Volume (~50 GB) an
hivegrid-dbfür Postgres-Daten (nicht auf Boot-Disk). - (optional) Server-Backups für
hivegrid-dbaktivieren.
3. Coolify auf control (🟥)
- Coolify auf
hivegrid-controlinstallieren (eigenständige Prod-Instanz). web/api/worker/dbals remote Server in Coolify einhängen (über Private Network).- Container-Registry-Zugang (Gitea-Registry) + Cosign-Keys in Coolify hinterlegen.
4. Daten- + Zentral-Dienste (🟥)
- db-VM: Postgres (+ DB
hivegrid, DBmcaptcha), PgBouncer (Transaction-Pooling, Port 6432), Redis.DATABASE_URL→ PgBouncer,DIRECT_URL→ 5432. - worker+services-VM: Meilisearch (Master-Key), ClamAV (clamd), mCaptcha (zeigt auf db-Postgres
mcaptcha+ Redis). - DEK-Master-Key (AD-8) generieren + sicher hinterlegen (nicht ins Repo).
- S3-Bucket
hivegrid-prod(fsn1) anlegen + Versioning;prod-s3-bucket-hetzner-credentialsumbucket=hivegrid-prodergänzen.
5. App-Env + Deploy (🟥)
- Prod-Env je App setzen (analog Staging-Keys, aber Prod-Werte):
DATABASE_URL/DIRECT_URL,REDIS/BULLMQ_REDIS_URL,MEILI_URL/MEILI_API_KEY,MCAPTCHA_*,CLAMAV_HOST/PORT,SESSION/CRYPTO/JWT-Secrets,APP_BASE_DOMAIN/Host-Vars (Prod-Domains), SentrySENTRY_ENVIRONMENT=production.WEB_NOINDEXNICHT setzen (Prod soll indexierbar sein). - Migrationen:
prisma migrate deployals Pre-Deployment-Command (wie Staging). - Promote-Deploy über die Pipeline (oder Coolify) → Images ausrollen.
6. Ingress / DNS (🟥 outward-facing)
- Cloudflare-Records (Zone
hivegrid.de):app.,api.,captcha.→ Prod-IPs, proxied (orange).admin.→ Prod-IP, dns-only + Firewall/Cloudflare-Access-Restriktion.- Kein Wildcard.
- Coolify/Traefik: Domains je App + Let's-Encrypt am Origin; Cloudflare SSL-Modus Full (strict).
7. Endabnahme (🟩 verifizieren)
app./api./healthz+/readyz= 200 (DB erreichbar, Migration angewandt).- Login inkl. 2FA-Gate; mCaptcha greift; Suche (Meili) antwortet; File-Upload-Scan (ClamAV) ok.
- Prod ist indexierbar:
/loginträgt keinX-Robots-Tag: noindex;robots.txtallow-all. - Sentry-Events landen unter
environment=production.
8. Rollback / Notfall
- Deploy-Rollback: vorheriges Image-Tag in Coolify re-deployen.
- DB: Volume-Snapshot / Backup einspielen (forward-only Migrationen → Inverse-SQL als Kommentar je Migration).
- DNS: TTL niedrig halten während des Cutovers.
Inventar (provisioniert 2026-06-19, fsn1)
| Ressource | Name | ID | Public IP | Private IP |
|---|---|---|---|---|
| Server control | hivegrid-prod-control (cx33) |
143044798 | 157.90.243.228 | 10.0.0.2 |
| Server web | hivegrid-prod-web (cx33) |
143044800 | 167.233.109.142 | 10.0.0.3 |
| Server api | hivegrid-prod-api (cx33) |
143044801 | 167.233.110.100 | 10.0.0.4 |
| Server worker+services | hivegrid-prod-worker (cx43) |
143044803 | 49.13.24.190 | 10.0.0.5 |
| Server db | hivegrid-prod-db (cx33) |
143044805 | 178.105.208.72 | 10.0.0.6 |
| Volume (db-Daten) | hivegrid-prod-db-data 50 GB |
106081942 | — | /dev/disk/by-id/scsi-0HC_Volume_106081942 |
| Network | hivegrid-prod-net 10.0.0.0/16 |
12347309 | — | — |
| Firewall app (22/80/443) | hivegrid-prod-fw-app |
11162316 | — | — |
| Firewall db (22) | hivegrid-prod-fw-db |
11162317 | — | — |
| SSH-Key | hivegrid-prod-node1 (~/.ssh/node1) |
113985925 | — | — |
Offen / Härtung: SSH derzeit von 0.0.0.0/0 (key-only) erreichbar → später auf Admin-/Bastion-IP bzw. nur über control einschränken. Öffentliche 80/443 später auf Cloudflare-IP-Ranges begrenzen.
Coolify (Schritt 3) — erledigt 2026-06-19
- Coolify v4.1.2 auf
controlinstalliert (alle Container healthy). - Root-Admin headless angelegt (
raffael@tool-ctrl.de) via RootUserSeeder (docker exec -e ROOT_USER_*— Passwort-Policy: mixedCase+Ziffer+Symbol+uncompromised). - API aktiviert + API-Token erzeugt (Sanctum, mit
team_id; CoolifyscreateToken-Override braucht Team-Kontext, daher direkt übertokens()->create).GET /api/v1/servers→ 200. - Zugang (Secrets in
~/.cli-tokens/coolify-prod, nicht im Repo): UIhttp://coolify.hivegrid.de:8000bzw.http://157.90.243.228:8000. - ⚠ Härtung offen: Coolify-FQDN auf
https://coolify.hivegrid.de(443) umstellen + Port 8000 wieder schließen.
Coolify-Remote-Server + DB (Schritt 3–4) — erledigt 2026-06-19
- Docker 29.6.0 auf web/api/worker/db installiert (get.docker.com), in Coolify als Remote-Server eingehängt → alle
usable=true. node1-Key in Coolify (privkey_uuid). UUIDs in~/.cli-tokens/coolify-prod. - Coolify-Projekt HiveGrid (
m74l9z6an57cw0316pir9rrb) / Environment production (h3woz1vm70yf6ajpzj0ch2fz). - Postgres 16 (managed) + Redis 7 (managed) auf db-VM. Creds + URLs in
~/.cli-tokens/hivegrid-prod-services. - Cross-Server-Konnektivität (Designentscheidung): Coolify-managed DBs laufen im Docker-Netz der db-VM; die App-Server (web/api/worker) erreichen sie über das private Hetzner-Netz (
10.0.0.6:5432/6379), nicht über Container-DNS. DBs sind viapublic_portauf dem Host gebunden (0.0.0.0), aber public durch fw-db (nur 22) dicht — verifiziert: web→db privat OPEN, public DICHT.
Offen (nächste Schritte):
- Meilisearch + ClamAV + mCaptcha als Coolify-Applications/Services auf worker-VM (kein managed-DB-Typ), exposed aufs private Netz. mCaptcha nutzt Postgres-DB
mcaptcha+ Redis. - PgBouncer vor Postgres (Compose auf db-VM) →
DATABASE_URLüber Pooler,DIRECT_URLdirekt. - DEK-Master-Key + S3-Bucket
hivegrid-prod. - App-Deploy (web/api/worker) nach mitapp-Muster: image-retag / build-once-deploy-via-pull (nicht rebuild) +
migrate deploy. - DNS
app./api./admin./captcha. → Prod-IPs(Cloudflare) + Sentry (ToolCTRL-Instanz, prod-DSNs).
Zentral-Dienste + Storage (Schritt 4) — erledigt 2026-06-19
- Meilisearch v1.10 + ClamAV 1.4 als Coolify-Compose-Service
hivegrid-prod-search-avauf worker-VM, privat exposed (10.0.0.5:7700/:3310). Verifiziert: Meili von api-VM cross-server{"status":"available"}, ClamAV healthy. Master-Key in~/.cli-tokens/hivegrid-prod-services. - mCaptcha-Stack (mcaptcha + eigene Postgres + mcaptcha/cache) als Coolify-Service
hivegrid-prod-mcaptchaauf worker-VM, FQDNcaptcha.hivegrid.de(DNS dns-only → worker, LE-Cert via Traefik im Hochlauf). Secrets gespeichert. Folgeschritt: Account+Sitekey in der mCaptcha-Instanz anlegen → neueMCAPTCHA_SITEKEY_*/ACCOUNT_SECRETin App-Env (Code-/Env-Anpassung). - S3-Bucket
hivegrid-prod(fsn1) angelegt + Versioning;prod-s3-bucket-hetzner-credentialsergänzt.
App-Deploy — Muster verstanden (mitapp / build-once-retag)
- Image-Schema:
git.tool-ctrl.de/toolctrl-labs/hivegrid/{web,api,worker}:<commit-sha>, build_packdockerimage, Port 8080. - Promote = retag, kein Rebuild: Prod deployt denselben SHA-Tag, der getestet/gestaged ist (aktuell
9ae3963d…= live auf Staging → Image existiert in der Gitea-Registry). - 7 Coolify-Apps (nicht 3!):
web,api,worker-general,worker-mail,worker-import,worker-webhook,worker-plugin— web/api aus/web+/api-Image, die 5 Worker aus/worker-Image mit unterschiedlichemWORKER_QUEUE_GROUP. - Deploy-Trigger:
scripts/coolify-deploy.mjs(PATCHdocker_registry_image_tag→ deploy),migrate deployals Pre-Deployment-Command. - Sentry (ToolCTRL-Instanz): gleiche DSNs wie Staging, nur
SENTRY_ENVIRONMENT=production(Estate-Regel: Staging↔Prod = Tag, kein eigenes Projekt).
Offen (Go-Live-Phase): Registry-Pull-Credential in Prod-Coolify · 7 Apps anlegen (Docker-Image) · App-Env Prod (aus Staging-Env transformiert: Prod-IPs/Domains, frische Secrets, prod-Sentry-Tag, kein WEB_NOINDEX) · erster Promote-Deploy + migrate deploy · DNS app./api./admin. · Endabnahme · mCaptcha-Sitekey + PgBouncer (Optimierung) als Folgeschritte.
App-Deploy + Go-Live (Schritt 5–6) — LIVE 2026-06-19
- Registry-Pull:
docker login git.tool-ctrl.deauf web/api/worker (Usergitea-admin; Härtung: scoped Bot-PAT). Imagehivegrid/{web,api,worker}:9ae3963d…(= Staging-Stand) gepullt. - 7 Coolify-Apps (Docker-Image):
hivegrid-web,-api,-worker-{general,mail,import,webhook,plugin}(Worker jeWORKER_QUEUE_GROUP). UUIDs in~/.cli-tokens/coolify-prod. - Prod-Env aus Staging transformiert (Bug gefixt: bei doppelten build/runtime-Keys nicht-leeren Wert bevorzugen — sonst Mailjet u.a. leer → Worker-Crash). Frische, app-konsistente Secrets in
~/.cli-tokens/hivegrid-prod-app-secrets. Demo-Seed AUS,WEB_NOINDEXraus (Prod indexierbar). - Postgres-Image korrigiert:
postgres:16-alpine→pgvector/pgvector:pg16(Migration04_enable_pgvectorbrauchte die Extension). Migration im laufenden web-Container ausgeführt → alle Migrationen angewandt (inkl. P2UserSession+TrustedDevice). - DNS (Cloudflare):
app./admin.→ web-VM,api.→ api-VM. SSL-Mode strict.app/apiproxied (Edge-TLS/WAF),admindns-only (Härtung folgt),captchadns-only bis Cert. - Endabnahme grün:
app.hivegrid.de+api.hivegrid.de/healthz+/readyz= 200 (DB-backed),/loginrendert „Anmelden | HiveGrid", kein X-Robots-Tag (indexierbar). Alle 7 Container healthy.
Fix: Admin-SSO-Login-Loop (Cookie-Domain bei Multi-Host) — 2026-06-19
Symptom: SSO-Login loopte (Audit zeigte wiederholt admin.login.sso.success, aber Folge-Request nicht eingeloggt). Ursache: resolveCookieDomain(APP_BASE_DOMAIN) band das Session-Cookie an app.hivegrid.de → galt nicht für admin.hivegrid.de (getrennte Hosts). Auf Staging unsichtbar, weil dort app+admin denselben Host teilen. Fix: APP_BASE_DOMAIN=hivegrid.de (Parent-Domain) auf allen Apps → Cookie-Domain hivegrid.de gilt für app./admin./api.; abgeleitete Hosts bleiben korrekt (getDerivedHosts → app./admin./api.hivegrid.de). Verifiziert: Set-Cookie: Domain=hivegrid.de. Lesson: Multi-Host-Prod braucht APP_BASE_DOMAIN = Registrable-Domain, nicht den app-Host.
Härtung/Abschluss-Runde — 2026-06-19 (nach SSO-Bestätigung)
- ✅ Admin-Fallback aus:
ADMIN_FALLBACK_LOGIN_ENABLED=false→ SSO (Authentik) ist einziger Admin-Pfad. - ✅ Sentry aktiviert:
SENTRY_DSN_WEB/API/WORKERaus zentraler Instanz gesetzt (environment=production), alle 7 Apps redeployed + healthy. (Waren vorher leer → Prod lief ohne Error-Tracking.) - ✅ mCaptcha komplett scharf: Root-Cause des fehlenden LE-Certs war
service ... port is missing(Traefik kannte Ziel-Port nicht — Compose-Service ohne Port-Deklaration). Fix: Compose mit korrekter Coolify-Magic- SERVICE_FQDN_MCAPTCHA_7000=captcha.hivegrid.de+expose: 7000→ Host+Port-Label generiert, LE-Cert geholt (captcha.hivegrid.deHTTP 302, gültiges Cert). Accounthivegrid+ Captcha-Widget via mCaptcha-API angelegt →MCAPTCHA_SITEKEY_LOGIN+MCAPTCHA_ACCOUNT_SECRET+MCAPTCHA_INSTANCE_URLauf web gesetzt + redeployed. Creds in~/.cli-tokens/hivegrid-prod-services.CAPTCHA_FAIL_OPEN=truebewusst belassen (captcha aktiv, aber mCaptcha-Ausfall sperrt nicht den Login).
CI-Promote verdrahtet + getestet — 2026-06-19
- Gitea-Repo-Variablen (Production-Slot):
COOLIFY_API_URL,COOLIFY_PROJECT_UUID,COOLIFY_ENVIRONMENT_NAME/UUID,COOLIFY_RESOURCE_UUIDS(7 Apps),COOLIFY_PROVISION_WORKER_GROUPS=false+ SecretCOOLIFY_TOKEN. (Multi-Server ok: Resource-Auflösung ist server-agnostisch per Image-Name+Queue-Group; Auto-Provisioning aus, da Apps existieren.) - App-Tags auf
mainumgestellt +:main-Alias in Registry erstellt (= aktueller SHA). Promote aliased Staging-Image →:main→ deployt. - Worker-Apps korrekt konfiguriert (vom CI-Drift-Check erzwungen, macht Prod robuster): Health-Ports 9101–9105 +
/readyz-Health-Checks; Worker-ENV (WORKER_ENABLED/HEALTH_PORT/ADMIN_PORT/CONCURRENCY je Gruppe; general: OUTBOX_DISPATCHER_/WORKFLOW_TRIGGER_OUTBOX_; mail: TRANSACTIONAL_MAIL_OUTBOX_REQUIRED/MARKETING_SEQUENCES_ENABLED). - Test-Promote (runs 8436–8438): iterativ alle Drifts gefixt; 8438 deployt Prod erfolgreich (production worker groups + web-health + dependent deploys grün; Prod app/api
/readyz200, alle 7 Container healthy,migrate status= up to date). - ⚠ Ein Step bleibt rot: „Verify production migration status after web deploy" läuft auf dem CI-Runner und braucht DB-Zugang — die Prod-DB liegt aber im privaten Netz des separaten Prod-Accounts (
10.0.0.6), für den Runner strukturell unerreichbar. Kein Prod-Problem (Migration lief als Pre-Deploy +migrate statusim Container = up to date). Kein Prod-Problem (Migration lief als Pre-Deploy +migrate statusim Container = up to date). - Container-Verify-Versuch (revertiert
b833c82e): Step auf SSH-Jump (Runner→control→web-Container) umgeschrieben → scheiterte am Preflight-Wiring-Guardscripts/verify-gitea-deploy-workflow.mjs(erzwingt exakte CI-Runner-Step-Struktur + verbietet secret-backed env inecho/SSH). Der Guard läuft im Preflight bei jedem Push → die Änderung machte die geteilte Pipeline rot (auch Staging). Sofort revertiert, Preflight wieder grün. Konsequenz: Jede Änderung am Step erfordert, zwei bewusste CI-Guards (Struktur + Secret-Leak) + Tests umzuschreiben → invasiv, mit Owner abzustimmen. Optionen: (A) Guard so anpassen, dass der Step bei privater DB nicht erzwungen wird; (B) Container-SSH-Verify voll umsetzen mit Host-Key-Pinning (Security-Review: keinStrictHostKeyChecking=noin committetem CI-Code) + Guards/Tests; (C) so lassen — Promote deployt erfolgreich, nur der redundante Schluss-Check ist bei Prod rot.
CI-Promote KOMPLETT GRÜN — 2026-06-20 (Run 8453)
Der erste echte Prod-Promote deckte drei latente Bugs im nie-gelaufenen Production-Pfad der geteilten deploy.yml auf (Prod gab es vorher nicht). Alle gefixt, lokal/manuell verifiziert, mit committeten Zwischenständen:
- migration-status-verify (war rot): CI-Runner erreicht die private Prod-DB nicht (separater Account, AD-24). Fix:
DB_MIGRATE_STATUS_ALLOW_UNREACHABLE=true(eingebauter Script-Mechanismus, erkennt P1001 → skip) +DATABASE_URL-Secret. Struktur unverändert → Wiring-Guard + Tests grün. - production-dependent-health worker-check (war rot): Step nutzte
remote_ssh, definierte es aber nie (Copy-Paste-Lücke ggü. Staging) → „worker container not found". Fix: SSH-Setup +remote_sshmit ProxyCommand (nicht ProxyJump — Key-Passthrough) über control-Bastion zur worker-VM (Multi-Server), gepinnte Host-Keys, Secret via Zwischen-var (umgeht Secret-Guard). Lokal verifiziert: Container gefunden + health-probeready. - web-health-revision-mismatch (war rot):
/healthzüberapp.hivegrid.delieferte eine 40 min alte Cloudflare-gecachte revision (cf-cache-status: HIT) statt der frisch deployten. Fix: CF-Cache-Rule/healthz+/readyz→ bypass (Health-Endpoints nie cachen) + Purge. Kein deploy.yml-Eingriff.
- Gitea-Config: Prod-Coolify-Variablen + Secrets (
COOLIFY_TOKEN,DATABASE_URL,COOLIFY_PROD_SSH_KEY/KNOWN_HOSTS, Host/Worker-IP-vars). Apps auf Tagmain,:main-Alias in Registry. - Ergebnis: Promote 8453 komplett grün (web-health + migration + dependent-health alle success). Prod läuft auf
94959b31, app/api/readyz= ready. Künftige Prod-Deploys:deploy.ymlworkflow_dispatchaction=promote, staged_sha=<:staging-SHA>, backup_confirmed=true.
Härtungs-/Service-Runde 2 — 2026-06-20
- ✅ mCaptcha scharf + proxied:
captcha.hivegrid.dejetzt Cloudflare-proxied; Sitekey + Account-Secret bereits gesetzt (CAPTCHA_FAIL_OPEN=trueals Safety belassen). - ✅ PgBouncer (alle 7 Apps): Coolify-Compose-Service
hivegrid-prod-pgbouncerauf db-VM (edoburu/pgbouncer:v1.23.1-p3, transaction-pool, scram,6432→5432). Apps:DATABASE_URL→10.0.0.6:6432?pgbouncer=true&connection_limit=10,DIRECT_URL→10.0.0.6:5432. Kanarienvogel web/readyz=ready verifiziert, dann api+5 Worker nachgezogen — alle healthy. Creds in~/.cli-tokens/hivegrid-prod-services. - ✅ Coolify-Port 8000 zu (ohne Tunnel): Coolify hinter den schon laufenden Traefik (coolify-proxy) gehängt via Dynamic-Config-Datei
/data/coolify/proxy/dynamic/coolify-ui.yaml(Host(coolify.hivegrid.de)→http://coolify:8080, certresolver letsencrypt) — file-watch, kein coolify-Restart, kein Selbst-Aussperr-Risiko. LE-Cert geholt,https://coolify.hivegrid.de/api/v1=200. Firewall fw-app: 8000 entfernt (jetzt 22/80/443). CICOOLIFY_API_URL→https://coolify.hivegrid.de/api/v1. Verifiziert: :8000 dicht, https-API 200, SSH+app/api weiter ok. (CF-Tunnel wurde erwogen, vom Owner abgelehnt → Traefik-Weg.) - ✅ Sentry gelöst (DSN-Host war falsch): Die alte DSN nutzte
sentry.boiler.saas-ctrl.com(→Staging-VM, 503) — toter Host, richtiger Key/Projekt. Über node1-SSH zum Dedi (176.9.111.179, self-hosted Sentry) die Projekte/Keys aus der Sentry-Postgres geholt: orgsentry, Projekteweb=4/api=2/worker=3. Korrekte DSNs (https://<key>@sentry.saas-ctrl.com/<id>) gesetzt auf web/api/worker +SENTRY_ENVIRONMENT=production. Verifiziert: Test-Envelope von der web-VM → HTTP 200; Container hat…@sentry.saas-ctrl.com/4; alle 7 healthy. Creds in~/.cli-tokens/hivegrid-prod-services. Hinweis: nutzt die generischen boilerplate-Projekte (web/api/worker, getrennt perenvironment-Tag) — dediziertehivegrid-*-Sentry-Projekte wären Estate-Standard-Verfeinerung (Folgeschritt).
Bewusst NICHT blind ausgeführt (echte Aufgaben, kein Einzeiler — Risiko/Begründung):
- Registry-Pull-Bot: scoped Bot-PAT statt
gitea-admin(Least-Privilege; aktuell funktional). - CF-Tunnel-Rest: der bei Coolify-Port erwogene CF-Tunnel wurde verworfen; ein leerer Tunnel-Eintrag
hivegrid-prod-coolifyohne Connector kann in Cloudflare gelöscht werden (API-Delete gab 400, harmlos da ohne cloudflared inaktiv).
Offene Folgeschritte (nicht-blockierend)
- mCaptcha: LE-Cert für
captcha.hivegrid.deverifizieren, dann proxied; Account+Sitekey anlegen →MCAPTCHA_*ins App-Env (bis dahinCAPTCHA_FAIL_OPEN=true). - Sentry: Events unter
environment=productionverifizieren (DSN aus Staging übernommen). - Härtung: SSH auf Bastion/Admin-IP einschränken ·
admin.hivegrid.deper Cloudflare-Access/IP schützen · scoped Registry-Bot stattgitea-admin· Coolify-FQDN auf https/443 + Port 8000 schließen. - Optimierung: PgBouncer vor Postgres · Coolify Pre-Deploy-Migrate-Mechanik fürs CI-Promote klären (lief manuell, Coolify-pre-deploy griff nicht).
- CI-Promote:
deploy.yml/Gitea-Variablen auf das Prod-Coolify verdrahten (für künftigepromote-Deploys ohne manuelle Schritte).
Härtung (Schritt 7) — teilweise erledigt 2026-06-19
- ✅ SSH-Angriffsfläche reduziert: neue FW
hivegrid-prod-fw-web(nur 80/443) auf web/api/worker; public SSH dort dicht, Zugang nur übercontrol(Bastion) + privates Netz.controlbehält 22/80/443/8000.db: kein public Port (fw-db rules geleert), nur privat. Verifiziert: control-SSH ok, web-SSH public dicht, web via control ok, app/api öffentlich ok, Coolify-Deploys (privates Netz) laufen weiter. - ✅ Demo-Seed AUS,
WEB_NOINDEXraus (prod indexierbar).
Offene Härtung/Folgeschritte (priorisiert):
- mCaptcha public-FQDN: Traefik liefert noch Default-Cert für
captcha.hivegrid.de(Compose-Service-Domain-Labels nicht generiert; fqdn per tinker reichte nicht). Container laufen intern. Domain am besten über Coolify-UI ammcaptcha-Service setzen → LE-Cert → proxied. Dann Account+Sitekey in der mCaptcha-Admin-UI anlegen →MCAPTCHA_SITEKEY_*/ACCOUNT_SECRETins App-Env,CAPTCHA_FAIL_OPEN=false. Bis dahin: fail-open (kein Captcha-Zwang). - Admin-SSO (Authentik) — eingerichtet 2026-06-19: OIDC-Provider + App
hivegrid-admin-prodinauth.tool-ctrl.de(Provider pk 31, explicit-consent-Flow, PKCE, signing-key gesetzt). Scopes openid/email/profile + eigenesgroups-Mapping (HiveGrid groups) → liefert dengroups-Claim für die Admin-Autorisierung (toolctrl-admin).AUTHENTIK_ADMIN_CLIENT_ID/SECRET,AUTHENTIK_ISSUER_URL,AUTHENTIK_ADMIN_REDIRECT_URI,AUTHENTIK_ADMIN_POST_LOGOUT_REDIRECT_URIauf web gesetzt + redeployed (Creds in~/.cli-tokens/hivegrid-prod-authentik). Verifiziert:/auth/admin/login→ 302 zu Authentik mit korrekter redirect_uri/PKCE; SSO-Button sichtbar; OIDC-Discovery grün; raffael/akeshan/dejan/arthur intoolctrl-admin. Noch zu tun (interaktiv/Browser): einmal per SSO einloggen + prüfen, ob der erste Login einen AdminUser JIT-provisioniert (sonst AdminUser in Prod-DB seeden). Erst nach erfolgreichem SSO-LoginADMIN_FALLBACK_LOGIN_ENABLED=falsesetzen (aktuelltrueals Backup, allowlist raffael/akeshan).admin.hivegrid.dedns-only; optional Cloudflare-Access davor. - Coolify-Port 8000 schließen, sobald Coolify hinter
https://coolify.hivegrid.de(443) läuft (Port aktuell offen, key+token-geschützt; für laufende API-Schritte noch gebraucht). - Registry: scoped Bot-PAT statt
gitea-adminfür den Image-Pull-Login. - CI-Promote:
deploy.yml/Gitea-Variablen aufs Prod-Coolify verdrahten (Deploys liefen manuell; Coolify-Pre-Deploy-Migrate griff nicht → Migration manuell im Container). - Sentry Events unter
environment=productionverifizieren · PgBouncer (Pooling) ergänzen.
Gesamt-Status (Stand 2026-06-20) — ✅ VOLLSTÄNDIG LIVE + GEHÄRTET
app./api./admin.hivegrid.de öffentlich (HTTPS), DB-backed (Postgres 16/pgvector + PgBouncer + Redis), P2-Set + 5 Worker-Gruppen, Revision 94959b31.
Alles erledigt:
- ✅ CI-Promote komplett grün (Run 8453) —
workflow_dispatch action=promote, staged_sha=<:staging-SHA>, backup_confirmed=true. Drei latente Multi-Server-Promote-Bugs gefixt (migration-status viaDB_MIGRATE_STATUS_ALLOW_UNREACHABLE, dependent-health via ProxyCommand-SSH, web-health via CF-Cache-Bypass). - ✅ SSH-gehärtet — control = Bastion, web/api/worker/db public-SSH dicht.
- ✅ Admin-SSO über Authentik (
auth.tool-ctrl.de, OIDC, groups-Claim →toolctrl-admin). - ✅ mCaptcha scharf + proxied (
captcha.hivegrid.de). - ✅ PgBouncer vor Postgres, alle 7 Apps über
6432. - ✅ Coolify-Port zu — nur noch
https://coolify.hivegrid.de(Traefik+LE),:8000firewalled. - ✅ Sentry funktional —
sentry.saas-ctrl.com(alteboiler.-DSN war tot/503).
Estate-Doku nachgezogen: infra/ README (Host-Inventar + DNS) + ADR-016 (dedizierter Account + eigenes Coolify) + LESSONS #13–#17 + onboard-pro-saas-Runbook.
Rest-Folgeschritte (Least-Privilege, kein Druck): scoped Registry-Bot statt gitea-admin · dedizierte hivegrid-*-Sentry-Projekte (statt geteilter boilerplate-Projekte) · Sentry-DSN-Host-Fix auch auf Staging nachziehen (dort steckt dieselbe tote boiler.-DSN) · leeren CF-Tunnel hivegrid-prod-coolify löschen.