# VNCmail+ — setup & deploy runbook VNCmail+ is VNC's fork of [Bulwark](https://github.com/bulwarkmail/webmail), a Next.js (App Router) JMAP webmail client for **Stalwart**. Stalwart is the source of truth; VNCmail+ is the UI. It deploys as a **container on Kubernetes (microk8s)** at `vncmail.sandbox.vnc.de` — see **[deploy/k8s/](deploy/k8s/README.md)**. > **License:** AGPL-3.0. Serving a modified VNCmail+ to users over the network > obligates VNC to offer those users the corresponding source. Keeping this fork > public (with a "Source" link in the imprint/UI) satisfies that. Loop in legal > before a public/customer-facing launch if a closed fork is ever desired. ## Architecture — why a container, not Vercel - Bulwark is a **stateful, long-lived server**: it persists settings-sync, admin config/state, and telemetry to a **local data directory** (`/app/data/*`). - **Vercel serverless was tried and dropped** — its filesystem is read-only except `/tmp`, so Bulwark's `mkdir ./data` crashes (`ENOENT /var/task/data`). You cannot point its data dirs at a remote host either (they're POSIX paths, not URLs). Bulwark's native model is a container + persistent volumes. - So VNCmail+ runs as a Docker image with **4 persistent volumes**, exactly like the existing `bulwark.sandbox.vnc.de`. - JMAP calls go through **server-side `/api/*` routes** (`proxy.ts`) → server-to- server to Stalwart, **no browser CORS**. Config is **runtime-read**. ## Branches (dev-first) | Branch | Role | |--------|------| | `main` | **Production.** Only updated by `git merge --ff-only dev`, then an explicit manual promote in CI. No prod environment exists yet — see "CI/CD" below. | | `dev` | Integration + QA — default working branch. Every push auto-builds and auto-deploys to the sandbox (`vncmail.sandbox.vnc.de`). | | `vnc/*`| Feature branches for UI work (branch off `dev`, MR into `dev` — required, gated by CI). | All VNC customization lives under `vnc/` (see `vnc/VNC-CHANGES.md`). ## CI/CD — GitLab (canonical), Vercel-style dev→prod Multiple developers work on this repo now, so `.gitlab-ci.yml` on [gitlab.vnc.biz](https://gitlab.vnc.biz/gitlab-instance-b9b5cf2f/vncmail-plus) (the canonical remote — GitHub `origin` is a passive mirror, not where CI or deploys happen) drives the whole flow: 1. **MR into `dev`** → `verify` stage runs (typecheck/lint/unit test/build). Required check — no push, no deploy. This is the multi-developer gate. 2. **Merge to `dev`** → `build` pushes one image, `registry.gitlab.vnc.biz/.../vncmail-plus:sha-`, then `deploy-dev` applies it to the sandbox automatically. No approval needed — dev always deploys first. 3. **Merge to `main`** (fast-forward only, see below) → a `promote` job appears, `when: manual`, gated behind a protected `production` GitLab environment. It **never rebuilds** — it retags the exact image already running on dev (registry-side copy, same digest) and would apply it to a `vncmail-prod` namespace pinned to that digest. Historical note: the old `-dev`/`-beta` GHCR image-name split (`.github/workflows/docker-publish.yml`) is retired by this — one image name now, environment lives only in the tag. **Production doesn't exist yet.** `deploy/k8s/overlays/prod/` is scaffolded (placeholder hostname, placeholder `JMAP_SERVER_URL` — there's no prod Stalwart instance to point it at either) but inert: the `promote` job's real `kubectl apply` step is deliberately left as a TODO in `.gitlab-ci.yml` until a real hostname is decided and prod Stalwart exists. Standing up the runner/ RBAC/registry this pipeline needs is an infra prerequisite, not something CI itself does — see the pipeline design doc referenced in `deploy/k8s/README.md`. ## Deploy (Kubernetes / microk8s) Full runbook: **[deploy/k8s/README.md](deploy/k8s/README.md)**. In short: 1. CI (above) builds and pushes the image, one name/many tags, to GitLab's registry. 2. `kubectl apply -k deploy/k8s/overlays/dev` (or `overlays/prod`, once real) — base manifests (namespace, 4 PVCs, deployment, service, ingress) live in `deploy/k8s/base/`, environment differences (namespace, hostname, replica count) are overlay patches. 3. DNS + a `secret.yaml` (from the overlay's `secret.example.yaml`, gitignored, created once by hand — CI never manages secret contents) + an image-pull secret are the remaining manual, human, one-time steps per environment. Runs alongside the existing `bulwark.sandbox.vnc.de`. Match your cluster's StorageClass / IngressClass / cert issuer to bulwark's (see the runbook). ## Deploy workflow (dev-first — ALWAYS) Same flow as every other VNC/SRC repo, now enforced structurally by CI rather than by convention: 1. Work on `dev` (or `vnc/*` → MR into `dev`, CI-gated). Merge → auto-builds and auto-deploys to `vncmail.sandbox.vnc.de`. QA there. 2. **Promote to production only on explicit go-live** — merge `dev` → `main`: ```bash git log dev..main # MUST be empty — main must have nothing dev lacks (else prod would revert) git checkout main && git merge --ff-only dev git push gitlab main # never GitHub — opens the manual `promote` job, does not run it git checkout dev ``` Then click `promote` in the GitLab pipeline UI (protected `production` environment — requires the right role) once prod actually exists (see "CI/CD" above). Never push straight to `main`. Never let a dev→main merge silently revert prod. ## Syncing upstream (Bulwark releases) Bring upstream into `dev` (NOT main), integrate + QA on the dev image, then promote as above: ```bash git fetch upstream git checkout dev && git merge upstream/main # resolve conflicts via vnc/VNC-CHANGES.md; QA on preview ``` ## Auth Basic auth via Stalwart is the default — users sign in with their `@sandbox.vnc.de` address + password; VNCmail+ authenticates them over JMAP. No extra config. (SSO via vncdirectory/OIDC is a later option — see `vnc/vercel.env.template`.)