CLI (np)

np ist das Kommandozeilenwerkzeug für Administration und Betrieb. Es greift ausschließlich über die REST-API auf NodePilot zu; ein direkter Datenbankzugriff findet nicht statt.

Agentenbefehle

np agent runs <EXECUTION-ID> listet Agentenläufe. np agent events <RUN-ID> --after 0 --page-size 200 liest gespeicherte Ereignisse. Verwaltung: np agent mcp list|tools|save|delete und np agent skill list|import|enabled|delete. Argumente stehen im jeweiligen --help; mcp save liest eine JSON-Konfiguration über --file, skill import erhält ZIP-Pfad und --version. Änderungen benötigen Admin-Rechte. Geheimnisse erscheinen nicht in Listen. Die Agenten-/Teamkonfiguration steht in der Activity-Referenz.

Installation

np ist kein .NET Global Tool — PackAsTool verträgt das geerbte net10.0-windows-TFM nicht (NETSDK1146). Stattdessen veröffentlichen und den Ordner in den PATH legen:

# Beide Installer liefern np bereits mit: <install>\tools\np\np.exe, beim Server-Setup
# zusaetzlich in der Maschinen-PATH. Nur aus einem Source-Checkout selbst bauen:
dotnet publish src/NodePilot.Cli -c Release -o C:\Tools\NodePilot-Cli
$env:PATH += ';C:\Tools\NodePilot-Cli'   # dauerhaft über Systemeigenschaften → Umgebungsvariablen

np auth login --server https://nodepilot.example.com

Konventionen in den Beispielen: <ARG> = erforderlich, [ARG] = optional. <ID-OR-NAME> nimmt eine Workflow-GUID oder den Workflow-Namen (exakte Schreibweise gewinnt, sonst case-insensitive; mehrdeutige Namen → Fehler mit GUID-Hinweis). Destruktive Befehle fragen interaktiv nach. delete und force-unlock kennen kein --yes; in CI/Pipes stdin umleiten, dann entfällt die Rückfrage. db --write, secrets reencrypt und settings restart-service nehmen --yes oder überspringen die Rückfrage bei umgeleitetem stdin. backup restore und die rekursiven Ordner-Löschungen (--recursive) verlangen bei umgeleitetem stdin --yes und brechen ohne ab. --file - liest bei workflow import/import-scorch, globals import und settings put/test von stdin (bei workflow step-test entsprechend --config-file -).

Command-Bereiche

BereichSubcommands
agentruns/events, mcp list/tools/save/delete, skill list/import/enabled/delete
authlogin / logout / whoami / methods (Local/LDAP/Windows; OIDC ist browserbasiert und über /api/auth/methods discoverbar)
workflowlist/get/run/lock/unlock/publish/enable/disable/cancel-all/concurrency-limit/duplicate/delete/export/import/versions/version/rollback/force-unlock/import-scorch/stats/contract/coverage/trigger/step-test/step-test-context/move-folder
execlist/get/steps/cancel/retry/watch/resume/paused-steps
machine / credential / globalslist/create/update/delete (+ machine/credential get, machine test, globals export/import/move-folder/folder)
userCRUD
shared-folderorg RBAC: list/create/rename/move/delete (--recursive samt Inhalt)/permissions/grant/revoke
maintenanceMaintenance Windows: list/get/create/update/delete
auditaudit list
alertingcatalog/list/get/create/update/delete/test-fire/deliveries (Create/Edit/Delete/Test-Fire nur für Admins — siehe Alerting)
system-alertcatalog/list/get/create/update/enable/disable/delete/test-fire — System-Alert-Policies (ADR 0008); create/update lesen eine SaveSystemAlertPolicyRequest-JSON via --file
healthHealth-Check (live/ready/leader)
croncron next
dbinfo/query (Read-Default, --write opt-in)
dashboardStats
failure-causesLetzte Fehlschläge, gruppiert nach normalisierter Meldung (--window-hours 1..720, Default 24)
dependencieslist/show/evidence (gespeicherte Abhängigkeiten zwischen Workflows, Maschinen, Credentials, Variablen und Custom Activities)
operationsgraph (Live-Ops-Snapshot: Workflows, Call-Graph, laufende + kürzlich beendete Executions; RBAC-folder-scoped; --window 30|60)
observabilitysummary/query/query-range
settingsstatus/restart-service/system-info/effective-sizing/get/put/test smtp|llm
secretsreencrypt
backupmanifest/export/preview/restore
configget/set

Globale Flags

Auf jedem Befehl — Basis-Settings-Klasse GlobalSettings (Settings/GlobalOptions.cs):

--server <URL>        Server-URL für diesen Aufruf überschreiben
--allow-insecure      HTTP nur für einen expliziten Loopback-Server erlauben (nur Entwicklung)
--profile <NAME>      Benanntes Verbindungsprofil (Default: 'default')
--tls-thumbprint <S>  Genau dieses Serverzertifikat akzeptieren (SHA-256-Fingerprint)
--insecure-tls        Zertifikatsprüfung nur für diesen Aufruf überspringen (nie gespeichert)
-o|--output <FORMAT>  table (TTY-Default) | json | yaml
--no-color            Farbausgabe aus (auto-off bei redirectiertem stdout)
-v|--verbose          HTTP-Request/Response-Trace auf stderr
np workflow list --profile prod -o json --no-color -v
np exec get 7e3f... --server https://np.internal:8443 -o yaml

Exit-Codes: 0 erfolgreich, 1 allgemeiner Fehler, 2 Lauf fehlgeschlagen/abgebrochen, 3 Anmeldung erforderlich, 4 Zugriff verweigert, 5 Server nicht verfügbar (Verbindung abgelehnt oder HTTP 503). Code 5 bedeutet nicht, dass ein schreibender Befehl gefahrlos wiederholt werden kann.

auth

np auth login ist interaktiv (fragt Username + Passwort ab, falls nicht per Flag geliefert). Flags: --username, --password (Literal — in Scripts meiden), --password-stdin (eine Zeile von stdin), --setup-token <T> (Bootstrap First-Admin), --windows (Anmeldung mit dem aktuellen Windows-Konto, ohne Passwort). Hat der Server noch keinen Admin, meldet np auth login das und verweist auf --setup-token (das Token steht auf der Abschlussseite des Setups bzw. in admin-setup.token im Datenverzeichnis) oder auf die erste Anmeldung im Browser. Ein falsches Token wird als abgelehnt gemeldet.

# Interaktiv (fragt user + pw ab)
np auth login --server https://np.internal:8443

# Vollständig gescriptet, Passwort via stdin
echo "S3cret!" | np auth login --username admin --password-stdin --server https://np.internal:8443

# Frischer Server: ersten Admin bootstrappen (admin-setup.token)
np auth login --username admin --password-stdin \
  --setup-token "$(cat admin-setup.token)" --server https://np.internal:8443

# Kerberos: als aktuelles Windows-Konto anmelden (braucht Authentication:Windows:Enabled)
np auth login --windows --server https://np.internal:8443

# Server mit selbstsigniertem Zertifikat (Lab): genau dieses Zertifikat pinnen
np auth login --server https://np.lab.local:8443 --tls-thumbprint A1B2...8F90

# Discovery (anonym, keine Session nötig) + Profil-Check
np auth methods --server https://np.internal:8443
np auth whoami -o json
np auth logout

Ohne --windows nutzt np auth login den Passwort-Endpunkt und deckt damit lokale sowie LDAP-Anmeldung ab. --windows authentifiziert das eigene Windows-Konto per Negotiate/Kerberos und braucht kein Passwort; Voraussetzung sind Authentication:Windows:Enabled am Server, ein registrierter SPN und ein Kerberos-Ticket (NTLM wird abgelehnt) — fehlt eines davon, endet der Befehl mit Exit-Code 3. OIDC bleibt ein Browserflow; np auth methods -o json liefert die Metadaten aus GET /api/auth/methods (oidc, oidcEndpoint, oidcDisplayName); die Tabellenansicht zeigt nur Local/LDAP/Windows.

workflow

run / trigger

run startet einen Lauf als authentifizierter User; trigger ist session-unabhängig und via X-Api-Key auf die im Schlüssel-Scope gelisteten Workflow-GUIDs begrenzt. Der Workflow benötigt außerdem einen aktiven manualTrigger.

run-Flags: -p|--params <k=v> (wiederholbar; nur erstes = splittet), --wait (pollen bis terminal), --follow (live Step-Events via SignalR), --debug, --timeout <s>.

