# Server, Datenbankrollen und Betrieb

> Der Konto- und Relay-Dienst, die PostgreSQL-Rolleninvariante, die Konfiguration, die ein Entwicklungsserver braucht, und was über HTTP-Health hinaus zu prüfen ist.

Handbuch / Mitwirken · Gegen den Quellcode geprüft am 2026-10-05 · https://www.hbcode.ai/de/docs/server-operations

## Der Server besitzt Konto- und Relay-Zuständigkeiten

Die Actix-Anwendung initialisiert beim Start gemeinsame Dienste: Datenbank-Pools, Kontoauthentifizierung, Verbindungsverwaltung sowie konfigurierte Anbieter- und Push-Integrationen. Der DeviceConnectionManager wird von den HTTP-Workern des Prozesses gemeinsam genutzt. Seine Live-Sockets, Verbindungsgenerationen und ausstehenden RPC-Einträge sind Prozesszustand. Auch der Relay-Session-Speicher hinter den Resume-Tokens liegt im Arbeitsspeicher, mit einer TTL von 24 Stunden und einem Aufräumlauf alle 5 Minuten. Jeder RPC und jeder Heartbeat-Frame erneuert einen Session-Eintrag. Redis ist für den gemeinsamen Anmeldezustand, die Ratenbegrenzung und Guthabenreservierungen für bezahlte Anfragen erforderlich, und der Prozess beendet sich beim Start ohne Redis. Redis macht ein Register von Live-Sockets nicht zwischen Serverprozessen übertragbar.

Beide Geräte und ihre ausstehende Anfrage müssen diesen Connection-Manager erreichen, deshalb läuft in Produktion ein Relay-Prozess pro Region mit explizitem Verbindungsabbau. Das Kapitel zum Geräte-Routing erklärt, warum eine Round-Robin-Replik das Routing zerstören würde.

## PostgreSQL-Rollen sind eine Start-Invariante

**Der Server beendet sich beim Start, wenn seine zwei Datenbank-Logins nicht getrennt und durch Zeilensicherheit begrenzt sind**

In PostgreSQL besitzt die Migrationsrolle die Tabellen, und 27 davon müssen Zeilensicherheit aktiviert und erzwungen haben. Der Server verbindet sich über zwei Logins, das System-Login hbcode und das Tenant-Login authenticated. Beim Start prüft er, dass keines der Logins Superuser ist, Zeilensicherheit umgeht, Rollen erbt oder Tabellen besitzt, dass keines das andere ist oder Mitglied des anderen oder des Tabellenbesitzers, und dass das Tenant-Login ohne Benutzer-ID aus 16 Tenant-Tabellen keine Zeilen liest. Jeder Fehler beendet den Prozess, bevor er auf Anfragen lauscht. Jeder Serverprozess öffnet einen eigenen System-Pool mit 8 und einen Tenant-Pool mit 16 Verbindungen, die seine HTTP-Worker teilen.

- **Laufzeit-Logins** (DATABASE_SYSTEM_URL · DATABASE_USER_URL): assert_runtime_boundary prüft die Principals, als die sich diese URLs verbinden, nicht die URLs. Schon vor dem Verbinden lehnt create_dual_pools zwei identische URLs ab.
- **Tenant-Kontext** (app.current_user_id): Mit set_config für eine Transaktion gesetzt, sodass eine Verbindung im Pool ihn am Ende der Transaktion vergisst. Zeilensicherheits-Policies, die für jedes Login geschrieben sind, entscheiden, was es anfassen darf.
- **Pools** (DB_POOL_SYSTEM_MAX · DB_POOL_USER_MAX): Einmal pro Serverprozess geöffnet und von allen seinen HTTP-Workern geteilt. Jeder weitere Prozess, auch die zweite Farbe während einer Blue-Green-Umschaltung, öffnet eigene.

create_dual_pools öffnet beide Pools und führt assert_runtime_boundary für die Principals aus, als die sie tatsächlich verbunden sind, sodass eine URL mit der falschen Rolle an denselben Prüfungen scheitert.

| Standardwert | System-Pool | Tenant-Pool |
| --- | --- | --- |
| Maximale Verbindungen | 8 | 16 |
| Minimale Verbindungen | 2 | 4 |
| application_name | hbcode-system | hbcode-user |
| Statement-Timeout | 30 Sekunden | 8 Sekunden |
| Idle-in-Transaction-Timeout | 60 Sekunden | 10 Sekunden |
| Lock-Timeout | 5 Sekunden | 2 Sekunden |

