Requested: dev-token.sh/.ps1 tokens should last 3h by default, with an argument to override the duration. Server-verified finding: STALWART_RECOVERY_ADMIN (the break-glass account docker-compose.yml and the scripts used) always issues OAuth tokens with a fixed 1h expiry, regardless of the server's configured accessTokenExpiry — confirmed against the live container, including after changing the setting and restarting. Confirmed x:ApiKey objects, by contrast, support an arbitrary expiresAt set per request, and their secret works directly as a bearer token. Add scripts/dev-server-init.sh (+ .ps1): a one-time, idempotent setup step that completes the server's bootstrap wizard (default domain, no TLS certificate request), creates a real "devadmin" admin account, and sets the server's default OAuth token lifetime to 3h. Rework dev-token.sh/.ps1 to authenticate as devadmin and create an x:ApiKey with a caller-supplied expiry (`dev-token.sh 1800` for 30 minutes, defaults to 10800s/3h) instead of running the OAuth PKCE flow against the recovery account. Verified end-to-end against a fresh container, including a real browser session against the running WebUI. Also includes an incidental package-lock.json sync (was still pinned to v1.0.8 / stale dependency ranges from before the upstream merge).
5.1 KiB
Development
How to run a local Stalwart test server and develop this WebUI against it. This file is meant to be read by humans and AI coding agents alike — see AGENTS.md for the rules that also apply while doing this.
Prerequisites
- Node.js 18+
- Docker (with the
docker composeCLI plugin)
1. Start a local test server
npm run dev:server
This runs docker compose up -d, which starts a disposable Stalwart
instance (docker-compose.yml) with:
- The management/JMAP HTTP API on
http://localhost:8080(the only port the WebUI dev proxy needs — seeserver.proxyinvite.config.ts). - Mail protocol ports (SMTP/IMAP/POP3/ManageSieve) exposed too, only needed if you're testing actual mail flows, not just admin UI screens.
- A fixed admin account baked in via
STALWART_RECOVERY_ADMIN:admin@example.org/c8321iEscHDy0GWV. Disposable dev credentials only — never reuse them for anything real. - Named Docker volumes (
stalwart-etc,stalwart-data) so data survives a restart. Data persists until you tear the volumes down.
Useful companions:
npm run dev:server:logs # tail the container's logs
npm run dev:server:down # stop it (add `-v` via `docker compose down -v` to also wipe data)
The server takes a couple of seconds to come up; docker compose logs stalwart
will show Network listener started ... localPort = 8080 once it's ready.
2. Initialize the server (first time only)
The container starts in Stalwart's bootstrap mode, which only allows
signing in as the break-glass STALWART_RECOVERY_ADMIN account — real
accounts and most settings aren't usable yet. Run once per fresh volume:
# Windows / PowerShell
pwsh ./scripts/dev-server-init.ps1
# Linux / macOS / any POSIX shell (including most AI agent sandboxes)
bash ./scripts/dev-server-init.sh
This completes the bootstrap wizard (default domain example.org, no TLS
certificate request — safe for local/offline use), creates a real
devadmin@example.org admin account, and sets the server's default OAuth
access token lifetime to 3 hours. It's idempotent — safe to re-run, it
no-ops once the server is already bootstrapped. You only need to re-run it
after docker compose down -v (which wipes the volumes).
3. Get an access token
For local development it's simpler to skip interactive login and use a
bearer token directly via VITE_ACCESS_TOKEN (see .env.development).
# Windows / PowerShell
pwsh ./scripts/dev-token.ps1 # 3 hour token (server default)
pwsh ./scripts/dev-token.ps1 -DurationSeconds 1800 # custom duration (30 min)
# Linux / macOS / any POSIX shell (including most AI agent sandboxes)
bash ./scripts/dev-token.sh # 3 hour token
bash ./scripts/dev-token.sh 1800 # custom duration (30 min)
Both scripts authenticate as the devadmin account created in step 2 and
create a Stalwart API key with the requested expiry (default 3 hours,
overridable per invocation — this is a genuine per-request duration, not
a global setting), then write its secret to .env.development.local
(gitignored, never committed) as VITE_ACCESS_TOKEN. Re-run the script
and restart npm run dev once the token expires (the UI starts returning
401s).
The STALWART_RECOVERY_ADMIN account is intentionally not used here: it's
a break-glass credential and its tokens always expire in a fixed 1 hour
regardless of server configuration, so it can't honor a custom duration.
4. Run the WebUI
npm install # first time only
npm run dev
Open http://localhost:5173. You should land directly in the admin panel
(no login screen) since VITE_ACCESS_TOKEN is set.
5. Verify your change
npm run typecheck
npm run lint
npm test
npm run build
For UI changes, actually look at the running app (browser or a browser automation tool) — passing typecheck/lint/tests proves the code compiles and existing behavior didn't regress, it doesn't prove the new UI works.
Resetting the test server
To start from a completely clean server (e.g. to re-test first-run behavior):
npm run dev:server:down
docker compose down -v # also removes the stalwart-etc/stalwart-data volumes
npm run dev:server
bash ./scripts/dev-server-init.sh # re-run: fresh volume needs bootstrapping again
Notes for AI agents
- This whole workflow (steps 1–4) is scriptable end-to-end without a
browser:
npm run dev:server, thenbash scripts/dev-server-init.sh(first time only), thenbash scripts/dev-token.sh, then the app is reachable athttp://localhost:5173withVITE_ACCESS_TOKENalready set. Verify backend connectivity directly withcurl, e.g.curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/jmap/session. - Read AGENTS.md before touching anything under
src/— the schema-fidelity rule applies to all development, local test server or not. - Don't commit
.env.development.local(it holds a live token) or leave the dev container running unexpectedly —npm run dev:server:downwhen you're done.