ver 0.1
This commit is contained in:
@@ -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
@@ -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
|
||||||
@@ -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. -->
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
@@ -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: ""
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
@@ -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
@@ -27,7 +27,7 @@ The project is considered successful when it can:
|
|||||||
|
|
||||||
## Phase 0 — Research and design
|
## Phase 0 — Research and design
|
||||||
|
|
||||||
**Status:** Active
|
**Status:** Complete
|
||||||
|
|
||||||
### Goals
|
### Goals
|
||||||
|
|
||||||
@@ -40,13 +40,13 @@ The project is considered successful when it can:
|
|||||||
- [x] Write `AGENT.md`
|
- [x] Write `AGENT.md`
|
||||||
- [x] Write `README.md`
|
- [x] Write `README.md`
|
||||||
- [x] Write `ROADMAP.md`
|
- [x] Write `ROADMAP.md`
|
||||||
- [ ] Create `CHANGELOG.md`
|
- [x] Create `CHANGELOG.md`
|
||||||
- [ ] Create `.env.example`
|
- [x] Create `.env.example`
|
||||||
- [ ] Create ADR template under `docs/decisions/`
|
- [x] Create ADR template under `docs/decisions/`
|
||||||
- [ ] Write `docs/architecture.md`
|
- [x] Write `docs/architecture.md`
|
||||||
- [ ] Decide base image strategy
|
- [x] Decide base image strategy
|
||||||
- [ ] Decide socket-mount vs DinD position for v1
|
- [x] Decide socket-mount vs DinD position for v1
|
||||||
- [ ] Decide registration mode defaults
|
- [x] Decide registration mode defaults
|
||||||
|
|
||||||
### Exit criteria
|
### Exit criteria
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ The project is considered successful when it can:
|
|||||||
|
|
||||||
## Phase 1 — Base image and bootstrap
|
## Phase 1 — Base image and bootstrap
|
||||||
|
|
||||||
**Status:** Planned
|
**Status:** Complete
|
||||||
|
|
||||||
### Goals
|
### Goals
|
||||||
|
|
||||||
@@ -69,24 +69,29 @@ The project is considered successful when it can:
|
|||||||
|
|
||||||
### Tasks
|
### Tasks
|
||||||
|
|
||||||
- [ ] Create `docker/runner/Dockerfile`
|
- [x] Create `docker/runner/Dockerfile`
|
||||||
- [ ] Pin base image tag
|
- [x] Pin base image tag
|
||||||
- [ ] Pin `act_runner` source/version
|
- [x] Pin `act_runner` source/version
|
||||||
- [ ] Install required dependencies at build time:
|
- [x] Install required dependencies at build time:
|
||||||
- [ ] bash
|
- [x] bash
|
||||||
- [ ] curl
|
- [x] curl
|
||||||
- [ ] git
|
- [x] git
|
||||||
- [ ] jq
|
- [x] jq
|
||||||
- [ ] nodejs
|
- [x] nodejs
|
||||||
- [ ] npm
|
- [x] npm
|
||||||
- [ ] docker CLI
|
- [x] docker CLI
|
||||||
- [ ] ca-certificates
|
- [x] ca-certificates
|
||||||
- [ ] tini
|
- [x] tini
|
||||||
- [ ] tzdata
|
- [x] tzdata
|
||||||
- [ ] Create `docker/runner/docker-entrypoint.sh`
|
- [x] Create `docker/runner/docker-entrypoint.sh`
|
||||||
- [ ] Create `docker/runner/healthcheck.sh`
|
- [x] Create `docker/runner/healthcheck.sh`
|
||||||
- [ ] Add image metadata labels
|
- [x] Add image metadata labels
|
||||||
- [ ] Verify startup prints tool versions
|
- [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
|
### Exit criteria
|
||||||
|
|
||||||
@@ -99,7 +104,7 @@ The project is considered successful when it can:
|
|||||||
|
|
||||||
## Phase 2 — Registration and config flows
|
## Phase 2 — Registration and config flows
|
||||||
|
|
||||||
**Status:** Planned
|
**Status:** Complete
|
||||||
|
|
||||||
### Goals
|
### Goals
|
||||||
|
|
||||||
@@ -109,15 +114,15 @@ The project is considered successful when it can:
|
|||||||
|
|
||||||
### Tasks
|
### Tasks
|
||||||
|
|
||||||
- [ ] Create `config/runner.example.yaml`
|
- [x] Create `config/runner.example.yaml`
|
||||||
- [ ] Create `config/labels.example.env`
|
- [x] Create `config/labels.example.env`
|
||||||
- [ ] Create `scripts/register-runner.sh`
|
- [x] Create `scripts/register-runner.sh`
|
||||||
- [ ] Create `scripts/deregister-runner.sh`
|
- [x] Create `scripts/deregister-runner.sh`
|
||||||
- [ ] Add config path validation
|
- [x] Add config path validation
|
||||||
- [ ] Add first-boot registration flow
|
- [x] Add first-boot registration flow
|
||||||
- [ ] Add re-run safe bootstrap logic
|
- [x] Add re-run safe bootstrap logic
|
||||||
- [ ] Document required environment variables
|
- [x] Document required environment variables
|
||||||
- [ ] Validate restart behavior with existing config
|
- [x] Validate restart behavior with existing config
|
||||||
|
|
||||||
### Exit criteria
|
### Exit criteria
|
||||||
|
|
||||||
@@ -125,6 +130,11 @@ The project is considered successful when it can:
|
|||||||
- Existing config is reused without duplicate registration.
|
- Existing config is reused without duplicate registration.
|
||||||
- Missing configuration fails fast with clear logs.
|
- 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
|
## Phase 3 — Docker workflow support
|
||||||
@@ -167,13 +177,13 @@ The project is considered successful when it can:
|
|||||||
|
|
||||||
### Tasks
|
### Tasks
|
||||||
|
|
||||||
- [ ] Create `docker/runner/smoke-test.sh`
|
- [x] Create `docker/runner/smoke-test.sh`
|
||||||
- [ ] Create `scripts/verify-runtime.sh`
|
- [x] Create `scripts/verify-runtime.sh`
|
||||||
- [ ] Create `scripts/test-local-runner.sh`
|
- [x] Create `scripts/test-local-runner.sh`
|
||||||
- [ ] Add `test/sample-workflows/checkout.yml`
|
- [x] Add `test/sample-workflows/checkout.yml`
|
||||||
- [ ] Add `test/sample-workflows/node-action.yml`
|
- [x] Add `test/sample-workflows/node-action.yml`
|
||||||
- [ ] Add `test/sample-workflows/docker-login.yml`
|
- [x] Add `test/sample-workflows/docker-login.yml`
|
||||||
- [ ] Add `test/sample-workflows/docker-build-push.yml`
|
- [x] Add `test/sample-workflows/docker-build-push.yml`
|
||||||
- [ ] Add regression test cases for:
|
- [ ] Add regression test cases for:
|
||||||
- [ ] missing `node`
|
- [ ] missing `node`
|
||||||
- [ ] broken Docker socket access
|
- [ ] broken Docker socket access
|
||||||
|
|||||||
Executable
+23
@@ -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}"
|
||||||
Executable
+51
@@ -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
|
||||||
Executable
+26
@@ -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}"
|
||||||
Executable
+36
@@ -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}."
|
||||||
Executable
+33
@@ -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}"
|
||||||
Executable
+11
@@ -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}"
|
||||||
Vendored
@@ -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 }}
|
||||||
@@ -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
|
||||||
@@ -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)"
|
||||||
Reference in New Issue
Block a user