Caldera docs
calderaapp.io

Die Runtime caldera.pyz

Das Programm, das einen Agent auf einem Rechner einrichtet und danach für ihn läuft und meldet: Befehle, Verzeichnisaufbau, die systemd-Unit, wie es den Agent überwacht, die Status-Datei, Einstellungen, Logs, Gesundheitsprüfungen, Updates und Rollback von Blueprints, das signierte Update der Runtime selbst, Verhalten ohne Caldera, Umgebungsvariablen, Python-Unterstützung und Sicherheitseigenschaften.

Was sie ist

caldera.pyz ist eine einzelne Python-3-Datei ohne Abhängigkeiten (nur die Standardbibliothek; sie bringt einen eigenen kleinen YAML-Leser mit). Sie ist zugleich das Kommandozeilenwerkzeug (caldera init <code>) und der dauerhaft laufende Prozess (caldera run), der

  • den Prozess des Agents startet, überwacht und stoppt,
  • im Agent-Protokoll an Caldera meldet und die Befehle anwendet (pausieren, fortsetzen, jetzt ausführen, Einstellungen, Log),
  • neue Versionen des Blueprints holt, auf Gesundheit prüft und anwendet, und zurückrollt,
  • auf Wunsch sich selbst durch eine signierte neue Runtime ersetzt (unten),
  • oder, bei einem Agent, der eigene Prozesse behält, keinen besitzt und alles an einen Adapter weitergibt (Adapter-Modus).

Sie entscheidet nie, was der Agent tut. Sie startet den Agent, liest die Status-Datei, die der Agent schreibt, und gibt sie weiter.

Dieselben Bytes werden unter https://app.calderaapp.io/cli/caldera.pyz ausgeliefert, mit der Prüfsumme unter /cli/caldera.pyz.sha256 und der Signatur unter /cli/caldera.pyz.sig. Das Archiv ist reproduzierbar: Dieselben Quellen ergeben dieselben Bytes.

Beschaffen

Der Install-Dialog zeigt den Befehl mit eingebetteter Prüfsumme:

curl -fsSL https://app.calderaapp.io/cli/caldera.pyz -o caldera.pyz && echo "<sha256>  caldera.pyz" | sha256sum -c -

Auf macOS shasum -a 256 -c - statt sha256sum -c - verwenden (der Dialog bietet diese Variante an). Die Prüfsumme im Dialog wird aus genau den Bytes berechnet, die der Server ausliefert, braucht also keine zweite Anfrage an denselben Ursprung.

Voraussetzungen

  • Python 3.10 oder neuer. Das Programm deklariert kein Minimum; 3.10 ist die niedrigste getestete Version. Getestet wurde auch mit 3.13 und 3.14.
  • Linux oder macOS. Es nutzt fcntl und /proc oder ps sowie eine systemd-User-Unit für den Service. Windows wird nicht unterstützt.
  • systemd für den Service. init funktioniert auch ohne systemd (--no-service, oder kein systemctl); der Agent wird dann mit caldera run --dir <dir> gestartet.
  • Ausgehendes HTTPS zu Caldera. Auf dem Rechner lauscht nichts.
  • ssh-keygen (OpenSSH) für das signierte Update der Runtime selbst. Es ist auf praktisch jedem Rechner vorhanden, auf dem ein Agent läuft. Ohne es arbeitet und meldet die Runtime weiter, aktualisiert sich aber nicht selbst.

Befehle

Alle Befehle lauten python3 caldera.pyz <command>. Fehler geben caldera: <message> auf stderr aus und beenden mit 1.

