Files
verdande/docs/configuration.md
kristianwindandClaude Opus 5.5 b9028e8f7a feat(app): fladen kan tale med en valgfri server, med sessionen som bearer
Tredje trin mod iOS-appen. I en browser deler fladen og API'et origin: hver
sti er relativ, og sessionen er en httpOnly-cookie. I appen ligger fladen i
appen selv på capacitor://localhost, og serveren er den, ejeren skrev ind.
Alt, der er forskelligt, bor i den nye `platform.js`, så browserens svar er
præcis, hvad det var før.

- `apiURL()` og `authorize()`: relative stier og cookie i en browser; den
  valgte server og `Authorization: Bearer` i appen, `credentials: 'omit'`.
- Login sender `client: app` og gemmer `session_token`; log ud fjerner det fra
  telefonen, også når serveren ikke svarede.
- Live-synk i appen beder om en billet og åbner socket'en på serveren med den.
- Første skærm i appen: "Hvilken Verdande vil du logge ind på?". Adressen
  prøves med /ping, før et adgangskodefelt vises, så en tastefejl, en router
  eller en server, der er nede, får sin egen sætning. Passkeys og opsætning af
  første konto vises ikke i appen; begge hører til serverens eget domæne.
- Billeder og downloads under /api/v1 hentes med tokenet og vises fra en blob,
  i appen og kun dér (`nativemedia.js`). Stien bevares i `data-src`, og
  `richtext.js` læser den først, så en note aldrig gemmer en blob-adresse.
- Service worker og Web Push er slået fra i appen: WKWebView har ingen af dem.

Serveren: `VERDANDE_APP_ORIGINS` regner flere origins som appens. Den findes
for E2E-projektet `app-origin`, der kører fladen fra app.localhost — samme
server, anden origin — med en Capacitor-stub og uden cookie. Prøven vælger
server, logger ind, ser et projekt fra "en anden enhed" dukke op over socket'en,
viser et billede fra en blob og logger ud.

Fire plantede fejl, hver fanget af prøven ved sit eget trin: ingen bearer,
socket uden billet, billeder ikke hentet, token efterladt ved log ud.

Dokumenterer også `VERDANDE_APP_SESSION_TTL` fra trin 1.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-23 14:16:33 +02:00

8.5 KiB

Configuration

Everything is an environment variable. There is no config file — a Rune has nowhere to put one that the operator would ever see.

Core

Variable Default What it does
VERDANDE_BASE_URL http://localhost:8080 The public address. Invite links, password resets, calendar feeds and the mail-to-task address are all built from it.
VERDANDE_ADDR :8080 Listen address.
VERDANDE_DATA_DIR /data Database, uploaded files and backups.
VERDANDE_DEV false Human-readable logs at debug level, and a relaxed origin check so a Vite dev server can connect. Never in production.
VERDANDE_EDITION full Which of the two editions this instance is. full is verdande — tasks, projects and notes. notes is urd: the task routes are not mounted at all, and the program calls itself urd. Any other value refuses to start, by name. See Two editions.

Behind a proxy

verdande keys its login rate limit and its audit log on the caller's address, so that address has to be one the caller cannot forge. A forwarded-for header is believed only when the machine that connected is a trusted proxy; a connection straight off the internet is keyed on the address it really came from.

Variable Default What it does
VERDANDE_TRUSTED_PROXIES private networks Whose X-Forwarded-For to believe, as comma-separated CIDRs. The default trusts loopback and the private ranges — the shape of a reverse proxy or tunnel sharing the host or the docker bridge. Set to none for an instance exposed directly with no proxy, so no forwarded header is ever believed.
VERDANDE_REAL_IP_HEADER X-Forwarded-For The header the client address is read from, for a trusted proxy.

!!! warning "A proxy that does not overwrite the header" The default is safe when your proxy replaces a client-supplied X-Forwarded-For with the real address — nginx, Caddy and Cloudflare Tunnel all do. If yours passes the client's own header through, narrow VERDANDE_TRUSTED_PROXIES to just your proxy's address so a visitor cannot choose the address they are rate-limited and logged as.

Variable Default What it does
VERDANDE_SESSION_TTL 720h How long a login lasts.
VERDANDE_APP_SESSION_TTL 4320h How long a login from the iOS app may sit unused. It slides: every use moves the end forward, so a phone in daily use stays signed in.
VERDANDE_INVITE_TTL 168h How long an invite link stays valid.
VERDANDE_RESET_TTL 1h How long a password-reset link stays valid.

Any Go duration: 720h, 30m, 45s.

