Zum Inhalt springen
Farbschema wählenSprache wählen

Einen Runner sicher einrichten

Diese Anleitung fügt einer SupaCloud-Installation einen Runner hinzu — eine separate Maschine, die Agent-Aufgaben abholt und ausführt. Der Runner verbindet sich rein ausgehend über HTTPS und arbeitet daher hinter NAT ohne eingehende Ports. Für die Konzepte (Pull-Transport, laufspezifisches Secret-Modell, die zwei Bedeutungen von „Runner-Flotte”) siehe die Erklärung Die Runner-Flotte.

  1. Setze eine Umgebungsvariable auf dem Server und starte ihn neu:

    SUPACLOUD_HUB_MODE=true

    Der Standard ist false. Solange aus, läuft jede Aufgabe lokal — egal wie viele Runner registriert sind. Genau das macht es sicher, den Hub-Modus im Notfall auszuschalten, ohne etwas zu deregistrieren.

  2. Bestätige, dass er an ist:

    Terminal-Fenster
    curl https://supacloud.example.com/api/runners/config \
    -H "Authorization: Bearer <admin-session-oder-API-token>"
    # → { "hub_mode_enabled": true }
  1. Registriere den Runner als System-Admin und sichere das Token:

    Terminal-Fenster
    curl -X POST https://supacloud.example.com/api/runners \
    -H "Authorization: Bearer <admin-session-oder-API-token>" \
    -H "Content-Type: application/json" \
    -d '{
    "name": "gpu-worker-01",
    "capabilities": { "images": ["unified"], "max_parallel": 2 }
    }'
  2. Die Antwort enthält das Klartext-Bearer-Token (scrn_…) genau einmal — der Server speichert nur dessen SHA-256-Hash. Kopiere es jetzt; im nächsten Schritt übergibst du es dem Worker. Geht es verloren, rotiere das Token, statt neu zu registrieren.

Ein Runner kann sich auch dafür entscheiden, Wasm auf seinem Node auszuführen — sowohl Marketplace-Connector-Nodes als auch freie Skripte (JS / TS / Python). Beides läuft in derselben WASI-0.2-Sandbox (pro Lauf Speicher- und Wall-Clock-Grenzen, deny-all-SSRF-geprüfter Egress) hinter der eigenen OS-Grenze des Runners. Zwei Capability-Schlüssel steuern das:

  • connectors: true — der Runner entscheidet sich, Connector-Node-Wasm auszuführen. Der Hub schickt einem Node ohne dieses Flag nie einen Connector-Lauf.
  • wasm.script_languages — die Skriptsprachen, deren Interpreter das Image des Runners tatsächlich bereitgestellt hat. Der Hub schickt einen Skript-Lauf nur an einen Runner, der sowohl connectors: true als auch die Sprache des Skripts hier bewirbt (ein Skript führt Wasm auf dem Node aus, also muss der Node sich für die Wasm-Ausführung entschieden haben und den Interpreter besitzen).
{
"name": "wasm-worker-01",
"capabilities": {
"images": ["unified"],
"connectors": true,
"wasm": { "script_languages": ["js", "ts", "py"] }
}
}

Liste nur Sprachen auf, deren Interpreter der Runner unter SUPACLOUD_SCRIPT_RUNTIME_DIR tatsächlich bereitgestellt hat (reproduzierbar gebaut durch scripts/build-script-runtimes.sh). Der Matcher ist fail-closed: ein fehlender oder fehlerhafter wasm-Schlüssel bedeutet, dass der Runner keine Skriptsprache bewirbt und nie ein Skript erhält — eine bestehende Flotte bleibt also völlig unberührt, und das Hinzufügen des Schlüssels ist ein sicheres, monotones Opt-in.