np workflow run deploy-prod -p environment=staging -p revision=abcd123
np workflow run deploy-prod -p env=prod --debug --follow
np workflow run 21f1c0d4-... -p env=prod --wait --timeout 600 -o json

trigger-Flags: --api-key <K>, --api-key-stdin, env NODEPILOT_TRIGGER_API_KEY (Preferred für Scripts), -p|--params, --idempotency-key <K>, --timeout, --wait (erfordert JWT-Session, da /api/executions/{id} JWT-only ist).

# API-Key aus stdin, Idempotency-Key gegen CI-Replays
np workflow trigger nightly-reconcile --api-key-stdin \
  --idempotency-key "ci-$(date +%s)" -p day=2026-06-25 --wait < api.key

# Key via env (sicherer als --api-key)
NODEPILOT_TRIGGER_API_KEY=xyz np workflow trigger hourly-report -p window=1h --timeout 300

lock / unlock / publish / enable / disable / cancel-all

Alle nehmen <ID-OR-NAME>, keine Extra-Flags (außer publish).

np workflow lock deploy-prod
np workflow unlock deploy-prod
np workflow enable deploy-prod     # 423 wenn gelockt
np workflow disable deploy-prod    # ignoriert Locks (Incident-Kill-Switch)
np workflow cancel-all deploy-prod
np workflow concurrency-limit deploy-prod -m 5   # hoechstens 5 gleichzeitig; weitere reihen sich ein
np workflow concurrency-limit deploy-prod -m none  # wieder unbegrenzt
np workflow duplicate deploy-prod
np workflow force-unlock deploy-prod   # Admin; fragt nach

publish — atomar Save + Enable + Unlock. Flags: -f|--file <PATH> (required, JSON-Definition), --expected-version <N> (required, die Version, auf der die Datei beruht, siehe np workflow get; eine neuere Serverversion endet mit 409 workflow_version_conflict, statt überschrieben zu werden), --name, --description.

np workflow publish deploy-prod -f ./deploy-prod.def.json --expected-version 7
np workflow publish deploy-prod -f ./deploy-prod.def.json --expected-version 7 --description "bump revision" -o json

versions / rollback

np workflow versions deploy-prod -o json
np workflow version deploy-prod 12 -o yaml          # eine Version voll
np workflow rollback deploy-prod 12 --reason "revert bad config"

contract / coverage / stats

np workflow contract deploy-prod -o json            # Inputs (manualTrigger) + Outputs (returnData + system)
np workflow coverage deploy-prod --window-days 7    # Default 30, max 365
np workflow stats deploy-prod --window-days 30      # 1..365, per-step Dauer + Fail-Rate

step-test / step-test-context

step-test-Positionale: <WORKFLOW> <STEP-ID>. Flags: -m|--mock <stepName.field=value> (wiederholbar), --config-file <PATH> (JSON-Override für data.config; - = stdin).

np workflow step-test deploy-prod runHealth -m checkDisk.output=7 -m checkDisk.param.freeGb=7
np workflow step-test deploy-prod runHealth --config-file ./health-override.json
np workflow step-test-context deploy-prod runHealth --list-runs --limit 20
np workflow step-test-context deploy-prod runHealth --execution 7e3f...

export / import / import-scorch

np workflow export deploy-prod --out ./deploy-prod.envelope.json
np workflow export --all --out ./all-workflows.json -o json
np workflow import -f ./deploy-prod.envelope.json
cat all.json | np workflow import -f -               # Namenskollision → Suffix
np workflow import -f ./deploy.json --target-folder 8a2f...        # Ziel-Folder (fehlt → Root)
np workflow import-scorch -f ./scorch-export.ois_export   # SCOrch .ois_export XML
np workflow import-scorch -f ./scorch.ois_export --target-folder 8a2f...

# Sicherer CI-Ablauf: Import bleibt disabled, ID aus JSON lesen, danach bewusst aktivieren
workflow_id="$(np workflow import -f ./deploy.json -o json | jq -r '.workflows[0].id')"
np workflow enable "$workflow_id"

