diff --git a/.gitea/workflows/server-deploy.yml b/.gitea/workflows/server-deploy.yml index 09f4959..3a7b859 100644 --- a/.gitea/workflows/server-deploy.yml +++ b/.gitea/workflows/server-deploy.yml @@ -71,16 +71,22 @@ jobs: fi } - # The four Go images build from the repo root and COPY - # shared/ plus their own directory, so shared/ rebuilds all - # four. proto/ is in server's list as insurance: the + # The three Go images here build from the repo root and + # COPY shared/ plus their own directory, so shared/ rebuilds + # all three. vantagectl also depends on shared/ but is NOT + # built here: it is a released tool, so its image is built and + # version-tagged by vantagectl-release.yml on a vantagectl/v* + # tag. A shared/ change therefore reaches it at the next + # release rather than on the next push to main, which is the + # point — an operator restoring a database should be running a + # version they can name, not whatever main built last night. + # proto/ is in server's list as insurance: the # generated pb is committed under server/, but a proto change # that someone regenerates in the same push should not depend # on that ordering. flag server '^(server/|shared/|proto/|default_steps/|go\.work)' flag sitesvc '^(sitesvc/|shared/|go\.work)' flag admin '^(admin/|shared/|go\.work)' - flag vantagectl '^(vantagectl/|shared/|go\.work)' # The three Next images and the docs site use their own # directory as the build context, so nothing outside it can @@ -160,14 +166,6 @@ jobs: docker build -t "$IMAGE" -f admin/Dockerfile . docker push "$IMAGE" - - name: Build and push vantagectl image - if: steps.changed.outputs.vantagectl == 'true' - run: | - IMAGE="${{ vars.DOCKER_HOST }}/${{ github.repository_owner }}/vantage/vantagectl:latest" - # Root context: vantagectl depends on the shared module. - docker build -t "$IMAGE" -f vantagectl/Dockerfile . - docker push "$IMAGE" - - name: Build and push adminsite image if: steps.changed.outputs.adminsite == 'true' run: | diff --git a/.gitea/workflows/vantagectl-release.yml b/.gitea/workflows/vantagectl-release.yml index 6b8ea57..716941c 100644 --- a/.gitea/workflows/vantagectl-release.yml +++ b/.gitea/workflows/vantagectl-release.yml @@ -58,3 +58,54 @@ jobs: vantagectl/dist/vantagectl-darwin-arm64 vantagectl/dist/vantagectl-windows-amd64.exe vantagectl/dist/checksums.txt + + # The image is built here rather than in server-deploy.yml on every push to + # main, because vantagectl is a released tool rather than a running service. + # An operator restoring a database should be able to name the version they + # ran; ":latest, rebuilt whenever main moved" cannot be named after the + # fact. It is a separate job from the binaries because it needs a + # docker-capable runner rather than a Go one, and it does not need the + # binaries — the image builds from source in its own stage. + image: + runs-on: ubuntu-docker + container: docker:dind + steps: + - name: Setup + run: apk add --update nodejs npm git + + - name: Checkout + uses: actions/checkout@v4 + + - name: Extract version + id: version + run: | + # v0.1.0 for the binary stamp, 0.1.0 for the image tag: a + # leading v is conventional on a git tag and unconventional on + # a container tag. + VERSION="${GITHUB_REF_NAME#vantagectl/}" + echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT + echo "IMAGE_TAG=${VERSION#v}" >> $GITHUB_OUTPUT + + - name: Log in to registry + run: | + echo "${{ secrets.RELEASE_TOKEN }}" | \ + docker login ${{ vars.DOCKER_HOST }} \ + -u "${{ secrets.REGISTRY_USER }}" --password-stdin + + - name: Build and push image + env: + VERSION: ${{ steps.version.outputs.VERSION }} + IMAGE_TAG: ${{ steps.version.outputs.IMAGE_TAG }} + run: | + REPO="${{ vars.DOCKER_HOST }}/${{ github.repository_owner }}/vantage/vantagectl" + # Root context: vantagectl depends on the shared module through + # a replace directive, so the build needs shared/ alongside it. + # VERSION is passed through so `vantagectl --version` inside the + # image reports the tag it was built from rather than "dev". + docker build \ + --build-arg VERSION="${VERSION}" \ + -t "${REPO}:${IMAGE_TAG}" \ + -t "${REPO}:latest" \ + -f vantagectl/Dockerfile . + docker push "${REPO}:${IMAGE_TAG}" + docker push "${REPO}:latest" diff --git a/CLAUDE.md b/CLAUDE.md index 29d254f..8f36683 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -657,8 +657,11 @@ temporary directory before verifying its checksums, and a scratch image has no `server`, but here it would break every restore rather than only vulnerability scanning. -**`shared/` now fans out to four Go images** in `server-deploy.yml`: -`server`, `sitesvc`, `admin` and `vantagectl` — see the CI section below. +**`shared/` reaches four Go images, but only three of them from +`server-deploy.yml`** (`server`, `sitesvc`, `admin`). The `vantagectl` image is +built by `vantagectl-release.yml` on a `vantagectl/v*` tag instead, so a +`shared/` change reaches it at the next release rather than the next push to +main — see the CI section below. ### API tokens and OpenAPI @@ -1318,7 +1321,7 @@ GOOS=linux GOARCH=amd64 go build \ ### `server-deploy.yml` — triggered on every push to `main` -Builds and pushes eight images to the Gitea container registry: `server`, `web`, `site`, `sitesvc`, `admin`, `adminsite`, `docsite` and `vantagectl`. +Builds and pushes seven images to the Gitea container registry: `server`, `web`, `site`, `sitesvc`, `admin`, `adminsite` and `docsite`. **`vantagectl` is deliberately not among them** — it is a released tool rather than a running service, and its image is version-tagged by `vantagectl-release.yml`. Note that despite the name, **this workflow does not deploy** — it only builds and pushes. There is no SSH step. Rolling images out is a separate manual step on the host: @@ -1334,13 +1337,16 @@ cd /opt/vantage && docker compose -f docker-compose.yml -f docker-compose.site.y | `server` | `server/`, `shared/`, `proto/`, `go.work` | | `admin` | `admin/`, `shared/`, `go.work` | | `sitesvc` | `sitesvc/`, `shared/`, `go.work` | -| `vantagectl` | `vantagectl/`, `shared/`, `go.work` | | `web` · `site` · `adminsite` · `docsite` | their own directory only | -`shared/` fans out to **four** Go images (`server`, `sitesvc`, `admin`, -`vantagectl`) because each of their Dockerfiles copies `shared/` from a root -context — **if a fifth service ever imports `shared/`, add it to that list or -it will ship stale**. A change to the workflow file rebuilds everything, since +`shared/` fans out to **three** images here (`server`, `sitesvc`, `admin`) +because each of their Dockerfiles copies `shared/` from a root context — **if a +fourth service ever imports `shared/`, add it to that list or it will ship +stale**. `vantagectl` also imports `shared/` and is the exception: it is built +by `vantagectl-release.yml`, so a `shared/` fix reaches it only when someone +cuts a `vantagectl/v*` tag. That is deliberate — an operator restoring a +database should be running a version they can name — but it does mean a +`shared/backup` fix is not live until it is released. A change to the workflow file rebuilds everything, since a build arg is baked into the image. So does anything that leaves no trustworthy base commit: a manual `workflow_dispatch`, a new branch, or a force-push whose old head is gone. diff --git a/docsite/docs/operations/backup-and-restore.md b/docsite/docs/operations/backup-and-restore.md index 57f60e8..cc604b9 100644 --- a/docsite/docs/operations/backup-and-restore.md +++ b/docsite/docs/operations/backup-and-restore.md @@ -40,6 +40,13 @@ nobody can read. reconnect on their own, because `servers.agent_token_hash` — the thing an agent authenticates with — is itself in the backup. +:::note Pin the version +The image is published on each `vantagectl/v*` release and tagged with that +version; `:latest` also moves. Pin a version in anything scheduled. A restore +is easier to reason about when you can say which build produced the archive and +which one read it back. +::: + ## Taking a backup The loose binary: @@ -59,7 +66,7 @@ docker run --rm \ -e MONGO_DB=vantage \ -e KEY_ENCRYPTION_KEY= \ -v /backups:/backups \ - gitea.hostxtra.co.uk/mrhid6/vantage/vantagectl:latest backup --out /backups + gitea.hostxtra.co.uk/mrhid6/vantage/vantagectl:0.1.0 backup --out /backups ``` Kubernetes, as a scheduled `CronJob` the Helm chart can render for you: @@ -68,7 +75,7 @@ Kubernetes, as a scheduled `CronJob` the Helm chart can render for you: backup: enabled: true schedule: "0 2 * * *" - image: "gitea.hostxtra.co.uk/mrhid6/vantage/vantagectl:latest" + image: "gitea.hostxtra.co.uk/mrhid6/vantage/vantagectl:0.1.0" pvcName: "vantage-backups" ```