VERDANDE_APP_ORIGINS (comma-separated) counts further origins as the iOS app's own, beside capacitor://localhost. It exists for the end-to-end suite, which runs the interface from a second host name to test it the way the app does; an instance has no reason to set it.

Mail

Variable Default What it does
VERDANDE_SMTP_HOST — Blank disables outbound mail entirely.
VERDANDE_SMTP_PORT 587
VERDANDE_SMTP_USER —
VERDANDE_SMTP_PASS —
VERDANDE_SMTP_FROM verdande@localhost Sender address. Also decides the domain of the mail-to-task address.
VERDANDE_SMTP_STARTTLS true unless port 465 Port 465 is TLS from the first byte; everything else negotiates.
VERDANDE_SMTP_INSECURE false Skip certificate verification. For a self-signed internal mail server, and nothing else.

!!! info "Running without a mail server" Perfectly supported. Invite links are shown in the app for you to send yourself, password resets are written to the log, and reminders arrive in the app and as Web Push rather than by email.

Google

Only needed if anyone will connect a Gmail mailbox or a Google calendar. One registration covers both: register an OAuth client in Google Cloud (APIs & Services → Credentials → OAuth client ID → Web application) and add both redirect URIs:

https://your-address/oauth/gmail/callback
https://your-address/oauth/calendar/callback

Then enable the APIs and scopes for whichever you want:

For Enable Scope
Mailboxes Gmail API gmail.modify
Calendar Google Calendar API calendar.readonly
Variable What it does
VERDANDE_GMAIL_CLIENT_ID The client id from Google Cloud.
VERDANDE_GMAIL_CLIENT_SECRET The client secret.

One registration serves every user on the instance; each person authorises their own mailbox from Settings → Integrations → Gmail and their own calendar from Settings → Integrations → Google Calendar. Both redirect URIs are derived from VERDANDE_BASE_URL, so they cannot drift out of step with what you registered.

!!! note "Two connections, two grants" Connecting a calendar does not disturb a working Gmail connection, and disconnecting one does not take the other with it. Google issues a refresh token per authorisation and keeps up to a hundred of them live per account, each carrying only the scopes it was granted with.

Secrets

Variable Default What it does
VERDANDE_SECRET_KEY — Encrypts the mail tokens and mailbox passwords in the database. 32 bytes of base64.

Mail tokens and mailbox passwords are encrypted at rest, and the key does not live in the database. That is the whole point: backups can be downloaded through the interface, so a copy that ends up somewhere it should not would otherwise open your mail.

Leave the variable empty and verdande writes a key to secret.key in the data directory on first start, mode 0600. Backups are a VACUUM INTO of the database alone, so the key file is not in them.

Generate one yourself if you would rather keep it out of the volume entirely:

openssl rand -base64 32

!!! warning "Lose the key and the mailboxes must be reconnected" Nothing else is lost — tasks, projects, files and history are not encrypted — but a token that cannot be decrypted cannot be used, and there is no way back from a key that is gone. If you restore a database somewhere new, bring secret.key with it, or set VERDANDE_SECRET_KEY to the same value.

A database written before there was a key is read as it stands and encrypted the next time each value is written, so turning this on costs nobody a reconnection.

Update checks

Variable Default What it does
VERDANDE_UPDATE_CHECK false Ask GitHub whether a newer release exists, and tell the administrators when one does.
VERDANDE_PANEL_URL — The Yggdrasil panel this instance runs under.
VERDANDE_PANEL_TOKEN — An API token from that panel, belonging to somebody with control of this server.
VERDANDE_PANEL_SERVER_ID — Which server to restart — the id in the panel's URL.

With the check on, administrators are told once per version — in the bell, in push and on the app's badge — rather than having to open the settings page and look. The notification leads to the page where the restart button is. With the check off nothing is fetched and nothing is said; see Notifications.

All three together let Settings → Notifications restart this instance, which is also how it picks up a new version: a container cannot replace its own image, so the panel recreates it and pulls :latest on the way. The request asks the panel for a scheduled restart rather than an immediate one — an immediate one is carried out while the request is still open, and stopping the container kills the very process waiting for the answer, so the panel never gets to the half that starts it again. Any one of the three alone does nothing, and the page names the ones that are missing.

The token is a panel credential with control over that server. Anybody who can read this environment can restart it — which is true of anybody with the host anyway — and it is never sent to a browser.

Off unless you turn it on. When on, verdande asks the public GitHub releases API at most every six hours and sends nothing about your instance — no identifier, no version, no count of anything. Only administrators are shown the result.

What is not configured here

AI providers are per-user, under settings in the app — see AI. One person's key is not the instance's key.

The VAPID keypair for Web Push is generated on first use and stored in the database. There is nothing to set.