ver 0.1
This commit is contained in:
@@ -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"
|
||||
@@ -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`.
|
||||
@@ -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"]
|
||||
Executable
+139
@@ -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}"
|
||||
Executable
+46
@@ -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
|
||||
Executable
+58
@@ -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
|
||||
Reference in New Issue
Block a user