Skip to content

ci: cache Rust builds, harden the workflows, share the release workflow - #3

Merged
Jertlok merged 3 commits into
mainfrom
ci/cache-harden
Sep 16, 2026
Merged

Jertlok merged 3 commits into
mainfrom
ci/cache-harden

Conversation

@Jertlok

@Jertlok Jertlok commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

What and why

change practice it follows
concurrency: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}, cancel-in-progress only for pull requests Home Assistant core and astral-sh/uv use the same group. The old group (ci-<ref>, always cancelling) let a second push to main cancel the first push's run: tap run 35024225791 shows it.
timeout-minutes on every job GitHub's default is 360 minutes; rust-lang/rust and Home Assistant set explicit timeouts. Sized a few times the measured job duration.
merge_group trigger Required for a merge queue (GitHub docs).
Commit check from porthole-dev/.github/actions/commit-check, pinned by commit One copy instead of six byte-identical ones. It fetches the whole commit history with --filter=tree:0, so a base older than the former fetch-depth: 100 fails loudly instead of reporting "no new commits". Check name stays Commit check.
Swatinem/rust-cache in Lint, Test and Build, saving only on main The cache pattern of most Rust projects (save-if: github.ref == refs/heads/main, from the action's own README): pull requests restore main's cache and cannot evict it with copies only they can read.
Test builds without fat LTO and shares one target directory with the preview build The Test job compiled the whole graph twice with lto = "fat", codegen-units = 1; the link step alone was ~6 minutes of each run. Tests exercise the code, not the shipped optimization; the Build job keeps the real profile.
GNU tar and zstd in the containers actions/cache cannot write a cache with busybox tar and only warns: the first run saved nothing and went green.
The release workflow calls porthole-dev/.github/.github/workflows/app-release.yml; releases carry the source archive, SHA512SUMS and a provenance attestation One copy instead of three. Release assets plus checksums are what the SLSA provenance and gh attestation verify attach to.
Workflow lint job: actionlint + zizmor uv runs zizmor on every pull request (check-zizmor.yml); both tools are what the OpenSSF Scorecard and GitHub's security hardening guide checks are about (pinning, token permissions, injection).
CI passed aggregate job uv's "all required jobs passed": one required check, so adding or renaming a job never needs a ruleset change, and a failed or cancelled job cannot be skipped past.
.github/dependabot.yml: GitHub Actions weekly, grouped, 7-day cooldown Scorecard "Pinned-Dependencies" expects SHA pins to be kept current; the cooldown is zizmor's dependabot-cooldown recommendation. Dependabot commits need the maintainer's sign-off before merge like any other.
persist-credentials: false, least-privilege permissions zizmor artipacked / excessive-permissions.

Timing (run 35034032549, ubuntu-24.04, 2 vCPU)

job before (median of the last 7 runs of main) after, cold cache after, warm cache
Lint 318 s 249 s 35 s (clippy 7 s)
Test 889 s 570-674 s 121 s (unit tests 13 s, UI checks 75 s)
Build 442 s 266 s 115 s
total runner time ~27 min ~19 min ~4.5 min

Cold numbers are the first run after a Cargo.lock or compiler change; the caches are 302 MB (build), 342 MB (test), 215 MB (lint).

Depends on

  1. ci: add shared commit check, workflow lint and app release .github#2 merged.
  2. While porthole-dev/.github is private: its Settings > Actions > General > Access set to "Accessible from repositories in the porthole-dev organization". Until then Commit check, Workflow lint and CI passed fail with "Unable to resolve action porthole-dev/.github, repository not found".
  3. The pins (@43919fe... # main) moved to the merged commit and its tag (# v1.0.0); zizmor's ref-version-mismatch warns until the comment names a tag.

Merge note

The Commit check fails until the commit carries the maintainer's Signed-off-by (git rebase --signoff main).

@Jertlok Jertlok changed the title ci: cache Rust builds, harden workflows, use the organization's shared CI ci: cache Rust builds, harden the workflows, share the release workflow Sep 15, 2026
@Jertlok
Jertlok marked this pull request as ready for review September 15, 2026 23:20
@Jertlok
Jertlok force-pushed the ci/cache-harden branch 3 times, most recently from e37218c to 93904a8 Compare September 16, 2026 06:27
The CI of a push was 27 minutes of runner time: Lint, Test and Build each
compiled the whole dependency graph from scratch, and the Test job did it
twice, both times with the shipped release profile (fat LTO, one codegen
unit) whose link step alone took six minutes of each fourteen-minute run.

- Swatinem/rust-cache in all three jobs, keyed by Cargo.lock and the
  compiler. Only pushes to main save, so pull requests restore main's
  cache instead of filling the repository's 10 GB with copies only they
  can read. GNU tar and zstd are installed because actions/cache cannot
  write the cache with busybox tar -- and only warns when it fails.
- The Test job builds without fat LTO (CARGO_PROFILE_RELEASE_LTO=false,
  16 codegen units) and shares one target directory between the unit
  tests and the preview build, so the dependencies are compiled once. The
  Build job keeps the shipped profile.
- Concurrency per pull request, cancelling only superseded pull request
  runs; a second push to main no longer cancels the first push's checks.
- A timeout on every job, and merge_group so the workflow can back a
  merge queue.
- A weekly run: the alpine:edge image the jobs use is the distribution
  the app ships to, and breakage it brings should not wait for a commit.
- The commit check and the release are porthole-dev/.github's shared
  action and reusable workflow instead of four copied scripts and a
  172-line copy of the release workflow. Releases gain the source archive
  and SHA512SUMS as assets, and a provenance attestation of the archive
  once the repository is public.
- Workflow lint (actionlint, zizmor) and a "CI passed" aggregate job: the
  one status check a ruleset needs to require.
- Dependabot keeps the pinned actions and the Rust dependencies current,
  weekly and grouped, with a seven-day cooldown.

Timings on ubuntu-24.04 (2 vCPU), run 35034032549 rerun with the caches
warm, against the median of the last seven runs of main:

  Lint   318 s -> 35 s     Test  889 s -> 121 s     Build  442 s -> 115 s

Assisted-by: Claude
Signed-off-by: Giuseppe Maggio <jertlok@proton.me>
The previous pin named a commit from the pull request branch, which the
rebase merge orphaned; an orphaned commit can be garbage-collected and the
action then stops resolving. Point at the commit main was tagged v1.0.0.

Assisted-by: Claude
Signed-off-by: Giuseppe Maggio <jertlok@proton.me>
The earlier re-pin only covered porthole-dev/.github/actions/*; the reusable
workflow reference still named the pull request commit, which the rebase
merge orphaned. zizmor flags it as an impostor-commit, correctly.

Assisted-by: Claude
Signed-off-by: Giuseppe Maggio <jertlok@proton.me>
@Jertlok
Jertlok merged commit 7580093 into main Sep 16, 2026
6 checks passed
@Jertlok
Jertlok deleted the ci/cache-harden branch September 16, 2026 07:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant