Skip to content

Commit 47e1176

Browse files
committed
docs: self-hosted runners configure rust-ci through the job env
docs/runner-services.md becomes docs/self-hosted.md: the variables each action reads, the job-started hook that hands them to every job including container jobs, the other routes, the trust notes and the limits. README and CHANGELOG follow the actions; the private transfer measurement is gone.
1 parent 5239df5 commit 47e1176

4 files changed

Lines changed: 72 additions & 98 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,17 @@ The format follows [Keep a Changelog](https://keepachangelog.com); releases are
77

88
### Added
99

10-
- `sccache`: installs a pinned sccache (version + per-arch sha256) as `RUSTC_WRAPPER`, backend-configured by the host's `.rust-ci-env` file beside the work directory (`SCCACHE_*` lines, allowlist-checked into the job env).
11-
`"auto"` no-ops without the file, so one workflow runs unchanged on hosted and self-hosted runners; the contract for host operators is docs/runner-services.md.
10+
- `sccache`: installs a pinned sccache (version + per-arch sha256) as `RUSTC_WRAPPER`; the backend is whatever `SCCACHE_*` the job environment carries, and `"auto"` no-ops without one, so one workflow runs unchanged on hosted and self-hosted runners.
1211
`mode: gha` targets GitHub's cache service instead; an explicit opt-in, since it exports the masked Actions runtime token into the job env and spends the repository's cache pool.
1312
- `sccache-stats`: hit/miss/request facts as `cache.sccache.*` rows in timing-report's Cache section, run as a late job step; never fails a job.
14-
- `crates-mirror`: writes the crates-io source replacement into `$CARGO_HOME/config.toml` from the host's `RUST_CI_CRATES_MIRROR` URL (source replacement is config-file-only, cargo#5416).
13+
- `crates-mirror`: writes the crates-io source replacement into `$CARGO_HOME/config.toml` from the `url` input or `RUST_CI_CRATES_MIRROR` in the job environment (source replacement is config-file-only, cargo#5416).
1514
Lockfile checksums keep pinning canonical crates.io hashes, so the mirror is an availability dependency, not a trust dependency.
1615
- `cut-release`: stamps a `CITATION.cff` into the release commit, where the repository keeps one: top-level `version`, and `date-released` where the file has one, both on the cut's date.
1716
Keys match whole-line, so `cff-version` survives; a missing file skips and `citation: ""` disables.
1817
- `rust-cache`: opt-in `local-target` (requires `cache-target`) keeps `target/` on the runner between jobs instead of transferring it, by pointing `CARGO_TARGET_DIR` at the runner's work tree, outside the workspace where `git clean -ffdx` cannot reach it.
19-
`"auto"` activates only when the host leaves a `.rust-ci-local-target` marker, so one workflow runs unchanged on hosted and self-hosted runners.
20-
Measured on a self-hosted runner: 525 s of a 20.5-minute job spent transferring 9.26 GB.
18+
`"auto"` follows `RUST_CI_LOCAL_TARGET=1` in the job environment, so one workflow runs unchanged on hosted and self-hosted runners.
2119
Consumers must read `CARGO_TARGET_DIR` rather than assume `./target`.
20+
- `docs/self-hosted.md`: how a host hands these variables to every job through the runner's job-started hook, which reaches `container:` jobs too.
2221

2322
## [1.8.0] - 2026-09-06
2423

‎README.md‎

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -244,12 +244,11 @@ It sets `CARGO_TARGET_DIR` to a directory in the runner's work tree, **outside t
244244
A target directory inside the workspace cannot survive a checkout, however persistent the disk is.
245245
**Consumers must read `CARGO_TARGET_DIR` rather than assume `./target`.**
246246

247-
`"auto"` activates only when the host leaves a `.rust-ci-local-target` marker beside the runner's work directory, so the same workflow runs unchanged on a hosted runner (no marker, transfer as before) and on a self-hosted one (marker, no transfer).
248-
A job env `RUST_CI_LOCAL_TARGET=1` counts as the marker, for a workflow that controls its runner but not the host.
249-
A marker rather than an env var because the runner does not forward its own environment into a job container, while the work tree is bind-mounted into it.
247+
`"auto"` activates when the job environment carries `RUST_CI_LOCAL_TARGET=1`, which a host whose work tree persists provides ([docs/self-hosted.md](docs/self-hosted.md)), so the same workflow runs unchanged on a hosted runner (transfer as before) and on a prepared self-hosted one (no transfer).
248+
Ephemeral runners must not set it: the directory dies with the instance.
250249

251250
Only the target entry is affected; the registry entry still uses `actions/cache`, since `$CARGO_HOME` usually lives in the toolchain image rather than the work tree.
252-
`rust-cache-save` needs no flag, because this mode hands over no key.
251+
`rust-cache-save` needs no flag, because this mode hands over no key and its steps skip.
253252
The directory sits at the work root, is shared per prefix across every repo on the runner, and is never pruned: the host owns it and can wipe `.rust-ci-target` to reclaim disk.
254253

255254
```yaml
@@ -283,23 +282,22 @@ The restore-everywhere/save-on-main pattern: PR jobs omit this action (or pass `
283282

284283
### `sccache`
285284

286-
Installs a pinned [sccache](https://github.com/mozilla/sccache) as `RUSTC_WRAPPER`, backend configured by the **runner's host**: every `SCCACHE_*` line of the host's `.rust-ci-env` file is allowlist-checked and imported into the job environment ([docs/runner-services.md](docs/runner-services.md) is the contract).
287-
The action is backend-agnostic; the host names the backend (WebDAV, S3, Redis, a directory).
288-
289-
`"auto"` activates only when that file offers `SCCACHE_*` configuration, so the same workflow runs unchanged on hosted and prepared self-hosted runners; a job env `RUST_CI_SCCACHE=1` counts as the offer.
285+
Installs a pinned [sccache](https://github.com/mozilla/sccache) as `RUSTC_WRAPPER`; the backend is whatever `SCCACHE_*` the job environment carries, and sccache reads it itself, so every backend it supports works (WebDAV, S3, Redis, a directory).
286+
`"auto"` activates when a non-empty `SCCACHE_*` variable is set, so the same workflow runs unchanged on hosted and self-hosted runners; [docs/self-hosted.md](docs/self-hosted.md) shows how a host provides the variables.
290287
The binary is fetched by pinned version **and sha256** (per-arch, Linux musl) into `$RUNNER_TEMP`, so the absolute `RUSTC_WRAPPER` path works inside job containers without touching `PATH`.
291288
`CARGO_INCREMENTAL` defaults to 0; an explicit consumer value is obeyed.
292289
Linking (`bin`/`dylib`/`cdylib`/proc-macro crates) and build-script execution stay uncached; the wins are the `lib` compiles, shared across every job on the same backend.
290+
The sealed Docker actions are untouched: they pin `CARGO_HOME`, forward `CARGO_.*` only and build offline.
293291

294-
`mode: gha` uses **GitHub's own cache service** as the backend, for runners without a host file (hosted ones).
295-
Explicit on purpose, never part of `auto`: it exports `ACTIONS_RUNTIME_TOKEN` (masked) and `ACTIONS_RESULTS_URL` into the job environment, and it spends the repository's Actions cache pool (10 GB free, expandable paid, 7-day-unused eviction).
292+
`mode: gha` uses **GitHub's own cache service** as the backend, the choice for hosted runners.
293+
Explicit on purpose, never part of `auto`: it exports `ACTIONS_RUNTIME_TOKEN` (masked) and `ACTIONS_RESULTS_URL` into the job environment, and it spends the repository's Actions cache pool (10 GB per repository by default, entries removed after 7 days without access).
296294
GitHub scopes cache writes per ref, so pull-request objects never reach the default branch's scope; cross-PR sharing therefore only flows through objects a default-branch build wrote.
297-
Every object is a network round-trip, so a host-local backend beats it wherever one exists.
298295

299296
```yaml
300297
- uses: gronke/rust-ci/.github/actions/sccache@main
301-
# with:
302-
# mode: auto # on | off | gha; auto follows the host's .rust-ci-env
298+
with:
299+
# hosted: GitHub's cache service; self-hosted: the SCCACHE_* backend the host put into the job env
300+
mode: ${{ runner.environment == 'github-hosted' && 'gha' || 'auto' }}
303301
```
304302
305303
### `sccache-stats`
@@ -316,14 +314,15 @@ A no-op when sccache is inactive; statistics never fail a job.
316314
### `crates-mirror`
317315

318316
Routes crates-io through a runner-local pull-through mirror: cargo's source replacement is config-file-only (cargo#5416), so this writes it into `$CARGO_HOME/config.toml`.
319-
The URL comes from the host's `.rust-ci-env` (`RUST_CI_CRATES_MIRROR=sparse+http://…/`), a job env of the same name, or the `url` input; `"auto"` no-ops without one, so hosted runners keep fetching from crates.io directly.
317+
The URL comes from the `url` input or from `RUST_CI_CRATES_MIRROR` in the job environment; `"auto"` no-ops without one, so hosted runners keep fetching from crates.io directly.
320318
`Cargo.lock` keeps the canonical crates.io checksums under source replacement and cargo verifies every download against them, so the mirror is an availability dependency, never a trust dependency.
321319
Place it before the first dependency resolution; an existing crates-io replacement in `$CARGO_HOME/config.toml` is refused rather than fought.
322320

323321
```yaml
324322
- uses: gronke/rust-ci/.github/actions/crates-mirror@main
325323
# with:
326-
# mode: auto # on | off; auto follows the host's .rust-ci-env
324+
# mode: auto # on | off; auto follows RUST_CI_CRATES_MIRROR in the job env
325+
# url: sparse+http://cache.internal:9980/crates/index/
327326
```
328327

329328
### `timing-start` / `timing-mark` / `timing-report`

‎docs/runner-services.md‎

Lines changed: 0 additions & 78 deletions
This file was deleted.

‎docs/self-hosted.md‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Self-hosted runners
2+
3+
A self-hosted runner can offer per-host services to the jobs it runs: a compile-cache backend for `sccache`, a crates.io pull-through for `crates-mirror`, and a work tree that persists between jobs for `rust-cache`.
4+
The actions read that offer from the job environment and from their inputs, nothing else.
5+
6+
## What the actions read
7+
8+
| Variable | Read by | Meaning |
9+
| --- | --- | --- |
10+
| `SCCACHE_*` | `sccache` | sccache's own backend configuration, for example `SCCACHE_WEBDAV_ENDPOINT`, `SCCACHE_BUCKET` with `SCCACHE_ENDPOINT`, `SCCACHE_REDIS_ENDPOINT` or `SCCACHE_DIR`; credentials such as `AWS_ACCESS_KEY_ID` are ordinary variables next to them. |
11+
| `RUST_CI_CRATES_MIRROR` | `crates-mirror` | A `sparse+http(s)://.../` registry URL of a crates.io pull-through, written into `$CARGO_HOME/config.toml` as the crates-io source replacement. |
12+
| `RUST_CI_LOCAL_TARGET=1` | `rust-cache` with `local-target: auto` | The runner keeps its work tree between jobs, so `target/` stays on disk instead of travelling through the cache. Never set it on ephemeral runners. |
13+
14+
One rule for every `auto`: the mode activates when its variable is present and non-empty, and does nothing otherwise.
15+
Hosted runners carry none of these variables, so one workflow runs unchanged on both.
16+
17+
## How a host sets them
18+
19+
Use the runner's job-started hook.
20+
It runs on the host at the start of every job, before any job container is created, and the lines it appends to `$GITHUB_ENV` become the job's environment.
21+
Every later step sees them, including the steps of a `container:` job, because the runner passes the job environment into each `docker exec`.
22+
23+
```sh
24+
#!/bin/sh
25+
# /usr/local/lib/ci/job-started.sh
26+
{
27+
echo "SCCACHE_WEBDAV_ENDPOINT=http://10.0.0.1:9980/"
28+
echo "RUST_CI_CRATES_MIRROR=sparse+http://10.0.0.1:9980/crates/index/"
29+
} >> "$GITHUB_ENV"
30+
# A credential is masked before it is written: echo "::add-mask::$secret"
31+
```
32+
33+
Point the runner at the script with `ACTIONS_RUNNER_HOOK_JOB_STARTED=/usr/local/lib/ci/job-started.sh`, either in the `.env` file of the runner directory or in the environment of the runner's service, and restart the runner.
34+
GitHub documents the hook under "Running scripts before or after a job".
35+
36+
Other routes, for completeness:
37+
38+
- A workflow may set the same variables itself, from `env:`, `vars.*` or `container.env`; an unset variable yields an empty value, which the actions treat as absent.
39+
- Variables in the runner's `.env` alone reach the steps of bare jobs, never the steps of a `container:` job, and never the `env` expression context.
40+
- `container.options: -e NAME` copies a variable from the runner's process environment into one workflow's container.
41+
- `runner.environment` is `github-hosted` or `self-hosted`, so a fleet whose self-hosted runners keep their work tree can pass `local-target: ${{ runner.environment == 'self-hosted' }}` and pick `mode: gha` for sccache on hosted runners.
42+
43+
## Trust
44+
45+
Everything the hook exports is readable by every job the runner executes, third-party actions included.
46+
Scope credentials to the cache backend alone, on a network the runners can reach and the world cannot.
47+
A compile cache written by untrusted jobs is a poisoning surface: keep release builds cache-off, or give trust tiers separate backends.
48+
The crates mirror serves under `Cargo.lock` checksum protection, so its requirement is availability, not trust.
49+
50+
## Limits
51+
52+
The sealed Docker actions pin `CARGO_HOME`, forward `CARGO_.*` only and build with `--network=none`, so neither sccache nor the mirror reaches them; their compile cache is the `target-dir` mount.
53+
Inside a `container:` job use a network backend: a directory on the host is not visible there.
54+
sccache runs one server per host and port, and the action restarts it with the current job's configuration; a runner that executes jobs concurrently sets `SCCACHE_SERVER_PORT` per job.

0 commit comments

Comments
 (0)