# CI-Runner auf node1 + CI-Pipeline-Overhaul (2026-06-28) > Operativer Record der CI-Umstellung. Runner-Host-Themen gehören mittelfristig auch nach > `infra/LESSONS.md` (Infra-Repo, Akeshan-Domain) — hier dokumentiert, weil die > `.gitea/workflows/`-Änderungen in diesem Repo liegen. ## Warum: Homeserver OOMte unter paralleler CI Der geteilte Homeserver (32 GB RAM, neben anderen Diensten ~17 GB belegt) ist bei paralleler CI wiederholt abgestürzt: parallele Jobs spikten den RAM auf 30+/32 GB, CPU auf 100%, der Linux-OOM-Killer riss den größten Job → Crash-Recover-Schleife (Runner grabbte sofort den nächsten Job). Parallelisierung auf der geteilten Box war der falsche Hebel. ## Lösung: node1 als dedizierter CI-Runner node1 (`176.9.111.179`, eigener Hetzner-Dedi mit self-hosted Sentry, **20 Kerne / 62 GB**) ist Mesh-Peer und erreicht Gitea über `10.42.0.2`. Gitea-Server bleibt, wo er ist — nur die Runner-Last zieht um. - Compose: `/opt/stacks/gitea-ci-runner/docker-compose.yml` (`network_mode: host`, `--add-host git.tool-ctrl.de:10.42.0.2`, `CONFIG_FILE=/data/config.yaml`). - Config: `/srv/data/gitea-ci-runner/config.yaml` (capacity, labels, cache.dir). - Homeserver-Runner steht auf `restart=no` (aus, kein Auto-Restart, keine OOM-Schleife). ### act_runner-Footguns (alle real getroffen) | Symptom | Ursache | Fix | |---|---|---| | `runner.capacity` wird ignoriert | run.sh überschreibt den compose-`command`, läuft ohne `--config` | env `CONFIG_FILE=/data/config.yaml` setzen | | "no matching runner online with label toolctrl-ci" | leere `runner.labels` → KEIN Fallback auf `.runner` | Labels EXPLIZIT in config.yaml | | Daemon "token is empty" trotz erfolgreicher Registrierung | `act_runner register` schrieb `.runner` ins Container-CWD → bei `--rm` weg | `docker run -w /data ...` beim Register | | Register "instance address is empty" | run.sh ignoriert CLI-Args, nutzt leeres `GITEA_INSTANCE_URL` | `--entrypoint act_runner` umgeht run.sh | | pnpm-Store-Cache greift nie (Cache-Dir 12K) | `cache.dir` default ephemer im Container, Restarts wischen ihn | `cache.dir: /data/actcache` (gemountet) | ## Capacity-Findings (empirisch gemessen) | Setup | Wallclock | RAM-Peak frei | Notiz | |---|---|---|---| | cap 4, docker-smoke auf Tail | ~10m | 35 GB | | | cap 6 | ~8m | 23-25 GB | | | cap 6 + docker-smoke parallel | ~7m35s | 23 GB | | | **cap 8 + docker-smoke parallel** | **~6m53s** | **25 GB** | Sweet-Spot | **cap 8 ist schneller als cap 6** trotz mehr Contention: Install/Netzwerk/Docker sind I/O-bound, die 20 Kerne tragen 8 Jobs; die Queueing-Reduktion schlägt die Mehr-Contention. cap >8 bringt nichts (Kerne saturiert). Reise gesamt: **~32m → ~6m53s**. ## Pipeline-Änderungen (.gitea/workflows/) - **docker-smoke `needs` entfernt** → läuft parallel zur Quality-Phase (baut Images aus Source, keine Cross-Job-Artefakt-Abhängigkeit), raus aus dem kritischen Tail. - **Sentry-Release-Jobs aus ci.yml entfernt** (ci.yml läuft nur auf `pull_request`; ein PR-Build wird nirgends deployed → die Release/Sourcemaps mappen auf keine Deployment). Release passiert in `deploy.yml` (push→main / promote). ## Install-Diagnose (warum KEIN Deps-Image) Gemessen: Install = **~43s Download + ~15s Link** (warmer Store). Die ~2-min-Annahme war zu hoch. Ein Deps-Image würde nur die 15s Link sparen → verworfen. Der Hebel war der **kalte Store** (Cache persistierte nicht) → mit `cache.dir` gefixt. ## Offene Punkte - **Sourcemaps im Deploy:** web nutzt `@sentry/vite-plugin` (lädt Sourcemaps beim Build hoch, wenn `SENTRY_AUTH_TOKEN` gesetzt). To-do: Token als BuildKit-`--secret` an den `web-build`-Dockerfile-Stage durchreichen (NICHT build-arg → kein Leak) + `SENTRY_PROJECT=web` explizit (Default ist alter Slug `hivegrid-web`). Nur auf echtem Deploy testbar → bis Sentry-Go-Live aufgeschoben. - **Persistenter Store-Cache:** `cache.dir` gesetzt + befüllt (2,9 GB), aber Hit-Gewinn bei cap 8 noch ungemessen (Per-Job-Restore-I/O könnte Download-Gewinn auffressen → ggf. auf geteilten gemounteten Store umstellen statt actions/cache).