build-production hergebruikte vars.VITE_UMAMI_WEBSITE_ID van de testbuild, maar Umami-website-ID's horen bij één specifieke domeinentry in het dashboard. Zonder deze fix zou productieverkeer in de teststatistieken terechtkomen. Introduceert VITE_UMAMI_WEBSITE_ID_PRODUCTION als losse Gitea variable, en documenteert dat er twee aparte Umami-website-entries nodig zijn (test.slpsoftware.nl / slpsoftware.nl). VITE_UMAMI_SCRIPT_URL en VITE_SENTRY_DSN blijven bewust gedeeld tussen omgevingen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
13 KiB
Umami Self-Hosted Setup (Analytics)
Stapsgewijze handleiding om Umami (self-hosted website-analytics) op te zetten op de
webserver-Pi (Pi Main, 192.168.1.103) met Podman, en bereikbaar te maken via
analytics.slpsoftware.nl. Dit vult het "Analytics/uptime dashboard" open item uit
operations/production-readiness-checklist.md in voor het analytics-gedeelte.
Umami draait onder een eigen dedicated Linux-user (umami) in plaats van onder het
bestaande hoofdaccount, zodat elke self-hosted service (nu en in de toekomst) netjes
geïsoleerd blijft — zie "Waarom een dedicated user" hieronder. Dit patroon kan hergebruikt
worden voor volgende self-hosted diensten op dezelfde Pi('s).
Vereisten
- Podman is al aanwezig op de Pi (bevestigd door de gebruiker) — systeembreed geïnstalleerd, dus beschikbaar voor elke user, ook een nieuw aangemaakte.
podman-composegeïnstalleerd voor deumami-user:pip3 install --user podman-compose(of via de package manager van je distro, indien beschikbaar) — zie stap 0 hieronder.- DNS: een A-record voor
analytics.slpsoftware.nldat naar het publieke IP van de reverse-proxy-Pi wijst (dezelfde Pi die ooktest.slpsoftware.nlafhandelt).
Waarom een dedicated user
Podman zelf hoeft niet per user geïnstalleerd te worden (het is een systeembreed pakket),
maar rootless Podman-containers erven wél de rechten van de user die ze start. Door Umami
onder een eigen umami-user te draaien in plaats van je eigen hoofdaccount:
- kan een kwetsbaarheid in de Umami-container (zelfs bij een container-escape binnen de rootless-namespace) geen bestanden van je persoonlijke account lezen/schrijven;
- houd je containerstorage, systemd user-services en logs van verschillende self-hosted diensten netjes gescheiden per user, ook als je Pi qua rekenkracht beperkt is;
- kun je dit patroon straks 1-op-1 hergebruiken voor een volgende self-hosted dienst (bv.
een eigen
uptime-user,git-user, etc.), zonder dat diensten elkaars bestanden kunnen benaderen.
0. Dedicated umami-user aanmaken (op de webserver-Pi)
- Maak de user aan (zonder wachtwoord-login is prima, we loggen in via
sudo -iu umamiofsu - umami):sudo useradd --create-home --shell /bin/bash umami sudo passwd -l umami # login met wachtwoord blokkeren, sudo -iu blijft werken - Controleer dat er een subuid/subgid-range is toegewezen (nodig voor rootless Podman).
Op de meeste moderne distributies (incl. Raspberry Pi OS) gebeurt dit automatisch bij
useradd:Zie je geen output, voeg dan handmatig een range toe (pas de startwaarde aan als die al in gebruik is door een andere user):grep umami /etc/subuid /etc/subgidsudo usermod --add-subuids 200000-265535 --add-subgids 200000-265535 umami - Zorg dat de
umami-sessie blijft "linger-en", zodat rootless Podman-services ook actief blijven zonder dat de user is ingelogd (nodig voor stap 2 hieronder):sudo loginctl enable-linger umami - Log in als de nieuwe user om de rest van de setup uit te voeren:
sudo -iu umami
1. Umami + database opzetten (als de umami-user, op de webserver-Pi)
- Installeer
podman-composevoor deze user, indien nog niet systeembreed aanwezig:pip3 install --user podman-compose - Maak een map aan, bv.
~/umami/(dit is nu/home/umami/umami/), en kopieer daarin:operations/deployment/umami/podman-compose.yml.example→~/umami/podman-compose.ymloperations/deployment/umami/.env.example→~/umami/.env
- Vul in
~/umami/.enveen echtePOSTGRES_PASSWORDenAPP_SECRETin (bv. viaopenssl rand -base64 32voor beide — gebruik twee verschillende waarden). - Start de containers:
cd ~/umami podman-compose up -d - Firewall (ufw) openzetten voor poort 3001. Pi Main gebruikt
ufwmet als default policydeny incoming— net als poort3000(Gitea) al een expliciete regel heeft voor het interne IP van de reverse-proxy-Pi, heeft3001(Umami) dezelfde regel nodig, anders krijg je bij het testencurl: (56) Recv failure: Connection reset by peer(ook wanneer je op Pi Main zelf naar het publieke IP192.168.1.103curl't — dat gaat namelijk nog steeds via de firewall-INPUT-chain, in tegenstelling totlocalhost/127.0.0.1):Controleer metsudo ufw allow from 192.168.1.102 to any port 3001 proto tcp sudo ufw reloadsudo ufw status verbosedat er nu een regel voor3001/tcpstaat, analoog aan de bestaande regel voor3000/tcp. - Controleer dat Umami draait en bereikbaar is op het interne netwerk. Let op: de
externe poort is
3001, niet het gebruikelijke3000— die poort is op deze Pi al in gebruik door Gitea (bind: address already in usebij het opstarten van de container als je toch3000gebruikt):Dit zou een JSON-antwoord metcurl http://192.168.1.103:3001/api/heartbeat"ok"moeten teruggeven. Krijg je toch eenConnection reset by peer, controleer dan eerst ofcurl http://localhost:3001/api/heartbeat(dus via loopback, buiten de firewall om) wél werkt — zo ja, dan zit het probleem zeker in de ufw-regel hierboven en niet in Umami/Podman zelf.
Let op — na een wijziging aan
podman-compose.yml/.env(bv. een andere poort):podman-compose up -dupdate alléén containers waarvan de configuratie is gewijzigd, maar een poortmapping (ports:) wordt door Podman niet live herladen op een bestaande, al aangemaakte container. Heb jepodman-compose up -dal eerder gedraaid met een oude versie vanpodman-compose.yml(bv. met poort3000in plaats van3001), werk dan eerst je lokale~/umami/podman-compose.ymlbij met de nieuwste versie uit dit repository, en draai daarna:cd ~/umami podman-compose down podman-compose up -d
podman-compose downverwijdert de containers (niet de database-data, die staat in een persistent volume) enup -dmaakt ze opnieuw aan met de bijgewerkte poort/config. Dit is enkel nodig als de container al bestond met de oude configuratie; bij een eerste, nieuweup -d(of als de vorige poging überhaupt nooit is gestart doordat Podman de poort niet kon claimen) is dit niet nodig.
2. Automatisch starten na reboot (systemd user service)
Podman-compose start niet vanzelf op na een herstart van de Pi, tenzij je dit expliciet
instelt. Omdat rootless Podman hier al gebruikt wordt, is een systemd user-service —
gekoppeld aan de umami-user — de eenvoudigste aanpak. De loginctl enable-linger umami
uit stap 0.3 is hiervoor al gezet, dus deze service blijft ook draaien zonder dat de
umami-user zelf is ingelogd.
- Log in (of blijf ingelogd) als de
umami-user:sudo -iu umami.
Let op — "Failed to connect to bus: No medium found" bij
systemctl --user: Dit betekent dat er (nog) geen D-Bus/systemd user-sessie draait voorumami, meestal omdatsudo -iu umamigeen volledige PAM-login-sessie registreert zoals een echte interactieve login dat wel doet — daardoor bestaat/run/user/<uid>(en de bus daarin) nog niet, zelfs met linger enabled. Los dit als volgt op:# Controleer eerst of linger daadwerkelijk actief staat: loginctl show-user umami | grep Linger # Verwacht: Linger=yes. Staat er Linger=no, herhaal dan: sudo loginctl enable-linger umami # Start de user-manager expliciet (uid van umami opzoeken met: id -u umami): sudo systemctl start user@$(id -u umami).service # Log opnieuw in als umami en zet XDG_RUNTIME_DIR expliciet, voor deze sessie: sudo -iu umami export XDG_RUNTIME_DIR=/run/user/$(id -u)Probeer daarna
systemctl --user daemon-reloadopnieuw. Blijft het misgaan, gebruik dan in plaats vansudo -iu umamihet commandosudo machinectl shell umami@ /bin/bashom in te loggen — dat registreert wél altijd een volledige sessie inclusief D-Bus, in tegenstelling totsudo -iu/su -.
- Maak
~/.config/systemd/user/umami.serviceaan:[Unit] Description=Umami analytics (podman-compose) After=network-online.target [Service] WorkingDirectory=%h/umami ExecStart=/usr/bin/podman-compose up ExecStop=/usr/bin/podman-compose down Restart=on-failure [Install] WantedBy=default.target - Activeer en start de service (nog steeds als de
umami-user):systemctl --user daemon-reload systemctl --user enable --now umami.service
3. Reverse proxy + SSL (op de reverse-proxy-Pi)
- Kopieer
operations/deployment/nginx/analytics-nginx.conf.example(de versie van vóór certbot: alleen poort 80, geen SSL) naar bv./etc/nginx/sites-available/slpsoftware-analytics.confop de reverse-proxy-Pi, en maak een symlink insites-enabled. - Zorg dat het DNS-record voor
analytics.slpsoftware.nlal actief is, en draai dan:Certbot herschrijft het bestand automatisch met de HTTPS-configuratie en de HTTP→HTTPS-redirect — ziesudo certbot --nginx -d analytics.slpsoftware.nloperations/deployment/nginx/analytics-nginx.conf.post-certbot.examplevoor hoe het er dan uitziet (referentie, niet zelf kopiëren). - Herlaad nginx:
sudo nginx -t && sudo systemctl reload nginx. - Test: open
https://analytics.slpsoftware.nlin de browser — je zou het Umami-inlogscherm moeten zien (standaard inloggegevens:admin/umami, direct wijzigen na eerste login).
4. Website registreren in Umami en het website-ID ophalen
Maak een aparte Umami-website-entry per omgeving aan — test en productie zijn verschillende domeinen en horen dus niet in dezelfde statistieken te belanden:
- Log in op
https://analytics.slpsoftware.nlen wijzig direct het standaardwachtwoord. - Ga naar Settings → Websites → Add website en maak een entry voor de testomgeving:
- Name:
SLP Software (test)(of naar keuze) - Domain:
test.slpsoftware.nl
- Name:
- Na het opslaan toont Umami een Website ID (een UUID) voor deze entry — nodig voor
VITE_UMAMI_WEBSITE_IDin de volgende stap. - Herhaal stap 2-3 voor productie zodra die omgeving wordt opgezet: een tweede website-
entry met Domain
slpsoftware.nl, met een eigen Website ID voorVITE_UMAMI_WEBSITE_ID_PRODUCTION.
5. Tracking script koppelen aan de website (build-configuratie)
De React-app (src/components/UmamiAnalytics.tsx) injecteert het Umami tracking-script
automatisch, mits de volgende build-time variabelen zijn ingesteld — geen van deze zijn
secrets (client-side zichtbaar), dus als Gitea Actions repository variables
(niet secrets), net als VITE_SENTRY_DSN:
| Variabele | Waarde |
|---|---|
VITE_UMAMI_SCRIPT_URL |
https://analytics.slpsoftware.nl/script.js (gedeeld tussen test en productie — zelfde Umami-instance/endpoint voor beide) |
VITE_UMAMI_WEBSITE_ID |
het Website ID van de test-website-entry uit stap 4 |
VITE_UMAMI_WEBSITE_ID_PRODUCTION |
het Website ID van de productie-website-entry uit stap 4 (pas nodig vóór de eerste deploy_production-run) |
Stel deze in via Gitea → Repository Settings → Actions → Variables. Zodra ze bestaan,
pakt de eerstvolgende build ze automatisch op; zonder deze variabelen slaat de app het
inladen van het script gewoon over (geen crash, geen tracking). Let op:
continuous_integration.yaml's build-job (test) leest VITE_UMAMI_WEBSITE_ID, de
build-production-job leest VITE_UMAMI_WEBSITE_ID_PRODUCTION — verwissel deze niet,
anders komt productieverkeer in de teststatistieken terecht (of andersom).
Lokaal (pnpm dev): het tracking-script wordt hier bewust nooit geladen (zie
UmamiAnalytics.tsx), zodat lokaal testen de bezoekersstatistieken niet vervuilt. Wil je
dit toch lokaal testen, zet dan tijdelijk beide waarden in .env.local (zie
.env.example) én verwijder tijdelijk de import.meta.env.DEV-check.
Openstaande handmatige stappen
- Dedicated
umami-user aanmaken op Pi Main (incl. subuid/subgid-check enloginctl enable-linger). podman-compose up -ddaadwerkelijk draaien als deumami-user op Pi Main.- ufw-regel toevoegen voor poort
3001/tcp(analoog aan de bestaande3000/tcp-regel) op Pi Main. - Systemd user-service instellen (onder de
umami-user) voor auto-start na reboot. - DNS-record + certbot voor
analytics.slpsoftware.nlop de reverse-proxy-Pi. - Standaard Umami-wachtwoord direct wijzigen na eerste login.
- Website aanmaken in Umami voor de testomgeving en het Website ID overnemen.
VITE_UMAMI_SCRIPT_URLenVITE_UMAMI_WEBSITE_IDals Gitea repository variables instellen.- Zodra productie wordt opgezet: aparte website aanmaken in Umami voor
slpsoftware.nlen het Website ID alsVITE_UMAMI_WEBSITE_ID_PRODUCTIONinstellen (zie stap 4/5 hierboven — vereist vóór de eerstedeploy_production-run).
Vervolgstappen voor toekomstige self-hosted diensten
Dit dedicated-user-patroon (stap 0 hierboven) is bewust generiek gehouden zodat het
hergebruikt kan worden: een volgende self-hosted dienst op dezelfde of een andere Pi kan
op dezelfde manier zijn eigen user krijgen (bv. useradd --create-home, subuid/subgid
controleren, loginctl enable-linger <user>, eigen ~/.config/systemd/user/<dienst>.service),
zodat diensten onderling geïsoleerd blijven zonder dat dit ten koste gaat van de
al beperkte rekenkracht van een Raspberry Pi (rootless Podman zelf blijft immers
systeembreed gedeeld, alleen de user-context verandert per dienst).