Beide Pools nutzen 2 Sekunden Acquire-Timeout, 60 Sekunden Leerlauf-Timeout und eine maximale Verbindungslebensdauer von 1.800 Sekunden. Jede Verbindung setzt die Zeitzone auf UTC. Ein fehlgeschlagener Verbindungsaufbau wird bis zu fünfmal versucht, und die Wartezeit wächst mit jedem Versuch um eine Sekunde. Jeder Serverprozess öffnet eigene Pools, die seine HTTP-Worker teilen. Weitere Prozesse, auch die zweite Farbe einer Blue-Green-Umschaltung, vervielfachen die Verbindungen und brauchen zuerst eine Prüfung der Datenbankkapazität.

## Lokalen Server konfigurieren

1. Kopieren Sie server/.env.example nach server/.env und konfigurieren Sie eine lokale Datenbank, eine Redis-URL, Einstellungen zur Verschlüsselung von JWT- und Refresh-Tokens sowie Auth0-Werte.
2. Legen Sie getrennte Migrations- und Runtime-Principals gegen eine lokale Entwicklungsdatenbank an.
3. Wenden Sie server/scripts/run-migrations.sh mit der benötigten Migrationsumgebung an. server/README.md beschreibt die Rollenbereitstellung und die Erhaltungsrichtlinie.

Aus dem Repository-Root lesen und ausführen:

```
cp server/.env.example server/.env
# Configure the local services and database roles first.
pnpm server:check
pnpm dev:server
```

Der Migrations-Runner verarbeitet infrastructure/ansible/playbooks/plantocode/migration-execution-plan.yml, denselben geordneten Plan, den die Bereitstellung nutzt. Die Migrationsrolle muss ein Superuser sein, der keiner von postgres, hbcode oder authenticated ist und in DATABASE_RUNTIME_ROLES fehlt. Als execution once markierte Dateien werden per sha256 in plantocode_internal.migration_execution_ledger gesperrt, und der Runner bricht ab, wenn eine Laufzeitrolle dieses Ledger erreichen kann. Eine Prüfsummenabweichung, eine once-Datei ohne einzelne Transaktion oder eine ungeplante .sql-Datei bricht die Ausführung ab. PRESERVE_EXISTING_DATABASE_IDENTITIES=true wird nur für die expand-Phase akzeptiert. Ersetzen Sie den Runner nicht durch sqlx migrate run.

| Zweck | Einstellungen und wann sie erforderlich sind |
| --- | --- |
| Kerndienste | DATABASE_SYSTEM_URL, DATABASE_USER_URL, REDIS_URL. Zentrale Datenbank- und gemeinsam genutzte Anmeldedienste. |
| Konto-Anmeldung | AUTH0_DOMAIN, AUTH0_API_AUDIENCE, AUTH0_ALLOWED_CLIENT_IDS. Die konfigurierte Vertrauensgrenze für Konto-Logins. |
| Tokens | JWT_SECRET, REFRESH_TOKEN_ENCRYPTION_KEY. Ausstellen von App-Anmeldedaten und Speichern von Refresh-Material. |
| Anbieter | Provider-API-Schlüssel. Nur die entsprechenden konfigurierten Provider-Routen. |
| Integrationen | Stripe-, APNs-, FCM-, E-Mail- und weitere Integrationseinstellungen. Ihre jeweiligen Konto- oder Benachrichtigungsfunktionen. |
| Weitere Datenbanken | METRICS_DATABASE_URL, ACCOUNT_CONTROL_DATABASE_URL. In der Entwicklung optional. Die Produktion startet ohne beide nicht, und jede muss auf eine eigene PostgreSQL-Datenbank zeigen. |
| Service-Health | SERVICE_HEALTH_REGION, SERVICE_HEALTH_RELEASE. Erforderlich, wenn METRICS_DATABASE_URL gesetzt ist. Die Region ist eu oder us. Das Release ist der SHA-256 der bereitgestellten Binärdatei als 64 Hex-Zeichen. |
| Vorschauen | WORKSPACE_PREVIEW_HMAC_SECRET. In der Entwicklung optional, in der Produktion erforderlich. Mindestens 32 Zeichen. |
| Deployment | PLANTOCODE_DEPLOYMENT_TOKEN. Der geschützte Endpunkt /health/deployment und seine Aktion /health/deployment/drain-device-links. Ohne das Token antworten beide mit 503. Ein falsches Token erhält 401. |

## Verhalten jenseits des HTTP-Health-Checks verifizieren

