17 KiB
Component: CLI
Purpose
The CLI is a standalone command-line tool for scripting and automating administrative operations against the ScadaLink 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/ScadaLink.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.CommandLinelibrary for command/subcommand/option parsing with built-in help generation. - Transport: HTTP client connecting to the Central Host's
POST /managementendpoint. Authentication is via HTTP Basic Auth — the server performs LDAP bind and role resolution. - Serialization: Commands serialized as JSON with a type discriminator (
commandfield). Message contracts from Commons define the command types.
Authentication
The CLI sends user credentials to the Management API via HTTP Basic Auth:
- The user provides credentials via
--username/--passwordoptions. - On each request, the CLI encodes credentials as a Basic Auth header and sends them with the command.
- The server performs LDAP authentication, group lookup, and role resolution — the CLI does not communicate with LDAP directly.
- 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 atPOST /managementon 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:
scadalink <group> <action> [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/ScadaLink.CLI/README.md; the command lists below mirror the implemented command
tree at the time of writing.
Template Commands
scadalink template list
scadalink template get --id <id>
scadalink template create --name <name> [--description <desc>] [--parent-id <id>]
scadalink template update --id <id> [--name <name>] [--description <desc>] [--parent-id <id>]
scadalink template validate --id <id>
scadalink template delete --id <id>
scadalink template attribute add --template-id <id> --name <name> --data-type <type> [--value <value>] [--description <desc>] [--data-source <ref>] [--locked <bool>]
scadalink template attribute update --id <id> [--name <name>] [--data-type <type>] [--value <value>] [--description <desc>] [--data-source <ref>] [--locked <bool>]
scadalink template attribute delete --id <id>
scadalink template alarm add --template-id <id> --name <name> --trigger-type <type> --priority <n> [--description <desc>] [--trigger-config <json>] [--locked <bool>]
scadalink template alarm update --id <id> [--name <name>] [--trigger-type <type>] [--priority <n>] [--description <desc>] [--trigger-config <json>] [--locked <bool>]
scadalink template alarm delete --id <id>
scadalink template script add --template-id <id> --name <name> --code <code> --trigger-type <type> [--trigger-config <json>] [--locked <bool>] [--parameters <json>] [--return-def <json>]
scadalink template script update --id <id> [--name <name>] [--code <code>] [--trigger-type <type>] [--trigger-config <json>] [--locked <bool>] [--parameters <json>] [--return-def <json>]
scadalink template script delete --id <id>
scadalink template composition add --template-id <id> --instance-name <name> --composed-template-id <id>
scadalink template composition delete --template-id <id> --instance-name <name>
Instance Commands
scadalink instance list [--site-id <id>] [--template-id <id>] [--search <term>]
scadalink instance get --id <id>
scadalink instance create --name <name> --template-id <id> --site-id <id> [--area-id <id>]
scadalink instance set-bindings --id <id> --bindings <json>
scadalink instance set-overrides --id <id> --overrides <json>
scadalink instance alarm-override set --instance-id <id> --alarm <name> [--trigger-config <json>] [--priority <n>]
scadalink instance alarm-override delete --instance-id <id> --alarm <name>
scadalink instance alarm-override list --instance-id <id>
scadalink instance set-area --id <id> [--area-id <id>]
scadalink instance diff --id <id>
scadalink instance deploy --id <id>
scadalink instance enable --id <id>
scadalink instance disable --id <id>
scadalink instance delete --id <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}.
Site Commands
scadalink site list
scadalink site get --id <id>
scadalink site create --identifier <id> --name <name> [--description <desc>] [--node-a-address <addr>] [--node-b-address <addr>] [--grpc-node-a-address <addr>] [--grpc-node-b-address <addr>]
scadalink site update --id <id> [--name <name>] [--description <desc>] [--node-a-address <addr>] [--node-b-address <addr>] [--grpc-node-a-address <addr>] [--grpc-node-b-address <addr>]
scadalink site delete --id <id>
scadalink site area list --site-id <id>
scadalink site area create --site-id <id> --name <name> [--parent-id <id>]
scadalink site area update --id <id> --name <name>
scadalink site area delete --id <id>
scadalink site deploy-artifacts [--site-id <id>]
Deployment Commands
scadalink deploy instance --id <id>
scadalink deploy artifacts [--site-id <id>]
scadalink deploy status [--instance-id <id>] [--status <status>] [--page <n>] [--page-size <n>]
Data Connection Commands
scadalink data-connection list [--site-id <id>]
scadalink data-connection get --id <id>
scadalink data-connection create --site-id <id> --name <name> --protocol <protocol> [--backup-config <json>] [--failover-retry-count <n>]
scadalink data-connection update --id <id> [--name <name>] [--protocol <protocol>] [--backup-config <json>] [--failover-retry-count <n>]
scadalink data-connection delete --id <id>
External System Commands
scadalink external-system list
scadalink external-system get --id <id>
scadalink external-system create --name <name> --endpoint-url <url> --auth-type <type> [--auth-config <json>]
scadalink external-system update --id <id> [--name <name>] [--endpoint-url <url>] [--auth-type <type>] [--auth-config <json>]
scadalink external-system delete --id <id>
scadalink external-system method list --external-system-id <id>
scadalink external-system method get --id <id>
scadalink external-system method create --external-system-id <id> --name <name> --http-method <verb> --path <path> [--params <json>] [--return <json>]
scadalink external-system method update --id <id> [--name <name>] [--http-method <verb>] [--path <path>] [--params <json>] [--return <json>]
scadalink external-system method delete --id <id>
Notification Commands
scadalink notification list
scadalink notification get --id <id>
scadalink notification create --name <name> --emails <comma-separated>
scadalink notification update --id <id> [--name <name>] [--emails <comma-separated>]
scadalink notification delete --id <id>
scadalink notification smtp list
scadalink notification smtp update --id <id> --server <host> --port <n> --auth-mode <mode> --from-address <email>
Security Commands
scadalink security api-key list
scadalink security api-key create --name <name>
scadalink security api-key update --id <id> --enabled <bool>
scadalink security api-key delete --id <id>
scadalink security role-mapping list
scadalink security role-mapping create --ldap-group <group> --role <role>
scadalink security role-mapping update --id <id> [--ldap-group <group>] [--role <role>]
scadalink security role-mapping delete --id <id>
scadalink security scope-rule list [--mapping-id <id>]
scadalink security scope-rule add --mapping-id <id> --site-id <id>
scadalink security scope-rule delete --id <id>
Audit Log Commands
scadalink audit-log query [--user <username>] [--entity-type <type>] [--action <action>] [--from <date>] [--to <date>] [--page <n>] [--page-size <n>]
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 scadalink audit group below.
Centralized Audit Commands
The scadalink audit group targets the centralized Audit Log component (#23) and
exposes the UI-equivalent operational audit surface. All three subcommands require
both the OperationalAudit and AuditExport permissions (see Security & Auth #10);
the server enforces permission checks and returns HTTP 403 (CLI exit code 2) on
denial.
scadalink audit query --site <s> --since <t> [--until <t>] [--kind <k>] [--user <u>] [--entity-id <id>] [--correlation-id <id>] [--status <s>] [--page <n>] [--page-size <n>]
scadalink audit export --since <t> --until <t> --format csv|jsonl|parquet --output <path> [--site <s>] [--kind <k>]
scadalink audit verify-chain --month <YYYY-MM>
audit query— filtered query against the centralAuditLogtable, matching the Central UI filter set (site, time range, audit kind, user, entity, correlation ID, status, paging). Results stream as JSON (default) or table.audit export— server-side streaming export of the centralAuditLogto 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 asaudit 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
scadalink health summary
scadalink health site --identifier <site-identifier>
scadalink health event-log --site <site-identifier> [--event-type <type>] [--severity <level>] [--keyword <term>] [--from <date>] [--to <date>] [--page <n>] [--page-size <n>] [--instance-name <name>]
scadalink health parked-messages --site <site-identifier> [--page <n>] [--page-size <n>]
Debug Commands
scadalink debug snapshot --id <id>
scadalink debug stream --id <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
scadalink shared-script list
scadalink shared-script get --id <id>
scadalink shared-script create --name <name> --code <code> [--parameters <json>] [--return-def <json>]
scadalink shared-script update --id <id> [--name <name>] [--code <code>] [--parameters <json>] [--return-def <json>]
scadalink shared-script delete --id <id>
Database Connection Commands
scadalink db-connection list
scadalink db-connection get --id <id>
scadalink db-connection create --name <name> --connection-string <string>
scadalink db-connection update --id <id> [--name <name>] [--connection-string <string>]
scadalink db-connection delete --id <id>
Inbound API Method Commands
scadalink api-method list
scadalink api-method get --id <id>
scadalink api-method create --name <name> --script <code> [--timeout <seconds>] [--parameters <json>] [--return-def <json>]
scadalink api-method update --id <id> [--script <code>] [--timeout <seconds>] [--parameters <json>] [--return-def <json>]
scadalink api-method delete --id <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):
- Command-line options:
--url,--username,--password,--format. - Environment variables:
SCADALINK_MANAGEMENT_URL— Management API URL (e.g.,http://central-host:5000).SCADALINK_FORMAT— Default output format (jsonortable).SCADALINK_USERNAME/SCADALINK_PASSWORD— LDAP credentials. Preferred over--passwordon the command line, which is visible in process listings and shell history. Credentials are never read from the config file.
- Configuration file:
~/.scadalink/config.json— Persistent defaults for management URL and output format only (never credentials).
Configuration File Format
{
"managementUrl": "http://central-host:5000"
}
Output Formats
- JSON (default): Machine-readable JSON output to stdout. Suitable for piping to
jqor processing in scripts. Errors are written to stderr as JSON objects witherrorandcodefields. - Table (
--format tableor--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 streamcommand'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. Thescadalink auditcommand group rides this same transport — there is no separate audit endpoint. - Audit Log (#23): The
scadalink audit query,audit export, andaudit verify-chainsubcommands target the centralized Audit Log component's query/export/verify surfaces via the Management API. Permission checks (OperationalAudit,AuditExport) are enforced server-side.
Interactions
- Management Service (via HTTP): The primary runtime dependency. All operations except
debug streamare 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 /managementand 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 streamcommand connects to the/hubs/debug-streamhub on the central server for real-time event streaming. This is the only CLI command that uses a persistent connection rather than request/response.