--target-folder <GUID> erfordert Edit auf dem Ziel-Folder (RBAC). JSON- und SCOrch-Importe erzeugen Workflows immer deaktiviert. Mit -o json steht der vollständige Importreport inklusive der erzeugten IDs auf stdout; Logs und Warnungen bleiben auf stderr. Für einen Bulk-Import jede gewünschte ID aus .workflows[] separat mit np workflow enable <ID> aktivieren.

move-folder

np workflow move-folder deploy-prod --target-folder 8a2f...

exec

Positional <EXECUTION-ID> (Guid) für get/steps/cancel/retry/watch/paused-steps.

np exec list -w deploy-prod --limit 20 -o table      # --workflow, --limit (ohne --limit blättert die CLI durch alle Läufe)
np exec get 7e3f...
np exec steps 7e3f...
np exec cancel 7e3f...
np exec retry 7e3f...                                 # nur terminal → 202 + Location
np exec watch 7e3f...
np exec watch 7e3f... --no-signalr                    # Polling-Fallback erzwingen
np exec paused-steps 7e3f...

resume (debug-paused): --step <ID> (required), --mode <continue|stepOver|stop> (Default continue), --override <k=v> (wiederholbar; globals.*- und __*-Schlüssel werden abgelehnt). paused-steps listet jeden wartenden Breakpoint mit Label, Grund, Pausenzeit und redigierten Variablen.

np exec resume 7e3f... --step runHealth --mode continue
np exec resume 7e3f... --step runHealth --mode stepOver --override freeGb=8 --override host=svr01
np exec resume 7e3f... --step runHealth --mode stop

machine

Maschinen-Body via Flags (nicht --file). create/update-Flags: --name, --hostname, --port (Default bei create: 5985, mit --ssl 5986), --ssl, --credential <GUID>, --tags <CSV>. update ist Client-seitig Patch (fetch + merge, Server will Full-Body).

np machine create --name srv01 --hostname srv01.internal --credential 4c2a... --tags prod,web
np machine update 9f1a... --port 5986 --ssl --tags prod,web,decommissioned
np machine list -o json
np machine get 9f1a...
np machine test 9f1a...
np machine test 9f1a... --credential 4c2a...   # Credential für diesen Probe überschreiben
np machine delete 9f1a...                       # fragt nach

credential

Credentials via Flags. create: --name, --username, --password (min 8 Zeichen; lieber --password-stdin), --password-stdin, --domain, --expires <ISO-DATE> (optionales Ablaufdatum — speist das CredentialExpiring-Alert-Signal). update: gleiche Flags, weggelassenes Password = unverändert, --no-expires löscht ein gesetztes Ablaufdatum.

echo 'Sup3rSecret!' | np credential create --name svc-winrm \
  --username svc-winrm@DOM --password-stdin --domain DOM --expires 2026-12-31
np credential list
np credential get 4c2a...
np credential update 4c2a... --name svc-winrm-v2 --password-stdin < newpw.txt   # Rotation
np credential update 4c2a... --no-expires                                       # Ablaufdatum entfernen
np credential delete 4c2a...

credential create hat kein --description-Flag — nur name/username/password/password-stdin/domain/expires.

globals

create: --name (Pattern [A-Za-z0-9_-]{1,100}, keine Punkte/Leerzeichen), --value/--value-stdin, --secret (DPAPI, masked on read), --description, --folder (Ordner-Id, -Pfad oder -Name; fehlt → Root).

Ordner sind rein organisatorisch — sie ändern nichts daran, wie {{globals.NAME}} auflöst (Namen bleiben global eindeutig).

np globals create --name adminEmail --value ops@internal --description "Default alert recipient"
echo '-----BEGIN PRIVATE KEY-----...' | np globals create --name signingKey --value-stdin --secret --folder /Secrets
np globals list -o json
np globals update 3b8c... --value new-email@internal
np globals update 3b8c... --no-secret
np globals update 3b8c... --folder /Environment/Prod       # verschiebt die Variable
np globals delete 3b8c...
np globals export --file ./globals.json                   # Secret-Werte ausgelassen (null)
np globals import -f ./globals.json --upsert
np globals import -f - --dry-run < globals.json

# Exportierte Secret-Werte sind null; --upsert bewahrt ein vorhandenes Secret.
# Neue Secrets brauchen einen echten Wert. Auch alte ***-Masken gelten als ausgelassen.

