This commit is contained in:
ValueOn AG 2026-05-25 23:10:57 +02:00
parent 9dd90a0eb5
commit 62adb15883
5 changed files with 217 additions and 189 deletions

View file

@ -49,10 +49,10 @@ Jeder Service-Bereich ist ein eigenes OpenStack-Projekt mit eigenem Netzwerk, ei
|---|---|---|---|---|---|---|
| `porta-main-ui-nyla` | main | Frontend (React/Vite) | a2-ram4-disk20-perf1 | 10.20.0.92 | 37.156.43.5 | `porta.poweron.swiss` |
| `porta-main-platform-core` | main | Backend (FastAPI) | a2-ram4-disk50-perf1 | 10.20.0.197 | 83.228.234.207 | `api.poweron.swiss` |
| `porta-main-db` | main | PostgreSQL + pgvector | a2-ram4-disk80-perf1 | 10.20.0.21 | 37.156.40.141 | -- |
| `porta-main-db` | main | PostgreSQL + pgvector | a2-ram4-disk80-perf1 | 10.20.0.21 | 37.156.40.141 | `db.poweron.swiss` |
| `porta-int-ui-nyla` | int | Frontend (React/Vite) | a2-ram4-disk20-perf1 | 10.20.0.182 | 37.156.41.74 | `porta-int.poweron.swiss` |
| `porta-int-platform-core` | int | Backend (FastAPI) | a2-ram4-disk50-perf1 | 10.20.0.74 | 37.156.43.14 | `api-int.poweron.swiss` |
| `porta-int-db` | int | PostgreSQL + pgvector | a2-ram4-disk80-perf1 | 10.20.0.175 | 37.156.42.67 | -- |
| `porta-int-db` | int | PostgreSQL + pgvector | a2-ram4-disk80-perf1 | 10.20.0.175 | 37.156.42.67 | `db-int.poweron.swiss` |
Key Pair: `ida-laptop` (alle Instanzen)

View file

@ -0,0 +1,117 @@
<!-- status: canonical -->
<!-- lastReviewed: 2026-05-25 -->
# Nginx-Konfiguration (Reverse Proxy)
Jede `platform-core`-VM (main + int) nutzt nginx als Reverse Proxy vor uvicorn (Port 8000).
## Relevante Einstellungen
| Einstellung | Wert | Zweck |
|---|---|---|
| `client_max_body_size` | `0` (unbegrenzt) | Kein Upload-Limit (Dateien, DB-Migration-Restore) |
| `proxy_pass` | `http://127.0.0.1:8000` | Weiterleitung an uvicorn |
| `proxy_http_version` | `1.1` | Erforderlich fuer WebSocket-Upgrade |
| `Upgrade` / `Connection` | `$http_upgrade` / `"upgrade"` | WebSocket-Support (STT-Streaming) |
| `proxy_read_timeout` | `600s` | Lange AI/STT-Requests |
| `proxy_send_timeout` | `600s` | Lange Uploads |
| `proxy_request_buffering` | `off` | Streaming-Uploads ohne Pufferung |
| SSL | Let's Encrypt (certbot) | TLS-Terminierung |
## Site-Config: porta-main-platform-core
Datei auf VM: `/etc/nginx/sites-enabled/gateway`
```nginx
server {
listen 80;
server_name api.poweron.swiss;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name api.poweron.swiss;
ssl_certificate /etc/letsencrypt/live/api.poweron.swiss/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.poweron.swiss/privkey.pem;
client_max_body_size 0;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_request_buffering off;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}
}
```
## Site-Config: porta-int-platform-core
Datei auf VM: `/etc/nginx/sites-enabled/gateway`
```nginx
server {
listen 80;
server_name api-int.poweron.swiss;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name api-int.poweron.swiss;
ssl_certificate /etc/letsencrypt/live/api-int.poweron.swiss/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api-int.poweron.swiss/privkey.pem;
client_max_body_size 0;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_request_buffering off;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}
}
```
## Einrichtung auf neuer VM
```bash
# 1. Nginx installieren
sudo apt update && sudo apt install -y nginx
# 2. Site-Config anlegen
sudo nano /etc/nginx/sites-enabled/gateway
# (Inhalt von oben einfuegen)
# 3. Default-Site entfernen
sudo rm -f /etc/nginx/sites-enabled/default
# 4. nginx.conf: client_max_body_size setzen
# In /etc/nginx/nginx.conf im http-Block einfuegen:
# client_max_body_size 0;
# 5. SSL-Zertifikat holen
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d api-int.poweron.swiss
# 6. Config testen + laden
sudo nginx -t && sudo systemctl reload nginx
# 7. Auto-Renewal pruefen
sudo certbot renew --dry-run
```

