This commit is contained in:
Jason Stedwell
2026-06-29 21:56:12 -05:00
parent bc207c1cf7
commit 1757777a32
32 changed files with 1468 additions and 44 deletions
+84
View File
@@ -0,0 +1,84 @@
# Architecture
## Design summary
The runner is a Docker image that wraps the official `act_runner` binary in a controlled, predictable environment. All dependencies are installed at image build time. The running container is long-lived and operator-managed.
---
## Layers
```
┌─────────────────────────────────────────────┐
│ Gitea instance (git.alwisp.com) │ ← dispatches jobs via act protocol
└───────────────────┬─────────────────────────┘
│ HTTP / WebSocket
┌───────────────────▼─────────────────────────┐
│ custom-gitea-runner container │
│ ┌──────────────────────────────────────┐ │
│ │ tini (PID 1) │ │
│ │ └── docker-entrypoint.sh │ │
│ │ ├── validate binaries │ │
│ │ ├── install custom CAs │ │
│ │ ├── register (first boot only) │ │
│ │ └── act_runner daemon │ │
│ └──────────────────────────────────────┘ │
│ │
│ Bundled tools (installed at build time): │
│ bash, curl, git, jq, node, npm, │
│ docker CLI, ca-certificates, tini │
└───────────────────┬─────────────────────────┘
│ /var/run/docker.sock mount
┌───────────────────▼─────────────────────────┐
│ Host Docker daemon │ ← runs job containers
└─────────────────────────────────────────────┘
```
---
## Key design decisions
See `docs/decisions/` for ADR-style records of each major choice.
| Decision | Choice | Doc |
|---|---|---|
| Base image | Ubuntu 22.04 | `decisions/0001-base-image.md` |
| act_runner strategy | Wrap official binary | — |
| Docker access | Socket mount (`/var/run/docker.sock`) | — |
| PID 1 | `tini` | — |
| Node.js source | NodeSource (Node 20 LTS) | — |
| v1 user model | root (Docker socket requires it) | — |
---
## Registration modes
**Mode A — Pre-generated config (preferred for stable production)**
Operator mounts a pre-generated `runner.yaml` at `RUNNER_CONFIG_PATH`. The entrypoint detects the file and skips registration, going straight to daemon start.
**Mode B — Bootstrap registration (first boot)**
If `RUNNER_CONFIG_PATH` does not exist, the entrypoint reads `GITEA_INSTANCE_URL`, `GITEA_RUNNER_TOKEN`, and `GITEA_RUNNER_NAME` from the environment, generates a default config, and calls `act_runner register`. Subsequent starts use the persisted config (Mode A path).
This flow is idempotent — reboots with an existing config will not re-register.
---
## Docker socket model
The runner container mounts the host Docker socket (`/var/run/docker.sock`). This allows:
- `docker build` and `docker push` in workflow steps
- `docker/login-action` against external registries
**Trust implication:** a workflow running on this runner has full access to the host Docker daemon. This is acceptable for a trusted, self-hosted environment. Future hardening options (DinD, rootless, network isolation) are tracked in `ROADMAP.md` Phase 6.
---
## Future hardening tracks
- Rootless container execution
- Docker-in-Docker (DinD) variant for true isolation
- Dedicated BuildKit daemon
- Ephemeral single-job runners
- Network policy enforcement
+49
View File
@@ -0,0 +1,49 @@
# ADR 0001 — Base Image Selection
**Status:** Accepted
---
## Context
The runner needs a base OS image that provides:
- A working apt package ecosystem for installing `git`, `curl`, `jq`, `tini`, `ca-certificates`, `tzdata`.
- Compatibility with Docker's official apt repository (for Docker CLI).
- Compatibility with NodeSource (for Node.js 20 LTS).
- A label environment the existing workflows reference: `ubuntu-latest`.
Candidates considered:
1. `ubuntu:22.04` — LTS, wide ecosystem support, good compatibility.
2. `ubuntu:24.04` — Newer LTS, some tooling still stabilizing.
3. `debian:bookworm-slim` — Smaller footprint, good compatibility, but `ubuntu-latest` label mismatch.
4. `debian:bullseye-slim` — Older, stable, but further from `ubuntu-latest` expectations.
---
## Decision
Use `ubuntu:22.04` as the base image, pinned by exact tag.
---
## Rationale
- Existing Gitea workflows use `runs-on: ubuntu-latest`. Basing the runner image on Ubuntu 22.04 maximizes compatibility with GitHub Actions steps written for that environment.
- Ubuntu 22.04 LTS has long-term support through 2027, reducing forced base image churn.
- Docker's apt repository and NodeSource both officially support Ubuntu 22.04.
- Ubuntu 22.04 is the most common self-hosted runner base image in community examples, so troubleshooting resources are abundant.
---
## Consequences
- Final image size will be larger than a minimal Alpine or Debian slim base, which is acceptable given the goal of bundling all runtime dependencies.
- When the workflow ecosystem moves to `ubuntu-24.04`, the base image can be bumped with a version argument change (`ARG UBUNTU_VERSION=24.04`).
---
## Alternatives considered
**Debian bookworm-slim:** Smaller image, but the `ubuntu-latest` label in existing workflows creates an expectation of Ubuntu compatibility. Switching to Debian may cause subtle incompatibilities with actions that check the OS ID.
**Ubuntu 24.04:** Not yet the default for `ubuntu-latest` as of the initial build date; 22.04 remains the more tested choice for self-hosted runners.
+134
View File
@@ -0,0 +1,134 @@
# Operations
## Normal startup sequence
When the container starts, expect to see:
```
======================================================
custom-gitea-runner
Image version : v0.1.0
Git revision : abc1234
======================================================
Runtime:
act_runner : v0.2.11
node : v20.x.x
npm : 10.x.x
git : 2.x.x
docker : 24.x.x
Config:
Config path : /config/runner.yaml
Work dir : /work
Gitea URL : https://git.example.com
Runner name : unraid-runner-01
Labels : ubuntu-latest:docker://node:20-bullseye
======================================================
[OK] All required binaries present.
[INFO] Existing config found at /config/runner.yaml. Skipping registration.
[START] Starting act_runner daemon ...
```
On first boot with no config:
```
[BOOTSTRAP] No config found at /config/runner.yaml. Initiating registration.
[BOOTSTRAP] Registration complete. Config at /config/runner.yaml.
[START] Starting act_runner daemon ...
```
---
## Health check
The container runs a health check every 30s. Check status with:
```bash
docker inspect --format='{{.State.Health.Status}}' gitea-runner
```
Or view health check output:
```bash
docker inspect --format='{{json .State.Health}}' gitea-runner | jq .
```
Expected healthy output from `healthcheck.sh`:
```
[HEALTH] OK : act_runner process is running
[HEALTH] OK : Config file present at /config/runner.yaml
[HEALTH] OK : Binary present: act_runner
[HEALTH] OK : Binary present: git
[HEALTH] OK : Binary present: node
[HEALTH] OK : Binary present: npm
[HEALTH] OK : Docker socket accessible
```
---
## Smoke test
Before production use, run the smoke test against the image:
```bash
./scripts/verify-runtime.sh custom-gitea-runner:v0.1.0
```
Or directly:
```bash
docker run --rm custom-gitea-runner:v0.1.0 /usr/local/bin/smoke-test.sh
```
---
## Checking runner registration in Gitea
1. Go to your Gitea instance.
2. Navigate to **Site Administration → Runners** (admin panel) or **Repository Settings → Runners**.
3. The runner should appear with status **Online**.
---
## Upgrade procedure
1. Build or pull the new pinned image tag.
2. Review `CHANGELOG.md` for breaking changes.
3. Stop the existing container.
4. Update the image tag in your compose file or Unraid template.
5. Start the container.
6. Verify startup logs and health check status.
7. Run a smoke workflow to confirm job execution works.
---
## Rollback procedure
1. Stop the container.
2. Revert the image tag to the prior known-good version.
3. Start the container.
4. Confirm the registration config is still valid (it should be — it's persisted in the config volume).
5. Run a smoke workflow.
---
## Re-registration
If you need to re-register the runner (e.g. after a token reset or renaming):
1. Stop the container.
2. Delete `config/runner.yaml`.
3. Set the new `GITEA_RUNNER_TOKEN` in your environment.
4. Start the container — it will register fresh on boot.
---
## Log access
```bash
# Tail live logs
docker logs -f gitea-runner
# Last 100 lines
docker logs --tail 100 gitea-runner
```
+145
View File
@@ -0,0 +1,145 @@
# Troubleshooting
Symptom-driven guide for common runner failures.
---
## Runner not appearing in Gitea admin panel
**Check:** Container logs for registration errors.
```bash
docker logs gitea-runner 2>&1 | grep -E "BOOTSTRAP|FATAL|ERROR"
```
**Common causes:**
| Cause | Fix |
|---|---|
| `GITEA_RUNNER_TOKEN` is invalid or expired | Generate a fresh token in Gitea admin → Runners |
| `GITEA_INSTANCE_URL` wrong or unreachable | Verify URL is reachable from inside the container: `docker exec gitea-runner curl -I "$GITEA_INSTANCE_URL"` |
| TLS cert error (self-signed Gitea) | Mount your CA cert to `/config/trusted-ca/` and restart |
| Runner name already registered | Use a unique `GITEA_RUNNER_NAME` or deregister the old one |
---
## `node: not found` in job logs
The runner image bundles Node.js 20 LTS. If you see this error it usually means:
- The job is running inside a container image that does not have Node.js (e.g. `alpine`).
- The `ubuntu-latest` label is mapped to a minimal image.
**Fix:** Ensure the label mapping uses a full Node image:
```
ubuntu-latest:docker://node:20-bullseye
```
Or use a host-mode label:
```
ubuntu-latest:host
```
---
## Docker socket permission error
**Symptom:**
```
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock
```
**Causes and fixes:**
| Cause | Fix |
|---|---|
| Socket not mounted | Add `-v /var/run/docker.sock:/var/run/docker.sock` |
| Socket GID mismatch | Container runs as root in v1 — this should not occur. If using a non-root custom image, add the runner user to the `docker` group or set `--group-add $(stat -c '%g' /var/run/docker.sock)` |
---
## Workflow checkout failures
**Symptom:** `actions/checkout` fails with an auth or host error.
**Check:**
1. Gitea URL is accessible from inside the container.
2. If using self-signed TLS, the CA cert is trusted inside the container.
```bash
docker exec gitea-runner curl -v https://git.example.com
```
If you see certificate errors, add your CA cert to `/config/trusted-ca/` and restart.
---
## Registry login failures (`docker/login-action`)
**Symptom:** Login to `registry.alwisp.com` fails.
**Check:**
1. Registry URL is correct.
2. Credentials are valid.
3. If using self-signed TLS on the registry, the CA cert needs to be trusted inside the container AND the Docker daemon config must also trust the registry.
For Docker daemon CA trust on the Unraid host:
```bash
mkdir -p /etc/docker/certs.d/registry.alwisp.com
cp my-ca.crt /etc/docker/certs.d/registry.alwisp.com/ca.crt
```
Then restart the Docker daemon and the runner container.
---
## Container job image issues
**Symptom:** Jobs that run inside container images fail to pull or execute.
**Check:**
- The container image referenced in the label mapping is pullable from inside the runner container.
- If using a self-hosted registry image, the runner container can authenticate to it.
---
## CA / certificate trust issues
**Symptom:** TLS errors when connecting to Gitea or the registry.
**Fix:**
1. Copy your CA cert (`.crt` format, PEM encoded) to `config/trusted-ca/`.
2. Restart the container — the entrypoint installs certs from that directory on startup.
3. Verify: `docker exec gitea-runner curl -v https://git.example.com` should show cert chain OK.
---
## Unraid-specific: container exits immediately
**Check:** Container logs before exit:
```bash
docker logs gitea-runner
```
Look for `[FATAL]` lines. Common cause is a missing required env var (`GITEA_INSTANCE_URL`, `GITEA_RUNNER_TOKEN`, `GITEA_RUNNER_NAME`) when no config file exists.
**Fix:** Set required env vars in the Unraid container template.
---
## Startup validation failures
**Symptom:** Container exits with `[FATAL] Missing required binaries`.
This is an image build problem — the binary should always be present if the image was built correctly.
**Fix:** Rebuild the image from the `docker/runner/Dockerfile` and verify:
```bash
./scripts/verify-runtime.sh custom-gitea-runner:latest
```
+121
View File
@@ -0,0 +1,121 @@
# Unraid Deployment Guide
---
## Overview
This guide covers deploying the custom-gitea-runner as a Docker container on an Unraid host. The recommended approach is Docker Compose via Unraid's compose manager, or manual template entry via the Unraid Docker UI.
---
## Prerequisites
- Unraid with Docker enabled.
- Access to your Gitea instance admin panel to generate a runner token.
- The runner image pushed to your registry (e.g. `registry.alwisp.com`), or built locally on the Unraid host.
---
## Step 1: Get a runner token
1. Log into your Gitea instance as an admin.
2. Go to **Site Administration → Runners → Create new runner**.
3. Copy the registration token. It will be used as `GITEA_RUNNER_TOKEN`.
---
## Step 2: Set up appdata directories
```bash
mkdir -p /mnt/user/appdata/gitea-runner/config
mkdir -p /mnt/user/appdata/gitea-runner/work
```
If using custom CA certificates:
```bash
mkdir -p /mnt/user/appdata/gitea-runner/config/trusted-ca
# Copy your CA cert:
cp my-ca.crt /mnt/user/appdata/gitea-runner/config/trusted-ca/
```
---
## Step 3: Docker UI container settings
In Unraid's Docker tab, click **Add Container** and fill in:
**Basic**
| Field | Value |
|---|---|
| Name | `gitea-runner` |
| Repository | `registry.alwisp.com/custom-gitea-runner:v0.1.0` |
| Network Type | `Bridge` |
| Privileged | `No` |
| Restart | `Unless Stopped` |
**Environment variables**
| Key | Value |
|---|---|
| `GITEA_INSTANCE_URL` | `https://git.example.com` |
| `GITEA_RUNNER_TOKEN` | *(your registration token)* |
| `GITEA_RUNNER_NAME` | `unraid-runner-01` |
| `GITEA_RUNNER_LABELS` | `ubuntu-latest:docker://node:20-bullseye` |
| `TZ` | `America/Chicago` |
**Volumes**
| Container path | Host path | Mode |
|---|---|---|
| `/config` | `/mnt/user/appdata/gitea-runner/config` | Read/Write |
| `/work` | `/mnt/user/appdata/gitea-runner/work` | Read/Write |
| `/var/run/docker.sock` | `/var/run/docker.sock` | Read/Write |
Click **Apply**.
---
## Step 4: Verify startup
View container logs in the Unraid Docker UI or via terminal:
```bash
docker logs gitea-runner
```
First boot should show `[BOOTSTRAP] Registration complete`.
Subsequent starts show `[INFO] Existing config found`.
---
## Step 5: Verify in Gitea
Go to Gitea **Site Administration → Runners**. The runner should appear as **Online**.
---
## Update procedure
1. Pull the new image tag to the Unraid host (or use the Community Applications plugin if listed).
2. In the Docker UI, edit the container and update the Repository to the new tag.
3. Click **Apply** — Unraid stops and recreates the container from the new image.
4. Check logs to confirm startup, then test a workflow.
## Rollback procedure
Repeat the update procedure but specify the previous known-good tag.
---
## Appdata backup
The `/config` volume (at `/mnt/user/appdata/gitea-runner/config/`) contains the runner registration config. Include it in Unraid's Appdata Backup plugin to preserve registration between host moves.
---
## Notes
- The Docker socket mount (`/var/run/docker.sock`) is intentional and required for build/push workflows. On a single-tenant, self-owned Unraid host this is the standard approach.
- The `/work` directory grows as jobs run. You can safely clear it when no jobs are active to reclaim space.