Files
VibeFinance/BEHEER.md
T

642 lines
26 KiB
Markdown
Raw Permalink Normal View History

# 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
```powershell
.\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.
2026-09-10 14:03:34 +02:00
### 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](#update-notificaties).
---
## Testen
```powershell
.\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:
```bash
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:
```bash
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
```
---
2026-09-09 17:36:34 +02:00
## 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
2026-09-10 15:40:27 +02:00
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:
```powershell
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.*`):
```ini
GITEA_TOKEN=<token met write:package + write:repository>
REGISTRY_USER=<je Gitea-account>
```
2026-09-09 17:36:34 +02:00
Een variabele die al in je shell staat wint, zodat je per run kunt afwijken. Een
lege waarde in het bestand wordt overgeslagen.
2026-09-09 17:36:34 +02:00
**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.
2026-09-09 17:36:34 +02:00
### Wat een foutcode betekent
| Status | Betekenis |
|---|---|
2026-09-10 15:40:27 +02:00
| 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. |
2026-09-10 14:03:34 +02:00
| 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. |
2026-09-09 17:36:34 +02:00
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.
2026-09-10 15:40:27 +02:00
**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:
2026-09-09 17:36:34 +02:00
```powershell
2026-09-10 15:40:27 +02:00
$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__ }
2026-09-09 17:36:34 +02:00
```
2026-09-10 15:40:27 +02:00
`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`.
2026-09-09 17:36:34 +02:00
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:
```bash
2026-09-10 14:03:34 +02:00
# 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)))'
2026-09-09 17:36:34 +02:00
```
2026-09-10 14:03:34 +02:00
> 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:
2026-09-09 17:36:34 +02:00
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.
2026-09-09 17:36:34 +02:00
---
## Release workflow
```powershell
# 1. Versienummer in package.json van "x.x.x-dev" naar "x.x.x"
2026-09-10 16:24:30 +02:00
# 2. Wijzigingen in Changelog.md onder de kop "## [Niet vrijgegeven]"
# (de kop zelf omzetten hoeft niet — dat doet het script)
2026-09-09 17:36:34 +02:00
# 3. Token staat in .env.release (zie Tokens) — of eenmalig in de shell:
$env:GITEA_TOKEN = "<vibefinance-workstation>"
2026-09-10 16:24:30 +02:00
.\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
2026-09-10 16:24:30 +02:00
# 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.
2026-09-09 17:36:34 +02:00
**Wat het script zelf bijwerkt** — met de hand aanpassen is dus dubbel werk:
2026-09-10 16:24:30 +02:00
| Bestand | Wat | Wanneer |
|---|---|---|
| `Changelog.md` | kop `## [Niet vrijgegeven]` → `## [x.x.x] - datum` | vóór de build |
2026-09-10 16:51:06 +02:00
| Git-tag | `vx.x.x` op `HEAD`, gepusht naar origin | stap 4 |
2026-09-10 16:24:30 +02:00
| `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 alle vier zonder te schrijven. Ze blijven na een echte
release als wijziging in je working tree staan; neem ze mee in de volgende
commit.
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`.
2026-09-09 17:36:34 +02:00
### `.env.release` — token niet elke sessie opnieuw zetten
2026-09-09 12:43:11 +02:00
Staat er een `.env.release` naast `release.ps1`, dan leest het script die in
2026-09-09 17:36:34 +02:00
voordat er iets gebeurt, en hoeft stap 3 niet meer met de hand:
2026-09-09 12:43:11 +02:00
```ini
2026-09-09 12:43:11 +02:00
# .env.release — staat in .gitignore, hoort alleen op de release-machine
2026-09-09 17:36:34 +02:00
GITEA_TOKEN=<vibefinance-workstation>
2026-09-09 12:43:11 +02:00
REGISTRY_USER=<gitea-gebruiker>
```
`REGISTRY_TOKEN` hoeft er niet in: beide scripts vallen bij een ontbrekende
`REGISTRY_TOKEN` terug op `GITEA_TOKEN`. Zie [Tokens](#tokens) voor de scopes en
voor wat een 401, 403 of 404 van Gitea betekent.
2026-09-09 17:36:34 +02:00
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.
2026-09-09 12:43:11 +02:00
---
## Docker
```bash
# 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
```
2026-09-10 16:24:30 +02:00
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](#pullen-op-een-deploy-machine).
### PostgreSQL versie-upgrade
Bij een upgrade van PostgreSQL (bijv. 16 → 18) is de bestaande data niet compatibel. Stappen:
```bash
# 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 |
|---|---|
2026-09-11 14:05:55 +02:00
| `/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
```bash
# 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.
2026-09-11 14:05:55 +02:00
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:**
```bash
# 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:**
```bash
# 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.
2026-09-11 14:05:55 +02:00
### Rollen
De rolkeuze in Gebruikersbeheer biedt drie waarden, maar de backend dwingt er op
dit moment één af:
| Rol | Wat de code ermee doet |
|---|---|
2026-09-11 14:05:55 +02:00
| `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`):
```bash
# 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:
```powershell
.\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:
```powershell
.\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
```
2026-09-10 16:51:06 +02:00
> 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:
```bash
docker login vibehoogie.duckdns.org
```
2026-09-09 17:36:34 +02:00
Gebruikersnaam is je Gitea-account, wachtwoord het **access token** van deze
machine — `vibefinance-workstation`, zie [Tokens](#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:
```powershell
docker-credential-desktop list
```
### Pullen op een deploy-machine
2026-09-10 14:03:34 +02:00
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:
```bash
2026-09-10 14:03:34 +02:00
docker manifest inspect vibehoogie.duckdns.org/vibe/vibefinance-app:latest
```
2026-09-10 14:03:34 +02:00
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:
```bash
2026-09-10 14:03:34 +02:00
docker login vibehoogie.duckdns.org
# gebruikersnaam: je Gitea-account
# wachtwoord: een token met read:package
```
2026-09-10 14:03:34 +02:00
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](#tokens).
2026-09-09 17:36:34 +02:00
---
## 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`
2026-09-10 15:40:27 +02:00
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`:
```bash
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`:
```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. |
2026-09-10 14:03:34 +02:00
| `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](#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
2026-09-09 17:36:34 +02:00
release-notes. Daarvoor is het werkstation-token met `write:repository` nodig
(zie [Tokens](#tokens)), meestal al aanwezig via `.env.release`:
```powershell
2026-09-09 17:36:34 +02:00
$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.