Befehl Tut Braucht Caldera?
init <code> [--dir DIR] [--url URL] [--no-service] Richtet einen Agent aus einem Einmal-Install-Code ein. Ja, als einziger Befehl.
run [--dir DIR] [--once] Der dauerhaft laufende Prozess: überwachen und melden. --once sendet eine Meldung und beendet (ein Test). Meldungen gehen hinaus; nichts wartet auf sie.
status [--dir DIR] Zeigt den Zustand des Agents. Nein
pause [--dir DIR] Schreibt die Datei paused und stoppt den Agent. Im Adapter-Modus: ruft adapter pause auf. Nein
resume [--dir DIR] Entfernt die Datei; startet den Agent, wenn die Runtime nicht läuft. Im Adapter-Modus: ruft adapter resume auf. Nein
rollback [--dir DIR] Geht auf die vorherige Blueprint-Version zurück. Nein
rollback-runtime [--dir DIR] Legt die vorherige Runtime-Datei zurück und sagt einer laufenden Runtime, von ihr neu zu starten. Nein
pin-runtime <version|off> [--dir DIR] Hält die Runtime auf einer Version fest: Sie aktualisiert sich auf keine andere. Nein
log [name] [--dir DIR] Listet die Logs des Agents oder gibt das Ende eines Logs aus. Nein
--version Gibt caldera 1 (runtime <release>) aus. 1 ist die Konstante der CLI; das Release ist beim Bauen eingebaut (dev aus dem Quelltext). Nein
  • --dir ist bei allen außer init standardmäßig das aktuelle Verzeichnis. Angeben oder in das Verzeichnis des Agents wechseln.
  • init --dir ist standardmäßig ~/caldera-agents/<agent name>; --url ist standardmäßig die Umgebungsvariable CALDERA_URL, sonst https://app.calderaapp.io. Die URL muss https sein; http wird nur für localhost, 127.0.0.1 und ::1 akzeptiert.
  • status gibt aus: Agent-Name, Blueprint und Version (mit der vorherigen), Prozess (running, paused oder not running), Runtime (running oder not running), die letzte Meldung (ok oder failed, und wie viele Sekunden her), das letzte Update (Ergebnis und Nachricht), eine Sperre, falls vorhanden, und den Wert jeder Einstellung.

Was init tut

  1. Vorschau. Liest, was der Code installieren würde, ohne ihn zu verbrauchen. Sie nennt den Agent, das Verzeichnis und die Geheimnisse. Alles, wobei eine Person noch abbrechen kann (ein belegtes Verzeichnis, ein Geheimnis, das sie nicht hat), kommt vor dem Verbrauchen des Codes.
  2. Verzeichnisprüfung. Enthält das Zielverzeichnis schon einen Agent (.caldera/config.json existiert), hält init an.
  3. Geheimnisse. Für jedes deklarierte Geheimnis: die Umgebungsvariable CALDERA_SECRET_<NAME>, wenn gesetzt; sonst Abfrage im Terminal (verdeckte Eingabe), wenn stdin ein Terminal ist; sonst ein Fehler, außer das Geheimnis ist optional (dann wird es übersprungen). Eine leere Antwort auf ein optionales Geheimnis überspringt es. Geheimnisse gehen nie an Caldera.
  4. Einlösen. Verbraucht den Code. Die Antwort trägt das Token des Agents, je Anbindung einen Satz Variablen und das Bundle. Die Runtime lehnt eine Antwort ab, die einen anderen Agent installiert als in der Vorschau, und einen Wert mit Zeilenumbruch.
  5. Entpacken. Prüft die SHA-256 des Bundles, entpackt es nach versions/<version>/, validiert das Manifest und prüft, dass jede Datei existiert, die das Manifest nennt. Ein Bundle, das eine Prüfung nicht besteht, hinterlässt nichts.
  6. Schreiben. agent.env, secrets.env (beide Modus 600), config.json, die Einstellungsdatei und den Link current.
  7. Service. Schreibt und startet eine systemd-User-Unit (außer bei --no-service). Findet sich kein systemctl, gibt es aus, wie der Agent von Hand gestartet wird: caldera run --dir <dir>.

Scheitert etwas nach Schritt 4, ist der Code verbraucht und der Agent existiert in Caldera ohne Rechner. Die Runtime gibt das aus, und eine Person löscht den Agent in Caldera und stellt einen neuen Code aus.

Das Verzeichnis

<dir>/                      z. B. ~/caldera-agents/my-agent
  versions/<semver>/        ein entpacktes Bundle je aufbewahrter Version (nie bearbeitet)
  current -> versions/X     die Version, die läuft; atomar getauscht
  state/                    was ein Update überlebt (die `state`-Pfade des Manifests)
  .caldera/                 die eigenen Dateien der Runtime; ein Bundle kann hier nie schreiben (Modus 700)
    config.json    (600)    URL, Agent-Name, Blueprint, vorherige Version, Einstellungen, Sperre, Zeitfenster, letztes Update
    agent.env      (600)    CALDERA_URL, CALDERA_TOKEN, Variablen der Anbindungen
    secrets.env    (600)    was `init` abgefragt hat
    settings.json  (600)    die Einstellungen, wie sie jetzt sind, zum Lesen für den Agent
    paused                  existiert, solange der Agent pausiert
    run-now                 von `run_now` angelegt; der Agent entfernt sie
    update.json    (600)    das Journal eines laufenden Updates
    lock                    gehalten, solange sich die Version ändert
    agent.pid               PID und Startzeit des Agents (seine Identität)
    runtime.pid             PID und Startzeit der Runtime
    runtime.json   (600)    der eigene Update-Zustand der Runtime: Version, vorherige, Sperre, Festhalten, letztes Ergebnis, Probezeit
    runtime.lock            gehalten, solange sich dieser Zustand ändert
    last-report.json        wann die letzte Meldung hinausging und ob sie funktioniert hat
    runtime.log             das Log der Runtime
    agent.out.log  (600)    stdout und stderr des Agents
    caldera.pyz             eine Kopie der Runtime, wenn `init` aus einer .pyz lief; die Datei, die die Unit ausführt, bei einem Selbst-Update ersetzt
    caldera.pyz.previous    die Runtime, die davor lief (Modus 700), für `rollback-runtime`

