# Component: CLI ## Purpose The CLI is a standalone command-line tool for scripting and automating administrative operations against the ScadaBridge central cluster. It connects to the Central Host's HTTP Management API (`POST /management`), which dispatches commands to the ManagementActor. Authentication and role resolution are handled server-side — the CLI sends credentials via HTTP Basic Auth. The CLI provides the same administrative capabilities as the Central UI, enabling automation, batch operations, and integration with CI/CD pipelines. ## Location Standalone executable, not part of the Host binary. Deployed on any machine with HTTP access to a central node. `src/ZB.MOM.WW.ScadaBridge.CLI/` ## Responsibilities - Parse command-line arguments and dispatch to the appropriate management operation. - Send HTTP requests to the Central Host's Management API endpoint with Basic Auth credentials. - Display structured responses from the Management API. - Support both JSON and human-readable table output formats. ## Technology - **Argument parsing**: `System.CommandLine` library for command/subcommand/option parsing with built-in help generation. - **Transport**: HTTP client connecting to the Central Host's `POST /management` endpoint. Authentication is via HTTP Basic Auth — the server performs LDAP bind and role resolution. - **Serialization**: Commands serialized as JSON with a type discriminator (`command` field). Message contracts from Commons define the command types. ## Authentication The CLI sends user credentials to the Management API via HTTP Basic Auth: 1. The user provides credentials via `--username` / `--password` options. 2. On each request, the CLI encodes credentials as a Basic Auth header and sends them with the command. 3. The server performs LDAP authentication, group lookup, and role resolution — the CLI does not communicate with LDAP directly. 4. Credentials are not stored or cached between invocations. Each CLI invocation requires fresh credentials. ## Connection The CLI connects to the Central Host via HTTP: - **Management URL**: The URL of a central node's web server (e.g., `http://localhost:9001`). The management API is served at `POST /management` on the same host as the Central UI. - **Failover**: For HA, use a load balancer URL in front of both central nodes. The management API is stateless (Basic Auth per request), so any central node can handle any request without sticky sessions. - **No Akka.NET dependency**: The CLI is a pure HTTP client with no Akka.NET runtime. ## Command Structure The CLI uses a hierarchical subcommand structure mirroring the Management Service message groups: ``` scadabridge [options] ``` All entities are identified by their integer **ID** (via `--id`, `--template-id`, `--site-id`, etc.), not by name. Create/update commands take individual flags — there is no `--file` option. The authoritative, always-current reference is the in-repo `src/ZB.MOM.WW.ScadaBridge.CLI/README.md`; the command lists below mirror the implemented command tree at the time of writing. ### Template Commands ``` scadabridge template list scadabridge template get --id scadabridge template create --name [--description ] [--parent-id ] scadabridge template update --id [--name ] [--description ] [--parent-id ] scadabridge template validate --id scadabridge template delete --id scadabridge template attribute add --template-id --name --data-type [--value ] [--description ] [--data-source ] [--locked ] scadabridge template attribute update --id [--name ] [--data-type ] [--value ] [--description ] [--data-source ] [--locked ] scadabridge template attribute delete --id scadabridge template alarm add --template-id --name --trigger-type --priority [--description ] [--trigger-config ] [--locked ] scadabridge template alarm update --id [--name ] [--trigger-type ] [--priority ] [--description ] [--trigger-config ] [--locked ] scadabridge template alarm delete --id scadabridge template script add --template-id --name --code --trigger-type [--trigger-config ] [--locked ] [--parameters ] [--return-def ] scadabridge template script update --id [--name ] [--code ] [--trigger-type ] [--trigger-config ] [--locked ] [--parameters ] [--return-def ] scadabridge template script delete --id scadabridge template composition add --template-id --instance-name --composed-template-id scadabridge template composition delete --template-id --instance-name scadabridge template native-alarm-source add --template-id --name --connection --source-ref [--filter ] [--description ] [--locked] scadabridge template native-alarm-source list --template-id scadabridge template native-alarm-source remove --id ``` ### Instance Commands ``` scadabridge instance list [--site-id ] [--template-id ] [--search ] scadabridge instance get --id scadabridge instance create --name --template-id --site-id [--area-id ] scadabridge instance set-bindings --id --bindings scadabridge instance set-overrides --id --overrides scadabridge instance alarm-override set --instance-id --alarm [--trigger-config ] [--priority ] scadabridge instance alarm-override delete --instance-id --alarm scadabridge instance alarm-override list --instance-id scadabridge instance native-alarm-source set --instance-id --source [--connection ] [--source-ref ] [--filter ] scadabridge instance native-alarm-source clear --instance-id --source scadabridge instance set-area --id [--area-id ] scadabridge instance diff --id scadabridge instance deploy --id scadabridge instance enable --id scadabridge instance disable --id scadabridge instance delete --id ``` `--bindings` is a JSON array of `[attributeName, dataConnectionId]` pairs, e.g. `[["Speed", 5], ["Mode", 7]]`. `--overrides` is a JSON object of attribute name to value, e.g. `{"Speed": "100", "Mode": null}`. ### Native Alarm Source Commands The `native-alarm-source` subcommands manage the **read-only native alarm mirror** — alarms surfaced from an alarm-capable data connection rather than evaluated by the ScadaBridge alarm engine. Native alarm sources are declared on a template and may be overridden per instance. The subcommands map to management commands that resolve via `ManagementCommandRegistry`: - `--connection` names an alarm-capable data connection (**OPC UA** or **MxGateway**). - `--source-ref` is the connection-specific reference: an **OPC UA `SourceNode` nodeId** or an **MxAccess object/area**. - `--filter` is an optional connection-specific filter expression that narrows the mirrored alarm set. **Template-level** (defines the inherited native alarm sources): | CLI command | Management command | Required role | |-------------|--------------------|---------------| | `template native-alarm-source add` | `AddTemplateNativeAlarmSourceCommand` | Design | | `template native-alarm-source list` | `ListTemplateNativeAlarmSourcesCommand` | — | | `template native-alarm-source remove` | `DeleteTemplateNativeAlarmSourceCommand` | Design | `add` takes `--name`, `--connection`, and `--source-ref` (required), plus optional `--filter`, `--description`, and `--locked` (a flag that prevents instance-level override). `remove` targets a single native alarm source by its own `--id`. **Instance-level** (per-instance overrides of an inherited source; upsert semantics): | CLI command | Management command | Required role | |-------------|--------------------|---------------| | `instance native-alarm-source set` | `SetInstanceNativeAlarmSourceOverrideCommand` | Deployment | | `instance native-alarm-source clear` | `DeleteInstanceNativeAlarmSourceOverrideCommand` | Deployment | `set` is an **upsert** keyed by `--instance-id` and `--source` (the inherited source name): a blank/omitted `--connection`, `--source-ref`, or `--filter` keeps the inherited value, so only the supplied options are overridden. `clear` removes the override and **reverts the instance to the inherited template value**. ### Site Commands ``` scadabridge site list scadabridge site get --id scadabridge site create --identifier --name [--description ] [--node-a-address ] [--node-b-address ] [--grpc-node-a-address ] [--grpc-node-b-address ] scadabridge site update --id [--name ] [--description ] [--node-a-address ] [--node-b-address ] [--grpc-node-a-address ] [--grpc-node-b-address ] scadabridge site delete --id scadabridge site area list --site-id scadabridge site area create --site-id --name [--parent-id ] scadabridge site area update --id --name scadabridge site area delete --id scadabridge site deploy-artifacts [--site-id ] ``` ### Deployment Commands ``` scadabridge deploy instance --id scadabridge deploy artifacts [--site-id ] scadabridge deploy status [--instance-id ] [--status ] [--page ] [--page-size ] ``` ### Data Connection Commands ``` scadabridge data-connection list [--site-id ] scadabridge data-connection get --id scadabridge data-connection create --site-id --name --protocol [--backup-config ] [--failover-retry-count ] scadabridge data-connection update --id [--name ] [--protocol ] [--backup-config ] [--failover-retry-count ] scadabridge data-connection delete --id ``` ### External System Commands ``` scadabridge external-system list scadabridge external-system get --id scadabridge external-system create --name --endpoint-url --auth-type [--auth-config ] scadabridge external-system update --id [--name ] [--endpoint-url ] [--auth-type ] [--auth-config ] scadabridge external-system delete --id scadabridge external-system method list --external-system-id scadabridge external-system method get --id scadabridge external-system method create --external-system-id --name --http-method --path [--params ] [--return ] scadabridge external-system method update --id [--name ] [--http-method ] [--path ] [--params ] [--return ] scadabridge external-system method delete --id ``` ### Notification Commands ``` scadabridge notification list scadabridge notification get --id scadabridge notification create --name --emails scadabridge notification update --id [--name ] [--emails ] scadabridge notification delete --id scadabridge notification smtp list scadabridge notification smtp update --id --server --port --auth-mode --from-address ``` ### Security Commands ``` scadabridge security api-key list scadabridge security api-key create --name scadabridge security api-key update --id --enabled scadabridge security api-key delete --id scadabridge security role-mapping list scadabridge security role-mapping create --ldap-group --role scadabridge security role-mapping update --id [--ldap-group ] [--role ] scadabridge security role-mapping delete --id scadabridge security scope-rule list [--mapping-id ] scadabridge security scope-rule add --mapping-id --site-id scadabridge security scope-rule delete --id ``` ### Audit Log Commands ``` scadabridge audit-log query [--user ] [--entity-type ] [--action ] [--from ] [--to ] [--page ] [--page-size ] ``` The legacy `audit-log query` above targets the original configuration-change audit (IAuditService) surface. The new centralized Audit Log component (#23) is exposed via the `scadabridge audit` group below. ### Centralized Audit Commands The `scadabridge audit` group targets the centralized Audit Log component (#23) and exposes the UI-equivalent operational audit surface. Permissions follow the same read-vs-export split the Central UI uses (see Component-AuditLog.md, Security & Tamper-Evidence, and Security & Auth #10): `audit query` and `audit verify-chain` require the `OperationalAudit` permission; `audit export` additionally requires `AuditExport`. The server enforces permission checks and returns HTTP 403 (CLI exit code 2) on denial. ``` scadabridge audit query --since [--until ] [--channel ] [--kind ] [--status ] [--site ] [--instance ] [--target ] [--actor ] [--correlation-id ] [--errors-only] [--page ] [--page-size ] scadabridge audit export --since --until --format csv|jsonl|parquet --output [--channel ] [--kind ] [--status ] [--site ] [--target ] [--actor ] scadabridge audit verify-chain --month ``` - `audit query` — filtered query against the central `AuditLog` table, matching the Central UI Audit Log page filter set (time range, channel, kind, status, site, instance/script, target, actor, correlation ID, errors-only). Results stream as JSON (default) or table. - `audit export` — server-side streaming export of the central `AuditLog` to the requested format (`csv`, `jsonl`, `parquet`) written to `--output`. The server streams rows rather than materializing them in memory; the CLI writes bytes through to disk. Supports the same scoping filters as `audit query`. - `audit verify-chain` — hash-chain verification for the named month. **No-op in v1**: the command is defined so the command tree is stable, but verification only becomes meaningful once the hash-chain ships (see Component-AuditLog.md, Security & Tamper-Evidence). Until then, the server responds with a "verification not yet available" status and the CLI exits 0. ### Health Commands ``` scadabridge health summary scadabridge health site --identifier scadabridge health event-log --site [--event-type ] [--severity ] [--keyword ] [--from ] [--to ] [--page ] [--page-size ] [--instance-name ] scadabridge health parked-messages --site [--page ] [--page-size ] ``` ### Debug Commands ``` scadabridge debug snapshot --id scadabridge debug stream --id ``` The `debug snapshot` command retrieves a point-in-time snapshot via the HTTP Management API. The `debug stream` command streams live attribute values and alarm state changes in real-time using a SignalR WebSocket connection. The CLI connects to the `/hubs/debug-stream` SignalR hub on the central server, authenticates with Basic Auth, and subscribes to the specified instance. Events are printed as they arrive — JSON format (default) outputs one NDJSON object per event; table format shows streaming rows. Press Ctrl+C to disconnect. Key behaviors: - **Automatic reconnection**: Uses SignalR's `.WithAutomaticReconnect()` to re-establish the connection on loss. - **Re-subscription**: Automatically re-subscribes to the instance after reconnection. - **Traefik compatible**: Works through the Traefik reverse proxy — WebSocket upgrade is proxied natively. - **Required role**: `Deployment`. Unlike `debug snapshot` (which uses the HTTP Management API), `debug stream` uses `Microsoft.AspNetCore.SignalR.Client` as a dependency for its WebSocket transport. ### Shared Script Commands ``` scadabridge shared-script list scadabridge shared-script get --id scadabridge shared-script create --name --code [--parameters ] [--return-def ] scadabridge shared-script update --id [--name ] [--code ] [--parameters ] [--return-def ] scadabridge shared-script delete --id ``` ### Database Connection Commands ``` scadabridge db-connection list scadabridge db-connection get --id scadabridge db-connection create --name --connection-string scadabridge db-connection update --id [--name ] [--connection-string ] scadabridge db-connection delete --id ``` ### Inbound API Method Commands ``` scadabridge api-method list scadabridge api-method get --id scadabridge api-method create --name --script [--timeout ] [--parameters ] [--return-def ] scadabridge api-method update --id [--script ] [--timeout ] [--parameters ] [--return-def ] scadabridge api-method delete --id ``` The `--format json|table` option is recursive and accepted on every command above. ## Configuration Configuration is resolved in the following priority order (highest wins): 1. **Command-line options**: `--url`, `--username`, `--password`, `--format`. 2. **Environment variables**: - `SCADALINK_MANAGEMENT_URL` — Management API URL (e.g., `http://central-host:5000`). - `SCADALINK_FORMAT` — Default output format (`json` or `table`). - `SCADALINK_USERNAME` / `SCADALINK_PASSWORD` — LDAP credentials. Preferred over `--password` on the command line, which is visible in process listings and shell history. Credentials are never read from the config file. 3. **Configuration file**: `~/.scadabridge/config.json` — Persistent defaults for management URL and output format only (never credentials). ### Configuration File Format ```json { "managementUrl": "http://central-host:5000" } ``` ## Output Formats - **JSON** (default): Machine-readable JSON output to stdout. Suitable for piping to `jq` or processing in scripts. Errors are written to stderr as JSON objects with `error` and `code` fields. - **Table** (`--format table` or `--table`): Human-readable tabular output with aligned columns. Suitable for interactive use. ## Exit Codes | Code | Meaning | |------|---------| | 0 | Success | | 1 | General error (command failed, connection failure, or authentication failure) | | 2 | Authorization failure (insufficient role) | ## Error Handling - **Connection failure**: If the CLI cannot connect to the management URL (e.g., DNS failure, connection refused), it exits with code 1 and a descriptive error message. - **Command timeout**: If the server does not respond within 30 seconds, the command fails with a timeout error (HTTP 504). - **Authentication failure**: If the server returns HTTP 401 (LDAP bind failed), the CLI exits with code 1. - **Authorization failure**: If the server returns HTTP 403, the CLI exits with code 2. ## Dependencies - **Commons**: Message contracts (`Messages/Management/`) for command type definitions and registry. - **System.CommandLine**: Command-line argument parsing. - **Microsoft.AspNetCore.SignalR.Client**: SignalR client for the `debug stream` command's WebSocket connection. - **Management Service (#18)**: The CLI hits the central cluster via the existing HTTP Management API (`POST /management`), which dispatches to the ManagementActor. The `scadabridge audit` command group rides a parallel REST surface on the same Host (`GET /api/audit/query` and `GET /api/audit/export`), sharing HTTP Basic Auth with `/management` but bypassing the actor for read-only, keyset-paged / streaming workloads. - **Audit Log (#23)**: The `scadabridge audit query` and `audit export` subcommands target the centralized Audit Log component's REST endpoints (`GET /api/audit/query`, `GET /api/audit/export`) on the Host's Management API surface; `audit verify-chain` rides `POST /management` until hash-chain verification ships. Permission checks (`OperationalAudit`, `AuditExport`) are enforced server-side by `AuditEndpoints`. ## Interactions - **Management Service (via HTTP)**: The primary runtime dependency. All operations except `debug stream` are sent as HTTP POST requests to the Management API endpoint on a central node, which dispatches to the ManagementActor. - **Central Host**: Serves the Management API at `POST /management` and the debug stream SignalR hub at `/hubs/debug-stream`. Handles LDAP authentication, role resolution, and ManagementActor dispatch. - **Debug Stream Hub (via SignalR WebSocket)**: The `debug stream` command connects to the `/hubs/debug-stream` hub on the central server for real-time event streaming. This is the only CLI command that uses a persistent connection rather than request/response.