Files
SRCmail/VNCMAIL-SETUP.md
T
Bernd Rodler 3512f935d1 feat(ci): GitLab CI/CD dev→prod pipeline, kustomize base+overlays
Multiple developers now work on this repo, and the only working deploy
trigger required pushing to GitHub - which contradicts the standing
GitLab-canonical policy for this repo - while every actual deploy was a
manual kubectl run against one environment (no prod exists at all).

Restructures deploy/k8s/ into base/ + overlays/{dev,prod}: overlays/dev
is a verified byte-for-byte no-op for the live sandbox (kubectl kustomize
diff against the old flat layout is empty), overlays/prod is scaffolded
but inert (placeholder hostname + JMAP_SERVER_URL, since neither a prod
hostname decision nor a prod Stalwart exist yet). deploy/k8s/ca/ (the
EJBCA internal CA) is untouched and never referenced by either overlay.

Adds .gitlab-ci.yml: verify (MR gate, no push/deploy) -> build+deploy-dev
(automatic on push to dev, one image name/tag-only environments, fixing
the old -dev/-beta naming split) -> promote (manual, protected
`production` environment, retags the exact dev digest via
`docker buildx imagetools create` - never rebuilds - and is left as a
documented TODO for the actual `kubectl apply` until prod is real).

Updates VNCMAIL-SETUP.md and deploy/k8s/README.md to describe the new
flow and correct the aspirational promotion description that assumed a
"production image" CI never actually built.

Also fixes a pre-existing lint error (no-control-regex false positive on
an intentional DN-sanitizing character class in lib/smime-ca/ejbca.ts)
that was blocking this commit's pre-commit hook - unrelated to this
change otherwise, confirmed already present on dev before this branch.

Runner/RBAC/registry setup is an infra prerequisite this commit cannot
provide - documented in the pipeline plan, not part of this diff.
2026-08-05 11:43:55 +02:00

5.9 KiB

VNCmail+ — setup & deploy runbook

VNCmail+ is VNC's fork of Bulwark, 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/.

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 (the canonical remote — GitHub origin is a passive mirror, not where CI or deploys happen) drives the whole flow:

  1. MR into devverify stage runs (typecheck/lint/unit test/build). Required check — no push, no deploy. This is the multi-developer gate.
  2. Merge to devbuild pushes one image, registry.gitlab.vnc.biz/.../vncmail-plus:sha-<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. 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 devmain:
    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:

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.)