Skip to content

CI & Release Process

Every platform repository uses the same pipeline rules. The gateway agent adds release channels on top.

Pipelines run on merge requests and tags

workflow:
  rules:
    - if: $CI_COMMIT_TAG
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

A pipeline runs for a merge request or for a tag. Pushing to main runs no pipeline and deploys nothing: a merged change reaches no environment until the commit is tagged.

Stage Runs on Purpose
check merge requests Tests, formatting, type checks, lint
build tags Build the image or binary
deploy / publish tags Ship it
promote tags, manual Gateway only: move a build to the stable channel

check is the merge gate and does not run on tags. A commit pushed straight to main is therefore untested by CI.

Services (portal, nexus, realtime)

A tag builds an image per environment and deploys:

Job When
deploy_dev automatically on the tag
deploy_stg, deploy_prd manual: press play in the tag pipeline

Nexus and radius-ctrl pull their build and deploy job templates from digilan/infra/ci-templates with include:.

To release: merge the MR, tag the merge commit on main, then run the manual staging and production jobs when ready.

Gateway agent

The agent is a binary that devices download, so its pipeline publishes to a release registry rather than deploying a container. A tag runs:

Job What it does
build:amd64, build:arm64 Cross-compile for OpenBSD (CGO_ENABLED=0, -trimpath), stamp main.version with the tag and bake in the OTA signing public key; sign with ed25519 and verify the signature before going on
publish:dev:<arch>, publish:prod:<arch> Upload to that environment's release bucket, then register the release on the beta channel with that environment's Nexus
promote:stable:dev:<arch>, promote:stable:prod:<arch> Manual. Register the same object (same sha256, signature and key, no rebuild) on the stable channel
  • The tag is the version, verbatim: v1.2.3-beta is version v1.2.3-beta. Tags are named vX.Y.Z-beta by convention; every tag, whatever its name, publishes to beta only.
  • Every tag publishes to every environment (dev and prod), with identical bytes. A failure in one environment's job leaves the other untouched and can be retried alone.
  • Promotion is per environment and per architecture. Run it once a canary device on beta has taken the update and stayed healthy. Devices follow their own channel (stable by default); a beta release is invisible to them until promoted.
  • Nexus refuses a second registration of the same version, architecture and channel, so re-running a job that already succeeded fails rather than registering twice.
  • Merge requests run go build, go vet, go test -race and a gofmt check.

A manual pipeline on main with BACKFILL_TAG=<tag> registers an existing tag's build in prod without rebuilding it: it fetches that tag's build:<arch> artifact (kept for one week), checks it is a clean build of that tag, re-derives sha256 and signature, and runs the prod publish and promote jobs.

CI variables

Names only; values come from tofu output in infra/terraform.

Scope Variables
shared OTA_S3_ENDPOINT, OTA_S3_REGION, OTA_SIGNING_PRIVATE_KEY (file), OTA_SIGNING_PUBLIC_KEY (file)
dev OTA_S3_BUCKET, OTA_PUBLISHER_ACCESS_KEY, OTA_PUBLISHER_SECRET_KEY, NEXUS_URL, RELEASE_PUBLISHER_KEY
prod OTA_S3_BUCKET_PROD, OTA_PUBLISHER_ACCESS_KEY_PROD, OTA_PUBLISHER_SECRET_KEY_PROD, NEXUS_URL_PROD, RELEASE_PUBLISHER_KEY_PROD

One signing key serves all environments, so a device moved between environments keeps accepting updates. The bucket publisher identities are write-only.

Only pipeline builds can self-update

The signing public key is baked into the binary at build time. A build made without it (make build-openbsd, or any local build without -X main.otaSigningPublicKey=...) logs self-update disabled and must be replaced by hand. The install image builders therefore refuse an agent without the release key (bench override: ALLOW_UNSIGNED_AGENT=1); see Build the install image.

See Agent Releases for channels and rollout, and OTA Updates for how a device installs a release.