New scripts/dev-seed.sh (+ .ps1), run after dev-server-init: seeds the test server with Users (alice, bob, carol — spanning User/Admin roles, group membership, an alias), Groups (engineering, marketing), Mailing Lists (newsletter, support), and custom Roles (Support Agent, Read-only Auditor), so each admin panel screen has real data to test against instead of an empty list. Idempotent (skips if 'alice' already exists), documented as workflow step 3 in DEVELOPMENT.md (renumbering the steps after it) and listed in AGENTS.md. Verified end-to-end with both scripts against a fresh server, including idempotency and the resulting UI (Accounts, Groups, Mailing Lists, Roles all populated as expected).
191 lines
7.4 KiB
Markdown
191 lines
7.4 KiB
Markdown
# 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](AGENTS.md) for the rules that also apply while doing this.
|
||
|
||
## Prerequisites
|
||
|
||
- Node.js 18+
|
||
- Docker (with the `docker compose` CLI plugin)
|
||
|
||
## 1. Start a local test server
|
||
|
||
```bash
|
||
npm run dev:server
|
||
```
|
||
|
||
This runs `docker compose up -d`, which starts a disposable Stalwart
|
||
instance ([`docker-compose.yml`](docker-compose.yml)) with:
|
||
|
||
- The management/JMAP HTTP API on `http://localhost:8080` (the only port
|
||
the WebUI dev proxy needs — see `server.proxy` in
|
||
[`vite.config.ts`](vite.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:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
# 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, sets the server's default OAuth
|
||
access token lifetime to 3 hours, and points the `x:Application` record
|
||
serving `/admin`/`/account` at this fork's description and release URL
|
||
(cosmetic — otherwise Settings > Web Applications' "Active WebUI" card
|
||
shows Stalwart's default "Stalwart Web Interface" / upstream release URL,
|
||
even though what's actually running is this fork served by Vite). 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. Seed sample data (optional)
|
||
|
||
```bash
|
||
# Windows / PowerShell
|
||
pwsh ./scripts/dev-seed.ps1
|
||
|
||
# Linux / macOS / any POSIX shell (including most AI agent sandboxes)
|
||
bash ./scripts/dev-seed.sh
|
||
```
|
||
|
||
Populates Users, Groups, Mailing Lists, and Roles with sample data so
|
||
every admin panel screen has something to test against:
|
||
|
||
- **Users**: `alice@example.org` / `AlicePass123!` (User role, in
|
||
engineering, 1 alias), `bob@example.org` / `BobPass123!` (User role, in
|
||
engineering + marketing), `carol@example.org` / `CarolPass123!` (Admin
|
||
role, in marketing) — alongside the `devadmin`/`admin` accounts from
|
||
step 2.
|
||
- **Groups**: `engineering`, `marketing`.
|
||
- **Mailing lists**: `newsletter@example.org`, `support@example.org`.
|
||
- **Roles**: `Support Agent`, `Read-only Auditor` (custom, alongside the
|
||
built-in System Administrator/Tenant Administrator/Group/User roles).
|
||
|
||
Idempotent — does nothing if `alice` already exists. Only needed once per
|
||
fresh volume, same as step 2.
|
||
|
||
## 4. 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`).
|
||
|
||
```bash
|
||
# 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.
|
||
|
||
## 5. Run the WebUI
|
||
|
||
```bash
|
||
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.
|
||
|
||
## 6. Verify your change
|
||
|
||
```bash
|
||
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):
|
||
|
||
```bash
|
||
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
|
||
bash ./scripts/dev-seed.sh # optional: re-seed sample data too
|
||
```
|
||
|
||
## Troubleshooting
|
||
|
||
- **UI shows a login screen instead of the admin panel, or the page fails
|
||
to load data**: `npm run dev` only reads `.env.development.local` at
|
||
startup. If you regenerate a token, or restart the Docker container,
|
||
while `npm run dev` is already running, stop it (Ctrl+C) and start it
|
||
again — it won't pick up the new token or reconnect on its own.
|
||
- **`Failed to fetch` / connection refused in the browser console**: the
|
||
test server isn't running or isn't ready yet. Check with
|
||
`docker ps --filter name=stalwart-webui-dev` and
|
||
`npm run dev:server:logs`; wait for `Network listener started ...
|
||
localPort = 8080` before retrying.
|
||
- **401s after everything was working**: your token expired. Re-run
|
||
`scripts/dev-token.sh` (or `.ps1`) and restart `npm run dev`.
|
||
- **`scripts/dev-server-init.sh` fails with connection errors**: the
|
||
container needs a few seconds after `npm run dev:server` before it
|
||
accepts requests — the script retries for ~30s, but if your machine is
|
||
slow, just re-run it.
|
||
|
||
## Notes for AI agents
|
||
|
||
- This whole workflow (steps 1–5) is scriptable end-to-end without a
|
||
browser: `npm run dev:server`, then `bash scripts/dev-server-init.sh`
|
||
and `bash scripts/dev-seed.sh` (first time only), then
|
||
`bash scripts/dev-token.sh`, then the app is reachable at
|
||
`http://localhost:5173` with `VITE_ACCESS_TOKEN` already set. Verify
|
||
backend connectivity directly with `curl`, e.g.
|
||
`curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/jmap/session`.
|
||
- Read [AGENTS.md](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:down` when
|
||
you're done.
|