U7 — tells a website builder what they need to know

WEBSITE_WORKSPACE.md, plus the parts of the README that were still
describing the old layout or a manual step the code doesn't need
anymore (the key-ring paragraph, mainly - that one was actively
wrong now, not just stale).
This commit is contained in:
2026-07-28 16:30:19 +02:00
parent 9f4ae475e7
commit 88770c5bd0
6 changed files with 258 additions and 6 deletions
+17 -6
View File
@@ -286,20 +286,26 @@ Op een shared-hosting omgeving (zoals mijnhostingpartner.nl) is er meestal maar
| Pad | Inhoud |
|---|---|
| `/` | De publieke website van de klant — **geen onderdeel van deze repo**, wordt los aangeleverd/gedeployed in `wwwroot/` |
| `/` | De publieke website van de klant, uit `wwwroot/web/`**geen onderdeel van deze repo**. Zie [`WEBSITE_WORKSPACE.md`](WEBSITE_WORKSPACE.md) voor het volledige contract (verplichte structuur, verboden/gereserveerde paden, SPA-fallback, same-origin API-calls, CSP, Umami) |
| `/admin` | De CMS admin-UI (`frontend/`), gebouwd met `base: '/admin/'` en automatisch gekopieerd naar `wwwroot/admin/` bij `dotnet publish` |
| `/api/v1/...` | Deze API |
| `/health` | Infrastructuur-liveness — zie "Health-check endpoint" hieronder |
Beide SPA's krijgen een fallback naar hun eigen `index.html` zodat client-side routes (bijv. `/admin/dashboard`) werken; ontbrekende bestanden (met een extensie, bijv. `/admin/assets/x.js`) blijven gewoon 404'en. Zie `Program.cs` (`MapFallbackToFile`) en `SlpModularCms.Api.csproj` (`BuildAndCopyAdminFrontend`-target).
Ontbreekt `wwwroot/web/` nog (een verse deployment vóórdat een website-workspace er iets in heeft gezet), dan start de applicatie gewoon door en toont `/` een ingebouwde placeholder-pagina — `/admin` en `/api/v1` blijven onverminderd werken.
Voor lokale ontwikkeling verandert er niets: `pnpm dev` blijft op `http://localhost:5173` draaien zonder `/admin`-prefix.
### 1. Build & Publish
Compileer de applicatie voor productie — dit bouwt en kopieert automatisch ook de admin-frontend naar `wwwroot/admin/`:
Publiceren gebeurt via de Gitea Actions-pipeline (`.gitea/workflows/continuous_integration.yaml``deploy-scp.yaml`), niet met een losse handmatige `dotnet publish`-stap: elke push naar `master` deployt automatisch naar test, productie alleen via een expliciete `workflow_dispatch` met de `deploy_production`-vlag aan. De pipeline publiceert framework-dependent voor `linux-arm64` en bouwt daarbij automatisch ook de admin-frontend mee naar `wwwroot/admin/`.
Elke deploy plaatst de nieuwe release in een verse map en wisselt pas daarna atomisch over naar `current` — er is dus nooit een moment waarop de applicatie een halfklare release serveert, en `wwwroot/web/` (de klantsite) staat structureel buiten die verwisselde map, zodat een deploy hem nooit kan raken. Volledige host-setup, rollback-procedure en de exacte deploy-stappen staan in de Operations-documentatie van deze feature, niet hier — dit README beschrijft alleen wat er gebouwd is, niet hoe je het voor het eerst inricht.
Wil je toch lokaal handmatig publiceren (bijv. om de output te inspecteren):
```powershell
dotnet publish src/SlpModularCms.Api -c Release -o ./publish
dotnet publish src/SlpModularCms.Api -c Release -r linux-arm64 --self-contained false -o ./publish
```
Kopieer daarna de publieke website van de klant naar `./publish/wwwroot/` (alles behalve de `admin/`-submap, die blijft ongemoeid).
### 2. Runtime Configuratie
In productie moeten gevoelige instellingen worden doorgegeven via Environment Variables:
@@ -308,9 +314,14 @@ In productie moeten gevoelige instellingen worden doorgegeven via Environment Va
- `JwtSettings__Issuer`
- `JwtSettings__Audience`
- `MasterModule__MasterUrl` — publieke URL van deze master-instantie (alleen relevant als de Master CMS Module actief is)
- `Observability__SentryDsn` — leeg is een normale, ondersteunde staat: Sentry wordt dan overgeslagen en alleen console-logging blijft actief
- `SecurityHeaders__AllowedScriptOrigins__0`, `SecurityHeaders__AllowedConnectOrigins__0`, ... — externe origins die de CSP van `/admin` en `/api/v1` mag toestaan (bijv. de Umami-scripthost en Sentry's ingest-endpoint). Zie `appsettings.json → SecurityHeaders` voor de volledige optiesvorm; een onbekende `PathPolicies`-policy-naam laat de applicatie bij **opstarten** falen, niet pas bij de eerste request
### 2a. Data Protection key ring (Master CMS Module)
De API keys van geregistreerde slaves worden versleuteld opgeslagen met ASP.NET Core Data Protection, standaard met een bestandssysteem-key-store. Voor gecontaineriseerde of multi-instance deployments **moet** een persistente key ring geconfigureerd worden (bijv. `PersistKeysToDbContext` of `PersistKeysToAzureBlobStorage`). Zonder dit worden alle opgeslagen API keys onleesbaar zodra de container herstart, waardoor master↔slave-communicatie stopt totdat instanties opnieuw worden toegevoegd.
De API keys van geregistreerde slaves worden versleuteld opgeslagen met ASP.NET Core Data Protection. De key ring wordt **automatisch** persistent opgeslagen in de database (`PersistKeysToDbContext`, met een expliciete, stabiele applicatie-discriminator) — dit hoeft niet meer apart geconfigureerd te worden. Dit is bewust zo gebouwd omdat de atomische release-switch (zie hierboven) bij elke deploy een nieuwe content-root-map gebruikt: zonder een database-backed key ring zou dat elke keer de key ring weggooien en alle opgeslagen slave-API-keys onleesbaar maken.
### 3. Database
Zorg dat de doeltabel bestaat en de migraties zijn uitgevoerd. In productie kan dit via een CI/CD pipeline worden afgehandeld met `dotnet ef migrations script` of door de applicatie bij startup migraties te laten draaien (indien geconfigureerd).
Migraties op `ApplicationDbContext` (Identity/Core) worden bij het opstarten **automatisch** toegepast (`MigrateCoreDatabase()`), en falen hard (fail-fast) als dat niet lukt — de applicatie start dan bewust niet door. Voor productie-deploys wordt vóór elke deploy een databasebackup genomen; zie de Operations-documentatie van deze feature voor de exacte procedure. Per-module migraties (`Modules.Master`, `Modules.Availability`) worden zoals voorheen automatisch toegepast via `UseModule` (zie "Per-module migraties" hierboven) — daar verandert niets aan.
### 4. Health-check endpoint
`GET /health` geeft alleen **infrastructuur-liveness** aan ("draait het proces en kan het een basale response geven") — géén databasecheck, om te voorkomen dat het endpoint rood kleurt om redenen die niets met "leeft het proces nog" te maken hebben. Dit is expliciet **iets anders** dan `GET /api/v1/Availability/status` (de CMS-eigen aan/uit-schakelaar) of `GET /api/v1/System/capabilities` — een instantie kan gezond zijn en toch bewust uitgeschakeld, en andersom. `/health` staat op de bypass-lijst van `AvailabilityMiddleware`, zodat een uitgeschakelde instantie dit endpoint altijd blijft beantwoorden.