Files
SlpSoftware/aidlc-docs/features/react-frontend/operations/monitoring/umami-setup.md
T
Sluijsens d71d0636c0
Continuous Integration / config (pull_request) Successful in 10s
Continuous Integration / prepare (pull_request) Successful in 1m13s
Continuous Integration / build (pull_request) Successful in 2m2s
Continuous Integration / test (pull_request) Successful in 1m51s
Continuous Integration / deploy-test (pull_request) Skipped
Split nginx voorbeeldconfigs in pre- en post-certbot varianten, plus access/error logs
De reverse-proxy en analytics nginx-configs toonden al de door certbot beheerde
eindstaat (met SSL-directives), terwijl je die juist nodig hebt vóórdat certbot
draait. Nu is het .example-bestand de kale HTTP-versie om te kopiëren, met een
apart .post-certbot.example ter referentie voor de staat erna. Alle configs
(inclusief webserver-nginx.conf.example) hebben nu ook expliciete error_log/
access_log directives.
2026-07-25 23:45:34 +02:00

12 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-compose geïnstalleerd voor de umami-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.nl dat naar het publieke IP van de reverse-proxy-Pi wijst (dezelfde Pi die ook test.slpsoftware.nl afhandelt).

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)

  1. Maak de user aan (zonder wachtwoord-login is prima, we loggen in via sudo -iu umami of su - umami):
    sudo useradd --create-home --shell /bin/bash umami
    sudo passwd -l umami   # login met wachtwoord blokkeren, sudo -iu blijft werken
    
  2. 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:
    grep umami /etc/subuid /etc/subgid
    
    Zie je geen output, voeg dan handmatig een range toe (pas de startwaarde aan als die al in gebruik is door een andere user):
    sudo usermod --add-subuids 200000-265535 --add-subgids 200000-265535 umami
    
  3. 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
    
  4. 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)

  1. Installeer podman-compose voor deze user, indien nog niet systeembreed aanwezig:
    pip3 install --user podman-compose
    
  2. 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.yml
    • operations/deployment/umami/.env.example~/umami/.env
  3. Vul in ~/umami/.env een echte POSTGRES_PASSWORD en APP_SECRET in (bv. via openssl rand -base64 32 voor beide — gebruik twee verschillende waarden).
  4. Start de containers:
    cd ~/umami
    podman-compose up -d
    
  5. Firewall (ufw) openzetten voor poort 3001. Pi Main gebruikt ufw met als default policy deny incoming — net als poort 3000 (Gitea) al een expliciete regel heeft voor het interne IP van de reverse-proxy-Pi, heeft 3001 (Umami) dezelfde regel nodig, anders krijg je bij het testen curl: (56) Recv failure: Connection reset by peer (ook wanneer je op Pi Main zelf naar het publieke IP 192.168.1.103 curl't — dat gaat namelijk nog steeds via de firewall-INPUT-chain, in tegenstelling tot localhost/127.0.0.1):
    sudo ufw allow from 192.168.1.102 to any port 3001 proto tcp
    sudo ufw reload
    
    Controleer met sudo ufw status verbose dat er nu een regel voor 3001/tcp staat, analoog aan de bestaande regel voor 3000/tcp.
  6. Controleer dat Umami draait en bereikbaar is op het interne netwerk. Let op: de externe poort is 3001, niet het gebruikelijke 3000 — die poort is op deze Pi al in gebruik door Gitea (bind: address already in use bij het opstarten van de container als je toch 3000 gebruikt):
    curl http://192.168.1.103:3001/api/heartbeat
    
    Dit zou een JSON-antwoord met "ok" moeten teruggeven. Krijg je toch een Connection reset by peer, controleer dan eerst of curl 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 -d update 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 je podman-compose up -d al eerder gedraaid met een oude versie van podman-compose.yml (bv. met poort 3000 in plaats van 3001), werk dan eerst je lokale ~/umami/podman-compose.yml bij met de nieuwste versie uit dit repository, en draai daarna:

cd ~/umami
podman-compose down
podman-compose up -d

podman-compose down verwijdert de containers (niet de database-data, die staat in een persistent volume) en up -d maakt ze opnieuw aan met de bijgewerkte poort/config. Dit is enkel nodig als de container al bestond met de oude configuratie; bij een eerste, nieuwe up -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.

  1. 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 voor umami, meestal omdat sudo -iu umami geen 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-reload opnieuw. Blijft het misgaan, gebruik dan in plaats van sudo -iu umami het commando sudo machinectl shell umami@ /bin/bash om in te loggen — dat registreert wél altijd een volledige sessie inclusief D-Bus, in tegenstelling tot sudo -iu/su -.

  1. Maak ~/.config/systemd/user/umami.service aan:
    [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
    
  2. 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)

  1. 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.conf op de reverse-proxy-Pi, en maak een symlink in sites-enabled.
  2. Zorg dat het DNS-record voor analytics.slpsoftware.nl al actief is, en draai dan:
    sudo certbot --nginx -d analytics.slpsoftware.nl
    
    Certbot herschrijft het bestand automatisch met de HTTPS-configuratie en de HTTP→HTTPS-redirect — zie operations/deployment/nginx/analytics-nginx.conf.post-certbot.example voor hoe het er dan uitziet (referentie, niet zelf kopiëren).
  3. Herlaad nginx: sudo nginx -t && sudo systemctl reload nginx.
  4. Test: open https://analytics.slpsoftware.nl in 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

  1. Log in op https://analytics.slpsoftware.nl en wijzig direct het standaardwachtwoord.
  2. Ga naar Settings → Websites → Add website en vul in:
    • Name: SLP Software (of naar keuze)
    • Domain: het domein van de daadwerkelijke website (bv. slpsoftware.nl of test.slpsoftware.nl, afhankelijk van welke omgeving je eerst wilt meten)
  3. Na het opslaan toont Umami een Website ID (een UUID) — dit heb je nodig voor de volgende stap.

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 twee build-time variabelen zijn ingesteld — beide zijn geen 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
VITE_UMAMI_WEBSITE_ID het Website ID uit stap 4

Stel deze in via Gitea → Repository Settings → Actions → Variables. Zodra beide 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).

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 en loginctl enable-linger).
  • podman-compose up -d daadwerkelijk draaien als de umami-user op Pi Main.
  • ufw-regel toevoegen voor poort 3001/tcp (analoog aan de bestaande 3000/tcp-regel) op Pi Main.
  • Systemd user-service instellen (onder de umami-user) voor auto-start na reboot.
  • DNS-record + certbot voor analytics.slpsoftware.nl op de reverse-proxy-Pi.
  • Standaard Umami-wachtwoord direct wijzigen na eerste login.
  • Website aanmaken in Umami en het Website ID overnemen.
  • VITE_UMAMI_SCRIPT_URL en VITE_UMAMI_WEBSITE_ID als Gitea repository variables instellen.

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).