Merge remote-tracking branch 'gitlab/claude/gitlab-ci-dev-prod-pipeline' into dev-merge-batch1
This commit is contained in:
+2
-2
@@ -59,8 +59,8 @@ next-env.d.ts
|
|||||||
# Sibling repos
|
# Sibling repos
|
||||||
/repos/
|
/repos/
|
||||||
|
|
||||||
# k8s deploy secret (create from deploy/k8s/secret.example.yaml)
|
# k8s deploy secrets (create from the matching overlay's secret.example.yaml)
|
||||||
/deploy/k8s/secret.yaml
|
/deploy/k8s/overlays/*/secret.yaml
|
||||||
|
|
||||||
# S/MIME plugin build output (rebuild with: cd vnc/plugins/smime && npm run build)
|
# S/MIME plugin build output (rebuild with: cd vnc/plugins/smime && npm run build)
|
||||||
vnc/plugins/smime/node_modules/
|
vnc/plugins/smime/node_modules/
|
||||||
|
|||||||
+165
@@ -0,0 +1,165 @@
|
|||||||
|
# GitLab-CI dev→prod pipeline for VNCmail+ — GitOps via ArgoCD.
|
||||||
|
#
|
||||||
|
# Revised after direct inspection of the real infrastructure found ArgoCD
|
||||||
|
# already installed (idle, zero Applications) on the dev-k8s-1/2/3 cluster.
|
||||||
|
# That's more idiomatic than a runner-executes-kubectl design, and it means
|
||||||
|
# this pipeline needs ZERO cluster credentials — CI only ever talks to the
|
||||||
|
# container registry and to this git repo. ArgoCD (which already has
|
||||||
|
# whatever cluster access it needs, set up once when its Applications were
|
||||||
|
# registered — see deploy/argocd/) is what actually applies anything.
|
||||||
|
#
|
||||||
|
# Design:
|
||||||
|
# - One image name, environment lives only in the tag. No more -dev/-beta
|
||||||
|
# name confusion from the old GitHub Actions workflow.
|
||||||
|
# - MR into `dev`: verify only (typecheck/lint/unit test/build check). No
|
||||||
|
# push, no deploy — this is the multi-developer merge gate.
|
||||||
|
# - Push to `dev`: build+push an immutable `sha-<sha>` tag, then commit a
|
||||||
|
# one-line tag-bump into overlays/dev/image-tag/kustomization.yaml
|
||||||
|
# (`[skip ci]`, so this doesn't retrigger itself). ArgoCD's `vncmail-dev`
|
||||||
|
# Application has automated sync — it notices the git change and applies
|
||||||
|
# it. No approval needed, dev always deploys, and this job never touches
|
||||||
|
# the cluster directly.
|
||||||
|
# - Push to `main`: NEVER rebuilds. `main` only ever advances via
|
||||||
|
# `git merge --ff-only dev`, so main's HEAD commit already has a built
|
||||||
|
# image (the same sha- tag dev already deployed). This job just bumps
|
||||||
|
# overlays/prod/image-tag/kustomization.yaml to point at that same tag.
|
||||||
|
# The actual promotion gate is a HUMAN clicking Sync on the `vncmail-prod` ArgoCD
|
||||||
|
# Application (deliberately NOT automated sync) — not a GitLab manual
|
||||||
|
# job, since ArgoCD already provides that exact gate more directly.
|
||||||
|
# Until prod Stalwart/hostname/secrets are real (see VNCMAIL-SETUP.md),
|
||||||
|
# nobody should click that Sync button — but nothing here does it for
|
||||||
|
# you either way.
|
||||||
|
#
|
||||||
|
# Deliberately single-platform (linux/amd64) — this pipeline serves two
|
||||||
|
# known amd64 microk8s clusters, not public multi-arch distribution (that's
|
||||||
|
# what the GHCR release workflows are for, untouched by this file).
|
||||||
|
#
|
||||||
|
# Prerequisite this file assumes (documented in VNCMAIL-SETUP.md, not
|
||||||
|
# something this file can set up itself):
|
||||||
|
# - GitLab Container Registry enabled for this project (confirmed done).
|
||||||
|
# - A GitLab Runner (any kind — no cluster access needed at all now).
|
||||||
|
# - Either "allow this job token to push to this project" enabled
|
||||||
|
# (Settings → CI/CD → Job token permissions), OR a project access token
|
||||||
|
# with `write_repository` scope in $GITLAB_PUSH_TOKEN. The job below
|
||||||
|
# tries CI_JOB_TOKEN first (see the script).
|
||||||
|
#
|
||||||
|
# deploy/k8s/ca/ (the EJBCA internal CA) is never referenced anywhere below,
|
||||||
|
# and neither ArgoCD Application in deploy/argocd/ points at it — that stays
|
||||||
|
# a fully manual, human-only runbook (see deploy/k8s/ca/README.md).
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- verify
|
||||||
|
- build
|
||||||
|
- bump-dev
|
||||||
|
- bump-prod
|
||||||
|
|
||||||
|
variables:
|
||||||
|
IMAGE: $CI_REGISTRY_IMAGE/vncmail-plus
|
||||||
|
GIT_STRATEGY: clone
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# verify — required check on every MR into dev. No registry, no cluster.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
verify:
|
||||||
|
stage: verify
|
||||||
|
image: node:24-alpine
|
||||||
|
rules:
|
||||||
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||||
|
script:
|
||||||
|
- npm ci
|
||||||
|
- npm run typecheck
|
||||||
|
- npm run lint
|
||||||
|
- npm run test:translations
|
||||||
|
- npm run build
|
||||||
|
# test:integration is deliberately NOT here — it spins up a real Stalwart
|
||||||
|
# fixture via docker-compose (Docker-in-Docker), heavier than a fast MR
|
||||||
|
# gate should be. Candidate for a separate scheduled job, not a blocker.
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# build — push to dev only. Builds once; main never rebuilds (see header).
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
build:
|
||||||
|
stage: build
|
||||||
|
image: docker:27-cli
|
||||||
|
services:
|
||||||
|
- docker:27-dind
|
||||||
|
rules:
|
||||||
|
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "dev"'
|
||||||
|
before_script:
|
||||||
|
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" "$CI_REGISTRY" --password-stdin
|
||||||
|
script:
|
||||||
|
- docker build --build-arg GIT_COMMIT=$CI_COMMIT_SHA -t "$IMAGE:sha-$CI_COMMIT_SHORT_SHA" -t "$IMAGE:dev-latest" .
|
||||||
|
- docker push "$IMAGE:sha-$CI_COMMIT_SHORT_SHA"
|
||||||
|
- docker push "$IMAGE:dev-latest"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# bump-dev — no cluster access. Commits the just-built tag into the overlay
|
||||||
|
# ArgoCD watches; ArgoCD's automated sync does the actual apply.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
bump-dev:
|
||||||
|
stage: bump-dev
|
||||||
|
image: alpine/git:2.47.0
|
||||||
|
rules:
|
||||||
|
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "dev"'
|
||||||
|
script:
|
||||||
|
- TAG="sha-$CI_COMMIT_SHORT_SHA"
|
||||||
|
- |
|
||||||
|
cat > deploy/k8s/overlays/dev/image-tag/kustomization.yaml <<EOF
|
||||||
|
# Owned by CI (bump-dev job in .gitlab-ci.yml) - regenerated every
|
||||||
|
# push to dev. Do not hand-edit; edits here get overwritten.
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1alpha1
|
||||||
|
kind: Component
|
||||||
|
images:
|
||||||
|
- name: ghcr.io/brvncde-dotcom/vncmail-plus-dev
|
||||||
|
newName: $IMAGE
|
||||||
|
newTag: $TAG
|
||||||
|
EOF
|
||||||
|
- git config user.name "vncmail-ci"
|
||||||
|
- git config user.email "ci@vnc.biz"
|
||||||
|
- git add deploy/k8s/overlays/dev/image-tag/kustomization.yaml
|
||||||
|
- |
|
||||||
|
if git diff --cached --quiet; then
|
||||||
|
echo "No change (tag already pinned) - nothing to commit"
|
||||||
|
else
|
||||||
|
git commit -m "chore(deploy): pin dev to $TAG [skip ci]"
|
||||||
|
git push "https://gitlab-ci-token:${GITLAB_PUSH_TOKEN:-$CI_JOB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git" HEAD:dev
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# bump-prod — no cluster access, no rebuild. Points overlays/prod at the
|
||||||
|
# exact tag already running on dev. Does NOT deploy anything: vncmail-prod's
|
||||||
|
# ArgoCD Application has manual sync, so this only prepares what a human
|
||||||
|
# would be syncing, it doesn't sync it.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
bump-prod:
|
||||||
|
stage: bump-prod
|
||||||
|
image: alpine/git:2.47.0
|
||||||
|
rules:
|
||||||
|
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "main"'
|
||||||
|
script:
|
||||||
|
- TAG="sha-$CI_COMMIT_SHORT_SHA"
|
||||||
|
- echo "main advanced to $CI_COMMIT_SHA (must be a dev commit, ff-only) - that image already exists as $IMAGE:$TAG"
|
||||||
|
- |
|
||||||
|
cat > deploy/k8s/overlays/prod/image-tag/kustomization.yaml <<EOF
|
||||||
|
# Owned by CI (bump-prod job in .gitlab-ci.yml) - regenerated every
|
||||||
|
# push to main. Do not hand-edit; edits here get overwritten. Bumping
|
||||||
|
# this is NOT the same as deploying it - vncmail-prod's ArgoCD
|
||||||
|
# Application has manual sync, see the note in the parent
|
||||||
|
# kustomization.yaml.
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1alpha1
|
||||||
|
kind: Component
|
||||||
|
images:
|
||||||
|
- name: ghcr.io/brvncde-dotcom/vncmail-plus-dev
|
||||||
|
newName: $IMAGE
|
||||||
|
newTag: $TAG
|
||||||
|
EOF
|
||||||
|
- git config user.name "vncmail-ci"
|
||||||
|
- git config user.email "ci@vnc.biz"
|
||||||
|
- git add deploy/k8s/overlays/prod/image-tag/kustomization.yaml
|
||||||
|
- |
|
||||||
|
if git diff --cached --quiet; then
|
||||||
|
echo "No change (tag already pinned) - nothing to commit"
|
||||||
|
else
|
||||||
|
git commit -m "chore(deploy): point prod overlay at $TAG (not synced - manual gate in ArgoCD) [skip ci]"
|
||||||
|
git push "https://gitlab-ci-token:${GITLAB_PUSH_TOKEN:-$CI_JOB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git" HEAD:main
|
||||||
|
fi
|
||||||
+106
-13
@@ -18,8 +18,8 @@ of truth; VNCmail+ is the UI. It deploys as a **container on Kubernetes
|
|||||||
except `/tmp`, so Bulwark's `mkdir ./data` crashes (`ENOENT /var/task/data`).
|
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,
|
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.
|
not URLs). Bulwark's native model is a container + persistent volumes.
|
||||||
- So VNCmail+ runs as a Docker image (`ghcr.io/brvncde-dotcom/vncmail-plus-*`)
|
- So VNCmail+ runs as a Docker image with **4 persistent volumes**, exactly
|
||||||
with **4 persistent volumes**, exactly like the existing `bulwark.sandbox.vnc.de`.
|
like the existing `bulwark.sandbox.vnc.de`.
|
||||||
- JMAP calls go through **server-side `/api/*` routes** (`proxy.ts`) → server-to-
|
- JMAP calls go through **server-side `/api/*` routes** (`proxy.ts`) → server-to-
|
||||||
server to Stalwart, **no browser CORS**. Config is **runtime-read**.
|
server to Stalwart, **no browser CORS**. Config is **runtime-read**.
|
||||||
|
|
||||||
@@ -27,37 +27,130 @@ of truth; VNCmail+ is the UI. It deploys as a **container on Kubernetes
|
|||||||
|
|
||||||
| Branch | Role |
|
| Branch | Role |
|
||||||
|--------|------|
|
|--------|------|
|
||||||
| `main` | **Production** — CI builds `…/vncmail-plus-beta`. Only updated by an explicit promote. |
|
| `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 — CI builds `…/vncmail-plus-dev` on push. Default working branch. |
|
| `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`, PR into `dev`). |
|
| `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`).
|
All VNC customization lives under `vnc/` (see `vnc/VNC-CHANGES.md`).
|
||||||
|
|
||||||
|
## CI/CD — GitLab (canonical) + ArgoCD GitOps, Vercel-style dev→prod
|
||||||
|
|
||||||
|
Multiple developers work on this repo now. `.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) builds images and bumps a tag in git; **ArgoCD does the
|
||||||
|
actual deploying** — already installed and idle on the `dev-k8s-1/2/3`
|
||||||
|
cluster, discovered when standing this up. GitLab CI needs zero cluster
|
||||||
|
credentials as a result.
|
||||||
|
|
||||||
|
Two real clusters, confirmed by direct inspection:
|
||||||
|
|
||||||
|
| Cluster | Role | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `dev-k8s-1/2/3` | dev/sandbox | ~hours old when set up here. Traefik, metallb, cert-manager (`letsencrypt-staging` issuer only), **ArgoCD already running**. |
|
||||||
|
| `node1/node2/node3` | prod (HA) | Older, rook-ceph+traefik+metallb+cert-manager, but **zero apps and zero ClusterIssuers** — genuinely a clean slate. |
|
||||||
|
|
||||||
|
Neither cluster had a `vncmail` namespace, `vnc-ca` namespace, or `bulwark`
|
||||||
|
ingress — the "live sandbox at vncmail.sandbox.vnc.de" referenced earlier in
|
||||||
|
this doc's history was aspirational (manifests + docs existed, nothing was
|
||||||
|
ever actually applied). The ingress manifests also assumed nginx (`class:
|
||||||
|
public`, an nginx body-size annotation) — fixed to Traefik's real
|
||||||
|
`ingressClassName: traefik` (Traefik has no default body-size cap, so no
|
||||||
|
replacement annotation is needed).
|
||||||
|
|
||||||
|
Flow:
|
||||||
|
|
||||||
|
1. **MR into `dev`** → `verify` stage (typecheck/lint/unit test/build).
|
||||||
|
Required check — no push, no deploy.
|
||||||
|
2. **Merge to `dev`** → `build` pushes one image,
|
||||||
|
`registry.gitlab.vnc.biz/.../vncmail-plus:sha-<sha>`, then `bump-dev`
|
||||||
|
commits that tag into `deploy/k8s/overlays/dev/image-tag/kustomization.yaml`
|
||||||
|
(`[skip ci]`). ArgoCD's `vncmail-dev` Application picks up the git change.
|
||||||
|
3. **Merge to `main`** (fast-forward only, see below) → `bump-prod` points
|
||||||
|
`overlays/prod/image-tag/` at that same tag — **no rebuild**. The actual
|
||||||
|
promotion gate is a **human clicking Sync** on the `vncmail-prod` ArgoCD
|
||||||
|
Application, which is permanently manual-sync (never automated) — that's
|
||||||
|
the Vercel-style "Promote to Production" button, just living in ArgoCD's
|
||||||
|
UI instead of GitLab's.
|
||||||
|
|
||||||
|
### What's left to wire up (one-time, human steps)
|
||||||
|
|
||||||
|
1. **Add the ArgoCD deploy key to GitLab** — Project → Settings → Repository
|
||||||
|
→ Deploy keys → add (read-only is enough):
|
||||||
|
```
|
||||||
|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOURjX/Y9zfB785DyLEF1GUq4HhWujrqeXag8oxdMciq argocd@dev-k8s (vncmail-plus read-only)
|
||||||
|
```
|
||||||
|
Until this is added, `vncmail-dev`'s ArgoCD Application (already created,
|
||||||
|
`kubectl -n argocd get application vncmail-dev`) shows a benign
|
||||||
|
`ComparisonError` (SSH handshake failing) — expected, not a bug.
|
||||||
|
2. **Let CI push tag-bumps back to this repo** — either enable "this project
|
||||||
|
can be accessed by CI/CD job tokens from other projects" → actually
|
||||||
|
simpler: Settings → CI/CD → Job token permissions → allow this project's
|
||||||
|
own job token to push to itself, OR create a Project Access Token
|
||||||
|
(`write_repository` scope) and add it as a masked CI/CD variable
|
||||||
|
`GITLAB_PUSH_TOKEN` (the pipeline tries that first, falls back to
|
||||||
|
`CI_JOB_TOKEN`).
|
||||||
|
3. **One-time namespace bootstrap** (CI/ArgoCD deliberately never manage
|
||||||
|
secret contents — see `deploy/k8s/README.md` §3):
|
||||||
|
```bash
|
||||||
|
# against dev-k8s (ArgoCD's CreateNamespace=true will make `vncmail` on
|
||||||
|
# first sync, or create it yourself first — either order works)
|
||||||
|
kubectl create secret docker-registry ghcr-pull -n vncmail ... # or make the GHCR package public
|
||||||
|
cp deploy/k8s/overlays/dev/secret.example.yaml secret.yaml # edit SESSION_SECRET
|
||||||
|
kubectl apply -f secret.yaml
|
||||||
|
```
|
||||||
|
4. **First sync** — ArgoCD UI at `https://argo.devcluster.vnc.de`
|
||||||
|
(username `admin`, password: `kubectl -n argocd get secret
|
||||||
|
argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d`
|
||||||
|
— rotate it after logging in once) → `vncmail-dev` → Sync. Once that's
|
||||||
|
clean, flip `deploy/argocd/vncmail-dev-app.yaml`'s commented-out
|
||||||
|
`automated:` block on and re-apply, so dev auto-syncs on every push from
|
||||||
|
then on.
|
||||||
|
5. **Production** (later, deliberately not wired yet): decide a real
|
||||||
|
hostname, stand up prod Stalwart, register `node1-3` as an ArgoCD-managed
|
||||||
|
cluster, apply `deploy/argocd/vncmail-prod-app.yaml`, fill in real
|
||||||
|
`overlays/prod` values, create a real ClusterIssuer on `node1-3` (there
|
||||||
|
isn't one today), then click Sync once — deliberately not before.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
## Deploy (Kubernetes / microk8s)
|
## Deploy (Kubernetes / microk8s)
|
||||||
|
|
||||||
Full runbook: **[deploy/k8s/README.md](deploy/k8s/README.md)**. In short:
|
Full runbook: **[deploy/k8s/README.md](deploy/k8s/README.md)**. In short:
|
||||||
|
|
||||||
1. CI builds the image on push to `dev`/`main` → `ghcr.io/brvncde-dotcom/vncmail-plus-dev` (`.github/workflows/docker-publish.yml`).
|
1. CI (above) builds and pushes the image, one name/many tags, to GitLab's
|
||||||
2. `kubectl apply` the manifests in `deploy/k8s/` (namespace, 4 PVCs, deployment, service, ingress) + a `secret.yaml` (from `secret.example.yaml`) + a `ghcr-pull` image-pull secret.
|
registry.
|
||||||
3. Point `vncmail.sandbox.vnc.de` DNS at the ingress; cert-manager issues TLS.
|
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
|
Runs alongside the existing `bulwark.sandbox.vnc.de`. Match your cluster's
|
||||||
StorageClass / IngressClass / cert issuer to bulwark's (see the runbook).
|
StorageClass / IngressClass / cert issuer to bulwark's (see the runbook).
|
||||||
|
|
||||||
## Deploy workflow (dev-first — ALWAYS)
|
## Deploy workflow (dev-first — ALWAYS)
|
||||||
|
|
||||||
Same flow as every other VNC/SRC repo:
|
Same flow as every other VNC/SRC repo, now enforced structurally by CI rather
|
||||||
|
than by convention:
|
||||||
|
|
||||||
1. Work on `dev` (or `vnc/*` → PR into `dev`). Push to `dev` → CI builds the `-dev` image → `kubectl -n vncmail rollout restart deploy/vncmail-plus` to pull it. QA at `vncmail.sandbox.vnc.de`.
|
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`:
|
2. **Promote to production only on explicit go-live** — merge `dev` → `main`:
|
||||||
```bash
|
```bash
|
||||||
git log dev..main # MUST be empty — main must have nothing dev lacks (else prod would revert)
|
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 checkout main && git merge --ff-only dev
|
||||||
git push origin main # CI builds the production image
|
git push gitlab main # never GitHub — opens the manual `promote` job, does not run it
|
||||||
git checkout dev
|
git checkout dev
|
||||||
```
|
```
|
||||||
Then roll the production deployment to the new image (pin its digest — see deploy/k8s/README.md).
|
Then click `promote` in the GitLab pipeline UI (protected `production`
|
||||||
Never push straight to `main`. Never let a dev→main merge silently revert prod.
|
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)
|
## Syncing upstream (Bulwark releases)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Registered on the dev-k8s-1/2/3 cluster (where ArgoCD already lives) via
|
||||||
|
# `kubectl apply` directly to the argocd namespace — this file is the
|
||||||
|
# version-controlled record of that, not something ArgoCD itself syncs
|
||||||
|
# (no app-of-apps here, deliberately kept simple for two Applications).
|
||||||
|
#
|
||||||
|
# syncPolicy starts WITHOUT automated — manual sync until the one-time
|
||||||
|
# per-namespace bootstrap (vncmail-env secret, image-pull secret — see
|
||||||
|
# deploy/k8s/README.md §3) is done by hand once. Flip to automated (see
|
||||||
|
# commented block below) only after a first manual sync succeeds cleanly.
|
||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: vncmail-dev
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: git@gitlab.vnc.biz:gitlab-instance-b9b5cf2f/vncmail-plus.git
|
||||||
|
targetRevision: dev
|
||||||
|
path: deploy/k8s/overlays/dev
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc # in-cluster — ArgoCD and vncmail-dev share this cluster
|
||||||
|
namespace: vncmail
|
||||||
|
syncPolicy:
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
|
# automated:
|
||||||
|
# prune: true
|
||||||
|
# selfHeal: true
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# NOT YET APPLIED to any cluster. Scaffolding only, matching
|
||||||
|
# deploy/k8s/overlays/prod's own "inert until Phase D" status.
|
||||||
|
#
|
||||||
|
# Unlike vncmail-dev-app.yaml, this targets a DIFFERENT cluster (node1-3,
|
||||||
|
# the HA "prod" cluster) than the one ArgoCD itself runs on (dev-k8s).
|
||||||
|
# That means before this can be applied, node1-3 needs to be registered as
|
||||||
|
# an ArgoCD-managed cluster (`argocd cluster add`, or an equivalent
|
||||||
|
# ServiceAccount+kubeconfig secret) — deliberately not done yet: there's no
|
||||||
|
# reason to wire cross-cluster RBAC into the prod HA cluster before prod
|
||||||
|
# hostname/Stalwart/secrets are real and someone's actually promoting.
|
||||||
|
#
|
||||||
|
# syncPolicy has no automated block at all, and won't get one even later —
|
||||||
|
# prod stays manual-sync-only permanently. That's the promotion gate.
|
||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: vncmail-prod
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: git@gitlab.vnc.biz:gitlab-instance-b9b5cf2f/vncmail-plus.git
|
||||||
|
targetRevision: main
|
||||||
|
path: deploy/k8s/overlays/prod
|
||||||
|
destination:
|
||||||
|
server: CHANGEME # the node1-3 cluster's registered ArgoCD server URL, once added
|
||||||
|
namespace: vncmail-prod
|
||||||
|
syncPolicy:
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
+87
-38
@@ -1,29 +1,68 @@
|
|||||||
# VNCmail+ — Admin Deployment Guide (microk8s)
|
# VNCmail+ — Admin Deployment Guide (microk8s)
|
||||||
|
|
||||||
Deploy VNCmail+ (VNC's Bulwark fork) as a container at **`vncmail.sandbox.vnc.de`**,
|
Deploy VNCmail+ (VNC's Bulwark fork) as a container at **`vncmail.sandbox.vnc.de`**,
|
||||||
**alongside** the existing `bulwark.sandbox.vnc.de`. Plain `kubectl apply` — no
|
**alongside** the existing `bulwark.sandbox.vnc.de`.
|
||||||
GitOps needed.
|
|
||||||
|
|
||||||
> Why a container (not Vercel): Bulwark is stateful — it writes settings/admin/
|
> Why a container (not Vercel): Bulwark is stateful — it writes settings/admin/
|
||||||
> telemetry to `/app/data`, which needs persistent volumes.
|
> telemetry to `/app/data`, which needs persistent volumes.
|
||||||
|
|
||||||
|
## Structure — base + overlays
|
||||||
|
|
||||||
|
```
|
||||||
|
deploy/k8s/
|
||||||
|
base/ # shared manifest shapes (namespace-agnostic)
|
||||||
|
overlays/
|
||||||
|
dev/ # the live sandbox — vncmail.sandbox.vnc.de, namespace vncmail
|
||||||
|
prod/ # scaffolded, NOT YET LIVE — see "Production status" below
|
||||||
|
ca/ # separate, isolated EJBCA internal CA — see ca/README.md.
|
||||||
|
# Never composed with base/ or either overlay above.
|
||||||
|
```
|
||||||
|
|
||||||
|
`kubectl apply -k overlays/dev` (or `overlays/prod`, once real) instead of
|
||||||
|
applying `base/` directly — `base/` alone has no namespace and won't apply
|
||||||
|
meaningfully on its own.
|
||||||
|
|
||||||
|
## Routine deploys go through CI + ArgoCD now
|
||||||
|
|
||||||
|
As of the GitLab CI/CD pipeline (`.gitlab-ci.yml`, see `../../VNCMAIL-SETUP.md`
|
||||||
|
§ CI/CD), **pushing to `dev` auto-builds and bumps the deploy tag; ArgoCD's
|
||||||
|
`vncmail-dev` Application applies it** — you should not normally need to run
|
||||||
|
`kubectl apply` for the sandbox by hand anymore, and CI never touches the
|
||||||
|
cluster directly (it only ever talks to the registry and to this git repo).
|
||||||
|
This guide's manual steps below are for first-time setup, the one-time
|
||||||
|
secret creation CI/ArgoCD deliberately never automate, and troubleshooting.
|
||||||
|
|
||||||
|
## Production status
|
||||||
|
|
||||||
|
**There is no production VNCmail+ deployment yet.** `overlays/prod/` exists
|
||||||
|
in the repo but is inert: its ingress hostname and its secret's
|
||||||
|
`JMAP_SERVER_URL` are both obvious placeholders (`vncmail.CHANGEME.invalid` /
|
||||||
|
`https://REPLACE-ME-prod-stalwart-not-yet-deployed.invalid`) that will fail
|
||||||
|
loudly rather than silently deploy against the wrong backend. Applying it
|
||||||
|
requires, in order: a real prod Stalwart instance to exist, a real hostname
|
||||||
|
decision, DNS, a real `secret.yaml`, and the `.gitlab-ci.yml` `promote` job's
|
||||||
|
`kubectl apply` step (currently a TODO placeholder) filled in. None of that
|
||||||
|
is CI's job to decide — it's an explicit, human-triggered event.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. What you are deploying
|
## 1. What you are deploying (per overlay)
|
||||||
|
|
||||||
| # | Object | File | Purpose |
|
| # | Object | File | Purpose |
|
||||||
|---|--------|------|---------|
|
|---|--------|------|---------|
|
||||||
| 1 | Namespace `vncmail` | `namespace.yaml` | Isolates the app |
|
| 1 | Namespace | `overlays/<env>/namespace.yaml` | Isolates the app (`vncmail` for dev, `vncmail-prod` for prod) |
|
||||||
| 2 | 4× PersistentVolumeClaim | `pvc.yaml` | `/app/data/{settings,admin,admin-state,telemetry}` |
|
| 2 | 4× PersistentVolumeClaim | `base/pvc.yaml` | `/app/data/{settings,admin,admin-state,telemetry}` |
|
||||||
| 3 | Secret `vncmail-env` | `secret.yaml` *(you create it)* | App config (JMAP URL, session secret, branding) |
|
| 3 | Secret `vncmail-env` | `overlays/<env>/secret.yaml` *(you create it)* | App config (JMAP URL, session secret, branding) |
|
||||||
| 4 | Secret `ghcr-pull` | *(you create it — command below)* | Pull the private image from GHCR |
|
| 4 | Image-pull secret | *(you create it — command below)* | Pull the (currently private) image |
|
||||||
| 5 | Deployment `vncmail-plus` | `deployment.yaml` | The app pod |
|
| 5 | Deployment `vncmail-plus` | `base/deployment.yaml` (+ overlay patches) | The app pod |
|
||||||
| 6 | Service `vncmail-plus` | `service.yaml` | ClusterIP :80 → pod :3000 |
|
| 6 | Service `vncmail-plus` | `base/service.yaml` | ClusterIP :80 → pod :3000 |
|
||||||
| 7 | Ingress `vncmail-plus` | `ingress.yaml` | TLS host `vncmail.sandbox.vnc.de` |
|
| 7 | Ingress `vncmail-plus` | `base/ingress.yaml` (+ overlay patches for prod) | TLS host |
|
||||||
|
|
||||||
**Image:** `ghcr.io/brvncde-dotcom/vncmail-plus-dev:latest`
|
**Image:** CI builds and pushes to `registry.gitlab.vnc.biz/gitlab-instance-b9b5cf2f/vncmail-plus`
|
||||||
(built automatically by CI from the `dev` branch). For anything beyond the
|
(tag `sha-<sha>` per deploy, moving pointers `dev-latest`/`prod-latest`). The
|
||||||
sandbox, pin a digest — see §5.
|
`ghcr.io/brvncde-dotcom/vncmail-plus-dev` image referenced in `base/deployment.yaml`
|
||||||
|
is a legacy default only — CI overrides it per-deploy via `kubectl set image`,
|
||||||
|
so what's committed there never needs to track what's actually running.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -43,45 +82,48 @@ kubectl get ingressclass
|
|||||||
kubectl get clusterissuer # cert-manager issuers (if used)
|
kubectl get clusterissuer # cert-manager issuers (if used)
|
||||||
```
|
```
|
||||||
|
|
||||||
Then edit if they differ from the defaults below:
|
Then edit if they differ from the defaults below (in `base/`, so both overlays
|
||||||
|
pick up the fix):
|
||||||
|
|
||||||
| Value | Default in manifests | File to edit |
|
| Value | Default in manifests | File to edit |
|
||||||
|-------|----------------------|--------------|
|
|-------|----------------------|--------------|
|
||||||
| StorageClass | `microk8s-hostpath` | `pvc.yaml` (all 4) |
|
| StorageClass | `microk8s-hostpath` | `base/pvc.yaml` (all 4) |
|
||||||
| IngressClass | `public` | `ingress.yaml` |
|
| IngressClass | `public` | `base/ingress.yaml` |
|
||||||
| cert-manager issuer | `letsencrypt-prod` | `ingress.yaml` |
|
| cert-manager issuer | `letsencrypt-prod` | `base/ingress.yaml` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Deploy (copy-paste, in order)
|
## 3. First-time setup (one-time, per environment — CI never does this)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd deploy/k8s
|
cd deploy/k8s/overlays/dev # or overlays/prod, once real
|
||||||
|
|
||||||
# a) Namespace
|
# a) Image-pull secret — the registry package is private.
|
||||||
kubectl apply -f namespace.yaml
|
|
||||||
|
|
||||||
# b) Image-pull secret — the GHCR package is private.
|
|
||||||
# Use a GitHub PAT (classic) with the read:packages scope.
|
|
||||||
kubectl create secret docker-registry ghcr-pull \
|
kubectl create secret docker-registry ghcr-pull \
|
||||||
--namespace vncmail \
|
--namespace vncmail \
|
||||||
--docker-server=ghcr.io \
|
--docker-server=ghcr.io \
|
||||||
--docker-username=brvncde-dotcom \
|
--docker-username=brvncde-dotcom \
|
||||||
--docker-password='<GITHUB_PAT_read:packages>' \
|
--docker-password='<GITHUB_PAT_read:packages>' \
|
||||||
--docker-email=br@vnc.biz
|
--docker-email=br@vnc.biz
|
||||||
|
# Once CI has cut over to registry.gitlab.vnc.biz, this becomes a
|
||||||
|
# docker-registry secret for that registry instead — see VNCMAIL-SETUP.md.
|
||||||
|
|
||||||
# c) App config secret — copy the template, set a real SESSION_SECRET, apply.
|
# b) App config secret — copy the template, set a real SESSION_SECRET, apply.
|
||||||
cp secret.example.yaml secret.yaml
|
cp secret.example.yaml secret.yaml
|
||||||
# edit secret.yaml: SESSION_SECRET: "$(openssl rand -base64 32)"
|
# edit secret.yaml: SESSION_SECRET: "$(openssl rand -base64 32)"
|
||||||
kubectl apply -f secret.yaml
|
kubectl apply -f secret.yaml
|
||||||
|
|
||||||
# d) Everything else (PVCs, Deployment, Service, Ingress)
|
# c) Everything else (namespace, PVCs, Deployment, Service, Ingress)
|
||||||
kubectl apply -k .
|
kubectl apply -k .
|
||||||
```
|
```
|
||||||
|
|
||||||
> Alternative to (b): make the GHCR package public
|
> Alternative to (a): make the registry package public, then delete the
|
||||||
> (GitHub → Packages → vncmail-plus-dev → Package settings → Change visibility),
|
> `imagePullSecrets:` block from `base/deployment.yaml`.
|
||||||
> then delete the `imagePullSecrets:` block from `deployment.yaml`.
|
|
||||||
|
After this one-time setup, routine deploys to `dev` happen automatically via
|
||||||
|
CI on every push — see "Routine deploys go through CI now" above. This
|
||||||
|
section is for first-time bring-up (or `overlays/prod`, once it's real) and
|
||||||
|
troubleshooting, not the everyday path.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -104,16 +146,23 @@ a bare username.
|
|||||||
|
|
||||||
## 5. Update to a new build
|
## 5. Update to a new build
|
||||||
|
|
||||||
```bash
|
Normally you don't — CI's `bump-dev` job + ArgoCD's automated sync do this
|
||||||
# CI rebuilds ghcr.io/brvncde-dotcom/vncmail-plus-dev on every push to `dev`.
|
on every push to `dev`. To do it by hand (e.g. troubleshooting, before
|
||||||
kubectl -n vncmail rollout restart deploy/vncmail-plus # pulls :latest (imagePullPolicy: Always)
|
automated sync is turned on):
|
||||||
|
|
||||||
# Production: pin a digest instead of :latest so rollouts are deterministic.
|
```bash
|
||||||
kubectl -n vncmail set image deploy/vncmail-plus \
|
kubectl -n vncmail set image deploy/vncmail-plus \
|
||||||
vncmail-plus=ghcr.io/brvncde-dotcom/vncmail-plus-dev@sha256:<digest>
|
vncmail-plus=registry.gitlab.vnc.biz/gitlab-instance-b9b5cf2f/vncmail-plus:sha-<sha>
|
||||||
```
|
```
|
||||||
|
|
||||||
Rollback: `kubectl -n vncmail rollout undo deploy/vncmail-plus`
|
ArgoCD will overwrite this on its next sync unless you also update
|
||||||
|
`deploy/k8s/overlays/dev/image-tag/kustomization.yaml` to match — that file
|
||||||
|
is CI-owned (see its header comment), so a by-hand `set image` is only ever
|
||||||
|
a temporary override, not a real fix.
|
||||||
|
|
||||||
|
Rollback (bypassing ArgoCD temporarily): `kubectl -n vncmail rollout undo deploy/vncmail-plus`.
|
||||||
|
The real rollback is reverting the commit that bumped the tag and letting
|
||||||
|
ArgoCD re-sync.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -121,9 +170,9 @@ Rollback: `kubectl -n vncmail rollout undo deploy/vncmail-plus`
|
|||||||
|
|
||||||
| Symptom | Cause / fix |
|
| Symptom | Cause / fix |
|
||||||
|---------|-------------|
|
|---------|-------------|
|
||||||
| Pod `ImagePullBackOff` | `ghcr-pull` secret missing/expired, or package still private. Recreate the secret (§3b) or make the package public. |
|
| Pod `ImagePullBackOff` | `ghcr-pull` secret missing/expired, or package still private. Recreate the secret (§3a) or make the package public. |
|
||||||
| Pod `CrashLoopBackOff`, logs show `EACCES`/permission on `/app/data` | Volume not writable by uid 1001. `securityContext.fsGroup: 1001` is set in `deployment.yaml` — keep it; some storage drivers also need it on the PVC. |
|
| Pod `CrashLoopBackOff`, logs show `EACCES`/permission on `/app/data` | Volume not writable by uid 1001. `securityContext.fsGroup: 1001` is set in `base/deployment.yaml` — keep it; some storage drivers also need it on the PVC. |
|
||||||
| PVC stuck `Pending` | Wrong `storageClassName` in `pvc.yaml`. Set it to one from `kubectl get sc`. |
|
| PVC stuck `Pending` | Wrong `storageClassName` in `base/pvc.yaml`. Set it to one from `kubectl get sc`. |
|
||||||
| Ingress has no address / no cert | Wrong `ingressClassName` or cert issuer. Match bulwark's (§2). Check `kubectl -n vncmail describe ingress vncmail-plus`. |
|
| Ingress has no address / no cert | Wrong `ingressClassName` or cert issuer. Match bulwark's (§2). Check `kubectl -n vncmail describe ingress vncmail-plus`. |
|
||||||
| Login shows "Ein Fehler ist aufgetreten" | Use the **full** email (`user@sandbox.vnc.de`), not a bare username. |
|
| Login shows "Ein Fehler ist aufgetreten" | Use the **full** email (`user@sandbox.vnc.de`), not a bare username. |
|
||||||
| Can't reach Stalwart | Check `JMAP_SERVER_URL` in the secret = `https://stalwart.sandbox.vnc.de`. |
|
| Can't reach Stalwart | Check `JMAP_SERVER_URL` in the secret = `https://stalwart.sandbox.vnc.de`. |
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ apiVersion: apps/v1
|
|||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: vncmail-plus
|
name: vncmail-plus
|
||||||
namespace: vncmail
|
|
||||||
labels:
|
labels:
|
||||||
app: vncmail-plus
|
app: vncmail-plus
|
||||||
spec:
|
spec:
|
||||||
@@ -26,12 +25,17 @@ spec:
|
|||||||
runAsGroup: 1001
|
runAsGroup: 1001
|
||||||
# ghcr package is private by default — see deploy/k8s/README.md to create
|
# ghcr package is private by default — see deploy/k8s/README.md to create
|
||||||
# this pull secret. Delete this block if you make the package public.
|
# this pull secret. Delete this block if you make the package public.
|
||||||
|
# NOTE: once CI moves to pushing registry.gitlab.vnc.biz images (the
|
||||||
|
# dev-auto-deploy phase of the GitLab pipeline), this needs to become a
|
||||||
|
# docker-registry secret for that registry instead — comments only,
|
||||||
|
# deliberately not renamed here, so this file stays a no-op today.
|
||||||
imagePullSecrets:
|
imagePullSecrets:
|
||||||
- name: ghcr-pull
|
- name: ghcr-pull
|
||||||
containers:
|
containers:
|
||||||
- name: vncmail-plus
|
- name: vncmail-plus
|
||||||
# dev image (built from the `dev` branch by CI). For production pin a
|
# Default/legacy value — CI overrides the image per-deploy via
|
||||||
# digest: ghcr.io/brvncde-dotcom/vncmail-plus-dev@sha256:<digest>
|
# `kustomize edit set image`, so what's committed here never goes
|
||||||
|
# stale. For a one-off manual apply, pin a digest instead of :latest.
|
||||||
image: ghcr.io/brvncde-dotcom/vncmail-plus-dev:latest
|
image: ghcr.io/brvncde-dotcom/vncmail-plus-dev:latest
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
ports:
|
ports:
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Both real clusters (node1-3 "prod", dev-k8s-1-3 "dev") run Traefik, not
|
||||||
|
# nginx — confirmed via `kubectl get ingressclass` (class is literally named
|
||||||
|
# `traefik`). Unlike nginx's restrictive 1MB default, Traefik has no default
|
||||||
|
# request-body-size cap, so there's no equivalent needed for mail attachment
|
||||||
|
# uploads (the old nginx.ingress.kubernetes.io/proxy-body-size annotation
|
||||||
|
# this file used to carry is simply not applicable here).
|
||||||
|
#
|
||||||
|
# Host, TLS secretName, and cert-manager issuer are ALL overlay-specific now
|
||||||
|
# (dev-k8s only has a `letsencrypt-staging` issuer; node1-3/prod has none
|
||||||
|
# configured yet) — every overlay's patch-ingress.yaml must override the
|
||||||
|
# CHANGEME placeholders below.
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: vncmail-plus
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: CHANGEME
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- CHANGEME.invalid
|
||||||
|
secretName: vncmail-plus-tls
|
||||||
|
rules:
|
||||||
|
- host: CHANGEME.invalid
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: vncmail-plus
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- pvc.yaml
|
||||||
|
- deployment.yaml
|
||||||
|
- service.yaml
|
||||||
|
- ingress.yaml
|
||||||
|
# - secret.yaml # create from an overlay's secret.example.yaml; not committed
|
||||||
|
|
||||||
|
# Namespace is intentionally NOT set here. Kustomize's `namespace:` transformer
|
||||||
|
# doesn't rename cluster-scoped Namespace objects, so each overlay ships its own
|
||||||
|
# namespace.yaml (the actual object) and its own `namespace:` field (which
|
||||||
|
# injects metadata.namespace into every namespaced resource below). Applying
|
||||||
|
# this base directly is meaningless — always go through an overlay.
|
||||||
@@ -5,7 +5,6 @@ apiVersion: v1
|
|||||||
kind: PersistentVolumeClaim
|
kind: PersistentVolumeClaim
|
||||||
metadata:
|
metadata:
|
||||||
name: vncmail-settings
|
name: vncmail-settings
|
||||||
namespace: vncmail
|
|
||||||
spec:
|
spec:
|
||||||
accessModes: [ReadWriteOnce]
|
accessModes: [ReadWriteOnce]
|
||||||
storageClassName: microk8s-hostpath
|
storageClassName: microk8s-hostpath
|
||||||
@@ -17,7 +16,6 @@ apiVersion: v1
|
|||||||
kind: PersistentVolumeClaim
|
kind: PersistentVolumeClaim
|
||||||
metadata:
|
metadata:
|
||||||
name: vncmail-admin
|
name: vncmail-admin
|
||||||
namespace: vncmail
|
|
||||||
spec:
|
spec:
|
||||||
accessModes: [ReadWriteOnce]
|
accessModes: [ReadWriteOnce]
|
||||||
storageClassName: microk8s-hostpath
|
storageClassName: microk8s-hostpath
|
||||||
@@ -29,7 +27,6 @@ apiVersion: v1
|
|||||||
kind: PersistentVolumeClaim
|
kind: PersistentVolumeClaim
|
||||||
metadata:
|
metadata:
|
||||||
name: vncmail-admin-state
|
name: vncmail-admin-state
|
||||||
namespace: vncmail
|
|
||||||
spec:
|
spec:
|
||||||
accessModes: [ReadWriteOnce]
|
accessModes: [ReadWriteOnce]
|
||||||
storageClassName: microk8s-hostpath
|
storageClassName: microk8s-hostpath
|
||||||
@@ -41,7 +38,6 @@ apiVersion: v1
|
|||||||
kind: PersistentVolumeClaim
|
kind: PersistentVolumeClaim
|
||||||
metadata:
|
metadata:
|
||||||
name: vncmail-telemetry
|
name: vncmail-telemetry
|
||||||
namespace: vncmail
|
|
||||||
spec:
|
spec:
|
||||||
accessModes: [ReadWriteOnce]
|
accessModes: [ReadWriteOnce]
|
||||||
storageClassName: microk8s-hostpath
|
storageClassName: microk8s-hostpath
|
||||||
@@ -2,7 +2,6 @@ apiVersion: v1
|
|||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
name: vncmail-plus
|
name: vncmail-plus
|
||||||
namespace: vncmail
|
|
||||||
labels:
|
labels:
|
||||||
app: vncmail-plus
|
app: vncmail-plus
|
||||||
spec:
|
spec:
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# Exposes VNCmail+ at vncmail.sandbox.vnc.de, alongside bulwark.sandbox.vnc.de.
|
|
||||||
# MATCH YOUR CLUSTER — inspect the existing Bulwark ingress and copy its
|
|
||||||
# ingressClassName + TLS/cert-manager annotations:
|
|
||||||
# kubectl get ingress -A | grep bulwark
|
|
||||||
# kubectl get ingress <bulwark-ingress> -n <ns> -o yaml
|
|
||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: Ingress
|
|
||||||
metadata:
|
|
||||||
name: vncmail-plus
|
|
||||||
namespace: vncmail
|
|
||||||
annotations:
|
|
||||||
# cert-manager issuer — set to whatever bulwark.sandbox.vnc.de uses.
|
|
||||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
|
||||||
# Mail attachments can be large; raise the nginx body limit.
|
|
||||||
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
|
|
||||||
spec:
|
|
||||||
# microk8s ingress addon class is usually "public" (nginx). Confirm with
|
|
||||||
# `kubectl get ingressclass` and match bulwark's.
|
|
||||||
ingressClassName: public
|
|
||||||
tls:
|
|
||||||
- hosts:
|
|
||||||
- vncmail.sandbox.vnc.de
|
|
||||||
secretName: vncmail-plus-tls
|
|
||||||
rules:
|
|
||||||
- host: vncmail.sandbox.vnc.de
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- path: /
|
|
||||||
pathType: Prefix
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: vncmail-plus
|
|
||||||
port:
|
|
||||||
number: 80
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Owned by CI (the bump-dev job in .gitlab-ci.yml), not by hand. Kept as its
|
||||||
|
# own Component so CI only ever rewrites this 6-line file, never the parent
|
||||||
|
# overlays/dev/kustomization.yaml (structure/patches there stay under normal
|
||||||
|
# code review — CI regenerating a whole hand-maintained file on every push
|
||||||
|
# would silently revert any change made there between deploys).
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1alpha1
|
||||||
|
kind: Component
|
||||||
|
images:
|
||||||
|
- name: ghcr.io/brvncde-dotcom/vncmail-plus-dev
|
||||||
|
newName: ghcr.io/brvncde-dotcom/vncmail-plus-dev
|
||||||
|
newTag: latest
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
namespace: vncmail
|
||||||
|
resources:
|
||||||
|
- ../../base
|
||||||
|
- namespace.yaml
|
||||||
|
# - secret.yaml # create from secret.example.yaml; not committed
|
||||||
|
|
||||||
|
patches:
|
||||||
|
- path: patch-ingress.yaml
|
||||||
|
|
||||||
|
components:
|
||||||
|
- image-tag
|
||||||
|
|
||||||
|
# Targets the dev-k8s-1/2/3 cluster (confirmed via direct access: this is
|
||||||
|
# where ArgoCD already lives). The image tag lives in image-tag/ (a separate
|
||||||
|
# Component CI owns — see .gitlab-ci.yml's bump-dev job) rather than here, so
|
||||||
|
# CI never needs to touch this file.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# dev-k8s cluster confirmed to have a `letsencrypt-staging` ClusterIssuer
|
||||||
|
# already (no `letsencrypt-prod` exists there) - staging avoids burning
|
||||||
|
# Let's Encrypt's real rate limits while this is still being stood up.
|
||||||
|
# vncmail.sandbox.vnc.de DNS does not point here yet either - this is the
|
||||||
|
# intended host, not a live one (see VNCMAIL-SETUP.md for what's still open).
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: vncmail-plus
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-staging
|
||||||
|
spec:
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- vncmail.sandbox.vnc.de
|
||||||
|
secretName: vncmail-plus-tls
|
||||||
|
rules:
|
||||||
|
- host: vncmail.sandbox.vnc.de
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: vncmail-plus
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Owned by CI (the bump-prod job in .gitlab-ci.yml), not by hand — same
|
||||||
|
# reasoning as overlays/dev/image-tag/. Starts pointed at an obviously-fake
|
||||||
|
# tag on purpose: nothing has been promoted yet, and vncmail-prod's ArgoCD
|
||||||
|
# Application has manual sync anyway, so this being "wrong" doesn't deploy
|
||||||
|
# anything wrong — it just means there's nothing to sync until a real
|
||||||
|
# `git push` to main updates it.
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1alpha1
|
||||||
|
kind: Component
|
||||||
|
images:
|
||||||
|
- name: ghcr.io/brvncde-dotcom/vncmail-plus-dev
|
||||||
|
newName: registry.gitlab.vnc.biz/gitlab-instance-b9b5cf2f/vncmail-plus
|
||||||
|
newTag: not-yet-promoted
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
namespace: vncmail-prod
|
||||||
|
resources:
|
||||||
|
- ../../base
|
||||||
|
- namespace.yaml
|
||||||
|
# - secret.yaml # create from secret.example.yaml; not committed
|
||||||
|
|
||||||
|
patches:
|
||||||
|
- path: patch-ingress.yaml
|
||||||
|
- path: patch-deployment.yaml
|
||||||
|
|
||||||
|
components:
|
||||||
|
- image-tag
|
||||||
|
|
||||||
|
# NOT MEANT TO BE SYNCED AS COMMITTED. Scaffolding only (see the pipeline
|
||||||
|
# plan's Phase C/D) — image-tag/'s placeholder tag is obviously-invalid on
|
||||||
|
# purpose. The bump-prod job in .gitlab-ci.yml keeps that tag pointed at
|
||||||
|
# whatever's already on dev once main advances, but vncmail-prod's ArgoCD
|
||||||
|
# Application has manual sync — a human still has to click Sync (or
|
||||||
|
# `argocd app sync vncmail-prod`) for any of this to actually apply.
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: vncmail-prod
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/part-of: vnclagoon-suite
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Basic HA. Still `strategy: Recreate` (inherited from base) since the PVCs
|
||||||
|
# are RWO — 2 replicas doesn't buy zero-downtime rollouts by itself, only
|
||||||
|
# tolerance for a node loss between deploys. Revisit if that's not enough.
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: vncmail-plus
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# PLACEHOLDER — the real production hostname has not been decided yet (see
|
||||||
|
# VNCMAIL-SETUP.md / the pipeline plan). vncmail.CHANGEME.invalid is
|
||||||
|
# deliberately unresolvable: applying this overlay as committed will not
|
||||||
|
# issue a cert or route traffic anywhere. Replace both occurrences below,
|
||||||
|
# and the matching TLS secretName, before Phase D (first real prod deploy).
|
||||||
|
#
|
||||||
|
# Targets node1-3 (the HA "prod" cluster). Deliberately does NOT override
|
||||||
|
# base's `cert-manager.io/cluster-issuer: CHANGEME` — node1-3 has ZERO
|
||||||
|
# ClusterIssuers configured today (confirmed via direct access). A human
|
||||||
|
# needs to create a real one there (ACME account, DNS-01 or HTTP-01 solver)
|
||||||
|
# before this can be anything but a placeholder.
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: vncmail-plus
|
||||||
|
spec:
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- vncmail.CHANGEME.invalid
|
||||||
|
secretName: vncmail-plus-prod-tls
|
||||||
|
rules:
|
||||||
|
- host: vncmail.CHANGEME.invalid
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: vncmail-plus
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Copy to secret.yaml, fill in real values, and apply. DO NOT commit secret.yaml
|
||||||
|
# (it is gitignored). Generate SESSION_SECRET with: openssl rand -base64 32
|
||||||
|
#
|
||||||
|
# JMAP_SERVER_URL is a PLACEHOLDER — there is no production Stalwart instance
|
||||||
|
# yet. This overlay cannot go live (Phase D) until one exists and this value
|
||||||
|
# points at it for real.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: vncmail-env
|
||||||
|
namespace: vncmail-prod
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
# Core — connect to Stalwart over JMAP
|
||||||
|
JMAP_SERVER_URL: "https://REPLACE-ME-prod-stalwart-not-yet-deployed.invalid"
|
||||||
|
SESSION_SECRET: "REPLACE_ME__openssl_rand_base64_32"
|
||||||
|
# Branding (theme defaults to VNClagoon in code; these set name + logo)
|
||||||
|
APP_NAME: "VNCmail+"
|
||||||
|
APP_SHORT_NAME: "VNCmail+"
|
||||||
|
LOGIN_COMPANY_NAME: "VNClagoon"
|
||||||
|
LOGIN_LOGO_DARK_URL: "/branding/vncmail-wordmark-on-dark.svg"
|
||||||
|
LOGIN_LOGO_LIGHT_URL: "/branding/vncmail-wordmark-on-light.svg"
|
||||||
|
APP_LOGO_DARK_URL: "/branding/vncmail-wordmark-on-dark.svg"
|
||||||
|
APP_LOGO_LIGHT_URL: "/branding/vncmail-wordmark-on-light.svg"
|
||||||
|
LOGIN_LOGO_MAX_HEIGHT: "52"
|
||||||
|
# Housekeeping
|
||||||
|
BULWARK_UPDATE_CHECK: "off"
|
||||||
|
# Data dirs default to /app/data/* (mounted to the PVCs) — no need to set them.
|
||||||
@@ -210,7 +210,9 @@ function escapeDn(value: string): string {
|
|||||||
.replace(/([\\,+"<>;=])/g, '\\$1')
|
.replace(/([\\,+"<>;=])/g, '\\$1')
|
||||||
.replace(/^([ #])/, '\\$1')
|
.replace(/^([ #])/, '\\$1')
|
||||||
.replace(/ $/, '\\ ')
|
.replace(/ $/, '\\ ')
|
||||||
// Control characters have no legitimate place in a DN.
|
// Control characters have no legitimate place in a DN — the class below
|
||||||
|
// is intentional, not a typo.
|
||||||
|
// eslint-disable-next-line no-control-regex
|
||||||
.replace(/[\x00-\x1f\x7f]/g, '');
|
.replace(/[\x00-\x1f\x7f]/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user