Ein eigenes Community-Check-Repository veröffentlichen

Ein eigenes Check-Repository betreiben: PowerShell-Check schreiben, Konfiguration und Ergebnisanzeige definieren, veröffentlichen und in octoja einbinden.

Ein Community-Check-Repository ist ein einfacher Ablageort im Web, der Monitoring-Checks veröffentlicht. Veröffentlichst du eines, kann jede octoja-Instanz es anbinden — die Checks erscheinen dann in der Check-Bibliothek und lassen sich Geräten genauso zuweisen wie eingebaute Checks. Diese Anleitung baut ein vollständiges Repository mit einem PowerShell-Check, der meldet, ob ein Windows-Prozess läuft.

Nutze ein Repository, wenn du denselben Check auf mehreren octoja-Instanzen brauchst — über deine eigenen Mandanten hinweg oder für Kunden, die eine eigene Instanz betreiben. Läuft der Check dagegen immer nur auf einer Instanz, ist ein Custom-Script-Check weniger Aufwand: du schreibst ihn im Browser, und octoja übernimmt die Verpackung. Siehe Einen Custom-Script-Check konfigurieren.

Bevor du beginnst

  • Ein Ort, der statische Dateien über HTTPS ausliefert — ein Webserver, ein Object Storage, ein CDN oder ein Hoster für statische Seiten. octoja klont kein Git-Repository, sondern lädt Dateien über HTTP oder HTTPS.
  • Die Berechtigung Check-Repository-Verwaltung auf der octoja-Instanz, an die du das fertige Repository anbindest. Berechtigungen werden über Gruppen unter Administration → Gruppen vergeben.
  • Ein kurzes Herstellerpräfix für deine Check-IDs, damit sie nicht mit Checks aus anderen Repositories kollidieren. Diese Anleitung verwendet acme.
  • Ein Windows-Gerät mit installiertem Agent, um den fertigen Check auszuprobieren.

Was du veröffentlichst

Ein Repository besteht aus drei Arten von Dateien: einem Index sowie je Check-Version einem UI-Manifest und einem Skript.

https://checks.example.com/
├── registry.json
└── checks/
    └── acme-process-running/
        └── 1.0.0/
            ├── ui.json
            └── check-process.ps1

Die Repository-URL, die du später angibst, lautet https://checks.example.com/. octoja löst jeden weiteren Pfad gegen diese URL auf — sie muss also mit einem Schrägstrich enden, wenn das Repository in einem Unterverzeichnis liegt: https://example.com/octoja-checks/. Der Pfad checks/{id}/{version}/ui.json ist fest vorgegeben: octoja baut ihn aus id und version der registry.json, die Ordnernamen müssen also exakt diesen Werten entsprechen.

Schritt 1 — Das Check-Skript schreiben

octoja übergibt dem Check seine Konfiguration als base64-kodiertes JSON in einer einzigen Zeile auf der Standardeingabe und schließt den Datenstrom danach. Das Skript antwortet mit base64-kodiertem JSON auf der Standardausgabe, eingefasst in zwei Markerzeilen. Speichere dies als checks/acme-process-running/1.0.0/check-process.ps1:

$ErrorActionPreference = 'Stop'

function Write-CheckResult {
    param([hashtable] $Result)

    $json = $Result | ConvertTo-Json -Depth 10 -Compress
    $bytes = [System.Text.Encoding]::UTF8.GetBytes($json)
    $base64 = [Convert]::ToBase64String($bytes)

    [Console]::Out.WriteLine('<<<OCTO_RESULT_BEGIN>>>')
    [Console]::Out.WriteLine($base64)
    [Console]::Out.WriteLine('<<<OCTO_RESULT_END>>>')
}

try {
    $encodedInput = [Console]::In.ReadLine()
    if ([string]::IsNullOrWhiteSpace($encodedInput)) {
        throw 'The check received no input.'
    }

    $inputBytes = [Convert]::FromBase64String($encodedInput)
    $checkInput = [System.Text.Encoding]::UTF8.GetString($inputBytes) | ConvertFrom-Json

    $processName = [string] $checkInput.processName
    if ([string]::IsNullOrWhiteSpace($processName)) {
        throw 'No process name was configured.'
    }

    $processes = @(Get-Process -Name $processName -ErrorAction SilentlyContinue)

    Write-CheckResult @{
        checkResult  = if ($processes.Count -gt 0) { 0 } else { 2 }
        processName  = $processName
        processCount = $processes.Count
        running      = $processes.Count -gt 0
    }

    exit 0
}
catch {
    [Console]::Error.WriteLine($_.Exception.ToString())

    Write-CheckResult @{
        checkResult  = 3
        errorMessage = $_.Exception.Message
    }

    exit 0
}

