Files
Sluijsens 6c08d0a842
Continuous Integration / config (pull_request) Successful in 10s
Continuous Integration / prepare (pull_request) Successful in 1m33s
Continuous Integration / build-production (pull_request) Skipped
Continuous Integration / build (pull_request) Successful in 2m13s
Continuous Integration / test (pull_request) Successful in 2m3s
Continuous Integration / deploy-test (pull_request) Skipped
Continuous Integration / deploy-production (pull_request) Skipped
Hernoem PI_MAIN_HOST secret naar PI_MAIN_ADDRESS
2026-07-30 13:32:42 +02:00

13 KiB

Deployment Instructions

Overview

Deployment gebeurt via Gitea Actions, opgesplitst in twee bestanden:

  • .gitea/workflows/continuous_integration.yaml — build/test/lint-gate, plus de deploy-test- en deploy-production-jobs.
  • .gitea/workflows/deploy.yaml — herbruikbare workflow die dist/ via SCP naar een omgeving uploadt.

Sinds deze stap wordt er automatisch gedeployed naar een testomgeving: een Raspberry Pi die de site serveert via nginx, bereikbaar achter een tweede Raspberry Pi met een nginx reverse proxy. Een productiedeploy is ook mogelijk, maar bewust niet automatisch — zie "How to Deploy to Production" hieronder.

Pipeline Files

  • continuous_integration.yaml — getriggerd door pull_request (build/test/lint-gate, ongeacht branch), push naar master (build/test/lint-gate + deploy-test), en handmatig via workflow_dispatch (met een optioneel deploy_production-vinkje).
    • Heeft bovenaan een env:-blok met alle aanpasbare waarden op één plek: NODE_VERSION, PNPM_VERSION, ARTIFACT_NAME (dist), ARTIFACT_NAME_PRODUCTION (dist-production), ARTIFACT_PATH (dist/), DEPLOY_ENVIRONMENT (test), DEPLOY_PATH (gelezen uit de Gitea repository variable DEPLOY_PATH_TEST, zie hieronder — bewust geen hardcoded pad), DEPLOY_ENVIRONMENT_PRODUCTION (production) en DEPLOY_PATH_PRODUCTION (gelezen uit de variable DEPLOY_PATH_PRODUCTION).
    • preparebuild (test-bundel, VITE_APP_ENV=test) → test (lint + unit tests)
    • build-production draait ernaast, alléén als deploy_production is aangevinkt bij een handmatige workflow_dispatch-run. Dit is een aparte build (niet hetzelfde artifact als build) omdat VITE_APP_ENV een build-time Vite-variabele is: één bundel kan niet tegelijk als test én production getagd zijn in Sentry/analytics.
    • Een losse config-job zet alle env-waarden (test én productie) om in job-outputs (zie hieronder waarom dat nodig is).
    • deploy-test (bij workflow_dispatch of een push naar master) roept deploy.yaml aan met artifact_name/environment/deploy_path afkomstig van needs.config.outputs.* (dus indirect uit het env:-blok).
    • deploy-production (alléén bij workflow_dispatch mét deploy_production: true) roept deploy.yaml op dezelfde manier aan, maar met de productie-artifact/omgeving/pad. Draait nooit automatisch bij een push naar master.
  • deploy.yaml — download de artifact (naam/lokaal pad = inputs.artifact_name) en upload de inhoud via een scp-commando (met sshpass voor het wachtwoord) in een gewone shell-stap naar de opgegeven deploy_path op de host uit de meegegeven secrets. Deze workflow is omgeving-agnostisch (test/productie) en hoefde niet gewijzigd te worden.

Waarom sshpass/scp in een shell-stap in plaats van de appleboy/scp-action Docker-action?

De oorspronkelijke aanpak gebruikte de appleboy/scp-action (een Docker-container-action). Dit werkte niet op deze zelf-gehoste Gitea-runner: de stap faalde met failed to attach to container: unable to upgrade to tcp, received 409, een bekende beperking van Podman's Docker-compatibele API, die het attach/log-streaming-mechanisme voor container-based actions niet volledig ondersteunt. De huidige aanpak (een normale run:-stap die sshpass installeert en zelf scp aanroept) heeft geen geneste container nodig en werkt daardoor wel.

Waarom een aparte config-job in plaats van rechtstreeks het env:-blok?

Gitea/GitHub Actions ondersteunt geen env-context in de with:-sectie waarmee een reusable workflow wordt aangeroepen (jobs.<job_id>.with) — dat werkt alléén binnen jobs.<job_id>.steps. De oplossing is een klein voorloop-job (config) dat de gewenste env-waarden via $GITHUB_OUTPUT naar job-outputs schrijft; die outputs (needs.config.outputs.*) zijn wél bruikbaar in jobs.<job_id>.with. Zo hoef je, om de artifact-naam/pad of de testdeploy-bestemming te wijzigen, alléén het env:-blok bovenaan continuous_integration.yaml aan te passen — niet de deploy-test-job zelf.

Eenmalige Setup — Gitea Secrets

Voeg deze secrets toe in Gitea: Repository → Settings → Actions → Secrets:

Secret Waarde
PI_MAIN_ADDRESS Intern IP-adres van de webserver-Pi (192.168.1.103)
PI_MAIN_PORT SSH-poort (2224)
PI_MAIN_USERNAME SSH-gebruikersnaam (webadmin)
PI_MAIN_PASSWORD Het SSH-wachtwoord van deze gebruiker

Deze secrets heten PI_MAIN_* (niet PI_TEST_*), omdat dezelfde Pi (Pi Main) en dezelfde inloggegevens naar verwachting ook voor toekomstige omgevingen/webhosts gebruikt worden. Mocht dat later veranderen, dan worden hiervoor alsnog omgeving-specifieke secrets geïntroduceerd.

Eenmalige Setup — Gitea Variables

Voeg deze variable toe in Gitea: Repository → Settings → Actions → Variables (geen secret — het is geen gevoelige waarde, net als VITE_UMAMI_SCRIPT_URL/VITE_SENTRY_DSN):

Variable Waarde
DEPLOY_PATH_TEST /html/test/slpsoftware
DEPLOY_PATH_PRODUCTION het uploadpad voor productie op Pi Main (bepaal dit zodra de productiemap op de Pi is aangemaakt, analoog aan stap 4 van "Eenmalige Setup — nginx & SSL"; bijv. /html/slpsoftware als de nginx root /mnt/storage1/www/html/slpsoftware wordt)

Dit vervangt het eerder hardcoded DEPLOY_PATH in het env:-blok van continuous_integration.yaml, zodat het upload-pad aangepast kan worden zonder de workflow zelf te wijzigen. Zonder DEPLOY_PATH_TEST is DEPLOY_PATH leeg en faalt de deploy-test-job bij de SCP-upload — deze variable moet dus vóór de eerste deploy zijn ingesteld. Zonder DEPLOY_PATH_PRODUCTION faalt op dezelfde manier de deploy-production-job; die hoeft pas ingesteld te zijn vóór de eerste keer dat je deploy_production aanvinkt.