current ist ein Symlink, und der Tausch ist ein einziges rename. Einen Symlink mit os.replace zu ersetzen ist atomar: In jedem Augenblick nennt current eine vollständige Version. Ein an beliebiger Stelle beendetes Update lässt current auf der alten oder der neuen Version, nie auf der Hälfte von beiden.

Zustand liegt außerhalb der Version und ist eingelinkt. Jeder Pfad in der state-Liste des Manifests liegt in <dir>/state, und jede Version bekommt einen Symlink darauf. Eine Datei, die das Bundle an einem State-Pfad mitbringt, ist ein Seed: Sie wird nur nach state kopiert, wenn dort noch nichts liegt. So kann eine neue Version Standardwerte mitbringen, ohne zu überschreiben, was der Agent gelernt hat.

Nur zwei Versionen werden aufbewahrt: die laufende und die vorherige. Ältere werden nach einem erfolgreichen Update gelöscht.

Der Service

init schreibt ~/.config/systemd/user/caldera-<name>.service (oder unter $XDG_CONFIG_HOME):

[Unit]
Description=Caldera runtime for the agent <name>
After=network-online.target

[Service]
ExecStart="<python>" "<dir>/.caldera/caldera.pyz" "run" "--dir" "<dir>"
Restart=always
RestartSec=10
KillMode=process

[Install]
WantedBy=default.target

danach systemctl --user daemon-reload und systemctl --user enable --now caldera-<name>.service. Damit er weiterläuft, wenn niemand angemeldet ist: loginctl enable-linger <user>.

Die Unit ändert sich nie, wenn sich die Runtime ändert. Sie nennt <dir>/.caldera/caldera.pyz, und ein Selbst-Update ersetzt diese Datei mit einem rename; Unit, Interpreter und Argumente bleiben, wie sie sind.

KillMode=process ist kein Detail. Der Standard (control-group) beendet beim Stoppen jeden Prozess, den die Unit gestartet hat, und würde den Agent bei einem Neustart der Runtime mitnehmen. Der Agent muss die Runtime überleben.

Logs der Runtime: journalctl --user -u caldera-<name>.service und <dir>/.caldera/runtime.log. Jede Zeile läuft durch einen Redaktor, der das Agent-Token und die eingegebenen Geheimnisse durch ihre Namen ersetzt.

Wie sie den Agent überwacht

caldera run hat zwei Threads, bewusst getrennt:

  • Der Supervisor sieht einmal pro Sekunde nach dem Agent und berührt nie das Netz.
  • Der Reporter spricht in jedem Intervall mit Caldera, mit einem Timeout bei jeder Anfrage (standardmäßig 10 s; CALDERA_REQUEST_TIMEOUT, 1 bis 120).

Ein Caldera, das zehn Sekunden hängt, verzögert die nächste Meldung und sonst nichts. Der Agent läuft weiter, eine Pause wird binnen einer Sekunde angewendet, und das Rollback-Zeitfenster zählt weiter.

Beim Start schließt die Runtime zuerst ein unterbrochenes Update ab oder nimmt es zurück (siehe das Journal).

Prozess-Runtime (runtime.type: process)

  • Der Agent wird aus runtime.command gestartet (eine Argumentliste, nie eine Shell), in versions/<version>/<workdir>, mit der Umgebung unten, stdin geschlossen, stdout und stderr an .caldera/agent.out.log angehängt (beim Start gekürzt, wenn größer als 5 MB).
  • Er wird in einer eigenen Session gestartet, ein Absturz, Neustart oder systemctl stop der Runtime nimmt ihn also nicht mit. Eine Runtime, die erneut startet, findet den Agent über seine PID-Datei (PID und Startzeit, weil eine PID allein wiederverwendet wird) und übernimmt ihn, statt einen zweiten zu starten.
  • Stoppen: SIGTERM an die Prozessgruppe, bis zu 10 Sekunden Gnadenfrist, dann SIGKILL und bis zu 5 weitere Sekunden.
  • Neustarts: Läuft der Agent nicht und ist nicht pausiert, wird er gestartet. Nach einem Fehler wartet der nächste Versuch 5 Sekunden, dann verdoppelt sich die Wartezeit, bis 300 Sekunden. Ein Prozess, der länger als 60 Sekunden lief, setzt die Zählung zurück. run_now überspringt das Warten.