Drei Details entscheiden darüber, ob der Check überhaupt funktioniert:

  • Die Marker. octoja liest ausschließlich die Bytes zwischen <<<OCTO_RESULT_BEGIN>>> und <<<OCTO_RESULT_END>>>. Alles andere auf der Standardausgabe — eine Write-Host-Zeile, ein Modul-Banner, ein versehentliches Write-Output — ist damit harmlos. Ohne die Marker liest octoja ersatzweise die gesamte Ausgabe als Ergebnis, und eine einzige zusätzliche Zeile zerstört den Check.
  • Beende immer mit Exit-Code 0. Bei einem Exit-Code ungleich null verwirft octoja das Ergebnis und meldet Failure, ganz gleich was das Skript geschrieben hat. Deshalb endet auch der Fehlerpfad oben mit exit 0 — er meldet das Problem stattdessen über checkResult.
  • Diagnoseausgaben gehören auf die Standardfehlerausgabe. Nutze [Console]::Error.WriteLine(…). Der Agent legt die Standardfehlerausgabe in seinem eigenen Log auf dem Gerät ab, sodass sie verfügbar ist, wenn ein Check sich seltsam verhält.

Hinweis: bei einem in octoja gebauten Custom-Script-Check ist es genau umgekehrt. Dort umhüllt octoja dein Skript und setzt die Marker selbst — schreibst du sie dort zusätzlich, zerstörst du das Ergebnis. In einem Repository wird dein Skript unverändert heruntergeladen und ausgeführt, das Setzen der Marker ist also deine Aufgabe.

Ergebnisstatus

Das Feld checkResult trägt den Status. Es akzeptiert die Zahl oder den Namen — 2 und "Critical" sind gleichwertig, und der Feldname selbst wird ohne Beachtung der Groß- und Kleinschreibung erkannt.

WertStatusBedeutung
0SuccessDer überwachte Zustand ist in Ordnung.
1WarningBeachtenswert, aber noch nicht kritisch.
2CriticalDer Check lief und hat einen kritischen Zustand festgestellt.
3FailureDer Check selbst konnte nicht korrekt ausgeführt werden.

Halte das Ergebnis klein. Die dekodierte Ausgabe ist auf 1 MB begrenzt; darüber bleibt der Status erhalten, die Werte entfallen. Vollständige Logs und Inventarlisten gehören nicht in ein Check-Ergebnis.

Schritt 2 — Den Check in der registry.json beschreiben

Der Index liegt im Wurzelverzeichnis des Repositories und listet jeden Check auf, den du anbietest. Speichere ihn als registry.json:

{
  "version": "1.0",
  "generated": "2026-08-11T12:00:00Z",
  "baseUrl": "https://checks.example.com/",
  "checks": [
    {
      "id": "acme-process-running",
      "name": {
        "en": "Process running",
        "de": "Prozess läuft"
      },
      "description": {
        "en": "Checks whether a Windows process is running.",
        "de": "Prüft, ob ein bestimmter Windows-Prozess läuft."
      },
      "author": "ACME",
      "icon": { "type": "component", "library": "lucide", "name": "activity" },
      "category": "services",
      "tags": ["windows", "process", "service"],
      "platforms": ["win-x64"],
      "version": "1.0.0",
      "uiVersion": "ui-1",
      "interval": 5,
      "timeoutSeconds": 60,
      "executionLocation": "Device",
      "executable": {
        "win-x64": {
          "downloadUri": "https://checks.example.com/checks/acme-process-running/1.0.0/check-process.ps1",
          "executableName": "check-process.ps1",
          "fileType": "Ps1",
          "executionType": "PowerShell"
        }
      }
    }
  ]
}

Die Felder, bei denen am häufigsten etwas schiefgeht:

