Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3080a58c4a | ||
|
|
b12c83e8b5 |
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+9
-20
@@ -2,10 +2,10 @@
|
|||||||
#
|
#
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
# CI for the Docker-executor runner (VM). The lint/test jobs run inside the
|
# CI for the self-hosted, host-executor Podman runners (see container/).
|
||||||
# shared trx-rs SDK image (container/Containerfile), which bakes in the pinned
|
# The runner image bakes in the Rust toolchain and all build dependencies,
|
||||||
# Rust toolchain and all build dependencies. The reuse job uses the upstream
|
# so jobs go straight to cargo — no apt/rustup setup steps (which also
|
||||||
# Docker action, which the Docker executor launches as a sibling container.
|
# collided on the dpkg lock when jobs ran concurrently in the same runner).
|
||||||
|
|
||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
@@ -16,43 +16,32 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
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:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: git.haxx.space/sjg/trx-rs/sdk:latest
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: rustfmt
|
- name: rustfmt
|
||||||
run: cargo fmt --all -- --check
|
run: cargo fmt --all -- --check
|
||||||
- name: clippy
|
- name: clippy
|
||||||
run: cargo clippy --workspace --all-targets --all-features -- -D warnings
|
run: cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||||
- name: sccache stats
|
|
||||||
if: always()
|
|
||||||
run: sccache --show-stats
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: git.haxx.space/sjg/trx-rs/sdk:latest
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --workspace --all-targets --locked
|
run: cargo build --workspace --all-targets --locked
|
||||||
- name: Test
|
- name: Test
|
||||||
run: cargo test --workspace --locked
|
run: cargo test --workspace --locked
|
||||||
- name: sccache stats
|
|
||||||
if: always()
|
|
||||||
run: sccache --show-stats
|
|
||||||
|
|
||||||
reuse:
|
reuse:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: fsfe/reuse-action@v5
|
- 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
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ path = [
|
|||||||
"trx-rs.toml.example",
|
"trx-rs.toml.example",
|
||||||
"docs/**",
|
"docs/**",
|
||||||
"aidocs/**",
|
"aidocs/**",
|
||||||
"container/**",
|
|
||||||
".devcontainer/**",
|
|
||||||
"src/decoders/trx-ftx/README.md",
|
"src/decoders/trx-ftx/README.md",
|
||||||
"src/decoders/trx-wxsat/README.md",
|
"src/decoders/trx-wxsat/README.md",
|
||||||
"assets/trx-logo.png",
|
"assets/trx-logo.png",
|
||||||
|
|||||||
+37
-31
@@ -2,54 +2,60 @@
|
|||||||
#
|
#
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
# trx-rs SDK / build image.
|
# Gitea Actions runner image for trx-rs CI (host-executor / "Pattern B").
|
||||||
#
|
#
|
||||||
# Single source of truth for the build environment. Used two ways:
|
# All build dependencies, the Rust toolchain, Node.js (for JS actions such as
|
||||||
# * CI — as the job container for the lint/test jobs (Docker executor).
|
# actions/checkout and actions/cache) and the `reuse` tool are baked in, so CI
|
||||||
# * Dev — run locally or via .devcontainer for a reproducible toolchain.
|
# 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
|
||||||
# Pinning the Rust version here (and in rust-toolchain.toml) means CI and every
|
# just become fast no-ops because everything is already installed.
|
||||||
# developer share the exact same rustc/clippy, so "works locally, fails in CI"
|
|
||||||
# cannot happen.
|
|
||||||
FROM docker.io/library/debian:bookworm-slim
|
FROM docker.io/library/debian:bookworm-slim
|
||||||
|
|
||||||
# Keep in sync with rust-toolchain.toml.
|
ARG ACT_RUNNER_VERSION=0.2.11
|
||||||
ARG RUST_VERSION=1.97.1
|
|
||||||
ARG NODE_MAJOR=20
|
ARG NODE_MAJOR=20
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
RUSTUP_HOME=/usr/local/rustup \
|
RUSTUP_HOME=/opt/rustup \
|
||||||
CARGO_HOME=/usr/local/cargo \
|
CARGO_HOME=/opt/cargo \
|
||||||
PATH=/usr/local/cargo/bin:/usr/local/bin:/usr/bin:/bin
|
PATH=/opt/cargo/bin:/usr/local/bin:/usr/bin:/bin
|
||||||
|
|
||||||
# Build dependencies (mirror README's manual instructions).
|
# Base tooling + trx-rs build dependencies (mirrors .gitea/workflows/ci.yml).
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
ca-certificates curl git \
|
ca-certificates curl xz-utils git sudo pipx \
|
||||||
build-essential pkg-config cmake clang libclang-dev \
|
build-essential pkg-config cmake clang libclang-dev \
|
||||||
libopus-dev libasound2-dev libsoapysdr-dev \
|
libopus-dev libasound2-dev libsoapysdr-dev \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Node.js — JS-based actions (actions/checkout, actions/cache) run *inside*
|
# Node.js (JS-based actions need node in PATH under the host executor).
|
||||||
# the job container under the Docker executor, so node must be present.
|
|
||||||
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_MAJOR}.x | bash - \
|
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_MAJOR}.x | bash - \
|
||||||
&& apt-get install -y --no-install-recommends nodejs \
|
&& apt-get install -y --no-install-recommends nodejs \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Pinned Rust toolchain, installed world-readable so any UID the runner or a
|
# REUSE >= 3 (Debian's packaged reuse is too old for REUSE.toml).
|
||||||
# devcontainer uses can invoke cargo.
|
# 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.
|
||||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
|
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
|
||||||
| sh -s -- -y --no-modify-path \
|
| sh -s -- -y --no-modify-path --profile minimal \
|
||||||
--default-toolchain "${RUST_VERSION}" --profile minimal \
|
|
||||||
--component rustfmt --component clippy \
|
--component rustfmt --component clippy \
|
||||||
&& chmod -R a+rwX "$RUSTUP_HOME" "$CARGO_HOME"
|
&& chmod -R a+rwX "$CARGO_HOME" "$RUSTUP_HOME"
|
||||||
|
|
||||||
# sccache — shared compilation cache. Enabled at build time via
|
# act_runner binary.
|
||||||
# RUSTC_WRAPPER (see the CI workflow and .devcontainer), not repo-wide, so
|
RUN arch="$(dpkg --print-architecture)"; \
|
||||||
# non-SDK builds are unaffected. musl build is static and runs anywhere.
|
case "$arch" in amd64) rarch=amd64;; arm64) rarch=arm64;; *) echo "unsupported arch $arch" >&2; exit 1;; esac; \
|
||||||
ARG SCCACHE_VERSION=0.8.2
|
curl -fsSL -o /usr/local/bin/act_runner \
|
||||||
RUN curl -fsSL "https://github.com/mozilla/sccache/releases/download/v${SCCACHE_VERSION}/sccache-v${SCCACHE_VERSION}-x86_64-unknown-linux-musl.tar.gz" \
|
"https://gitea.com/gitea/act_runner/releases/download/v${ACT_RUNNER_VERSION}/act_runner-${ACT_RUNNER_VERSION}-linux-${rarch}" \
|
||||||
| tar -xz -C /tmp \
|
&& chmod +x /usr/local/bin/act_runner
|
||||||
&& install -m755 "/tmp/sccache-v${SCCACHE_VERSION}-x86_64-unknown-linux-musl/sccache" /usr/local/bin/sccache \
|
|
||||||
&& rm -rf /tmp/sccache-*
|
|
||||||
|
|
||||||
WORKDIR /work
|
# 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"]
|
||||||
|
|||||||
+90
-102
@@ -3,128 +3,116 @@ SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
|
|||||||
SPDX-License-Identifier: GPL-2.0-or-later
|
SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
-->
|
-->
|
||||||
|
|
||||||
# trx-rs SDK image
|
# Podman-based Gitea Actions runners
|
||||||
|
|
||||||
A single container image that is the canonical build environment for trx-rs,
|
Run two independent Gitea Actions runners on one host as rootless Podman
|
||||||
used **both** by CI and by developers. It bakes in the pinned Rust toolchain
|
containers managed by systemd (Quadlet) — one per project — instead of two
|
||||||
(matching `rust-toolchain.toml`) and every build dependency, so the compiler
|
VMs. Uses the **host executor**: workflow steps run directly inside a
|
||||||
and `clippy` are identical everywhere — no "works on my machine".
|
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 |
|
| File | Purpose |
|
||||||
|------|---------|
|
|------|---------|
|
||||||
| `Containerfile` | The SDK image (Debian + build deps + pinned Rust + Node + git). |
|
| `Containerfile` | Runner image: Debian + build deps + clang + Rust + Node + `reuse` + `act_runner`. |
|
||||||
| `runner-config.example.yaml` | Example act_runner config for the CI VM (Docker executor). |
|
| `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. |
|
||||||
|
|
||||||
## Build and publish
|
## Prerequisites (once per host)
|
||||||
|
|
||||||
|
Rootless Podman with cgroups v2 (default on modern distros). As the unprivileged
|
||||||
|
user that will own the runners:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# from the repo root
|
# Survive logout / start on boot without an interactive session.
|
||||||
podman build -t git.haxx.space/sjg/trx-rs/sdk:latest container
|
loginctl enable-linger "$USER"
|
||||||
podman login git.haxx.space
|
|
||||||
podman push git.haxx.space/sjg/trx-rs/sdk:latest
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Tag with the Rust version too (e.g. `:1.97.1`) if you want reproducible pins.
|
No `podman.socket` is required for the host executor.
|
||||||
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.
|
|
||||||
|
|
||||||
## Developer use
|
## 1. Build the image
|
||||||
|
|
||||||
Reproducible one-off build, no local toolchain needed:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
podman run --rm -it -v "$PWD":/work -w /work \
|
cd container
|
||||||
git.haxx.space/sjg/trx-rs/sdk:latest \
|
podman build -t trx-rs-ci:latest .
|
||||||
cargo build --release
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Or open the repo in the image via VS Code / JetBrains "Reopen in Container"
|
## 2. Get a registration token
|
||||||
(`.devcontainer/devcontainer.json` points at the same image).
|
|
||||||
|
|
||||||
Building outside the container? `rust-toolchain.toml` pins the same rustc, so
|
For **each** repo: *Settings → Actions → Runners → Create new Runner* and copy
|
||||||
`rustup` installs the matching toolchain automatically.
|
the token. (Org- or instance-level tokens work too if you prefer wider scope.)
|
||||||
|
|
||||||
## CI use
|
## 3. Install and start the runners
|
||||||
|
|
||||||
`.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.
|
|
||||||
|
|
||||||
## Compilation cache (sccache)
|
|
||||||
|
|
||||||
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
|
|
||||||
<vcpu placement='static'>2</vcpu>
|
|
||||||
<cputune>
|
|
||||||
<vcpupin vcpu='0' cpuset='4'/>
|
|
||||||
<vcpupin vcpu='1' cpuset='5'/>
|
|
||||||
</cputune>
|
|
||||||
```
|
|
||||||
|
|
||||||
On bare metal, the `container.options: "--cpus=2"` and `capacity: 1` in
|
|
||||||
`runner-config.example.yaml` already bound each runner.
|
|
||||||
|
|
||||||
**Set it up:**
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Docker + a dedicated user with socket access
|
mkdir -p ~/.config/containers/systemd
|
||||||
apk add docker docker-cli
|
cp trx-rs-runner.container project2-runner.container ~/.config/containers/systemd/
|
||||||
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)
|
# Paste each repo's token for the FIRST boot only:
|
||||||
curl -fsSL -o /usr/local/bin/act_runner \
|
# Environment=GITEA_RUNNER_REGISTRATION_TOKEN=xxxx…
|
||||||
https://gitea.com/gitea/act_runner/releases/download/v0.2.11/act_runner-0.2.11-linux-amd64
|
$EDITOR ~/.config/containers/systemd/trx-rs-runner.container
|
||||||
chmod +x /usr/local/bin/act_runner
|
$EDITOR ~/.config/containers/systemd/project2-runner.container
|
||||||
|
|
||||||
# 3. Config + register one runner per project (scope keeps their jobs apart)
|
systemctl --user daemon-reload
|
||||||
install -Dm644 container/runner-config.example.yaml /etc/act_runner/trx-rs.yaml
|
systemctl --user start trx-rs-runner
|
||||||
install -d -o act /var/lib/act_runner/trx-rs
|
systemctl --user start project2-runner
|
||||||
su act -s /bin/sh -c 'cd /var/lib/act_runner/trx-rs && \
|
|
||||||
act_runner register --no-interactive \
|
|
||||||
--instance https://git.haxx.space --token <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)
|
systemctl --user status trx-rs-runner
|
||||||
install -m755 container/act_runner.openrc /etc/init.d/act_runner
|
podman logs -f gitea-runner-trx-rs
|
||||||
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
|
Once each runner shows **online** in the repo's runner list, blank out the
|
||||||
`tail -f /var/log/act_runner.trx-rs.log`.
|
`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"
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
#
|
|
||||||
# Per-instance settings for an act_runner OpenRC service.
|
|
||||||
# Copy to /etc/conf.d/<service-name>, 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"
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
#!/sbin/openrc-run
|
|
||||||
# SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
|
|
||||||
# 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}"
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
|
||||||
|
#
|
||||||
|
# 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
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
|
||||||
|
#
|
||||||
|
# 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"
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
|
||||||
|
#
|
||||||
|
# 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
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
|
|
||||||
#
|
|
||||||
# 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"
|
|
||||||
# Reuse the host VM's Docker network for the built-in cache/artifact server.
|
|
||||||
network: "host"
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
|
||||||
|
#
|
||||||
|
# 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
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
|
|
||||||
#
|
|
||||||
# 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"]
|
|
||||||
@@ -1624,6 +1624,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script defer src="https://cdn.jsdelivr.net/npm/opus-decoder@0.7.11/dist/opus-decoder.min.js" charset="UTF-8"></script>
|
<script defer src="https://cdn.jsdelivr.net/npm/opus-decoder@0.7.11/dist/opus-decoder.min.js" charset="UTF-8"></script>
|
||||||
|
<script defer src="/vendor/leaflet.js"></script>
|
||||||
|
<script defer src="/leaflet-ais-tracksymbol.js"></script>
|
||||||
<script defer src="/webgl-renderer.js"></script>
|
<script defer src="/webgl-renderer.js"></script>
|
||||||
<script defer src="/app.js"></script>
|
<script defer src="/app.js"></script>
|
||||||
<script>
|
<script>
|
||||||
@@ -1632,41 +1634,67 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
var pluginScripts = {
|
var pluginScripts = {
|
||||||
'digital-modes': ['/ft8.js', '/ft4.js', '/ft2.js', '/wspr.js', '/cw.js', '/background-decode.js', '/sat.js', '/wefax.js'],
|
'digital-modes': ['/ft8.js', '/ft4.js', '/ft2.js', '/wspr.js', '/cw.js', '/background-decode.js', '/sat.js', '/wefax.js'],
|
||||||
'map-data': ['/map-core.js', '/ais.js', '/vdes.js', '/aprs.js', '/hf-aprs.js'],
|
'map-data': ['/map-core.js', '/ais.js', '/vdes.js', '/aprs.js', '/hf-aprs.js'],
|
||||||
'map': ['/map-core.js', '/leaflet-ais-tracksymbol.js', '/ais.js', '/vdes.js', '/aprs.js', '/hf-aprs.js', '/sat.js', '/sat-scheduler.js'],
|
'map': ['/map-core.js', '/ais.js', '/vdes.js', '/aprs.js', '/hf-aprs.js', '/sat.js', '/sat-scheduler.js'],
|
||||||
'statistics': ['/map-core.js'],
|
'statistics': ['/map-core.js'],
|
||||||
'bookmarks': ['/bookmarks.js'],
|
'bookmarks': ['/bookmarks.js'],
|
||||||
'recorder': [],
|
'recorder': [],
|
||||||
'settings': ['/vchan.js', '/scheduler.js']
|
'settings': ['/vchan.js', '/scheduler.js']
|
||||||
};
|
};
|
||||||
var loaded = new Set();
|
var loaded = new Set();
|
||||||
function loadPlugins(tab) {
|
var loading = new Map();
|
||||||
var scripts = pluginScripts[tab];
|
|
||||||
if (!scripts) return;
|
function loadScript(src) {
|
||||||
scripts.forEach(function(src) {
|
if (loaded.has(src)) return Promise.resolve();
|
||||||
if (loaded.has(src)) return;
|
if (loading.has(src)) return loading.get(src);
|
||||||
loaded.add(src);
|
|
||||||
|
var request = new Promise(function(resolve, reject) {
|
||||||
var s = document.createElement('script');
|
var s = document.createElement('script');
|
||||||
s.src = src;
|
s.src = src;
|
||||||
s.defer = true;
|
s.onload = function() {
|
||||||
|
loaded.add(src);
|
||||||
|
loading.delete(src);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
s.onerror = function() {
|
||||||
|
loading.delete(src);
|
||||||
|
reject(new Error('Failed to load plugin script: ' + src));
|
||||||
|
};
|
||||||
document.body.appendChild(s);
|
document.body.appendChild(s);
|
||||||
});
|
});
|
||||||
|
loading.set(src, request);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadPlugins(tab) {
|
||||||
|
var scripts = pluginScripts[tab];
|
||||||
|
if (!scripts) return Promise.resolve();
|
||||||
|
return scripts.reduce(function(sequence, src) {
|
||||||
|
return sequence.then(function() { return loadScript(src); });
|
||||||
|
}, Promise.resolve());
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestPlugins(tab) {
|
||||||
|
return loadPlugins(tab).catch(function(err) {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// Eager plugin loading is triggered by app.js (after window.trx is set up)
|
// Eager plugin loading is triggered by app.js (after window.trx is set up)
|
||||||
// via window.loadEagerPlugins(). Dynamic scripts are effectively async, so
|
// via window.loadEagerPlugins(). Dynamic scripts are effectively async, so
|
||||||
// loading them before app.js would cause map-core.js to crash when
|
// loading them before app.js would cause map-core.js to crash when
|
||||||
// window.trx is not yet defined.
|
// window.trx is not yet defined.
|
||||||
window.loadEagerPlugins = function() {
|
window.loadEagerPlugins = function() {
|
||||||
['digital-modes', 'map-data', 'bookmarks', 'settings'].forEach(loadPlugins);
|
return Promise.all(
|
||||||
|
['digital-modes', 'map-data', 'bookmarks', 'settings'].map(requestPlugins)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
// Load others on tab switch
|
// Load others on tab switch
|
||||||
document.addEventListener('click', function(e) {
|
document.addEventListener('click', function(e) {
|
||||||
var tab = e.target.closest('[data-tab]');
|
var tab = e.target.closest('[data-tab]');
|
||||||
if (tab) loadPlugins(tab.dataset.tab);
|
if (tab) requestPlugins(tab.dataset.tab);
|
||||||
});
|
});
|
||||||
window.loadPluginsForTab = loadPlugins;
|
window.loadPluginsForTab = loadPlugins;
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<!-- Template cloning is handled by navigateToTab() in app.js -->
|
<!-- Template cloning is handled by navigateToTab() in app.js -->
|
||||||
<script defer src="/vendor/leaflet.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user