Compose-Runtime (runtime.type: compose)

Derselbe Vertrag über docker compose -f <file>: up -d zum Starten, stop zum Stoppen, ps --status running -q zum Nachsehen, mit 300 Sekunden Timeout je Aufruf. Sie wurde auf keinem echten Docker-Rechner überprüft. Als unerprobt behandeln.

Die Umgebung des Agents

Der Agent bekommt die eigene Umgebung der Runtime plus alles in .caldera/agent.env und .caldera/secrets.env, außer CALDERA_TOKEN: Der Agent braucht das Token nicht, das für die Runtime spricht, und ein Prozess, der es nie hielt, kann es nicht preisgeben. Dazu diese Variablen:

Variable Ist
CALDERA_SETTING_<KEY> der Wert jeder Einstellung beim Start des Prozesses (true oder false bei bool, leer wenn ungesetzt); der Schlüssel ist großgeschrieben
CALDERA_SETTINGS_FILE JSON aller Einstellungen, bei jeder Änderung neu geschrieben
CALDERA_PAUSE_FILE existiert, solange der Agent pausiert (die Runtime stoppt den Prozess, das Prüfen ist also optional)
CALDERA_RUN_NOW_FILE von run_now angelegt; der Agent löscht sie, wenn er handelt
CALDERA_STATE_DIR, CALDERA_AGENT_DIR das Zustandsverzeichnis und das Verzeichnis des Agents
CALDERA_AGENT_STARTED Unix-Zeit (Sekunden, drei Nachkommastellen), zu der dieser Prozess gestartet wurde
CALDERA_BINDING_<PRODUCT>_URL, _WORKSPACE, _TOKEN ein Satz je Anbindung (aus agent.env)

CALDERA_URL steht ebenfalls in der Umgebung (es steht in agent.env). Der Agent braucht nichts davon, um zu laufen. So arbeitet er ohne Caldera mit.

Die Status-Datei

Der Agent meldet, indem er die JSON-Datei schreibt, die das Manifest als status nennt (relativ zum Versionsverzeichnis, zum Beispiel data/status.json), in der Form einer Protokoll-Meldung. Die Runtime gibt nur diese Schlüssel weiter: state, health, active, gauges, runs, panels. Jeder andere Schlüssel, etwa ein heartbeat, den der Agent für seine eigene Gesundheitsprüfung führt, wird nicht weitergegeben.

  • Eine Datei über 200 KB wird nicht gelesen; die Meldung bekommt eine Gesundheitszeile "Status file" bei warn.
  • Eine Datei, die kein JSON-Objekt ist, kostet eine Gesundheitszeile, nicht die Meldung.
  • Eine Datei, die noch nicht existiert, bedeutet, dass der Agent noch nichts gesagt hat.
  • Atomar schreiben (eine temporäre Datei schreiben, dann umbenennen). Der Blueprint starter im Katalog tut das.

Die Runtime ergänzt agent (Version und Hostname), settings, controls, acks und applied, dazu die Gesundheitseinträge Process und je Anbindung Binding <product>. Sie ersetzt state durch Paused (warn) oder Not running (bad, mit "restarting in N s"), wenn das zutrifft, und verwendet Running (ok), wenn der Agent keinen Zustand meldet.

Befehle von Caldera

Jeder wird gegen das Manifest dieses Agents geprüft und in der nächsten Meldung mit einem Ack beantwortet.

Befehl Was die Runtime tut
pause Steht pause in den controls des Manifests: schreibt .caldera/paused, stoppt den Agent. Ack "Paused."
resume Wenn angeboten: entfernt die Datei; der Supervisor startet den Agent. Ack "Running again."
run_now Wenn angeboten: wird abgelehnt mit "Paused. Resume first.", solange pausiert ist. Sonst legt sie die Datei run-now an und überspringt ein Neustart-Warten. Ack "Requested."
set Jeder Schlüssel muss eine deklarierte Einstellung sein und jeder Wert innerhalb seiner Grenzen, sonst ändert sich nichts (ganz oder gar nicht). Ack "Applied: ." oder "Nothing changed: : ."
log Nur ein im Manifest unter logs deklarierter Name und nur eine Datei, die innerhalb des Agent-Verzeichnisses und außerhalb von .caldera/ aufgelöst wird. Die letzten 512 KB werden hochgeladen; alles andere wird mit dem Text "There is no such log." beantwortet. Kein Ack: Der Upload ist die Antwort.
alles andere Abgelehnt: "This agent does not know the command ..."

