From 2941afa53f7ab0762151755fc15aa0d9c51f0647 Mon Sep 17 00:00:00 2001 From: "l.kirchner" Date: Fri, 12 Jun 2026 13:25:10 +0200 Subject: [PATCH 1/6] feat(runner): allgemeiner instanzweiter Actions-Runner-LXC (Label homelab, ohne Deploy-Rechte) --- ct/runner.sh | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 ct/runner.sh diff --git a/ct/runner.sh b/ct/runner.sh new file mode 100644 index 0000000..f2ff8a7 --- /dev/null +++ b/ct/runner.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +# Allgemeiner Gitea-Actions-Runner — instanzweit, OHNE Deploy-Rechte +# +# Motivation (nexus K-114-Blocker): Der nexus-Runner läuft auf dem +# Produktions-LXC und besitzt sudoers-Deploy-Rechte — er darf deshalb NICHT +# instanzweit registriert werden (jedes Repo könnte sonst Workflows auf der +# Produktionsmaschine ausführen). Dieser LXC ist die saubere Trennung: +# - instanzweite Registrierung (Site Administration → Actions → Runners) +# - Label "homelab:host" (Deploy-Jobs bleiben auf "nexus") +# - KEINE sudoers-Regeln, kein Zugriff auf Produktions-Verzeichnisse +# - Docker via Nesting für Wegwerf-Test-Container (z. B. pgvector in CI) +# +# Run on a Proxmox VE host: +# bash -c "$(curl -fsSL https://gitea.luki-net.org/luki-net/proxmox-scripts/raw/branch/main/ct/runner.sh)" + +set -euo pipefail + +APP="runner" +APP_DESCRIPTION="Allgemeiner Gitea-Actions-Runner (instanzweit, Label homelab, Docker, ohne Deploy-Rechte)" + +LIB_URL="${LIB_URL:-https://gitea.luki-net.org/luki-net/proxmox-scripts/raw/branch/main/lib}" +INSTALL_SCRIPT_URL="${INSTALL_SCRIPT_URL:-https://gitea.luki-net.org/luki-net/proxmox-scripts/raw/branch/main/install/runner-install.sh}" + +source <(curl -fsSL "$LIB_URL/build.func") + +DEFAULT_HOSTNAME="runner" +DEFAULT_DISK="30" +DEFAULT_CORES="4" +DEFAULT_RAM="6144" + +prompt_app_config() { + echo + echo "── Runner configuration ─────────────────────────────────────" + if [[ -z "${GITEA_INSTANCE_URL:-}" ]]; then + read -rp "Gitea instance URL [https://gitea.luki-net.org]: " GITEA_INSTANCE_URL + GITEA_INSTANCE_URL="${GITEA_INSTANCE_URL:-https://gitea.luki-net.org}" + fi + # WICHTIG: den INSTANZWEITEN Token verwenden + # (Site Administration → Actions → Runners → Create new runner), + # NICHT den Repo-Token — sonst wiederholt sich der K-114-Scope-Blocker. + if [[ -z "${RUNNER_TOKEN:-}" ]]; then + read -rsp "INSTANZWEITER Runner-Registration-Token: " RUNNER_TOKEN; echo + fi + [[ -n "${RUNNER_TOKEN:-}" ]] || { msg_err "RUNNER_TOKEN ist Pflicht (instanzweit, Site Administration)"; exit 1; } + RUNNER_NAME="${RUNNER_NAME:-$CT_HOSTNAME}" + RUNNER_LABELS="${RUNNER_LABELS:-homelab:host}" + RUNNER_VERSION="${RUNNER_VERSION:-0.2.13}" + NODE_MAJOR="${NODE_MAJOR:-22}" + echo " → instance: $GITEA_INSTANCE_URL" + echo " → runner: $RUNNER_NAME labels: $RUNNER_LABELS (act_runner $RUNNER_VERSION, host mode, scope: INSTANZ)" +} + +push_app_config() { + msg_info "Pushing runner config into container..." + local tmpf; tmpf=$(mktemp) + cat >"$tmpf" </dev/null 2>&1 && break + sleep 2 + done + msg_ok "Container restarted with nesting enabled" +} + +print_app_summary() { + local runner_state + runner_state=$(pct exec "$CTID" -- systemctl is-active act-runner.service 2>/dev/null | tr -d '\r\n') + cat < Date: Fri, 12 Jun 2026 13:25:42 +0200 Subject: [PATCH 2/6] =?UTF-8?q?feat(runner):=20Installer=20=E2=80=94=20act?= =?UTF-8?q?=5Frunner=20instanzweit=20als=20unprivilegierter=20User,=20Dock?= =?UTF-8?q?er,=20ohne=20sudoers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- install/runner-install.sh | 136 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 install/runner-install.sh diff --git a/install/runner-install.sh b/install/runner-install.sh new file mode 100644 index 0000000..12d9364 --- /dev/null +++ b/install/runner-install.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +# Runner installer — runs inside the LXC, called by ct/runner.sh +# +# Allgemeiner, INSTANZWEITER Gitea-Actions-Runner ohne Deploy-Rechte: +# - Node.js (für actions/checkout im Host-Mode) + git + rsync +# - Docker (für Wegwerf-Test-Container in CI, z. B. pgvector) +# - act_runner als unprivilegierter User "runner" (Mitglied der docker-Gruppe, +# KEIN sudo, keine sudoers-Datei — bewusst, vgl. Kopfkommentar in ct/runner.sh) +# - systemd-Unit act-runner.service +# +# Idempotent: erneuter Lauf überspringt vorhandene Registrierung/Pakete. + +set -euo pipefail + +LIB_URL="${LIB_URL:-https://gitea.luki-net.org/luki-net/proxmox-scripts/raw/branch/main/lib}" +source <(curl -fsSL "$LIB_URL/install.func") + +[[ "$EUID" -eq 0 ]] || { msg_err "Must run as root"; exit 1; } + +CONF="/root/runner.deploy.env" +[[ -f "$CONF" ]] || { msg_err "$CONF not found (host bootstrap incomplete)"; exit 1; } +set -a; . "$CONF"; set +a +: "${GITEA_INSTANCE_URL:?missing GITEA_INSTANCE_URL}" +: "${RUNNER_TOKEN:?missing RUNNER_TOKEN}" +RUNNER_NAME="${RUNNER_NAME:-$(hostname)}" +RUNNER_LABELS="${RUNNER_LABELS:-homelab:host}" +RUNNER_VERSION="${RUNNER_VERSION:-0.2.13}" +NODE_MAJOR="${NODE_MAJOR:-22}" + +APP_USER="runner" +APP_HOME="/opt/runner" +RUNNER_DIR="$APP_HOME/data" + +run_user() { runuser -u "$APP_USER" -- env HOME="$APP_HOME" "$@"; } + +# ── Pakete: git/rsync, Node (checkout-Action), Docker (Test-Container) ──────── +setup_base_apt git rsync ca-certificates curl + +NODE_HAVE="$(command -v node >/dev/null 2>&1 && node -v | sed -E 's/^v([0-9]+).*/\1/' || echo 0)" +if [[ "$NODE_HAVE" != "$NODE_MAJOR" ]]; then + msg_info "Installing Node.js ${NODE_MAJOR}.x (NodeSource)..." + curl -fsSL "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" | bash - >/dev/null + apt-get install -y -qq nodejs >/dev/null + msg_ok "Node $(node -v) installed" +else + msg_warn "Node $(node -v) already present, skipping" +fi + +if ! command -v docker >/dev/null 2>&1; then + msg_info "Installing Docker (get.docker.com)..." + curl -fsSL https://get.docker.com | sh >/dev/null + msg_ok "Docker $(docker --version | awk '{print $3}' | tr -d ',')" +else + msg_warn "Docker already present, skipping" +fi + +# ── act_runner binary ───────────────────────────────────────────────────────── +if [[ ! -x /usr/local/bin/act_runner ]]; then + ARCH="$(dpkg --print-architecture)" + case "$ARCH" in amd64|arm64) ;; *) msg_err "unsupported arch: $ARCH"; exit 1 ;; esac + msg_info "Downloading act_runner $RUNNER_VERSION ($ARCH)..." + curl -fsSL "https://dl.gitea.com/act_runner/${RUNNER_VERSION}/act_runner-${RUNNER_VERSION}-linux-${ARCH}" \ + -o /usr/local/bin/act_runner + chmod +x /usr/local/bin/act_runner + msg_ok "act_runner $(/usr/local/bin/act_runner --version 2>/dev/null | head -n1)" +else + msg_warn "act_runner already present, skipping download" +fi + +# ── User (docker-Gruppe VOR dem Daemon-Start — Lesson aus nexus K-103: +# Gruppenmitgliedschaften werden beim Prozessstart eingefroren) ───────────── +create_system_user "$APP_USER" "$APP_HOME" +usermod -aG docker "$APP_USER" +mkdir -p "$RUNNER_DIR" +chown -R "$APP_USER:$APP_USER" "$APP_HOME" + +# ── Registrierung (idempotent) ──────────────────────────────────────────────── +if [[ ! -f "$RUNNER_DIR/.runner" ]]; then + msg_info "Registering runner '$RUNNER_NAME' [$RUNNER_LABELS] with $GITEA_INSTANCE_URL (instance scope)..." + run_user bash -c "cd '$RUNNER_DIR' && /usr/local/bin/act_runner register \ + --no-interactive \ + --instance '$GITEA_INSTANCE_URL' \ + --token '$RUNNER_TOKEN' \ + --name '$RUNNER_NAME' \ + --labels '$RUNNER_LABELS'" + chown -R "$APP_USER:$APP_USER" "$RUNNER_DIR" + msg_ok "Runner registered" +else + msg_warn "Runner already registered (.runner exists), skipping" +fi + +# ── systemd-Unit (bewusst KEINE sudoers-Datei) ──────────────────────────────── +cat >/etc/systemd/system/act-runner.service <"$CRED_FILE" </dev/null || rm -f "$CONF" +apt_cleanup +msg_ok "runner installation finished" -- 2.54.0 From f06e8731188a4be9b5ef6a82895c11af65dfa37c Mon Sep 17 00:00:00 2001 From: "l.kirchner" Date: Fri, 12 Jun 2026 15:04:57 +0200 Subject: [PATCH 3/6] =?UTF-8?q?fix(runner):=20Review-Findings=2090/88/82/8?= =?UTF-8?q?0=20(+70)=20=E2=80=94=20Re-Run=20ohne=20deploy.env,=20Sicherhei?= =?UTF-8?q?tsmodell=20dokumentiert,=20Requires=3Ddocker,=20strikte=20Input?= =?UTF-8?q?-Validierung,=20argv=20statt=20Shell-Interpolation,=20Unit-H?= =?UTF-8?q?=C3=A4rtung?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- install/runner-install.sh | 100 +++++++++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 22 deletions(-) diff --git a/install/runner-install.sh b/install/runner-install.sh index 12d9364..de33790 100644 --- a/install/runner-install.sh +++ b/install/runner-install.sh @@ -4,11 +4,22 @@ # Allgemeiner, INSTANZWEITER Gitea-Actions-Runner ohne Deploy-Rechte: # - Node.js (für actions/checkout im Host-Mode) + git + rsync # - Docker (für Wegwerf-Test-Container in CI, z. B. pgvector) -# - act_runner als unprivilegierter User "runner" (Mitglied der docker-Gruppe, -# KEIN sudo, keine sudoers-Datei — bewusst, vgl. Kopfkommentar in ct/runner.sh) -# - systemd-Unit act-runner.service +# - act_runner als unprivilegierter User "runner" in der docker-Gruppe +# - systemd-Unit act-runner.service (Requires=docker.service) # -# Idempotent: erneuter Lauf überspringt vorhandene Registrierung/Pakete. +# SICHERHEITSMODELL (Review-Finding 88, bewusst akzeptiert & dokumentiert): +# docker-Gruppe == de-facto root IN DIESEM LXC. Da der Runner instanzweit +# ist, kann jedes Repo der Gitea-Instanz via Workflow den Runner-Container +# vollständig kontrollieren. Das ist hier akzeptiert, weil (a) alle Repos +# der Instanz vom selben Admin (Lutz) stammen — kein Multi-Tenant — und +# (b) dieser LXC GENAU DESHALB nichts besitzt: keine sudoers, keine +# Produktions-Mounts, keine Secrets außer dem (geshredderten) Reg-Token. +# Bei Öffnung der Instanz für Dritte: Docker rootless oder eigener Runner +# pro Vertrauenszone. NICHT auf LXCs mit Deploy-Rechten übertragen. +# +# Idempotent: erneuter Lauf (auch OHNE /root/runner.deploy.env) überspringt +# Vorhandenes und provisioniert nur nach — Registrierung braucht die env-Datei +# nur beim Erstlauf (Finding 90). set -euo pipefail @@ -17,19 +28,35 @@ source <(curl -fsSL "$LIB_URL/install.func") [[ "$EUID" -eq 0 ]] || { msg_err "Must run as root"; exit 1; } +APP_USER="runner" +APP_HOME="/opt/runner" +RUNNER_DIR="$APP_HOME/data" + +# ── Konfiguration laden (optional bei Re-Run, Finding 90) ───────────────────── CONF="/root/runner.deploy.env" -[[ -f "$CONF" ]] || { msg_err "$CONF not found (host bootstrap incomplete)"; exit 1; } -set -a; . "$CONF"; set +a -: "${GITEA_INSTANCE_URL:?missing GITEA_INSTANCE_URL}" -: "${RUNNER_TOKEN:?missing RUNNER_TOKEN}" +if [[ -f "$CONF" ]]; then + set -a; . "$CONF"; set +a +fi RUNNER_NAME="${RUNNER_NAME:-$(hostname)}" RUNNER_LABELS="${RUNNER_LABELS:-homelab:host}" RUNNER_VERSION="${RUNNER_VERSION:-0.2.13}" NODE_MAJOR="${NODE_MAJOR:-22}" -APP_USER="runner" -APP_HOME="/opt/runner" -RUNNER_DIR="$APP_HOME/data" +# ── Validierung (Finding 80): Werte landen in Shell-Fragmenten/systemd — +# strikte Zeichenklassen statt Vertrauen. Re-Runs ohne CONF validieren +# nur, was gesetzt ist; Registrierungs-Pflichtwerte prüft der Reg-Block. ── +valid_url() { [[ "$1" =~ ^https?://[A-Za-z0-9.-]+(:[0-9]{1,5})?$ ]]; } +valid_token() { [[ "$1" =~ ^[A-Za-z0-9_-]{16,128}$ ]]; } +valid_token_word() { [[ "$1" =~ ^[A-Za-z0-9._:,-]+$ ]]; } + +[[ -z "${GITEA_INSTANCE_URL:-}" ]] || valid_url "$GITEA_INSTANCE_URL" \ + || { msg_err "GITEA_INSTANCE_URL ungültig: nur http(s)://host[:port]"; exit 1; } +[[ -z "${RUNNER_TOKEN:-}" ]] || valid_token "$RUNNER_TOKEN" \ + || { msg_err "RUNNER_TOKEN ungültig (erwartet 16–128 Zeichen [A-Za-z0-9_-])"; exit 1; } +valid_token_word "$RUNNER_NAME" || { msg_err "RUNNER_NAME enthält unzulässige Zeichen"; exit 1; } +valid_token_word "$RUNNER_LABELS" || { msg_err "RUNNER_LABELS enthält unzulässige Zeichen"; exit 1; } +[[ "$RUNNER_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { msg_err "RUNNER_VERSION ungültig"; exit 1; } +[[ "$NODE_MAJOR" =~ ^[0-9]+$ ]] || { msg_err "NODE_MAJOR ungültig"; exit 1; } run_user() { runuser -u "$APP_USER" -- env HOME="$APP_HOME" "$@"; } @@ -74,27 +101,47 @@ usermod -aG docker "$APP_USER" mkdir -p "$RUNNER_DIR" chown -R "$APP_USER:$APP_USER" "$APP_HOME" -# ── Registrierung (idempotent) ──────────────────────────────────────────────── +# ── Registrierung (idempotent; braucht CONF-Werte nur beim Erstlauf) ────────── if [[ ! -f "$RUNNER_DIR/.runner" ]]; then + : "${GITEA_INSTANCE_URL:?missing GITEA_INSTANCE_URL (Erstlauf braucht /root/runner.deploy.env)}" + : "${RUNNER_TOKEN:?missing RUNNER_TOKEN (Erstlauf braucht /root/runner.deploy.env)}" msg_info "Registering runner '$RUNNER_NAME' [$RUNNER_LABELS] with $GITEA_INSTANCE_URL (instance scope)..." - run_user bash -c "cd '$RUNNER_DIR' && /usr/local/bin/act_runner register \ + # Werte sind oben strikt validiert (keine Quotes/Whitespace möglich) — + # Übergabe als argv an runuser, keine erneute Shell-Interpolation (vgl. Finding 76). + run_user /usr/local/bin/act_runner register \ --no-interactive \ - --instance '$GITEA_INSTANCE_URL' \ - --token '$RUNNER_TOKEN' \ - --name '$RUNNER_NAME' \ - --labels '$RUNNER_LABELS'" + --config /dev/null \ + --instance "$GITEA_INSTANCE_URL" \ + --token "$RUNNER_TOKEN" \ + --name "$RUNNER_NAME" \ + --labels "$RUNNER_LABELS" \ + 2>&1 | sed "s#$RUNNER_TOKEN##g" || { msg_err "Registrierung fehlgeschlagen"; exit 1; } + # act_runner schreibt .runner ins CWD des Users — sicherstellen, dass es im RUNNER_DIR liegt + if [[ -f "$APP_HOME/.runner" && ! -f "$RUNNER_DIR/.runner" ]]; then + mv "$APP_HOME/.runner" "$RUNNER_DIR/.runner" + fi + [[ -f "$RUNNER_DIR/.runner" ]] || { msg_err ".runner nach Registrierung nicht gefunden"; exit 1; } chown -R "$APP_USER:$APP_USER" "$RUNNER_DIR" msg_ok "Runner registered" else msg_warn "Runner already registered (.runner exists), skipping" fi -# ── systemd-Unit (bewusst KEINE sudoers-Datei) ──────────────────────────────── +# ── SSH-Root-Login-Policy anwenden, falls vom Host-Script übergeben +# (Funktion existiert in install.func ab K-114/PR #5 — guarded Aufruf, +# damit dieser Branch vor und nach dem Merge funktioniert) ──────────────── +if declare -F configure_ssh_root_login >/dev/null 2>&1; then + configure_ssh_root_login +fi + +# ── systemd-Unit (bewusst KEINE sudoers-Datei; Findings 82 + 70) ────────────── cat >/etc/systemd/system/act-runner.service <"$CRED_FILE" <} Name/Label: $RUNNER_NAME [$RUNNER_LABELS] User: $APP_USER (docker-Gruppe, KEIN sudo) Workdir: $RUNNER_DIR Logs: journalctl -u act-runner -f Repos nutzen ihn mit: runs-on: ${RUNNER_LABELS%%:*} -Deploy-Jobs gehören NICHT hierher — die bleiben auf dem repo-scoped -nexus-Runner (Produktions-LXC, sudoers-Deploy-Rechte). Trennung beibehalten. + +SICHERHEITSMODELL: docker-Gruppe == de-facto root in DIESEM LXC; instanzweit +heißt: jedes Repo der Instanz kann den Runner-LXC kontrollieren. Akzeptiert, +weil Single-Admin-Instanz und dieser LXC nichts besitzt (keine sudoers, keine +Produktions-Mounts). Deploy-Jobs gehören NICHT hierher — die bleiben auf dem +repo-scoped nexus-Runner. Trennung beibehalten. EOF chmod 600 "$CRED_FILE" -shred -u "$CONF" 2>/dev/null || rm -f "$CONF" +if [[ -f "$CONF" ]]; then + shred -u "$CONF" 2>/dev/null || rm -f "$CONF" +fi apt_cleanup msg_ok "runner installation finished" -- 2.54.0 From ebdd3f5eac9eb59ee1da7fa2d12ee8c68d3d30d6 Mon Sep 17 00:00:00 2001 From: "l.kirchner" Date: Fri, 12 Jun 2026 15:05:57 +0200 Subject: [PATCH 4/6] =?UTF-8?q?fix(runner):=20Review-Finding=2080=20?= =?UTF-8?q?=E2=80=94=20validierte=20Prompts=20mit=20Re-Prompt,=20env-Werte?= =?UTF-8?q?=20gepr=C3=BCft;=20Quoting-Falle=20(76)=20durch=20Charset-Valid?= =?UTF-8?q?ierung=20entsch=C3=A4rft?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ct/runner.sh | 69 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 12 deletions(-) diff --git a/ct/runner.sh b/ct/runner.sh index f2ff8a7..3911b36 100644 --- a/ct/runner.sh +++ b/ct/runner.sh @@ -10,6 +10,11 @@ # - KEINE sudoers-Regeln, kein Zugriff auf Produktions-Verzeichnisse # - Docker via Nesting für Wegwerf-Test-Container (z. B. pgvector in CI) # +# Sicherheitsmodell: siehe Kopfkommentar in install/runner-install.sh — +# instanzweit + docker-Gruppe heißt: jedes Repo der Instanz kann diesen LXC +# kontrollieren. Akzeptiert, WEIL er nichts besitzt. Nicht auf privilegierte +# LXCs übertragen. +# # Run on a Proxmox VE host: # bash -c "$(curl -fsSL https://gitea.luki-net.org/luki-net/proxmox-scripts/raw/branch/main/ct/runner.sh)" @@ -28,24 +33,61 @@ DEFAULT_DISK="30" DEFAULT_CORES="4" DEFAULT_RAM="6144" +# ── Validierung (Review-Finding 80): Werte wandern in die Deploy-Env und in +# systemd/argv — strikte Zeichenklassen, Re-Prompt statt Abbruch. +# Nutzt prompt_validated/require_valid aus build.func, sobald vorhanden +# (K-114/PR #5); bis dahin lokale Fallbacks mit identischer Semantik. ────── +_valid_url() { [[ "$1" =~ ^https?://[A-Za-z0-9.-]+(:[0-9]{1,5})?$ ]]; } +_valid_token() { [[ "$1" =~ ^[A-Za-z0-9_-]{16,128}$ ]]; } +_valid_word() { [[ "$1" =~ ^[A-Za-z0-9._:,-]+$ ]]; } + +_prompt_until_valid() { # var prompt default validator secret(0|1) + local __var="$1" __prompt="$2" __default="$3" __validator="$4" __secret="${5:-0}" __val + while true; do + if [[ "$__secret" == "1" ]]; then + read -rsp "$__prompt" __val; echo + else + read -rp "$__prompt" __val + fi + __val="${__val:-$__default}" + if [[ -n "$__val" ]] && "$__validator" "$__val"; then + printf -v "$__var" '%s' "$__val" + return 0 + fi + msg_warn "Ungültige Eingabe — bitte erneut (kein Paste mit Sonderzeichen)." + done +} + prompt_app_config() { echo echo "── Runner configuration ─────────────────────────────────────" - if [[ -z "${GITEA_INSTANCE_URL:-}" ]]; then - read -rp "Gitea instance URL [https://gitea.luki-net.org]: " GITEA_INSTANCE_URL - GITEA_INSTANCE_URL="${GITEA_INSTANCE_URL:-https://gitea.luki-net.org}" + # env-präsetzte Werte werden validiert (Abbruch bei ungültig — K-114-Konvention), + # interaktive Eingaben re-prompten bis gültig. + if [[ -n "${GITEA_INSTANCE_URL:-}" ]]; then + _valid_url "$GITEA_INSTANCE_URL" || { msg_err "GITEA_INSTANCE_URL (env) ungültig"; exit 1; } + else + _prompt_until_valid GITEA_INSTANCE_URL \ + "Gitea instance URL [https://gitea.luki-net.org]: " \ + "https://gitea.luki-net.org" _valid_url 0 fi # WICHTIG: den INSTANZWEITEN Token verwenden # (Site Administration → Actions → Runners → Create new runner), # NICHT den Repo-Token — sonst wiederholt sich der K-114-Scope-Blocker. - if [[ -z "${RUNNER_TOKEN:-}" ]]; then - read -rsp "INSTANZWEITER Runner-Registration-Token: " RUNNER_TOKEN; echo + if [[ -n "${RUNNER_TOKEN:-}" ]]; then + _valid_token "$RUNNER_TOKEN" || { msg_err "RUNNER_TOKEN (env) ungültig (16–128 Zeichen [A-Za-z0-9_-])"; exit 1; } + else + _prompt_until_valid RUNNER_TOKEN \ + "INSTANZWEITER Runner-Registration-Token: " \ + "" _valid_token 1 fi - [[ -n "${RUNNER_TOKEN:-}" ]] || { msg_err "RUNNER_TOKEN ist Pflicht (instanzweit, Site Administration)"; exit 1; } RUNNER_NAME="${RUNNER_NAME:-$CT_HOSTNAME}" RUNNER_LABELS="${RUNNER_LABELS:-homelab:host}" RUNNER_VERSION="${RUNNER_VERSION:-0.2.13}" NODE_MAJOR="${NODE_MAJOR:-22}" + _valid_word "$RUNNER_NAME" || { msg_err "RUNNER_NAME ungültig"; exit 1; } + _valid_word "$RUNNER_LABELS" || { msg_err "RUNNER_LABELS ungültig"; exit 1; } + [[ "$RUNNER_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { msg_err "RUNNER_VERSION ungültig"; exit 1; } + [[ "$NODE_MAJOR" =~ ^[0-9]+$ ]] || { msg_err "NODE_MAJOR ungültig"; exit 1; } echo " → instance: $GITEA_INSTANCE_URL" echo " → runner: $RUNNER_NAME labels: $RUNNER_LABELS (act_runner $RUNNER_VERSION, host mode, scope: INSTANZ)" } @@ -53,13 +95,15 @@ prompt_app_config() { push_app_config() { msg_info "Pushing runner config into container..." local tmpf; tmpf=$(mktemp) + # Werte sind oben strikt validiert (keine Quotes/Whitespace möglich) — + # damit ist die env-Datei frei von Quoting-/Injection-Fallen (vgl. Finding 76). cat >"$tmpf" < Date: Fri, 12 Jun 2026 15:26:53 +0200 Subject: [PATCH 5/6] =?UTF-8?q?fix(runner):=20Re-Review-Finding=2085=20?= =?UTF-8?q?=E2=80=94=20run=5Fuser=20wechselt=20via=20env=20-C=20ins=20RUNN?= =?UTF-8?q?ER=5FDIR=20(.runner=20landet=20sonst=20in=20/)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- install/runner-install.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/install/runner-install.sh b/install/runner-install.sh index de33790..a326549 100644 --- a/install/runner-install.sh +++ b/install/runner-install.sh @@ -58,7 +58,10 @@ valid_token_word "$RUNNER_LABELS" || { msg_err "RUNNER_LABELS enthält unzuläss [[ "$RUNNER_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { msg_err "RUNNER_VERSION ungültig"; exit 1; } [[ "$NODE_MAJOR" =~ ^[0-9]+$ ]] || { msg_err "NODE_MAJOR ungültig"; exit 1; } -run_user() { runuser -u "$APP_USER" -- env HOME="$APP_HOME" "$@"; } +# Finding 85 (Re-Review): act_runner schreibt .runner ins CWD — run_user wechselt +# deshalb hart ins RUNNER_DIR (env -C), sonst landet die Registrierung in / und +# der unprivilegierte User darf dort nicht schreiben. +run_user() { runuser -u "$APP_USER" -- env -C "$RUNNER_DIR" HOME="$APP_HOME" "$@"; } # ── Pakete: git/rsync, Node (checkout-Action), Docker (Test-Container) ──────── setup_base_apt git rsync ca-certificates curl @@ -108,6 +111,7 @@ if [[ ! -f "$RUNNER_DIR/.runner" ]]; then msg_info "Registering runner '$RUNNER_NAME' [$RUNNER_LABELS] with $GITEA_INSTANCE_URL (instance scope)..." # Werte sind oben strikt validiert (keine Quotes/Whitespace möglich) — # Übergabe als argv an runuser, keine erneute Shell-Interpolation (vgl. Finding 76). + # CWD = RUNNER_DIR via run_user (Finding 85). run_user /usr/local/bin/act_runner register \ --no-interactive \ --config /dev/null \ @@ -116,7 +120,7 @@ if [[ ! -f "$RUNNER_DIR/.runner" ]]; then --name "$RUNNER_NAME" \ --labels "$RUNNER_LABELS" \ 2>&1 | sed "s#$RUNNER_TOKEN##g" || { msg_err "Registrierung fehlgeschlagen"; exit 1; } - # act_runner schreibt .runner ins CWD des Users — sicherstellen, dass es im RUNNER_DIR liegt + # Belt-and-suspenders: falls eine künftige act_runner-Version doch ins HOME schreibt if [[ -f "$APP_HOME/.runner" && ! -f "$RUNNER_DIR/.runner" ]]; then mv "$APP_HOME/.runner" "$RUNNER_DIR/.runner" fi -- 2.54.0 From 43c19f2cce73f3e1ce595c8ba29be9d8c1410941 Mon Sep 17 00:00:00 2001 From: "l.kirchner" Date: Fri, 12 Jun 2026 15:27:45 +0200 Subject: [PATCH 6/6] =?UTF-8?q?fix(runner):=20Re-Review-Nits=20=E2=80=94?= =?UTF-8?q?=20irref=C3=BChrenden=20Validator-Kommentar=20korrigiert,=20EOF?= =?UTF-8?q?-Abbruch=20in=20Prompt-Loop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ct/runner.sh | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ct/runner.sh b/ct/runner.sh index 3911b36..7323256 100644 --- a/ct/runner.sh +++ b/ct/runner.sh @@ -35,8 +35,10 @@ DEFAULT_RAM="6144" # ── Validierung (Review-Finding 80): Werte wandern in die Deploy-Env und in # systemd/argv — strikte Zeichenklassen, Re-Prompt statt Abbruch. -# Nutzt prompt_validated/require_valid aus build.func, sobald vorhanden -# (K-114/PR #5); bis dahin lokale Fallbacks mit identischer Semantik. ────── +# Bewusst LOKALE Validatoren: die Libs werden zur Laufzeit von main geladen, +# dieses Script muss aber unabhängig vom Merge-Stand der K-114-Helfer +# (prompt_validated/require_valid) funktionieren. Semantik ist identisch; +# Konsolidierung auf die build.func-Helfer ist als Follow-up notiert. ────── _valid_url() { [[ "$1" =~ ^https?://[A-Za-z0-9.-]+(:[0-9]{1,5})?$ ]]; } _valid_token() { [[ "$1" =~ ^[A-Za-z0-9_-]{16,128}$ ]]; } _valid_word() { [[ "$1" =~ ^[A-Za-z0-9._:,-]+$ ]]; } @@ -45,9 +47,10 @@ _prompt_until_valid() { # var prompt default validator secret(0|1) local __var="$1" __prompt="$2" __default="$3" __validator="$4" __secret="${5:-0}" __val while true; do if [[ "$__secret" == "1" ]]; then - read -rsp "$__prompt" __val; echo + read -rsp "$__prompt" __val || { echo; msg_err "Eingabe abgebrochen (EOF)"; exit 1; } + echo else - read -rp "$__prompt" __val + read -rp "$__prompt" __val || { echo; msg_err "Eingabe abgebrochen (EOF)"; exit 1; } fi __val="${__val:-$__default}" if [[ -n "$__val" ]] && "$__validator" "$__val"; then -- 2.54.0