Files
SRCmail/.gitlab-ci.yml
T
Bernd Rodler 68d08dbae6 fix(ci): revert to GHCR - GitLab's own registry never got past step 1
GitLab's Container Registry was enabled at the omnibus service level
(registry.gitlab.vnc.biz responds, confirmed with a real GitLab-shaped
401), but the pipeline's build job kept trying to auth against Docker
Hub instead - CI_REGISTRY was empty. Root cause: registry_external_url
only starts the registry SERVICE; gitlab_rails['registry_enabled'] = true
is a separate key that tells the Rails app the registry exists, and it
was never set. Symptom matched exactly: registry reachable, but no
Container Registry toggle anywhere in project settings OR admin settings,
and CI_REGISTRY empty in every job regardless of retry.

Reverting the pipeline to ghcr.io/brvncde-dotcom/vncmail-plus-dev - the
exact image the sandbox was already running before any of this session's
pipeline existed, confirmed public (no imagePullSecrets needed). This is
a revert to a known-working path, not a new risk.

Needs $GITLAB_CI_GHCR_TOKEN (GitHub PAT, write:packages) and
$GITLAB_CI_GHCR_USER as masked/protected CI/CD variables - a GitHub
credential has to come from GitHub, nothing on the GitLab side can
substitute for it.
2026-08-05 19:16:21 +02:00

186 lines
9.0 KiB
YAML

# 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):
# - A GitHub PAT (write:packages) for ghcr.io/brvncde-dotcom as
# $GITLAB_CI_GHCR_TOKEN, plus the matching GitHub username as
# $GITLAB_CI_GHCR_USER — both masked/protected CI/CD variables.
# GitLab's own Container Registry was tried first (registry_external_url
# alone isn't enough - gitlab_rails['registry_enabled'] = true is a
# separate config key that never got set, so CI_REGISTRY stayed empty);
# revisit switching back once that's actually confirmed working.
# - 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:
# Reverted to GHCR 2026-08-05: GitLab's own Container Registry never made
# it past "the registry service responds" - the Rails app itself never
# picked it up (no project-settings toggle appeared, CI_REGISTRY stayed
# empty even after the omnibus registry_external_url config), most likely
# because gitlab_rails['registry_enabled'] = true was never separately
# set. That's a second, distinct config key from registry_external_url -
# revisit switching back once/if that's actually confirmed on the server.
# Same image name the sandbox already ran before this pipeline existed
# (confirmed public - no imagePullSecrets needed, see
# base/deployment.yaml's history), so this is a revert, not a new risk.
IMAGE: ghcr.io/brvncde-dotcom/vncmail-plus-dev
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:
# GHCR needs a real GitHub PAT (write:packages) as GITLAB_CI_GHCR_TOKEN,
# plus GITLAB_CI_GHCR_USER (the GitHub username the PAT belongs to) —
# both as masked/protected CI/CD variables. Nothing GitLab-native can
# substitute here; a GitHub credential has to come from GitHub.
- echo "$GITLAB_CI_GHCR_TOKEN" | docker login ghcr.io -u "$GITLAB_CI_GHCR_USER" --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