Ein Control, das das Manifest nicht anbietet, wird genauso abgelehnt. controls ist standardmäßig pause und resume; run_now muss genannt werden.

Die Pause ist dieselbe Datei, die caldera pause schreibt. Das macht jeden Befehl ohne Caldera umkehrbar.

Einstellungen

  • Das Manifest deklariert Schema, Grenzen und Standardwerte. Werte liegen in .caldera/config.json; eine Einstellung ohne gespeicherten Wert liest ihren Standardwert.
  • set schreibt settings.json neu und startet den Agent nicht neu. Die Umgebungsvariablen CALDERA_SETTING_<KEY> ändern sich erst beim nächsten Start; ein Agent, der sofort reagieren muss, liest vor jeder Entscheidung CALDERA_SETTINGS_FILE.
  • null löscht eine Einstellung nur, wenn das Manifest ihr keinen Standardwert gibt.
  • Ein Schlüssel, den eine neuere Version nicht mehr deklariert, bleibt in config.json, wird aber ignoriert.

Gesundheitsprüfungen

Der health.command des Manifests (argv, in dem Arbeitsverzeichnis der neuen Version ausgeführt, mit der Umgebung des Agents einschließlich Geheimnissen und CALDERA_AGENT_STARTED) entscheidet, ob eine Version funktioniert. Exit 0 besteht. Er schlägt fehl bei einem Exit-Code ungleich 0, einem Timeout (timeout_seconds, standardmäßig 60, höchstens 600) oder einem Befehl, der nicht laufen kann. Ein Manifest ohne health besteht immer.

Die Prüfung so schreiben, dass sie voraussetzt, dass die neue Version gearbeitet hat. Einen Heartbeat in der Status-Datei mit CALDERA_AGENT_STARTED vergleichen: Eine frisch wirkende Datei, die die vorherige Version hinterlassen hat, darf nicht für die neue gelten. Eine gute Prüfung verifiziert außerdem die Konfiguration des Agents und dass die Programme, die er aufruft, im Pfad liegen.

Updates und Rollback von Blueprints

Ein Update hat fünf Schritte, und die Reihenfolge ist das Sicherheitsargument:

  1. Holen des Bundles und Prüfen der Prüfsumme. Ein Netzfehler beendet das Update als failed, mit Wiederholung nach 10 Minuten.
  2. Bereitstellen neben der laufenden Version und validieren. Ein abgelehntes Bundle endet als failed, und die Version wird gesperrt.
  3. Tauschen von current, ein rename.
  4. Neu starten auf der neuen Version und health ausführen.
  5. Behalten (previous = die alte Version, ein Zeitfenster von 30 Minuten, Ergebnis ok), wenn health besteht, oder zurücktauschen, die alte Version neu starten, Ergebnis rolled_back und die neue sperren, wenn nicht.

Ist es gewollt? Die Runtime wendet desired an, wenn alles zutrifft: Es nennt den eigenen Blueprint des Agents, version ist eine Semantic Version (alles andere wird nie als Pfad benutzt), sie ist nicht die laufende Version, und sie ist nicht gesperrt.

Die Sperre. Eine Version, von der die Runtime zurückgerollt hat oder die sie abgelehnt hat, wird nicht erneut angewendet, bis desired eine andere Version nennt. Sonst würde die nächste Meldung ein lokales caldera rollback rückgängig machen. Ein erfolgreiches Update auf eine andere Version hebt die Sperre auf. Die eine Ausnahme ist ein fehlgeschlagener Abruf (Caldera nicht erreichbar): Er wird nach zehn Minuten wiederholt.

Ergebnisse (gemeldet in applied.outcome, mit einer einzeiligen Nachricht):

Ergebnis Bedeutung
ok Die neue Version läuft und hat ihre Gesundheitsprüfung bestanden. Die Nachricht lautet vor jedem Update "installed".
rolled_back Die neue Version hat die Gesundheitsprüfung nicht bestanden, oder wurde im Zeitfenster schlechter, oder eine Person hat caldera rollback ausgeführt. Der Agent ist zurück auf der alten.
failed Nichts hat sich geändert: Das Bundle konnte nicht geholt werden, wurde abgelehnt, oder ein Update wurde unterbrochen und zurückgenommen.

