1 Commits

Author SHA1 Message Date
Will Anderson 5868607c27 fix(ci): point Gitea Actions runners at public instance URL
The in-cluster service DNS (`gitea.git.svc.cluster.local`) is not
resolvable from build containers running with `network: host`. The
runner config has an `extra_hosts` mapping for this name, but `host`
networking shares the host's network namespace and bypasses the
container-level hosts file — which silently nullifies the mapping.

Symptom: every Gitea Actions run on `dharma-el` (and any other
neuron-technologies repo that has CI defined) failed at the first
`actions/checkout` step with `Could not resolve host:
gitea.git.svc.cluster.local`. CI had never actually validated for
that org.

Fix: register both the `will` and `neuron-technologies` runners with
the public URL `https://git.neuralplatform.ai`. The runner polls Gitea
over Cloudflare, and the build container's clone URL is derived from
the runner's instance URL, so it inherits a name the build container
can resolve.

Also bumped `config-version` annotations on both runner deployments
to force a rolling restart — the init container needs to re-register
with the new URL.

Trade-off: the runner now polls Gitea over Cloudflare instead of
directly on the cluster network. Latency cost is small relative to
build time, and the failure mode is gone.
2026-05-04 15:52:10 -05:00
5 changed files with 23 additions and 131 deletions
+5 -19
View File
@@ -29,13 +29,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
zstd \
&& rm -rf /var/lib/apt/lists/*
# Node.js 20 LTS via binary tarball (nodesource apt repo is unreliable on Ubuntu 24.04)
RUN NODE_VERSION=20.19.1 \
&& curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" \
| tar -xJ -C /usr/local --strip-components=1 \
&& node --version \
&& npm --version \
&& npm install -g yarn
# Node.js 20 LTS — required to execute Forgejo JS actions (checkout, upload-artifact, cache, etc.)
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y nodejs \
&& npm install -g yarn \
&& rm -rf /var/lib/apt/lists/*
# Python 3 + pip + venv
RUN apt-get update && apt-get install -y --no-install-recommends \
@@ -85,15 +83,3 @@ RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
&& apt-get update \
&& apt-get install -y --no-install-recommends gh \
&& rm -rf /var/lib/apt/lists/*
# Cloudflare Access bootstrap for git clones to git.neuralplatform.ai.
# This script is sourced by bash in build containers via BASH_ENV (set by
# act_runner's container.env in deployment.yaml) so it runs before every
# step. It configures git insteadOf + CF Access extraHeaders from
# CF_ACCESS_CLIENT_ID / CF_ACCESS_CLIENT_SECRET env vars.
#
# We deliberately don't set ENTRYPOINT / CMD here — act_runner spawns
# build containers with its own entrypoint to keep them alive between
# steps, and overriding it breaks job execution.
COPY git-cf-access-init.sh /usr/local/bin/git-cf-access-init.sh
RUN chmod +x /usr/local/bin/git-cf-access-init.sh
@@ -8,7 +8,7 @@ metadata:
labels:
app: gitea-runner
annotations:
config-version: "2026-05-04-cf-access-public-url"
config-version: "2026-05-04-public-instance-url"
spec:
replicas: 1
selector:
@@ -19,7 +19,7 @@ spec:
labels:
app: gitea-runner
annotations:
config-version: "2026-05-04-cf-access-public-url"
config-version: "2026-05-04-public-instance-url"
spec:
securityContext:
runAsNonRoot: false
@@ -35,7 +35,7 @@ spec:
--name legion \
--labels "self-hosted:docker://registry.neuralplatform.ai/ci-base:latest,ubuntu-latest:docker://registry.neuralplatform.ai/ci-base:latest,ubuntu-24.04:docker://registry.neuralplatform.ai/ci-base:latest,linux,x64" \
--no-interactive
cat > /data/config.yaml << EOF
cat > /data/config.yaml << 'EOF'
runner:
capacity: 2
timeout: 3h
@@ -45,16 +45,6 @@ spec:
force_pull: false
valid_volumes: []
default_image: "registry.neuralplatform.ai/ci-base:latest"
# Build containers run with network: host. The in-cluster
# gitea name does not resolve there, so we redirect git
# operations to https://git.neuralplatform.ai using CF
# Access service-token headers. BASH_ENV makes bash source
# /usr/local/bin/git-cf-access-init.sh before every step,
# which sets up the redirect + headers.
env:
CF_ACCESS_CLIENT_ID: "${CF_ACCESS_CLIENT_ID}"
CF_ACCESS_CLIENT_SECRET: "${CF_ACCESS_CLIENT_SECRET}"
BASH_ENV: "/usr/local/bin/git-cf-access-init.sh"
extra_hosts:
- "gitea.git.svc.cluster.local:10.43.1.53"
EOF
@@ -102,7 +92,7 @@ metadata:
labels:
app: neuron-technologies-runner
annotations:
config-version: "2026-05-04-cf-access-public-url"
config-version: "2026-05-04-public-instance-url"
spec:
replicas: 2
selector:
@@ -113,7 +103,7 @@ spec:
labels:
app: neuron-technologies-runner
annotations:
config-version: "2026-05-04-cf-access-public-url"
config-version: "2026-05-04-public-instance-url"
spec:
securityContext:
runAsNonRoot: false
@@ -129,7 +119,7 @@ spec:
--name "legion-nt-$(hostname)" \
--labels "self-hosted:docker://registry.neuralplatform.ai/ci-base:latest,ubuntu-latest:docker://registry.neuralplatform.ai/ci-base:latest,ubuntu-24.04:docker://registry.neuralplatform.ai/ci-base:latest,linux,x64" \
--no-interactive
cat > /data/config.yaml << EOF
cat > /data/config.yaml << 'EOF'
runner:
capacity: 2
timeout: 3h
@@ -139,16 +129,6 @@ spec:
force_pull: false
valid_volumes: []
default_image: "registry.neuralplatform.ai/ci-base:latest"
# Build containers run with network: host. The in-cluster
# gitea name does not resolve there, so we redirect git
# operations to https://git.neuralplatform.ai using CF
# Access service-token headers. BASH_ENV makes bash source
# /usr/local/bin/git-cf-access-init.sh before every step,
# which sets up the redirect + headers.
env:
CF_ACCESS_CLIENT_ID: "${CF_ACCESS_CLIENT_ID}"
CF_ACCESS_CLIENT_SECRET: "${CF_ACCESS_CLIENT_SECRET}"
BASH_ENV: "/usr/local/bin/git-cf-access-init.sh"
extra_hosts:
- "gitea.git.svc.cluster.local:10.43.1.53"
EOF
@@ -1,20 +1,12 @@
---
# gitea-runner-secret — neural-platform org runner token
#
# GITEA_INSTANCE_URL stays as the in-cluster URL — the act_runner daemon
# polls it constantly and we don't want every poll to hit Cloudflare Access.
# Build containers, however, need the public URL because they run with
# network: host and can't resolve gitea.git.svc.cluster.local. The
# git-cf-access-init.sh entrypoint in the ci-base image rewrites the
# in-cluster URL to https://git.neuralplatform.ai with the CF Access
# headers from CF_ACCESS_CLIENT_ID / CF_ACCESS_CLIENT_SECRET below.
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: gitea-runner-secret
namespace: ci
annotations:
force-sync: "2026-05-04-cf-access"
force-sync: "2026-04-23"
spec:
refreshInterval: 1h
secretStoreRef:
@@ -25,23 +17,19 @@ spec:
creationPolicy: Owner
template:
data:
GITEA_INSTANCE_URL: "http://gitea.git.svc.cluster.local:3000"
# Public URL — the in-cluster name (gitea.git.svc.cluster.local) is
# not resolvable from build containers running with `network: host`,
# which causes `git fetch` to fail at the very first checkout step.
# The runner polls Gitea over Cloudflare; the latency cost is small
# and the build container's clone URL is derived from this instance,
# so it has to be a name the build container can resolve.
GITEA_INSTANCE_URL: "https://git.neuralplatform.ai"
GITEA_RUNNER_REGISTRATION_TOKEN: "{{ .runner_token }}"
CF_ACCESS_CLIENT_ID: "{{ .cf_access_client_id }}"
CF_ACCESS_CLIENT_SECRET: "{{ .cf_access_client_secret }}"
data:
- secretKey: runner_token
remoteRef:
key: secret/data/gitea
property: runner_token
- secretKey: cf_access_client_id
remoteRef:
key: secret/data/gitea-runner-cf-access
property: client_id
- secretKey: cf_access_client_secret
remoteRef:
key: secret/data/gitea-runner-cf-access
property: client_secret
---
# neuron-technologies-runner-secret — neuron-technologies org runner token
apiVersion: external-secrets.io/v1beta1
@@ -49,8 +37,6 @@ kind: ExternalSecret
metadata:
name: neuron-technologies-runner-secret
namespace: ci
annotations:
force-sync: "2026-05-04-cf-access"
spec:
refreshInterval: 1h
secretStoreRef:
@@ -61,20 +47,11 @@ spec:
creationPolicy: Owner
template:
data:
GITEA_INSTANCE_URL: "http://gitea.git.svc.cluster.local:3000"
# Public URL — see commentary on the gitea-runner-secret above.
GITEA_INSTANCE_URL: "https://git.neuralplatform.ai"
GITEA_RUNNER_REGISTRATION_TOKEN: "{{ .runner_token }}"
CF_ACCESS_CLIENT_ID: "{{ .cf_access_client_id }}"
CF_ACCESS_CLIENT_SECRET: "{{ .cf_access_client_secret }}"
data:
- secretKey: runner_token
remoteRef:
key: secret/data/gitea
property: neuron_technologies_runner_token
- secretKey: cf_access_client_id
remoteRef:
key: secret/data/gitea-runner-cf-access
property: client_id
- secretKey: cf_access_client_secret
remoteRef:
key: secret/data/gitea-runner-cf-access
property: client_secret
@@ -1,50 +0,0 @@
#!/bin/sh
# git-cf-access-init.sh
#
# Configures git so any clone/fetch from Gitea ends up going to
# git.neuralplatform.ai with the runner's Cloudflare Access service-token
# headers attached.
#
# How this gets invoked:
# The forgejo-runner job execution path runs each step via a
# non-interactive bash invocation inside the build container. Setting
# BASH_ENV=/usr/local/bin/git-cf-access-init.sh in act_runner's
# container.env causes bash to source this script before any step's
# commands run. (See servers/legion/k8s/gitea-runner/deployment.yaml.)
#
# What it does:
# 1. Rewrites http://gitea.git.svc.cluster.local:3000/ → https://git.neuralplatform.ai/
# via insteadOf. The runner registered against the in-cluster URL (no
# CF Access on the daemon's polling loop), so act_runner advertises
# that URL to the build container as github.server_url. Build
# containers run with network: host and can't resolve
# *.svc.cluster.local, so we need to redirect to the public URL.
# 2. Adds the CF Access service-token headers to outbound requests to
# git.neuralplatform.ai so the clone authenticates through CF Access.
#
# Idempotent — re-runs replace any prior config keys without accumulating
# duplicate header entries.
#
# Known limitation: actions/checkout sets an Authorization extraheader
# keyed to the server URL it was given (the in-cluster URL). After
# insteadOf substitution the request goes to the public URL where git
# matches http.<public>.extraheader, and the in-cluster-keyed
# Authorization header is dropped. For public repos this is fine. For
# private repos the per-job token will not be sent — see the PR
# description for the follow-up plan if dharma-el's CI needs that token.
if [ -n "${CF_ACCESS_CLIENT_ID:-}" ] && [ -n "${CF_ACCESS_CLIENT_SECRET:-}" ]; then
git config --global --replace-all \
url."https://git.neuralplatform.ai/".insteadOf \
"http://gitea.git.svc.cluster.local:3000/" 2>/dev/null || true
# Reset extraHeader on the public URL, then add both CF Access headers.
git config --global --unset-all \
http."https://git.neuralplatform.ai/".extraHeader 2>/dev/null || true
git config --global --add \
http."https://git.neuralplatform.ai/".extraHeader \
"CF-Access-Client-Id: ${CF_ACCESS_CLIENT_ID}" 2>/dev/null || true
git config --global --add \
http."https://git.neuralplatform.ai/".extraHeader \
"CF-Access-Client-Secret: ${CF_ACCESS_CLIENT_SECRET}" 2>/dev/null || true
fi
@@ -14,6 +14,5 @@ spec:
services:
- name: dharma
port: 8765
# TLS terminates at Cloudflare; tunnel reaches Traefik with noTLSVerify.
# Traefik websecure entrypoint has its own default cert (no resolver
# configured in this cluster), matching every other neuron-prod IngressRoute.
tls:
certResolver: letsencrypt