FeldWorauf du achten musst
generatedSteuert die automatische stündliche Synchronisation: octoja lädt nur, wenn dieser Zeitstempel neuer ist als die letzte Synchronisation. Erhöhe ihn bei jeder Veröffentlichung, sonst bleibt deine Änderung unsichtbar, bis jemand von Hand synchronisiert.
baseUrlNur informativ. octoja löst Pfade gegen die URL auf, mit der das Repository angebunden wurde, nicht gegen diesen Wert — ein Repository zieht also nicht um, wenn du ihn änderst.
categoryNutze einen der Werte backup, hardware, network, services, security, custom. Jeder andere Wert funktioniert ebenfalls, erscheint aber unübersetzt als Rohwert.
versionZugleich das Pfadsegment {version} und der Cache-Schlüssel des Agents — siehe Ein Update veröffentlichen weiter unten.
uiVersionEine frei wählbare Kennung für den Stand der ui.json. Ändere sie, wann immer sich das Manifest ändert. Lässt du sie weg, lädt octoja jedes Manifest bei jeder Synchronisation erneut.
platformsLaufzeit-Kennungen wie win-x64, linux-x64, osx-arm64. Zu jeder gehört ein passender Eintrag unter executable.
iconEin lucide-Icon-Name, in kebab-case oder PascalCase. Ein unbekannter Name fällt still auf ein Standard-Icon zurück.
timeoutSecondsWie lange ein einzelner Durchlauf dauern darf. Standard ist 300, wenn das Feld fehlt.

interval ist das Standard-Ausführungsintervall in Minuten, und executionLocation steht auf Device für einen Check, der auf dem Agent läuft — das ist der Standard und kann weggelassen werden. Lokalisierte Texte nehmen die Schlüssel en, de, fr und nl; gib immer en an, denn das ist der Rückfallwert, wenn die Sprache eines Lesers fehlt. Die vollständige Feldliste steht in der Check-Repository-Referenz.

Schritt 3 — Konfiguration und Ergebnisanzeige in der ui.json definieren

Das Manifest legt fest, was beim Zuweisen des Checks konfiguriert wird und wie das Ergebnis dargestellt wird. Speichere es als checks/acme-process-running/1.0.0/ui.json:

{
  "id": "acme/acme-process-running",
  "input": {
    "processName": {
      "type": "string",
      "title": { "en": "Process name", "de": "Prozessname" },
      "description": {
        "en": "Process name without .exe, for example spoolsv.",
        "de": "Prozessname ohne .exe, zum Beispiel spoolsv."
      },
      "default": "spoolsv",
      "isRequired": true
    }
  },
  "output": {
    "detailRenderer": {
      "type": "react-inline",
      "code": "export default function ProcessRunning({ data, lang }) {\n  const de = (lang || '').startsWith('de');\n  const running = data?.running === true;\n  const failed = Number(data?.checkResult) === 3;\n  const color = failed || !running ? 'text-destructive' : 'text-green-600 dark:text-green-400';\n  const label = failed ? (de ? 'Check fehlgeschlagen' : 'Check failed') : running ? (de ? 'Prozess läuft' : 'Process is running') : (de ? 'Prozess läuft nicht' : 'Process is not running');\n  return <div className='rounded-lg border border-border p-3'><div className={'text-sm font-medium ' + color}>{label}</div>{data?.processName && <div className='mt-1 text-xs text-muted-foreground'>{data.processName}{running ? ' · ' + (data.processCount ?? 0) : ''}</div>}</div>;\n}"
    }
  }
}

Jeder Schlüssel unter input wird zu einem Feld im Zuweisungsdialog und kommt im Eingabe-JSON des Skripts unter genau diesem Namen an. Das Beispiel deklariert processName, das Skript liest also $checkInput.processName — benennst du eines um, musst du auch das andere umbenennen.

Gängige Feldtypen sind string, text, integer, number, boolean, select, radio und array. Nutze password für alles Geheime: der Wert wird in der Oberfläche maskiert und bei der Zuweisung gespeichert, statt in deinen veröffentlichten Dateien zu liegen.

Fragt der Check ein entferntes Gerät ab statt der Maschine, auf der er läuft, deklariere das Ziel als string-Feld mit dem Schlüssel host, ip, ipaddress, target, url, endpoint oder address — damit lässt sich der Check Netzwerkgeräten zuweisen, wo octoja das Feld ausblendet und bei jedem Lauf die Adresse des Geräts einsetzt. Wähle string, nicht texttext ist trotz des Namens kein Eingabefeld, sondern eine statische Beschriftung, und als Host-Schlüssel zählt nur string. Die vollständigen Regeln, einschließlich der Array-Variante und der SNMP-Zugangsdaten-Schlüssel, stehen in der Check-Repository-Referenz.