Das Zeitfenster. Nach einem behaltenen Update merkt sich die Runtime den Level des Agents zu diesem Zeitpunkt. 30 Minuten lang (fest, nicht einstellbar) prüft sie alle 15 Sekunden und rollt von selbst zurück, wenn der Level schlechter wurde. Der Level ist bad, wenn der Agent nicht läuft, sonst der level des state in der Status-Datei (info zählt als ok). Gesundheitseinträge zählen nicht; ein pausierter Agent wird nicht geprüft, und die Pause einer Person ist nie ein Grund zum Zurückrollen.

Rollback (caldera rollback, oder Caldera nennt die alte Version) tauscht current auf die vorherige Version und startet neu. Er braucht kein Netz, nur die Platte. Weil nur zwei Versionen aufbewahrt werden, wechselt rollback zwischen der laufenden und der vorherigen Version hin und her. Um weiter zurückzugehen, nennt Caldera diese Version, und die Runtime holt sie erneut.

Das Journal und die Wiederherstellung

update.json hält den erreichten Schritt fest: fetched, staged, swapped. Ein Prozess, der zwischen zwei Schritten beendet wurde, wird beim nächsten Start behandelt:

  • bei fetched oder staged beendet: Die Reste werden verworfen; die alte Version war nie aufgehört, die richtige zu sein;
  • bei swapped beendet (die neue Version ist eingelinkt, aber niemand hat sie geprüft): zurück auf die alte Version, Ergebnis failed, Sperre auf der neuen.

Eine Sperrdatei (Lock) verhindert, dass sich rollback und ein Update verschränken, und dass der Supervisor während eines Updates die falsche Version startet.

Die Runtime selbst aktualisieren

Ein Update der Runtime ist Code von Caldera, der als Benutzer des Agents läuft, der auf einem echten Rechner Schlüssel und Produkt-Token hält. Eine Prüfsumme vom selben Server beweist nichts gegen einen kompromittierten Server oder ein manipuliertes Release, deshalb ist jede Runtime-Datei signiert, außerhalb des Servers, und die Runtime lehnt ein Update ab, das sie nicht verifizieren kann. Die Runtime aktualisiert sich nie ungefragt: Ein Admin drückt auf der Seite des Agents "Runtime aktualisieren", wodurch die nächste Antwort runtime_desired trägt.

  1. Herunterladen von caldera.pyz und caldera.pyz.sig, ohne Token. Ein Netzfehler oder eine fehlende Signatur endet als failed, wiederholt nach 10 Minuten.
  2. Verifizieren der Prüfsumme, dann der Signatur. Kein Schlüssel, kein ssh-keygen oder eine falsche Signatur endet als failed; die alte Datei bleibt.
  3. Die neue Datei nach ihrer Version fragen (python new.pyz --version). Eine Datei, die nicht startet oder eine andere Version nennt, endet als failed.
  4. Tauschen und neu starten. Die alte Datei wird als .previous aufbewahrt, die neue ersetzt sie mit einem rename, und die Runtime startet an Ort und Stelle neu.
  5. Probezeit. Die erste erfolgreiche Meldung binnen fünf Minuten macht sie gut (ok). Eine Runtime, die abstürzt oder nicht rechtzeitig meldet, geht von selbst auf .previous zurück (rolled_back), und die Version wird gesperrt.

Die Signatur. Der Release-Build signiert caldera.pyz mit einem OpenSSH-ed25519-Schlüssel, Namespace caldera-runtime. Die Runtime prüft sie mit

ssh-keygen -Y verify -f <allowed_signers> -I caldera-release -n caldera-runtime -s <sig> < caldera.pyz

gegen einen öffentlichen Schlüssel, der in die Runtime eingebaut ist. Kein ssh-keygen, keine Signatur (/cli/caldera.pyz.sig antwortet 404), eine Signatur eines anderen Schlüssels oder für andere Bytes: kein Update. Das Ergebnis ist failed, mit dem Grund in runtime.applied.message, und die alte Datei bleibt unberührt.

Ist es gewollt? Auf den Wunsch wird gehandelt, wenn runtime_desired eine Versionszeichenkette ist ([0-9A-Za-z][0-9A-Za-z._-]{0,63}, nie ein Pfad), sie nicht die laufende Version ist, nicht gesperrt ist, nicht durch ein Festhalten ausgeschlossen ist und kein früheres Update noch in der Probezeit ist.

