Add container/: a rootless Podman + systemd (Quadlet) setup to run per-project Gitea Actions runners on one host instead of VMs. Uses the host executor with a purpose-built image that bakes in the Rust toolchain and all build dependencies (opus, alsa, soapysdr, clang), so CI runs skip the per-run install and cold soapysdr-sys build. Includes the runner Containerfile, first-boot registration entrypoint, act_runner config template, two Quadlet units (trx-rs + a second project), and a README covering build, registration, the required host-executor workflow tweak, and tuning. Assisted-By: Claude Code (claude-opus-4) Claude-Session: https://claude.ai/code/session_01NFpGtGTWUEYXLwZeZs2RAV Signed-off-by: Stan Grams <sjg@haxx.space>
119 lines
4.1 KiB
Markdown
119 lines
4.1 KiB
Markdown
<!--
|
||
SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
|
||
SPDX-License-Identifier: GPL-2.0-or-later
|
||
-->
|
||
|
||
# Podman-based Gitea Actions runners
|
||
|
||
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
|
||
|
||
| 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. |
|
||
|
||
## Prerequisites (once per host)
|
||
|
||
Rootless Podman with cgroups v2 (default on modern distros). As the unprivileged
|
||
user that will own the runners:
|
||
|
||
```bash
|
||
# Survive logout / start on boot without an interactive session.
|
||
loginctl enable-linger "$USER"
|
||
```
|
||
|
||
No `podman.socket` is required for the host executor.
|
||
|
||
## 1. Build the image
|
||
|
||
```bash
|
||
cd container
|
||
podman build -t trx-rs-ci:latest .
|
||
```
|
||
|
||
## 2. Get a registration token
|
||
|
||
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.)
|
||
|
||
## 3. Install and start the runners
|
||
|
||
```bash
|
||
mkdir -p ~/.config/containers/systemd
|
||
cp trx-rs-runner.container project2-runner.container ~/.config/containers/systemd/
|
||
|
||
# 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
|
||
|
||
systemctl --user daemon-reload
|
||
systemctl --user start trx-rs-runner
|
||
systemctl --user start project2-runner
|
||
|
||
systemctl --user status trx-rs-runner
|
||
podman logs -f gitea-runner-trx-rs
|
||
```
|
||
|
||
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`.
|
||
|
||
## 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; 1–2 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"
|
||
```
|