Files
VibeFinance/BEHEER.md
T
2026-09-11 15:20:09 +02:00

26 KiB

VibeFinance — Beheer

De app draait in 2 containers: app (Express API + gebouwde frontend) en postgres. Eén image, één origin — geen aparte webserver en geen interne proxy.


Ontwikkeling

.\dev.ps1                      # build + start (standaard)
.\dev.ps1 build                # alleen bouwen
.\dev.ps1 up -Target app       # alleen de app herbouwen + herstarten
.\dev.ps1 up -Target postgres  # alleen de database herstarten
.\dev.ps1 logs                 # logs van de app volgen
.\dev.ps1 test                 # testsuite draaien
.\dev.ps1 down                 # stoppen én volumes wissen
.\dev.ps1 push                 # dev-image naar registry pushen

Op macOS/Linux: gebruik ./dev.sh met dezelfde argumenten.

In dev draait één container beide processen (zie docker/dev-entrypoint.sh):

Adres Wat
http://localhost Vite dev-server met hot reload
http://localhost:3001/api/health de API rechtstreeks

Vite proxyt /api naar localhost:3001 binnen dezelfde container.

⚠️ Alleen .\dev.ps1 down wist de development-database (docker compose down -v). .\dev.ps1 up doet een gewone down en laat het volume staan.

Welke versie de dev-app toont

De versie in "Over VibeFinance" komt uit VITE_APP_VERSION. dev.ps1 leest die uit de root package.json, zet hem in de omgeving en synct hem naar frontend/package.json. In de dev-container is het een runtime-variabele (docker-compose.dev.yml, sectie environment) en geen build-arg: Vite bakt __APP_VERSION__ in bij het starten van de dev-server, niet bij het bouwen van het image. Is hij leeg, dan valt vite.config.js terug op frontend/package.json.

Ziet de app een oude versie, dan draait de dev-server nog van vóór de bump — .\dev.ps1 up hercreëert de container en start Vite opnieuw. Start je de stack handmatig met docker compose -f docker-compose.dev.yml up, dan staat VITE_APP_VERSION niet in je shell en zie je de versie uit frontend/package.json.

Een versie met een suffix (1.0.1-dev) onderdrukt de update-melding permanent — zie Update-notificaties.


Testen

.\test.ps1              # dependency-check + backend-tests + build + smoketest
.\test.ps1 -Unit        # dependency-check + backend-tests
.\test.ps1 -NoSmoke     # zonder de stack te starten
.\test.ps1 -Strict      # faal als een dependency achterloopt
.\test.ps1 -SkipDeps    # dependency-check overslaan (bijv. offline)

Alles loopt in Docker; Node op de host is niet nodig.

Stap Wat er gebeurt
Dependency-check docker/check-updates.mjs vergelijkt package.json met het npm-register
Backend-tests Dockerfile target test → node --test in backend/test/, zonder database
Productiebuild Dockerfile target production — faalt de Vite-build, faalt deze stap
Smoketest app + postgres starten en /api/health en / opvragen

De dependency-check draait standaard bij elke testronde, dus je ziet direct of er updates open staan. Pakketten die bewust achterblijven zet je met een reden in docker/update-exceptions.json; die worden als [vast ] gemeld en laten de check niet falen. Zonder netwerk: -SkipDeps. In een CI-gate: -Strict.

De check los draaien:

docker run --rm -v ${PWD}:/repo -w /repo node:22-alpine node docker/check-updates.mjs
docker run --rm -v ${PWD}:/repo -w /repo node:22-alpine node docker/check-updates.mjs --strict

Losse backend-tests en linting in een lopende dev-container:

docker compose -f docker-compose.dev.yml exec app npm test
docker compose -f docker-compose.dev.yml exec app npm run lint
docker compose -f docker-compose.dev.yml exec -w /app/frontend app npm run lint

Tokens

Eén token, op het werkstation. Dat is genoeg omdat de Vibe-organisatie op public staat: alles wat alleen leest heeft geen token meer nodig — git clone, docker compose pull en de update-check van de app werken anoniem. Wat overblijft is schrijven, en dat gebeurt allemaal vanaf deze ene machine.