Wird ein Skript an die Flotte verteilt, schickt der Hub nur Metadaten — die Sprache plus Quelltext und Eingaben als Daten — und einen SHA-256-Integritätsanker seines eigenen Interpreters. Er schickt nie den mehrere MB großen Interpreter. Der Runner löst seinen eigenen lokalen, vertrauenswürdigen Interpreter für diese Sprache auf und prüft den SHA erneut, bevor er ausführt; eine Abweichung (Versionsdrift oder Manipulation) schlägt fail-closed fehl. Eine Anfrage, die versucht, Interpreter-Bytes einzuschmuggeln, wird als Protokollverletzung abgelehnt. Der Interpreter ist stets ein lokal vertrauenswürdiges Runner-Asset — ein per Payload mitgeschickter wird nie ausgeführt.

Das wird instanzweit über SUPACLOUD_CONNECTOR_EXECUTOR_MODE gesteuert (in_process — der Standard — / pooled / runner_fleet); setze es auf runner_fleet, um Connector- und Skript-Läufe an die Flotte zu verteilen. Mit dem Standardmodus in_process laufen Skripte auf dem Hub genau wie zuvor — für bestehende Deployments ist dies ein No-op ohne Konfiguration.

Abschnitt 2 erzeugt ein langlebiges scrn_…-Bearer für einen Runner, den du betreibst. Um einen Runner auf einem Host hochzufahren, dem du weniger vertraust — die Maschine einer Kundin, Edge-Compute, die GPU-Box einer Partnerin — nutze stattdessen den Enrollment-Ablauf. Du übergibst dem Betreiber ein einmaliges, kurzlebiges Enrollment-Token; sein Runner tauscht es beim Boot gegen ein scope-begrenztes, kurzlebiges scrnj_…-JWT ein und erneuert es automatisch. Der Betreiber hält nie ein langlebiges Hub-Credential, und der Runner kann ausschließlich die Workspaces bedienen, auf die du ihn scopt hast. Zum Trust-Modell dahinter siehe Runner-Flotten-Sicherheit.

  1. Enrollment-Token erzeugen (Admin). Wähle Trust-Klasse, Ausführungs-Tier, Isolation und die Workspaces, die der Runner bedienen darf:

    Terminal-Fenster
    curl -X POST https://supacloud.example.com/api/runners/enroll \
    -H "Authorization: Bearer <admin-session-oder-API-token>" \
    -H "Content-Type: application/json" \
    -d '{
    "class": "edge",
    "tier": "docker",
    "isolation": "container",
    "allowed_workspace_ids": ["<workspace-uuid>"],
    "allowed_labels": []
    }'

    class ist trusted oder edge; tier ist wasm, docker oder both; isolation ist container oder microvm. Die Antwort enthält das Klartext-Enrollment-Token genau einmal (der Server speichert nur dessen Hash) und eine kurze Ablaufzeit. Kopiere es jetzt und übergib es dem Betreiber über einen sicheren Kanal.

  2. Worker mit dem Installer onboarden (Betreiber). Der Betreiber führt den gehosteten Ein-Zeilen-Installer aus. Dieser lädt das signierte Runner-Artefakt, verifiziert Prüfsumme und Signatur, installiert das Binary und bootet den Runner — rein ausgehend, keine eingehenden Ports:

    Terminal-Fenster
    curl -fsSL https://supacloud.example.com/install-runner.sh | \
    SUPACLOUD_HUB_URL=https://supacloud.example.com \
    SUPACLOUD_RUNNER_ENROLLMENT_TOKEN=<einmal-token> \
    SUPACLOUD_RUNNER_NAME=customer-edge-01 \
    SUPACLOUD_RUNNER_LABELS=windows,unity,gpu \
    bash

    Das Runner-Binary selbst tauscht das Enrollment-Token ein: beim Boot im --runner-Modus ruft es den gRPC-RPC RunnerService.ExchangeToken ({enrollment_token, name, capabilities} → das scope-begrenzte scrnj_…-JWT, ADR 0050) und bewirbt capabilities.labels (klein gefaltet aus SUPACLOUD_RUNNER_LABELS) plus die backends/isolation des konfigurierten Backends. Der Installer trägt nur das einmalige Token und die Labels; er hält nie einen Bearer. Zum Onboarding ohne Installer setzt du SUPACLOUD_RUNNER_ENROLLMENT_TOKEN (und SUPACLOUD_RUNNER_LABELS) und startest das Binary oder Image genau wie in Abschnitt 3 — oder, wenn du den Eintausch selbst fährst, setzt du stattdessen SUPACLOUD_RUNNER_TOKEN=<das scrnj_-JWT>.

  3. Der Runner erneuert sein JWT automatisch. Der Eintausch verbrennt das Enrollment-Token (es lässt sich nicht erneut verwenden) und liefert ein scope-begrenztes scrnj_…-JWT, das Trust-Klasse, Tier, Isolation und die erlaubten Workspaces trägt. Das JWT lebt höchstens eine Stunde; der Runner-Daemon holt ein frisches, bevor das alte abläuft — standardmäßig bei etwa der halben Token-TTL, abgeleitet aus token_expires_at des Grants / dem JWT-exp —, und jede Erneuerung ruft den grant-prüfenden gRPC-RPC RunnerService.RefreshToken des Hubs auf, sodass der Entzug eines Workspaces innerhalb einer Token-Lebensdauer wirkt. Um den Takt explizit festzulegen, setze SUPACLOUD_RUNNER_TOKEN_REFRESH_SECS auf die Anzahl Sekunden-vor-Ablauf, bei der neu eingetauscht wird (0, der Standard, bedeutet automatisch ableiten).