# Ordner-Baum (Admin)
np globals folder list
np globals folder create --name Databases
np globals folder create --name Prod --parent /Environment
np globals folder rename <folder-id> --name Renamed
np globals folder move <folder-id> --parent /Environment    # reparent (Zyklus-/Tiefen-geschützt)
np globals folder delete <folder-id>                        # nur wenn leer
np globals folder delete <folder-id> --recursive --yes      # samt Unterordnern und Variablen
np globals move-folder <var-id> --folder /Databases         # Variable → Ordner

user · shared-folder · maintenance

user (Admin)

np user list -o json
echo 'TempPw1!' | np user create --username jane.doe --password-stdin --role Operator
np user update 5a1c... --role Admin --active true
np user update 5a1c... --password-stdin < newpw.txt
np user delete 5a1c...

shared-folder (org RBAC-Baum)

np shared-folder list
np shared-folder create --name "Prod Workflows" --parent 8a2f...
np shared-folder rename 8a2f... --name "Prod Flows"
np shared-folder move 8a2f... --parent 9b1c...
np shared-folder move 8a2f... --to-root
np shared-folder delete 8a2f...                          # muss leer sein
np shared-folder delete 8a2f... --recursive --yes         # samt Unterordnern und Workflows
np shared-folder permissions 8a2f...
np shared-folder grant 8a2f... --principal-type User   --principal-key 5a1c...            --role FolderEditor
np shared-folder grant 8a2f... --principal-type Group  --principal-key S-1-5-21-100-200-300 --role FolderOperator
np shared-folder revoke 8a2f... 7e3f...                  # <FOLDER> <PERMISSION-ID>

Rollen: FolderViewer | FolderOperator | FolderEditor | FolderAdmin. --principal-type: User | Group (Role ist V1-reserviert).

maintenance (Admin)

create/update-Flags: --name, --description, --enabled/--disabled, --mode <Blackout|AllowOnly>, --scope <Global|Folders|Workflows>, --recurrence <OneTime|Weekly|Cron>, --tz <TZID>, --one-time-start/--one-time-end <ISO>, --days <CSV> (Sun..Sat; ein unbekannter Tag bricht den Befehl ab), --start/--end <HH:MM>, --cron <EXPR> (Quartz, mit Sekundenfeld), --duration-minutes <N>, --folder/--workflow (wiederholbar).

np maintenance list
# Wöchentliches Change-Freeze-Fenster Sa/So 02:00–04:00 Lokalzeit
np maintenance create --name "Change freeze" --mode Blackout --scope Global \
  --recurrence Weekly --days Sat,Sun --start 02:00 --end 04:00 --tz "W. Europe Standard Time"
# Einmaliges Deploy-Fenster, nur ein Workflow darf laufen
np maintenance create --name "Deploy window" --mode AllowOnly --scope Workflows \
  --workflow 21f1c0d4-... --recurrence OneTime \
  --one-time-start 2026-06-25T20:00:00Z --one-time-end 2026-06-25T22:00:00Z
# Cron-Fenster: jeden Samstag 03:00 Lokalzeit, 90 Minuten offen
np maintenance create --name "Sat patching" --mode Blackout --recurrence Cron \
  --cron "0 0 3 ? * SAT" --duration-minutes 90 --tz "W. Europe Standard Time"
np maintenance update 2c4b... --enabled --end 05:00
np maintenance delete 2c4b...

audit · health · cron · dashboard · observability

# Audit (Admin) — Cursor-Pagination via --after-ts/--after-id
np audit list --action WORKFLOW_PUBLISHED --since 2026-06-01T00:00:00Z --limit 50 -o json
np audit list --resource-type Workflow --resource-id 21f1c0d4-... \
  --after-ts 2026-06-25T10:00:00Z --after-id 7e3f...

# Health (anonym — /live + /ready + /leader), Exit 0 nur wenn live+ready ok.
# Der Leader-Status (leader|follower|leader_unhealthy) wird nur angezeigt —
# ein passiver HA-Follower ist gesund und kippt den Exit-Code nicht.
np health --server https://np.internal:8443

# Quartz cron — nächste Feuerzeiten
np cron next "0 0 2 ? * MON-FRI" --count 10

