Implement manual versioned Synology deployments for S6
All checks were successful
Build, test and publish / verify (push) Successful in 6m34s
All checks were successful
Build, test and publish / verify (push) Successful in 6m34s
This commit is contained in:
281
docs/production-deployment.md
Normal file
281
docs/production-deployment.md
Normal file
@@ -0,0 +1,281 @@
|
||||
# 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.
|
||||
|
||||
**Tot S16 is geïmplementeerd kan iedere deployment, retry, rollback of automatisch
|
||||
herstel alle applicatiegegevens wissen.** Informeer de launching partner vooraf.
|
||||
Een onderbroken berekening moet opnieuw worden gestart. Een herstelde
|
||||
applicatieversie herstelt geen verloren gegevens.
|
||||
|
||||
## 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`.
|
||||
|
||||
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
|
||||
|
||||
S16 blokkeert S6 niet. Zodra S16 is ingevoerd, moeten database/volumes buiten de
|
||||
vervangbare containers blijven en saved data zowel deployment als rollback
|
||||
overleven. De gewone procedure vereist dan databasecompatibiliteit met de vorige
|
||||
applicatieversie. Plan incompatibele migraties apart: imageherstel maakt geen
|
||||
migratie ongedaan. 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.
|
||||
Reference in New Issue
Block a user