Der detailRenderer ist optional — lässt du ihn weg, werden die Ergebnisfelder unverändert aufgelistet. Ist er vorhanden, ist sein code eine kleine React-Komponente, die data (dein Ergebnis-JSON), input (die konfigurierten Werte) und lang erhält. JSX ist erlaubt und wird für dich kompiliert. Renderer-Code aus einem Community-Repository läuft in einem isolierten Bereich ohne Zugriff auf die octoja-Sitzung, Cookies oder die umgebende Seite — er kann also das Ergebnis anzeigen und sonst nichts.

Schritt 4 — Das Skript lokal testen

Probiere das Protokoll vor dem Veröffentlichen auf deinem eigenen Rechner aus. Führe dies in dem Ordner aus, in dem das Skript liegt:

$inputJson = '{"processName":"spoolsv"}'
$inputBytes = [System.Text.Encoding]::UTF8.GetBytes($inputJson)
$inputBase64 = [Convert]::ToBase64String($inputBytes)

$raw = ($inputBase64 | powershell.exe -ExecutionPolicy Bypass -File .\check-process.ps1) -join "`n"

if ($raw -match '(?s)<<<OCTO_RESULT_BEGIN>>>(.*?)<<<OCTO_RESULT_END>>>') {
    [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Matches[1].Trim()))
} else {
    Write-Warning 'No result block found — check what the script writes to standard output.'
    $raw
}

Diese Extraktion macht octoja mit der Ausgabe genauso. Die letzte Zeile sollte dein Ergebnis-JSON ausgeben, zum Beispiel:

{"checkResult":0,"running":true,"processName":"spoolsv","processCount":1}

Führe es noch einmal mit einem nicht laufenden und mit einem leeren Prozessnamen aus, um zu bestätigen, dass du 2 und 3 erhältst — und jedes Mal einen Exit-Code von 0. Die Reihenfolge der Felder im JSON ist nicht festgelegt, nur die Feldnamen zählen.

Beachte, dass in diesem Befehl kein -NoProfile steht, denn der Agent verwendet es ebenfalls nicht — er ruft powershell -ExecutionPolicy Bypass -File mit Windows PowerShell auf. Ohne -NoProfile zu testen bedeutet: ein systemweites PowerShell-Profil, das beim Start etwas ausgibt, fällt hier auf statt erst in der Produktion. Teste auf einem Gerät, das deinen Zielsystemen ähnelt.

Schritt 5 — Die Dateien veröffentlichen

Kopiere die drei Dateien auf den Webserver und behalte dabei den Aufbau aus Was du veröffentlichst bei:

  • /registry.json
  • /checks/acme-process-running/1.0.0/ui.json
  • /checks/acme-process-running/1.0.0/check-process.ps1

Prüfe anschließend, dass alle drei erreichbar sind:

curl https://checks.example.com/registry.json
curl https://checks.example.com/checks/acme-process-running/1.0.0/ui.json
curl https://checks.example.com/checks/acme-process-running/1.0.0/check-process.ps1

Ein privates Repository darf über alle drei Pfade hinter einer HTTP-Basic-Authentifizierung liegen. octoja sendet dieselben Zugangsdaten beim Lesen der Registry, beim Abruf eines Manifests und beim Herunterladen eines Skripts — und kombiniert Zugangsdaten nur mit einer https://-URL, außer bei Loopback-Adressen wie localhost.

Schritt 6 — Das Repository anbinden

Gehe auf der octoja-Instanz zu Einstellungen → Repositories, klicke auf Community-Repository hinzufügen und trage einen Anzeigenamen sowie https://checks.example.com/ ein. Bestätige mit Verbindung prüfen, dass octoja die Registry lesen kann — es meldet, wie viele Checks es gefunden hat — und speichere dann. Die vollständige Anleitung inklusive Basic-Auth-Zugangsdaten und der Bedeutung jeder Fehlermeldung findest du unter Ein Community-Check-Repository anbinden.

Dein Check erscheint danach mit dem Abzeichen Community in der Check-Bibliothek und lässt sich über ein Konfigurationspaket zuweisen.

Ein Update veröffentlichen

Ändert sich das Skript, veröffentliche es unter einer neuen Version:

  1. Wähle eine neue Versionsnummer, zum Beispiel 1.0.1.
  2. Lege /checks/acme-process-running/1.0.1/ an und veröffentliche Skript und ui.json dort.
  3. Aktualisiere version und die downloadUri des Executables in der registry.json.
  4. Setze generated auf die aktuelle UTC-Zeit.

