Upgrade & Release
Subnetra is a single static binary with no persistent on-disk data-plane state, so a node upgrade is mechanically “swap the binary and restart.” The real risk is wire compatibility across a mesh, and the procedure below manages it.
Upgrade & rollback runbook
Because the transport is fail-closed — a datagram that fails AEAD
authentication is silently dropped — a mesh that is half-upgraded across a
wire-breaking boundary silently partitions: there is no error, only a climbing
auth_or_invalid drop counter on both sides.
Safe procedure:
- Read the release notes for any wire-breaking change. If the wire is unchanged, nodes interoperate across the upgrade and ordering does not matter.
- Stage a canary. Upgrade one spoke first and watch
subnetra statuson both it and the hub —onlinestays true andauth_or_invalidstays flat. - Roll forward the rest. The binary swap is atomic; restart is stateless (a fresh session epoch is derived each lifetime).
- Rollback is the reverse binary swap + restart. No state migration is involved.
# Per node
sudo install -m 0755 subnetrad subnetra /usr/local/bin/
subnetrad --check --config /etc/subnetra/config.json
sudo systemctl restart subnetrad # stateless restart; a new epoch is derived
subnetra status # confirm peers online, auth_or_invalid flat
Keep the previous binary around for an instant rollback, and watch
auth_or_invalidas your partition alarm during the rollout.
Key rotation runbook
PSKs are per link. To rotate one link’s key without a flag day, exploit that each direction/epoch is keyed independently:
- Generate a new PSK (
openssl rand -hex 32). - Update both ends of the link to the new PSK.
- Restart both daemons (or restart one and accept a brief
auth_or_invalidblip until the other follows). Because there is no shared mesh key, only this one link is affected.
Watch auth_or_invalid during the change: a transient rise as the two ends cross
over is expected; a sustained rise means the two ends disagree on the key.
The full step-by-step is in
docs/deployment.md §6 “Key rotation runbook”.
Cutting a release (maintainers)
The release version lives in exactly one place: the .version field of
build.zig.zon. It
is injected into the daemon banner at build time via the build_options module —
never hard-code a version string in src/.
To publish vX.Y.Z:
- Bump
.versioninbuild.zig.zonto the newX.Y.Z(semantic versioning). - Commit that bump on
mainvia the normal PR flow. - Tag the commit
vX.Y.Z— the tag must equalv+ thebuild.zig.zonversion. A guard job fails the release if they disagree, so a mismatched tag never ships. - Pushing the
v*tag triggers.github/workflows/release.yml, which builds the four-arch static binaries, the GHCR multi-arch image, the offlinedocker load-able per-arch image tarballs, and the macOS spoke binaries, and publishes them all to the GitHub Release with a combinedSHA256SUMS.txt.
Do not create a v* tag without first bumping build.zig.zon to match. The
release process is documented in
docs/release.md.
Verifying downloads
Every release ships a SHA256SUMS.txt. Verify any asset before installing or
docker load-ing it:
sha256sum -c SHA256SUMS.txt 2>/dev/null | grep subnetra-<version>-linux-amd64.tar.gz