Das Enrollment-Token ist einmalig: sobald der Runner es einlöst, ist es verbraucht und nicht wiederverwendbar. Ein enrolter Runner persistiert deshalb das erhaltene scope-begrenzte JWT und liest es beim nächsten Start wieder ein:

  • Das JWT wird nach SUPACLOUD_RUNNER_TOKEN_FILE geschrieben; die Installer setzen dies auf runner-token unter dem State-Verzeichnis des Runners — /var/lib/supacloud-runner, wenn der Installer als root läuft, sonst $XDG_STATE_HOME/supacloud-runner; unter Windows %LOCALAPPDATA%\supacloud\state. (Der eingebaute Fallback des Runner-Binaries ist $XDG_STATE_HOME/supacloud-runner/runner-token, unter Windows %LOCALAPPDATA%\supacloud\runner-token.)
  • Der Schreibvorgang ist atomar (eine Temp-Datei im selben Verzeichnis, dann ein Rename) und der Dateimodus ist 0600.
  • Beim Boot liest der Runner die Datei bevor er irgendetwas eintauschen würde. Ein noch gültiges Token wird unverändert verwendet — kein zweiter ExchangeToken-Aufruf — ein Reboot braucht also kein frisches Enrollment-Token. Eine abgelaufene, fehlerhafte oder für Gruppe/Andere lesbare Datei wird verworfen, und der Runner enrolt stattdessen neu.
  • Jede erfolgreiche JWT-Erneuerung schreibt das frische Token zurück, sodass die Datei dem lebenden Credential folgt.

Die Datei ist ein Bearer-Secret at rest: halte den Runner-Host und sein State-Verzeichnis privat, und beachte, dass --uninstall sie entfernt. Um beim Aktualisieren des Binaries eine persistierte Runner-Identität weiterzuverwenden, führe den Installer ohne SUPACLOUD_ENROLLMENT_TOKEN aus; ein übergebenes Token erzwingt immer ein frisches Enrollment (der Installer verwirft die alte Datei, damit das neue Token eingelöst wird).

Der Runner ist dasselbe SupaCloud-Binary im --runner-Modus. Wähle die Plattform, auf der du installierst. In jedem Fall ist die Konfiguration dieselbe Menge an Umgebungsvariablen; nur der Start unterscheidet sich.

Sobald dein Release-Kanal die signierten Binaries hostet, übernehmen die Installer das gesamte Onboarding auf einer Worker-Node — Plattform erkennen, das gepinnte Binary herunterladen, Prüfsumme und Signatur verifizieren, installieren und supacloud-runner --runner über TLS ohne eingehende Ports starten. Der Runner selbst tauscht das einmalige Enrollment-Token ein und bewirbt seine Labels als Capabilities, sodass der Betreiber in Sekunden wieder in der UI ist. Das einzige Secret, das du übergibst, ist das einmalige Enrollment-Token aus dem Mint-Schritt; der Skript-Body enthält keines.

Linux / macOS (genau die Ein-Zeilen-Anweisung, die die SupaCloud-UI dir gibt):

Terminal-Fenster
curl -fsSL "$SUPACLOUD_URL/install-runner.sh" \
| SUPACLOUD_ENROLLMENT_TOKEN=<einmal-token> \
SUPACLOUD_RUNNER_LABELS=gpu,blender \
bash

Windows (PowerShell):

Terminal-Fenster
$env:SUPACLOUD_URL = "https://supacloud.example.com"
$env:SUPACLOUD_ENROLLMENT_TOKEN = "<einmal-token>"
$env:SUPACLOUD_RUNNER_LABELS = "gpu,blender"
irm "$env:SUPACLOUD_URL/install-runner.ps1" | iex

Die SupaCloud-UI zeigt nach dem Minten beide Ein-Zeiler (der Runner-Host muss nicht das OS des Browsers sein). Labels werden vor dem Senden klein gefaltet und dedupliziert, sodass ein handgetipptes GPU und ein vorgeschlagenes gpu EIN Label sind — die Flotte vergleicht Labels mit exakter Gleichheit, und ein nicht gefaltetes GPU würde etwas bewerben, wonach keine Aufgabe je fragen kann.

Nützliche Overrides (Umgebungsvariablen, beide Installer): SUPACLOUD_RUNNER_NAME (Standard: Hostname), SUPACLOUD_RUNNER_VERSION / SUPACLOUD_DOWNLOAD_BASE (ein bestimmtes Release oder einen Artifact-Host pinnen) und SUPACLOUD_RUNNER_DRY_RUN=1, um den aufgelösten Download-, Verify- und Start-Plan auszugeben, ohne das Netzwerk anzufassen.

Der Hub leitet diese Pfade auf den veröffentlichten, gestempelten Installer in deinem Release-Feed um — er trägt das Skript nicht selbst, es gibt also genau eine Kopie. Konfiguriere den Feed mit zwei server-seitigen Variablen:

  • SUPACLOUD_RUNNER_VERSION — die Release-Version, z. B. v0.7.0.
  • SUPACLOUD_RUNNER_DOWNLOAD_BASE — die Basis-URL des Release-Feeds, z. B. https://git.blockworx.tech/api/packages/bw-public/generic/supacloud-runner.

Das Redirect-Ziel ist <download base>/<version>/install-runner.{sh,ps1}. Bis beide gesetzt sind, antworten die Einstiegspunkte mit 503 — onboarde bis dahin über das Docker-Image oder ein selbstgebautes Binary (das Enrollment-Token funktioniert identisch). Version/Download-Basis und die Signatur-Anker backt runner-release.yml in den veröffentlichten Installer; der Hub stempelt oder schreibt ihn nie um.

Die Ein-Zeilen-Installer registrieren einen Autostart-Dienst, damit der Runner nach einem Reboot zurückkommt und sauber außer Betrieb genommen werden kann:

  • Linux: eine systemd-Unit — eine System-Unit als root, sonst eine User-Unit (systemctl --user, mit aktiviertem Lingering, damit sie ohne Login startet). Ist systemd nicht verfügbar, fällt der Installer auf einen dokumentierten nohup-Start zurück und druckt den Folge-Befehl; erzwinge den Fallback mit SUPACLOUD_RUNNER_NO_SERVICE=1. Logs gehen ins Journal (journalctl -u supacloud-runner -f) oder im Fallback ins State-Verzeichnis.
  • macOS: ein launchd LaunchDaemon (root) oder LaunchAgent (User) mit RunAtLoad + KeepAlive; stdout/stderr gehen nach runner.out.log / runner.err.log im State-Verzeichnis.
  • Windows: eine geplante Aufgabe — als SYSTEM beim Start, wenn der Installer erhöht läuft, sonst als aktueller Benutzer beim Logon (mit Rückfall auf den benutzereigenen Run-Schlüssel, wo der Aufgabenplaner gesperrt ist). Eine geplante Aufgabe statt sc.exe/New-Service, weil der Runner ein Konsolen-Binary ist, das das Service Control Protocol nicht implementiert und sonst nicht starten würde (Windows-Fehler 1053). Logs gehen nach logs\runner.log unter dem Installationsverzeichnis.

Ein erneuter Installer-Lauf ist idempotent: er ersetzt das Binary und startet den Dienst neu, und er verwendet das persistierte Token wieder, sofern du kein frisches SUPACLOUD_ENROLLMENT_TOKEN übergibst. Um alles zu entfernen — Dienst, Binary und persistiertes Token —:

Terminal-Fenster
curl -fsSL "$SUPACLOUD_URL/install-runner.sh" | bash -s -- --uninstall
Terminal-Fenster
.\install-runner.ps1 -Uninstall

SUPACLOUD_RUNNER_INSTALL_DIR, SUPACLOUD_RUNNER_STATE_DIR und SUPACLOUD_RUNNER_TOKEN_FILE platzieren Binary, State-Verzeichnis und Token-Datei explizit.

SUPACLOUD_HUB_URL=https://supacloud.example.com
SUPACLOUD_RUNNER_TOKEN=scrn_xxxxxxxxxxxxxxxxxxxxxxxx
SUPACLOUD_RUNNER_NAME=gpu-worker-01
# Optionale Takt-Overrides (sinnvolle Defaults gezeigt):
# SUPACLOUD_RUNNER_HEARTBEAT_SECS=30
# SUPACLOUD_RUNNER_POLL_SECS=3
# Docker-Netzwerk für Agent-Container (muss auf dem Worker existieren):
# AGENT_NETWORK=supacloud-agents

Die Hub-URL muss https:// sein. Ein Klartext-http://-Hub wird beim Start abgelehnt, außer du setzt explizit SUPACLOUD_RUNNER_ALLOW_INSECURE_TRANSPORT=true — nur in einem vertrauenswürdigen privaten Netz je akzeptabel.

  1. Lege die obigen Variablen in eine .env neben das Binary (oder exportiere sie), dann:

    Terminal-Fenster
    supacloud --runner
  2. Oder mit dem Docker-Image (Docker-Socket einhängen, damit der Runner Agent-Container auf seinem eigenen Host starten kann):

    Terminal-Fenster
    docker run -d --name supacloud-runner \
    -e SUPACLOUD_HUB_URL=https://supacloud.example.com \
    -e SUPACLOUD_RUNNER_TOKEN=scrn_xxxxxxxxxxxxxxxxxxxxxxxx \
    -e SUPACLOUD_RUNNER_NAME=gpu-worker-01 \
    -v /var/run/docker.sock:/var/run/docker.sock \
    <dein-supacloud-image> --runner
  1. Der Runner braucht einen Docker-Host, um Agent-Container zu starten — installiere Docker Desktop (WSL-2-Backend) auf dem Worker.

  2. Setze die Variablen in PowerShell für die Sitzung und starte das Binary:

    Terminal-Fenster
    $env:SUPACLOUD_HUB_URL = "https://supacloud.example.com"
    $env:SUPACLOUD_RUNNER_TOKEN = "scrn_xxxxxxxxxxxxxxxxxxxxxxxx"
    $env:SUPACLOUD_RUNNER_NAME = "win-worker-01"
    .\supacloud.exe --runner

    Für einen dauerhaft laufenden Worker führe denselben Befehl als Dienst aus (z. B. mit NSSM) oder nutze den Docker-Image-Befehl aus dem Linux/macOS-Tab innerhalb von WSL 2.

microVM-Ausführungs-Backend (stärkste Isolation)

Abschnitt betitelt „microVM-Ausführungs-Backend (stärkste Isolation)“

