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.
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'smkdir ./datacrashes (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:
- MR into
dev→verifystage runs (typecheck/lint/unit test/build). Required check — no push, no deploy. This is the multi-developer gate. - Merge to
dev→buildpushes one image,registry.gitlab.vnc.biz/.../vncmail-plus:sha-<sha>, thendeploy-devapplies it to the sandbox automatically. No approval needed — dev always deploys first. - Merge to
main(fast-forward only, see below) → apromotejob appears,when: manual, gated behind a protectedproductionGitLab environment. It never rebuilds — it retags the exact image already running on dev (registry-side copy, same digest) and would apply it to avncmail-prodnamespace 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:
- CI (above) builds and pushes the image, one name/many tags, to GitLab's registry.
kubectl apply -k deploy/k8s/overlays/dev(oroverlays/prod, once real) — base manifests (namespace, 4 PVCs, deployment, service, ingress) live indeploy/k8s/base/, environment differences (namespace, hostname, replica count) are overlay patches.- DNS + a
secret.yaml(from the overlay'ssecret.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:
- Work on
dev(orvnc/*→ MR intodev, CI-gated). Merge → auto-builds and auto-deploys tovncmail.sandbox.vnc.de. QA there. - Promote to production only on explicit go-live — merge
dev→main:Then clickgit 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 devpromotein the GitLab pipeline UI (protectedproductionenvironment — requires the right role) once prod actually exists (see "CI/CD" above). Never push straight tomain. 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.)