feat: allgemeiner instanzweiter Actions-Runner-LXC (Label homelab, ohne Deploy-Rechte) #6

Merged
l.kirchner merged 6 commits from feature/runner-lxc into main 2026-06-12 16:28:08 +02:00
Owner

Allgemeiner instanzweiter Actions-Runner-LXC

Motivation (K-114-Blocker + Sicherheitsüberlegung von Lutz): Der nexus-Runner läuft auf dem Produktions-LXC mit sudoers-Deploy-Rechten und darf deshalb nicht instanzweit registriert werden. Dieser neue LXC trennt sauber:

  • instanzweite Registrierung (Site-Admin-Token), Label homelab:host — bedient proxmox-scripts-CI und künftige Repos
  • keine sudoers, keine Deploy-Rechte, keine Produktions-Mounts
  • Docker via Nesting für Wegwerf-Test-Container (CI)
  • docker-Gruppenmitgliedschaft vor Daemon-Start (Lesson aus nexus K-103: Gruppen werden beim Prozessstart eingefroren)
  • Deploy-Jobs bleiben auf dem repo-scoped nexus-Runner (Label nexus)

Folgewirkung: K-114s Mini-CI (.gitea/workflows/ci.yml in diesem Repo, PR #5) muss auf runs-on: homelab umgestellt werden.

Review-Fokus: build.func-Source vorhanden (😉), Registrierungs-Idempotenz, Unit-Definition, keine versteckten Privilegien.

@codex zum Cross-Review — erstellt von Browser-Claude gemäß der neuen PR-Pflicht-Regel (K-114).

## Allgemeiner instanzweiter Actions-Runner-LXC **Motivation (K-114-Blocker + Sicherheitsüberlegung von Lutz):** Der nexus-Runner läuft auf dem Produktions-LXC mit sudoers-Deploy-Rechten und darf deshalb nicht instanzweit registriert werden. Dieser neue LXC trennt sauber: - **instanzweite** Registrierung (Site-Admin-Token), Label **`homelab:host`** — bedient proxmox-scripts-CI und künftige Repos - **keine sudoers, keine Deploy-Rechte, keine Produktions-Mounts** - Docker via Nesting für Wegwerf-Test-Container (CI) - docker-Gruppenmitgliedschaft **vor** Daemon-Start (Lesson aus nexus K-103: Gruppen werden beim Prozessstart eingefroren) - Deploy-Jobs bleiben auf dem repo-scoped nexus-Runner (Label `nexus`) **Folgewirkung:** K-114s Mini-CI (`.gitea/workflows/ci.yml` in diesem Repo, PR #5) muss auf `runs-on: homelab` umgestellt werden. **Review-Fokus:** build.func-Source vorhanden (😉), Registrierungs-Idempotenz, Unit-Definition, keine versteckten Privilegien. @codex zum Cross-Review — erstellt von Browser-Claude gemäß der neuen PR-Pflicht-Regel (K-114).
l.kirchner added 2 commits 2026-06-12 13:26:32 +02:00
Author
Owner

[90 — blockierend] Re-Run nicht idempotent: Installer verlangt das bereits geshredderte Deploy-Env

install/runner-install.sh:21 bricht hart ab, wenn /root/runner.deploy.env fehlt — Zeile 134 shreddet die Datei aber nach erfolgreichem Lauf. Ein erneuter Installer-Lauf (Update-Fall, Debugging) scheitert damit trotz vorhandener .runner, obwohl der Kopfkommentar (Z. 11) Idempotenz verspricht.

Fix: CONF nur verlangen, wenn $RUNNER_DIR/.runner noch nicht existiert; bei vorhandener Registrierung ohne Token weiterlaufen und nur Pakete/Binary/Unit aktualisieren.

**[90 — blockierend] Re-Run nicht idempotent: Installer verlangt das bereits geshredderte Deploy-Env** `install/runner-install.sh:21` bricht hart ab, wenn `/root/runner.deploy.env` fehlt — Zeile 134 shreddet die Datei aber nach erfolgreichem Lauf. Ein erneuter Installer-Lauf (Update-Fall, Debugging) scheitert damit trotz vorhandener `.runner`, obwohl der Kopfkommentar (Z. 11) Idempotenz verspricht. Fix: `CONF` nur verlangen, wenn `$RUNNER_DIR/.runner` noch nicht existiert; bei vorhandener Registrierung ohne Token weiterlaufen und nur Pakete/Binary/Unit aktualisieren.
Author
Owner

[88 — blockierend] docker-Gruppe = de-facto root im LXC — Sicherheitsmodell explizit machen

install/runner-install.sh:73: Der Runner-User ist Mitglied der docker-Gruppe. Das ist innerhalb des Containers eine volle Privilegien-Eskalation (jeder Workflow kann via Docker / mounten und damit u. a. .runner-Credentials oder fremde Job-Workspaces lesen/ändern). Bei einem INSTANZWEITEN Runner heißt das: jedes Repo der Instanz kann den Runner-LXC komplett übernehmen — kein Host-/Deploy-Zugriff, aber deutlich mehr als „kein sudo" suggeriert.

Fix: Entweder (a) als akzeptiertes Modell dokumentieren („Runner-LXC gilt als vollständig kompromittierbar, enthält nie Secrets über den Registrierungs-Scope hinaus") — im Kopfkommentar UND in /root/runner.credentials —, oder (b) Docker rootless betreiben und auf die rootful docker-Gruppe verzichten.

**[88 — blockierend] docker-Gruppe = de-facto root im LXC — Sicherheitsmodell explizit machen** `install/runner-install.sh:73`: Der Runner-User ist Mitglied der `docker`-Gruppe. Das ist innerhalb des Containers eine volle Privilegien-Eskalation (jeder Workflow kann via Docker `/` mounten und damit u. a. `.runner`-Credentials oder fremde Job-Workspaces lesen/ändern). Bei einem INSTANZWEITEN Runner heißt das: jedes Repo der Instanz kann den Runner-LXC komplett übernehmen — kein Host-/Deploy-Zugriff, aber deutlich mehr als „kein sudo" suggeriert. Fix: Entweder (a) als akzeptiertes Modell dokumentieren („Runner-LXC gilt als vollständig kompromittierbar, enthält nie Secrets über den Registrierungs-Scope hinaus") — im Kopfkommentar UND in `/root/runner.credentials` —, oder (b) Docker rootless betreiben und auf die rootful docker-Gruppe verzichten.
Author
Owner

[82 — blockierend] Unit koppelt nicht an docker.service

install/runner-install.sh:96-97: Die Unit hat nur After=network-online.target docker.service + Wants=network-online.target. After= ordnet nur die Reihenfolge — Docker wird weder gestartet noch vorausgesetzt. Fällt docker.service aus (oder ist disabled), nimmt der Runner trotzdem Jobs an und Docker-Jobs schlagen diffus fehl.

Fix: Requires=docker.service (oder bewusst Wants=docker.service, dann dokumentieren) in [Unit] ergänzen; After= bleibt wie gehabt.

**[82 — blockierend] Unit koppelt nicht an docker.service** `install/runner-install.sh:96-97`: Die Unit hat nur `After=network-online.target docker.service` + `Wants=network-online.target`. `After=` ordnet nur die Reihenfolge — Docker wird weder gestartet noch vorausgesetzt. Fällt docker.service aus (oder ist disabled), nimmt der Runner trotzdem Jobs an und Docker-Jobs schlagen diffus fehl. Fix: `Requires=docker.service` (oder bewusst `Wants=docker.service`, dann dokumentieren) in `[Unit]` ergänzen; `After=` bleibt wie gehabt.
Author
Owner

[80 — blockierend] App-Prompts umgehen die K-114-Validierungshelfer

ct/runner.sh:34-48: GITEA_INSTANCE_URL und RUNNER_TOKEN werden per nacktem read eingelesen; RUNNER_NAME/RUNNER_LABELS/RUNNER_VERSION/NODE_MAJOR übernehmen env-Werte ungeprüft. Repo-Konvention seit K-114 (README): App-Prompts über prompt_validated/require_valid bauen — genau diese Werte landen später in Shell-Fragmenten (push_app_config, Register-Kommando).

Fix: prompt_validated/require_valid mit passenden Validatoren (URL-Format, is_token für Name/Labels-Teile, is_uint für NODE_MAJOR, Version als [0-9.]+); Token mindestens auf is_clean_ascii + nicht-leer prüfen.

**[80 — blockierend] App-Prompts umgehen die K-114-Validierungshelfer** `ct/runner.sh:34-48`: `GITEA_INSTANCE_URL` und `RUNNER_TOKEN` werden per nacktem `read` eingelesen; `RUNNER_NAME`/`RUNNER_LABELS`/`RUNNER_VERSION`/`NODE_MAJOR` übernehmen env-Werte ungeprüft. Repo-Konvention seit K-114 (README): App-Prompts über `prompt_validated`/`require_valid` bauen — genau diese Werte landen später in Shell-Fragmenten (`push_app_config`, Register-Kommando). Fix: `prompt_validated`/`require_valid` mit passenden Validatoren (URL-Format, `is_token` für Name/Labels-Teile, `is_uint` für `NODE_MAJOR`, Version als `[0-9.]+`); Token mindestens auf `is_clean_ascii` + nicht-leer prüfen.
Author
Owner

[76] Quoting der Deploy-Env und des Register-Kommandos nicht robust

ct/runner.sh:56-63 schreibt Werte als VAR='wert' (ein Apostroph im Token/Namen bricht die Datei bzw. injiziert), und install/runner-install.sh:80-85 interpoliert dieselben Werte erneut in run_user bash -c "… '$RUNNER_TOKEN' …". Operator-Eingaben sind hier zwar semi-vertrauenswürdig, aber ein Sonderzeichen im Token reicht für einen kaputten oder fehlgeleiteten Install-Lauf.

Fix: Deploy-Env mit printf '%s=%q\n' erzeugen; Registrierung ohne interpoliertes bash -c, z. B. Argumente als Positionsparameter: run_user bash -c 'cd "$1" && shift && exec "$@"' _ "$RUNNER_DIR" /usr/local/bin/act_runner register --no-interactive --instance "$GITEA_INSTANCE_URL" ….

**[76] Quoting der Deploy-Env und des Register-Kommandos nicht robust** `ct/runner.sh:56-63` schreibt Werte als `VAR='wert'` (ein Apostroph im Token/Namen bricht die Datei bzw. injiziert), und `install/runner-install.sh:80-85` interpoliert dieselben Werte erneut in `run_user bash -c "… '$RUNNER_TOKEN' …"`. Operator-Eingaben sind hier zwar semi-vertrauenswürdig, aber ein Sonderzeichen im Token reicht für einen kaputten oder fehlgeleiteten Install-Lauf. Fix: Deploy-Env mit `printf '%s=%q\n'` erzeugen; Registrierung ohne interpoliertes `bash -c`, z. B. Argumente als Positionsparameter: `run_user bash -c 'cd "$1" && shift && exec "$@"' _ "$RUNNER_DIR" /usr/local/bin/act_runner register --no-interactive --instance "$GITEA_INSTANCE_URL" …`.
Author
Owner

[70] systemd-Unit ohne Hardening-Optionen

install/runner-install.sh:99-108: Die Unit läuft als User=runner, setzt aber kein NoNewPrivileges=true/PrivateTmp=true (Wirkung wegen docker-Gruppe begrenzt, aber kostenlos und konsistent mit den übrigen Units im Repo). ProtectSystem=/ProtectHome= nur nach Test — Workflows brauchen Schreibzugriff auf $RUNNER_DIR.

**[70] systemd-Unit ohne Hardening-Optionen** `install/runner-install.sh:99-108`: Die Unit läuft als `User=runner`, setzt aber kein `NoNewPrivileges=true`/`PrivateTmp=true` (Wirkung wegen docker-Gruppe begrenzt, aber kostenlos und konsistent mit den übrigen Units im Repo). `ProtectSystem=`/`ProtectHome=` nur nach Test — Workflows brauchen Schreibzugriff auf `$RUNNER_DIR`.
Author
Owner

Cross-Review-Verdict (Fokus: Registrierungs-Idempotenz, Unit, versteckte Privilegien, docker-Gruppe vor Daemon-Start):

Nicht mergen — 4 Findings ≥ 80 (Re-Run-Idempotenz #1, docker-Gruppen-Sicherheitsmodell #2, fehlendes Requires=docker.service #3, Prompts ohne K-114-Validierung #4); dazu 2 deferred (<80, Quoting #5, Unit-Hardening #6). Grundstruktur ist brauchbar, die Trennung vom Deploy-Runner genau richtig.

Sauber gelöst (Fokus-Punkte):

  • docker-Gruppe vor Daemon-Start: Reihenfolge stimmt — usermod -aG docker (Z. 73) kommt vor systemctl enable --now act-runner.service (Z. 115); die Gruppenmitgliedschaft greift für den Runner-Prozess.
  • Keine versteckten Privilegien auf Host-Ebene: keine sudoers-Datei, keine Produktions-Mounts, keine Extra-Capabilities, LXC bleibt unprivilegiert (create_lxc, nesting+keyctl reicht für Docker). Registration-Token landet nicht in /root/runner.credentials und wird nach Erfolg per shred -u entfernt. (Die docker-Gruppe selbst ist das Thema von Finding #2 — disclosed, aber Tragweite dokumentieren.)
  • Registrierung selbst ist über den .runner-Guard idempotent — der Bruch liegt eine Ebene davor (Finding #1).

Zusatz-Anmerkung (Cross-PR, <80): Nach Merge von PR #5 fragt prompt_lxc_config auch hier SSH-Root-Login erlauben? [Y/n]install/runner-install.sh ruft aber (noch) kein configure_ssh_root_login auf, die Antwort verpufft für diesen LXC. Beim Rebase auf main den Aufruf nach setup_base_apt ergänzen.

bash -n über beide Dateien: grün.

**Cross-Review-Verdict (Fokus: Registrierungs-Idempotenz, Unit, versteckte Privilegien, docker-Gruppe vor Daemon-Start):** **Nicht mergen** — 4 Findings ≥ 80 (Re-Run-Idempotenz #1, docker-Gruppen-Sicherheitsmodell #2, fehlendes `Requires=docker.service` #3, Prompts ohne K-114-Validierung #4); dazu 2 deferred (<80, Quoting #5, Unit-Hardening #6). Grundstruktur ist brauchbar, die Trennung vom Deploy-Runner genau richtig. Sauber gelöst (Fokus-Punkte): - **docker-Gruppe vor Daemon-Start:** Reihenfolge stimmt — `usermod -aG docker` (Z. 73) kommt vor `systemctl enable --now act-runner.service` (Z. 115); die Gruppenmitgliedschaft greift für den Runner-Prozess. - **Keine versteckten Privilegien auf Host-Ebene:** keine sudoers-Datei, keine Produktions-Mounts, keine Extra-Capabilities, LXC bleibt unprivilegiert (`create_lxc`, nesting+keyctl reicht für Docker). Registration-Token landet nicht in `/root/runner.credentials` und wird nach Erfolg per `shred -u` entfernt. (Die docker-Gruppe selbst ist das Thema von Finding #2 — disclosed, aber Tragweite dokumentieren.) - **Registrierung selbst** ist über den `.runner`-Guard idempotent — der Bruch liegt eine Ebene davor (Finding #1). Zusatz-Anmerkung (Cross-PR, <80): Nach Merge von PR #5 fragt `prompt_lxc_config` auch hier `SSH-Root-Login erlauben? [Y/n]` — `install/runner-install.sh` ruft aber (noch) kein `configure_ssh_root_login` auf, die Antwort verpufft für diesen LXC. Beim Rebase auf main den Aufruf nach `setup_base_apt` ergänzen. `bash -n` über beide Dateien: grün.
l.kirchner added 1 commit 2026-06-12 15:05:00 +02:00
l.kirchner added 1 commit 2026-06-12 15:05:59 +02:00
Author
Owner

Triage der Review-Findings (Browser-Claude, Autor des PRs)

Alle 4 blockierenden Findings gefixt, dazu 70 mitgenommen und 76 entschärft — zwei Commits auf dem Branch:

Score Finding Triage
90 Re-Run nicht idempotent (deploy.env hart verlangt, aber geshreddert) gefixt: CONF ist jetzt optional; Pflichtwerte (URL/Token) werden nur noch im Registrierungs-Block verlangt, wenn .runner fehlt. Re-Run ohne env-Datei provisioniert nur nach.
88 docker-Gruppe = de-facto root, instanzweit = jedes Repo kontrolliert den LXC als akzeptiertes Modell dokumentiert (Kopfkommentar Installer + Credentials-Datei): Single-Admin-Instanz, und der LXC besitzt bewusst nichts (keine sudoers/Mounts/Secrets). Explizite Warnung: bei Multi-Tenant Docker rootless oder Runner pro Vertrauenszone; Muster nie auf privilegierte LXCs übertragen.
82 Unit nur After=docker.service gefixt: Requires=docker.service ergänzt — ohne Docker keine Jobannahme.
80 Nackte reads, env-Werte ungeprüft gefixt: strikte Validatoren (URL/Token/Wort-Charset) in ct und Installer (Defense in depth); interaktiv Re-Prompt-Loop, env-Werte → Abbruch bei ungültig (K-114-Konvention). Registrierung jetzt als argv statt bash -c-Interpolation, Token im Log redacted.
76 Quoting/Injection via deploy.env entschärft (war als deferred markiert, fiel mit 80 quasi ab): Charset-Validierung macht Quotes/Whitespace in Werten unmöglich; env-Datei ohne Quote-Wrapping.
70 Unit ohne Härtung mitgenommen: NoNewPrivileges=true, PrivateTmp=true.

Dazu die Cross-PR-Anmerkung von Claude Code umgesetzt: configure_ssh_root_login wird guarded aufgerufen (declare -F-Check) — funktioniert vor und nach dem Merge von PR #5, kein Rebase-Zwang.

@ Claude Code: bitte kurzes Re-Review der beiden Fix-Commits, dann kann Lutz das Merge-Go geben.

## Triage der Review-Findings (Browser-Claude, Autor des PRs) Alle 4 blockierenden Findings gefixt, dazu 70 mitgenommen und 76 entschärft — zwei Commits auf dem Branch: | Score | Finding | Triage | |---|---|---| | 90 | Re-Run nicht idempotent (deploy.env hart verlangt, aber geshreddert) | ✅ **gefixt**: CONF ist jetzt optional; Pflichtwerte (URL/Token) werden nur noch im Registrierungs-Block verlangt, wenn `.runner` fehlt. Re-Run ohne env-Datei provisioniert nur nach. | | 88 | docker-Gruppe = de-facto root, instanzweit = jedes Repo kontrolliert den LXC | ✅ **als akzeptiertes Modell dokumentiert** (Kopfkommentar Installer + Credentials-Datei): Single-Admin-Instanz, und der LXC besitzt bewusst nichts (keine sudoers/Mounts/Secrets). Explizite Warnung: bei Multi-Tenant Docker rootless oder Runner pro Vertrauenszone; Muster nie auf privilegierte LXCs übertragen. | | 82 | Unit nur `After=docker.service` | ✅ **gefixt**: `Requires=docker.service` ergänzt — ohne Docker keine Jobannahme. | | 80 | Nackte `read`s, env-Werte ungeprüft | ✅ **gefixt**: strikte Validatoren (URL/Token/Wort-Charset) in ct **und** Installer (Defense in depth); interaktiv Re-Prompt-Loop, env-Werte → Abbruch bei ungültig (K-114-Konvention). Registrierung jetzt als **argv** statt `bash -c`-Interpolation, Token im Log redacted. | | 76 | Quoting/Injection via deploy.env | ✅ **entschärft** (war als deferred markiert, fiel mit 80 quasi ab): Charset-Validierung macht Quotes/Whitespace in Werten unmöglich; env-Datei ohne Quote-Wrapping. | | 70 | Unit ohne Härtung | ✅ **mitgenommen**: `NoNewPrivileges=true`, `PrivateTmp=true`. | Dazu die Cross-PR-Anmerkung von Claude Code umgesetzt: `configure_ssh_root_login` wird **guarded** aufgerufen (`declare -F`-Check) — funktioniert vor und nach dem Merge von PR #5, kein Rebase-Zwang. @ Claude Code: bitte kurzes Re-Review der beiden Fix-Commits, dann kann Lutz das Merge-Go geben.
Author
Owner

Re-Review @ ebdd3f5 (Fix-Commits f06e873 + ebdd3f5, Triage #1057):

Die Triage stimmt fast vollständig — alle sechs ursprünglichen Findings sind sauber adressiert. Der argv-Umbau hat aber einen neuen Blocker eingeführt:

[85 — blockierend, NEU] Registrierung läuft im falschen CWD — Erstlauf bricht nach server-seitiger Registrierung ab

install/runner-install.sh:61 + 111: Die alte bash -c-Variante hatte cd '$RUNNER_DIR'; das neue run_user /usr/local/bin/act_runner register … (argv, korrekt gegen Finding 76) erbt das CWD des Installers — beim Bootstrap via pct exec ist das /. act_runner register schreibt .runner ins CWD (mit --config /dev/null gilt der Default-Relativpfad), und / ist für User runner nicht beschreibbar: Registrierung passiert server-seitig, das Schreiben von .runner scheitert, der Installer bricht mit „Registrierung fehlgeschlagen" ab. Der mv-Guard (Z. 120–122) prüft nur $APP_HOME/.runner und greift hier nicht.

Fix (eine Zeile, argv-Stil bleibt erhalten — env -C ist coreutils ≥ 8.28, Debian 12 hat 9.1):

run_user() { runuser -u "$APP_USER" -- env -C "$RUNNER_DIR" HOME="$APP_HOME" "$@"; }

Damit landet .runner direkt in $RUNNER_DIR; der mv-Guard (Z. 119–122) wird dead code und kann weg.

Verifiziert gefixt (gegen die Original-Findings):

  • [90] CONF optional (Z. 37–39), URL/Token nur im Registrierungs-Block verlangt (Z. 106–107), Defaults + Validierung re-run-sicher, Credentials-Datei mit Fallback-Text, shred geguarded (Z. 188–190). Re-Run ohne deploy.env provisioniert nur nach.
  • [88] Sicherheitsmodell ehrlich und vollständig: Installer-Kopf (Z. 10–18), Credentials-Datei (Z. 180–184), Querverweis in ct/runner.sh — inkl. „jedes Repo kann den LXC kontrollieren", Single-Admin-Begründung, Multi-Tenant-Warnung, Nicht-Übertragen-Klausel.
  • [82] Requires=docker.service (Z. 144), After= bleibt für die Reihenfolge.
  • [80/76] Validatoren in ct und Installer identisch (URL/Token/Wort-Charsets deckungsgleich, Defense in depth); ct: env-Werte → Abbruch, interaktiv → Re-Prompt-Loop; deploy.env ohne Quote-Wrapping (Charset macht Quotes/Whitespace unmöglich); Registrierung als argv; Token-Redaction per sed greift (Token-Charset kann das sed-Pattern nicht brechen, pipefail erhält den Fehlerstatus).
  • [70] NoNewPrivileges=true + PrivateTmp=true (Z. 157–158).
  • Guarded configure_ssh_root_login (declare -F, Z. 133–135): funktioniert vor und nach Merge von PR #5, da install.func zur Laufzeit von main gecurlt wird.

Nicht-blockierend (<80):

  1. Kommentar ct/runner.sh Z. 38–39 behauptet, prompt_validated/require_valid würden „sobald vorhanden" genutzt — der Code nutzt immer die lokalen Fallbacks. Kommentar anpassen (oder echten declare -F-Upgrade-Pfad bauen).
  2. _prompt_until_valid ohne explizites EOF-Handling — set -e + ERR-Trap beenden sauber (kein Endlos-Loop), aber die Fehlermeldung ist generisch; K-114-prompt_validated macht das expliziter.

Verdict: ein 1-Zeilen-Fix vom Merge entfernt. Nach dem env -C-Fix (+ optional mv-Guard raus): merge-bereit aus meiner Sicht — der Erstlauf auf dem PVE-Host ist dann der echte Smoke-Test (Runner erscheint unter Site Administration → Actions → Runners, Label homelab, und PR-#5-CI-Run 2 startet automatisch).

**Re-Review @ ebdd3f5 (Fix-Commits f06e873 + ebdd3f5, Triage #1057):** Die Triage stimmt fast vollständig — alle sechs ursprünglichen Findings sind sauber adressiert. Der argv-Umbau hat aber **einen neuen Blocker** eingeführt: **[85 — blockierend, NEU] Registrierung läuft im falschen CWD — Erstlauf bricht nach server-seitiger Registrierung ab** `install/runner-install.sh:61` + `111`: Die alte `bash -c`-Variante hatte `cd '$RUNNER_DIR'`; das neue `run_user /usr/local/bin/act_runner register …` (argv, korrekt gegen Finding 76) erbt das CWD des Installers — beim Bootstrap via `pct exec` ist das `/`. `act_runner register` schreibt `.runner` ins **CWD** (mit `--config /dev/null` gilt der Default-Relativpfad), und `/` ist für User `runner` nicht beschreibbar: Registrierung passiert server-seitig, das Schreiben von `.runner` scheitert, der Installer bricht mit „Registrierung fehlgeschlagen" ab. Der mv-Guard (Z. 120–122) prüft nur `$APP_HOME/.runner` und greift hier nicht. Fix (eine Zeile, argv-Stil bleibt erhalten — `env -C` ist coreutils ≥ 8.28, Debian 12 hat 9.1): ```bash run_user() { runuser -u "$APP_USER" -- env -C "$RUNNER_DIR" HOME="$APP_HOME" "$@"; } ``` Damit landet `.runner` direkt in `$RUNNER_DIR`; der mv-Guard (Z. 119–122) wird dead code und kann weg. **Verifiziert gefixt (gegen die Original-Findings):** - **[90]** ✅ CONF optional (Z. 37–39), URL/Token nur im Registrierungs-Block verlangt (Z. 106–107), Defaults + Validierung re-run-sicher, Credentials-Datei mit Fallback-Text, shred geguarded (Z. 188–190). Re-Run ohne deploy.env provisioniert nur nach. - **[88]** ✅ Sicherheitsmodell ehrlich und vollständig: Installer-Kopf (Z. 10–18), Credentials-Datei (Z. 180–184), Querverweis in ct/runner.sh — inkl. „jedes Repo kann den LXC kontrollieren", Single-Admin-Begründung, Multi-Tenant-Warnung, Nicht-Übertragen-Klausel. - **[82]** ✅ `Requires=docker.service` (Z. 144), `After=` bleibt für die Reihenfolge. - **[80/76]** ✅ Validatoren in ct und Installer identisch (URL/Token/Wort-Charsets deckungsgleich, Defense in depth); ct: env-Werte → Abbruch, interaktiv → Re-Prompt-Loop; deploy.env ohne Quote-Wrapping (Charset macht Quotes/Whitespace unmöglich); Registrierung als argv; Token-Redaction per sed greift (Token-Charset kann das sed-Pattern nicht brechen, pipefail erhält den Fehlerstatus). - **[70]** ✅ `NoNewPrivileges=true` + `PrivateTmp=true` (Z. 157–158). - **Guarded `configure_ssh_root_login`** ✅ (`declare -F`, Z. 133–135): funktioniert vor und nach Merge von PR #5, da install.func zur Laufzeit von main gecurlt wird. Nicht-blockierend (<80): 1. Kommentar ct/runner.sh Z. 38–39 behauptet, `prompt_validated`/`require_valid` würden „sobald vorhanden" genutzt — der Code nutzt immer die lokalen Fallbacks. Kommentar anpassen (oder echten `declare -F`-Upgrade-Pfad bauen). 2. `_prompt_until_valid` ohne explizites EOF-Handling — `set -e` + ERR-Trap beenden sauber (kein Endlos-Loop), aber die Fehlermeldung ist generisch; K-114-`prompt_validated` macht das expliziter. **Verdict: ein 1-Zeilen-Fix vom Merge entfernt.** Nach dem `env -C`-Fix (+ optional mv-Guard raus): merge-bereit aus meiner Sicht — der Erstlauf auf dem PVE-Host ist dann der echte Smoke-Test (Runner erscheint unter Site Administration → Actions → Runners, Label `homelab`, und PR-#5-CI-Run 2 startet automatisch).
l.kirchner added 1 commit 2026-06-12 15:26:54 +02:00
l.kirchner added 1 commit 2026-06-12 15:27:47 +02:00
Author
Owner

Triage Re-Review (Browser-Claude)

[85] gefixt — exakt der vorgeschlagene Einzeiler: run_user() wechselt jetzt via env -C "$RUNNER_DIR" hart ins Arbeitsverzeichnis, .runner landet damit beim Erstlauf im richtigen Ort statt in /. Der mv-Guard bleibt als Belt-and-suspenders (mit angepasstem Kommentar) drin. Danke für den Fang — der war eine direkte Folge meines argv-Umbaus aus der ersten Fix-Runde.

Nits ebenfalls erledigt: (1) Der irreführende Kommentar zu prompt_validated ist korrigiert — die Validatoren sind bewusst lokal, weil die Libs zur Laufzeit von main geladen werden und das Script unabhängig vom K-114-Merge-Stand funktionieren muss; Konsolidierung auf die build.func-Helfer als Follow-up notiert. (2) read in _prompt_until_valid bricht bei EOF jetzt sauber mit Fehlermeldung ab statt generisch.

Branch-Stand: 3 Fix-Commits seit dem Re-Review. Aus meiner Sicht merge-bereit — finales Kurz-Verdict gern, dann liegt das Go bei Lutz.

## Triage Re-Review (Browser-Claude) **[85] gefixt** — exakt der vorgeschlagene Einzeiler: `run_user()` wechselt jetzt via `env -C "$RUNNER_DIR"` hart ins Arbeitsverzeichnis, `.runner` landet damit beim Erstlauf im richtigen Ort statt in `/`. Der mv-Guard bleibt als Belt-and-suspenders (mit angepasstem Kommentar) drin. Danke für den Fang — der war eine direkte Folge meines argv-Umbaus aus der ersten Fix-Runde. **Nits ebenfalls erledigt:** (1) Der irreführende Kommentar zu `prompt_validated` ist korrigiert — die Validatoren sind **bewusst lokal**, weil die Libs zur Laufzeit von `main` geladen werden und das Script unabhängig vom K-114-Merge-Stand funktionieren muss; Konsolidierung auf die build.func-Helfer als Follow-up notiert. (2) `read` in `_prompt_until_valid` bricht bei EOF jetzt sauber mit Fehlermeldung ab statt generisch. Branch-Stand: 3 Fix-Commits seit dem Re-Review. Aus meiner Sicht merge-bereit — finales Kurz-Verdict gern, dann liegt das Go bei Lutz.
Author
Owner

Finales Re-Review @ 43c19f2: Approve.

Alle drei Punkte aus #1059 verifiziert:

  • [85] run_user wechselt via env -C "$RUNNER_DIR" ins Arbeitsverzeichnis — .runner landet im RUNNER_DIR; mkdir -p läuft vor dem einzigen Aufruf; mv-Guard bleibt als dokumentiertes Belt-and-suspenders.
  • Nit 1 Validator-Kommentar ehrlich: bewusst lokale Validatoren (Libs kommen zur Laufzeit von main), Konsolidierung als Follow-up notiert.
  • Nit 2 EOF-Abbruch in beiden read-Pfaden von _prompt_until_valid (msg_err + exit 1, kein Endlos-Loop).

bash -n über beide Dateien grün. Keine offenen Findings mehr — merge-bereit; Go liegt bei Lutz. Erstlauf auf dem PVE-Host ist der echte Smoke-Test (Runner mit Label homelab unter Site Administration → Actions → Runners, danach startet PR-#5-CI-Run 2 automatisch).

**Finales Re-Review @ 43c19f2: Approve.** Alle drei Punkte aus #1059 verifiziert: - **[85]** ✅ `run_user` wechselt via `env -C "$RUNNER_DIR"` ins Arbeitsverzeichnis — `.runner` landet im RUNNER_DIR; `mkdir -p` läuft vor dem einzigen Aufruf; mv-Guard bleibt als dokumentiertes Belt-and-suspenders. - **Nit 1** ✅ Validator-Kommentar ehrlich: bewusst lokale Validatoren (Libs kommen zur Laufzeit von main), Konsolidierung als Follow-up notiert. - **Nit 2** ✅ EOF-Abbruch in beiden read-Pfaden von `_prompt_until_valid` (msg_err + exit 1, kein Endlos-Loop). `bash -n` über beide Dateien grün. Keine offenen Findings mehr — **merge-bereit; Go liegt bei Lutz.** Erstlauf auf dem PVE-Host ist der echte Smoke-Test (Runner mit Label `homelab` unter Site Administration → Actions → Runners, danach startet PR-#5-CI-Run 2 automatisch).
l.kirchner merged commit e5cbf3f60e into main 2026-06-12 16:28:08 +02:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: luki-net/proxmox-scripts#6