Containerize forgejo-runner with Tailscale gateway for tailnet access
Some checks failed
Test CI / test (pull_request) Failing after 48s
Some checks failed
Test CI / test (pull_request) Failing after 48s
Architecture: - tailscale_ci_gateway role: Runs Tailscale container on tailnet-jobs network - forgejo_runner role: Runs runner daemon in container on same network - Job containers also use tailnet-jobs network This allows the runner and jobs to reach forge.tail8d86e.ts.net via the Tailscale gateway, avoiding hairpinning issues with localhost. Changes: - Add tailscale_ci_gateway role with launchd management - Refactor forgejo_runner to use containerized daemon - Runner registers with Tailscale URL instead of localhost - Job containers run on tailnet-jobs network - Update playbook role ordering (gateway before runner) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
018b44186f
commit
fdf5153130
10 changed files with 199 additions and 62 deletions
|
|
@ -78,6 +78,23 @@
|
|||
no_log: true
|
||||
tags: [forgejo_runner]
|
||||
|
||||
# Tailscale CI gateway auth key (for job container tailnet access)
|
||||
- name: Fetch tailscale ci-gateway auth key
|
||||
ansible.builtin.command:
|
||||
cmd: op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get w3663ffnvkewbftncqxtcpeavy --fields ci-gateway-ts-auth-key --reveal
|
||||
delegate_to: localhost
|
||||
register: _tailscale_ci_gateway_auth_key
|
||||
changed_when: false
|
||||
no_log: true
|
||||
check_mode: false
|
||||
tags: [tailscale_ci_gateway]
|
||||
|
||||
- name: Set tailscale ci-gateway auth key fact
|
||||
ansible.builtin.set_fact:
|
||||
tailscale_ci_gateway_auth_key: "{{ _tailscale_ci_gateway_auth_key.stdout }}"
|
||||
no_log: true
|
||||
tags: [tailscale_ci_gateway]
|
||||
|
||||
roles:
|
||||
- role: alloy
|
||||
tags: alloy
|
||||
|
|
@ -99,5 +116,7 @@
|
|||
tags: plex_metrics
|
||||
- role: tailscale_serve
|
||||
tags: tailscale-serve
|
||||
- role: tailscale_ci_gateway
|
||||
tags: tailscale_ci_gateway
|
||||
- role: forgejo_runner
|
||||
tags: forgejo_runner
|
||||
|
|
|
|||
|
|
@ -1,25 +1,37 @@
|
|||
---
|
||||
forgejo_runner_repo_dir: /Users/erichblume/code/3rd/forgejo-runner
|
||||
forgejo_runner_binary: "{{ forgejo_runner_repo_dir }}/forgejo-runner"
|
||||
# Forgejo Runner - containerized daemon on tailnet-jobs network
|
||||
#
|
||||
# The runner daemon runs in a Docker container with access to the tailnet
|
||||
# via the tailscale-ci-gateway. This allows it to register with Forgejo
|
||||
# using the Tailscale URL, so job containers can also reach Forgejo.
|
||||
|
||||
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
|
||||
forgejo_runner_instance_url: "http://localhost:3001"
|
||||
# Container settings
|
||||
forgejo_runner_container_name: forgejo-runner
|
||||
forgejo_runner_image: code.forgejo.org/forgejo/runner:6.2.1
|
||||
forgejo_runner_network: tailnet-jobs
|
||||
|
||||
# Runner registration - use Tailscale URL since we're on tailnet-jobs network
|
||||
forgejo_runner_instance_url: "https://forge.tail8d86e.ts.net"
|
||||
forgejo_runner_name: "indri-docker-runner"
|
||||
|
||||
# Labels format: label:docker://image
|
||||
#
|
||||
# Bootstrap mode (use upstream images to build our own):
|
||||
# Job containers also run on tailnet-jobs network and can reach:
|
||||
# - forge.tail8d86e.ts.net for git clone
|
||||
# - registry.tail8d86e.ts.net for container push/pull
|
||||
#
|
||||
# Bootstrap mode (use upstream images until we build ci-base):
|
||||
# docker-builder:docker://docker:27-cli
|
||||
# ubuntu-latest:docker://catthehacker/ubuntu:act-22.04
|
||||
#
|
||||
# Production mode (use our own images from zot via host.docker.internal):
|
||||
# docker-builder:docker://host.docker.internal:5050/blumeops/ci-base:latest
|
||||
# ubuntu-latest:docker://host.docker.internal:5050/blumeops/ci-base:latest
|
||||
# Production mode (use our own images from zot):
|
||||
# docker-builder:docker://registry.tail8d86e.ts.net/blumeops/ci-base:latest
|
||||
# ubuntu-latest:docker://registry.tail8d86e.ts.net/blumeops/ci-base:latest
|
||||
#
|
||||
# Note: Docker daemon.json must include host.docker.internal:5050 in insecure-registries
|
||||
# Currently using bootstrap mode until ci-base is built
|
||||
forgejo_runner_labels: "docker-builder:docker://docker:27-cli,ubuntu-latest:docker://catthehacker/ubuntu:act-22.04,ubuntu-22.04:docker://catthehacker/ubuntu:act-22.04"
|
||||
|
||||
# Runner config
|
||||
|
|
@ -27,5 +39,4 @@ forgejo_runner_capacity: 2
|
|||
forgejo_runner_timeout: 3h
|
||||
|
||||
# Docker container settings for jobs
|
||||
# Note: network is hardcoded to "host" so containers can reach localhost:3001 (Forgejo)
|
||||
forgejo_runner_privileged: true # Needed for container builds
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
- name: Restart forgejo-runner
|
||||
listen: Restart forgejo-runner
|
||||
ansible.builtin.shell: |
|
||||
launchctl unload ~/Library/LaunchAgents/mcquack.eblume.forgejo-runner.plist 2>/dev/null || true
|
||||
launchctl load ~/Library/LaunchAgents/mcquack.eblume.forgejo-runner.plist
|
||||
launchctl unload ~/Library/LaunchAgents/mcquack.forgejo-runner.plist 2>/dev/null || true
|
||||
launchctl load ~/Library/LaunchAgents/mcquack.forgejo-runner.plist
|
||||
changed_when: true
|
||||
|
|
|
|||
|
|
@ -1,34 +1,12 @@
|
|||
---
|
||||
# Forgejo Runner on indri
|
||||
# Forgejo Runner - containerized daemon on tailnet-jobs network
|
||||
#
|
||||
# Uses Docker container mode for job isolation.
|
||||
# Can build containers using Docker (via socket).
|
||||
# The runner daemon runs in a Docker container with access to the tailnet
|
||||
# via the tailscale-ci-gateway. Job containers also run on tailnet-jobs
|
||||
# and can reach Forgejo via Tailscale.
|
||||
#
|
||||
# ONE-TIME SETUP (before running ansible):
|
||||
#
|
||||
# 1. Clone forgejo-runner from forge mirror:
|
||||
# ssh indri 'git clone https://forge.tail8d86e.ts.net/eblume/forgejo-runner.git ~/code/3rd/forgejo-runner'
|
||||
#
|
||||
# 2. Set up Go via mise:
|
||||
# ssh indri 'cd ~/code/3rd/forgejo-runner && mise use go@1.24'
|
||||
#
|
||||
# 3. Build:
|
||||
# ssh indri 'cd ~/code/3rd/forgejo-runner && mise x -- make build'
|
||||
#
|
||||
# 4. Run ansible to deploy config and LaunchAgent
|
||||
|
||||
- name: Verify forgejo-runner binary exists
|
||||
ansible.builtin.stat:
|
||||
path: "{{ forgejo_runner_binary }}"
|
||||
register: forgejo_runner_binary_stat
|
||||
|
||||
- name: Fail if forgejo-runner binary not found
|
||||
ansible.builtin.fail:
|
||||
msg: |
|
||||
Forgejo-runner binary not found at {{ forgejo_runner_binary }}.
|
||||
Please build from source first:
|
||||
ssh indri 'cd ~/code/3rd/forgejo-runner && mise x -- make build'
|
||||
when: not forgejo_runner_binary_stat.stat.exists
|
||||
# DEPENDENCIES:
|
||||
# - tailscale_ci_gateway role must run first (creates tailnet-jobs network)
|
||||
|
||||
- name: Ensure forgejo-runner directories exist
|
||||
ansible.builtin.file:
|
||||
|
|
@ -46,38 +24,47 @@
|
|||
mode: '0644'
|
||||
notify: Restart forgejo-runner
|
||||
|
||||
- name: Pull forgejo-runner image
|
||||
ansible.builtin.command:
|
||||
cmd: docker pull {{ forgejo_runner_image }}
|
||||
register: forgejo_runner_pull
|
||||
changed_when: "'Downloaded newer image' in forgejo_runner_pull.stdout or 'Pull complete' in forgejo_runner_pull.stdout"
|
||||
|
||||
- name: Check if runner is registered
|
||||
ansible.builtin.stat:
|
||||
path: "{{ forgejo_runner_data_dir }}/.runner"
|
||||
register: forgejo_runner_registered
|
||||
|
||||
- name: Register runner with Forgejo
|
||||
- name: Register runner with Forgejo (via tailnet)
|
||||
ansible.builtin.command:
|
||||
cmd: >
|
||||
{{ forgejo_runner_binary }} register
|
||||
docker run --rm
|
||||
--network {{ forgejo_runner_network }}
|
||||
-v {{ forgejo_runner_data_dir }}:/data
|
||||
{{ forgejo_runner_image }}
|
||||
forgejo-runner 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 LaunchAgent plist
|
||||
- name: Deploy forgejo-runner launchd plist
|
||||
ansible.builtin.template:
|
||||
src: forgejo-runner.plist.j2
|
||||
dest: ~/Library/LaunchAgents/mcquack.eblume.forgejo-runner.plist
|
||||
dest: ~/Library/LaunchAgents/mcquack.forgejo-runner.plist
|
||||
mode: '0644'
|
||||
notify: Restart forgejo-runner
|
||||
|
||||
- name: Check if forgejo-runner LaunchAgent is loaded
|
||||
ansible.builtin.command: launchctl list mcquack.eblume.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 LaunchAgent if not loaded
|
||||
ansible.builtin.command: launchctl load ~/Library/LaunchAgents/mcquack.eblume.forgejo-runner.plist
|
||||
- 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
|
||||
|
|
|
|||
|
|
@ -3,16 +3,15 @@ log:
|
|||
level: info
|
||||
|
||||
runner:
|
||||
file: {{ forgejo_runner_data_dir }}/.runner
|
||||
# Path inside the container (data dir mounted at /data)
|
||||
file: /data/.runner
|
||||
capacity: {{ forgejo_runner_capacity }}
|
||||
timeout: {{ forgejo_runner_timeout }}
|
||||
|
||||
container:
|
||||
network: "bridge"
|
||||
# Use tailnet-jobs network so job containers can reach Forgejo via Tailscale gateway
|
||||
network: "{{ forgejo_runner_network }}"
|
||||
privileged: {{ forgejo_runner_privileged | lower }}
|
||||
# Map localhost to Docker host so containers can reach Forgejo at localhost:3001
|
||||
# host-gateway is a special Docker value that resolves to the host IP
|
||||
options: "--add-host localhost:host-gateway"
|
||||
# Mount Docker socket so jobs can build containers
|
||||
valid_volumes:
|
||||
- /var/run/docker.sock
|
||||
|
|
|
|||
|
|
@ -4,16 +4,30 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>mcquack.eblume.forgejo-runner</string>
|
||||
<string>mcquack.forgejo-runner</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>{{ forgejo_runner_binary }}</string>
|
||||
<string>daemon</string>
|
||||
<string>--config</string>
|
||||
<string>{{ forgejo_runner_config_dir }}/config.yaml</string>
|
||||
<string>/bin/bash</string>
|
||||
<string>-c</string>
|
||||
<string><![CDATA[
|
||||
# Stop and remove existing container if present
|
||||
docker stop {{ forgejo_runner_container_name }} 2>/dev/null || true
|
||||
docker rm {{ forgejo_runner_container_name }} 2>/dev/null || true
|
||||
|
||||
# Run the forgejo-runner daemon in a container
|
||||
# - On tailnet-jobs network (can reach Forgejo via Tailscale gateway)
|
||||
# - Mounts docker socket to spawn job containers
|
||||
# - Mounts config and data directories
|
||||
exec docker run --rm \
|
||||
--name {{ forgejo_runner_container_name }} \
|
||||
--network {{ forgejo_runner_network }} \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v {{ forgejo_runner_config_dir }}/config.yaml:/config.yaml:ro \
|
||||
-v {{ forgejo_runner_data_dir }}:/data \
|
||||
{{ forgejo_runner_image }} \
|
||||
forgejo-runner daemon --config /config.yaml
|
||||
]]></string>
|
||||
</array>
|
||||
<key>WorkingDirectory</key>
|
||||
<string>{{ forgejo_runner_data_dir }}</string>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
|
|
|
|||
9
ansible/roles/tailscale_ci_gateway/defaults/main.yml
Normal file
9
ansible/roles/tailscale_ci_gateway/defaults/main.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
# Tailscale CI Gateway - provides tailnet access for Forgejo runner job containers
|
||||
|
||||
tailscale_ci_gateway_state_dir: /Users/erichblume/.tailscale-ci-gateway
|
||||
tailscale_ci_gateway_network: tailnet-jobs
|
||||
tailscale_ci_gateway_network_subnet: "172.30.0.0/24"
|
||||
tailscale_ci_gateway_container_name: tailscale-ci-gateway
|
||||
tailscale_ci_gateway_hostname: ci-gateway
|
||||
tailscale_ci_gateway_image: tailscale/tailscale:latest
|
||||
7
ansible/roles/tailscale_ci_gateway/handlers/main.yml
Normal file
7
ansible/roles/tailscale_ci_gateway/handlers/main.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
- name: Restart tailscale-ci-gateway
|
||||
listen: Restart tailscale-ci-gateway
|
||||
ansible.builtin.shell: |
|
||||
launchctl unload ~/Library/LaunchAgents/mcquack.tailscale-ci-gateway.plist 2>/dev/null || true
|
||||
launchctl load ~/Library/LaunchAgents/mcquack.tailscale-ci-gateway.plist
|
||||
changed_when: true
|
||||
46
ansible/roles/tailscale_ci_gateway/tasks/main.yml
Normal file
46
ansible/roles/tailscale_ci_gateway/tasks/main.yml
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
# Tailscale CI Gateway role
|
||||
# Manages a Tailscale container that provides tailnet access for CI job containers
|
||||
|
||||
- name: Ensure state directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ tailscale_ci_gateway_state_dir }}"
|
||||
state: directory
|
||||
mode: "0700"
|
||||
|
||||
- name: Check if Docker network exists
|
||||
ansible.builtin.command:
|
||||
cmd: docker network inspect {{ tailscale_ci_gateway_network }}
|
||||
register: tailscale_ci_gateway_network_check
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
|
||||
- name: Create Docker network for CI jobs
|
||||
ansible.builtin.command:
|
||||
cmd: >-
|
||||
docker network create
|
||||
--driver bridge
|
||||
--subnet {{ tailscale_ci_gateway_network_subnet }}
|
||||
{{ tailscale_ci_gateway_network }}
|
||||
when: tailscale_ci_gateway_network_check.rc != 0
|
||||
changed_when: true
|
||||
|
||||
- name: Pull Tailscale image
|
||||
ansible.builtin.command:
|
||||
cmd: docker pull {{ tailscale_ci_gateway_image }}
|
||||
register: tailscale_ci_gateway_pull
|
||||
changed_when: "'Downloaded newer image' in tailscale_ci_gateway_pull.stdout or 'Pull complete' in tailscale_ci_gateway_pull.stdout"
|
||||
|
||||
- name: Deploy launchd plist for Tailscale CI gateway
|
||||
ansible.builtin.template:
|
||||
src: tailscale-ci-gateway.plist.j2
|
||||
dest: ~/Library/LaunchAgents/mcquack.tailscale-ci-gateway.plist
|
||||
mode: "0644"
|
||||
notify: Restart tailscale-ci-gateway
|
||||
|
||||
- name: Ensure Tailscale CI gateway is loaded
|
||||
ansible.builtin.command:
|
||||
cmd: launchctl load ~/Library/LaunchAgents/mcquack.tailscale-ci-gateway.plist
|
||||
register: tailscale_ci_gateway_load
|
||||
failed_when: false
|
||||
changed_when: tailscale_ci_gateway_load.rc == 0
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>mcquack.tailscale-ci-gateway</string>
|
||||
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/bin/bash</string>
|
||||
<string>-c</string>
|
||||
<string><![CDATA[
|
||||
# Stop and remove existing container if present
|
||||
docker stop {{ tailscale_ci_gateway_container_name }} 2>/dev/null || true
|
||||
docker rm {{ tailscale_ci_gateway_container_name }} 2>/dev/null || true
|
||||
|
||||
# Run the container (foreground so launchd manages lifecycle)
|
||||
exec docker run --rm \
|
||||
--name {{ tailscale_ci_gateway_container_name }} \
|
||||
--hostname {{ tailscale_ci_gateway_hostname }} \
|
||||
--network {{ tailscale_ci_gateway_network }} \
|
||||
--cap-add NET_ADMIN \
|
||||
--cap-add NET_RAW \
|
||||
-v {{ tailscale_ci_gateway_state_dir }}:/var/lib/tailscale \
|
||||
-e TS_AUTHKEY="{{ tailscale_ci_gateway_auth_key }}" \
|
||||
-e TS_STATE_DIR=/var/lib/tailscale \
|
||||
-e TS_USERSPACE=false \
|
||||
-e TS_ACCEPT_DNS=true \
|
||||
{{ tailscale_ci_gateway_image }}
|
||||
]]></string>
|
||||
</array>
|
||||
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
|
||||
<key>StandardOutPath</key>
|
||||
<string>{{ ansible_env.HOME }}/Library/Logs/mcquack.tailscale-ci-gateway.out.log</string>
|
||||
|
||||
<key>StandardErrorPath</key>
|
||||
<string>{{ ansible_env.HOME }}/Library/Logs/mcquack.tailscale-ci-gateway.err.log</string>
|
||||
</dict>
|
||||
</plist>
|
||||
Loading…
Add table
Add a link
Reference in a new issue