Was gesperrt wird. Eine Version, von der die Runtime zurückgegangen ist, und eine Datei, die sie wegen ihres Inhalts abgelehnt hat (eine Prüfsumme, eine Signatur, eine Datei, die nicht startet, eine andere Version als der Wunsch), wird nicht erneut versucht, bis runtime_desired eine andere Version nennt. Was nur Ärger des Rechners oder des Netzes ist (kein ssh-keygen, noch keine Signatur, ein fehlgeschlagener Download, nicht aus der Datei der Unit gestartet), wird nach zehn Minuten wiederholt, solange der Wunsch besteht.

Neustart an Ort und Stelle. Nach dem Tausch ersetzt sich die Runtime im selben Prozess durch die neue Datei: dieselbe Prozess-ID, die systemd-Unit sieht also einen ununterbrochenen Prozess. Die eigenen Prozesse des Agents werden nicht berührt: Im Prozess-Modus läuft der Agent in einer eigenen Session, und die neue Runtime findet ihn über seine PID-Datei wieder; im Adapter-Modus gibt es nichts zu übernehmen. Nur eine Runtime, die aus <dir>/.caldera/caldera.pyz läuft (was die Unit nennt), kann sich ersetzen; eine von woanders gestartete sagt das.

Atomar und stabil. Die alte Datei wird zuerst nach .caldera/caldera.pyz.previous geschrieben, dann ersetzt die neue Datei caldera.pyz mit einem rename: Beide Dateien sind in jedem Augenblick vollständig. Die Unit wird nie umgeschrieben.

Die Probezeit. Die neue Runtime gilt nach ihrer ersten erfolgreichen Meldung binnen fünf Minuten als gut (das Fenster beginnt mit jedem Start der neuen Runtime neu). Sie geht von selbst auf die vorherige Datei zurück, wenn das Fenster ohne erfolgreiche Meldung vergeht oder wenn sie mehr als dreimal ohne eine gestartet wurde (eine Absturzschleife). Das Zurückgehen legt die vorherige Datei an ihren Platz, sperrt die Version, startet an Ort und Stelle neu und meldet rolled_back. Ist Caldera während der Probezeit nicht erreichbar, geht daher auch ein gutes Update zurück und wird gesperrt: Das ist der Preis einer Prüfung, die kein Vertrauen in die neue Datei braucht.

Ergebnisse sind die des Blueprints (ok, rolled_back, failed) und laufen in der Meldung als runtime.applied mit der Version, die jetzt funktioniert. Solange eine Runtime in der Probezeit ist, behauptet sie nichts; ok wird erst gemeldet, nachdem sie sich bewährt hat.

Offline. caldera rollback-runtime tauscht die beiden Dateien (ein zweiter Aufruf geht also wieder vor), sperrt die Version, die es verlassen hat, und signalisiert einer laufenden Runtime, von der Datei neu zu starten; ohne laufende Runtime wird die Datei beim nächsten Start benutzt. caldera pin-runtime <version> lässt die Runtime auf keine andere Version aktualisieren als diese, was Caldera auch wünscht; pin-runtime off hebt es auf. Das Festhalten wird gemeldet. Beides braucht keine Verbindung. Ist Caldera nicht erreichbar, geschieht gar nichts: Die Runtime behält die Datei, die sie hat.

Eine Runtime, die vor dieser Funktion installiert wurde, kann sich nicht selbst aktualisieren. .caldera/caldera.pyz einmal von Hand ersetzen; danach geht es. Siehe Agents betreiben.

Adapter-Modus

Bei einem Agent, der eigene Prozesse behält, besitzt die Runtime keinen. Sie startet nichts, stoppt nichts, startet nichts neu und liest keine Datei des Agents. Sie ruft einen Befehl auf, den das Manifest nennt, mit einem Verb. Die Schnittstelle steht unter Adapter-Modus und sein Vertrag.

Der Hinweis auf das Claude-Konto

Die Runtime kann melden, bei welchem Claude-Konto ihr Rechner angemeldet ist. Siehe Der Hinweis auf das Claude-Konto.

Verhalten, wenn Caldera nicht erreichbar ist

  • Der Reporter loggt report failed: ... und versucht es im nächsten Intervall erneut. Sonst ändert sich nichts: Der Agent läuft weiter, Neustarts passieren weiter, Pause und Fortsetzen funktionieren, das Rollback-Zeitfenster zählt weiter.
  • Ein Abruf, der während eines Updates fehlschlägt, ist Ergebnis failed und wird nach zehn Minuten wiederholt.
  • status, pause, resume und rollback öffnen nie eine Verbindung.
  • Die Runtime zu stoppen stoppt den Agent nicht.
  • Nur init braucht Caldera.

Umgebungsvariablen

