Skip to content

GitHub Actions — self-hosted runners (homelab)

Homelab CI uses repo-scoped self-hosted runners on two pools. GitHub-hosted ubuntu-latest was blocked by account billing (2026-06-26); all workflows target these pools.

Runner admin: homelab → Settings → Actions → Runners

Same pattern as tiktooker (tiktok-ci) and dnd_session_parser (dnd-session-parser) on WSL.


Pools

Label Host Use when
homelab-ci WSL on dev PC (CaptainKangapoo) Lint, docs deploy, Tailscale ACL — internet only
homelab-lan infra-services (192.168.6.17) Komodo webhook, UniFi/Proxmox scans — needs LAN DNS/IPs
dependabot WSL runner 04 (also carries homelab-ci) Dependabot PR CI — isolated from the main CI pool

Workflows use explicit labels so homelab jobs never land on tiktok/dnd runners (those are registered to other repos).

runs-on: [self-hosted, Linux, X64, homelab-ci]
# or
runs-on: [self-hosted, Linux, X64, homelab-lan]
# Dependabot PRs (lint.yml) — dedicated runner 04:
runs-on: ${{ fromJSON(github.actor == 'dependabot[bot]' && '["self-hosted","Linux","X64","homelab-ci","dependabot"]' || '["self-hosted","Linux","X64","homelab-ci"]') }}

Current inventory

Name Pool Status
infra-services homelab-lan Online — systemd on infra-services
windows-CaptainKangapoo-homelab-runner-01 homelab-ci Online — WSL systemd
windows-CaptainKangapoo-homelab-runner-02 homelab-ci Online — WSL systemd
windows-CaptainKangapoo-homelab-runner-03 homelab-ci Online — WSL systemd
windows-CaptainKangapoo-homelab-runner-04 homelab-ci, dependabot Dependabot PR CI — WSL systemd

Install script: scripts/install-homelab-ci-runners-wsl.sh

bash scripts/install-homelab-ci-runners-wsl.sh 3           # CI pool
bash scripts/install-homelab-ci-runners-wsl.sh --dependabot # runner 04

Check live:

gh api repos/notarealemail/homelab/actions/runners --jq '.runners[] | {name, status, labels: [.labels[].name]}'

Register homelab-lan (infra-services)

Already installed at /home/someone/actions-runner (2026-06-26). Add the custom label if the runner only has defaults:

# From dev machine (gh auth)
gh api --method PUT repos/notarealemail/homelab/actions/runners/RUNNER_ID/labels \
  -f "labels[]=homelab-lan"

Or re-register with labels:

ssh infra-services
cd ~/actions-runner
./config.sh remove
# New token: GitHub → homelab → Settings → Actions → Runners → New self-hosted runner
./config.sh --url https://github.com/notarealemail/homelab \
  --token TOKEN \
  --name infra-services \
  --labels homelab-lan \
  --unattended
sudo ./svc.sh install someone
sudo ./svc.sh start

Service: actions.runner.notarealemail-homelab.infra-services.service

Prereqs on host: git, Python 3.12+, uv optional; LAN DNS for *.infra.realemail.app, reachability to UDM (192.168.1.1) and Proxmox as needed.


Register homelab-ci (WSL)

Mirror the tiktok/dnd WSL layout — one or more runners with the homelab-ci label only (do not reuse tiktok-ci / dnd-session-parser registrations; those belong to other repos).

On WSL (Ubuntu), per runner instance — or use the install script:

# From repo root on WSL (gh auth login required)
bash scripts/install-homelab-ci-runners-wsl.sh 3

Manual one-off (token from GitHub → homelab → Settings → Actions → Runners):

DIR=~/actions-runner-homelab-ci-01
mkdir -p "$DIR" && cd "$DIR"
curl -fsSL -o runner.tar.gz \
  https://github.com/actions/runner/releases/download/v2.335.1/actions-runner-linux-x64-2.335.1.tar.gz
tar xzf runner.tar.gz
./config.sh --url https://github.com/notarealemail/homelab --token TOKEN \
  --name windows-CaptainKangapoo-homelab-runner-01 --labels homelab-ci --unattended
sudo ./svc.sh install someone && sudo ./svc.sh start

Prereqs: git, Python 3.12/3.13, Node 22, npm, Docker optional. Runners need outbound HTTPS (PyPI, npm, Cloudflare API for docs deploy).

Parallelism: lint.yml runs nine jobs — two or three homelab-ci runners avoid long queues. Dependabot PRs target runner 04 (homelab-ci + dependabot) so dependency bumps do not starve human PRs.


Dependabot

Config: .github/dependabot.yml — npm (root), pip (pyproject.toml), GitHub Actions, and docker-compose per services/* stack.

Same isolation pattern as dnd_session_parser and tiktooker (runner-10): one WSL runner carries both the repo CI label and dependabot. lint.yml switches runs-on when github.actor == 'dependabot[bot]'.

Register runner 04:

bash scripts/install-homelab-ci-runners-wsl.sh --dependabot

Security workflows

Ported from dnd_session_parser — all run on homelab-ci (Dependabot PRs on runner 04):

Workflow Tools Triggers
Secrets scan Gitleaks + TruffleHog OSS PR, push, weekly Tue 03:30
Dependency Security pip-audit (uv.lock) + npm audit PR (path-filtered), push, weekly Mon 07:00
Semgrep OSS p/ci ERROR gate PR, push, weekly Mon 07:30

Gitleaks config: .gitleaks.toml allowlists SOPS ciphertext and repo placeholders. pip-audit baseline: .github/pip-audit-ignore.txt.

No new GitHub secrets are required — scans use GITHUB_TOKEN (Gitleaks) or run offline. Optional: add DISCORD_WEBHOOK and a dependabot-discord.yml-style workflow if you want PR notifications like dnd.

Branch protection (GitHub UI): Settings → Branches → main → require status checks: Gitleaks, TruffleHog OSS, Semgrep OSS (high-confidence gate), and the relevant Dependency Security jobs once the first runs are green.


Workflow → pool map

Workflow Pool
lint.yml homelab-ci
secrets-scan.yml homelab-ci / dependabot
dependency-security.yml homelab-ci / dependabot
semgrep.yml homelab-ci / dependabot
docs.yml homelab-ci
tailscale-acl.yml homelab-ci
komodo-deploy.yml homelab-lan
network-scan.yml homelab-lan
proxmox-scan.yml homelab-lan
unifi-fix-airplay.yml homelab-lan

Troubleshooting

Job queued forever

No online runner with the requested label. Register a runner or fix labels:

gh api repos/notarealemail/homelab/actions/runners --jq '.runners[] | select(.status==\"online\") | .labels[].name' | sort -u

Wrong pool picked up job

Labels are wrong on the runner. LAN jobs must not carry homelab-ci only; CI runners must not be the sole homelab-lan host unless you accept LAN workloads on WSL (not recommended — no infra DNS).

Runner offline after reboot

# infra-services
ssh infra-services 'sudo systemctl status actions.runner.notarealemail-homelab.infra-services'

# WSL — restart your user systemd unit or ./run.sh