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
|
||||
/repos/
|
||||
|
||||
# k8s deploy secret (create from deploy/k8s/secret.example.yaml)
|
||||
/deploy/k8s/secret.yaml
|
||||
# k8s deploy secrets (create from the matching overlay's secret.example.yaml)
|
||||
/deploy/k8s/overlays/*/secret.yaml
|
||||
|
||||
# S/MIME plugin build output (rebuild with: cd vnc/plugins/smime && npm run build)
|
||||
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`).
|
||||
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 (`ghcr.io/brvncde-dotcom/vncmail-plus-*`)
|
||||
with **4 persistent volumes**, exactly like the existing `bulwark.sandbox.vnc.de`.
|
||||
- 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**.
|
||||
|
||||
@@ -27,37 +27,130 @@ of truth; VNCmail+ is the UI. It deploys as a **container on Kubernetes
|
||||
|
||||
| Branch | Role |
|
||||
|--------|------|
|
||||
| `main` | **Production** — CI builds `…/vncmail-plus-beta`. Only updated by an explicit promote. |
|
||||
| `dev` | Integration + QA — CI builds `…/vncmail-plus-dev` on push. Default working branch. |
|
||||
| `vnc/*`| Feature branches for UI work (branch off `dev`, PR into `dev`). |
|
||||
| `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) + 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)
|
||||
|
||||
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`).
|
||||
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.
|
||||
3. Point `vncmail.sandbox.vnc.de` DNS at the ingress; cert-manager issues TLS.
|
||||
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:
|
||||
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`:
|
||||
```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 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
|
||||
```
|
||||
Then roll the production deployment to the new image (pin its digest — see deploy/k8s/README.md).
|
||||
Never push straight to `main`. Never let a dev→main merge silently revert prod.
|
||||
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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
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
|
||||
GitOps needed.
|
||||
**alongside** the existing `bulwark.sandbox.vnc.de`.
|
||||
|
||||
> Why a container (not Vercel): Bulwark is stateful — it writes settings/admin/
|
||||
> 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 |
|
||||
|---|--------|------|---------|
|
||||
| 1 | Namespace `vncmail` | `namespace.yaml` | Isolates the app |
|
||||
| 2 | 4× PersistentVolumeClaim | `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) |
|
||||
| 4 | Secret `ghcr-pull` | *(you create it — command below)* | Pull the private image from GHCR |
|
||||
| 5 | Deployment `vncmail-plus` | `deployment.yaml` | The app pod |
|
||||
| 6 | Service `vncmail-plus` | `service.yaml` | ClusterIP :80 → pod :3000 |
|
||||
| 7 | Ingress `vncmail-plus` | `ingress.yaml` | TLS host `vncmail.sandbox.vnc.de` |
|
||||
| 1 | Namespace | `overlays/<env>/namespace.yaml` | Isolates the app (`vncmail` for dev, `vncmail-prod` for prod) |
|
||||
| 2 | 4× PersistentVolumeClaim | `base/pvc.yaml` | `/app/data/{settings,admin,admin-state,telemetry}` |
|
||||
| 3 | Secret `vncmail-env` | `overlays/<env>/secret.yaml` *(you create it)* | App config (JMAP URL, session secret, branding) |
|
||||
| 4 | Image-pull secret | *(you create it — command below)* | Pull the (currently private) image |
|
||||
| 5 | Deployment `vncmail-plus` | `base/deployment.yaml` (+ overlay patches) | The app pod |
|
||||
| 6 | Service `vncmail-plus` | `base/service.yaml` | ClusterIP :80 → pod :3000 |
|
||||
| 7 | Ingress `vncmail-plus` | `base/ingress.yaml` (+ overlay patches for prod) | TLS host |
|
||||
|
||||
**Image:** `ghcr.io/brvncde-dotcom/vncmail-plus-dev:latest`
|
||||
(built automatically by CI from the `dev` branch). For anything beyond the
|
||||
sandbox, pin a digest — see §5.
|
||||
**Image:** CI builds and pushes to `registry.gitlab.vnc.biz/gitlab-instance-b9b5cf2f/vncmail-plus`
|
||||
(tag `sha-<sha>` per deploy, moving pointers `dev-latest`/`prod-latest`). The
|
||||
`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)
|
||||
```
|
||||
|
||||
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 |
|
||||
|-------|----------------------|--------------|
|
||||
| StorageClass | `microk8s-hostpath` | `pvc.yaml` (all 4) |
|
||||
| IngressClass | `public` | `ingress.yaml` |
|
||||
| cert-manager issuer | `letsencrypt-prod` | `ingress.yaml` |
|
||||
| StorageClass | `microk8s-hostpath` | `base/pvc.yaml` (all 4) |
|
||||
| IngressClass | `public` | `base/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
|
||||
cd deploy/k8s
|
||||
cd deploy/k8s/overlays/dev # or overlays/prod, once real
|
||||
|
||||
# a) Namespace
|
||||
kubectl apply -f namespace.yaml
|
||||
|
||||
# b) Image-pull secret — the GHCR package is private.
|
||||
# Use a GitHub PAT (classic) with the read:packages scope.
|
||||
# a) Image-pull secret — the registry package is private.
|
||||
kubectl create secret docker-registry ghcr-pull \
|
||||
--namespace vncmail \
|
||||
--docker-server=ghcr.io \
|
||||
--docker-username=brvncde-dotcom \
|
||||
--docker-password='<GITHUB_PAT_read:packages>' \
|
||||
--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
|
||||
# edit secret.yaml: SESSION_SECRET: "$(openssl rand -base64 32)"
|
||||
kubectl apply -f secret.yaml
|
||||
|
||||
# d) Everything else (PVCs, Deployment, Service, Ingress)
|
||||
# c) Everything else (namespace, PVCs, Deployment, Service, Ingress)
|
||||
kubectl apply -k .
|
||||
```
|
||||
|
||||
> Alternative to (b): make the GHCR package public
|
||||
> (GitHub → Packages → vncmail-plus-dev → Package settings → Change visibility),
|
||||
> then delete the `imagePullSecrets:` block from `deployment.yaml`.
|
||||
> Alternative to (a): make the registry package public, then delete the
|
||||
> `imagePullSecrets:` block from `base/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
|
||||
|
||||
```bash
|
||||
# CI rebuilds ghcr.io/brvncde-dotcom/vncmail-plus-dev on every push to `dev`.
|
||||
kubectl -n vncmail rollout restart deploy/vncmail-plus # pulls :latest (imagePullPolicy: Always)
|
||||
Normally you don't — CI's `bump-dev` job + ArgoCD's automated sync do this
|
||||
on every push to `dev`. To do it by hand (e.g. troubleshooting, before
|
||||
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 \
|
||||
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 |
|
||||
|---------|-------------|
|
||||
| Pod `ImagePullBackOff` | `ghcr-pull` secret missing/expired, or package still private. Recreate the secret (§3b) 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. |
|
||||
| PVC stuck `Pending` | Wrong `storageClassName` in `pvc.yaml`. Set it to one from `kubectl get sc`. |
|
||||
| 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 `base/deployment.yaml` — keep it; some storage drivers also need it on the PVC. |
|
||||
| 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`. |
|
||||
| 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`. |
|
||||
|
||||
@@ -2,7 +2,6 @@ apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: vncmail-plus
|
||||
namespace: vncmail
|
||||
labels:
|
||||
app: vncmail-plus
|
||||
spec:
|
||||
@@ -26,12 +25,17 @@ spec:
|
||||
runAsGroup: 1001
|
||||
# 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.
|
||||
# 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:
|
||||
- name: ghcr-pull
|
||||
containers:
|
||||
- name: vncmail-plus
|
||||
# dev image (built from the `dev` branch by CI). For production pin a
|
||||
# digest: ghcr.io/brvncde-dotcom/vncmail-plus-dev@sha256:<digest>
|
||||
# Default/legacy value — CI overrides the image per-deploy via
|
||||
# `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
|
||||
imagePullPolicy: Always
|
||||
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
|
||||
metadata:
|
||||
name: vncmail-settings
|
||||
namespace: vncmail
|
||||
spec:
|
||||
accessModes: [ReadWriteOnce]
|
||||
storageClassName: microk8s-hostpath
|
||||
@@ -17,7 +16,6 @@ apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: vncmail-admin
|
||||
namespace: vncmail
|
||||
spec:
|
||||
accessModes: [ReadWriteOnce]
|
||||
storageClassName: microk8s-hostpath
|
||||
@@ -29,7 +27,6 @@ apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: vncmail-admin-state
|
||||
namespace: vncmail
|
||||
spec:
|
||||
accessModes: [ReadWriteOnce]
|
||||
storageClassName: microk8s-hostpath
|
||||
@@ -41,7 +38,6 @@ apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: vncmail-telemetry
|
||||
namespace: vncmail
|
||||
spec:
|
||||
accessModes: [ReadWriteOnce]
|
||||
storageClassName: microk8s-hostpath
|
||||
@@ -2,7 +2,6 @@ apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: vncmail-plus
|
||||
namespace: vncmail
|
||||
labels:
|
||||
app: vncmail-plus
|
||||
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(/^([ #])/, '\\$1')
|
||||
.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, '');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user