diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..bc185b54 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,22 @@ +{ + "name": "trx-rs SDK", + "image": "git.haxx.space/sjg/trx-rs/sdk:latest", + "workspaceFolder": "/work", + "workspaceMount": "source=${localWorkspaceFolder},target=/work,type=bind", + "mounts": [ + "source=trx-rs-sccache,target=/sccache,type=volume" + ], + "containerEnv": { + "RUSTC_WRAPPER": "sccache", + "CARGO_INCREMENTAL": "0", + "SCCACHE_DIR": "/sccache" + }, + "customizations": { + "vscode": { + "extensions": [ + "rust-lang.rust-analyzer", + "tamasfe.even-better-toml" + ] + } + } +} diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index d8fcf43a..89c07d8f 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -2,10 +2,11 @@ # # SPDX-License-Identifier: GPL-2.0-or-later -# CI for the self-hosted, host-executor Podman runners (see container/). -# The runner image bakes in the Rust toolchain and all build dependencies, -# so jobs go straight to cargo — no apt/rustup setup steps (which also -# collided on the dpkg lock when jobs ran concurrently in the same runner). +# CI for the Docker-executor runner (VM). The lint, test and frontend jobs run +# inside the shared trx-rs SDK image (container/Containerfile), which bakes in +# the pinned Rust toolchain, Node.js, Chromium and all build dependencies. The +# reuse job uses the upstream Docker action, which the Docker executor launches +# as a sibling container. name: CI @@ -16,28 +17,44 @@ on: env: CARGO_TERM_COLOR: always + # sccache: shared compilation cache persisted on the runner host (see the + # -v mount in runner-config.example.yaml). CARGO_INCREMENTAL=0 because + # sccache cannot cache incremental artifacts. + RUSTC_WRAPPER: sccache + CARGO_INCREMENTAL: "0" + SCCACHE_DIR: /sccache + SCCACHE_CACHE_SIZE: "20G" jobs: lint: runs-on: ubuntu-latest + container: git.haxx.space/sjg/trx-rs/sdk:latest steps: - uses: actions/checkout@v4 - name: rustfmt run: cargo fmt --all -- --check - name: clippy run: cargo clippy --workspace --all-targets --all-features -- -D warnings + - name: sccache stats + if: always() + run: sccache --show-stats test: runs-on: ubuntu-latest + container: git.haxx.space/sjg/trx-rs/sdk:latest steps: - uses: actions/checkout@v4 - name: Build run: cargo build --workspace --all-targets --locked - name: Test run: cargo test --workspace --locked + - name: sccache stats + if: always() + run: sccache --show-stats frontend: runs-on: ubuntu-latest + container: git.haxx.space/sjg/trx-rs/sdk:latest defaults: run: working-directory: src/trx-client/trx-frontend/trx-frontend-http/frontend @@ -58,22 +75,15 @@ jobs: run: npm run lint - name: Test run: npm test - - name: Install browser smoke dependency - run: command -v chromium >/dev/null || (sudo apt-get update && sudo apt-get install -y --no-install-recommends chromium) + # Chromium comes from the SDK image at the path the smoke test defaults + # to, so there is nothing to install here. - name: Browser smoke test run: npm run test:browser - name: Verify generated assets run: npm run verify-generated - - name: Verify generated-file licensing - working-directory: . - run: reuse lint reuse: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: REUSE compliance - # `reuse` CLI instead of fsfe/reuse-action: the latter is a Docker - # action, which the host-executor runners cannot run. `reuse` is baked - # into the runner image (see container/Containerfile). - run: reuse lint + - uses: fsfe/reuse-action@v5 diff --git a/REUSE.toml b/REUSE.toml index 223a64e1..e39535e3 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -12,6 +12,8 @@ path = [ "trx-rs.toml.example", "docs/**", "aidocs/**", + "container/**", + ".devcontainer/**", "src/decoders/trx-ftx/README.md", "src/decoders/trx-wxsat/README.md", "assets/trx-logo.png", diff --git a/container/Containerfile b/container/Containerfile index 86f4f292..740b0417 100644 --- a/container/Containerfile +++ b/container/Containerfile @@ -2,60 +2,67 @@ # # SPDX-License-Identifier: GPL-2.0-or-later -# Gitea Actions runner image for trx-rs CI (host-executor / "Pattern B"). +# trx-rs SDK / build image. # -# All build dependencies, the Rust toolchain, Node.js (for JS actions such as -# actions/checkout and actions/cache) and the `reuse` tool are baked in, so CI -# runs skip the per-run apt/rustup install cost. `sudo` is present so the -# existing workflow's `sudo apt-get ...` / rustup steps remain valid — they -# just become fast no-ops because everything is already installed. +# Single source of truth for the build environment. Used two ways: +# * CI — as the job container for the lint/test jobs (Docker executor). +# * Dev — run locally or via .devcontainer for a reproducible toolchain. +# +# Pinning the Rust version here (and in rust-toolchain.toml) means CI and every +# developer share the exact same rustc/clippy, so "works locally, fails in CI" +# cannot happen. FROM docker.io/library/debian:bookworm-slim -ARG ACT_RUNNER_VERSION=0.2.11 +# Keep in sync with rust-toolchain.toml. +ARG RUST_VERSION=1.97.1 ARG NODE_MAJOR=20 ENV DEBIAN_FRONTEND=noninteractive \ - RUSTUP_HOME=/opt/rustup \ - CARGO_HOME=/opt/cargo \ - PATH=/opt/cargo/bin:/usr/local/bin:/usr/bin:/bin + RUSTUP_HOME=/usr/local/rustup \ + CARGO_HOME=/usr/local/cargo \ + PATH=/usr/local/cargo/bin:/usr/local/bin:/usr/bin:/bin -# Base tooling + trx-rs build dependencies (mirrors .gitea/workflows/ci.yml). +# Build dependencies (mirror README's manual instructions). RUN apt-get update && apt-get install -y --no-install-recommends \ - ca-certificates curl xz-utils git sudo pipx \ + ca-certificates curl git \ build-essential pkg-config cmake clang libclang-dev \ libopus-dev libasound2-dev libsoapysdr-dev chromium \ && rm -rf /var/lib/apt/lists/* -# Node.js (JS-based actions need node in PATH under the host executor). +# Node.js — JS-based actions (actions/checkout, actions/cache) run *inside* +# the job container under the Docker executor, so node must be present. RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_MAJOR}.x | bash - \ && apt-get install -y --no-install-recommends nodejs \ && rm -rf /var/lib/apt/lists/* -# REUSE >= 3 (Debian's packaged reuse is too old for REUSE.toml). -# The [charset-normalizer] extra provides an encoding-detection backend; -# without it (and without libmagic) reuse fails to import at runtime. -RUN PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx install 'reuse[charset-normalizer]' - -# Rust stable with rustfmt + clippy, installed system-wide. +# Pinned Rust toolchain, installed world-readable so any UID the runner or a +# devcontainer uses can invoke cargo. RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ - | sh -s -- -y --no-modify-path --profile minimal \ - --component rustfmt --component clippy \ - && chmod -R a+rwX "$CARGO_HOME" "$RUSTUP_HOME" + | sh -s -- -y --no-modify-path \ + --default-toolchain "${RUST_VERSION}" --profile minimal \ + --component rustfmt --component clippy \ + && chmod -R a+rwX "$RUSTUP_HOME" "$CARGO_HOME" -# act_runner binary. -RUN arch="$(dpkg --print-architecture)"; \ - case "$arch" in amd64) rarch=amd64;; arm64) rarch=arm64;; *) echo "unsupported arch $arch" >&2; exit 1;; esac; \ - curl -fsSL -o /usr/local/bin/act_runner \ - "https://gitea.com/gitea/act_runner/releases/download/v${ACT_RUNNER_VERSION}/act_runner-${ACT_RUNNER_VERSION}-linux-${rarch}" \ - && chmod +x /usr/local/bin/act_runner +# sccache — shared compilation cache. Enabled at build time via +# RUSTC_WRAPPER (see the CI workflow and .devcontainer), not repo-wide, so +# non-SDK builds are unaffected. musl build is static and runs anywhere. +# +# The release asset is per-architecture, so resolve it from `uname -m` rather +# than hardcoding one triple: everything else in this image is arch-agnostic, +# and a pinned x86_64 URL is what forces an amd64 build (and Rosetta or qemu) +# on an arm64 host. `uname -m` reflects the build platform under plain +# docker/podman build as well as buildx, unlike the BuildKit-only TARGETARCH. +ARG SCCACHE_VERSION=0.8.2 +RUN set -eux; \ + case "$(uname -m)" in \ + x86_64) sccache_arch=x86_64 ;; \ + aarch64|arm64) sccache_arch=aarch64 ;; \ + *) echo "unsupported architecture for sccache: $(uname -m)" >&2; exit 1 ;; \ + esac; \ + sccache_dist="sccache-v${SCCACHE_VERSION}-${sccache_arch}-unknown-linux-musl"; \ + curl -fsSL "https://github.com/mozilla/sccache/releases/download/v${SCCACHE_VERSION}/${sccache_dist}.tar.gz" \ + | tar -xz -C /tmp; \ + install -m755 "/tmp/${sccache_dist}/sccache" /usr/local/bin/sccache; \ + rm -rf /tmp/sccache-* -# Default config template (seeded into the /data volume on first boot). -COPY config.yaml /etc/act_runner/config.yaml -COPY entrypoint.sh /usr/local/bin/entrypoint.sh -RUN chmod +x /usr/local/bin/entrypoint.sh - -# /data holds the .runner registration, cache and workflow workspaces. -VOLUME /data -WORKDIR /data - -ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] +WORKDIR /work diff --git a/container/README.md b/container/README.md index 36dab96a..0ccca5c4 100644 --- a/container/README.md +++ b/container/README.md @@ -3,116 +3,191 @@ SPDX-FileCopyrightText: 2026 Stan Grams 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) +## Build and publish -Rootless Podman with cgroups v2 (default on modern distros). As the unprivileged -user that will own the runners: +Nothing in the image is architecture-specific: the base image, the Debian build +dependencies, Node.js, `rustup` and the `sccache` release all resolve per +architecture, so the same `Containerfile` builds natively on x86_64 and arm64. + +Single architecture — the tag then only works on the architecture you built it +on: ```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. - -## 1. Build the image +**Both architectures without emulation.** The CI runner is x86_64 and Apple +Silicon developer machines are arm64, so `:latest` has to be a manifest list — +a single-architecture tag makes the other side fall back to Rosetta or qemu. +Build each half natively on a host of that architecture, then join them: ```bash -cd container -podman build -t trx-rs-ci:latest . +# on an x86_64 host +podman build --platform linux/amd64 -t git.haxx.space/sjg/trx-rs/sdk:latest-amd64 container +podman push git.haxx.space/sjg/trx-rs/sdk:latest-amd64 + +# on an arm64 host +podman build --platform linux/arm64 -t git.haxx.space/sjg/trx-rs/sdk:latest-arm64 container +podman push git.haxx.space/sjg/trx-rs/sdk:latest-arm64 + +# from either, once both are pushed +podman manifest create git.haxx.space/sjg/trx-rs/sdk:latest \ + git.haxx.space/sjg/trx-rs/sdk:latest-amd64 \ + git.haxx.space/sjg/trx-rs/sdk:latest-arm64 +podman manifest push --all git.haxx.space/sjg/trx-rs/sdk:latest ``` -## 2. Get a registration token +Building both from one machine is a single command +(`podman build --platform linux/amd64,linux/arm64 --manifest ...`), but the +foreign half runs under emulation and is slow — the two-host flow above is +what keeps every build native. -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.) +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. -## 3. Install and start the runners +Pushing a rebuilt image is not enough on its own: `:latest` is a moving tag, and +act_runner reuses whatever it cached the first time unless `force_pull: true` is +set (see `runner-config.example.yaml`). Without it the job log says +`Image exists? true` and the run behaves as though the image were never +rebuilt — a tool added to the `Containerfile` reads as missing from the image. +Either set `force_pull`, or refresh the VM's copy by hand: ```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 +docker pull git.haxx.space/sjg/trx-rs/sdk:latest +docker run --rm git.haxx.space/sjg/trx-rs/sdk:latest sccache --version ``` -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`. +### macOS note -## Required workflow change: the `reuse` job +Apple's `container` CLI builds through a BuildKit helper VM that is configured +with Rosetta whether or not the target is x86_64, so `container build` fails +with *"Rosetta is not installed"* on a clean machine. That is a property of the +builder, not of this image — `container run` works natively without it. Either +install Rosetta once (`softwareupdate --install-rosetta`, after which an arm64 +build still produces a native arm64 image), or build with Podman, whose arm64 +BuildKit needs no emulation. -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: +## Developer use -```yaml - reuse: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: REUSE compliance - run: reuse lint +Reproducible one-off build, no local toolchain needed: + +```bash +podman run --rm -it -v "$PWD":/work -w /work \ + git.haxx.space/sjg/trx-rs/sdk:latest \ + cargo build --release ``` -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.) +Or open the repo in the image via VS Code / JetBrains "Reopen in Container" +(`.devcontainer/devcontainer.json` points at the same image). -> 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. +Building outside the container? `rust-toolchain.toml` pins the same rustc, so +`rustup` installs the matching toolchain automatically. -## Tuning +## CI use -- **`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. +`.gitea/workflows/ci.yml` runs the `lint`, `test` and `frontend` jobs *inside* +this image via the `container:` key, so they skip all setup and go straight to +`cargo` and `npm`. The frontend job needs three things from the image beyond +Rust: Node.js for the toolchain, Chromium at `/usr/bin/chromium` for the +browser smoke test, and `cargo` — `npm run verify-generated` regenerates the +Rust wire contracts before checking for drift. -## Committing these files +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, and it lints the whole repository, so no job runs its own +licence check. -If you add this directory to a REUSE-checked repo, register the markdown in -`REUSE.toml` (the other files carry inline SPDX headers): +## Compilation cache (sccache) -```toml -[[annotations]] -path = ["container/**"] -SPDX-FileCopyrightText = "2026 Stan Grams " -SPDX-License-Identifier = "GPL-2.0-or-later" +The SDK image ships [`sccache`](https://github.com/mozilla/sccache). It is +enabled via `RUSTC_WRAPPER=sccache` in CI and the devcontainer (not repo-wide, +so plain `cargo` builds outside the SDK are unaffected). + +- **CI** persists the cache on the runner host — create the dir once: + `mkdir -p /var/cache/sccache`. It is bind-mounted into each job container at + `/sccache` (see `runner-config.example.yaml`), so cache survives across runs + and is shared between the lint/test jobs and both projects. +- **Devcontainer** uses a named volume (`trx-rs-sccache`). +- Check effectiveness with `sccache --show-stats` (the CI jobs print it). + +`CARGO_INCREMENTAL=0` is set wherever sccache is on, since sccache cannot cache +incremental artifacts. + +## CI runner (Alpine / OpenRC) + +The runner uses the **Docker executor** (not the host executor): per-job +container isolation and standard `ubuntu-latest` semantics. `act_runner` runs +as an OpenRC service. Files provided: + +| File | Purpose | +|------|---------| +| `act_runner.openrc` | OpenRC init script (`supervise-daemon`, depends on docker). | +| `act_runner.confd.example` | Per-instance `conf.d` settings for multi-runner hosts. | + +**Cap the thread budget.** In a VM, pin its vCPUs to specific host threads +(libvirt/KVM): + +```xml +2 + + + + ``` + +On bare metal, the `container.options: "--cpus=2"` and `capacity: 1` in +`runner-config.example.yaml` already bound each runner. + +**Set it up:** + +```bash +# 1. Docker + a dedicated user with socket access +apk add docker docker-cli +rc-update add docker default && rc-service docker start +adduser -S -D -H -h /var/lib/act_runner act +addgroup act docker + +# 2. act_runner binary (static Go build, works on musl) +# Upstream publishes per-architecture builds; pick the host's. +case "$(uname -m)" in x86_64) arch=amd64 ;; aarch64) arch=arm64 ;; esac +curl -fsSL -o /usr/local/bin/act_runner \ + "https://gitea.com/gitea/act_runner/releases/download/v0.2.11/act_runner-0.2.11-linux-${arch}" +chmod +x /usr/local/bin/act_runner + +# 3. Config + register one runner per project (scope keeps their jobs apart) +install -Dm644 container/runner-config.example.yaml /etc/act_runner/trx-rs.yaml +install -d -o act /var/lib/act_runner/trx-rs +su act -s /bin/sh -c 'cd /var/lib/act_runner/trx-rs && \ + act_runner register --no-interactive \ + --instance https://git.haxx.space --token \ + --name trx-rs-ci \ + --labels "ubuntu-latest:docker://catthehacker/ubuntu:act-latest"' + +# 4. OpenRC service (repeat the symlink+conf.d for the second project) +install -m755 container/act_runner.openrc /etc/init.d/act_runner +ln -s act_runner /etc/init.d/act_runner.trx-rs +install -m644 container/act_runner.confd.example /etc/conf.d/act_runner.trx-rs +rc-update add act_runner.trx-rs default +rc-service act_runner.trx-rs start +``` + +Check it with `rc-service act_runner.trx-rs status` and +`tail -f /var/log/act_runner.trx-rs.log`. diff --git a/container/act_runner.confd.example b/container/act_runner.confd.example new file mode 100644 index 00000000..b3ee409e --- /dev/null +++ b/container/act_runner.confd.example @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2026 Stan Grams +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Per-instance settings for an act_runner OpenRC service. +# Copy to /etc/conf.d/, e.g. /etc/conf.d/act_runner.trx-rs +# (the name must match the /etc/init.d/ symlink). + +# User that runs the daemon. Must be a member of the `docker` group. +runner_user="act" + +# Per-instance state dir (holds the .runner registration) and config file, +# so two runners on one host stay independent. +runner_dir="/var/lib/act_runner/trx-rs" +runner_config="/etc/act_runner/trx-rs.yaml" diff --git a/container/act_runner.openrc b/container/act_runner.openrc new file mode 100644 index 00000000..5c87aff3 --- /dev/null +++ b/container/act_runner.openrc @@ -0,0 +1,44 @@ +#!/sbin/openrc-run +# SPDX-FileCopyrightText: 2026 Stan Grams +# SPDX-License-Identifier: GPL-2.0-or-later +# +# OpenRC service for a Gitea act_runner (Docker executor) on Alpine. +# +# Install as /etc/init.d/act_runner (chmod +x). Single instance uses +# /etc/act_runner/config.yaml. For one runner per project, symlink this script +# and add a matching conf.d file: +# +# ln -s act_runner /etc/init.d/act_runner.trx-rs +# cp container/act_runner.confd.example /etc/conf.d/act_runner.trx-rs +# $EDITOR /etc/conf.d/act_runner.trx-rs # set runner_dir / runner_config +# rc-update add act_runner.trx-rs default +# rc-service act_runner.trx-rs start + +description="Gitea Actions runner" + +: "${runner_user:=act}" +: "${runner_dir:=/var/lib/act_runner}" +: "${runner_config:=/etc/act_runner/config.yaml}" + +command="/usr/local/bin/act_runner" +command_args="daemon --config ${runner_config}" +# No group given, so supplementary groups (incl. docker) are initialised. +command_user="${runner_user}" +directory="${runner_dir}" + +supervisor="supervise-daemon" +respawn_delay=5 +respawn_max=0 +pidfile="/run/${RC_SVCNAME}.pid" +output_log="/var/log/${RC_SVCNAME}.log" +error_log="/var/log/${RC_SVCNAME}.log" + +depend() { + need docker + use net dns +} + +start_pre() { + checkpath -d -m 0750 -o "${runner_user}" "${runner_dir}" + checkpath -f -m 0640 -o "${runner_user}" "${output_log}" +} diff --git a/container/config.yaml b/container/config.yaml deleted file mode 100644 index 6ffe1c0c..00000000 --- a/container/config.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Stan Grams -# -# SPDX-License-Identifier: GPL-2.0-or-later -# -# act_runner configuration template. Seeded into /data/config.yaml on first -# boot; edit the copy inside the volume to change settings per runner. - -log: - level: info - -runner: - # Registration state. Relative to the daemon's working directory (/data). - file: .runner - # Concurrent jobs this runner will pick up. Rust builds are heavy — keep this - # modest, especially if two runners share one host. The trx-rs workflow has - # three parallel jobs (lint, test, reuse); capacity 2 lets two overlap. - capacity: 2 - timeout: 3h - # Map the workflow's `runs-on: ubuntu-latest` to the HOST executor, i.e. run - # steps directly inside THIS container (which already has all the toolchain). - # No Docker/Podman socket is required in this mode. - labels: - - "ubuntu-latest:host" - -cache: - # Built-in actions cache server (used by actions/cache). Stored in the volume. - enabled: true - dir: "/data/cache" - -host: - # Where per-job workspaces are created. - workdir_parent: /data/workflows diff --git a/container/entrypoint.sh b/container/entrypoint.sh deleted file mode 100644 index 72a1dfe3..00000000 --- a/container/entrypoint.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash -# SPDX-FileCopyrightText: 2026 Stan Grams -# -# SPDX-License-Identifier: GPL-2.0-or-later -# -# Registers the runner on first boot (if no .runner state exists in /data), -# then runs the act_runner daemon. Idempotent: on subsequent boots it reuses -# the stored registration and ignores the token. -set -euo pipefail - -CONFIG_FILE="${CONFIG_FILE:-/data/config.yaml}" - -cd /data - -# Seed the config from the image's template on first boot so it lives in the -# persistent volume and can be edited there. -if [ ! -f "$CONFIG_FILE" ]; then - cp /etc/act_runner/config.yaml "$CONFIG_FILE" -fi - -# runner.file in config.yaml is ".runner" (relative to this CWD => /data/.runner). -if [ ! -f /data/.runner ]; then - if [ -z "${GITEA_RUNNER_REGISTRATION_TOKEN:-}" ]; then - echo "ERROR: no /data/.runner registration and GITEA_RUNNER_REGISTRATION_TOKEN is empty." >&2 - echo " Grab a token from the repo's Settings -> Actions -> Runners and set it" >&2 - echo " in the Quadlet unit for the first boot only." >&2 - exit 1 - fi - echo "Registering runner '${GITEA_RUNNER_NAME:-podman}' with ${GITEA_INSTANCE_URL} ..." - act_runner register --no-interactive \ - --config "$CONFIG_FILE" \ - --instance "${GITEA_INSTANCE_URL:?set GITEA_INSTANCE_URL}" \ - --token "$GITEA_RUNNER_REGISTRATION_TOKEN" \ - --name "${GITEA_RUNNER_NAME:-podman}" \ - --labels "${GITEA_RUNNER_LABELS:-ubuntu-latest:host}" -fi - -exec act_runner daemon --config "$CONFIG_FILE" diff --git a/container/project2-runner.container b/container/project2-runner.container deleted file mode 100644 index 6448f4e2..00000000 --- a/container/project2-runner.container +++ /dev/null @@ -1,40 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Stan Grams -# -# SPDX-License-Identifier: GPL-2.0-or-later -# -# Rootless Podman Quadlet for the SECOND project's Gitea Actions runner, -# co-located on the same host as the trx-rs runner. -# -# It has its own name, its own data volume and its own registration token, so -# the two runners are fully independent. They share the `ubuntu-latest` label, -# but registration SCOPE (which repo each token came from) keeps their jobs -# separate — neither will pick up the other's work. -# -# If project 2 needs different build dependencies, build it its own image from -# an adjusted Containerfile and point Image= at that instead of reusing the -# trx-rs image below. - -[Unit] -Description=Gitea Actions runner — project 2 -After=network-online.target -Wants=network-online.target - -[Container] -Image=localhost/gitea-act-runner:latest -ContainerName=gitea-runner-project2 -Volume=gitea-runner-project2-data:/data:Z - -Environment=CONFIG_FILE=/data/config.yaml -Environment=GITEA_INSTANCE_URL=https://git.haxx.space -Environment=GITEA_RUNNER_NAME=project2-podman -Environment=GITEA_RUNNER_LABELS=ubuntu-latest:host -Environment=GITEA_RUNNER_REGISTRATION_TOKEN= - -PodmanArgs=--cpus=4.0 --memory=6g - -[Service] -Restart=always -TimeoutStartSec=0 - -[Install] -WantedBy=default.target diff --git a/container/runner-config.example.yaml b/container/runner-config.example.yaml new file mode 100644 index 00000000..86d78bd5 --- /dev/null +++ b/container/runner-config.example.yaml @@ -0,0 +1,47 @@ +# SPDX-FileCopyrightText: 2026 Stan Grams +# +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Example act_runner config for the Docker-executor runner that lives in the +# CI VM. This is NOT the SDK image — it configures the runner that launches +# per-job containers (including the trx-rs SDK image referenced by the +# workflow's `container:` key). Copy to the VM and pass with +# `act_runner daemon --config`. + +log: + level: info + +runner: + file: .runner + # One concurrent job. With one runner per project on a 2-vCPU VM this keeps + # total CI usage at ~2 threads. + capacity: 1 + timeout: 3h + # Docker executor: no ":host" suffix. Maps runs-on labels to base images + # (the workflow overrides these per job via `container:`). + labels: + - "ubuntu-latest:docker://catthehacker/ubuntu:act-latest" + +cache: + enabled: true + +container: + # Cap every job container's CPU so CI stays within the 2-thread budget even + # if capacity is raised later. The -v mount persists the sccache cache on the + # host (create it first: `mkdir -p /var/cache/sccache`), matching SCCACHE_DIR + # in the workflow. + options: "--cpus=2 -v /var/cache/sccache:/sccache" + # act_runner rejects every bind mount unless it is listed here — the default + # is an empty allowlist, so the -v above is dropped with only a + # "[...] is not a valid volume, will be ignored" line in the job log, and + # SCCACHE_DIR then points at a directory that does not outlive the job. + valid_volumes: + - /var/cache/sccache + # Reuse the host VM's Docker network for the built-in cache/artifact server. + network: "host" + # The workflow pulls the SDK image by the moving `:latest` tag. Without this + # the runner logs "Image exists? true" and reuses whatever it cached the + # first time, so pushing a rebuilt image has no effect until someone pulls + # on the VM by hand — which looks like the image is missing a tool it in + # fact has. The extra registry round-trip per job is nothing next to a build. + force_pull: true diff --git a/container/trx-rs-runner.container b/container/trx-rs-runner.container deleted file mode 100644 index 53b460e0..00000000 --- a/container/trx-rs-runner.container +++ /dev/null @@ -1,41 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Stan Grams -# -# SPDX-License-Identifier: GPL-2.0-or-later -# -# Rootless Podman Quadlet for the trx-rs Gitea Actions runner. -# Install to ~/.config/containers/systemd/trx-rs-runner.container then: -# systemctl --user daemon-reload -# systemctl --user start trx-rs-runner -# -# First boot only: paste a registration token (repo Settings -> Actions -> -# Runners) into GITEA_RUNNER_REGISTRATION_TOKEN. After the runner appears -# online you can blank it again — the registration is persisted in the volume. - -[Unit] -Description=Gitea Actions runner — trx-rs -After=network-online.target -Wants=network-online.target - -[Container] -Image=localhost/trx-rs-ci:latest -ContainerName=gitea-runner-trx-rs -# Persistent state: .runner registration, cache, workspaces. -Volume=gitea-runner-trx-rs-data:/data:Z - -Environment=CONFIG_FILE=/data/config.yaml -Environment=GITEA_INSTANCE_URL=https://git.haxx.space -Environment=GITEA_RUNNER_NAME=trx-rs-podman -Environment=GITEA_RUNNER_LABELS=ubuntu-latest:host -Environment=GITEA_RUNNER_REGISTRATION_TOKEN= - -# Resource caps so a heavy Rust build here cannot starve the other project's -# runner on the same host. Tune to your box. -PodmanArgs=--cpus=4.0 --memory=6g - -[Service] -Restart=always -# A cold Rust build can be slow; don't let systemd consider startup failed. -TimeoutStartSec=0 - -[Install] -WantedBy=default.target diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..323e27b0 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2026 Stan Grams +# +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Pins the Rust toolchain for reproducible builds. Keep in sync with the +# SDK image (container/Containerfile, ARG RUST_VERSION). rustup honours this +# automatically for local builds outside the SDK container. + +[toolchain] +channel = "1.97.1" +components = ["rustfmt", "clippy"]