ci: publish and deploy verified releases

This commit is contained in:
Alex 2026-08-24 23:36:36 +02:00
parent 3fa278d447
commit 99fde46ed3
6 changed files with 225 additions and 0 deletions

View file

@ -5,6 +5,7 @@
.codex .codex
.claude .claude
.cursor .cursor
.ci
.idea .idea
.vscode .vscode
AGENTS.md AGENTS.md

3
.forgejo/actionlint.yaml Normal file
View file

@ -0,0 +1,3 @@
self-hosted-runner:
labels:
- docker

View file

@ -0,0 +1,181 @@
name: CI and deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
jobs:
verify:
runs-on: docker
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Scan secrets
run: |
docker run --rm -v "$PWD:/repo" \
ghcr.io/gitleaks/gitleaks:v8.30.1 \
detect --source /repo --redact -v
- name: Test API
run: |
docker run --rm -v "$PWD/apps/api:/src" -w /src golang:1.26 \
bash -ec 'go vet ./... && go test -race ./...'
- name: Test load generator
run: |
docker run --rm -v "$PWD/apps/loadgen:/src" -w /src golang:1.26 \
bash -ec 'go vet ./... && go test -race ./... && test "$(wc -l < main.go)" -lt 200'
- name: Lint Go
run: |
docker run --rm -v "$PWD:/repo" -w /repo/apps/api \
golangci/golangci-lint:v2.12.2 golangci-lint run
docker run --rm -v "$PWD:/repo" -w /repo/apps/loadgen \
golangci/golangci-lint:v2.12.2 golangci-lint run
- name: Check reachable Go vulnerabilities
run: |
docker run --rm -v "$PWD/apps/api:/src" -w /src golang:1.26 \
bash -ec 'go run golang.org/x/vuln/cmd/govulncheck@v1.7.0 ./...'
docker run --rm -v "$PWD/apps/loadgen:/src" -w /src golang:1.26 \
bash -ec 'go run golang.org/x/vuln/cmd/govulncheck@v1.7.0 ./...'
- name: Check frontend and shell syntax
run: |
docker run --rm -v "$PWD:/repo" -w /repo node:24-alpine \
node --check apps/web/app.js
docker run --rm -v "$PWD:/repo" -w /repo bash:5.3 \
bash -ec 'for file in scripts/k3d/lab.sh scripts/provision/bootstrap.sh scripts/provision/lab.sh; do bash -n "$file"; done'
- name: Validate Compose and Kubernetes configuration
env:
CLUSTER_API_UPSTREAM: 192.0.2.1:8080
NEREUS_HOST: nereus.example.test
run: |
docker compose -f compose.yaml config -q
docker compose -f build/compose.mini.yaml config -q
for path in \
deploy/base \
deploy/overlays/dev \
deploy/overlays/prod \
deploy/rollouts \
deploy/observability
do
docker run --rm -v "$PWD:/work" -w /work \
registry.k8s.io/kubectl:v1.33.4 kustomize "$path" >/dev/null
done
- name: Scan configuration
run: |
docker run --rm -v "$PWD:/repo" -w /repo aquasec/trivy:0.74.0 \
config --severity HIGH,CRITICAL --exit-code 1 \
--skip-dirs .git --skip-dirs terraform .
publish:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: verify
runs-on: docker
env:
API_IMAGE: git.fiwlabs.dev/fiwdev/nereus-api:${{ github.sha }}
LOADGEN_IMAGE: git.fiwlabs.dev/fiwdev/nereus-loadgen:${{ github.sha }}
WEB_IMAGE: git.fiwlabs.dev/fiwdev/nereus-web:${{ github.sha }}
DOCKER_CONFIG: ${{ github.workspace }}/.ci/docker
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Log in to registry
env:
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
run: |
install -d -m 0700 "$DOCKER_CONFIG"
printf '%s' "$REGISTRY_PASSWORD" | \
docker login git.fiwlabs.dev --username "$REGISTRY_USERNAME" --password-stdin
- name: Build and push images
run: |
docker build -f build/api.Dockerfile \
--build-arg "APP_VERSION=${{ github.sha }}" \
-t "$API_IMAGE" .
docker build -f build/loadgen.Dockerfile -t "$LOADGEN_IMAGE" .
docker build -f build/web.Dockerfile -t "$WEB_IMAGE" .
for image in "$API_IMAGE" "$LOADGEN_IMAGE" "$WEB_IMAGE"
do
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy:0.74.0 image --severity HIGH,CRITICAL \
--ignore-unfixed --exit-code 1 "$image"
done
docker push "$API_IMAGE"
docker push "$LOADGEN_IMAGE"
docker push "$WEB_IMAGE"
- name: Remove registry credentials
if: always()
run: rm -rf .ci
deploy:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: publish
runs-on: docker
env:
API_IMAGE: git.fiwlabs.dev/fiwdev/nereus-api:${{ github.sha }}
LOADGEN_IMAGE: git.fiwlabs.dev/fiwdev/nereus-loadgen:${{ github.sha }}
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Prepare cluster access
env:
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
KUBECONFIG_B64: ${{ secrets.KUBECONFIG_B64 }}
run: |
install -d -m 0700 .ci
printf '%s' "$KUBECONFIG_B64" | base64 -d >.ci/kubeconfig
chmod 0600 .ci/kubeconfig
printf '%s' "$REGISTRY_PASSWORD" | \
docker --config "$PWD/.ci/docker" login git.fiwlabs.dev \
--username "$REGISTRY_USERNAME" --password-stdin
- name: Apply and verify rollout
run: |
kube() {
docker run --rm --network host \
-v "$PWD:/work" -w /work \
registry.k8s.io/kubectl:v1.33.4 \
--kubeconfig=/work/.ci/kubeconfig "$@"
}
kube create namespace nereus --dry-run=client -o yaml | kube apply -f -
kube create secret generic nereus-registry \
--namespace nereus \
--type kubernetes.io/dockerconfigjson \
--from-file=.dockerconfigjson=/work/.ci/docker/config.json \
--dry-run=client -o yaml | kube apply -f -
kube get secret nereus-db --namespace nereus >/dev/null
kube apply -k deploy/observability
kube apply -k deploy/overlays/prod
kube patch rollout.argoproj.io nereus-api --namespace nereus --type merge \
--patch "{\"spec\":{\"template\":{\"spec\":{\"containers\":[{\"name\":\"api\",\"image\":\"$API_IMAGE\"}]}}}}"
kube set image deployment/nereus-loadgen --namespace nereus \
"loadgen=$LOADGEN_IMAGE"
kube set image deployment/nereus-loadgen-preview --namespace nereus \
"loadgen=$LOADGEN_IMAGE"
kube rollout status deployment/nereus-loadgen --namespace nereus --timeout=5m
kube rollout status deployment/nereus-loadgen-preview --namespace nereus --timeout=5m
kube wait rollout.argoproj.io/nereus-api --namespace nereus \
--for=jsonpath='{.status.phase}'=Healthy --timeout=10m
kube get rollout.argoproj.io/nereus-api --namespace nereus
- name: Remove cluster credentials
if: always()
run: rm -rf .ci

