Reusable CI/CD workflows, composite actions, and Taskfile modules for Solti repositories.
Consumer repositories keep their project-specific commands and release configuration.
This repository owns the shared GitHub Actions machinery and selects versioned toolchain containers from
soltiHQ/images.
| Quick start | Workflows | Actions | Taskfiles |
| Component | Location | Used from |
|---|---|---|
| Reusable workflows | .github/workflows/ |
A caller workflow job through uses |
| Composite actions | <name>/action.yml |
A workflow step through uses |
| Taskfile modules | taskfiles/ |
A repository Taskfile.yml through includes |
Workflows and actions use the v1 tag.
Remote Taskfile includes should use the same tag.
Most Taskfile modules run tools inside versioned images from soltiHQ/images:
| Module | Image | Version source |
|---|---|---|
| Rust | ghcr.io/soltihq/ci/rust:<version> |
Root Cargo.toml rust-version |
| Go | ghcr.io/soltihq/ci/golang:<version> |
Root go.mod go directive |
| Docs | ghcr.io/soltihq/ci/docs:<version> |
Shared Docs Taskfile |
| Node | ghcr.io/soltihq/ci/node:<version> |
Shared Node Taskfile |
| Terraform | ghcr.io/soltihq/ci/terraform:<version> |
Shared Terraform Taskfile |
| AWS | ghcr.io/soltihq/ci/aws:<version> |
Shared AWS Taskfile |
CI refreshes the selected image before each task. Local runs reuse an existing image and pull it when missing.
The Proto module runs local buf and clang-format executables. Its reusable CI workflow pins Buf 1.50.0 and
clang-format 18, while local consumers can run formatting without Docker or registry credentials.
A Rust repository exposes its own ci/* tasks through Taskfile.yml:
version: '3'
includes:
rust:
taskfile: https://raw.githubusercontent.com/soltiHQ/actions/v1/taskfiles/rust/Taskfile.yml
tasks:
ci/fmt:
cmds:
- task: rust:fmt
ci/check:
cmds:
- task: rust:check
vars: { CHECK_ARGS: '--all-targets --all-features --locked' }The pull-request workflow delegates CI to the shared workflow:
name: PR
on:
pull_request:
jobs:
ci:
uses: soltiHQ/actions/.github/workflows/rust-ci.yml@v1For a Cargo workspace, enable workspace-specific checks:
jobs:
ci:
uses: soltiHQ/actions/.github/workflows/rust-ci.yml@v1
with:
workspace: trueThe branch-protection check is ci / gate when the caller job is named ci.
Documentation uses the separate docs / gate check when its caller job is named docs.
| Workflow | Purpose |
|---|---|
rust-ci.yml |
Validate a Rust package or workspace |
proto-ci.yml |
Validate Protobuf formatting, schema, and compatibility |
docs-ci.yml |
Validate product-owned documentation sources and examples |
proto-release.yml |
Verify and publish one tagged Protobuf contract release |
rust-release.yml |
Publish Rust crates in dependency order and create one release |
label-check.yml |
Require a changelog label declared in .github/release.yml |
docs-notify.yml |
Notify the site about an exact product documentation release |
static-ci.yml |
Build a static site and validate its Terraform stack |
static-release.yml |
Apply infrastructure, upload a tagged site, and invalidate its CDN |
TODO: fix it; The Rust release workflow installs
protocbefore Cargo publishes package tarballs.
rust-ci.yml calls the consumer repository's ci/* tasks.
The shared workflow owns job isolation, caching, matrices, and the final gate.
| Job | Consumer task or behavior |
|---|---|
fmt |
ci/fmt |
MSRV |
ci/check |
unittest |
ci/test-unit |
integration |
ci/test-integration |
clippy |
ci/clippy FEATURE=<configuration> |
audit |
ci/audit |
examples-build |
ci/build CRATE=<package> for packages containing examples |
package |
ci/package when workspace: true |
preflight |
Advisory ci/publish-dry-run; not included in the final gate |
gate |
Require every non-advisory CI dependency to succeed |
Package repositories test none, every declared feature, and all in separate Clippy jobs.
Workspace repositories test none and all across the workspace.
A workspace can add focused package configurations:
[workspace.metadata.ci]
clippy-matrix = [
{ package = "my-crate", features = ["feature-a", "feature-b"] },
]Each entry must name an existing workspace package and existing features.
proto-ci.yml calls the consumer repository's ci/fmt, ci/lint, ci/build, and ci/breaking tasks in isolated
jobs, then exposes their combined result through gate. It pins Buf 1.50.0 and clang-format 18.
jobs:
ci:
name: ci
uses: soltiHQ/actions/.github/workflows/proto-ci.yml@v1docs-ci.yml checks out the product repository and runs its stable ci/docs task. The product task owns its API
documentation, doctest, example, manifest, and content policy. The reusable workflow does not checkout or build the
site renderer and does not receive deployment credentials.
jobs:
docs:
name: docs
uses: soltiHQ/actions/.github/workflows/docs-ci.yml@v1The branch-protection check is docs / gate when the caller job is named docs. The reusable workflow passes the
current repository token to ci/docs, allowing its external link check to authenticate GitHub requests.
proto-release.yml verifies that the exact SemVer tag matches the repository's root version file and that the
tagged commit belongs to main. It then creates a GitHub release with the exact proto_ref consumers can pin.
jobs:
release:
uses: soltiHQ/actions/.github/workflows/proto-release.yml@v1Call docs-notify.yml from a dependent job when the tagged contract also publishes product documentation.
label-check.yml reads allowed changelog and exclusion labels from the caller's .github/release.yml.
The pull request must carry at least one of them.
jobs:
label-check:
uses: soltiHQ/actions/.github/workflows/label-check.yml@v1The branch-protection check is label-check / required when the caller job is named label-check.
static-ci.yml runs the consumer's code build, dependency audit, and Terraform format/validate checks.
When docs-task is set, it also runs that Taskfile command and includes the result in the gate.
static-release.yml verifies that the tag belongs to the default branch, builds one artifact, applies Terraform,
exports the resulting infrastructure outputs, uploads the artifact to the resolved S3 bucket, invalidates CloudFront,
and creates the GitHub release.
The release workflow receives infrastructure coordinates as typed inputs. AWS authentication uses a role assumed through GitHub OIDC; consumers do not pass long-lived AWS access keys. Terraform owns infrastructure state and output generation. The deploy job receives only the resolved bucket, CloudFront distribution ID, and AWS region before invoking the consumer's AWS deployment task.
Product repositories own the Markdown sources and docs/site.yml. The manifest declares product identity,
compatibility line, version source, and navigation. It may also declare an API reference. Each page has title and
description frontmatter and appears once in the navigation. Detailed API contracts stay in the product's reference
documentation.
Optional api and examples blocks add generated navigation pages. Rust products can build a flat API map with
docs:api/generate; the examples page consumes one catalog and every Rust source declared by that catalog.
docs-ci.yml validates the sources on pull requests. After a release succeeds, call docs-notify.yml to send its
exact tag and commit to the site:
jobs:
docs:
needs: release
uses: soltiHQ/actions/.github/workflows/docs-notify.yml@v1
with:
site-repository: soltiHQ/site
source-repository: ${{ github.repository }}
source-ref: ${{ github.ref_name }}
client-id: ${{ vars.DOCS_SITE_APP_CLIENT_ID }}
secrets:
private-key: ${{ secrets.DOCS_SITE_APP_PRIVATE_KEY }}The GitHub App needs Actions: write on the site repository. Store its client ID as DOCS_SITE_APP_CLIENT_ID and
its private key as DOCS_SITE_APP_PRIVATE_KEY. These values may be organization-level credentials restricted to the
product repositories. Rendering, AWS access, and deployment remain site responsibilities.
rust-release.yml reads one or more publishable crates from .github/crates.txt by default.
The tagged commit must belong to default-branch, which defaults to main.
The file must contain every publishable crate exactly once and in dependency order.
Every published crate version must match the tag.
jobs:
publish:
uses: soltiHQ/actions/.github/workflows/rust-release.yml@v1
with:
crates-file: .github/crates.txt
prepare-task: proto/vendor
allow-dirty: true
secrets:
crates-io-token: ${{ secrets.CRATES_IO_TOKEN }}prepare-task is optional.
Use allow-dirty only when that task creates required package inputs.
| Action | Purpose |
|---|---|
taskfile |
Install Task, export optional variables, and run one repository task |
gate |
Validate the results supplied through toJSON(needs) |
cargo-cache |
Cache .cache/cargo and .cache/target under a caller-provided scope |
cargo-publish |
Publish crates in order, tolerate existing versions, and retry HTTP 429 |
ghcr-build |
Build and publish a multi-platform GHCR image with version and commit tags |
docs-validate |
Validate docs/site.yml, navigation, page frontmatter, and source files |
docs-publish |
Publish one compatibility line without deleting another product or line |
The taskfile action installs Task 3.44.1 by default.
Set its version input to override the binary version.
gate accepts two explicit exceptions:
allowtolerates any result for named jobs;allow-skippedtolerates onlyskippedfor named jobs.
ghcr-build targets linux/amd64 and linux/arm64 by default.
It publishes <tag> and <tag>-sha-<commit>.
The modules provide low-level tasks.
Consumer repositories wrap them with stable, repository-specific commands such as ci/test or proto/vendor.
Every tool module includes the shared Docker module internally and delegates container execution to it.
| Module task | Operation |
|---|---|
fmt |
Check cargo fmt |
check |
Run cargo check |
build |
Build one package selected by CRATE |
clippy |
Run Clippy with warnings denied |
test |
Run Cargo tests |
bench |
Run Cargo benchmarks |
audit |
Run cargo audit |
package-list |
Inspect the package file set for CRATE |
publish-dry-run |
Simulate publishing CRATE |
doc |
Build stable rustdoc with warnings denied |
docs |
Emulate the docs.rs nightly rustdoc build |
fmt/fix |
Apply cargo fmt |
audit/fix |
Apply supported cargo audit fix changes |
Argument variables such as CHECK_ARGS, CLIPPY_ARGS, and TEST_ARGS let the consumer define its exact repository policy.
includes:
docs:
taskfile: https://raw.githubusercontent.com/soltiHQ/actions/v1/taskfiles/docs/Taskfile.yml
tasks:
ci/docs:
cmds:
- task: docs:validate
vars:
VERSION: 0.8.0
PRODUCT: taskvisor
TITLE: Taskvisor
REPOSITORY: https://github.com/soltiHQ/taskvisor
VERSION_PROVIDER: cargo
VERSION_PACKAGE: taskvisor
REFERENCE_LABEL: API reference
REFERENCE_URL: https://docs.rs/taskvisor/{version}/taskvisor/
CARGO_SNIPPET_SOURCES: README.md docs/installation.md
LINK_SOURCES: docs README.md guide.md src/lib.rs
- task: docs:links/external
vars:
LINK_SOURCES: docs README.md guide.md src/lib.rsvalidate runs in ghcr.io/soltihq/ci/docs:1.0.0 through the shared Docker module. It validates the docs/site.yml
contract, navigation, frontmatter, declared Markdown sources, exact product identity, and the compatibility line
derived from VERSION. For the Cargo provider, each dependency declaration in every whitespace-separated
CARGO_SNIPPET_SOURCES path must use the manifest compatibility line. Lychee checks the whitespace-separated
LINK_SOURCES paths, including local fragments, in offline mode. External URLs are checked by the subsequent
links/external task.
links/external checks HTTP and HTTPS URLs, including fragments, and retries transient failures. Run it from the same
consumer ci/docs task after validate. The shared docs workflow supplies GITHUB_TOKEN, and the Taskfile passes it
to Lychee without placing a token in Taskfile data. A product may use a repository-root lychee.toml to remap its
own default-branch GitHub links to the current checkout, keeping pull-request checks independent of merge order.
VERSION, PRODUCT, REPOSITORY, and VERSION_PROVIDER are required. TITLE, VERSION_PACKAGE,
REFERENCE_LABEL, REFERENCE_URL, CARGO_SNIPPET_SOURCES, and LINK_SOURCES enable their corresponding exact
checks when provided. MANIFEST_PATH defaults to docs/site.yml; ACTIONS_REF defaults to v1 and must match a
non-v1 Taskfile URL used for development. DOCS_IMAGE overrides the pinned docs image for image development.
The caller still owns language-specific API documentation, doctests, examples, and product-content checks. Local use
requires Docker rather than host-installed Ruby or Lychee.
api/generate requires RUSTDOC_DIR, API_OUTPUT, PRODUCT, PACKAGE, and VERSION. It reads stable rustdoc HTML
generated with all features and writes a sorted, version-pinned docs.rs API map. The output parent is created inside
the repository when needed.
includes:
go:
taskfile: https://raw.githubusercontent.com/soltiHQ/actions/v1/taskfiles/go/Taskfile.yml
tasks:
ci/test:
cmds:
- task: go:test
ci/lint:
cmds:
- task: go:golangci| Module task | Operation |
|---|---|
gofumpt |
Check tracked Go files |
golangci |
Run golangci-lint |
build |
Build a named binary for the selected GOOS and GOARCH |
govulncheck |
Run Go vulnerability analysis |
test |
Run Go tests |
proto |
Generate protobuf sources with Buf |
templ |
Generate templ sources |
tailwindcss |
Build CSS from required INPUT and OUTPUT paths |
tidy |
Run go mod tidy |
vendor |
Run go mod vendor |
fmt |
Apply gofumpt |
includes:
proto:
taskfile: https://raw.githubusercontent.com/soltiHQ/actions/v1/taskfiles/proto/Taskfile.yml
tasks:
fmt:
cmds:
- task: proto:format/fix
ci/fmt:
cmds:
- task: proto:format
ci/lint:
cmds:
- task: proto:lint| Module task | Operation |
|---|---|
lint |
Run buf lint |
build |
Compile the schema with buf build |
format |
Check every .proto file with clang-format |
breaking |
Compare against .git#branch=main or an explicit AGAINST |
format/fix |
Apply clang-format |
Code generation remains a consumer responsibility.
The Proto module validates the schema. Local runs require buf and clang-format; on macOS, the module also
detects the formatter bundled with Xcode Command Line Tools. Set CLANG_FORMAT to select an explicit executable.
The Node module pins its image tag in the shared Taskfile and exposes internal type-check, build, and audit tasks.
Each command installs the exact lockfile with npm ci inside the pinned ghcr.io/soltihq/ci/node image.
The Terraform module pins its image tag in the shared Taskfile. It exposes internal format, validate, and apply tasks. Remote operations initialize the S3 backend with its lockfile. The static release workflow reads the resulting Terraform outputs directly after apply and passes the selected values to the deployment job.
The AWS module pins its image tag in the shared Taskfile. It exposes internal s3/publish,
s3/publish-preserving-docs, and cloudfront/invalidate tasks. Both publish tasks upload immutable assets and upload
index.html with no-cache metadata. The explicit preserving variant removes stale non-index site objects while applying
the same docs/* exclusion to both sync phases, then copies only the site-owned docs catalog, root sitemap, and logo.
Product documentation releases own every product subtree separately.
cloudfront/invalidate creates a full distribution invalidation and waits for it.
taskfiles/helpers/Taskfile.yml provides two internal building blocks:
download/filedownloads one missing file;proto/vendorchecks out a selectedsoltiHQ/protorevision and replaces mapped destination trees.
Consumers decide the protobuf revision and source-to-destination mappings.
Use @v1 for workflows and composite actions.
Use /v1/ in raw Taskfile URLs.
Moving the v1 tag updates every consumer on its next run.
Test shared changes from an explicit branch or commit before moving the tag.
Toolchain image versions are independent of the actions revision.
They come from Cargo.toml, go.mod, the shared Node, Terraform, and AWS Taskfiles, or the explicit Proto setting.
Issues and pull requests are welcome.
Changes to reusable workflows, actions, and Taskfile modules affect their consumers independently. Test the boundary being changed from a representative caller repository.
Read the contributing guide before a large change.