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
+30
View File
@@ -0,0 +1,30 @@
# custom-gitea-runner environment template
# Copy this file to .env and fill in real values.
# Never commit .env to version control.
# ─── Required for bootstrap registration (Mode B) ────────────────────────────
GITEA_INSTANCE_URL=https://git.example.com
GITEA_RUNNER_TOKEN=your-registration-token-here
GITEA_RUNNER_NAME=unraid-runner-01
# ─── Optional registration settings ─────────────────────────────────────────
# Comma-separated label list. Each label maps a GitHub Actions runner label
# to an act_runner execution environment.
# Format: <label>:<scheme>://<image>
# For host-based execution (no inner container): label:host
GITEA_RUNNER_LABELS=ubuntu-latest:docker://node:20-bullseye,host:host
# ─── Runner runtime ───────────────────────────────────────────────────────────
# Path inside the container where the runner config YAML lives.
RUNNER_CONFIG_PATH=/config/runner.yaml
# Working directory for job runs inside the container.
RUNNER_WORKDIR=/work
# ─── Optional: Timezone ───────────────────────────────────────────────────────
TZ=America/Chicago
# ─── Optional: Registry credentials (used by workflows, not the runner itself)
# These are typically set as Gitea secrets in your repository, not here.
# REGISTRY_USER=your-registry-user
# REGISTRY_TOKEN=your-registry-token
+26
View File
@@ -0,0 +1,26 @@
# Runtime secrets and local config
.env
.runner
# Generated runner config (mount, don't commit)
config/runner.yaml
# Local work directory
work/
# Custom CA files (mount, don't commit)
config/trusted-ca/*.crt
config/trusted-ca/*.pem
# Build artifacts
*.tar
*.tar.gz
# macOS
.DS_Store
# Editor
.vscode/
.idea/
*.swp
*.swo
+25
View File
@@ -0,0 +1,25 @@
# Changelog
All notable changes to this project will be documented in this file.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
---
## [Unreleased]
### Added
- Initial repository scaffold: directory structure, Dockerfile, entrypoint, healthcheck, smoke-test, config examples, compose example, and docs.
- Phase 0 planning documents: `AGENT.md`, `README.md`, `ROADMAP.md`.
- First successful local image build: `custom-gitea-runner:v0.1.0` (act_runner v0.2.11, Node 20, Docker CLI). Runtime smoke test passes 17/17.
- First live registration validated against `git.alwisp.com` (Gitea 1.26.4): instance-level runner registers, comes online, runs healthy, and restart reuses the existing config without creating a duplicate.
### Fixed
- `docker/runner/Dockerfile`: `COPY` paths for the entrypoint, healthcheck, and smoke-test scripts now resolve from the repo-root build context used by `scripts/build-image.sh` (previously assumed the build context was `docker/runner/`, so the build failed at the `COPY` steps).
- `scripts/verify-runtime.sh`: now runs the smoke test via `--entrypoint` so it bypasses the `tini`/bootstrap `ENTRYPOINT` instead of triggering the registration flow and failing on a missing `GITEA_INSTANCE_URL`.
- `docker/runner/docker-entrypoint.sh`: `GITEA_RUNNER_LABELS` is now honored. `act_runner register` ignores its `--labels` flag when a config already exists, so the bootstrap generated a config with default labels and the env var was silently a no-op. The entrypoint now injects the labels into the generated config before registering.
- `scripts/deregister-runner.sh`: rewritten to deregister via the Gitea REST API. The previous version called `act_runner daemon --unregister`, a flag/command that does not exist in act_runner — deregistration must go through `DELETE /api/v1/admin/actions/runners/{id}`. Now takes a runner name or id and an admin API token.
---
<!-- Releases appear below this line, newest first. -->
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Jason Stedwell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+20
View File
@@ -0,0 +1,20 @@
# labels.example.env — runner label configuration reference
#
# Labels tell act_runner which GitHub Actions runner environments to emulate.
# Format: <label>:<scheme>://<image>
# docker:// — run the job step inside the specified container image
# host — run the job step directly on the runner host (no inner container)
#
# Set GITEA_RUNNER_LABELS in .env or docker-compose to one of the examples below.
# ─── Match the ubuntu-latest label used in most GitHub Actions workflows ────
GITEA_RUNNER_LABELS=ubuntu-latest:docker://node:20-bullseye
# ─── Multiple labels (comma-separated) ─────────────────────────────────────
# GITEA_RUNNER_LABELS=ubuntu-latest:docker://node:20-bullseye,ubuntu-22.04:docker://node:20-bullseye,host:host
# ─── Host-only mode (no inner Docker containers) ────────────────────────────
# GITEA_RUNNER_LABELS=host:host
# ─── Custom image on self-hosted registry ───────────────────────────────────
# GITEA_RUNNER_LABELS=ubuntu-latest:docker://registry.alwisp.com/ci/runner-base:latest
+45
View File
@@ -0,0 +1,45 @@
# runner.example.yaml — act_runner configuration template
# Copy to config/runner.yaml and adjust for your environment.
# This file is generated/modified by `act_runner register`. Fields below
# reflect commonly tuned settings; see act_runner docs for the full reference.
log:
# Log verbosity: trace, debug, info, warn, error, fatal
level: info
runner:
# How many jobs this runner can execute concurrently
capacity: 1
# Extra environment variables injected into every job
envs: {}
# Maximum runtime for a single job
timeout: 3h0m0s
# Allow insecure (non-TLS) connections to the Gitea instance
insecure: false
# Fetch interval for new jobs
fetch_timeout: 5s
fetch_interval: 2s
cache:
# Enable the built-in cache server (speeds up actions/cache steps)
enabled: true
dir: ""
host: ""
port: 0
external_server: ""
container:
# Docker network mode for job containers
network: bridge
# Run job containers with --privileged (avoid unless required)
privileged: false
# Additional docker run options (space-separated)
options: ""
# Override parent directory for job workspaces
workdir_parent: ""
# Uncomment to use a custom Docker daemon socket
# docker_host: unix:///var/run/docker.sock
host:
# Override parent directory for host-mode workspaces
workdir_parent: ""
View File
+48
View File
@@ -0,0 +1,48 @@
# docker-compose.example.yml — custom-gitea-runner deployment
# Copy and adjust for your environment. Use pinned image tags in production.
#
# Modes:
# Mode A (pre-generated config): mount config/runner.yaml — set RUNNER_CONFIG_PATH
# Mode B (bootstrap on first run): set GITEA_INSTANCE_URL, GITEA_RUNNER_TOKEN, GITEA_RUNNER_NAME
services:
gitea-runner:
image: registry.alwisp.com/custom-gitea-runner:v0.1.0 # pin your version here
container_name: gitea-runner
restart: unless-stopped
environment:
# ── Required for Mode B (bootstrap registration) ───────────────────────
GITEA_INSTANCE_URL: https://git.example.com
GITEA_RUNNER_NAME: unraid-runner-01
GITEA_RUNNER_TOKEN: ${GITEA_RUNNER_TOKEN} # set in .env file
# ── Optional ──────────────────────────────────────────────────────────
GITEA_RUNNER_LABELS: ubuntu-latest:docker://node:20-bullseye
RUNNER_CONFIG_PATH: /config/runner.yaml
RUNNER_WORKDIR: /work
TZ: America/Chicago
volumes:
# Runner config (persisted across restarts; generated on first boot in Mode B)
- ./config:/config
# Job workspace (writable scratch space for running jobs)
- ./work:/work
# Docker socket — required for docker build/push workflows
# Grants the runner full Docker daemon access. Understand the trust model.
- /var/run/docker.sock:/var/run/docker.sock
# Limit resources to avoid runaway jobs starving the host
deploy:
resources:
limits:
cpus: "4"
memory: 4G
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
+75
View File
@@ -0,0 +1,75 @@
# Unraid Template Notes — custom-gitea-runner
This document covers the recommended Unraid Docker container configuration for the custom-gitea-runner.
---
## Container basics
| Field | Value |
|---|---|
| Repository | `registry.alwisp.com/custom-gitea-runner:v0.1.0` |
| Container name | `gitea-runner` |
| Network type | `Bridge` |
| Privileged | `No` (leave unchecked — socket mount handles Docker access) |
---
## Environment variables
Set these in the "Environment Variables" section of the Unraid container template.
| Variable | Required | Value |
|---|---|---|
| `GITEA_INSTANCE_URL` | Yes | `https://git.example.com` |
| `GITEA_RUNNER_TOKEN` | Yes | Registration token from Gitea admin |
| `GITEA_RUNNER_NAME` | Yes | e.g. `unraid-runner-01` |
| `GITEA_RUNNER_LABELS` | No | `ubuntu-latest:docker://node:20-bullseye` |
| `RUNNER_CONFIG_PATH` | No | `/config/runner.yaml` |
| `RUNNER_WORKDIR` | No | `/work` |
| `TZ` | No | `America/Chicago` |
---
## Volume mounts
| Container path | Host path | Notes |
|---|---|---|
| `/config` | `/mnt/user/appdata/gitea-runner/config` | Config persistence. Generated on first boot. |
| `/work` | `/mnt/user/appdata/gitea-runner/work` | Job workspace. Can be cleaned between runs. |
| `/var/run/docker.sock` | `/var/run/docker.sock` | Docker socket. Required for build/push workflows. |
---
## Post-install checklist
1. Set all required environment variables.
2. Start the container.
3. Check logs — look for `[BOOTSTRAP] Registration complete` or `[START] Starting act_runner daemon`.
4. In Gitea admin panel, verify the runner appears under **Admin → Runners**.
5. Trigger a test workflow and confirm job pickup.
---
## Update workflow
1. Pull the new image tag from the registry.
2. In Unraid, update the Repository field to the new pinned tag.
3. Stop → Start the container (Unraid recreates from the new image).
4. Confirm registration still valid in Gitea admin panel.
5. Run a smoke workflow.
---
## Rollback workflow
1. Stop the container.
2. Change the Repository field back to the prior known-good tag.
3. Start the container.
4. Verify registration and smoke workflow.
---
## Notes on Docker socket
Mounting `/var/run/docker.sock` gives the runner access to the host Docker daemon. This is intentional for build/push workflows. Understand this means jobs running on this runner can interact with all containers on the Unraid host. For a self-owned, trusted environment this is acceptable. Future hardening options are documented in `docs/architecture.md`.
+83
View File
@@ -0,0 +1,83 @@
# syntax=docker/dockerfile:1
# custom-gitea-runner — self-owned Gitea Actions runner
# All dependencies installed at build time; no runtime apt-get.
ARG UBUNTU_VERSION=22.04
FROM ubuntu:${UBUNTU_VERSION}
ARG ACT_RUNNER_VERSION=0.2.11
ARG NODE_MAJOR=20
ARG BUILD_DATE
ARG VCS_REF
ARG VERSION=dev
LABEL org.opencontainers.image.title="custom-gitea-runner" \
org.opencontainers.image.description="Self-owned Gitea Actions runner with bundled tooling" \
org.opencontainers.image.version="${VERSION}" \
org.opencontainers.image.created="${BUILD_DATE}" \
org.opencontainers.image.revision="${VCS_REF}"
ENV DEBIAN_FRONTEND=noninteractive \
IMAGE_VERSION="${VERSION}" \
GIT_REVISION="${VCS_REF}" \
RUNNER_CONFIG_PATH=/config/runner.yaml \
RUNNER_WORKDIR=/work
# ─── Base packages ────────────────────────────────────────────────────────────
RUN apt-get update && apt-get install -y --no-install-recommends \
bash \
ca-certificates \
curl \
git \
gnupg \
jq \
tini \
tzdata \
&& rm -rf /var/lib/apt/lists/*
# ─── Node.js (NodeSource — Ubuntu 22.04 default is 12.x, too old) ─────────────
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/*
# ─── Docker CLI ───────────────────────────────────────────────────────────────
RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
| gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
> /etc/apt/sources.list.d/docker.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends docker-ce-cli \
&& rm -rf /var/lib/apt/lists/*
# ─── act_runner binary (pinned) ───────────────────────────────────────────────
# To update: bump ACT_RUNNER_VERSION in the build command or here.
# Releases: https://gitea.com/gitea/act_runner/releases
RUN ARCH=$(dpkg --print-architecture) \
&& curl -fsSL \
"https://gitea.com/gitea/act_runner/releases/download/v${ACT_RUNNER_VERSION}/act_runner-${ACT_RUNNER_VERSION}-linux-${ARCH}" \
-o /usr/local/bin/act_runner \
&& chmod +x /usr/local/bin/act_runner
# ─── Runtime directories and scripts ─────────────────────────────────────────
RUN mkdir -p /config /work /config/trusted-ca
COPY docker/runner/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
COPY docker/runner/healthcheck.sh /usr/local/bin/healthcheck.sh
COPY docker/runner/smoke-test.sh /usr/local/bin/smoke-test.sh
RUN chmod +x \
/usr/local/bin/docker-entrypoint.sh \
/usr/local/bin/healthcheck.sh \
/usr/local/bin/smoke-test.sh
VOLUME ["/config", "/work"]
HEALTHCHECK \
--interval=30s \
--timeout=10s \
--start-period=60s \
--retries=3 \
CMD ["/usr/local/bin/healthcheck.sh"]
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/docker-entrypoint.sh"]
+139
View File
@@ -0,0 +1,139 @@
#!/usr/bin/env bash
# docker-entrypoint.sh — custom-gitea-runner bootstrap and daemon startup
# Runs as PID 1 via tini. Handles validation, optional CA trust, registration, and daemon start.
set -euo pipefail
RUNNER_CONFIG_PATH="${RUNNER_CONFIG_PATH:-/config/runner.yaml}"
RUNNER_WORKDIR="${RUNNER_WORKDIR:-/work}"
# ─── Banner ───────────────────────────────────────────────────────────────────
print_banner() {
echo "======================================================"
echo " custom-gitea-runner"
echo " Image version : ${IMAGE_VERSION:-unknown}"
echo " Git revision : ${GIT_REVISION:-unknown}"
echo "======================================================"
echo "Runtime:"
echo " act_runner : $(act_runner --version 2>&1 | head -1 || echo 'unknown')"
echo " node : $(node --version 2>&1 || echo 'unknown')"
echo " npm : $(npm --version 2>&1 || echo 'unknown')"
echo " git : $(git --version 2>&1 | awk '{print $3}' || echo 'unknown')"
echo " docker : $(docker --version 2>&1 | awk '{print $3}' | tr -d ',' || echo 'unavailable')"
echo "Config:"
echo " Config path : ${RUNNER_CONFIG_PATH}"
echo " Work dir : ${RUNNER_WORKDIR}"
echo " Gitea URL : ${GITEA_INSTANCE_URL:-[not set]}"
echo " Runner name : ${GITEA_RUNNER_NAME:-[not set]}"
echo " Labels : ${GITEA_RUNNER_LABELS:-[not set]}"
echo "======================================================"
}
# ─── Binary validation ────────────────────────────────────────────────────────
validate_binaries() {
local missing=0
for bin in act_runner git node npm; do
if ! command -v "$bin" &>/dev/null; then
echo "[ERROR] Required binary not found: ${bin}"
missing=1
fi
done
if [ "$missing" -ne 0 ]; then
echo "[FATAL] Missing required binaries. This is an image build issue."
exit 1
fi
echo "[OK] All required binaries present."
}
# ─── Custom CA trust (optional) ───────────────────────────────────────────────
install_custom_ca() {
local ca_dir="/config/trusted-ca"
if [ -d "$ca_dir" ] && compgen -G "${ca_dir}/*.crt" >/dev/null 2>&1; then
echo "[CA] Installing custom CA certificates from ${ca_dir} ..."
cp "${ca_dir}"/*.crt /usr/local/share/ca-certificates/ 2>/dev/null || true
update-ca-certificates --fresh 2>&1 | grep -v "^$" || true
echo "[CA] Custom CA trust updated."
fi
}
# ─── Registration (Mode B: bootstrap from env vars) ───────────────────────────
require_env() {
local var="$1"
if [ -z "${!var:-}" ]; then
echo "[FATAL] Required environment variable not set: ${var}"
exit 1
fi
}
# Rewrite the `labels:` list in the runner config. act_runner reads labels
# from the config file at registration time and IGNORES the `register --labels`
# flag whenever the config already exists — so to honor GITEA_RUNNER_LABELS we
# must edit the generated config before registering.
apply_labels() {
local labels_csv="$1" config="$2" tmp
echo "[BOOTSTRAP] Applying labels: ${labels_csv}"
tmp="$(mktemp)"
awk -v csv="${labels_csv}" '
BEGIN { n = split(csv, arr, ",") }
/^[[:space:]]+labels:[[:space:]]*$/ {
print
for (i = 1; i <= n; i++) {
gsub(/^[ \t]+|[ \t]+$/, "", arr[i])
printf " - \"%s\"\n", arr[i]
}
inlabels = 1
next
}
inlabels == 1 {
if ($0 ~ /^[[:space:]]+-[[:space:]]/) { next }
inlabels = 0
}
{ print }
' "${config}" > "${tmp}" && mv "${tmp}" "${config}"
}
register_runner() {
echo "[BOOTSTRAP] No config found at ${RUNNER_CONFIG_PATH}. Initiating registration."
require_env GITEA_INSTANCE_URL
require_env GITEA_RUNNER_TOKEN
require_env GITEA_RUNNER_NAME
mkdir -p "$(dirname "${RUNNER_CONFIG_PATH}")"
# Generate the default config template (includes cache/container defaults),
# then inject the operator's labels into it before registering.
act_runner generate-config > "${RUNNER_CONFIG_PATH}"
local labels="${GITEA_RUNNER_LABELS:-ubuntu-latest:docker://node:20-bullseye}"
apply_labels "${labels}" "${RUNNER_CONFIG_PATH}"
act_runner register \
--config "${RUNNER_CONFIG_PATH}" \
--instance "${GITEA_INSTANCE_URL}" \
--token "${GITEA_RUNNER_TOKEN}" \
--name "${GITEA_RUNNER_NAME}" \
--no-interactive
echo "[BOOTSTRAP] Registration complete. Config at ${RUNNER_CONFIG_PATH}."
}
# ─── Main ─────────────────────────────────────────────────────────────────────
print_banner
validate_binaries
install_custom_ca
mkdir -p "${RUNNER_WORKDIR}"
if [ ! -f "${RUNNER_CONFIG_PATH}" ]; then
register_runner
else
echo "[INFO] Existing config found at ${RUNNER_CONFIG_PATH}. Skipping registration."
fi
echo "[START] Starting act_runner daemon ..."
exec act_runner daemon --config "${RUNNER_CONFIG_PATH}"
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
# healthcheck.sh — reports runner health for Docker HEALTHCHECK instruction
set -euo pipefail
RUNNER_CONFIG_PATH="${RUNNER_CONFIG_PATH:-/config/runner.yaml}"
exit_code=0
check_ok() { echo "[HEALTH] OK : $1"; }
check_warn() { echo "[HEALTH] WARN : $1"; }
check_fail() { echo "[HEALTH] FAIL : $1"; exit_code=1; }
# Runner process
if pgrep -x act_runner > /dev/null 2>&1; then
check_ok "act_runner process is running"
else
check_fail "act_runner process not found"
fi
# Config file
if [ -f "${RUNNER_CONFIG_PATH}" ]; then
check_ok "Config file present at ${RUNNER_CONFIG_PATH}"
else
check_warn "Config file not found at ${RUNNER_CONFIG_PATH}"
fi
# Required binaries
for bin in act_runner git node npm; do
if command -v "$bin" &>/dev/null; then
check_ok "Binary present: ${bin}"
else
check_fail "Binary missing: ${bin}"
fi
done
# Docker socket (warn only — some label configurations don't need it)
if [ -S /var/run/docker.sock ]; then
if docker info &>/dev/null 2>&1; then
check_ok "Docker socket accessible"
else
check_warn "Docker socket present but not accessible (permission issue?)"
fi
else
check_warn "Docker socket not mounted — Docker build/push workflows will fail"
fi
exit $exit_code
+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env bash
# smoke-test.sh — quick runtime self-test for the runner image
# Run inside the container to validate the tool chain before production use.
set -euo pipefail
PASS=0
FAIL=0
ok() { echo "[SMOKE] PASS : $1"; PASS=$((PASS + 1)); }
fail() { echo "[SMOKE] FAIL : $1"; FAIL=$((FAIL + 1)); }
echo "======================================"
echo " custom-gitea-runner smoke test"
echo "======================================"
# Binary presence and basic execution
for bin in bash curl git jq node npm act_runner tini docker; do
if command -v "$bin" &>/dev/null; then
ok "${bin} found: $(command -v "$bin")"
else
fail "${bin} not found"
fi
done
# Version output checks
echo "--- versions ---"
git --version && ok "git --version" || fail "git --version failed"
node --version && ok "node --version" || fail "node --version failed"
npm --version && ok "npm --version" || fail "npm --version failed"
act_runner --version 2>&1 | head -1 && ok "act_runner --version" || fail "act_runner --version failed"
docker --version && ok "docker --version" || fail "docker --version failed"
# Node.js can run a basic script
if node -e "process.exit(0)" 2>/dev/null; then
ok "node can execute scripts"
else
fail "node cannot execute scripts"
fi
# jq parses JSON
if echo '{"test":1}' | jq .test > /dev/null 2>&1; then
ok "jq parses JSON"
else
fail "jq cannot parse JSON"
fi
# curl is usable (don't actually make a request in smoke test)
if curl --version &>/dev/null; then
ok "curl is usable"
else
fail "curl is not usable"
fi
echo "======================================"
echo " Results: ${PASS} passed, ${FAIL} failed"
echo "======================================"
[ "$FAIL" -eq 0 ] && exit 0 || exit 1
+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.
+54 -44
View File
@@ -27,7 +27,7 @@ The project is considered successful when it can:
## Phase 0 — Research and design
**Status:** Active
**Status:** Complete
### Goals
@@ -40,13 +40,13 @@ The project is considered successful when it can:
- [x] Write `AGENT.md`
- [x] Write `README.md`
- [x] Write `ROADMAP.md`
- [ ] Create `CHANGELOG.md`
- [ ] Create `.env.example`
- [ ] Create ADR template under `docs/decisions/`
- [ ] Write `docs/architecture.md`
- [ ] Decide base image strategy
- [ ] Decide socket-mount vs DinD position for v1
- [ ] Decide registration mode defaults
- [x] Create `CHANGELOG.md`
- [x] Create `.env.example`
- [x] Create ADR template under `docs/decisions/`
- [x] Write `docs/architecture.md`
- [x] Decide base image strategy
- [x] Decide socket-mount vs DinD position for v1
- [x] Decide registration mode defaults
### Exit criteria
@@ -59,7 +59,7 @@ The project is considered successful when it can:
## Phase 1 — Base image and bootstrap
**Status:** Planned
**Status:** Complete
### Goals
@@ -69,24 +69,29 @@ The project is considered successful when it can:
### Tasks
- [ ] Create `docker/runner/Dockerfile`
- [ ] Pin base image tag
- [ ] Pin `act_runner` source/version
- [ ] Install required dependencies at build time:
- [ ] bash
- [ ] curl
- [ ] git
- [ ] jq
- [ ] nodejs
- [ ] npm
- [ ] docker CLI
- [ ] ca-certificates
- [ ] tini
- [ ] tzdata
- [ ] Create `docker/runner/docker-entrypoint.sh`
- [ ] Create `docker/runner/healthcheck.sh`
- [ ] Add image metadata labels
- [ ] Verify startup prints tool versions
- [x] Create `docker/runner/Dockerfile`
- [x] Pin base image tag
- [x] Pin `act_runner` source/version
- [x] Install required dependencies at build time:
- [x] bash
- [x] curl
- [x] git
- [x] jq
- [x] nodejs
- [x] npm
- [x] docker CLI
- [x] ca-certificates
- [x] tini
- [x] tzdata
- [x] Create `docker/runner/docker-entrypoint.sh`
- [x] Create `docker/runner/healthcheck.sh`
- [x] Add image metadata labels
- [x] Verify startup prints tool versions
### Notes
- First successful build: `custom-gitea-runner:v0.1.0` (act_runner v0.2.11, node v20.20.2, npm 10.8.2, git 2.34.1, docker CLI 29.6.1).
- Runtime smoke test passes 17/17 via `./scripts/verify-runtime.sh`.
### Exit criteria
@@ -99,7 +104,7 @@ The project is considered successful when it can:
## Phase 2 — Registration and config flows
**Status:** Planned
**Status:** Complete
### Goals
@@ -109,15 +114,15 @@ The project is considered successful when it can:
### Tasks
- [ ] Create `config/runner.example.yaml`
- [ ] Create `config/labels.example.env`
- [ ] Create `scripts/register-runner.sh`
- [ ] Create `scripts/deregister-runner.sh`
- [ ] Add config path validation
- [ ] Add first-boot registration flow
- [ ] Add re-run safe bootstrap logic
- [ ] Document required environment variables
- [ ] Validate restart behavior with existing config
- [x] Create `config/runner.example.yaml`
- [x] Create `config/labels.example.env`
- [x] Create `scripts/register-runner.sh`
- [x] Create `scripts/deregister-runner.sh`
- [x] Add config path validation
- [x] Add first-boot registration flow
- [x] Add re-run safe bootstrap logic
- [x] Document required environment variables
- [x] Validate restart behavior with existing config
### Exit criteria
@@ -125,6 +130,11 @@ The project is considered successful when it can:
- Existing config is reused without duplicate registration.
- Missing configuration fails fast with clear logs.
### Notes
- Validated live against `git.alwisp.com` (Gitea 1.26.4): runner `forgerunner-01` registered as instance-level runner (id 5), came online and healthy, and a container restart reused the existing config with no duplicate registration.
- Registration token obtained via `POST /api/v1/admin/actions/runners/registration-token`; deregistration via `DELETE /api/v1/admin/actions/runners/{id}` (act_runner has no unregister command).
---
## Phase 3 — Docker workflow support
@@ -167,13 +177,13 @@ The project is considered successful when it can:
### Tasks
- [ ] Create `docker/runner/smoke-test.sh`
- [ ] Create `scripts/verify-runtime.sh`
- [ ] Create `scripts/test-local-runner.sh`
- [ ] Add `test/sample-workflows/checkout.yml`
- [ ] Add `test/sample-workflows/node-action.yml`
- [ ] Add `test/sample-workflows/docker-login.yml`
- [ ] Add `test/sample-workflows/docker-build-push.yml`
- [x] Create `docker/runner/smoke-test.sh`
- [x] Create `scripts/verify-runtime.sh`
- [x] Create `scripts/test-local-runner.sh`
- [x] Add `test/sample-workflows/checkout.yml`
- [x] Add `test/sample-workflows/node-action.yml`
- [x] Add `test/sample-workflows/docker-login.yml`
- [x] Add `test/sample-workflows/docker-build-push.yml`
- [ ] Add regression test cases for:
- [ ] missing `node`
- [ ] broken Docker socket access
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# build-image.sh — build the custom-gitea-runner Docker image locally
set -euo pipefail
IMAGE_NAME="${IMAGE_NAME:-custom-gitea-runner}"
VERSION="${VERSION:-dev}"
VCS_REF="$(git rev-parse --short HEAD 2>/dev/null || echo 'unknown')"
BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "Building ${IMAGE_NAME}:${VERSION} ..."
echo " VCS_REF : ${VCS_REF}"
echo " BUILD_DATE : ${BUILD_DATE}"
docker build \
--build-arg "VERSION=${VERSION}" \
--build-arg "VCS_REF=${VCS_REF}" \
--build-arg "BUILD_DATE=${BUILD_DATE}" \
--tag "${IMAGE_NAME}:${VERSION}" \
--tag "${IMAGE_NAME}:latest" \
--file docker/runner/Dockerfile \
.
echo "Build complete: ${IMAGE_NAME}:${VERSION}"
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# deregister-runner.sh — remove a runner registration from Gitea via the API.
#
# act_runner has no "unregister" command, so deregistration is done through the
# Gitea REST API. This requires an *API* token (not a registration token) with
# admin rights, plus the runner id or name. Run this before retiring or renaming
# a runner to keep the Gitea UI clean, then delete the local config to start fresh.
#
# Usage:
# GITEA_INSTANCE_URL=https://git.example.com \
# GITEA_API_TOKEN=<admin-api-token> \
# ./scripts/deregister-runner.sh <runner-name|runner-id>
set -euo pipefail
: "${GITEA_INSTANCE_URL:?GITEA_INSTANCE_URL must be set (e.g. https://git.example.com)}"
: "${GITEA_API_TOKEN:?GITEA_API_TOKEN must be set (an admin API token, not a registration token)}"
TARGET="${1:-}"
if [ -z "${TARGET}" ]; then
echo "[FATAL] Provide the runner name or id as the first argument." >&2
echo "Usage: $0 <runner-name|runner-id>" >&2
exit 1
fi
API="${GITEA_INSTANCE_URL%/}/api/v1"
AUTH="Authorization: token ${GITEA_API_TOKEN}"
# Resolve a runner name to an id if a non-numeric target was given.
if [[ "${TARGET}" =~ ^[0-9]+$ ]]; then
RUNNER_ID="${TARGET}"
else
echo "Resolving runner id for name '${TARGET}' ..."
RUNNER_ID="$(curl -fsS -H "${AUTH}" "${API}/admin/actions/runners" \
| jq -r --arg n "${TARGET}" '.runners[] | select(.name==$n) | .id' | head -1)"
if [ -z "${RUNNER_ID}" ]; then
echo "[FATAL] No runner found with name '${TARGET}'." >&2
exit 1
fi
fi
echo "Deregistering runner id ${RUNNER_ID} from ${GITEA_INSTANCE_URL} ..."
http_code="$(curl -fsS -o /dev/null -w '%{http_code}' \
-X DELETE -H "${AUTH}" "${API}/admin/actions/runners/${RUNNER_ID}")"
if [ "${http_code}" = "204" ]; then
echo "Deregistration complete (runner id ${RUNNER_ID} removed)."
echo "Remove the local config to start fresh: rm config/runner.yaml"
else
echo "[FATAL] Unexpected HTTP status ${http_code} from delete request." >&2
exit 1
fi
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# publish-image.sh — tag and push the runner image to a registry
set -euo pipefail
IMAGE_NAME="${IMAGE_NAME:-custom-gitea-runner}"
REGISTRY="${REGISTRY:-registry.alwisp.com}"
VERSION="${VERSION:-}"
if [ -z "$VERSION" ]; then
echo "[ERROR] VERSION must be set (e.g. VERSION=v0.1.0 ./scripts/publish-image.sh)"
exit 1
fi
LOCAL_TAG="${IMAGE_NAME}:${VERSION}"
REMOTE_TAG="${REGISTRY}/${IMAGE_NAME}:${VERSION}"
REMOTE_LATEST="${REGISTRY}/${IMAGE_NAME}:latest"
echo "Tagging ${LOCAL_TAG} as ${REMOTE_TAG} ..."
docker tag "${LOCAL_TAG}" "${REMOTE_TAG}"
docker tag "${LOCAL_TAG}" "${REMOTE_LATEST}"
echo "Pushing ${REMOTE_TAG} ..."
docker push "${REMOTE_TAG}"
docker push "${REMOTE_LATEST}"
echo "Published: ${REMOTE_TAG}"
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# register-runner.sh — standalone registration helper (run outside container)
# Useful for pre-generating config before first container start.
set -euo pipefail
: "${GITEA_INSTANCE_URL:?GITEA_INSTANCE_URL must be set}"
: "${GITEA_RUNNER_TOKEN:?GITEA_RUNNER_TOKEN must be set}"
: "${GITEA_RUNNER_NAME:?GITEA_RUNNER_NAME must be set}"
RUNNER_CONFIG_PATH="${RUNNER_CONFIG_PATH:-./config/runner.yaml}"
GITEA_RUNNER_LABELS="${GITEA_RUNNER_LABELS:-ubuntu-latest:docker://node:20-bullseye}"
echo "Registering runner: ${GITEA_RUNNER_NAME}"
echo " Instance : ${GITEA_INSTANCE_URL}"
echo " Config : ${RUNNER_CONFIG_PATH}"
echo " Labels : ${GITEA_RUNNER_LABELS}"
mkdir -p "$(dirname "${RUNNER_CONFIG_PATH}")"
docker run --rm \
-v "$(pwd)/config:/config" \
-e GITEA_INSTANCE_URL="${GITEA_INSTANCE_URL}" \
-e GITEA_RUNNER_TOKEN="${GITEA_RUNNER_TOKEN}" \
-e GITEA_RUNNER_NAME="${GITEA_RUNNER_NAME}" \
-e GITEA_RUNNER_LABELS="${GITEA_RUNNER_LABELS}" \
-e RUNNER_CONFIG_PATH="/config/runner.yaml" \
custom-gitea-runner:latest \
act_runner register \
--config /config/runner.yaml \
--instance "${GITEA_INSTANCE_URL}" \
--token "${GITEA_RUNNER_TOKEN}" \
--name "${GITEA_RUNNER_NAME}" \
--labels "${GITEA_RUNNER_LABELS}" \
--no-interactive
echo "Registration complete. Config written to ${RUNNER_CONFIG_PATH}."
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# test-local-runner.sh — start a short-lived runner for local workflow testing
# Requires a running Gitea instance and a valid registration token.
set -euo pipefail
: "${GITEA_INSTANCE_URL:?GITEA_INSTANCE_URL must be set}"
: "${GITEA_RUNNER_TOKEN:?GITEA_RUNNER_TOKEN must be set}"
: "${GITEA_RUNNER_NAME:?GITEA_RUNNER_NAME must be set}"
IMAGE="${IMAGE:-custom-gitea-runner:latest}"
CONFIG_DIR="$(mktemp -d /tmp/runner-test-config.XXXXXX)"
WORK_DIR="$(mktemp -d /tmp/runner-test-work.XXXXXX)"
cleanup() {
echo "Cleaning up temp dirs ..."
rm -rf "$CONFIG_DIR" "$WORK_DIR"
}
trap cleanup EXIT
echo "Starting local test runner ..."
echo " Image : ${IMAGE}"
echo " Config : ${CONFIG_DIR}"
echo " Work : ${WORK_DIR}"
docker run --rm \
-v "${CONFIG_DIR}:/config" \
-v "${WORK_DIR}:/work" \
-v /var/run/docker.sock:/var/run/docker.sock \
-e GITEA_INSTANCE_URL="${GITEA_INSTANCE_URL}" \
-e GITEA_RUNNER_TOKEN="${GITEA_RUNNER_TOKEN}" \
-e GITEA_RUNNER_NAME="${GITEA_RUNNER_NAME}" \
-e GITEA_RUNNER_LABELS="${GITEA_RUNNER_LABELS:-ubuntu-latest:docker://node:20-bullseye}" \
"${IMAGE}"
+11
View File
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
# verify-runtime.sh — validate the runner image tool chain without starting the daemon
set -euo pipefail
IMAGE="${1:-custom-gitea-runner:latest}"
echo "Running smoke test against image: ${IMAGE}"
docker run --rm \
--entrypoint /usr/local/bin/smoke-test.sh \
"${IMAGE}"
View File
View File
+17
View File
@@ -0,0 +1,17 @@
name: Smoke — Checkout
on:
workflow_dispatch:
jobs:
checkout:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Verify working directory
run: |
echo "Repo: $(pwd)"
ls -la
git log --oneline -5
@@ -0,0 +1,31 @@
name: Smoke — Docker Build and Push
on:
push:
branches: [main]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Log in to Gitea Container Registry
uses: docker/login-action@v3
with:
registry: registry.alwisp.com
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Build and Push
run: |
docker build \
-t registry.alwisp.com/${{ gitea.repository_owner }}/${{ gitea.repository }}:latest \
-t registry.alwisp.com/${{ gitea.repository_owner }}/${{ gitea.repository }}:${{ gitea.sha }} \
-f docker/runner/Dockerfile \
.
docker push registry.alwisp.com/${{ gitea.repository_owner }}/${{ gitea.repository }}:latest
docker push registry.alwisp.com/${{ gitea.repository_owner }}/${{ gitea.repository }}:${{ gitea.sha }}
+18
View File
@@ -0,0 +1,18 @@
name: Smoke — Docker Login
on:
workflow_dispatch:
jobs:
login:
runs-on: ubuntu-latest
steps:
- name: Log in to Gitea Container Registry
uses: docker/login-action@v3
with:
registry: registry.alwisp.com
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Confirm Docker login
run: docker info
+19
View File
@@ -0,0 +1,19 @@
name: Smoke — Node Action
on:
workflow_dispatch:
jobs:
node:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Print Node version
run: |
node --version
npm --version
- name: Run inline Node script
run: node -e "console.log('Node.js works:', process.version)"
View File