# Dashboard & Observability (Kennzahlen im Terminal; die Weboberfläche ist die Server-Adresse im Browser)
np dashboard -o json
np failure-causes                             # letzte 24 h, nach Ursache gruppiert
np failure-causes --window-hours 168 -o json  # 1..720 Stunden; GUIDs und Zeitstempel werden
                                              # vor dem Gruppieren aus den Meldungen gefaltet
np observability summary
np observability query --query "up{job=\"nodepilot\"}"
np observability query-range --query "rate(nodepilot_workflows_total[5m])" \
  --start 1719100000 --end 1719103600 --step 1m

alerting · operations

# Alerting (Read Admin/Op; Create/Edit/Delete/Test-Fire Admin-only)
np alerting catalog                           # Event-Typen, Filter-Felder, Kanäle
np alerting list
np alerting get 9a2f...
np alerting create --name "Prod-Fehler" --event-types ExecutionFailed \
  --scope Workflows --workflow 21f1c0d4-... --email ops@internal   # Regel komplett per Flags
np alerting update 9a2f... --cooldown-minutes 15 --disabled                 # nur übergebene Felder ändern
np alerting test-fire 9a2f...
np alerting deliveries --rule 9a2f...         # Zustell-Ledger der Regel (--status Pending|Sent|Failed)
np alerting deliveries --limit 50 -o json
np alerting delete 9a2f...

# Operations — Live-Ops-Snapshot: Workflows, Call-Graph, laufende + kürzlich
# beendete Executions (alle Rollen, RBAC-folder-scoped)
np operations graph -o json
np operations graph --window 60               # Rückblick für beendete Läufe: 30 | 60 Min

Einzeln aufgelistet werden die neuesten 4000 beendeten Läufe des Fensters. Gibt es mehr, liefert density[] zusätzlich gebucketete Zähler (total/failed/cancelled je Workflow und Zeitscheibe) über das ganze Fenster — „wie viel lief in der letzten Stunde?" bleibt also beantwortbar, auch wenn nicht jeder Lauf einzeln aufgeführt ist. Die Tabellenausgabe nennt in dem Fall die Gesamtzahl; meta.densityBucketSeconds gibt die Scheibenbreite, meta.densityCapped sagt, dass selbst die Zähler nur eine Untergrenze sind.

system-alert · policies (ADR 0008)

np system-alert catalog                       # verfuegbare Quellen + Felder/Parameter
np system-alert list
np system-alert get 9a2f...
np system-alert create --file ./policy.json   # SaveSystemAlertPolicyRequest als JSON
np system-alert update 9a2f... --file ./policy.json
np system-alert enable 9a2f...
np system-alert disable 9a2f...
np system-alert test-fire 9a2f...
np system-alert delete 9a2f...

settings (Admin, ETag-gated)

File-Roundtrip, kein set key=value. get [SECTION] (mit --etag-only für Chaining), put <SECTION> (--file, --etag), test smtp|llm (--file).

np settings status
np settings restart-service --yes                  # Windows-Dienst neu starten (Admin)
np settings system-info
np settings get Smtp -o json
ETAG=$(np settings get Smtp --etag-only)            # schwacher Validator, Quotes/Prefix stripped
np settings put Smtp --file ./smtp.json --etag "$ETAG"
np settings test smtp --file ./smtp-probe.json
np settings test llm --file ./llm-probe.json

smtp-probe.json (Envelope, inneres settings = serverseitiges SmtpSettingsDto; ******** oder __unchanged__ behält das gespeicherte Passwort):

{ "settings": { "Host": "smtp.internal", "Port": 587, "Username": "alerts", "Password": "********",
                "From": "alerts@internal" },
  "toAddress": "ops@internal" }

secrets · backup · db · config

# secrets — Bulk-Re-Encryption nach Key-Rotation / Provider-Migration
np secrets reencrypt --yes                            # Exit 1 bei Partial Success

# backup — Passphrase NIE als Flag (--passphrase-env / --passphrase-file / Prompt)
np backup manifest
np backup export --out ./np-2026-06-25.npbackup --passphrase-env BACKUP_PW
np backup export --out ./partial.npbackup --sections workflows,credentials --passphrase-file ./pw.txt
np backup preview ./np-2026-06-25.npbackup --passphrase-env BACKUP_PW
np backup restore ./np-2026-06-25.npbackup --passphrase-env BACKUP_PW \
  --policy skip,users=overwrite --yes                 # skip|rename|overwrite, per-section überschreibbar