Das Standard-Backend docker führt jede Aufgabe in einem Container auf dem lokalen Docker-Host des Runners aus. Für einen nicht vertrauenswürdigen oder Edge-Runner ist das microVM-Backend die stärkste Isolationsstufe: jede beanspruchte Aufgabe läuft in einem eigenen kurzlebigen hardware-virtualisierten Gast (eine Firecracker/Kata-artige microVM), sodass ein kompromittierter Agent-Prozess hinter einer VM-Grenze eingeschlossen ist, nicht nur in einem Kernel-Namespace. Wie die Stufen sich vergleichen, siehe Runner-Flotte Defense-in-Depth → Isolationsstufen.

  1. Baue (oder beziehe) ein Runner-Binary, das mit dem microvm-backend-Feature kompiliert wurde.

  2. Stelle auf dem Worker einen microVM-Launcher bereit (z. B. einen Firecracker/Kata-Wrapper), wähle dann das Backend und richte den Runner darauf aus:

    SUPACLOUD_RUNNER_EXECUTION_BACKEND=microvm
    SUPACLOUD_RUNNER_MICROVM_CMD=/usr/local/bin/launch-microvm
  3. Onboarde (oder onboarde neu) den Runner mit isolation: microvm, damit der fail-closed Dispatch-Filter des Hubs microVM-pflichtige Aufgaben an ihn und nur an ihn routet.

Standardmäßig vertraut der Runner dem Hub über One-Way-TLS (der Hub präsentiert ein Server-Zertifikat; der Runner verifiziert es gegen den System-Trust-Store). Um einen gegenseitigen TLS-Handshake zu verlangen — sodass der Hub auch jeden Runner per Client-Zertifikat authentifiziert, ein starker zweiter Faktor zusätzlich zum Bearer-Token — konfiguriere beide Seiten. mTLS ist optional; ist nichts davon gesetzt, ist der Transport genau wie heute.

  1. Auf dem Hub (Server). TLS direkt ausliefern und Client-Zertifikate verlangen:

    SUPACLOUD_TLS_CERT_PATH=/etc/supacloud/tls/hub.crt
    SUPACLOUD_TLS_KEY_PATH=/etc/supacloud/tls/hub.key
    SUPACLOUD_TLS_CLIENT_CA_PATH=/etc/supacloud/tls/runner-ca.crt

    SUPACLOUD_TLS_CERT_PATH + SUPACLOUD_TLS_KEY_PATH schalten TLS ein; SUPACLOUD_TLS_CLIENT_CA_PATH verlangt und verifiziert zusätzlich ein Client-Zertifikat gegen diese CA. Eine Verbindung ohne vertrauenswürdiges Client-Zertifikat wird beim Handshake abgewiesen.

  2. Auf jedem Runner. Eine Client-Identität präsentieren und (optional) die Hub-CA pinnen:

    SUPACLOUD_RUNNER_TLS_CLIENT_CERT_PATH=/etc/supacloud/tls/runner.crt
    SUPACLOUD_RUNNER_TLS_CLIENT_KEY_PATH=/etc/supacloud/tls/runner.key
    SUPACLOUD_RUNNER_TLS_CA_PATH=/etc/supacloud/tls/hub-ca.crt # optional: Hub-CA pinnen
  1. Beim Start macht der Runner einen Heartbeat-Aufruf. Wird das Token abgelehnt, beendet er sich sofort mit klarer Fehlermeldung; ist der Hub kurz nicht erreichbar, startet er trotzdem und versucht es weiter.

  2. Die Zeile des Runners wechselt beim ersten erfolgreichen Heartbeat von pending → online. Bestätige es vom Server aus:

    Terminal-Fenster
    curl https://supacloud.example.com/api/runners \
    -H "Authorization: Bearer <admin-session-oder-API-token>"
  3. Starte eine Aufgabe, deren Image der Runner bewirbt. Bei aktivem Hub-Modus und einem passenden Online-Runner verteilt der Server sie an den Runner, und der Live-Ereignisstrom zeigt den Fortschritt genau wie bei einem lokalen Lauf.