Eenmalige Setup — Domeinnaam & DNS

  • Test: test.slpsoftware.nl → moet als DNS A-record wijzen naar het publieke IP van de reverse-proxy-Pi.
  • Productie (domein al bekend, nginx/SSL-configuratie op de Pi's moet nog opgezet worden voor de react-frontend): slpsoftware.nl (en www.slpsoftware.nl) → zelfde reverse-proxy-Pi. De deploy-pipeline zelf ondersteunt productie al (zie hieronder); wat nog ontbreekt is de webroot-map op de webserver-Pi, analoog aan de teststappen hieronder.

Eenmalige Setup — nginx & SSL op de Raspberry Pi's

Test

  1. Kopieer operations/deployment/nginx/webserver-nginx.conf.example naar /etc/nginx/sites-available/ op de webserver-Pi, maak een symlink in sites-enabled/, en herlaad nginx. Dit bestand is de daadwerkelijk in gebruik zijnde configuratie (server_name test.slpsoftware.nl, luistert op poort 80, serveert vanaf /mnt/storage1/www/html/test/slpsoftware).
  2. Kopieer operations/deployment/nginx/reverse-proxy-nginx.conf.example naar /etc/nginx/sites-available/slpsoftware-test.conf op de reverse-proxy-Pi, maak een symlink in sites-enabled/, en herlaad nginx. Dit is de versie van vóór certbot (alleen poort 80, geen SSL), met server_name test.slpsoftware.nl.
  3. Vraag op de reverse-proxy-Pi een SSL-certificaat aan met certbot (Let's Encrypt), nadat het DNS-record klopt: sudo certbot --nginx -d test.slpsoftware.nl. Certbot herschrijft dit bestand automatisch met de HTTPS-configuratie en de HTTP→HTTPS-redirect — zie operations/deployment/nginx/reverse-proxy-nginx.conf.post-certbot.example voor hoe het er dan uitziet (referentie, niet zelf kopiëren).
  4. Zorg dat de map /mnt/storage1/www/html/test/slpsoftware bestaat op de webserver-Pi en schrijfbaar is voor de gebruiker webadmin (bijv. sudo mkdir -p /mnt/storage1/www/html/test/slpsoftware && sudo chown webadmin:webadmin /mnt/storage1/www/html/test/slpsoftware).

Productie

De reverse-proxy-Pi bedient op slpsoftware.nl/www.slpsoftware.nl in werkelijkheid meer dan alleen deze react-frontend-site (o.a. mail/iRedAdmin-proxying naar een aparte host) — dat valt buiten de scope van deze feature. De onderstaande voorbeeldbestanden dekken alléén het react-frontend-gedeelte:

  1. Kopieer operations/deployment/nginx/production-nginx.conf.example naar /etc/nginx/sites-available/slpsoftware.conf op de reverse-proxy-Pi (of voeg het location/server-gedeelte toe aan een bestaand bestand als daar al andere server-blocks voor dit domein in staan), maak een symlink in sites-enabled/, en herlaad nginx. Dit is de versie van vóór certbot (alleen poort 80, geen SSL).
  2. Vraag op de reverse-proxy-Pi een SSL-certificaat aan met certbot, nadat de DNS-records kloppen: sudo certbot --nginx -d slpsoftware.nl -d www.slpsoftware.nl. Certbot herschrijft dit bestand automatisch — zie operations/deployment/nginx/production-nginx.conf.post-certbot.example voor hoe het er dan uitziet (referentie, niet zelf kopiëren). Let op: de acme-challenge location hoort in het 443-blok, niet in het losse poort-80-blok — zie de toelichting in dat referentiebestand.
  3. Zorg dat de productie-webroot-map bestaat op de webserver-Pi en schrijfbaar is voor webadmin (bijv. sudo mkdir -p /mnt/storage1/www/html/slpsoftware && sudo chown webadmin:webadmin /mnt/storage1/www/html/slpsoftware), analoog aan stap 4 van de testomgeving.

Waarom deploy_path en de nginx root niet hetzelfde pad zijn: de pipeline uploadt via SCP naar deploy_path = /html/test/slpsoftware (de waarde van de Gitea variable DEPLOY_PATH_TEST, ingelezen via env.DEPLOY_PATH in continuous_integration.yaml), terwijl de nginx root in webserver-nginx.conf.example het volledige pad /mnt/storage1/www/html/test/slpsoftware is. Dit is geen fout of inconsistentie: de SSH/SCP-gebruiker (webadmin) heeft /mnt/storage1/www als root (vergelijkbaar met een FTP-chroot), dus vanuit het perspectief van deze gebruiker is /html/test/slpsoftware het juiste (relatieve) pad, terwijl dat op het bestandssysteem van de Pi zelf overeenkomt met het volledige pad /mnt/storage1/www/html/test/slpsoftware dat nginx als root gebruikt. Kortom: deploy_path (/html/test/slpsoftware) + de root van de webadmin-gebruiker (/mnt/storage1/www) = de nginx root (/mnt/storage1/www/html/test/slpsoftware).

How to Deploy to Test

Automatisch

Merge een pull request naar master — de deploy-test job draait dan automatisch na een groene build/test-run.

Handmatig

  1. In Gitea, open de repository's Actions tab.
  2. Selecteer de Continuous Integration workflow.
  3. Klik Run workflow, kies de gewenste branch/ref, en start.

How to Deploy to Production

Productie deployt nooit automatisch bij een push naar master — alleen via een expliciete, handmatige actie:

  1. In Gitea, open de repository's Actions tab.
  2. Selecteer de Continuous Integration workflow.
  3. Klik Run workflow, kies de gewenste branch/ref (meestal master).
  4. Vink deploy_production aan voordat je de run start.
  5. Dit triggert naast de gebruikelijke build/test/deploy-test ook build-production en deploy-production.

Vereist eenmalig vooraf:

  • De Gitea-variable DEPLOY_PATH_PRODUCTION (zie boven).
  • De Gitea-variable VITE_UMAMI_WEBSITE_ID_PRODUCTION (zie umami-setup.md stap 4/5) — zónder deze wordt productieverkeer per ongeluk meegeteld bij de teststatistieken.
  • De productie-nginx/SSL-configuratie + webroot-map op de Pi's (zie "Eenmalige Setup — nginx & SSL op de Raspberry Pi's → Productie").

Verifying a Deployment

  1. Bevestig dat de Gitea Actions run succesvol is (alle relevante jobs groen — deploy-test altijd, build-production/deploy-production alleen als je deploy_production had aangevinkt).
  2. Open de omgeving in de browser (test: via het adres/IP dat je bij de reverse-proxy hebt ingesteld; productie: slpsoftware.nl zodra de nginx/SSL-setup daar staat) en controleer dat de site correct laadt (check de browserconsole op fouten, zoals in de handmatige smoke test in construction/build-and-test/integration-test-instructions.md).

Future Work

  • Van wachtwoord naar SSH-key: vervang sshpass -p "${{ secrets.PI_MAIN_PASSWORD }}" scp ... in deploy.yaml door een scp-commando met -i <key-bestand> (een nieuwe secret PI_MAIN_SSH_KEY die je eerst als bestand wegschrijft in de run-stap), en zet de bijbehorende public key in ~/.ssh/authorized_keys van de webadmin-gebruiker op de webserver-Pi. Verwijder daarna het wachtwoord-secret.
  • Webserver-Pi-vhost voor productie: er is nog geen webserver-nginx.conf.example-tegenhanger voor productie (de vhost op de webserver-Pi zelf die slpsoftware.nl serveert vanaf de productie-webroot). Bouw die op naar analogie van nginx/webserver-nginx.conf.example, met server_name slpsoftware.nl www.slpsoftware.nl en de productie-webroot als root.
  • Apart productie-Sentry-project: build-production gebruikt momenteel dezelfde VITE_SENTRY_DSN als de testbuild (Umami heeft al aparte website-ID's per omgeving, zie umami-setup.md). Overweeg dit te splitsen zodra test- en productie-events niet meer door elkaar gemengd mogen worden in hetzelfde Sentry-project.