View file

@ -1,187 +0,0 @@
<!-- status: build -->
<!-- started: 2026-05-17 -->
<!-- completed: 2026-05-17 -->
<!-- component: gateway | frontend-nyla -->
# UDB DataSource Settings (⚙️) + konfigurierbare RAG-Limits
> **Implementierungsstand 2026-05-17:** S0S11 abgeschlossen. Wichtige Abweichung
> vom ursprünglichen Plan: das "lazy-fill defaults beim ersten Bootstrap"
> wurde **nicht** im Walker implementiert (würde die Caller-Override-Semantik
> aushebeln und liess Tests rot werden). Stattdessen: `_ragLimits` exponiert
> zwei klare APIs — `getStoredOverrides()` (nur explizite Overrides, für Walker)
> und `getRagLimits()` (Overrides + Defaults gemerged, für API/Cost-Estimate).
> Das Modal zeigt fehlende Werte als Placeholder aus dem Cost-Estimate-Endpoint
> an, damit der User immer eine konkrete Zahl sieht — ohne die Datenbank-Zeile
> dafür anfassen zu müssen. Siehe CHANGELOG 2026-05-17 für die Detail-Liste.
## Beschreibung und Kontext
Heute sind die RAG-Walker-Limits (`maxBytes`, `maxItems`, `maxFileSize`, `maxDepth`, `maxWorkspaces`, `maxListsPerWorkspace`, `maxTasks`) als Modul-Konstanten in den `subConnectorSync*.py`-Dateien hartkodiert. Ein User mit z. B. einem 500 MB grossen SharePoint-Folder erhält nach 200 MB ein stilles Stoppen (`stoppedAtLimit=maxBytes`) und kann das ohne Code-Änderung nicht anpassen. Heute (2026-05-17) sichtbar geworden auf einem Basecamp-Folder mit ~731 Dateien, von denen nur 25 indexiert wurden, bevor der `maxBytes`-Default zugriff.
Gleichzeitig fehlt für Feature-Instanz-DataSources (`FeatureDataSource`) bisher überhaupt ein UI, an dem der Owner Settings anpassen könnte. UserConnections haben den Wizard und das Edit-Modal auf der Connections-Seite für Feature-Daten gibt es keinen analogen Ort.
Beide Lücken lassen sich mit derselben Architektur schliessen: **ein generisches Settings-Icon (⚙️) pro Node in der UDB**, das ein typ- und scope-abhängiges Modal öffnet. Initial wird darin RAG-Limit-Werte und Connection-Master-Switch gepflegt, später kommen weitere Einstellungen (z. B. Custom-Neutralization-Regeln, Re-Sync-Cadence, Polling-Strategie) ohne neue Icons dazu.
Wichtiger Architektur-Constraint:
- **Keine Icon-Inflation in der UDB.** Bereits vorhanden: Scope-Toggle, Neutralize-Toggle, RAG-Index-Toggle (🧠), Chat (💬). Wir fügen genau **ein** zusätzliches Icon (⚙️) hinzu nicht ein Icon pro Setting.
## Fokus und kritische Details
- UDB ist die **einzige** Stelle, wo Settings pro Datenobjekt gemanagt werden. Andere Pfade (Wizard, Connection-Edit-Modal) editieren nur die Connection-Ebene, nie einzelne DataSources.
- **Speicherort = Quelle der Wahrheit. Keine Override-Schichten, keine Resolver-Logik.** Was im UI angezeigt wird, ist exakt das, was im Walker greift. Die Werte werden bei der DataSource-Erstellung mit sinnvollen Defaults vorbefüllt (kopiert aus globalen Konstanten); danach kann der User sie einfach editieren. Vorteil: nachvollziehbar, debuggbar, keine versteckten Vererbungen.
- Walker müssen ihre Default-Konstanten zentralisieren und beim Lesen einer DataSource entweder den dort gespeicherten Wert nehmen oder, falls noch nicht gesetzt, den zentralen Default ausliefern und gleichzeitig auf der DataSource persistieren (lazy initialization → ab dann sind die Werte sichtbar im UI).
- Settings-Werte landen in einer **JSON-Spalte** auf der DataSource bzw. FeatureDataSource (`settings: Optional[Dict[str, Any]]`) damit ist die Erweiterbarkeit garantiert, ohne neue Spalten/Migrationen pro neuem Setting.
- DataSource vs. FeatureDataSource: gleiche Spalte (`settings`), gleiches UI, gleiches API-Pattern (`PATCH /api/datasources/{id}/settings` und `PATCH /api/feature-data-sources/{id}/settings`).
- Audit-Log-Pflicht: jede Änderung an `settings` schreibt einen `AuditCategory.PERMISSION`-Eintrag (auch wenn es nur ein numerischer Limit-Wert ist RAG-Ingest-Grenze ist eine Compliance-relevante Entscheidung).
- Defaults bleiben **konservativ** (200 MB / 10'000 Items / 25 MB / Tiefe 8). Anheben braucht User-Aktion → keine versehentlichen Kostenexplosionen.
- **Connection-Master-Switch im Modal:** Im Settings-Modal einer DataSource ist auch der Connection-weite Master-Switch `knowledgeIngestionEnabled` ("Knowledge database active") togglebar damit ist das Modal die zentrale Stelle für alle relevanten Settings, ohne zwischen Seiten wechseln zu müssen. Dieser Toggle wirkt auf alle DataSources derselben Connection (es ist explizit als "Connection-Setting" gekennzeichnet, nicht als DataSource-Setting).
- **Kosten-Indikator statt Hard-Cap:** Keine harten Obergrenzen. Stattdessen zeigt das Modal eine indikative (nicht-verbindliche) Realtime-Kostenschätzung: basierend auf erwarteter Item-Zahl, Token-Schätzung pro Item und aktuellen Embedding-Preisen. Klar als "indikativ" gekennzeichnet, der User trägt die Verantwortung.
## Ziel und Nicht-Ziele
- **Ziel:**
- Ein Settings-Icon ⚙️ pro Tree-Node in der UDB-Sidebar (`SourcesTab.tsx`), das ein Modal öffnet.
- Im Modal: drei klar abgegrenzte Sektionen:
1. **Connection** (Master-Switch `knowledgeIngestionEnabled`, gemeinsame Mail-/ClickUp-Preferences aus `knowledgePreferences`).
2. **DataSource RAG-Limits** (`maxBytes`, `maxFileSize`, `maxItems`, `maxDepth`).
3. **Kostenschätzung** (indikativer Wert mit Hinweis).
- Backend: `DataSource.settings` und `FeatureDataSource.settings` als JSON-Spalte. Endpunkte zum Lesen/Patchen.
- Walker (`subConnectorSyncSharepoint.py`, `subConnectorSyncKdrive.py`, `subConnectorSyncGdrive.py`, `subConnectorSyncClickup.py`) lesen Limits direkt aus `DataSource.settings.ragLimits`, fallen auf zentrale Defaults zurück, wenn (noch) leer.
- Partial-Banner auf `RagInventoryPage` zeigt zusätzlich Hint: "Limit kann pro DataSource in der UDB unter ⚙️ angehoben werden."
- Owner-Kontrolle: für UserConnection-DataSources nur Owner; für FeatureDataSource Owner oder `workspace-admin`.
- **Nicht-Ziel:**
- Mandate-weite Defaults / Override-Schichten / Resolver-Layer.
- Hard-Caps (User/Admin trägt Verantwortung).
- Settings-Vererbung im Tree (Parent-Folder → Children) aktuell wirkt eine Änderung nur auf die konkrete DataSource.
- Eigene Settings für Mail-Connectors (`Outlook`, `Gmail`) auf DataSource-Ebene die haben keine Folder-Hierarchie. Die Mail-Preferences bleiben Connection-weit und werden im Modal in der "Connection"-Sektion editiert.
## Architektur-Skizze
### Daten-Schicht
`DataSource` und `FeatureDataSource` bekommen je eine neue Spalte:
```python
settings: Optional[Dict[str, Any]] = Field(
default=None,
description="DataSource-scoped settings (JSON). Currently used keys: ragLimits.",
json_schema_extra={"frontend_type": "json", "frontend_readonly": True, "frontend_required": False},
)
```
JSON-Schema-Konvention:
```json
{
"ragLimits": {
"maxBytes": 524288000,
"maxFileSize": 52428800,
"maxItems": 20000,
"maxDepth": 12
}
}
```
**Keine Resolver-Schichten**: was in `settings.ragLimits` steht, gilt. Wenn der Key fehlt, nimmt der Walker den zentralen Default aus `_ragLimits.RAG_LIMITS_DEFAULT` und schreibt ihn beim nächsten Bootstrap **einmalig** in die DataSource zurück (lazy fill), damit der User die Werte auch ohne vorherigen Sync schon im UI sieht und editieren kann.
### Backend-API
```
PATCH /api/datasources/{id}/settings { settings: { ragLimits: {...} } }
PATCH /api/feature-data-sources/{id}/settings { settings: { ragLimits: {...} } }
GET /api/datasources/{id}/cost-estimate → { estimatedTokens, estimatedChf, basis: {...} }
```
`/cost-estimate`: schätzt anhand Item-Count (sofern bekannt aus letztem Sync) × tokens-per-item-heuristik × Embedding-Preis. Liefert auch die Annahmen (`basis`), damit der User die Plausibilität prüfen kann.
### Walker-Refactor
Heute hartkodiert in jedem Walker:
```python
MAX_BYTES_DEFAULT = 200 * 1024 * 1024
MAX_ITEMS_DEFAULT = 10_000
```
Wird zu:
```python
# gateway/modules/serviceCenter/services/serviceKnowledge/_ragLimits.py
RAG_LIMITS_DEFAULT = {
"maxBytes": 200 * 1024 * 1024,
"maxFileSize": 25 * 1024 * 1024,
"maxItems": 10_000,
"maxDepth": 8,
}
def getRagLimits(dataSource: Dict[str, Any]) -> Dict[str, int]:
"""Read limits from DataSource.settings.ragLimits, fall back to defaults
for missing keys. Pure read; lazy persist is the caller's responsibility."""
stored = (dataSource.get("settings") or {}).get("ragLimits") or {}
return {**RAG_LIMITS_DEFAULT, **stored}
def lazyFillRagLimits(rootIf, dataSourceId: str, dataSource: Dict[str, Any]) -> None:
"""Persist defaults to settings if not yet present, so the UI shows them."""
```
Jeder Walker holt seine Limits beim Eintritt in `_bootstrap*` einmal und gibt sie an `_walkFolder()` / `_finalizeResult()` weiter die Konstante existiert nur noch im neuen Modul als Default-Fallback.
### Frontend-UI
In `SourcesTab.tsx` (Tree-Node-Render), zwischen Brain-Icon (🧠) und Chat-Icon (💬):
```typescript
<button
onClick={async (e) => {
e.stopPropagation();
const dsId = ds?.id ?? await onEnsureDs(node);
if (dsId) openSettingsModal(dsId, scope);
}}
title={t('Einstellungen')}
style={{ ... }}
>
⚙️
</button>
```
Settings-Modal (`DataSourceSettingsModal.tsx`):
- **Sektion "Connection"** (oben, mit Connection-Label und Authority-Icon):
- Toggle `knowledgeIngestionEnabled` (Master-Switch). Hinweis: wirkt auf alle DataSources dieser Connection.
- Optional, wenn relevant: `mailContentDepth`, `mailIndexAttachments`, `filesIndexBinaries`, `clickupScope`, `clickupIndexAttachments`, `maxAgeDays` (aus `knowledgePreferences`).
- PATCH → `/api/connections/{id}/knowledge-consent` bzw. `/api/connections/{id}/knowledge-preferences`.
- **Sektion "RAG-Limits"** (nur wenn DataSource den Walker-Typ unterstützt):
- Felder `maxBytes`, `maxFileSize`, `maxItems`, `maxDepth` mit lesbaren Units (MB, Anzahl).
- PATCH → `/api/datasources/{id}/settings`.
- **Sektion "Kostenschätzung"** (read-only):
- "Indikative Kosten: ~X CHF pro Voll-Sync" (mit Basis-Tooltip).
- GET → `/api/datasources/{id}/cost-estimate` beim Modal-Öffnen.
Auf der `RagInventoryPage`-Partial-Banner-Komponente:
```typescript
<span>... Limit {l} erreicht. Weitere Dateien wurden NICHT indexiert. {' '}
<a onClick={() => openSettingsModalForConn(conn)}>Limit anpassen</a> oder
DataSource enger eingrenzen, dann erneut starten.</span>
```
## Erfolgskriterien
- Auf einer SharePoint-DataSource mit > 200 MB kann der Owner über das ⚙️-Icon `maxBytes` auf z. B. 1 GB anheben, einen Re-Index starten und sieht im Inventory > 200 MB indexierte Bytes ohne `stoppedAtLimit`-Banner.
- Auf einer FeatureDataSource ist das gleiche Settings-Modal verfügbar und speichert sauber in `FeatureDataSource.settings`.
- Wenn `settings.ragLimits` leer ist, ist das Verhalten der Walker **bitidentisch** zur Vor-Plan-Version (keine Regression).
- Der Connection-Master-Switch im Modal und der Toggle auf der Connections-Page und auf der RagInventoryPage zeigen immer denselben Wert (alle drei rufen `/knowledge-consent`).
- Audit-Log enthält pro Settings-Change einen `PERMISSION`-Eintrag mit `dataSourceId`, `userId`, `mandateId`, `oldSettings`, `newSettings`.
- Keine zusätzlichen Icons in der UDB ausser dem einen ⚙️.
- Kostenschätzung wird als "indikativ, nicht verbindlich" gekennzeichnet und zeigt die zugrunde liegenden Annahmen.
## Schritte
- [ ] S1 — Datenmodell: `settings: JSON` an `DataSource` und `FeatureDataSource` ergänzen (`gateway/modules/datamodels/datamodelDataSource.py`, `datamodelFeatureDataSource.py`); SQL-Migration in `gateway/scripts/script_db_migrate_datasource_settings.py`.
- [ ] S2 — Zentralisierte Defaults: `gateway/modules/serviceCenter/services/serviceKnowledge/_ragLimits.py` neu mit `RAG_LIMITS_DEFAULT`, `getRagLimits()`, `lazyFillRagLimits()`.
- [ ] S3 — Walker-Refactor: `subConnectorSyncSharepoint.py`, `subConnectorSyncKdrive.py`, `subConnectorSyncGdrive.py`, `subConnectorSyncClickup.py` lesen Limits via `getRagLimits()` und schreiben Defaults via `lazyFillRagLimits()` zurück.
- [ ] S4 — Backend-Endpunkte: `PATCH /api/datasources/{id}/settings`, `PATCH /api/feature-data-sources/{id}/settings`, `GET /api/datasources/{id}/cost-estimate` in `routeDataSources.py` (und `routeFeatureDataSources.py`, falls noch nicht da). RBAC: Owner für DataSource, Owner + `workspace-admin` für FeatureDataSource.
- [ ] S5 — Frontend ⚙️-Button in `SourcesTab.tsx` (Tree-Row, zwischen 🧠 und 💬), Opacity-Logik: voll sichtbar, wenn `settings` befüllt sind, sonst gedimmt.
- [ ] S6 — Frontend `DataSourceSettingsModal.tsx`: drei Sektionen (Connection / RAG-Limits / Kostenschätzung), per-Feld-Validation, Speichern via API.
- [ ] S7 — `RagInventoryPage.tsx` Partial-Banner: Link "Limit anpassen" öffnet Modal für die betroffene DataSource (Heuristik: DS, die `stoppedAtLimit` ausgelöst hat bei mehreren: die mit den meisten verarbeiteten Bytes).
- [ ] S8 — Audit-Logging in den neuen Settings-Endpunkten.
- [ ] S9 — Cost-Estimate-Engine: `gateway/modules/serviceCenter/services/serviceKnowledge/_costEstimate.py` mit Heuristik (Items × Tokens × Embedding-Preis) und Basis-Annahmen-Output.
- [ ] S10 — Tests: Unit-Tests für `getRagLimits()` / `lazyFillRagLimits()`, API-Tests für PATCH-Endpunkte (RBAC), Cost-Estimate-Unit-Test, Frontend-Smoke-Test (Modal öffnet, speichert, refetch, Cost-Anzeige).
- [ ] S11 — Doku: `wiki/b-reference/platform/rag-pipeline.md` Abschnitt "Limits & Settings" ergänzen, plus Eintrag in `wiki/TOPICS.md`. CHANGELOG-Zeile.
## Offene Fragen / Decisions
- **F1 Override-Logik?****Entschieden: KEINE Override-Schichten.** Die DataSource speichert ihre eigenen Werte direkt; Walker liest sie 1:1. Lazy-Fill mit zentralen Defaults beim ersten Sync, damit das UI immer einen sinnvollen Startwert zeigt. Nachvollziehbar, debuggbar, keine versteckten Vererbungen.
- **F2 RBAC?****Entschieden:** Owner für UserConnection-DataSources, Owner oder `workspace-admin` für FeatureDataSource (analog Scope-/Neutralize-Toggle).
- **F3 Hard-Cap pro Mandat?****Entschieden: NEIN.** Stattdessen indikative Realtime-Kostenschätzung im Modal. User/Admin trägt Verantwortung.
- **F4 Mail/ClickUp-Preferences im Modal?****Entschieden: JA.** Connection-weite Preferences werden in der Sektion "Connection" des Modals editiert das macht das Modal zur einzigen Stelle für alle relevanten Settings, ohne Seitenwechsel.

View file

@ -0,0 +1,98 @@
<!-- status: done -->
<!-- started: 2026-05-25 -->
<!-- component: platform -->
# INT-Umgebung auf Infomaniak migrieren
## Beschreibung und Kontext
Migration der `poweron-swiss` INT-Umgebung (platform-core, ui-nyla) von Azure auf Infomaniak Public Cloud.
Betrifft nur `poweron-swiss`-Repos. Die `poweron`-Repos (gateway, frontend_nyla) bleiben auf Azure.
## Checkliste
### Env-Dateien (platform-core, ui-nyla)
- [x] `env-int.env`: APP_API_URL → `https://api-int.poweron.swiss`
- [x] `env-int.env`: DB_HOST → `db-int.poweron.swiss`
- [x] `env-int.env`: DB_USER → `poweron_dev`
- [x] `env-int.env`: OAuth-Redirect-URIs → `api-int.poweron.swiss`
- [x] `env-int.env`: CORS (APP_ALLOWED_ORIGINS) → alle UI-Domains (poweron.swiss + poweron-center.net)
- [x] `env-int.env`: APP_KEY_SYSVAR → `/srv/gateway/shared/secrets/master_key.txt`
- [x] `env-int.env`: APP_LOGGING_LOG_DIR → `srv/gateway/shared/logs`
- [x] `env-int.env`: TEAMSBOT_BROWSER_BOT_URL → `http://teamsbot.poweron.swiss:4100`
- [x] `env-prod.env`: DB_HOST → `db.poweron.swiss`
- [x] `env-prod.env`: CORS aktualisiert
- [x] `env-prod.env`: TEAMSBOT_URL aktualisiert
- [x] `ui-nyla` env-int.env: bereits korrekt (`VITE_API_BASE_URL=https://api-int.poweron.swiss`)
### OAuth-Provider (externe Portale)
- [x] Microsoft Entra: Redirect-URIs auf `api-int.poweron.swiss` aktualisiert
- [x] Google OAuth Console: Redirect-URIs auf `api-int.poweron.swiss` aktualisiert
### DNS
- [x] `db.poweron.swiss` → 10.20.0.21 (porta-main-db)
- [x] `db-int.poweron.swiss` → 10.20.0.175 (porta-int-db)
- [x] `teamsbot.poweron.swiss` → eingerichtet
- [x] `api-int.poweron.swiss` → eingerichtet
### Server: porta-int-db (db-int.poweron.swiss)
SSH: `ssh -i ~/.ssh/ida-laptop.pem ubuntu@37.156.42.67`
- [x] VM läuft, SSH-Verbindung OK
**Schritt 1: Bestand prüfen**
- [x] PostgreSQL-Status prüfen
```
sudo systemctl status postgresql
```
- [x] Falls PostgreSQL läuft: vorhandene User prüfen
```
sudo -u postgres psql -c "\du" --> USER poweron_dev exists
```
- [x] Vorhandene Datenbanken prüfen
```
sudo -u postgres psql -c "\l"
```
- [x] pg_hba.conf prüfen (Zugriff vom internen Subnetz?)
```
sudo cat /etc/postgresql/*/main/pg_hba.conf | grep -v "^#" | grep -v "^$"
```
**Schritt 2: Passwort + Zugriff**
- [x] Passwort für `poweron_dev` gesetzt
- [x] Verbindungstest lokal auf DB-VM OK (`psql -h 127.0.0.1 -U poweron_dev -d postgres -c "SELECT 1;"`)
### Server: porta-int-platform-core (api-int.poweron.swiss)
- [x] Verbindungstest zur DB OK
- [x] Master Key abgelegt (`/srv/gateway/shared/secrets/master_key.txt`)
- [x] Log-Verzeichnis vorhanden
- [x] App-Verzeichnis + Git-Repo vorhanden
- [x] Python venv vorhanden
- [x] systemd-Service `gateway` konfiguriert
- [x] Nginx: `client_max_body_size 0`, WebSocket-Upgrade, Timeouts 600s, `proxy_request_buffering off` (analog PROD)
- [x] SSL: Let's Encrypt Zertifikat fuer `api-int.poweron.swiss` aktiv
### DB-Passwort verschlüsseln
- [x] Passwort in `env-int.env` eingetragen und verschlüsselt (`DB_PASSWORD_SECRET = INT_ENC:...`)
### Forgejo CI/CD
- [x] Workflow `int_porta-int-platform-core.yml` geprüft — zeigt korrekt auf `api-int.poweron.swiss`, Branch `int`, Pfad `/srv/gateway/current`
### Wiki
- [x] DNS-Einträge für DB-Server in `infrastructure.md` nachgetragen
- [x] CHANGELOG aktualisiert
## Notizen
- SSH auf porta-int-db geht nur über öffentliche IP `37.156.42.67` (DNS `db-int.poweron.swiss` zeigt auf interne IP 10.20.0.175, korrekt für App-Zugriff)
- SSH auf porta-main-db: `37.156.40.141`