# db — Read-Default, --write opt-in (DbAdmin:AllowWriteQueries=true serverseitig)
np db info
np db query --sql "SELECT TOP 10 * FROM Workflows ORDER BY CreatedAt DESC"
np db query --file ./remediate.sql --write --yes

# config — CLI-seitig (kein Server-Roundtrip)
np config get
np config set server https://np.internal:8443
np config set default-profile prod
np config set tls-thumbprint A1B2...8F90   # 'none' löscht den Pin wieder

TLS: nicht vertrauenswürdige Serverzertifikate

Hinter „TLS geht nicht" stecken zwei verschiedene Störungen, die verschiedene Abhilfen brauchen.

Der Host ist keiner der Namen des Zertifikats. Die Meldung lautet „The hostname is not among the certificate names" und listet die Namen auf, die das Zertifikat trägt. Vertrauen ist hier nicht das Problem, sondern die URL. Die CLI auf einen Namen zeigen lassen, den das Zertifikat wirklich nennt — die Meldung druckt den Befehl mit:

np config set server https://nodepilot.contoso.local:8443

Das ist der übliche Ausgang, wenn np auf dem Server selbst gegen https://localhost läuft: Das Zertifikat nennt den öffentlichen Hostnamen, nicht localhost. Ein Import in einen Root-Store hilft nicht — ein Namens-Mismatch ist kein Vertrauensproblem. Pinnen nur dann, wenn der Host so bleiben muss, etwa hinter einem Reverse-Proxy oder einem Alias.

Die Kette validiert nicht. Die Meldung lautet „This client does not trust the server certificate". Diesen Fall decken die beiden Optionen unten ab.

Die CLI prüft die Zertifikatskette wie jeder andere Client — anders als ein Browser lässt sie sich nicht durchklicken. Schlägt der Handshake fehl, nennt die Fehlermeldung die Ursache, das vom Server präsentierte Zertifikat und dessen SHA-256-Fingerprint.

Zwei unterstützte Wege für einen Server, dessen Zertifikat die Client-Maschine nicht kennt:

  • Pinnen (empfohlen für Lab/Pilot): --tls-thumbprint <SHA-256>. np auth login speichert den Pin im Profil, danach brauchen Folgeaufrufe kein Flag mehr. Alternativ NODEPILOT_TLS_THUMBPRINT oder np config set tls-thumbprint <SHA-256>.
  • Systemweit vertrauen: das Zertifikat nach Cert:\LocalMachine\Root importieren — gilt dann für alle Programme, nicht nur für np.

Regeln, die dabei gelten:

  • Der Pin ist additiv: ein Zertifikat mit gültiger Kette und passendem Hostnamen wird auch bei abweichendem Pin akzeptiert, damit eine reguläre Zertifikatserneuerung das Profil nicht aussperrt.
  • Schlägt die reguläre TLS-Prüfung fehl, entscheidet ein gesetzter Pin: ein passender Pin akzeptiert das Zertifikat auch bei einem Hostnamen-Mismatch; ein abweichender Pin führt zur Ablehnung, auch mit --insecure-tls.
  • Ein gespeicherter Pin gilt nur für den Server, für den er gesetzt wurde; ein Serverwechsel löscht ihn.
  • Ohne gesetzten Pin kann --insecure-tls (oder NODEPILOT_TLS_NO_VERIFY=1) einen TLS-Prüffehler für einen Aufruf übergehen. Der Bypass wird nie gespeichert und druckt jedes Mal eine Warnung.
  • Präzedenz: --tls-thumbprint › NODEPILOT_TLS_THUMBPRINT › Profil.

Token-Storage

DPAPI-verschlüsselt (CurrentUser-Scope) unter %APPDATA%\NodePilot\session-<profile-hash>.dat (eine Datei pro Profil; der Name ist ein SHA-256-Hash des Profilnamens ohne Beachtung der Groß-/Kleinschreibung). Refresh transparent via TokenRefreshHandler. Plaintext-Config (Server-URL, Default-Profile, TLS-Pin) in config.json.

Architektur-Konvention

Neuer API-Endpoint → parallele CLI-Methode in NodePilotApiClient.cs + Command. DTOs in Cli/Api/Dtos/ dupliziert (kein ProjectReference).

Alle Kapitel