Skip to content

Latest commit

 

History

History
164 lines (127 loc) · 8.53 KB

File metadata and controls

164 lines (127 loc) · 8.53 KB

rust-template

GitHub template repository for new Rust projects. Provides CI, a staging branch for dependabot updates, a scheduled security audit, release tagging and three publish workflows out of the box.

Use it

  1. Click "Use this template" -> "Create a new repository" on GitHub.
  2. Rename the package in Cargo.toml and update the binary path in Dockerfile (the target/release/rust-template line).
  3. Set the committer identity: pass name and email to the bump-release-action step in bump-and-release.yml (it defaults to voidmason).
  4. Create a dependencies branch from master: dependabot targets it, and the sync workflow requires it to exist.
  5. Add the secrets listed in Secrets.

Dependency flow

Dependabot opens weekly PRs into the dependencies branch, where updates accumulate away from the trunk. Each PR is gated by lite CI (ci-dependencies.yml): the reusable checks alone, without the release build that full CI adds.

On every push to master the sync workflow merges the trunk into dependencies (branch-sync-action): a clean merge is pushed, a lockfile-only conflict is regenerated automatically, a source conflict opens a PR to resolve by hand. Delivery back to the trunk is a single reviewed PR dependencies -> master, which runs the full ci.yml.

Workflows

File Trigger Purpose
ci.yml push/PR on master, manual checks -> release build
checks.yml workflow_call fmt + clippy + check + test, reused as a gate
ci-dependencies.yml push/PR on dependencies, manual lite CI for dependency bumps: checks, no build
sync-dependencies.yml push on master, manual merge master into the dependencies branch
audit.yml weekly cron, manifest changes, manual cargo audit against the RustSec advisory DB
bump-and-release.yml manual bump version, tag, GitHub Release
publish-crates.yml manual on a tag publish to crates.io
publish-docker-hub.yml manual on a tag build and push image to Docker Hub, multi-arch
publish-ghcr.yml manual on a tag build and push image to ghcr.io, multi-arch

CI

checks.yml is the shared entry gate. It runs fmt, clippy, cargo check and cargo test --all-features on the toolchain from rust-toolchain.toml. ci.yml chains the gate, then a release build; ci-dependencies.yml runs the gate alone.

Keep rust-toolchain.toml: neither the CI jobs nor the Dockerfile carry a toolchain step. Without it, CI falls back to the runner's bundled Rust and the Docker build to the image's.

Bump and release

Manual trigger from any branch (selected via "Use workflow from"). The only input besides commit_note is version - it carries the whole intent, there are no separate beta flags.

version What it does Example Release
patch --bump patch 0.1.1 -> 0.1.2 full
minor --bump minor 0.1.1 -> 0.2.0 full
major --bump major 0.1.1 -> 1.0.0 full
beta --bump beta 0.1.1 -> 0.1.2-beta.1 prerelease
beta-minor next minor + -beta.1 0.1.1 -> 0.2.0-beta.1 prerelease
beta-major next major + -beta.1 0.1.1 -> 1.0.0-beta.1 prerelease
finalize --bump release 0.1.2-beta.3 -> 0.1.2 full

Version math is delegated to cargo set-version (cargo-edit); bash only routes the action:

  • beta is universal: on a release version it starts a patch pre-release, on any pre-release it increments the counter (beta.1 -> beta.2 -> ...). cargo picks by current state, so there is no separate "continue".
  • beta-minor/beta-major exist because --bump beta always patches the base, so a minor/major pre-release cannot be started in one command.
  • A beta series is tied to one target version. While the version is a pre-release, repeating beta accumulates beta.N. Switching the level mid-series starts a new one (beta-minor/beta-major).
  • finalize strips the suffix. With no active pre-release it is a no-op: the workflow fails loudly instead of producing an empty commit.
  • prerelease vs full GitHub Release is chosen by the -beta.N suffix in the tag, not a separate flag.

The checks gate comes first; nothing is bumped or tagged if it fails. Then bump-release-action runs cargo set-version (Cargo.lock follows), commits the changed manifests, tags vX.Y.Z[-beta.N], and atomically pushes the commit and tag to the selected branch. It uses RELEASE_TOKEN throughout: a push made with the default GITHUB_TOKEN would not trigger other workflows, so CI and the dependencies sync would skip the bump commit. The same action then creates the GitHub Release - final tags only, a beta stays a plain tag - also via RELEASE_TOKEN.

Publish

Each publish workflow is triggered manually with "Use workflow from: tags/vX.Y.Z". A guard rejects runs from a branch. Each one first runs the reusable checks against the tagged commit and publishes only if they pass.

publish-crates takes a dry_run flag, publish-docker-hub asks for the image name (namespace/name), GHCR derives everything from the repository. Docker tags come from the tag name; latest is set only for non-beta tags.

Publish is manual on purpose, so a tag without a published artifact is allowed. If you switch to auto-publishing on tag push, you will need a PAT: a tag pushed with GITHUB_TOKEN does not trigger other workflows.

Audit

cargo audit checks the dependency tree against the RustSec advisory database every Monday at 06:00 UTC, on any push touching Cargo.toml/Cargo.lock, and on demand. The schedule surfaces new advisories even when the repository is quiet.

Dependabot

Two ecosystems, both opening PRs into the dependencies branch:

  • cargo - weekly, max 5 open PRs, minor+patch grouped, major opens its own PR
  • github-actions - weekly, max 5 open PRs, same grouping

Secrets

Add these in repository settings before using the relevant workflow:

Secret Used by Notes
RELEASE_TOKEN bump-and-release, sync-dependencies PAT, see below
CARGO_REGISTRY_TOKEN publish-crates crates.io API token
DOCKERHUB_USERNAME publish-docker-hub Docker Hub login
DOCKERHUB_TOKEN publish-docker-hub Docker Hub access token

RELEASE_TOKEN is a PAT with contents and pull requests read/write (classic: repo), plus the workflow scope - a sync can carry changes under .github/workflows. A PAT rather than GITHUB_TOKEN because pushes made with the default token do not trigger the workflows that must run on the pushed commits: CI on the bump commit, the lite CI on a synced dependencies.

GHCR publish uses the built-in GITHUB_TOKEN (packages: write). No extra secret. The GitHub Release is created by bump-and-release with RELEASE_TOKEN, not GITHUB_TOKEN.

Dockerfile

Multi-stage build: compile on rust:1-bookworm, run from distroless/cc-debian12. Both are Debian 12, so the binary gets the same glibc at build and at run time - a load-bearing pairing; bump the builder and runtime tags together.

The binary path is target/release/rust-template. Rename it to match the package name in Cargo.toml, or the COPY --from=builder step fails.

A .dockerignore keeps .git, target and local env files out of the build context.

distroless/cc ships only glibc, libgcc and libssl, and has no package manager. A crate that dynamically links another C library builds cleanly but fails at startup on the missing .so - link it statically or vendor it (openssl's vendored feature, or rustls), or change the runtime base. Build-time -dev packages still go in the builder stage via apt-get.