No description
  • Python 62%
  • Shell 38%
Find a file
Your Name 9c77f0bebd init: ephemeral Forgejo runner orchestrator on KVM
Spawns transient VMs per CI job with per-repo CI-user and ephemeral write:package token. Components: orchestrator (Python), base-image builder (Bash), Debian Trixie + UEFI runner VM. See ARCHITECTURE.md.

Signed-off-by: Your Name <you@example.com>
2026-06-12 02:23:36 +02:00
.gitignore init: ephemeral Forgejo runner orchestrator on KVM 2026-06-12 02:23:36 +02:00
ARCHITECTURE.md init: ephemeral Forgejo runner orchestrator on KVM 2026-06-12 02:23:36 +02:00
build-runner-base.sh init: ephemeral Forgejo runner orchestrator on KVM 2026-06-12 02:23:36 +02:00
README.md init: ephemeral Forgejo runner orchestrator on KVM 2026-06-12 02:23:36 +02:00
runner_controller.py init: ephemeral Forgejo runner orchestrator on KVM 2026-06-12 02:23:36 +02:00
smoke-test.sh init: ephemeral Forgejo runner orchestrator on KVM 2026-06-12 02:23:36 +02:00

forgejo-kvm-runner

Ephemeral Forgejo runner on KVM. Fresh VM per CI job. Per-repo CI user with an ephemeral write:package token for the Forgejo registry. Architecture: ARCHITECTURE.md.

Prerequisites

Host packages (Arch):

sudo pacman -S libvirt qemu-base virt-install cloud-image-utils libvirt-python python-requests edk2-ovmf
sudo systemctl enable --now libvirtd
sudo virsh net-autostart default && sudo virsh net-start default

Forgejo admin token with scopes: sudo, read:user, write:user, write:admin_users, read:repository, write:repository, read:package, write:package, plus the admin scope covering the Actions discovery endpoint (else: 403 on discovery poll).

Run-through

1. Build the base image

sudo ./build-runner-base.sh

~7 min: Debian download → virt-customize → smoke-boot → atomic publish to /var/lib/libvirt/images/runner-base.qcow2.

2. Target repo workflow

  • runs-on: external-host
  • No docker login step — registry token is injected via cloud-init
  • Image tag: git.jonathan-schmidt.de/<owner>-<repo>-ci/<image>:<sha>

3. Start the controller

read -s FORGEJO_ADMIN_TOKEN && export FORGEJO_ADMIN_TOKEN
export FORGEJO_URL=https://git.jonathan-schmidt.de
sudo -E python runner_controller.py

Expect: startup sweep...entering discovery loop (label=external-host, interval=15s, max_vms=3).

4. Trigger a job

Push a commit. Controller log:

queue: dispatching job <id> (handle=...)
dispatch job <id> for <owner>/<repo> (vm=fr-<short>)
creating CI user <owner>-<repo>-ci for ...     # first job per repo only
VM fr-<short> started for job <id>
VM fr-<short> finished (job <id>)

VM boot ~30 s, job duration depends on the build.

5. Stop

Ctrl+C. Controller waits for in-flight jobs, then exits. startup_sweep revokes leftover orch-* tokens on the next start.

Debugging

virsh list                     # is a VM running?
virsh console fr-<short>       # live cloud-init / runner output (Ctrl+] to detach)
journalctl -u libvirtd -n 50

CI-user state: sudo cat /var/lib/forgejo-runner-controller/ci-users.json.