Skip to content

Apache 2.0

Solti Actions

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 |

Contract

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.

Quick start

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@v1

For a Cargo workspace, enable workspace-specific checks:

jobs:
  ci:
    uses: soltiHQ/actions/.github/workflows/rust-ci.yml@v1
    with:
      workspace: true

The 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.

Reusable workflows

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 protoc before Cargo publishes package tarballs.

Rust CI

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

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@v1

Docs CI

docs-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@v1

The 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

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@v1

Call docs-notify.yml from a dependent job when the tagged contract also publishes product documentation.

Labels

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@v1

The branch-protection check is label-check / required when the caller job is named label-check.

Static site CI and release

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.

Versioned product documentation

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

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.

Composite actions

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:

  • allow tolerates any result for named jobs;
  • allow-skipped tolerates only skipped for named jobs.

ghcr-build targets linux/amd64 and linux/arm64 by default. It publishes <tag> and <tag>-sha-<commit>.

Taskfile modules

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.rs

validate 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/file downloads one missing file;
  • proto/vendor checks out a selected soltiHQ/proto revision and replaces mapped destination trees.

Consumers decide the protobuf revision and source-to-destination mappings.

Versioning

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.

Contributing

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.


soltiHQ

About

Solti reusable GitHub actions

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages