[chore](trx-rs): shared SDK image for CI and developers
CI / lint (pull_request) Failing after 1s
CI / test (pull_request) Failing after 1s
CI / reuse (pull_request) Failing after 4s

Rework container/ from a host-executor act_runner image into a single
"SDK" build image used everywhere: as the CI job container (Docker
executor) and by developers locally / via .devcontainer. It bakes in a
pinned Rust toolchain and all build dependencies, so CI and every
developer share the exact same rustc/clippy.

- container/Containerfile: SDK image (Debian + deps + pinned Rust + Node).
- rust-toolchain.toml: pin the toolchain to match the image; also ends the
  "CI clippy newer than local" version skew.
- .gitea/workflows/ci.yml: lint/test run inside the SDK image via
  `container:`; reuse returns to fsfe/reuse-action (Docker executor runs
  it as a sibling container, so nothing REUSE-related is baked in).
- .devcontainer/devcontainer.json: dev use of the same image.
- container/runner-config.example.yaml: Docker-executor runner config for
  the CI VM, capped for a 2-thread budget.
- Drop the obsolete host-executor entrypoint/config/Quadlet units.

Assisted-By: Claude Code (claude-opus-4)
Claude-Session: https://claude.ai/code/session_01NFpGtGTWUEYXLwZeZs2RAV
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
sjg
2026-07-19 00:22:27 +02:00
parent ff4e2a5c5d
commit e9cf5e8739
11 changed files with 142 additions and 290 deletions
+51 -91
View File
@@ -3,116 +3,76 @@ SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
SPDX-License-Identifier: GPL-2.0-or-later
-->
# Podman-based Gitea Actions runners
# trx-rs SDK image
Run two independent Gitea Actions runners on one host as rootless Podman
containers managed by systemd (Quadlet) — one per project — instead of two
VMs. Uses the **host executor**: workflow steps run directly inside a
purpose-built runner image that already has the Rust toolchain and all build
dependencies baked in, so CI runs skip the per-run install cost and no
Docker/Podman socket is needed.
## Files
A single container image that is the canonical build environment for trx-rs,
used **both** by CI and by developers. It bakes in the pinned Rust toolchain
(matching `rust-toolchain.toml`) and every build dependency, so the compiler
and `clippy` are identical everywhere — no "works on my machine".
| File | Purpose |
|------|---------|
| `Containerfile` | Runner image: Debian + build deps + clang + Rust + Node + `reuse` + `act_runner`. |
| `entrypoint.sh` | Registers on first boot (if needed), then runs the daemon. |
| `config.yaml` | act_runner config template (seeded into each runner's volume). |
| `trx-rs-runner.container` | Quadlet unit for the trx-rs runner. |
| `project2-runner.container` | Quadlet unit for the second project's runner. |
| `Containerfile` | The SDK image (Debian + build deps + pinned Rust + Node + git). |
| `runner-config.example.yaml` | Example act_runner config for the CI VM (Docker executor). |
## Prerequisites (once per host)
Rootless Podman with cgroups v2 (default on modern distros). As the unprivileged
user that will own the runners:
## Build and publish
```bash
# Survive logout / start on boot without an interactive session.
loginctl enable-linger "$USER"
# from the repo root
podman build -t git.haxx.space/sjg/trx-rs-sdk:latest container
podman login git.haxx.space
podman push git.haxx.space/sjg/trx-rs-sdk:latest
```
No `podman.socket` is required for the host executor.
Tag with the Rust version too (e.g. `:1.97.1`) if you want reproducible pins.
Make the package **public** (Gitea → Packages → the image → Settings) so the CI
runner and developers can pull it without credentials. If you keep it private,
add `credentials:` under the workflow's `container:` and log the runner into the
registry.
## 1. Build the image
## Developer use
Reproducible one-off build, no local toolchain needed:
```bash
cd container
podman build -t trx-rs-ci:latest .
podman run --rm -it -v "$PWD":/work -w /work \
git.haxx.space/sjg/trx-rs-sdk:latest \
cargo build --release
```
## 2. Get a registration token
Or open the repo in the image via VS Code / JetBrains "Reopen in Container"
(`.devcontainer/devcontainer.json` points at the same image).
For **each** repo: *Settings → Actions → Runners → Create new Runner* and copy
the token. (Org- or instance-level tokens work too if you prefer wider scope.)
Building outside the container? `rust-toolchain.toml` pins the same rustc, so
`rustup` installs the matching toolchain automatically.
## 3. Install and start the runners
## CI use
```bash
mkdir -p ~/.config/containers/systemd
cp trx-rs-runner.container project2-runner.container ~/.config/containers/systemd/
`.gitea/workflows/ci.yml` runs the `lint` and `test` jobs *inside* this image
via the `container:` key, so they skip all setup and go straight to `cargo`.
The `reuse` job stays on the upstream `fsfe/reuse-action` (a Docker action the
Docker executor launches as a sibling container) — nothing REUSE-related is
baked into the SDK.
# Paste each repo's token for the FIRST boot only:
# Environment=GITEA_RUNNER_REGISTRATION_TOKEN=xxxx…
$EDITOR ~/.config/containers/systemd/trx-rs-runner.container
$EDITOR ~/.config/containers/systemd/project2-runner.container
## CI runner (VM)
systemctl --user daemon-reload
systemctl --user start trx-rs-runner
systemctl --user start project2-runner
The runner runs in a small VM using the **Docker executor** (not the host
executor). This gives per-job container isolation and standard `ubuntu-latest`
semantics, and caps CI resources at the VM boundary.
systemctl --user status trx-rs-runner
podman logs -f gitea-runner-trx-rs
```
1. **Size the VM to your thread budget.** 2 vCPUs = a hard 2-thread ceiling.
Pin them to specific host threads so CI never touches the rest (libvirt/KVM):
Once each runner shows **online** in the repo's runner list, blank out the
`GITEA_RUNNER_REGISTRATION_TOKEN` line again (the registration is persisted in
the `…-data` volume) and `systemctl --user daemon-reload`.
```xml
<vcpu placement='static'>2</vcpu>
<cputune>
<vcpupin vcpu='0' cpuset='4'/>
<vcpupin vcpu='1' cpuset='5'/>
</cputune>
```
## Required workflow change: the `reuse` job
The host executor runs steps directly in the container and therefore **cannot
run Docker-based actions**. The current `reuse` job uses `fsfe/reuse-action@v5`,
which is a Docker action. `reuse` is baked into the image, so replace that job
with a plain command:
```yaml
reuse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: REUSE compliance
run: reuse lint
```
The `lint` and `test` jobs need no changes: their `sudo apt-get …` and rustup
steps still run, but become fast no-ops because the image already has those
packages and the toolchain. (`sudo` is included in the image for exactly this
reason.)
> If you would rather keep Docker-based actions and per-run images, use the
> **Docker executor** instead: drop the `:host` suffix from the label in
> `config.yaml`, enable `systemctl --user --now enable podman.socket`, mount it
> into the container, and set `container.docker_host` to the socket path. That
> trades the baked-in speed for stronger per-job isolation.
## Tuning
- **`capacity`** (in `config.yaml`) — concurrent jobs per runner. Rust builds
are heavy; 12 is sensible when two runners share a host.
- **`PodmanArgs=--cpus/--memory`** (in each `.container`) — hard resource caps
so one project cannot starve the other.
- **SELinux** — the `:Z` volume flag is already set; keep it if SELinux is
enforcing.
## Committing these files
If you add this directory to a REUSE-checked repo, register the markdown in
`REUSE.toml` (the other files carry inline SPDX headers):
```toml
[[annotations]]
path = ["container/**"]
SPDX-FileCopyrightText = "2026 Stan Grams <sjg@haxx.space>"
SPDX-License-Identifier = "GPL-2.0-or-later"
```
2. **Inside the VM:** install Docker (moby) and `act_runner`.
3. **Register one runner per project** (separate tokens from each repo's
*Settings → Actions → Runners*; scope keeps their jobs apart).
4. **Configure** with `runner-config.example.yaml` — `capacity: 1` and
`container.options: "--cpus=2"` keep usage inside the 2-thread budget.