GET /health prüft den grundlegenden Serverzustand. Das geschützte /health/deployment unterstützt die Deployment-Inspektion. Verifizieren Sie anschließend einen Konto-Login, zwei Geräteregistrierungen, einen schreibgeschützten Desktop-RPC und eine kontrollierte Outbox-Operation. Ein fehlerfreier HTTP-Listener allein testet weder die Trennung von Datenbankrollen noch native Callbacks, Provider-Konfigurationen oder das Relay-Routing.

Eine nützliche Relay-Spur folgt der Anfrage-ID, dem Quell-Client, dem Ziel-Desktop, der Methode und der Verbindungsgeneration. Der Weiterleitungscode extrahiert den W3C-Trace-Kontext und injiziert den Kindkontext in den nächsten Hop. Bei mehrdeutiger Zustellung prüfen Sie den remote_rpc_idempotency-Anspruch des Desktops und seine chat_operation_ledger-Zeilen, bevor Sie schließen, ob Arbeit gelaufen ist.

**Ein Release wird auf dem US-Canary fertig, bevor es in der EU beginnt. Die zwei Regionen laufen daher eine Zeit lang mit verschiedenen Releases**

Zwei Host-Spuren auf einer nicht maßstäblichen Zeitachse. Die Spur interserver-us durchläuft fünf Schritte in fester Reihenfolge: das Datenbank-Backup, api-expand, api-binary-deploy, die Soak-Phase und api-migrate. Danach bedient sie das neue Release. Während all dem bedient die Spur hetzner-primary das vorherige Release. Ein Betreiber prüft das Canary-Ergebnis. Erst dann durchläuft hetzner-primary dieselben fünf Schritte in derselben Reihenfolge.

- **Backup** (/usr/local/bin/backup_postgresql.sh): Läuft auf jedem Host zuerst. Seine geprüfte Ausgabe ist der einzige Weg zurück nach einer Vorwärtsmigration.
- **Expand** (site-app.yml --tags api-expand): Wendet den abwärtskompatiblen Teil des Migrationsplans an. Die Binärdatei, die noch läuft, arbeitet auf dem erweiterten Schema weiter.
- **Binärdatei und Soak-Phase** (site-app.yml --tags api-binary-deploy): Aktiviert die neue Binärdatei mit den Blue-Green-Diensten. Der Betreiber prüft Health und Logs und wartet das Ende der Soak-Phase ab.
- **Migrate** (site-app.yml --tags api-migrate): Schreibt beide Release-Farben fest und wendet den Contract-Teil des Plans an. Nach diesem Schritt gibt es keine allgemeine Rollback-Aktion.
- **Ein Host pro Lauf** (--limit interserver-us · --limit hetzner-primary): Jede Aktion, die die Produktion ändert, zielt auf genau einen Host im Inventar. Die EU-Abfolge beginnt erst, wenn das Canary-Ergebnis geprüft ist.

Die Produktion hat zwei Hosts im Ansible-Inventar: interserver-us in den USA und hetzner-primary in der EU. Jeder betreibt den Server als zwei Dienste, plantocode-blue auf Port 8080 und plantocode-green auf Port 8081. Jede Aktion, die die Produktion ändert, läuft über site-app.yml mit genau einem Host in --limit. infrastructure/ansible/operations.md ist das Runbook. Inventar und Geheimnisse sind Betreiberkonfiguration.

| Schritt auf einem Host | Aktion und Ergebnis |
| --- | --- |
| Backup | backup_postgresql.sh in /usr/local/bin. Ein geprüftes Datenbank-Backup, das der Betreiber aufbewahrt. |
| Expand | --tags api-expand. Die abwärtskompatible Phase des Migrationsplans ist angewendet und bestätigt. |
| Binary | --tags api-binary-deploy. Die neue Binärdatei ist aktiv. Der Betreiber prüft Health und Logs und schließt die Soak-Phase ab. |
| Migrate | --tags api-migrate. Beide Release-Farben sind festgeschrieben, und die Contract-Phase ist angewendet. |

Das Runbook verbietet drei Abkürzungen. Kopieren Sie nie eine Release-SQL-Datei auf einen Server, um psql von Hand auszuführen. Führen Sie nie eine Anwendungs- oder Datenbankänderung ohne genau ein --limit aus. FLUSHALL in Redis ist keine routinemäßige Wiederherstellung und braucht die ausdrückliche Freigabe des Betreibers.

- [Warum ein Relay-Prozess pro Region](https://www.hbcode.ai/de/docs/device-routing.md): Live-Sockets und ausstehende Anfragen sind Prozesszustand, daher kommt ein Drain, bevor ein Prozess stoppt.
