Merge branch 'master' into feature/analytics_uptime_dashboard
# Conflicts: # .env.example # aidlc-docs/features/react-frontend/aidlc-state.md # aidlc-docs/features/react-frontend/audit.md # aidlc-docs/features/react-frontend/operations/monitoring/monitoring-plan.md # aidlc-docs/features/react-frontend/operations/monitoring/monitoring-setup.md # aidlc-docs/features/react-frontend/operations/production-readiness-checklist.md # src/components/RootLayout.tsx # src/vite-env.d.ts
This commit is contained in:
+11
-7
@@ -13,7 +13,10 @@ Sinds deze stap wordt er automatisch gedeployed naar een **testomgeving**: een R
|
||||
- `prepare` → `build` (uploadt de artifact, naam/pad uit `env.ARTIFACT_NAME`/`env.ARTIFACT_PATH`) → `test` (lint + unit tests)
|
||||
- Een losse `config`-job zet deze `env`-waarden om in job-outputs (zie hieronder waarom dat nodig is).
|
||||
- `deploy-test` (alleen 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.yaml` — download de artifact (naam/lokaal pad = `inputs.artifact_name`) en upload de inhoud via `appleboy/scp-action` naar de opgegeven `deploy_path` op de host uit de meegegeven secrets.
|
||||
- `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.
|
||||
|
||||
### 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.
|
||||
@@ -35,11 +38,12 @@ Deze secrets heten `PI_MAIN_*` (niet `PI_TEST_*`), omdat dezelfde Pi (Pi Main) e
|
||||
- **Productie** (nog niet automatisch gedeployed, maar domein al bekend): `slpsoftware.nl` (en `www.slpsoftware.nl`) → zelfde reverse-proxy-Pi, zodra productie wordt opgezet.
|
||||
|
||||
## Eenmalige Setup — nginx & SSL op de Raspberry Pi's
|
||||
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.
|
||||
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 bestand gebruikt al `test.slpsoftware.nl` als `server_name`.
|
||||
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. Ook dit bestand is de daadwerkelijk in gebruik zijnde (door certbot beheerde) configuratie, 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 regelt automatisch de HTTPS-configuratie en de HTTP→HTTPS-redirect (net zoals je gewend bent van certbot).
|
||||
4. Zorg dat de map `/html/test/slpsoftware` bestaat op de webserver-Pi en schrijfbaar is voor de gebruiker `webadmin` (bijv. `sudo mkdir -p /html/test/slpsoftware && sudo chown webadmin:webadmin /html/test/slpsoftware`).
|
||||
5. Voor later, wanneer productie wordt opgezet: zie `operations/deployment/nginx/reverse-proxy-nginx-production.conf.example` (domein `slpsoftware.nl`, certbot-commando alvast gedocumenteerd).
|
||||
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`).
|
||||
|
||||
> **Waarom `deploy_path` en de nginx `root` niet hetzelfde pad zijn**: de pipeline uploadt via SCP naar `deploy_path` = `/html/test/slpsoftware` (zie `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
|
||||
@@ -55,5 +59,5 @@ Merge een pull request naar `master` — de `deploy-test` job draait dan automat
|
||||
2. Open de testomgeving in de browser (via het adres/IP dat je bij de reverse-proxy hebt ingesteld) 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 `password: ${{ secrets.PI_MAIN_PASSWORD }}` in `deploy.yaml` door `key: ${{ secrets.PI_MAIN_SSH_KEY }}` (een nieuwe secret met de private key-inhoud), en zet de bijbehorende public key in `~/.ssh/authorized_keys` van de `webadmin`-gebruiker op de webserver-Pi. Verwijder daarna het wachtwoord-secret.
|
||||
- **Productie-omgeving**: voeg een `deploy-production`-job toe zodra de definitieve productiehosting bekend is (zie `deployment-plan.md`'s "Open Item"), en pas `nginx/reverse-proxy-nginx-production.conf.example` (domein `slpsoftware.nl`) toe zodra de webserver-locatie voor productie vastligt.
|
||||
- **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.
|
||||
- **Productie-omgeving**: voeg een `deploy-production`-job toe zodra de definitieve productiehosting bekend is (zie `deployment-plan.md`'s "Open Item"). De productie-nginx-voorbeeldconfiguratie is op verzoek van de gebruiker verwijderd totdat er een goed-werkende versie is; die kan later opnieuw opgebouwd worden naar analogie van `nginx/webserver-nginx.conf.example` en `nginx/reverse-proxy-nginx.conf.example`, met domein `slpsoftware.nl`.
|
||||
|
||||
@@ -11,13 +11,13 @@ Sinds deze stap is er een echte, geautomatiseerde upload naar een **testomgeving
|
||||
1. Bij elke pull request draait automatisch de build/test/lint-gate (`prepare` → `build` → `test`), zodat merge requests direct gevalideerd worden.
|
||||
2. Zodra een pull request naar `master` gemerged wordt (of de workflow handmatig via `workflow_dispatch` gestart wordt), draait aanvullend de `deploy-test` job.
|
||||
3. `deploy-test` roept de herbruikbare `deploy.yaml` workflow aan met `artifact_name`/`environment`/`deploy_path`, en geeft via `secrets: inherit` de Pi-inloggegevens door. Deze drie waarden (samen met de artifact-naam/pad die de `build`-job gebruikt) staan als variabelen in het `env:`-blok bovenaan `continuous_integration.yaml` (`ARTIFACT_NAME`, `ARTIFACT_PATH`, `DEPLOY_ENVIRONMENT`, `DEPLOY_PATH`), en worden via een kleine `config`-job als job-outputs doorgegeven aan `deploy-test` (nodig omdat de `env`-context zelf niet werkt in de `with:`-sectie van een reusable-workflow-aanroep).
|
||||
4. `deploy.yaml` downloadt de artifact en uploadt de inhoud via SCP (wachtwoord-login) naar de webserver-Pi op het interne netwerk (`192.168.1.103`, poort `2224`).
|
||||
5. nginx op de webserver-Pi serveert de bestanden vanaf `/html/test/slpsoftware`; de reverse-proxy-Pi stuurt binnenkomend verkeer door naar deze webserver-Pi. Voorbeeldconfiguraties staan in `operations/deployment/nginx/`.
|
||||
4. `deploy.yaml` downloadt de artifact en uploadt de inhoud via een `scp`-commando (met `sshpass` voor het wachtwoord) in een gewone shell-stap naar de webserver-Pi op het interne netwerk (`192.168.1.103`, poort `2224`). Dit vervangt de eerdere `appleboy/scp-action` (Docker-container-action), die faalde op de zelf-gehoste Podman-runner (`failed to attach to container: unable to upgrade to tcp, received 409`).
|
||||
5. nginx op de webserver-Pi serveert de bestanden vanaf `/mnt/storage1/www/html/test/slpsoftware`; de reverse-proxy-Pi stuurt binnenkomend verkeer door naar deze webserver-Pi. Voorbeeldconfiguraties staan in `operations/deployment/nginx/` en zijn de daadwerkelijk in gebruik zijnde configuraties (niet langer illustratieve concepten).
|
||||
6. De reverse-proxy-Pi is ook verantwoordelijk voor SSL: certificaten worden net als voorheen aangevraagd via certbot (Let's Encrypt) en HTTP-verkeer wordt doorverwezen naar HTTPS.
|
||||
|
||||
## Environments
|
||||
- **Test** (nieuw, geautomatiseerd): zoals hierboven beschreven — de enige omgeving die op dit moment daadwerkelijk automatisch gedeployed wordt. Domeinnaam: `test.slpsoftware.nl` (SSL via certbot op de reverse-proxy-Pi).
|
||||
- **Productie**: nog niet geautomatiseerd. Zodra de definitieve productiehosting bekend is, kan een vergelijkbare `deploy-production`-job worden toegevoegd die `deploy.yaml` aanroept met `environment: production` en de productie-secrets/pad. Domeinnaam ligt al vast: `slpsoftware.nl` (SSL eveneens via certbot; zie `nginx/reverse-proxy-nginx-production.conf.example`).
|
||||
- **Productie**: nog niet geautomatiseerd. Zodra de definitieve productiehosting bekend is, kan een vergelijkbare `deploy-production`-job worden toegevoegd die `deploy.yaml` aanroept met `environment: production` en de productie-secrets/pad. Domeinnaam ligt al vast: `slpsoftware.nl` (SSL eveneens via certbot). Er is (nog) geen productie-nginx-voorbeeldconfiguratie; deze is op verzoek verwijderd totdat er een goed-werkende, foutloze versie is, en kan later opnieuw opgebouwd worden naar analogie van de testomgeving-configuraties.
|
||||
|
||||
## Automation Level
|
||||
Volledig geautomatiseerd voor de testomgeving: build, test, lint én upload naar de test-Pi gebeuren zonder handmatige tussenstap, zodra er gemerged wordt naar `master` (of handmatig getriggerd wordt). Alleen productie is nog niet geautomatiseerd.
|
||||
@@ -37,7 +37,7 @@ Deze secrets heten bewust `PI_MAIN_*` in plaats van `PI_TEST_*`: alle webhosts g
|
||||
Dit is bewust wachtwoord-authenticatie (voor nu, zoals gekozen), zodat de testomgeving snel werkend is. Zie "Future Work" in `deployment-instructions.md` voor de overstap naar SSH-key-authenticatie.
|
||||
|
||||
## Open Item — Productie-deploy Nog Niet Geautomatiseerd
|
||||
Zodra de definitieve productiehosting bekend is (en of dit dezelfde soort Raspberry Pi-opstelling is, of een externe hostingpartij), voeg een `deploy-production`-job toe aan `continuous_integration.yaml` die `deploy.yaml` aanroept met `environment: production`. Zolang het dezelfde webhost (Pi Main) blijft, kunnen de bestaande `PI_MAIN_*` secrets hergebruikt worden; pas dit pas aan naar omgeving-specifieke secrets als productie daadwerkelijk op een andere host komt. Domeinnaam (`slpsoftware.nl`) en SSL-aanpak (certbot/Let's Encrypt op de reverse-proxy-Pi) liggen al vast, zie `nginx/reverse-proxy-nginx-production.conf.example`.
|
||||
Zodra de definitieve productiehosting bekend is (en of dit dezelfde soort Raspberry Pi-opstelling is, of een externe hostingpartij), voeg een `deploy-production`-job toe aan `continuous_integration.yaml` die `deploy.yaml` aanroept met `environment: production`. Zolang het dezelfde webhost (Pi Main) blijft, kunnen de bestaande `PI_MAIN_*` secrets hergebruikt worden; pas dit pas aan naar omgeving-specifieke secrets als productie daadwerkelijk op een andere host komt. Domeinnaam (`slpsoftware.nl`) en SSL-aanpak (certbot/Let's Encrypt op de reverse-proxy-Pi) liggen al vast; er is bewust (nog) geen productie-nginx-voorbeeldconfiguratie, deze wordt later opnieuw opgebouwd zodra er een goed-werkende, foutloze versie is.
|
||||
|
||||
## Verified Build Prerequisite
|
||||
Dit plan bouwt voort op de Build and Test-stage (`construction/build-and-test/build-and-test-summary.md`): `pnpm run build` produceert een statische `dist/`-bundel zonder server-side vereisten, geschikt om direct door nginx geserveerd te worden.
|
||||
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
# Voorbeeldconfiguratie voor de nginx reverse proxy op de Raspberry Pi voor de
|
||||
# PRODUCTIEOMGEVING, bereikbaar via het domein slpsoftware.nl.
|
||||
#
|
||||
# LET OP: dit bestand is nog niet in gebruik — de productie-deploy is nog niet
|
||||
# geautomatiseerd (zie deployment-plan.md's "Open Item — Productie-deploy Nog
|
||||
# Niet Geautomatiseerd"). Dit voorbeeld ligt hier alvast klaar zodat het
|
||||
# domein en SSL-certificaat (net als bij de testomgeving via certbot) bekend
|
||||
# zijn wanneer de productie-deploy wordt opgezet. Vervang <PROD_WEBSERVER_IP>
|
||||
# en <PROD_WEBSERVER_POORT> door de daadwerkelijke waarden zodra die bekend
|
||||
# zijn (mogelijk dezelfde webserver-Pi op een ander poortnummer/pad, of een
|
||||
# aparte Pi/host).
|
||||
#
|
||||
# Kopieer dit bestand handmatig naar bijvoorbeeld
|
||||
# /etc/nginx/sites-available/slpsoftware-production.conf op de reverse-proxy-Pi,
|
||||
# maak een symlink in sites-enabled, en herlaad nginx.
|
||||
#
|
||||
# SSL-certificaat aanvragen met certbot (Let's Encrypt), nadat het DNS-record
|
||||
# voor slpsoftware.nl naar het publieke IP van deze Pi wijst:
|
||||
# sudo certbot --nginx -d slpsoftware.nl -d www.slpsoftware.nl
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
server_name slpsoftware.nl www.slpsoftware.nl;
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
server_name slpsoftware.nl www.slpsoftware.nl;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/slpsoftware.nl/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/slpsoftware.nl/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://<PROD_WEBSERVER_IP>:<PROD_WEBSERVER_POORT>;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
+53
-25
@@ -11,46 +11,74 @@
|
||||
# certbot (Let's Encrypt) rechtstreeks op deze reverse-proxy-Pi, bijvoorbeeld:
|
||||
# sudo certbot --nginx -d test.slpsoftware.nl
|
||||
# Zorg dat het DNS-record voor test.slpsoftware.nl al naar het publieke IP van
|
||||
# deze Pi wijst voordat je certbot draait. Certbot vult automatisch het
|
||||
# onderstaande `listen 443 ssl` server-block aan (of maakt het aan) en
|
||||
# schrijft de HTTP-server hieronder om naar een 301-redirect. Het onderstaande
|
||||
# is dus vooral illustratief voor de eindsituatie.
|
||||
|
||||
# HTTP: alleen gebruikt voor de Let's Encrypt ACME-challenge en om door te
|
||||
# verwijzen naar HTTPS. Na het draaien van certbot ziet dit block er zo uit.
|
||||
# deze Pi wijst voordat je certbot draait.
|
||||
#
|
||||
# Dit is de daadwerkelijk in gebruik zijnde configuratie op de reverse-proxy-Pi,
|
||||
# zoals door certbot gegenereerd/beheerd. Het HTTP-server-block onderaan (dat
|
||||
# doorverwijst naar HTTPS, behalve voor de ACME-challenge) en de #-commentaren
|
||||
# "managed by Certbot" zijn automatisch door certbot toegevoegd/aangepast.
|
||||
#
|
||||
# LET OP - gevonden foutje: de header hieronder heet `X-Real-IP` (met
|
||||
# koppelteken), niet `X-Real_IP` (met underscore). Een header met een
|
||||
# underscore is geen geldige/herkenbare naam voor de standaard `$http_x_real_ip`-
|
||||
# achtige verwerking; nginx stuurt de header wel door zoals opgegeven, maar
|
||||
# de ontvangende applicatie/module verwacht het koppelteken-formaat. Corrigeer
|
||||
# dit op de Pi zelf naar `X-Real-IP` zodra je de config daar aanpast.
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
server_name test.slpsoftware.nl;
|
||||
|
||||
error_log /var/log/nginx/slpsoftware-test_error.log;
|
||||
access_log /var/log/nginx/slpsoftware-test_access.log;
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
# Sentry "tunnel" endpoint (see src/main.tsx's Sentry.init tunnel option).
|
||||
# Browser ad-blockers/privacy extensions commonly block requests that go
|
||||
# straight to *.ingest.<region>.sentry.io (ERR_BLOCKED_BY_CLIENT), since
|
||||
# that looks like third-party tracking. By forwarding this path server-side
|
||||
# to Sentry instead, the browser only ever talks to our own domain
|
||||
# (test.slpsoftware.nl), so it is no longer recognized/blocked as
|
||||
# third-party tracking.
|
||||
#
|
||||
# LET OP: de org-/project-id in de proxy_pass hieronder MOET overeenkomen
|
||||
# met de VITE_SENTRY_DSN die voor DEZE omgeving is ingesteld (de
|
||||
# `VITE_SENTRY_DSN` Gitea repository variable voor de testomgeving). Pas
|
||||
# dit aan als je ooit van Sentry-project/DSN wisselt.
|
||||
location /sentry-tunnel {
|
||||
proxy_pass https://o4511795618185216.ingest.de.sentry.io/api/4511795622838352/envelope/;
|
||||
proxy_set_header Host o4511795618185216.ingest.de.sentry.io;
|
||||
proxy_ssl_server_name on;
|
||||
}
|
||||
}
|
||||
|
||||
# HTTPS: het daadwerkelijke reverse-proxy-verkeer naar de webserver-Pi.
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
server_name test.slpsoftware.nl;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/test.slpsoftware.nl/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/test.slpsoftware.nl/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://192.168.1.103:80;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
listen 443 ssl; # managed by Certbot
|
||||
listen [::]:443 ssl; # managed by Certbot
|
||||
ssl_certificate /etc/letsencrypt/live/test.slpsoftware.nl/fullchain.pem; # managed by Certbot
|
||||
ssl_certificate_key /etc/letsencrypt/live/test.slpsoftware.nl/privkey.pem; # managed by Certbot
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||
|
||||
}
|
||||
|
||||
server {
|
||||
if ($host = test.slpsoftware.nl) {
|
||||
return 301 https://$host$request_uri;
|
||||
} # managed by Certbot
|
||||
|
||||
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
server_name test.slpsoftware.nl;
|
||||
return 404; # managed by Certbot
|
||||
}
|
||||
+14
-9
@@ -3,21 +3,20 @@
|
||||
#
|
||||
# Deze Pi is NIET rechtstreeks vanaf het internet bereikbaar; de andere
|
||||
# Raspberry Pi (met de nginx reverse proxy, zie reverse-proxy-nginx.conf.example)
|
||||
# stuurt inkomend verkeer door naar deze server op het interne netwerk.
|
||||
# stuurt inkomend verkeer door naar deze server op het interne netwerk (poort 80).
|
||||
#
|
||||
# Dit is de daadwerkelijk in gebruik zijnde configuratie op de webserver-Pi.
|
||||
# Kopieer dit bestand handmatig naar bijvoorbeeld
|
||||
# /etc/nginx/sites-available/slpsoftware-test.conf op de webserver-Pi,
|
||||
# maak een symlink in sites-enabled, en herlaad nginx (`sudo nginx -t && sudo systemctl reload nginx`).
|
||||
|
||||
server {
|
||||
# Alleen luisteren op het interne (LAN) IP-adres van deze Pi, niet op 0.0.0.0,
|
||||
# zodat deze poort niet per ongeluk vanaf buiten het netwerk bereikbaar is.
|
||||
listen 192.168.1.103:80;
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
server_name _;
|
||||
server_name test.slpsoftware.nl;
|
||||
|
||||
root /html/test/slpsoftware;
|
||||
root /mnt/storage1/www/html/test/slpsoftware;
|
||||
index index.html;
|
||||
|
||||
# De React-app gebruikt client-side routing (React Router), dus onbekende
|
||||
@@ -34,6 +33,12 @@ server {
|
||||
}
|
||||
|
||||
# Doorgeven van het echte client-IP en protocol, afkomstig van de reverse proxy.
|
||||
set_real_ip_from 192.168.1.0/24;
|
||||
real_ip_header X-Forwarded-For;
|
||||
}
|
||||
# Uitgecommentarieerd: de reverse-proxy Pi geeft het echte client-IP al door
|
||||
# via de X-Forwarded-For header hierboven; deze twee directieven zouden nginx
|
||||
# dat IP ook laten gebruiken als "remote address" in de eigen access-log van
|
||||
# deze Pi. Zonder deze regels tonen de logs van deze webserver-Pi het interne
|
||||
# IP van de reverse-proxy Pi als bezoeker-IP (functioneel geen probleem, wel
|
||||
# minder nauwkeurig voor logging/analyse op deze Pi zelf).
|
||||
#set_real_ip_from 192.168.1.0/24;
|
||||
#real_ip_header X-Forwarded-For;
|
||||
}
|
||||
@@ -6,7 +6,7 @@ Sinds deze stap wordt de testomgeving automatisch gedeployed door de `deploy-tes
|
||||
## Rolling Back the Live Site (Test-omgeving)
|
||||
1. Identificeer de laatste bekend-goede commit op `master` (of een eerdere `release/*`-branch/tag) die succesvol gedeployed was.
|
||||
2. Trigger de **Continuous Integration** workflow handmatig (`workflow_dispatch`) tegen die commit/branch/tag in Gitea Actions.
|
||||
3. De `deploy-test` job uploadt automatisch de resulterende `dist/`-build naar `/html/test/slpsoftware` op de webserver-Pi, en overschrijft daarmee de huidige (foutieve) bestanden.
|
||||
3. De `deploy-test` job uploadt automatisch de resulterende `dist/`-build via SCP naar `/html/test/slpsoftware` op de webserver-Pi — dit relatieve pad staat t.o.v. de root van de SCP-gebruiker (`/mnt/storage1/www`), waardoor nginx dezelfde bestanden serveert vanaf het volledige pad `/mnt/storage1/www/html/test/slpsoftware` (zie de uitleg hierover in `deployment-instructions.md`) — en overschrijft daarmee de huidige (foutieve) bestanden.
|
||||
4. Verify de live testomgeving reflecteert de teruggedraaide versie (via het adres achter de reverse proxy).
|
||||
|
||||
## Keeping Rollback Possible
|
||||
|
||||
@@ -13,7 +13,7 @@ Initial answers to the monitoring plan were contradictory: Question 2 selected "
|
||||
## Chosen Approach(es)
|
||||
|
||||
### Logging
|
||||
Client-side errors (JavaScript crashes, broken links) should be logged, but the concrete destination is not yet decided (original Question 3 = C, "not yet determined"). This is tracked as an **open action item** below rather than blocking this stage.
|
||||
Client-side errors (JavaScript crashes, broken links) are logged. **Decided**: Sentry free tier (in addition to the browser console) — see the follow-up in `audit.md` and the implementation in `monitoring-setup.md`.
|
||||
|
||||
### Dashboards
|
||||
A combination of:
|
||||
@@ -27,7 +27,7 @@ A combination of:
|
||||
- **Reuse of existing infrastructure**: this feature does not plug into any pre-existing shared monitoring (original Question 7 = A) — there is none yet. Should a shared back-end/CMS monitoring stack be introduced later, this can be revisited.
|
||||
|
||||
## Open Action Items
|
||||
1. **Decide logging destination**: choose between "browser console only" (no central storage, manual debugging) or a free/low-cost external error-tracking service (e.g. Sentry free tier) once this becomes a priority. Until decided, `monitoring-setup.md` documents both options so either can be adopted without re-doing this stage.
|
||||
1. ~~Decide logging destination~~ — **Resolved and verified**: Sentry free tier, wired into `ErrorBoundary`/`main.tsx` (see `monitoring-setup.md`), including tracing, environment/release tags, and a tunnel to bypass ad-blocker blocking. The user confirmed errors, logs, and metrics are received correctly, both locally and on the test environment. The Gitea Actions variable `VITE_SENTRY_DSN` still needs to be created by the user with a real Sentry project DSN for the test/production build; until then that build simply skips Sentry initialization and only console-logging is active (local development already has its own DSN via `.env.local`).
|
||||
2. ~~Pick concrete analytics + uptime tools~~ — **Resolved**: self-hosted Umami + UptimeRobot (see `monitoring-setup.md` and `umami-setup.md`). Remaining manual follow-ups: actually deploying the Umami containers on the Pi, registering DNS/SSL for `analytics.slpsoftware.nl`, creating the UptimeRobot monitor, and setting the `VITE_UMAMI_SCRIPT_URL`/`VITE_UMAMI_WEBSITE_ID` Gitea repository variables.
|
||||
|
||||
## Rationale
|
||||
|
||||
@@ -9,20 +9,34 @@ Concrete setup steps for the approaches chosen in `monitoring-plan.md`: **Loggin
|
||||
- Broken/failed navigation (e.g. an unexpected router error).
|
||||
- No user PII, form input, or sensitive data should ever be logged — this is a public marketing site, but keep this discipline regardless.
|
||||
|
||||
### Destination — open decision
|
||||
The destination was not finalized (original Question 3 = C). Two supported options, either of which can be adopted later without further design work:
|
||||
### Destination — decided: Sentry free tier + console
|
||||
Both the console and Sentry are now active (original Question 3 resolved as a combination):
|
||||
|
||||
**Option 1: Browser console only (default today)**
|
||||
- No code changes needed — errors already surface via `console.error` inside the existing `ErrorBoundary`.
|
||||
- Zero cost, but not centrally visible; only useful for manual debugging (e.g. via a user's screenshot or a support request).
|
||||
**Console (always on)**
|
||||
- Errors already surface via `console.error` inside `ErrorBoundary.componentDidCatch` — unchanged, zero cost, useful for local/manual debugging.
|
||||
|
||||
**Option 2: External error-tracking service (e.g. Sentry free tier)**
|
||||
- When decided, add `@sentry/react` as a dependency, initialize it once in the app entry point (e.g. `src/main.tsx`) with the project DSN, and report caught errors from the `ErrorBoundary`'s `componentDidCatch`/`onError` hook to Sentry in addition to the console.
|
||||
- Store the DSN as a Gitea Actions variable (or a build-time `.env` value, since it's not a secret — Sentry DSNs are safe to expose client-side) and inject it via Vite's `import.meta.env`.
|
||||
**Sentry free tier (implemented)**
|
||||
- `@sentry/react` is a dependency; `src/main.tsx` calls `Sentry.init({ ... })` at startup, but only when a DSN is present — if not configured, Sentry is silently skipped and only console-logging remains active (safe default, no crash on missing config).
|
||||
- `ErrorBoundary.componentDidCatch` calls `Sentry.captureException(error, { extra: { componentStack: info.componentStack } })` in addition to `console.error`.
|
||||
- **Tracing/performance** is also enabled (Sentry's recommended default alongside error monitoring, per the official React SDK setup guide): `tanstackRouterBrowserTracingIntegration(router)` is wired up so route navigations are captured as transactions, with `tracesSampleRate: 1.0` (capture all — appropriate for a low-traffic marketing site; lower this if traffic grows significantly).
|
||||
- **Environment/release tagging**: `environment` is set to `import.meta.env.VITE_APP_ENV` (`'test'` / `'production'`, set at build time — see below), falling back to `import.meta.env.MODE` for local development (`pnpm dev` → `'development'`). This fallback is needed because `vite build` runs in production mode by default regardless of target environment, so `MODE` alone cannot distinguish a test build from a production build. `release` is set to the app version from `package.json` (injected at build time via `vite.config.ts`'s `define: { __APP_VERSION__ }`), so events in Sentry can be filtered/grouped per environment and per shipped version.
|
||||
- The DSN is injected at build time via Vite's `import.meta.env.VITE_SENTRY_DSN` (typed in `src/vite-env.d.ts`). It is **not** a secret (Sentry DSNs are safe to expose client-side), so it is passed as a **Gitea Actions repository variable** (`vars.VITE_SENTRY_DSN`, not a secret) to the `Build` step in `continuous_integration.yaml`.
|
||||
- **Local development DSN**: for `pnpm dev`, Vite automatically loads a `.env.local` file (already covered by `.gitignore`'s `*.local` rule, so it is never committed). Copy `.env.example` to `.env.local` and set `VITE_SENTRY_DSN` there to enable Sentry locally — a separate Sentry project/DSN is recommended so local test noise doesn't mix with the test-environment data. Omitting `.env.local` (or leaving the value empty) simply disables Sentry locally, falling back to console-only logging.
|
||||
- **Manual follow-up required**: create a free Sentry project (https://sentry.io) for this app, copy its DSN, and set it as the `VITE_SENTRY_DSN` repository variable in Gitea (Repository Settings → Actions → Variables). Until that variable is set, the build still succeeds and the site still works — Sentry reporting simply stays inactive.
|
||||
- Free tier limits (error volume, retention) are typically sufficient for a low-traffic marketing site.
|
||||
- **Not (yet) implemented, by explicit choice**: automatic source map upload (via `@sentry/vite-plugin`), which the official Sentry setup guide also recommends so stack traces show real source code instead of minified code. This requires a Sentry auth token/org/project as a new Gitea secret; deliberately left out of scope for now — revisit if readable production stack traces become a priority.
|
||||
- **Ad-blocker mitigation via a Sentry "tunnel" (all environments, including local)**: requests straight to `*.ingest.<region>.sentry.io` are commonly blocked client-side by ad-blockers/privacy extensions (`ERR_BLOCKED_BY_CLIENT`), because they resemble third-party tracking. To avoid this, `Sentry.init()` always sets `tunnel: '/sentry-tunnel'`. In deployed (test/production) environments, the reverse-proxy Pi (`nginx/reverse-proxy-nginx.conf.example`) forwards that path server-side to Sentry's envelope endpoint, so the browser only ever talks to the first-party domain (e.g. `test.slpsoftware.nl`). Locally (`pnpm dev`), the equivalent route is provided by Vite's own dev-server proxy (`server.proxy` in `vite.config.ts`), so no ad-blocker whitelisting/disabling is needed anymore for local testing either. This relies on full control over the reverse-proxy's nginx config, which is the case here (self-hosted on the user's own Raspberry Pi's) — it would not work on a third-party/shared host without reverse-proxy access. **Important**: the org-/project-id hardcoded in both the nginx `location /sentry-tunnel` block and the Vite `server.proxy` target must match whatever `VITE_SENTRY_DSN`/`.env.local` DSN is actually configured; update all together if the Sentry project/DSN ever changes.
|
||||
|
||||
**Log level strategy**: only errors are logged (no verbose/info-level client logging) — this is a static site with no meaningful "business events" beyond page views, which are covered by analytics (see Dashboards below), not logging.
|
||||
|
||||
### Temporary manual test tool: `SentryTestButton`
|
||||
- `src/components/SentryTestButton.tsx` renders a "Break the world" button, mounted globally via `RootLayout.tsx`, used to manually verify that errors, logs (`Sentry.logger.info`), and metrics (`Sentry.metrics.count`) actually arrive in Sentry end-to-end.
|
||||
- **Visibility**: only shown during local development (`pnpm dev`, via Vite's `import.meta.env.DEV`) and in the test environment (via the new build-time `VITE_APP_ENV` variable, set to `test` by `continuous_integration.yaml`'s `Build` step). It is hidden by default (including in any future production build) unless one of those conditions is explicitly true.
|
||||
- **Visual feedback**: clicking the button immediately shows a green confirmation toast ("Test error verzonden naar Sentry ✅", `role="status"`) that auto-hides after 4 seconds, so the user gets clear confirmation that the test action fired — without this, the resulting uncaught error/blank state gave no indication anything happened. The actual log/metric/throw (`handleSentryTestErrorClick`) is fired on the next tick (`setTimeout(..., 0)`) so the toast has a chance to render/paint first.
|
||||
- `Sentry.logger.*` requires `enableLogs: true` in `Sentry.init()` (`src/main.tsx`) — added specifically to support this test button (and any future structured logging).
|
||||
- The thrown error is **intentionally uncaught**: React error boundaries do not catch errors thrown from event handlers (only render/lifecycle errors), so this relies on Sentry's own global `window.onerror` handler, exactly like the official Sentry test snippet.
|
||||
- This is a temporary verification tool, not a permanent feature — remove `SentryTestButton` (and its usage in `RootLayout.tsx`) once Sentry has been confirmed to receive test errors/logs/metrics end-to-end.
|
||||
|
||||
## Dashboards
|
||||
|
||||
### Website analytics — decided: self-hosted Umami
|
||||
@@ -66,7 +80,7 @@ public URL from the outside, entirely independent of the site's own codebase.
|
||||
|
||||
| Concern | Approach | Status |
|
||||
|---|---|---|
|
||||
| Client-side errors | Logging (console today; Sentry free tier optional later) | Destination open item |
|
||||
| Client-side errors | Logging (console + Sentry free tier, incl. tracing, environment/release tags, and an ad-blocker-proof tunnel) | Implemented and confirmed working end-to-end (errors, logs, and metrics received) locally and on the test environment; the `VITE_SENTRY_DSN` Gitea repository variable still needs to be created by the user for the test/production build to report to Sentry (local already works via `.env.local`) |
|
||||
| Visitor/usage insight | Analytics dashboard (self-hosted Umami) | Tool decided; deployment (Podman on Pi Main + `analytics.slpsoftware.nl`) is a manual follow-up, see `umami-setup.md` |
|
||||
| Site reachability | Uptime dashboard (UptimeRobot) | Tool decided; account creation + production URL are manual follow-ups |
|
||||
| Alerting | Out of scope | Not configured |
|
||||
|
||||
@@ -19,6 +19,6 @@
|
||||
- **Ready for Production**: Yes with caveats
|
||||
- **Open Follow-ups**:
|
||||
- Finalize the hosting/domain setup and extend `.gitea/workflows/deploy.yml`'s `deploy` job to actually upload `dist/` to the host, instead of only packaging it as a downloadable artifact (see `deployment-plan.md` "Open Item")
|
||||
- Decide the client-side error logging destination (browser console only vs. an external service such as Sentry free tier) — see `monitoring-setup.md`
|
||||
- ~~Decide the client-side error logging destination~~ — **Resolved and verified**: console + Sentry free tier, implemented and confirmed working end-to-end (errors, logs, metrics) locally and on the test environment, including tracing, environment/release tags, and an ad-blocker-proof tunnel (see `monitoring-setup.md`); remaining manual step is creating the `VITE_SENTRY_DSN` Gitea Actions variable for the test/production build
|
||||
- ~~Pick and configure the concrete analytics tool and uptime dashboard tool~~ — **Resolved**: self-hosted Umami (Podman on Pi Main) + UptimeRobot decided; see `monitoring-setup.md` and the new `umami-setup.md`. Remaining manual follow-ups: actually deploy the Umami containers, set up `analytics.slpsoftware.nl` DNS/SSL, create the UptimeRobot monitor, and register the final production URL once hosting is finalized
|
||||
- Run a formal Lighthouse performance check before the first real production deployment (flagged as not yet run in `build-and-test-summary.md`)
|
||||
|
||||
Reference in New Issue
Block a user