1
.gitignore vendored
View file

@ -75,6 +75,7 @@ CLAUDE.local.md
.direnv/ .direnv/
.envrc .envrc
.ci/
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Terraform. The lock file is committed on purpose; state never is. # Terraform. The lock file is committed on purpose; state never is.

29
docs/ci-cd.md Normal file
View file

@ -0,0 +1,29 @@
# Forgejo CI/CD
`.forgejo/workflows/pipeline.yml` runs verification for pushes and pull
requests. A push to `main` also publishes immutable commit-SHA tags for the API,
load generator, and web images, then deploys the API and load generator through
the production overlay and Argo Rollouts.
Two load-generator Deployments keep traffic on both the active and preview
Services. Preview traffic is required for the pre-promotion analysis to measure
the candidate revision instead of treating absent samples as success.
The repository needs a dedicated Forgejo runner labelled `docker`. It must have
Docker with Compose support, outbound access to the configured registries, and
network access to the Kubernetes API endpoint contained in the kubeconfig.
Keep this runner private to trusted repositories because deployment jobs can
access the Docker socket and production credentials.
Configure these encrypted repository or organization Actions secrets in the
Forgejo UI:
- `REGISTRY_USERNAME`: account allowed to push the three Nereus packages.
- `REGISTRY_PASSWORD`: package-scoped token for that account.
- `KUBECONFIG_B64`: base64 encoding of a least-privilege deployment kubeconfig.
The workflow never prints these values. It writes the kubeconfig to the
ephemeral workspace with mode `0600`, removes it in an `always()` step, and
creates the Kubernetes registry pull secret through a pipe so its generated
manifest is not logged or committed. The pre-existing `nereus-db` Secret is
required and is only checked for presence.

View file

@ -209,6 +209,16 @@ task. Mark a task `[x]` only after its stated verification passes.
- [H] Maintain Forgejo Actions and GitHub mirror workflows. - [H] Maintain Forgejo Actions and GitHub mirror workflows.
- [H] Create sealed secrets from off-repository plaintext inputs. - [H] Create sealed secrets from off-repository plaintext inputs.
## Forgejo CI/CD
- [x] Run API and load-generator race tests, vet, and pinned lint in containers.
- [x] Scan committed history and the working tree for secrets.
- [x] Scan release configuration and images for high or critical findings.
- [x] Build immutable commit-SHA API, load-generator, and web image tags.
- [x] Keep active and preview traffic flowing during rollout analysis.
- [~] Run the Forgejo workflow after a private `docker` runner and encrypted secrets are configured.
- [~] Publish images and execute the first automated production rollout.
## End-to-end acceptance ## End-to-end acceptance
- [ ] Provision two clean Fedora hosts. - [ ] Provision two clean Fedora hosts.