Token Machine Scopes Gebruikt voor
vibefinance-workstation dit werkstation write:package, write:repository docker push (release.ps1, dev.ps1 push) én het aanmaken van de Gitea-release

Er is geen apart host-token en geen apart registry-token:

  • git push gaat over SSH (gitea@…:Vibe/VibeFinance.git), dus die gebruikt je SSH-sleutel en niet dit token. Werkt SSH even niet — bijvoorbeeld omdat de ssh-agent niet meedoet in een niet-interactieve shell, wat Permission denied (publickey,password) geeft — dan kun je lezen nu anoniem over https, omdat de organisatie public is:

    git fetch https://vibehoogie.duckdns.org/Vibe/VibeFinance.git --prune --tags "+refs/heads/*:refs/remotes/origin/*"
    

    Die refspec werkt ook origin/* bij, dus daarna doen git merge --ff-only origin/main en git log origin/main het weer. Pushen blijft SSH; release.ps1 leest daarvoor core.sshCommand uit, omdat op Windows alleen de OpenSSH van het systeem met de ssh-agent praat en Git's eigen ssh niet.

  • De registry heeft geen eigen variabele nodig: release.ps1 en dev.ps1 vallen bij een ontbrekende REGISTRY_TOKEN terug op GITEA_TOKEN. Zet alleen REGISTRY_USER erbij, zodat het script zelf kan inloggen als de credential store leeg is.

  • De Docker-host hoeft niets meer te hebben: pull en update-check zijn anoniem.

Aanmaken: Gitea → Settings → Applications → Generate New Token, met beide scopes aangevinkt. Doe dat onder een account dat push-rechten op de repo heeft — lid zijn van de organisatie is daarvoor niet automatisch genoeg. Scopes van een bestaand token zijn later niet aan te passen, dus vink ze in één keer goed aan.

Waar hij terechtkomt — .env.release naast de scripts, niet in git (.gitignore pakt .env.*):

GITEA_TOKEN=<token met write:package + write:repository>
REGISTRY_USER=<je Gitea-account>

Een variabele die al in je shell staat wint, zodat je per run kunt afwijken. Een lege waarde in het bestand wordt overgeslagen.

Als de organisatie weer privé gaat heb je er wél een tweede bij nodig: een read-only token op de Docker-host, voor docker compose pull en UPDATE_CHECK_TOKEN. Houd dat token dan read-only. Het staat in platte tekst in .env op een server die de app op poort 3400 publiceert; met schrijfrechten kan wie bij dat bestand komt — via de container, een shell op die host, of een backup — een eigen image als latest naar de registry pushen, en die host pullt latest zelf. Read-only houdt je eigen deploy-pad uit de aanvalsketen.

Wat een foutcode betekent

Status Betekenis
401 Token ongeldig, verlopen of ingetrokken — Gitea herkent hem niet. Op de registry: helemaal niet ingelogd.
403 Het token bestaat en werkt, maar mist de scope voor dit endpoint. Gitea noemt de vereiste scope in de response-body.
404 Het token mag de repo niet zien. Gitea geeft geen 403, om niet te verklappen dat iets bestaat: verkeerd account, of geen toegang tot de organisatie. Nu de organisatie public is, wijst een 404 op een leesactie eerder op een verkeerde URL dan op rechten.

Scopes van een bestaand token zijn in Gitea niet aan te passen. Bij een 403 maak je dus een nieuw token aan; bijwerken kan niet.

Leeft mijn token nog? Juist dat onderscheid tussen 401 en 403 is de test. Vraag niet /api/v1/user op om te kijken "wie het token is": het werkstation-token heeft geen read:user, dus dat endpoint geeft altijd 403 en zegt niets over geldigheid. Kijk naar de statuscode, niet naar de inhoud:

$u = "https://vibehoogie.duckdns.org/api/v1/user/repos"
try   { (Invoke-WebRequest $u -Headers @{ Authorization = "token $env:GITEA_TOKEN" } -UseBasicParsing).StatusCode }
catch { $_.Exception.Response.StatusCode.value__ }

403 = het token leeft (alleen deze scope ontbreekt). 401 = ingetrokken of fout gekopieerd. Nagemeten op 2026-09-10: geen header 401, een nep-token van 40 nullen 401, het werkstation-token 403.

Vanuit de app-container op de host valt de body van een fout anders weg — busybox-wget gooit die bij een foutstatus weg, en Node zit in het image:

# Anoniem, zoals de app het doet zonder UPDATE_CHECK_TOKEN
docker exec vibefinance_app node -e 'fetch(process.env.UPDATE_CHECK_URL,{headers:{Accept:"application/json"}}).then(async r=>console.log(r.status, (await r.text()).slice(0,300)))'

# Mét token, precies zoals routes/update.js de header opbouwt
docker exec vibefinance_app node -e 'const t=process.env.UPDATE_CHECK_TOKEN; fetch(process.env.UPDATE_CHECK_URL,{headers:{Accept:"application/json",...(t&&{Authorization:`token ${t}`})}}).then(async r=>console.log(r.status, (await r.text()).slice(0,300)))'

Let op: een ongeldig token geeft 401, ook op deze publieke repo — Gitea valt bij een verkeerd token niet terug op anonieme toegang. Krijg je 401 terwijl anoniem 200 geeft, haal UPDATE_CHECK_TOKEN dan uit .env in plaats van hem te vervangen. Een leeg token is onschuldig: Authorization: token negeert Gitea (200), en config/index.js zet met || null bij een lege waarde helemaal geen header. Nagemeten op 2026-09-10: geen header 200, token ongeldig123 401, leeg 200.

Oude tokens opruimen

Nu er één token overblijft, kunnen de rest weg — maar niet op de gok. De lijst onder Settings → Applications heeft een kolom Last used. Werkwijze:

  1. Zet het nieuwe token in .env.release en draai een release die helemaal doorloopt (inclusief docker push en stap 5).
  2. Kijk in de lijst welk token net Last used heeft gekregen.
  3. Wacht een dag en trek de tokens in die niet meer meebewegen.

Betrouwbaarder dan uit de naam afleiden wat waar in gebruik is. Trek vooral niets in vóór die controle: het token achter je docker login zit in de credential store en niet in een bestand, dus je ziet aan niets welke het is — trek je die in, dan valt de push in stap 3 om. Loopt dat toch mis, dan is docker login vibehoogie.duckdns.org met het nieuwe token genoeg.


Release workflow

# 1. Versienummer in package.json van "x.x.x-dev" naar "x.x.x"
# 2. Wijzigingen in Changelog.md onder de kop "## [Niet vrijgegeven]"
#    (de kop zelf omzetten hoeft niet — dat doet het script)
# 3. Token staat in .env.release (zie Tokens) — of eenmalig in de shell:
$env:GITEA_TOKEN = "<vibefinance-workstation>"

.\release.ps1              # test, bouw (amd64+arm64), push, tag, Gitea-release
.\release.ps1 -DryRun      # droogloop — toont alle stappen zonder uit te voeren
.\release.ps1 -NoBuild     # sla docker build over, alleen tag + release
.\release.ps1 -SkipTests   # sla de testsuite over (niet aanbevolen)
.\release.ps1 -NoChangelog # publiceer bewust zonder release-notes

# Klaar. Het versienummer voor de volgende cyclus zet het script zelf.

Het script bouwt één image voor linux/amd64 en linux/arm64, zodat dezelfde tag op een Raspberry Pi en op een x86-server werkt.

Wat het script zelf bijwerkt — met de hand aanpassen is dus dubbel werk:

Bestand Wat Wanneer
Changelog.md kop ## [Niet vrijgegeven] → ## [x.x.x] - datum vóór de build
Git-tag vx.x.x op HEAD, gepusht naar origin stap 4
frontend/package.json versie gelijkgetrokken met de root package.json vóór de build
README.md de versie-x.x.x-badge bovenaan vóór de build
package.json (root) versie → x.x.(x+1)-dev voor de volgende cyclus ná het publiceren

Een -DryRun meldt de vier bestandswijzigingen zonder ze te schrijven (de Git-tag is de uitzondering in die tabel: die hoort bij stap 4, niet bij de auto-updates). Na een echte release blijven die vier als wijziging in je working tree staan; neem ze mee in de volgende commit — de tag staat dan op de commit ervóór, dus de release bevat ze nog niet.

De laatste staat bewust ná het publiceren: breekt de release eerder af, dan schuift het versienummer niet vooruit. Bijeffect dat je moet kennen — na een geslaagde release staat er een -dev nummer in package.json, en de controle bovenin het script weigert een -dev versie. Dat sluit een dubbele publicatie uit, maar wil je dezelfde versie bewust opnieuw releasen (bijvoorbeeld omdat een tag is verdwenen), zet het nummer dan eerst zelf terug naar x.x.x.

Zonder changelog-sectie breekt het script af. De release-notes komen uit de sectie ## [x.x.x] in Changelog.md. Vond het script daar niets, dan publiceerde het stil de tekst "Release x.x.x" — dat gebeurde bij 0.1.9 en 1.0.2. Nu zet het de kop ## [Niet vrijgegeven] zelf om, en als er ook dán niets over deze versie in staat, stopt het vóór de build met de melding wat je moet toevoegen. Wil je bewust zonder notes publiceren, gebruik -NoChangelog.

De controle staat vóór de drie auto-updates hierboven, zodat een afgebroken release geen gewijzigde bestanden achterlaat. Is een release toch al gepubliceerd met de placeholder, dan repareer je dat met een PATCH op /repos/<org>/<repo>/releases/<id> met de juiste body.

.env.release — token niet elke sessie opnieuw zetten

Staat er een .env.release naast release.ps1, dan leest het script die in voordat er iets gebeurt, en hoeft stap 3 niet meer met de hand:

# .env.release — staat in .gitignore, hoort alleen op de release-machine
GITEA_TOKEN=<vibefinance-workstation>
REGISTRY_USER=<gitea-gebruiker>

REGISTRY_TOKEN hoeft er niet in: beide scripts vallen bij een ontbrekende REGISTRY_TOKEN terug op GITEA_TOKEN. Zie Tokens voor de scopes en voor wat een 401, 403 of 404 van Gitea betekent.

Een variabele die al in je shell staat wint, zodat je per run kunt afwijken zonder het bestand aan te passen. Het script logt alleen welke namen het heeft geladen, nooit de waarden.


Docker

# Update via pre-built images
docker compose pull && docker compose up -d

# Reset inclusief database  ⚠️ wist alle data
docker compose down -v && docker compose up -d

Draai dit in de map met docker-compose.yml en .env. docker compose zoekt dat bestand namelijk in de huidige map en kijkt niet omhoog; staat het er niet, dan is het antwoord no configuration file provided: not found. Met docker compose -f /pad/naar/docker-compose.yml kun je het expliciet meegeven.

Pullen zelf werkt anoniem zolang de organisatie public is — zie Pullen op een deploy-machine.

PostgreSQL versie-upgrade

Bij een upgrade van PostgreSQL (bijv. 16 → 18) is de bestaande data niet compatibel. Stappen:

# 1. Backup maken
docker exec vibefinance_postgres pg_dump -U vibefinance vibefinance > backup-$(date +%F).sql

# 2. Oude containers en volumes verwijderen
docker compose down -v

# 3. Nieuwe versie starten
docker compose up -d

# 4. Backup terugzetten
docker exec -i vibefinance_postgres psql -U vibefinance -d vibefinance < backup-YYYY-MM-DD.sql

Architectuur — één app-container

De Express-app serveert zowel de API als de gebouwde frontend:

Verzoek Wat de app doet
/api/... REST API (rate limited, Cache-Control: no-store)
/assets/... gehashte build-bestanden uit STATIC_DIR, 1 jaar immutable
/favicon.svg, /icons/... vaste namen, dus max-age=0, must-revalidate
pad mét extensie zonder bestand echte 404, niet index.html
al het overige index.html — fallback voor React-routing, no-store

STATIC_DIR staat standaard op /app/public; daar zet de Dockerfile de Vite-build neer. Bestaat die map niet (bijv. in dev), dan serveert de app alleen de API en logt dat bij het starten.

Ontbreekt de build in het image, dan krijg je op / een 404 en werkt alleen /api/.... Controleer dan de frontend-build stage in de Dockerfile.


Logs en diagnose

# Live logs (dev) — beide processen van de app-container
docker compose -f docker-compose.dev.yml logs -f app

# Live logs (productie)
docker compose logs -f

# Shell in de app-container (dev)
docker compose -f docker-compose.dev.yml exec app sh

# Status containers
docker compose ps

# Database inspecteren (dev)
docker exec -it vibefinance-postgres-dev psql -U vibefinance -d vibefinance

# Database inspecteren (productie)
docker exec -it vibefinance_postgres psql -U vibefinance -d vibefinance

Data persistentie

De backend slaat alle data op in PostgreSQL.

Omgeving Volume Mount pad
Productie vibefinance_pgdata /var/lib/postgresql
Development postgres_dev_data /var/lib/postgresql

PostgreSQL 18 gebruikt PGDATA=/var/lib/postgresql/18/docker. Het volume is gemount op /var/lib/postgresql zodat data bewaard blijft na een container restart.

Daarnaast heeft de app zelf een backup onder Instellingen: die schrijft een ZIP met de data van de ingelogde gebruiker (data.json + meta.json) en leest zo'n ZIP ook weer in. Handig per gebruiker, maar het is geen servercopie — accounts, wachtwoordhashes en de data van andere gebruikers zitten er niet in. Voor een volledige backup gebruik je pg_dump hieronder.

Backup maken:

# Dev
docker exec vibefinance-postgres-dev pg_dump -U vibefinance vibefinance > backup-$(date +%F).sql

# Productie
docker exec vibefinance_postgres pg_dump -U vibefinance vibefinance > backup-$(date +%F).sql

Backup terugzetten:

# Dev
docker exec -i vibefinance-postgres-dev psql -U vibefinance -d vibefinance < backup-2026-04-08.sql

# Productie
docker exec -i vibefinance_postgres psql -U vibefinance -d vibefinance < backup-2026-04-08.sql

Gebruikersbeheer

De eerste geregistreerde gebruiker krijgt automatisch de Admin-rol. Verdere accounts kunnen via de app aangemaakt worden (Gebruikersbeheer, Admin vereist) of via de API.

Rollen

De rolkeuze in Gebruikersbeheer biedt drie waarden, maar de backend dwingt er op dit moment één af:

Rol Wat de code ermee doet
Admin Enige rol die wordt gecontroleerd: requireAdmin in middleware/auth.js bewaakt alle routes onder /api/users
Bewerker Geen eigen rechten — gelijk aan Viewer
Viewer Standaardrol voor een nieuw account; mag de eigen data lezen én schrijven

Dat Viewer toch mag schrijven is geen gat in de afscherming: /api/data werkt per gebruiker en gaat altijd over de rij van het eigen token (req.user.sub). Niemand ziet dus data van een ander. Wat ontbreekt is een read-only account — wil je dat, dan is er een controle op rol nodig bij PUT/DELETE /api/data.

De "Gebruikers"-knop in de navigatiebalk staat bij iedereen in beeld; bij een niet-Admin antwoordt /api/users met 403 en blijft de lijst leeg. De afscherming zit dus in de API, niet in de navigatie.


Productie checklist

  • POSTGRES_PASSWORD instellen op een sterk wachtwoord
  • JWT_SECRET aanpassen (genereer met openssl rand -hex 64)
  • CORS_ORIGIN instellen op je domeinnaam
  • APP_PORT aanpassen indien poort 3400 al in gebruik is
  • Testsuite groen (test.ps1 / ./test.sh)
  • TLS afhandelen via een externe reverse proxy (bijv. Nginx Proxy Manager, Caddy, Traefik)

Dependencies bijwerken

Node hoeft niet op de host te staan; de lockfiles zijn met een container bij te werken. Per map (frontend en backend):

# Laatste versies opzoeken
docker run --rm -v ${PWD}/frontend:/w -w /w node:22-alpine npm outdated

# Versies in package.json aanpassen, daarna de lockfile herschrijven
docker run --rm -v ${PWD}/frontend:/w -w /w node:22-alpine npm install --package-lock-only

Daarna altijd de testsuite draaien — die bouwt ook het productie-image, dus een kapotte upgrade valt meteen op:

.\test.ps1

En de dev-stack herbouwen. node_modules staan in anonieme volumes; zonder --renew-anon-volumes blijven de oude versies staan. .\dev.ps1 up doet dit al automatisch:

.\dev.ps1 up

Let op bij majors: eslint-plugin-react loopt achter op ESLint (peer <= ^9.7), daarom staat de frontend op ESLint 9 en de backend op 10. Express 5 vereist benoemde wildcards in routes (/api/*splat); server.js gebruikt daarom pad-loze middleware in plaats van app.get("*").


Registry

vibehoogie.duckdns.org/vibe/vibefinance-app   (frontend + backend in één image)

Tags:
  latest          productie — meest recente release
  x.x.x           productie — specifieke versie
  latest-dev      development — meest recente dev build
  x.x.x-dev       development — specifieke dev versie

Image-tags hebben géén v-prefix (1.0.4), Git-tags wél (v1.0.4). Dat is geen inconsistentie maar de conventie aan beide kanten: container-registries taggen op het kale nummer, Git-releases met v.

Inloggen op de registry:

docker login vibehoogie.duckdns.org

Gebruikersnaam is je Gitea-account, wachtwoord het access token van deze machine — vibefinance-workstation, zie Tokens. Zonder login faalt een push met 401 Unauthorized; release.ps1 en dev.ps1 push controleren dit vóór de build en stoppen dan meteen.

Liever niet handmatig inloggen? Zet REGISTRY_USER in .env.release, dan logt het script zelf in met GITEA_TOKEN — een aparte REGISTRY_TOKEN is alleen nodig als je daar bewust een ánder token voor wilt gebruiken.

Twee valkuilen: log in op vibehoogie.duckdns.org zonder :3000 (dat is een aparte sleutel in de credential store), en doe het als dezelfde Windows-gebruiker in dezelfde shell — Docker-credentials zijn per gebruikersprofiel.

Controleren wat er is opgeslagen:

docker-credential-desktop list

Pullen op een deploy-machine

De organisatie is public, dus een pull werkt anoniem — docker compose pull && docker compose up -d en klaar, zonder docker login.

Controleren zonder te pullen:

docker manifest inspect vibehoogie.duckdns.org/vibe/vibefinance-app:latest

Krijg je 401 Unauthorized, "no basic auth credentials" of "pull access denied", dan is het package niet publiek leesbaar (organisatie weer op privé, of de package-eigenaar wijkt af van de organisatie). Dan alsnog eenmalig op de host:

docker login vibehoogie.duckdns.org
# gebruikersnaam: je Gitea-account
# wachtwoord:     een token met read:package

De login komt in ~/.docker/config.json van de gebruiker die het commando uitvoert — doe het dus als dezelfde gebruiker (bij sudo docker is dat root). Log in op vibehoogie.duckdns.org zonder :3000.

Moet je terug naar een token, dan kan datzelfde token ook als UPDATE_CHECK_TOKEN in de .env op deze host — één token voor beide, zie Tokens.


Update-notificaties

De app vergelijkt de draaiende versie met de laatste release op Gitea en toont dat op het belletje in de topbalk en onder "Over VibeFinance".

Hoe de check loopt

  1. De frontend vraagt /api/update-check op de eigen backend — de Gitea API stuurt geen CORS-headers, dus de browser mag die niet direct bevragen.
  2. De backend haalt releases/latest bij Gitea op en cachet het antwoord 15 minuten.
  3. De frontend kijkt bij het openen, daarna elk half uur en bij het terugkeren naar het tabblad. Alleen een écht hoger versienummer geeft een melding.

Builds met een -dev suffix melden nooit (die lopen voor op de laatste release). Wil je de melding tijdens ontwikkeling toch testen: open de app met ?updatecheck in de URL. Let op dat isNieuwer alleen bij een écht hoger nummer aanslaat — bij 1.0.2-dev naast release 1.0.1 blijft de kaart dus ook met ?updatecheck leeg. Zet dan tijdelijk een lagere versie in de root package.json.

Wat de melding toont

Alleen de kop, de versieregel en een link:

🔔  Nieuwe versie beschikbaar
    v1.0.1 is uitgebracht. Je gebruikt momenteel v1.0.0.
    Release bekijken →

De release-notes staan er bewust niet in. /api/update-check stuurt ze wel mee als notes, maar dat is de ruwe Markdown uit de changelog; in een kaart van 280px leverde dat zichtbare ### -koppen en afgekapte regels op. Wie de details wil, volgt de link. Het veld blijft in de API zitten voor het geval je ze ooit ergens met meer ruimte wilt tonen — dan is Markdown renderen de volgende stap.

De release-link hangt aan Gitea's ROOT_URL

De url in het antwoord is de html_url van de release, en die stelt Gitea bij elk verzoek samen uit ROOT_URL in app.ini — hij wordt niet per release opgeslagen. Staat ROOT_URL op een intern adres, dan wijst "Release bekijken" naar iets dat buiten je netwerk niet bestaat, terwijl de melding zelf gewoon werkt (die leest alleen het versienummer).

Nakijken welke waarde Gitea daadwerkelijk gebruikt — de issuer is ROOT_URL:

curl -s https://vibehoogie.duckdns.org/.well-known/openid-configuration | grep issuer

app.ini wordt alleen bij het opstarten gelezen, dus na een wijziging moet Gitea herstarten. Omdat de URL wordt samengesteld en niet bewaard, zijn daarna álle releases in één keer goed — ook de oude. Een PATCH per release is niet nodig.

Configuratie

De bron staat in de root package.json:

"repository": "https://vibehoogie.duckdns.org/vibe/VibeFinance",
"updateCheckUrl": "https://vibehoogie.duckdns.org/api/v1/repos/vibe/VibeFinance/releases/latest"

dev.ps1 zet die URL als UPDATE_CHECK_URL in de omgeving; in productie komt hij uit .env (of de default in docker-compose.yml). Beschikbare backend-variabelen:

Variabele Betekenis
UPDATE_CHECK_URL Release-API die gepolld wordt. Leeg = melding uit.
UPDATE_CHECK_TOKEN Optioneel. De Vibe-organisatie is public, dus releases/latest antwoordt anoniem met 200 en de check heeft geen token nodig. Gaat de organisatie weer privé, dan is het host-token nodig (read:repository, zie Tokens): zonder geeft releases/latest dan 404, met een verkeerde scope 403 — in beide gevallen blijft de melding leeg, met alleen een WARN in het containerlog.

Een release publiceren

Een kale Git-tag is niet genoeg: releases/latest geeft alleen echte releases terug. release.ps1 maakt die aan (stap 4) met de changelog-sectie van die versie als release-notes. Daarvoor is het werkstation-token met write:repository nodig (zie Tokens), meestal al aanwezig via .env.release:

$env:GITEA_TOKEN = "<vibefinance-workstation>"   # of gewoon uit .env.release
.\release.ps1

Zonder token slaat het script die stap over met een waarschuwing — images en tag staan dan wel klaar, maar gebruikers zien geen melding tot de release bestaat.