Garlemald Server uses Semantic Versioning 2.0.0 driven by
git tags. The git tag vX.Y.Z is the source of truth; the root
Cargo.toml [workspace.package].version (inherited by every member crate via
version.workspace = true) and Cargo.lock are kept in lockstep automatically.
developis the default branch and the integration branch for day-to-day work. Branch features offdevelopand PR back into it.developis protected and requires the CI checks (fmt/clippy/build/test) plus an open pull request before merging (no approving review is required, so you can merge your own PR once CI is green).mainis the protected release branch. A release is cut by opening a PR fromdevelopintomain; when it merges, the push tomaintriggersrelease.yml(version bump + tag), which in turn triggersrelease-binaries.yml(per-platform binaries). Nothing merged intodevelopproduces a release or a tag — only thedevelop→mainmerge does.
Flow: feature → develop (CI-gated) → release PR develop → main (CI-gated) →
automatic bump + tag + GitHub Release with binaries.
mainintentionally does not require a pull request, so the release automation can push the bump commit straight to it viaRELEASE_PAT.The version bump lands on
mainonly (the tag is the source of truth), sodevelop'sCargo.tomlversion may lag the latest tag between releases. This does not cause merge conflicts — onlyrelease.ymlever edits the version line, so eachdevelop→mainmerge keepsmain's higher version automatically. If you'd rather keepdevelop's version current, periodically mergemainback intodevelop(a standard git-flow back-merge).
.github/workflows/release.yml runs on every push/merge to main (i.e. when a
develop → main release PR merges) and:
- reads the highest existing
vX.Y.Ztag, - picks a bump level (see below),
- rewrites
[workspace.package].versioninCargo.tomland runscargo update --workspaceto syncCargo.lock, - commits that back to
mainaschore(release): vX.Y.Z, - pushes an annotated
vX.Y.Ztag, and - publishes a GitHub Release with auto-generated notes.
That tag push then triggers release-binaries.yml (issue #15), which
cross-compiles the four server binaries per platform and attaches the archives
- SHA-256 checksums to the same Release — see Binary releases.
After a release, git describe --tags on main and [workspace.package].version
report the same X.Y.Z, and cargo build stamps every server binary with it.
| Bump | How to trigger |
|---|---|
| Patch | Default. Any merge to main with no release label → Z increments. |
| Minor | Add the release:minor label to the PR before merging → Y+1, Z=0. |
| Major | Add the release:major label to the PR before merging → X+1, Y=0, Z=0. |
Guidance: bump minor for a new subsystem/feature, major for a breaking
wire/protocol or DB-schema change. The label is read from the PR associated with
the merge commit; if both labels are present, release:major wins.
Because the next version is computed from the highest tag, you can also bump out-of-band by pushing a tag yourself:
git tag -a v0.2.0 -m v0.2.0 && git push origin v0.2.0The automation then continues patch-incrementing from there (v0.2.1, …). This
is handy for the first minor/major when no PR is involved.
The workflow needs to push the version-bump commit to main, which is
branch-protected (requires the fmt/clippy/build/test checks). The
default GITHUB_TOKEN cannot push to a protected branch, so the workflow uses a
Personal Access Token:
- Create a fine-grained PAT owned by a repo admin, scoped to only
Garlemald-Server, with Repository permissions → Contents: Read and write (and Pull requests: Read if you later want richer notes). - Save it as the repository secret
RELEASE_PAT(Settings → Secrets and variables → Actions). - Keep branch protection's "Do not allow administrators to bypass" OFF
(
enforce_admins: false). The admin-owned PAT relies on that exemption to push the bump commit past the required checks. If you turn admin enforcement on, the bump push will be rejected.
The release:minor and release:major labels must exist in the repo (created as
part of issue #13).
Security note. Because
RELEASE_PATis admin-owned andenforce_adminsis off, a leak of this token lets the holder push tomainbypassing the required checks — a strictly larger capability than vanilla Contents: write. Give the PAT a short expiry, rotate it on a schedule, and treat a suspected leak as urgent. The release step also needscrates.ioreachable (it runscargo update --workspaceto resyncCargo.lock); a registry/network blip will fail the run, which is safe to re-run.
A PAT push re-triggers workflows (unlike the default token), so the bump commit's
push to main would re-run this workflow. That loop is broken by the job's if
guard, which skips commits authored by github-actions[bot] (the release bot).
The bump commit is deliberately not marked [skip ci]. [skip ci] would
suppress not just this workflow but also the tag-push event — and the binary
release (release-binaries.yml) triggers on that tag push, so a skip marker would
silently prevent the per-platform binaries from ever building.
When the SemVer step pushes a vX.Y.Z tag, .github/workflows/release-binaries.yml
fires (on: push: tags: ['v*.*.*'], plus a manual workflow_dispatch). It:
- builds the four server binaries (
lobby-server,map-server,web-server,world-server) in--release --lockedacross a platform matrix (x86_64-unknown-linux-gnu,aarch64-apple-darwin,x86_64-apple-darwin,x86_64-pc-windows-msvc), - packages each platform into
garlemald-server-vX.Y.Z-<target>.tar.gz(.zipon Windows) with a matching.sha256, - and attaches all archives + checksums to the existing
vX.Y.ZRelease (idempotently, so re-runs replace assets rather than duplicating them).
This only works because the SemVer step pushes the tag with RELEASE_PAT (a PAT
push triggers downstream workflows) and the tagged commit is not [skip ci].
To (re)build assets for an existing tag, run the workflow manually via
workflow_dispatch with the tag name.
The sequence starts from the v0.1.0 tag on main. The first merge after the
release automation lands bumps it to v0.1.1 (or the labeled minor/major).