Files
agiledancers/docs/production-deployment.md
Wanja Krah 694bf3fa86
All checks were successful
Build, test and publish / verify (push) Successful in 7m31s
S16 postgresql opslag added
2026-11-04 17:10:25 +01:00

306 lines
16 KiB
Markdown

# Productie uitrollen op de Synology (S6)
De buildworkflow bouwt, test en publiceert automatisch. Productie verandert
alleen via **Actions → Deploy production → Run workflow** of **Rollback
production**. Start beide vanaf `main`; de workflows verschijnen na de merge.
**Voor de eerste S16-deployment: exporteer alle scholen voordat de oude backend
stopt.** De oude release bewaart gegevens alleen in geheugen. Configureer ook
`DATABASE_PASSWORD` in `production.env`. Zie [database-inrichting en overgang](persistent-storage.md).
Nieuwe releases gebruiken een apart PostgreSQL-volume; verwijder dit volume niet.
Onderbroken berekeningen worden als Interrupted hersteld en starten niet vanzelf.
## Eenmalige DSM-inrichting
Gebruik je bestaande beheeraccount voor deze stappen:
1. Maak onder **Configuratiescherm → Gebruiker en groep → Gebruiker → Maken**
het account `agiledancers-deploy` met een uniek sterk wachtwoord. Voeg het toe
aan **administrators**: DSM vereist deze groep voor SSH. Het is daardoor een
beheerder, geen beperkt bestandsaccount.
2. Bij **Gedeelde mapmachtigingen**: `docker` op **Lezen/Schrijven**, `homes`
ongewijzigd, overige mappen op **Geen toegang**. `git` is niet nodig: deployment
gebruikt de Gitea-API en registry. Blokkeer `homes` niet, omdat dit ook de eigen
thuismap kan blokkeren.
3. De toepassingen onder **Toegangsmachtigingen toewijzen** zijn niet nodig en
mogen op **Weigeren**. Gebruik je eigen beheeraccount voor DSM. Deployment
gebruikt SSH-shellopdrachten, geen SFTP, rsync of bestandsdeling.
4. Schakel onder **Gebruiker en groep → Geavanceerd** de
**gebruikersbasismapservice** in als deze uitstaat.
5. Schakel **Terminal en SNMP → Terminal → SSH-service inschakelen** in. De
maintainer heeft SSH-poort **22** bevestigd. Beperk toegang tot mini-pc/LAN/VPN;
configureer geen internet-portforwarding.
6. Controleer dat Container Manager/Docker draait, dat Docker Compose **v2**,
`curl`, `timeout` met `-k` en `sh` beschikbaar zijn. Op de NAS is geen Java,
Python of Git nodig. Controleer het Docker-pad, doorgaans `/usr/local/bin/docker`.
Map- en toepassingsrechten vormen geen beveiligde isolatie voor een account met
beheerder-/Dockerrechten. De runner voert ook vertrouwde repositorycode uit.
Geef alleen vertrouwde beheerders schrijftoegang tot workflows en credentials.
Bronnen: [SSH op DSM](https://kb.synology.com/en-au/DSM/help/DSM/AdminCenter/system_terminal),
[sleutelaanmelding en basismappen](https://kb.synology.com/en-us/DSM/tutorial/How_to_log_in_to_DSM_with_key_pairs_as_admin_or_root_permission_via_SSH_on_computers),
[maprechten](https://kb.synology.com/nl-nl/DSM/help/DSM/AdminCenter/file_share_privilege?version=7).
## SSH-sleutel en hostkey
Maak op de mini-pc een aparte sleutel buiten de repository. De lege passphrase
maakt niet-interactieve uitvoering mogelijk; behandel de private sleutel als secret.
```sh
ssh-keygen -t ed25519 -f "$HOME/.config/agiledancers-runner/s6-deploy-key" -N '' -C agiledancers-production
ssh-copy-id -i "$HOME/.config/agiledancers-runner/s6-deploy-key.pub" -p 22 agiledancers-deploy@192.168.2.28
```
Genereer niet opnieuw als de sleutel al bestaat. De implementatiesessie heeft
deze dedicated sleutel op de mini-pc aangemaakt. De gebruiker installeert de
publieke sleutel via een eigen interactieve terminal; deel geen wachtwoorden.
Controleer de serverfingerprint via een reeds vertrouwde beheerverbinding, tegen
`/etc/ssh/ssh_host_ed25519_key.pub`. Een keyscan alleen bewijst de serveridentiteit
niet. Bewaar de geverifieerde known_hosts-regel; bij een andere poort gebruikt
die `[host]:poort`. De helper vereist `StrictHostKeyChecking=yes`, gebruikt alleen
de dedicated sleutel en negeert SSH-agent en lokale SSH-configuratie.
Op de NAS: eigen `~/.ssh` mode `700`, `authorized_keys` mode `600`, juiste eigenaar,
thuismap niet schrijfbaar voor andere gebruikers. De public-keyregel kan worden
beperkt met de werkelijke vaste mini-pc-IP en zonder forwarding/TTY:
```text
from="<mini-pc-LAN-IP>",no-agent-forwarding,no-port-forwarding,no-X11-forwarding,no-pty ssh-ed25519 <public-key> agiledancers-production
```
Als de sleutel na `ssh-copy-id` nog wordt geweigerd door ruime DSM-basismaprechten,
voer dan eenmaal met wachtwoordauthenticatie uit als dit account:
```sh
chmod go-w "$HOME"
chmod 700 "$HOME/.ssh"
chmod 600 "$HOME/.ssh/authorized_keys"
```
## Productiemap en Dockerrechten
Controleer het volume: `/volume1/docker/agiledancers-production` is een voorbeeld,
geen vastgestelde locatie. Maak de map als deploymentaccount, mode `700`, met
dat account als eigenaar. Houd deze stack gescheiden van Gitea en andere projecten.
Kopieer `deploy/production.env.example` naar `<productiemap>/production.env`, mode
`600`. Stel de private NAS-IP, vrije frontendpoort (voorbeeld `8088`), absolute
Docker-locatie en `DOCKER_CONFIG` in. Gebruik een eigen `docker-auth`-submap.
Alleen de frontend bindt aan de private IPv4; de backend krijgt geen hostpoort.
De Docker-projectnaam is vast `agiledancers-production`.
### DSM-firewall
De bevestigde productieomgeving gebruikt frontendpoort **8088** op
`192.168.2.28` en Docker-subnet **172.18.0.0/16**. Maak in
**Configuratiescherm → Beveiliging → Firewall → Regels bewerken** toestaan-regels
op **Alle interfaces**, boven eventuele weigerregels:
| Poort | Protocol | Bron | Actie |
| --- | --- | --- | --- |
| 8088 | TCP | `192.168.2.0`, masker `255.255.255.0` | Toestaan |
| 8080 | TCP | `172.18.0.0`, masker `255.255.0.0` | Toestaan |
De eerste regel geeft LAN-clients toegang; de tweede laat de frontend de backend
bereiken. Voeg voor bestaande VPN-clients zo nodig hun eigen bron-subnet toe
aan de frontendregel. Er wordt geen backendpoort op de NAS gepubliceerd en de
firewall blijft aan. Controleer bij het opnieuw aanmaken van het Docker-netwerk
het werkelijke subnet met `docker network inspect` en pas de regel aan als dat
subnet wijzigt. Gewone deployments vervangen containers en behouden het netwerk.
De tijdelijke S6-acceptatieregel voor `172.20.0.0/16` mag na de geïsoleerde tests
weg; dat testnetwerk en de testcontainers zijn opgeruimd.
Als Docker sudo vereist, laat een beheerder met `visudo` een afzonderlijke
sudoers-regel installeren en valideren, met het gecontroleerde Docker-pad:
```text
agiledancers-deploy ALL=(root) NOPASSWD: /usr/local/bin/docker
```
Dit staat alleen dat executable toe, maar Dockerbeheer geeft in de praktijk
rootmogelijkheden. Gebruik geen `NOPASSWD: ALL`, wereldschrijfbare Docker-socket
of wachtwoord in de workflow. De helper gebruikt `sudo -n`. Als Docker al direct
toegankelijk is voor het account, stel `DOCKER_SUDO=0` in.
Provision apart een **pull-only** Gitea-packagecredential. Log als deploymentaccount
op de NAS in, via de interactieve wachtwoordprompt:
```sh
mkdir -p /volume1/docker/agiledancers-production/docker-auth
chmod 700 /volume1/docker/agiledancers-production/docker-auth
sudo -n /usr/local/bin/docker --config /volume1/docker/agiledancers-production/docker-auth login 127.0.0.1:3000 --username <pull-account>
sudo -n /usr/local/bin/docker compose version
sudo -n /usr/local/bin/docker info --format '{{.OSType}}/{{.Architecture}}'
```
Als DSM geen `visudo` bevat, is `deploy/enable-docker.sh` het eenmalige alternatief.
Kopieer het naar de eigen NAS-thuismap en voer het daar met sudo uit. Het maakt
uitsluitend de bovengenoemde regel, controleert die door als deploymentaccount
Docker te starten en verwijdert een nieuwe regel als die controle faalt. Een
afwijkende bestaande regel wordt niet overschreven. De implementatiesessie heeft
deze stap op de NAS uitgevoerd; Docker rapporteert `linux/x86_64`.
Pas paden aan voor de echte configuratie. De bevestigde NAS-Dockerdaemon staat
HTTP-registryverkeer alleen op loopback toe. Gebruik daarom vanaf die NAS
`127.0.0.1:3000`: dezelfde Gitea-registry en gepubliceerde SHA-images, zonder een
daemonherstart die Gitea en Kimai zou onderbreken. De mini-pc blijft naar
`192.168.2.28:3000` publiceren. Deploymentrecords bevatten de op de NAS bruikbare
loopback-digestreferenties. Authenticatie-/netwerkfouten zijn deploymentfouten.
## Gitea-configuratie
Stel onder repository **Settings → Actions** in:
| Soort | Naam | Inhoud |
| --- | --- | --- |
| Variabele | `DEPLOY_HOST` | Private NAS-IP/hostnaam, passend bij known_hosts |
| Variabele | `DEPLOY_USER` | `agiledancers-deploy` |
| Variabele | `DEPLOY_SSH_PORT` | `22` |
| Variabele | `DEPLOY_DIRECTORY` | Absolute productiemap |
| Variabele | `DEPLOY_REGISTRY` | `127.0.0.1:3000`, de NAS-lokale alias van dezelfde registry |
| Secret | `DEPLOY_SSH_KEY` | Volledige private sleutel, inclusief begin/eindregels |
| Secret | `DEPLOY_KNOWN_HOSTS` | Onafhankelijk geverifieerde NAS-hostkey-regel |
| Secret | `RELEASE_TOKEN` | Apart Gitea-token voor Actions lezen en Git-tags maken |
Het releaseaccount heeft repositorylees-/schrijfrechten en Actions-metadatatoegang
nodig (`write:repository` op deze Gitea). Het NAS-registrycredential is apart en
heeft alleen de noodzakelijke package-leestoegang. Hergebruik de publicatie-PAT
niet voor deployment. Bewaar secrets buiten Git en logs.
De setuphelper kan deze configuratie en de productiemap zonder containerstart
inrichten. Bewaar `admin-token`, `s6-release-token`, `s6-pull-token` en de private
`s6-deploy-key` als owner-only bestanden (mode `600`) in
`~/.config/agiledancers-runner/`. Gebruik de eerder vertrouwde NAS-hostkey in
`~/.ssh/known_hosts` en voer uit:
```sh
java ci/Ci.java configure-deployment
```
Defaults zijn de bevestigde NAS/account/SSH-poort, repository
`wanjakrah/agiledancers`, productiemap `/volume1/docker/agiledancers-production`,
NAS-IP-binding `192.168.2.28` en frontendpoort `8088`. De helper controleert een
geslaagde main-build, weigert een bezette poort bij eerste inrichting, bewaart
bestaande productieconfiguratie en controleert de aparte registry-login voordat
Gitea-secrets/variabelen worden gezet. Er wordt geen release/tag gemaakt en geen
container vervangen. Variabelen met de namen uit bovenstaande tabel en
`DEPLOY_BIND_IP`, `DEPLOY_FRONTEND_PORT`, `PULL_USER` en de credential-`*_FILE`-
paden kunnen de defaults expliciet vervangen.
Bescherm release-tags `v*` in Gitea en beperk tagcreatie tot de releasebeheerder.
De helper maakt alleen nieuwe tags; hij verplaatst of verwijdert ze nooit. Laat
registry-cleanup de images van huidig/vorig niet verwijderen. Gebruik geen
algemene Docker-image/volume-prune als onderhoud van deze stack.
## Nieuwe release, retry en rollback
1. Merge naar `main` en wacht op een geslaagde **Build, test and publish** voor
die SHA, inclusief imagepublicatie.
2. Open **Deploy production → Run workflow**, branch `main`, mode `new`, keuze
`patch`, `minor` of `major`. Laat het versieveld leeg. De eerste release is
`v1.0.0`; daarna telt de hoogste bestaande release-tag, inclusief mislukte
deployments. Minor/major zetten de lagere componenten op nul.
3. De workflow toont versie/SHA. De immutable annotated Git-tag wordt na
imagevoorbereiding, vóór vervanging gemaakt en blijft bij falen bestaan.
Een tag bewijst geen succesvolle productiedeployment.
4. Gebruik mode `retry` met een bestaande tag om dezelfde SHA/versie opnieuw uit
te rollen. De incrementkeuze wordt genegeerd: Gitea toont alle velden, ook
wanneer ze voor de gekozen mode niet relevant zijn. Een retry mag afwijken
van huidig main, maar geen nieuwere succesvolle release verdringen.
5. **Rollback production → Run workflow → main** herstelt de vorige succesvolle
release zonder versie/tag. Na succes wisselen huidig/vorig om; nogmaals
rollback kan dus terugschakelen. De hoogste succesvolle versie blijft bewaard.
De NAS-lock omvat alle modes en versieallocatie. Een gelijktijdige run faalt
zichtbaar. Gitea 1.25 negeert workflow-`concurrency`; de serverlock is leidend.
Main wordt direct vóór vervanging opnieuw gecontroleerd. Zodra vervanging start,
wordt de gepinde versie afgemaakt of hersteld. Branchbuilds rollen niets uit.
## Checks en operationele state
De helper haalt beide SHA-images terug, controleert revisie/platform en legt
registry-digests vast. Compose gebruikt die digests, nooit de bewegende main-tag.
De recovery-images worden eveneens vooraf gecontroleerd. Binnen vijf minuten
moeten backend-readiness, frontendpagina, GET `/api/dance-schools` via de
gepubliceerde frontendpoort en containeridentiteit slagen. Deze checks schrijven
geen gebruikersdata. De frontend herstart ook om de nieuwe backend-IP te resolven.
De productiemap bevat:
- `state.json`: atomisch geschreven huidig/vorig en hoogste succesvolle versie.
Redeployment van dezelfde versie behoudt de afzonderlijke rollbackversie.
- `releases/<attempt>/`: behouden Compose- en digest-/versieconfiguratie.
- `pending.json`: kandidaat en vorige state, vóór iedere vervanging geschreven.
- `deploy.lock/owner`: unieke run/poging; `operation/` markeert een actieve
vervanging op de NAS.
- `history/`: pogingjournalen en uitkomsten; niet opruimen tijdens een release.
Een ongezonde kandidaat herstelt automatisch de vorige succesvolle release en
blijft een mislukte workflow. Bij de eerste release zonder voorganger, mislukt
herstel, runner/SSH-onderbreking of onduidelijke statecommit blijven journal en
lock staan. Nieuwe runs weigeren dan wijzigingen. Remote opdrachten hebben ook
op de NAS een timeout; herstel overlapt geen nog actieve vervanging. Geen
eindeloze herstelpogingen.
Download artifact `deployment-<run>-<attempt>` of `rollback-<run>-<attempt>` voor
log, uitkomst, versie/SHA/digests en beschikbare foutdiagnostiek. Retentie is
veertien dagen; serverinstellingen mogen dit niet inkorten. Log geen environment,
Docker-authconfig of volledige container-inspect met eventuele secrets.
## Handmatig herstel
Gebruik een vertrouwde beheerverbinding. Verwijder nooit alleen de lock om een
nieuwe release door te drukken.
1. Annuleer de betrokken Gitea-run. Controleer op de NAS dat geen SSH-deployment,
timeout, Compose-vervanging of herstel van deze poging meer actief is. Onderzoek
`deploy.lock/operation`; wacht op de timeout of beëindig alleen de geverifieerde
vastgelopen deploymentprocessen.
2. Bewaar state, pending journal, history en containerlogs. Vergelijk de kandidaat,
de `before`-state en werkelijke images. De statecommit kan al zijn uitgevoerd
terwijl de runner geen antwoord ontving.
3. Behoud een aantoonbaar gezonde kandidaat met overeenkomende state. Anders:
herstel `before.current` uit het journal met de behouden Compose-/env-bestanden,
dezelfde projectnaam en digest-images. Start geen tweede stack.
4. Voer dezelfde checks uit. Vanaf de checkout op de mini-pc kun je het echte
controlescript uitvoeren met bestaande lockowner en gekozen release-directory:
```sh
ssh -i <private-key-path> -p <ssh-port> <deploy-user>@<nas> \
'timeout -k 5 300 sh -s -- apply <production-directory> <lock-owner> <release-directory-id>' < deploy/remote.sh
```
Gebruik de eerder gepinde hostkey en gecontroleerde waarden uit het journal.
Verwijder een achtergebleven lege `operation/` alleen als stap 1 heeft bewezen
dat de oude operatie gestopt is.
5. Bij herstel van de oude release: herstel ook de volledige `before`-state
atomisch, inclusief high-watermark en rollbackdoel. Bij een mislukte eerste
release: stop de defecte stack en behoud de lege succesvolle state. Verwijder
geen volumes of release-tags.
6. Noteer het herstel in history, archiveer het opgeloste journal en verwijder
daarna pas de lockowner en lege lockdirectory. Start vervolgens een retry.
Laat de blokkade staan als gezondheid of state onduidelijk is.
## Na S16 en lokale verificatie
De databaseservice en het volume `agiledancers-production-database` blijven buiten
de vervanging van backend en frontend. `production.env` levert het databasewachtwoord
ook bij retry en rollback; het staat niet in releasebestanden. De procedure vereist
databasecompatibiliteit met de vorige applicatieversie. Plan incompatibele migraties
apart: imageherstel maakt geen migratie ongedaan. Rollback naar een oude release
van vóór S16 kan de database niet lezen. Backup/restore blijven S17-werk.
```sh
java ci/Ci.java test
java ci/Ci.java release-test
sh -n deploy/remote.sh
mvn -N -Dformat.mode=check exec:exec@format
```
De buildworkflow voert beide Java-testsuites uit vóór publicatie. Release-tests
wijzigen geen productie, registry of tags. Leg echte NAS-/Gitea-bewijzen vast in
`docs/s6-validation.md`; configuratie en lokale tests alleen voltooien S6 niet.