Variable Benutzt von Ist
CALDERA_URL init der Standard für --url
CALDERA_REQUEST_TIMEOUT run und die lokalen Befehle Sekunden, die eine Anfrage dauern darf; standardmäßig 10, begrenzt auf 1 bis 120
CALDERA_SECRET_<NAME> init der Wert für das deklarierte Geheimnis <NAME>, für unbeaufsichtigte Installationen
XDG_CONFIG_HOME init wohin die systemd-User-Unit geschrieben wird (standardmäßig ~/.config)

Die Variablen, die der Agent bekommt, stehen in der Tabelle oben. Namen, die mit CALDERA_ beginnen, gehören der Runtime; die secrets eines Manifests dürfen sie nicht benutzen.

Sicherheitseigenschaften

Eigenschaft Wie
Das Token ist privat agent.env und secrets.env haben Modus 600 und werden über eine temporäre Datei geschrieben, die von Anfang an mit diesem Modus angelegt wird, ein Geheimnis ist also zwischendurch nie lesbar. .caldera/ hat Modus 700.
Der Agent bekommt das Token nie CALDERA_TOKEN wird aus der Umgebung des Agents entfernt.
Geheimnisse bleiben aus Logs Jede Logzeile läuft durch einen Redaktor für das Token und die eingegebenen Geheimnisse (Werte ab 6 Zeichen).
Keine Weiterleitungen Eine Weiterleitung würde den Authorization-Header dorthin erneut senden, wohin sie zeigt. Das Protokoll hat keine, also wird eine abgelehnt.
Kein Klartext von diesem Rechner weg Nur https, http nur für localhost.
Fehler nennen kein Geheimnis Ein Fehler trägt den HTTP-Status und den Fehlercode des Servers, nie einen Header, Body, ein Token oder eine URL.
Ein Bundle ist nicht vertrauenswürdig Wird ganz abgelehnt, ohne etwas zu hinterlassen, wenn ein Eintrag einen absoluten Pfad, .., einen Backslash, einen Laufwerksbuchstaben oder ein NUL hat; ein Symlink, Gerät oder eine andere nicht reguläre Datei ist; doppelt vorkommt; unter .caldera/ liegt; oder wenn das Archiv mehr als 5000 Einträge hat, entpackt mehr als 100 MB ergibt (beim Entpacken gezählt, nicht aus Headern gelesen) oder eine Datei mehr als 50 MB, oder größer als 20 MB ist; oder eine vom Manifest genannte Datei fehlt; oder seine Version nicht die erfragte ist.
Der echte Pfad wird geprüft Nach dem Auflösen muss jedes Ziel innerhalb des Verzeichnisses bleiben; jeder Pfad aus einem Manifest (workdir, status, Logs) wird über eine Prüfung des echten Pfads zusammengesetzt, ein Symlink kann also kein Lesen aus dem Verzeichnis des Agents hinaus oder nach .caldera/ hinein führen.
Die Prüfsumme wird verifiziert Bei der Installation muss die sha256 der Antwort zum Bundle passen. Beim Update muss der Header X-Caldera-Sha256 passen; ein fehlender Header heißt keine Prüfung, die Runtime verlässt sich also darauf, dass Caldera ihn sendet.
Das Runtime-Update ist signiert Eine Runtime-Datei wird erst ersetzt, nachdem ihre Signatur gegen den Schlüssel verifiziert wurde, der in der laufenden Runtime eingebaut ist.
Keine Shell runtime.command und health.command sind Argumentlisten.
Versionen sind unveränderlich Ein Versionsverzeichnis wird nach dem Bereitstellen nie bearbeitet.
Dotenv-Zeilen sind sicher Ein Name oder Wert, der aus einer Zeile ausbrechen würde (ein Zeilenumbruch oder NUL), wird abgelehnt.

Was sie nicht tut: Sie verschlüsselt secrets.env nicht (eine private Datei ist der Schutz), und sie rotiert runtime.log nicht.

Bekannte Grenzen

  • Das Erneuern des Tokens ist Handarbeit. Das Erneuern des Tokens in Caldera erreicht den Rechner nicht; agent.env bearbeiten und neu starten (siehe Agents betreiben).
  • Kein Container-Image der Runtime. Die Compose-Runtime ruft docker compose vom Host aus auf.
  • Keine Ersetzung in .mcp.json. Die Runtime liefert die Datei unverändert aus und gibt die Variablen in der Umgebung mit; was die Datei liest, löst ${NAME} auf.
  • Das 30-Minuten-Fenster ist fest.
  • Eine Änderung an einem Geheimnis heißt, secrets.env von Hand zu bearbeiten und neu zu starten.