From 008533491ff073bef490417199d546cd1b7852fb Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 25 Jan 2026 17:21:29 -0800 Subject: [PATCH 01/15] Add containerized forgejo-runner for Phase 1 ratcheting Part of the runner ratcheting plan to migrate from host-mode to k8s runners. - Debian-based image with forgejo-runner and Docker CLI - Mounts Docker socket for container builds - Auto-registers on first start - Host networking for access to *.ops.eblu.me services Co-Authored-By: Claude Opus 4.5 --- containers/forgejo-runner/Dockerfile | 63 +++++++++++++++++++++++++ containers/forgejo-runner/config.yaml | 25 ++++++++++ containers/forgejo-runner/entrypoint.sh | 36 ++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 containers/forgejo-runner/Dockerfile create mode 100644 containers/forgejo-runner/config.yaml create mode 100644 containers/forgejo-runner/entrypoint.sh diff --git a/containers/forgejo-runner/Dockerfile b/containers/forgejo-runner/Dockerfile new file mode 100644 index 0000000..03b3349 --- /dev/null +++ b/containers/forgejo-runner/Dockerfile @@ -0,0 +1,63 @@ +# Forgejo Actions Runner - Containerized +# +# A containerized runner capable of building containers via Docker socket mount. +# Part of the runner ratcheting plan (Phase 1+). +# +# Build: +# docker build -t registry.ops.eblu.me/blumeops/forgejo-runner:v1.0.0 . +# +# Run (Phase 1 - Docker on indri): +# docker run -d \ +# --name forgejo-runner \ +# -v /var/run/docker.sock:/var/run/docker.sock \ +# -e FORGEJO_URL=https://forge.ops.eblu.me \ +# -e RUNNER_TOKEN= \ +# -e RUNNER_NAME=indri-docker-runner \ +# registry.ops.eblu.me/blumeops/forgejo-runner:v1.0.0 +# +# The runner registers itself on first start and persists state in /data. + +FROM debian:bookworm-slim + +# Forgejo runner version - check https://code.forgejo.org/forgejo/runner/releases +ARG RUNNER_VERSION=6.3.1 +ARG TARGETARCH + +# Install base dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + git \ + jq \ + gnupg \ + lsb-release \ + && rm -rf /var/lib/apt/lists/* + +# Install Docker CLI (not daemon - we mount the socket) +RUN install -m 0755 -d /etc/apt/keyrings \ + && curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc \ + && chmod a+r /etc/apt/keyrings/docker.asc \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $(lsb_release -cs) 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/* + +# Install forgejo-runner +RUN ARCH=$(case "${TARGETARCH}" in "amd64") echo "amd64";; "arm64") echo "arm64";; *) echo "amd64";; esac) \ + && curl -fsSL "https://code.forgejo.org/forgejo/runner/releases/download/v${RUNNER_VERSION}/forgejo-runner-${RUNNER_VERSION}-linux-${ARCH}.xz" -o /tmp/runner.xz \ + && xz -d /tmp/runner.xz \ + && mv /tmp/runner /usr/local/bin/forgejo-runner \ + && chmod +x /usr/local/bin/forgejo-runner + +# Create data directory for runner state +RUN mkdir -p /data +WORKDIR /data + +# Copy entrypoint script +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +# Copy runner config template +COPY config.yaml /etc/forgejo-runner/config.yaml + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/containers/forgejo-runner/config.yaml b/containers/forgejo-runner/config.yaml new file mode 100644 index 0000000..60ad82e --- /dev/null +++ b/containers/forgejo-runner/config.yaml @@ -0,0 +1,25 @@ +# Forgejo Runner configuration +# See: https://forgejo.org/docs/latest/admin/actions/#configuration + +log: + level: info + +runner: + file: /data/.runner + capacity: 2 + timeout: 3h + # Fetch task interval + fetch_timeout: 5s + fetch_interval: 2s + +container: + # Use host network so containers can reach services on the host + # (e.g., registry.ops.eblu.me resolves to host's Tailscale IP) + network: host + # Don't use privileged mode by default + privileged: false + # Mount docker socket for container builds + options: -v /var/run/docker.sock:/var/run/docker.sock + # Valid volumes that can be mounted + valid_volumes: + - /var/run/docker.sock diff --git a/containers/forgejo-runner/entrypoint.sh b/containers/forgejo-runner/entrypoint.sh new file mode 100644 index 0000000..9977ce9 --- /dev/null +++ b/containers/forgejo-runner/entrypoint.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# Forgejo Runner entrypoint script +# +# Registers the runner on first start, then runs the daemon. +# State is persisted in /data so restarts don't re-register. + +set -e + +# Required environment variables +: "${FORGEJO_URL:?FORGEJO_URL is required (e.g., https://forge.ops.eblu.me)}" +: "${RUNNER_TOKEN:?RUNNER_TOKEN is required (from Forgejo admin > Actions > Runners)}" + +# Optional environment variables with defaults +RUNNER_NAME="${RUNNER_NAME:-forgejo-runner}" +RUNNER_LABELS="${RUNNER_LABELS:-docker:docker://debian:bookworm-slim}" + +# Registration file indicates runner is already registered +RUNNER_FILE="/data/.runner" + +# Register if not already registered +if [ ! -f "$RUNNER_FILE" ]; then + echo "Registering runner '${RUNNER_NAME}' with ${FORGEJO_URL}..." + forgejo-runner register \ + --instance "${FORGEJO_URL}" \ + --token "${RUNNER_TOKEN}" \ + --name "${RUNNER_NAME}" \ + --labels "${RUNNER_LABELS}" \ + --no-interactive + echo "Registration complete." +else + echo "Runner already registered, skipping registration." +fi + +# Start the runner daemon +echo "Starting forgejo-runner daemon..." +exec forgejo-runner daemon --config /etc/forgejo-runner/config.yaml -- 2.50.1 (Apple Git-155) From ef6e40d8f208a02b6340590852c6fac0fe005235 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 25 Jan 2026 17:22:56 -0800 Subject: [PATCH 02/15] Fix: add xz-utils for runner binary extraction Co-Authored-By: Claude Opus 4.5 --- containers/forgejo-runner/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/containers/forgejo-runner/Dockerfile b/containers/forgejo-runner/Dockerfile index 03b3349..0f8addb 100644 --- a/containers/forgejo-runner/Dockerfile +++ b/containers/forgejo-runner/Dockerfile @@ -31,6 +31,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ jq \ gnupg \ lsb-release \ + xz-utils \ && rm -rf /var/lib/apt/lists/* # Install Docker CLI (not daemon - we mount the socket) -- 2.50.1 (Apple Git-155) From 82d0162282b975ae5fadc1bcbaec8311cc0c8d94 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 25 Jan 2026 17:49:16 -0800 Subject: [PATCH 03/15] Switch container builds to docker runner (Phase 1 ratcheting) Test that the containerized runner can build containers. Co-Authored-By: Claude Opus 4.5 --- .forgejo/workflows/build-container.yaml | 2 +- .github/actionlint.yaml | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.forgejo/workflows/build-container.yaml b/.forgejo/workflows/build-container.yaml index 6ed1be8..8ccf48f 100644 --- a/.forgejo/workflows/build-container.yaml +++ b/.forgejo/workflows/build-container.yaml @@ -15,7 +15,7 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: docker steps: - name: Parse tag id: parse diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index 12f3259..0735751 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -1,5 +1,3 @@ self-hosted-runner: labels: - - docker-builder - - ubuntu-latest - - ubuntu-22.04 + - docker -- 2.50.1 (Apple Git-155) From f19795615c2590ab827191ef2d9096d1ba610cb3 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 25 Jan 2026 18:06:33 -0800 Subject: [PATCH 04/15] Simplify forgejo-runner to job execution image - Remove daemon entrypoint (host runner handles daemon) - Add Node.js 20.x for GitHub Actions compatibility - Keep Docker CLI for container builds - Switch workflow back to ubuntu-latest (host runner) Co-Authored-By: Claude Opus 4.5 --- .forgejo/workflows/build-container.yaml | 2 +- .github/actionlint.yaml | 1 + containers/forgejo-runner/Dockerfile | 51 +++++++------------------ containers/forgejo-runner/config.yaml | 25 ------------ containers/forgejo-runner/entrypoint.sh | 36 ----------------- 5 files changed, 16 insertions(+), 99 deletions(-) delete mode 100644 containers/forgejo-runner/config.yaml delete mode 100644 containers/forgejo-runner/entrypoint.sh diff --git a/.forgejo/workflows/build-container.yaml b/.forgejo/workflows/build-container.yaml index 8ccf48f..6ed1be8 100644 --- a/.forgejo/workflows/build-container.yaml +++ b/.forgejo/workflows/build-container.yaml @@ -15,7 +15,7 @@ on: jobs: build: - runs-on: docker + runs-on: ubuntu-latest steps: - name: Parse tag id: parse diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index 0735751..0f274ef 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -1,3 +1,4 @@ self-hosted-runner: labels: + - ubuntu-latest - docker diff --git a/containers/forgejo-runner/Dockerfile b/containers/forgejo-runner/Dockerfile index 0f8addb..dcdcc78 100644 --- a/containers/forgejo-runner/Dockerfile +++ b/containers/forgejo-runner/Dockerfile @@ -1,26 +1,15 @@ -# Forgejo Actions Runner - Containerized +# Forgejo Actions Job Execution Image # -# A containerized runner capable of building containers via Docker socket mount. -# Part of the runner ratcheting plan (Phase 1+). +# This image is used as the job execution environment for Forgejo Actions. +# The host runner daemon creates containers from this image to run workflow steps. # -# Build: -# docker build -t registry.ops.eblu.me/blumeops/forgejo-runner:v1.0.0 . +# Includes: Node.js (for GitHub Actions), Docker CLI, git, and common CI tools. # -# Run (Phase 1 - Docker on indri): -# docker run -d \ -# --name forgejo-runner \ -# -v /var/run/docker.sock:/var/run/docker.sock \ -# -e FORGEJO_URL=https://forge.ops.eblu.me \ -# -e RUNNER_TOKEN= \ -# -e RUNNER_NAME=indri-docker-runner \ -# registry.ops.eblu.me/blumeops/forgejo-runner:v1.0.0 -# -# The runner registers itself on first start and persists state in /data. +# Usage: Configure runner with label like: +# docker:docker://registry.ops.eblu.me/blumeops/forgejo-runner:latest FROM debian:bookworm-slim -# Forgejo runner version - check https://code.forgejo.org/forgejo/runner/releases -ARG RUNNER_VERSION=6.3.1 ARG TARGETARCH # Install base dependencies @@ -34,7 +23,12 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ xz-utils \ && rm -rf /var/lib/apt/lists/* -# Install Docker CLI (not daemon - we mount the socket) +# Install Node.js 20.x (required for actions/checkout@v4 and other GitHub Actions) +RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ + && apt-get install -y --no-install-recommends nodejs \ + && rm -rf /var/lib/apt/lists/* + +# Install Docker CLI (for container builds - daemon accessed via socket mount) RUN install -m 0755 -d /etc/apt/keyrings \ && curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc \ && chmod a+r /etc/apt/keyrings/docker.asc \ @@ -43,22 +37,5 @@ RUN install -m 0755 -d /etc/apt/keyrings \ && apt-get install -y --no-install-recommends docker-ce-cli \ && rm -rf /var/lib/apt/lists/* -# Install forgejo-runner -RUN ARCH=$(case "${TARGETARCH}" in "amd64") echo "amd64";; "arm64") echo "arm64";; *) echo "amd64";; esac) \ - && curl -fsSL "https://code.forgejo.org/forgejo/runner/releases/download/v${RUNNER_VERSION}/forgejo-runner-${RUNNER_VERSION}-linux-${ARCH}.xz" -o /tmp/runner.xz \ - && xz -d /tmp/runner.xz \ - && mv /tmp/runner /usr/local/bin/forgejo-runner \ - && chmod +x /usr/local/bin/forgejo-runner - -# Create data directory for runner state -RUN mkdir -p /data -WORKDIR /data - -# Copy entrypoint script -COPY entrypoint.sh /entrypoint.sh -RUN chmod +x /entrypoint.sh - -# Copy runner config template -COPY config.yaml /etc/forgejo-runner/config.yaml - -ENTRYPOINT ["/entrypoint.sh"] +# Default to bash +CMD ["/bin/bash"] diff --git a/containers/forgejo-runner/config.yaml b/containers/forgejo-runner/config.yaml deleted file mode 100644 index 60ad82e..0000000 --- a/containers/forgejo-runner/config.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Forgejo Runner configuration -# See: https://forgejo.org/docs/latest/admin/actions/#configuration - -log: - level: info - -runner: - file: /data/.runner - capacity: 2 - timeout: 3h - # Fetch task interval - fetch_timeout: 5s - fetch_interval: 2s - -container: - # Use host network so containers can reach services on the host - # (e.g., registry.ops.eblu.me resolves to host's Tailscale IP) - network: host - # Don't use privileged mode by default - privileged: false - # Mount docker socket for container builds - options: -v /var/run/docker.sock:/var/run/docker.sock - # Valid volumes that can be mounted - valid_volumes: - - /var/run/docker.sock diff --git a/containers/forgejo-runner/entrypoint.sh b/containers/forgejo-runner/entrypoint.sh deleted file mode 100644 index 9977ce9..0000000 --- a/containers/forgejo-runner/entrypoint.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash -# Forgejo Runner entrypoint script -# -# Registers the runner on first start, then runs the daemon. -# State is persisted in /data so restarts don't re-register. - -set -e - -# Required environment variables -: "${FORGEJO_URL:?FORGEJO_URL is required (e.g., https://forge.ops.eblu.me)}" -: "${RUNNER_TOKEN:?RUNNER_TOKEN is required (from Forgejo admin > Actions > Runners)}" - -# Optional environment variables with defaults -RUNNER_NAME="${RUNNER_NAME:-forgejo-runner}" -RUNNER_LABELS="${RUNNER_LABELS:-docker:docker://debian:bookworm-slim}" - -# Registration file indicates runner is already registered -RUNNER_FILE="/data/.runner" - -# Register if not already registered -if [ ! -f "$RUNNER_FILE" ]; then - echo "Registering runner '${RUNNER_NAME}' with ${FORGEJO_URL}..." - forgejo-runner register \ - --instance "${FORGEJO_URL}" \ - --token "${RUNNER_TOKEN}" \ - --name "${RUNNER_NAME}" \ - --labels "${RUNNER_LABELS}" \ - --no-interactive - echo "Registration complete." -else - echo "Runner already registered, skipping registration." -fi - -# Start the runner daemon -echo "Starting forgejo-runner daemon..." -exec forgejo-runner daemon --config /etc/forgejo-runner/config.yaml -- 2.50.1 (Apple Git-155) From fcbc3b958eea1cc64a9c602c76bba576555efeec Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 25 Jan 2026 18:12:32 -0800 Subject: [PATCH 05/15] Add docker mode to runner for containerized job execution - Runner now supports both host and docker labels - docker label uses forgejo-runner:v2.1.0 image with Node.js + Docker CLI - Switch build workflow to docker mode for ratcheting test Co-Authored-By: Claude Opus 4.5 --- .forgejo/workflows/build-container.yaml | 2 +- ansible/roles/forgejo_runner/defaults/main.yml | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.forgejo/workflows/build-container.yaml b/.forgejo/workflows/build-container.yaml index 6ed1be8..8ccf48f 100644 --- a/.forgejo/workflows/build-container.yaml +++ b/.forgejo/workflows/build-container.yaml @@ -15,7 +15,7 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: docker steps: - name: Parse tag id: parse diff --git a/ansible/roles/forgejo_runner/defaults/main.yml b/ansible/roles/forgejo_runner/defaults/main.yml index 75cbd0c..1606d0e 100644 --- a/ansible/roles/forgejo_runner/defaults/main.yml +++ b/ansible/roles/forgejo_runner/defaults/main.yml @@ -1,9 +1,11 @@ --- -# Forgejo Runner - host execution mode +# Forgejo Runner - hybrid host/docker execution # -# The runner daemon runs directly on indri and executes jobs on the host. -# This avoids container networking complexity since it can reach Forgejo -# at localhost:3001 directly. +# The runner daemon runs directly on indri. Jobs can run either: +# - On host (ubuntu-latest:host) - for builds needing host tools +# - In Docker (docker:docker://...) - for containerized builds +# +# The docker mode uses our custom job execution image with Node.js and Docker CLI. forgejo_runner_binary: /Users/erichblume/code/3rd/forgejo-runner/forgejo-runner forgejo_runner_data_dir: /Users/erichblume/.forgejo-runner @@ -14,9 +16,10 @@ forgejo_runner_log_dir: /Users/erichblume/Library/Logs forgejo_runner_instance_url: "http://localhost:3001" forgejo_runner_name: "indri-host-runner" -# Labels format for host execution: label:host -# Jobs run directly on the host, not in containers -forgejo_runner_labels: "ubuntu-latest:host,ubuntu-22.04:host" +# Labels: both host and docker modes +# - ubuntu-latest:host - jobs run directly on host +# - docker:docker://... - jobs run in container with Node.js + Docker CLI +forgejo_runner_labels: "ubuntu-latest:host,docker:docker://registry.ops.eblu.me/blumeops/forgejo-runner:v2.1.0" # Runner config forgejo_runner_capacity: 2 -- 2.50.1 (Apple Git-155) From e61e70078a4b4eab9548c0c212a2165a06f8449c Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 25 Jan 2026 18:18:50 -0800 Subject: [PATCH 06/15] Use forge.ops.eblu.me for runner URL (works from containers) localhost:3001 doesn't work from Docker containers on macOS because Docker Desktop's host networking runs in a VM. Co-Authored-By: Claude Opus 4.5 --- ansible/roles/forgejo_runner/defaults/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ansible/roles/forgejo_runner/defaults/main.yml b/ansible/roles/forgejo_runner/defaults/main.yml index 1606d0e..6d00c73 100644 --- a/ansible/roles/forgejo_runner/defaults/main.yml +++ b/ansible/roles/forgejo_runner/defaults/main.yml @@ -12,8 +12,8 @@ forgejo_runner_data_dir: /Users/erichblume/.forgejo-runner forgejo_runner_config_dir: /Users/erichblume/.config/forgejo-runner forgejo_runner_log_dir: /Users/erichblume/Library/Logs -# Runner registration - use localhost since we're running on indri -forgejo_runner_instance_url: "http://localhost:3001" +# Runner registration - use public URL so it works from both host and containers +forgejo_runner_instance_url: "https://forge.ops.eblu.me" forgejo_runner_name: "indri-host-runner" # Labels: both host and docker modes -- 2.50.1 (Apple Git-155) From 411b07e23c2db2321fb645767d6afdb363f52074 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 25 Jan 2026 18:48:57 -0800 Subject: [PATCH 07/15] Mount Docker socket in job containers for DinD Co-Authored-By: Claude Opus 4.5 --- ansible/roles/forgejo_runner/templates/config.yaml.j2 | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ansible/roles/forgejo_runner/templates/config.yaml.j2 b/ansible/roles/forgejo_runner/templates/config.yaml.j2 index 07bdb8d..b5ae974 100644 --- a/ansible/roles/forgejo_runner/templates/config.yaml.j2 +++ b/ansible/roles/forgejo_runner/templates/config.yaml.j2 @@ -7,7 +7,11 @@ runner: capacity: {{ forgejo_runner_capacity }} timeout: {{ forgejo_runner_timeout }} -# Even in host execution mode, some actions run in containers. -# Use host networking so containers can access localhost services. +# Container settings for docker execution mode container: + # Host networking so containers can reach services (forge.ops.eblu.me, etc.) network: "host" + # Mount Docker socket for container builds (DinD) + options: "-v /var/run/docker.sock:/var/run/docker.sock" + valid_volumes: + - /var/run/docker.sock -- 2.50.1 (Apple Git-155) From 24e7df02df660a75ab52ec6471a112f834c621fc Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 25 Jan 2026 18:57:20 -0800 Subject: [PATCH 08/15] Add k8s forgejo-runner deployment (Phase 2 ratcheting) - Deployment with forgejo-runner daemon + DinD sidecar - ConfigMap for runner configuration - Secret template for runner token (op inject) - ArgoCD Application definition Co-Authored-By: Claude Opus 4.5 --- argocd/apps/forgejo-runner.yaml | 17 ++++ .../manifests/forgejo-runner/configmap.yaml | 20 +++++ .../manifests/forgejo-runner/deployment.yaml | 77 +++++++++++++++++++ .../manifests/forgejo-runner/namespace.yaml | 4 + .../manifests/forgejo-runner/secret.yaml.tpl | 17 ++++ 5 files changed, 135 insertions(+) create mode 100644 argocd/apps/forgejo-runner.yaml create mode 100644 argocd/manifests/forgejo-runner/configmap.yaml create mode 100644 argocd/manifests/forgejo-runner/deployment.yaml create mode 100644 argocd/manifests/forgejo-runner/namespace.yaml create mode 100644 argocd/manifests/forgejo-runner/secret.yaml.tpl diff --git a/argocd/apps/forgejo-runner.yaml b/argocd/apps/forgejo-runner.yaml new file mode 100644 index 0000000..5bca762 --- /dev/null +++ b/argocd/apps/forgejo-runner.yaml @@ -0,0 +1,17 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: forgejo-runner + namespace: argocd +spec: + project: default + source: + repoURL: https://forge.ops.eblu.me/eblume/blumeops.git + targetRevision: main + path: argocd/manifests/forgejo-runner + destination: + server: https://kubernetes.default.svc + namespace: forgejo-runner + syncPolicy: + syncOptions: + - CreateNamespace=true diff --git a/argocd/manifests/forgejo-runner/configmap.yaml b/argocd/manifests/forgejo-runner/configmap.yaml new file mode 100644 index 0000000..aa035b1 --- /dev/null +++ b/argocd/manifests/forgejo-runner/configmap.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: forgejo-runner-config + namespace: forgejo-runner +data: + config.yaml: | + log: + level: info + + runner: + file: /data/.runner + capacity: 2 + timeout: 3h + + container: + # Use our custom job execution image with Node.js + Docker CLI + # Jobs requesting "docker" label will use this image + network: "host" + # DinD doesn't need socket mount - it uses DOCKER_HOST env var diff --git a/argocd/manifests/forgejo-runner/deployment.yaml b/argocd/manifests/forgejo-runner/deployment.yaml new file mode 100644 index 0000000..28d2c65 --- /dev/null +++ b/argocd/manifests/forgejo-runner/deployment.yaml @@ -0,0 +1,77 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: forgejo-runner + namespace: forgejo-runner + labels: + app: forgejo-runner +spec: + replicas: 1 + selector: + matchLabels: + app: forgejo-runner + template: + metadata: + labels: + app: forgejo-runner + spec: + containers: + # Forgejo runner daemon + - name: runner + image: code.forgejo.org/forgejo/runner:6.3.1 + env: + - name: DOCKER_HOST + value: tcp://localhost:2375 + command: + - /bin/sh + - -c + - | + # Wait for DinD to be ready + echo "Waiting for Docker daemon..." + while ! wget -q -O /dev/null http://localhost:2375/_ping 2>/dev/null; do + sleep 1 + done + echo "Docker daemon ready" + + # Register if not already registered + if [ ! -f /data/.runner ]; then + echo "Registering runner..." + forgejo-runner register \ + --instance "$FORGEJO_URL" \ + --token "$RUNNER_TOKEN" \ + --name "$RUNNER_NAME" \ + --labels "$RUNNER_LABELS" \ + --no-interactive + fi + + # Start daemon + exec forgejo-runner daemon --config /config/config.yaml + envFrom: + - secretRef: + name: forgejo-runner-env + volumeMounts: + - name: data + mountPath: /data + - name: config + mountPath: /config + + # Docker-in-Docker sidecar + - name: dind + image: docker:27-dind + securityContext: + privileged: true + env: + - name: DOCKER_TLS_CERTDIR + value: "" + volumeMounts: + - name: dind-storage + mountPath: /var/lib/docker + + volumes: + - name: data + emptyDir: {} + - name: dind-storage + emptyDir: {} + - name: config + configMap: + name: forgejo-runner-config diff --git a/argocd/manifests/forgejo-runner/namespace.yaml b/argocd/manifests/forgejo-runner/namespace.yaml new file mode 100644 index 0000000..19441b1 --- /dev/null +++ b/argocd/manifests/forgejo-runner/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: forgejo-runner diff --git a/argocd/manifests/forgejo-runner/secret.yaml.tpl b/argocd/manifests/forgejo-runner/secret.yaml.tpl new file mode 100644 index 0000000..d1a61fd --- /dev/null +++ b/argocd/manifests/forgejo-runner/secret.yaml.tpl @@ -0,0 +1,17 @@ +# Forgejo Runner Environment Secret +# This template is processed by `op inject` to resolve 1Password references. +# +# Usage: +# op inject -i secret.yaml.tpl | kubectl --context=minikube-indri apply -f - +# +apiVersion: v1 +kind: Secret +metadata: + name: forgejo-runner-env + namespace: forgejo-runner +type: Opaque +stringData: + FORGEJO_URL: "https://forge.ops.eblu.me" + RUNNER_NAME: "k8s-runner" + RUNNER_LABELS: "docker:docker://registry.ops.eblu.me/blumeops/forgejo-runner:v2.1.3" + RUNNER_TOKEN: "{{ op://vg6xf6vvfmoh5hqjjhlhbeoaie/w3663ffnvkewbftncqxtcpeavy/runner_reg }}" -- 2.50.1 (Apple Git-155) From 47bbdf5d0087da1d1223b7d1d02b48a004bf4943 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 25 Jan 2026 19:06:49 -0800 Subject: [PATCH 09/15] Set docker_host for DinD in k8s runner config Job containers need to connect to DinD via TCP, not socket. Co-Authored-By: Claude Opus 4.5 --- argocd/manifests/forgejo-runner/configmap.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/argocd/manifests/forgejo-runner/configmap.yaml b/argocd/manifests/forgejo-runner/configmap.yaml index aa035b1..860f96b 100644 --- a/argocd/manifests/forgejo-runner/configmap.yaml +++ b/argocd/manifests/forgejo-runner/configmap.yaml @@ -15,6 +15,6 @@ data: container: # Use our custom job execution image with Node.js + Docker CLI - # Jobs requesting "docker" label will use this image network: "host" - # DinD doesn't need socket mount - it uses DOCKER_HOST env var + # Connect to DinD sidecar via TCP (not socket) + docker_host: tcp://127.0.0.1:2375 -- 2.50.1 (Apple Git-155) From 1f595b60410d039cfd676d1a497a5467a2befa68 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 25 Jan 2026 19:11:38 -0800 Subject: [PATCH 10/15] Use k8s label for k8s runner testing Differentiate k8s runner from host runner with unique label. Co-Authored-By: Claude Opus 4.5 --- .forgejo/workflows/build-container.yaml | 2 +- .github/actionlint.yaml | 1 + argocd/manifests/forgejo-runner/secret.yaml.tpl | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.forgejo/workflows/build-container.yaml b/.forgejo/workflows/build-container.yaml index 8ccf48f..59291f0 100644 --- a/.forgejo/workflows/build-container.yaml +++ b/.forgejo/workflows/build-container.yaml @@ -15,7 +15,7 @@ on: jobs: build: - runs-on: docker + runs-on: k8s steps: - name: Parse tag id: parse diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index 0f274ef..8ec4ee1 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -2,3 +2,4 @@ self-hosted-runner: labels: - ubuntu-latest - docker + - k8s diff --git a/argocd/manifests/forgejo-runner/secret.yaml.tpl b/argocd/manifests/forgejo-runner/secret.yaml.tpl index d1a61fd..e9c1ca5 100644 --- a/argocd/manifests/forgejo-runner/secret.yaml.tpl +++ b/argocd/manifests/forgejo-runner/secret.yaml.tpl @@ -13,5 +13,5 @@ type: Opaque stringData: FORGEJO_URL: "https://forge.ops.eblu.me" RUNNER_NAME: "k8s-runner" - RUNNER_LABELS: "docker:docker://registry.ops.eblu.me/blumeops/forgejo-runner:v2.1.3" + RUNNER_LABELS: "k8s:docker://registry.ops.eblu.me/blumeops/forgejo-runner:v2.1.3" RUNNER_TOKEN: "{{ op://vg6xf6vvfmoh5hqjjhlhbeoaie/w3663ffnvkewbftncqxtcpeavy/runner_reg }}" -- 2.50.1 (Apple Git-155) From c7a58b8fdb3762fe3217f574451963bb7559e9f7 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 25 Jan 2026 19:22:37 -0800 Subject: [PATCH 11/15] Add DOCKER_HOST env var to workflow for k8s DinD support The k8s runner uses a DinD sidecar accessible via TCP on port 2375. While the runner daemon config has docker_host set, job containers don't inherit this - they need the DOCKER_HOST env var set explicitly. Co-Authored-By: Claude Opus 4.5 --- .forgejo/workflows/build-container.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.forgejo/workflows/build-container.yaml b/.forgejo/workflows/build-container.yaml index 59291f0..3bc1efd 100644 --- a/.forgejo/workflows/build-container.yaml +++ b/.forgejo/workflows/build-container.yaml @@ -16,6 +16,9 @@ on: jobs: build: runs-on: k8s + env: + # For k8s runner with DinD sidecar, Docker is available via TCP + DOCKER_HOST: tcp://127.0.0.1:2375 steps: - name: Parse tag id: parse -- 2.50.1 (Apple Git-155) From 4927c79f5bb2a66420a66fa0bcdc93f84b0a3990 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 25 Jan 2026 19:31:00 -0800 Subject: [PATCH 12/15] Retire host-mode forgejo runner (Phase 3 complete) - Remove forgejo_runner ansible role (no longer needed) - Remove runner pre_tasks from indri.yml playbook - Clean up actionlint.yaml to only allow k8s label - Host runner was stopped and cleaned up on indri All CI jobs now run on the k8s runner with DinD sidecar. Co-Authored-By: Claude Opus 4.5 --- .github/actionlint.yaml | 2 - ansible/playbooks/indri.yml | 19 ------- .../roles/forgejo_runner/defaults/main.yml | 26 --------- .../roles/forgejo_runner/handlers/main.yml | 7 --- ansible/roles/forgejo_runner/tasks/main.yml | 57 ------------------- .../forgejo_runner/templates/config.yaml.j2 | 17 ------ .../templates/forgejo-runner.plist.j2 | 33 ----------- 7 files changed, 161 deletions(-) delete mode 100644 ansible/roles/forgejo_runner/defaults/main.yml delete mode 100644 ansible/roles/forgejo_runner/handlers/main.yml delete mode 100644 ansible/roles/forgejo_runner/tasks/main.yml delete mode 100644 ansible/roles/forgejo_runner/templates/config.yaml.j2 delete mode 100644 ansible/roles/forgejo_runner/templates/forgejo-runner.plist.j2 diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index 8ec4ee1..20281ec 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -1,5 +1,3 @@ self-hosted-runner: labels: - - ubuntu-latest - - docker - k8s diff --git a/ansible/playbooks/indri.yml b/ansible/playbooks/indri.yml index 44afe2d..9ea46eb 100644 --- a/ansible/playbooks/indri.yml +++ b/ansible/playbooks/indri.yml @@ -61,23 +61,6 @@ no_log: true tags: [forgejo] - # Forgejo runner token (for indri-based runner) - - name: Fetch forgejo runner token - ansible.builtin.command: - cmd: op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get w3663ffnvkewbftncqxtcpeavy --fields runner_reg --reveal - delegate_to: localhost - register: _forgejo_runner_token - changed_when: false - no_log: true - check_mode: false - tags: [forgejo_runner] - - - name: Set forgejo runner token fact - ansible.builtin.set_fact: - forgejo_runner_token: "{{ _forgejo_runner_token.stdout }}" - no_log: true - tags: [forgejo_runner] - # Caddy Gandi token for ACME DNS-01 challenges - name: Fetch Gandi PAT for Caddy ansible.builtin.command: @@ -114,7 +97,5 @@ tags: minikube_metrics - role: plex_metrics tags: plex_metrics - - role: forgejo_runner - tags: forgejo_runner - role: caddy tags: caddy diff --git a/ansible/roles/forgejo_runner/defaults/main.yml b/ansible/roles/forgejo_runner/defaults/main.yml deleted file mode 100644 index 6d00c73..0000000 --- a/ansible/roles/forgejo_runner/defaults/main.yml +++ /dev/null @@ -1,26 +0,0 @@ ---- -# Forgejo Runner - hybrid host/docker execution -# -# The runner daemon runs directly on indri. Jobs can run either: -# - On host (ubuntu-latest:host) - for builds needing host tools -# - In Docker (docker:docker://...) - for containerized builds -# -# The docker mode uses our custom job execution image with Node.js and Docker CLI. - -forgejo_runner_binary: /Users/erichblume/code/3rd/forgejo-runner/forgejo-runner -forgejo_runner_data_dir: /Users/erichblume/.forgejo-runner -forgejo_runner_config_dir: /Users/erichblume/.config/forgejo-runner -forgejo_runner_log_dir: /Users/erichblume/Library/Logs - -# Runner registration - use public URL so it works from both host and containers -forgejo_runner_instance_url: "https://forge.ops.eblu.me" -forgejo_runner_name: "indri-host-runner" - -# Labels: both host and docker modes -# - ubuntu-latest:host - jobs run directly on host -# - docker:docker://... - jobs run in container with Node.js + Docker CLI -forgejo_runner_labels: "ubuntu-latest:host,docker:docker://registry.ops.eblu.me/blumeops/forgejo-runner:v2.1.0" - -# Runner config -forgejo_runner_capacity: 2 -forgejo_runner_timeout: 3h diff --git a/ansible/roles/forgejo_runner/handlers/main.yml b/ansible/roles/forgejo_runner/handlers/main.yml deleted file mode 100644 index 8ace37c..0000000 --- a/ansible/roles/forgejo_runner/handlers/main.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -- name: Restart forgejo-runner - listen: Restart forgejo-runner - ansible.builtin.shell: | - launchctl unload ~/Library/LaunchAgents/mcquack.forgejo-runner.plist 2>/dev/null || true - launchctl load ~/Library/LaunchAgents/mcquack.forgejo-runner.plist - changed_when: true diff --git a/ansible/roles/forgejo_runner/tasks/main.yml b/ansible/roles/forgejo_runner/tasks/main.yml deleted file mode 100644 index 0f28d88..0000000 --- a/ansible/roles/forgejo_runner/tasks/main.yml +++ /dev/null @@ -1,57 +0,0 @@ ---- -# Forgejo Runner - host execution mode -# -# The runner daemon runs directly on indri using a locally compiled binary. -# Jobs execute on the host, reaching Forgejo at localhost:3001. - -- name: Ensure forgejo-runner directories exist - ansible.builtin.file: - path: "{{ item }}" - state: directory - mode: '0755' - loop: - - "{{ forgejo_runner_data_dir }}" - - "{{ forgejo_runner_config_dir }}" - -- name: Deploy forgejo-runner config - ansible.builtin.template: - src: config.yaml.j2 - dest: "{{ forgejo_runner_config_dir }}/config.yaml" - mode: '0644' - notify: Restart forgejo-runner - -- name: Check if runner is registered - ansible.builtin.stat: - path: "{{ forgejo_runner_data_dir }}/.runner" - register: forgejo_runner_registered - -- name: Register runner with Forgejo - ansible.builtin.command: - cmd: > - {{ forgejo_runner_binary }} register - --instance "{{ forgejo_runner_instance_url }}" - --token "{{ forgejo_runner_token }}" - --name "{{ forgejo_runner_name }}" - --labels "{{ forgejo_runner_labels }}" - --no-interactive - chdir: "{{ forgejo_runner_data_dir }}" - when: not forgejo_runner_registered.stat.exists - changed_when: true - -- name: Deploy forgejo-runner launchd plist - ansible.builtin.template: - src: forgejo-runner.plist.j2 - dest: ~/Library/LaunchAgents/mcquack.forgejo-runner.plist - mode: '0644' - notify: Restart forgejo-runner - -- name: Check if forgejo-runner is loaded - ansible.builtin.command: launchctl list mcquack.forgejo-runner - register: forgejo_runner_launchctl_check - changed_when: false - failed_when: false - -- name: Load forgejo-runner if not loaded - ansible.builtin.command: launchctl load ~/Library/LaunchAgents/mcquack.forgejo-runner.plist - when: forgejo_runner_launchctl_check.rc != 0 - changed_when: true diff --git a/ansible/roles/forgejo_runner/templates/config.yaml.j2 b/ansible/roles/forgejo_runner/templates/config.yaml.j2 deleted file mode 100644 index b5ae974..0000000 --- a/ansible/roles/forgejo_runner/templates/config.yaml.j2 +++ /dev/null @@ -1,17 +0,0 @@ -# {{ ansible_managed }} -log: - level: info - -runner: - file: {{ forgejo_runner_data_dir }}/.runner - capacity: {{ forgejo_runner_capacity }} - timeout: {{ forgejo_runner_timeout }} - -# Container settings for docker execution mode -container: - # Host networking so containers can reach services (forge.ops.eblu.me, etc.) - network: "host" - # Mount Docker socket for container builds (DinD) - options: "-v /var/run/docker.sock:/var/run/docker.sock" - valid_volumes: - - /var/run/docker.sock diff --git a/ansible/roles/forgejo_runner/templates/forgejo-runner.plist.j2 b/ansible/roles/forgejo_runner/templates/forgejo-runner.plist.j2 deleted file mode 100644 index e04fa0d..0000000 --- a/ansible/roles/forgejo_runner/templates/forgejo-runner.plist.j2 +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - Label - mcquack.forgejo-runner - ProgramArguments - - {{ forgejo_runner_binary }} - daemon - --config - {{ forgejo_runner_config_dir }}/config.yaml - - WorkingDirectory - {{ forgejo_runner_data_dir }} - EnvironmentVariables - - PATH - /Users/erichblume/.local/share/mise/shims:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin - HOME - /Users/erichblume - - RunAtLoad - - KeepAlive - - StandardOutPath - {{ forgejo_runner_log_dir }}/mcquack.forgejo-runner.out.log - StandardErrorPath - {{ forgejo_runner_log_dir }}/mcquack.forgejo-runner.err.log - - -- 2.50.1 (Apple Git-155) From 2c6e450e1ad5a6b543d8293af11d3738b59e2059 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 25 Jan 2026 19:35:28 -0800 Subject: [PATCH 13/15] Bump runner job image to v2.1.7 Co-Authored-By: Claude Opus 4.5 --- argocd/manifests/forgejo-runner/secret.yaml.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/argocd/manifests/forgejo-runner/secret.yaml.tpl b/argocd/manifests/forgejo-runner/secret.yaml.tpl index e9c1ca5..b24029a 100644 --- a/argocd/manifests/forgejo-runner/secret.yaml.tpl +++ b/argocd/manifests/forgejo-runner/secret.yaml.tpl @@ -13,5 +13,5 @@ type: Opaque stringData: FORGEJO_URL: "https://forge.ops.eblu.me" RUNNER_NAME: "k8s-runner" - RUNNER_LABELS: "k8s:docker://registry.ops.eblu.me/blumeops/forgejo-runner:v2.1.3" + RUNNER_LABELS: "k8s:docker://registry.ops.eblu.me/blumeops/forgejo-runner:v2.1.7" RUNNER_TOKEN: "{{ op://vg6xf6vvfmoh5hqjjhlhbeoaie/w3663ffnvkewbftncqxtcpeavy/runner_reg }}" -- 2.50.1 (Apple Git-155) From c4b14c0b89b2aba05ad2d0d6f058588a1366bd0d Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 25 Jan 2026 19:40:33 -0800 Subject: [PATCH 14/15] Move DOCKER_HOST to runner config instead of workflow The runner's container.env config sets environment variables in all job containers, so we don't need to specify DOCKER_HOST per-workflow. Co-Authored-By: Claude Opus 4.5 --- .forgejo/workflows/build-container.yaml | 3 --- argocd/manifests/forgejo-runner/configmap.yaml | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.forgejo/workflows/build-container.yaml b/.forgejo/workflows/build-container.yaml index 3bc1efd..59291f0 100644 --- a/.forgejo/workflows/build-container.yaml +++ b/.forgejo/workflows/build-container.yaml @@ -16,9 +16,6 @@ on: jobs: build: runs-on: k8s - env: - # For k8s runner with DinD sidecar, Docker is available via TCP - DOCKER_HOST: tcp://127.0.0.1:2375 steps: - name: Parse tag id: parse diff --git a/argocd/manifests/forgejo-runner/configmap.yaml b/argocd/manifests/forgejo-runner/configmap.yaml index 860f96b..7d8ddef 100644 --- a/argocd/manifests/forgejo-runner/configmap.yaml +++ b/argocd/manifests/forgejo-runner/configmap.yaml @@ -18,3 +18,6 @@ data: network: "host" # Connect to DinD sidecar via TCP (not socket) docker_host: tcp://127.0.0.1:2375 + # Set DOCKER_HOST in job containers so they can run docker commands + env: + DOCKER_HOST: tcp://127.0.0.1:2375 -- 2.50.1 (Apple Git-155) From ce10997f7969ed47703326f686b8a7996efdff9f Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 25 Jan 2026 19:52:30 -0800 Subject: [PATCH 15/15] Fix: Move DOCKER_HOST to runner.envs (not container.env) The act_runner config uses runner.envs for job environment variables, not container.env which doesn't exist. Co-Authored-By: Claude Opus 4.5 --- argocd/manifests/forgejo-runner/configmap.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/argocd/manifests/forgejo-runner/configmap.yaml b/argocd/manifests/forgejo-runner/configmap.yaml index 7d8ddef..dd3c3ef 100644 --- a/argocd/manifests/forgejo-runner/configmap.yaml +++ b/argocd/manifests/forgejo-runner/configmap.yaml @@ -12,12 +12,12 @@ data: file: /data/.runner capacity: 2 timeout: 3h + # Set DOCKER_HOST in job containers so they can run docker commands + envs: + DOCKER_HOST: tcp://127.0.0.1:2375 container: # Use our custom job execution image with Node.js + Docker CLI network: "host" # Connect to DinD sidecar via TCP (not socket) docker_host: tcp://127.0.0.1:2375 - # Set DOCKER_HOST in job containers so they can run docker commands - env: - DOCKER_HOST: tcp://127.0.0.1:2375 -- 2.50.1 (Apple Git-155)