Die Version ist Teil des Pfads, unter dem der Agent das Skript ablegt, und ein Agent, der diese Version bereits hat, lädt sie nie erneut. Ein geändertes Skript unter unveränderter Version erreicht deshalb keine Geräte, die die alte Fassung ausgeführt haben — sie behalten ihre Kopie. Erhöhe die Version immer.

Ändert sich nur die ui.json, ersetze sie an Ort und Stelle, ändere uiVersion und setze generated. Die Geräte behalten das Skript, das sie schon haben, und die neuen Konfigurationsfelder und die neue Ergebnisanzeige kommen mit der nächsten Synchronisation.

octoja synchronisiert angebundene Repositories automatisch einmal pro Stunde. Jetzt synchronisieren auf der Seite Repositories erzwingt eine sofortige Aktualisierung.

Sicherheit

Ein Repository gibt ausführbaren Code an jedes Gerät weiter, das seine Checks ausführt — behandle das Veröffentlichen deshalb in beide Richtungen als Vertrauensverhältnis.

RegelWarum
Liefere das Repository über HTTPS aus und schütze ein privates zusätzlich mit Basic-Authentifizierung.Wer die Dateien ändern kann, entscheidet, was auf jedem angebundenen Gerät ausgeführt wird.
Schreibe niemals Passwörter, API-Schlüssel oder Token in das Skript oder in die registry.json.Beides sind veröffentlichte Dateien. Nutze stattdessen ein password-Eingabefeld, damit das Geheimnis je Zuweisung eingegeben und dort gespeichert wird.
Übergib einen konfigurierten Wert niemals an Invoke-Expression, cmd.exe oder einen dynamisch zusammengesetzten Befehl.Check-Skripte laufen mit den Rechten des octoja-Agentendienstes. Behandle jede Eingabe als nicht vertrauenswürdig.
Halte veröffentlichte Check-IDs stabil.Ein aus der registry.json entfernter Check bleibt in der Check-Bibliothek bereits synchronisierter Instanzen, bis das Repository gelöscht wird.
Nutze absolute Pfade oder $PSScriptRoot für Dateien, die das Skript liest.Das Skript läuft nicht mit seinem eigenen Ordner als Arbeitsverzeichnis.

Dasselbe gilt, wenn du ein fremdes Repository anbindest: nimm nur eines aus einer Quelle auf, der du vertraust.

Fehlerbehebung

Der Verbindungstest meldet, dass die Registry nicht gelesen werden konnte. Die URL hat geantwortet, aber octoja fand direkt darunter keine lesbare registry.json. Prüfe, ob die Basis-URL mit einem Schrägstrich endet, und rufe registry.json mit curl ab, um zu bestätigen, dass der Server die Datei zurückgibt und nicht ein Verzeichnislisting oder eine Fehlerseite.

Der Check ist gelistet, hat aber keine Konfigurationsfelder. Das Manifest konnte bei der ersten Synchronisation nicht geladen werden. Prüfe, ob checks/{id}/{version}/ui.json für genau die id und version aus der Registry erreichbar ist — ein abweichender Ordnername ist die übliche Ursache — und nutze dann Jetzt synchronisieren.

Der Check meldet auf dem Gerät Failure. Entweder hat das Skript mit einem Exit-Code ungleich null geendet, oder octoja konnte aus seiner Ausgabe kein Ergebnis lesen. Prüfe, ob das Skript auf jedem Pfad mit exit 0 endet und ob die Marker exakt wie gezeigt geschrieben werden. Der Agent bewahrt die Standardfehlerausgabe des Skripts in seinem Check-Log auf dem Gerät auf.

Ein geändertes Skript zeigt keine Wirkung. Die Version in der registry.json hat sich nicht geändert, die Agenten haben also ihre vorhandene Kopie behalten. Veröffentliche unter einer neuen Versionsnummer.

Die Check-Bibliothek zeigt den neuen Check nicht. Erhöhe generated in der registry.json — die stündliche Synchronisation überspringt ein Repository, das nichts Neueres meldet — oder nutze Jetzt synchronisieren. Zum Sehen der Check-Bibliothek brauchst du außerdem die Berechtigung Monitoring-Check-Verwaltung.

Aktuelle Störungen und bekannte Einschränkungen findest du unter Produktstatus & bekannte Einschränkungen.

Verwandte Artikel