e2f433e849
Every CLI option that can carry a secret gets a `--<name>-file <path|->`
sibling ('-' reads stdin), so the value stays out of the process list and
shell history: --password-file (global), --primary-config-file /
--backup-config-file (data-connection create|update), --auth-config-file
(external-system create|update), --connection-string-file (db-connection
create|update), --credentials-file (notification smtp create|update) and
--auth-token-file (notification sms create|update). Follows the existing
--config-file convention; `@path` is avoided because System.CommandLine
expands @-tokens as response files.
The value is read at parse time; a missing/unreadable/empty source, or '-'
on a terminal stdin, is a parse error naming option + path only, never the
content. Inline and file forms are mutually exclusive, at most one option
may read stdin, and one trailing line break is stripped. Inline forms keep
working.
docker/seed-sites.sh and docker-env2/seed-sites.sh now write the MxGateway
config (windev gateway key) to a mode-600 temp file removed by an EXIT trap
and pass --primary-config-file; seed-flow-outbound-demo.sh pipes its rig
connection string via --connection-string-file -. CLI README (new "Secrets
from a file or stdin" section + per-command tables), Component-CLI.md and
both docker READMEs updated.
Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
173 lines
7.8 KiB
Bash
Executable File
173 lines
7.8 KiB
Bash
Executable File
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
# Seed env2's single test site with Akka and gRPC addresses.
|
|
# Run after deploy.sh once the env2 central cluster is healthy.
|
|
#
|
|
# Prerequisites:
|
|
# - Infrastructure services running (infra/docker-compose up -d)
|
|
# - Env2 application containers running (docker-env2/deploy.sh)
|
|
# - Env2 central cluster healthy (curl http://localhost:9100/health/ready)
|
|
#
|
|
# Usage:
|
|
# docker-env2/seed-sites.sh
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
CLI="dotnet run --project $PROJECT_ROOT/src/ZB.MOM.WW.ScadaBridge.CLI --"
|
|
AUTH="--username multi-role --password password"
|
|
URL="--url http://localhost:9100"
|
|
|
|
echo "=== Seeding ScadaBridge Env2 Sites ==="
|
|
|
|
echo ""
|
|
echo "Creating Site-X (Env2 Site X)..."
|
|
$CLI $URL $AUTH site create \
|
|
--name "Env2 Site X" \
|
|
--identifier "site-x" \
|
|
--description "Env2 test site - two-node cluster" \
|
|
--node-a-address "akka.tcp://scadabridge@scadabridge-env2-site-x-a:8082" \
|
|
--node-b-address "akka.tcp://scadabridge@scadabridge-env2-site-x-b:8082" \
|
|
--grpc-node-a-address "http://scadabridge-env2-site-x-a:8083" \
|
|
--grpc-node-b-address "http://scadabridge-env2-site-x-b:8083" \
|
|
|| echo " (Site-X may already exist)"
|
|
|
|
echo ""
|
|
echo "Creating Engineering Alerts notification list..."
|
|
$CLI $URL $AUTH notification create \
|
|
--name "Engineering Alerts" \
|
|
--emails "engineer@company.com" \
|
|
|| echo " (Engineering Alerts may already exist)"
|
|
|
|
echo ""
|
|
echo "Creating MxGateway data connection (shared gateway) on site-x..."
|
|
# Shared MxGateway data connection pointing at the MxAccess Gateway. Data
|
|
# connections are site-scoped (the DCL runs on site clusters only), so it is
|
|
# created on the env2 site. Config is the typed MxGatewayEndpointConfig JSON
|
|
# (camelCase keys), matching MxGatewayEndpointConfigSerializer.
|
|
MXGW_ENDPOINT="http://10.100.0.48:5120"
|
|
# The windev gateway API key is NOT kept in the repo. Supply it via the
|
|
# MXGW_APIKEY env var, or a private file (default
|
|
# ~/.config/scadabridge/windev-mxgw-apikey, override with MXGW_APIKEY_FILE).
|
|
# Without a key the MxGateway data connection step is skipped.
|
|
MXGW_APIKEY_FILE="${MXGW_APIKEY_FILE:-$HOME/.config/scadabridge/windev-mxgw-apikey}"
|
|
if [ -z "${MXGW_APIKEY:-}" ] && [ -r "$MXGW_APIKEY_FILE" ]; then
|
|
MXGW_APIKEY=$(tr -d '\r\n' < "$MXGW_APIKEY_FILE")
|
|
fi
|
|
MXGW_APIKEY="${MXGW_APIKEY:-}"
|
|
# The config (which carries the key) goes to the CLI through a mode-600 temp file
|
|
# (--primary-config-file), never on the command line where `ps` and shell history
|
|
# would show it (ScadaBridge#137). printf is a bash builtin, so the key is not in
|
|
# any process's argv while the file is written either.
|
|
MXGW_CONFIG_FILE=""
|
|
if [ -n "$MXGW_APIKEY" ]; then
|
|
MXGW_CONFIG_FILE=$(umask 077 && mktemp "${TMPDIR:-/tmp}/sb-mxgw-config.XXXXXX")
|
|
trap 'rm -f "$MXGW_CONFIG_FILE"' EXIT
|
|
printf '{"endpoint":"%s","apiKey":"%s","clientName":"","writeUserId":0,"useTls":false,"caFile":"","serverName":"","readTimeoutMs":5000}' \
|
|
"$MXGW_ENDPOINT" "$MXGW_APIKEY" > "$MXGW_CONFIG_FILE"
|
|
fi
|
|
|
|
SITE_X_ID=$($CLI $URL $AUTH --format json site list \
|
|
| python3 -c "import sys,json; print(next((s['id'] for s in json.load(sys.stdin) if s.get('siteIdentifier')=='site-x'), ''))" 2>/dev/null)
|
|
if [ -z "$MXGW_APIKEY" ]; then
|
|
echo " (no MXGW_APIKEY / $MXGW_APIKEY_FILE — skipping MxGateway connection)"
|
|
elif [ -n "$SITE_X_ID" ]; then
|
|
CONN_NAME="ScadaBridge Site X"
|
|
echo " site-x (id=$SITE_X_ID): creating '$CONN_NAME'..."
|
|
$CLI $URL $AUTH data-connection create \
|
|
--site-id "$SITE_X_ID" \
|
|
--name "$CONN_NAME" \
|
|
--protocol "MxGateway" \
|
|
--primary-config-file "$MXGW_CONFIG_FILE" \
|
|
|| echo " ('$CONN_NAME' may already exist on site-x)"
|
|
else
|
|
echo " (site-x not found — skipping MxGateway connection)"
|
|
fi
|
|
|
|
echo ""
|
|
echo "Seeding LDAP group mappings (Design + Deployment)..."
|
|
# SecurityConfiguration.HasData declares 4 mappings but the InitialSchema
|
|
# migration only inserts the Admin row, so a fresh ScadaBridgeConfig2 starts
|
|
# with multi-role getting Admin only -- no Design and no Deployment access.
|
|
# Insert the missing three idempotently. (Mirror of docker/seed-sites.sh.)
|
|
docker exec -i scadabridge-mssql /opt/mssql-tools18/bin/sqlcmd \
|
|
-S localhost -U sa -P 'ScadaBridge_Dev1#' -C \
|
|
-d ScadaBridgeConfig2 -Q "
|
|
SET IDENTITY_INSERT LdapGroupMappings ON;
|
|
IF NOT EXISTS (SELECT 1 FROM LdapGroupMappings WHERE Id = 2)
|
|
INSERT INTO LdapGroupMappings (Id, LdapGroupName, Role) VALUES (2, 'SCADA-Designers', 'Design');
|
|
IF NOT EXISTS (SELECT 1 FROM LdapGroupMappings WHERE Id = 3)
|
|
INSERT INTO LdapGroupMappings (Id, LdapGroupName, Role) VALUES (3, 'SCADA-Deploy-All', 'Deployment');
|
|
IF NOT EXISTS (SELECT 1 FROM LdapGroupMappings WHERE Id = 4)
|
|
INSERT INTO LdapGroupMappings (Id, LdapGroupName, Role) VALUES (4, 'SCADA-Deploy-SiteA', 'Deployment');
|
|
SET IDENTITY_INSERT LdapGroupMappings OFF;
|
|
"
|
|
|
|
echo ""
|
|
echo "Creating MxAlarm demo template + native alarm source binding..."
|
|
# Mirrors native MxAccess Gateway alarms (read-only) into ScadaBridge. The
|
|
# binding points at the shared 'ScadaBridge Site X' MxGateway connection and a
|
|
# representative MxAccess area/object. Requires the Design role (granted above).
|
|
if [ -n "$SITE_X_ID" ]; then
|
|
CONN_NAME="ScadaBridge Site X"
|
|
TEMPLATE_NAME="MxAlarmDemo"
|
|
# Representative MxAccess area object to mirror alarms from. Adjust to a real
|
|
# Galaxy area present on the gateway.
|
|
NAS_SOURCE_REF="\$Area_001"
|
|
|
|
$CLI $URL $AUTH template create \
|
|
--name "$TEMPLATE_NAME" \
|
|
--description "Demo: mirrors native MxAccess Gateway alarms (read-only)" \
|
|
|| echo " ('$TEMPLATE_NAME' may already exist)"
|
|
|
|
TEMPLATE_ID=$($CLI $URL $AUTH --format json template list \
|
|
| python3 -c "import sys,json; print(next((t['id'] for t in json.load(sys.stdin) if t.get('name')=='$TEMPLATE_NAME'), ''))" 2>/dev/null)
|
|
|
|
if [ -n "$TEMPLATE_ID" ]; then
|
|
echo " template '$TEMPLATE_NAME' (id=$TEMPLATE_ID): adding native alarm source 'GalaxyAlarms'..."
|
|
$CLI $URL $AUTH template native-alarm-source add \
|
|
--template-id "$TEMPLATE_ID" \
|
|
--name "GalaxyAlarms" \
|
|
--connection "$CONN_NAME" \
|
|
--source-ref "$NAS_SOURCE_REF" \
|
|
|| echo " ('GalaxyAlarms' may already exist on the template)"
|
|
|
|
echo " creating instance 'MxAlarmDemo-1' at site-x..."
|
|
$CLI $URL $AUTH instance create \
|
|
--name "MxAlarmDemo-1" \
|
|
--template-id "$TEMPLATE_ID" \
|
|
--site-id "$SITE_X_ID" \
|
|
|| echo " ('MxAlarmDemo-1' may already exist)"
|
|
else
|
|
echo " (template '$TEMPLATE_NAME' not found — skipping native alarm source seed)"
|
|
fi
|
|
else
|
|
echo " (site-x not found — skipping native alarm source demo seed)"
|
|
fi
|
|
|
|
echo ""
|
|
echo "Deploying artifacts to all env2 sites (pushes the data connection so the"
|
|
echo "site establishes it — the MxGateway DataConnectionActor connects eagerly)..."
|
|
$CLI $URL $AUTH deploy artifacts \
|
|
|| echo " (artifact deploy reported an issue — check 'deploy status')"
|
|
|
|
echo ""
|
|
echo "Deploying the MxAlarm demo instance to site-x..."
|
|
INSTANCE_ID=$($CLI $URL $AUTH --format json instance list \
|
|
| python3 -c "import sys,json; print(next((i['id'] for i in json.load(sys.stdin) if i.get('uniqueName')=='MxAlarmDemo-1'), ''))" 2>/dev/null)
|
|
if [ -n "$INSTANCE_ID" ]; then
|
|
$CLI $URL $AUTH instance deploy --id "$INSTANCE_ID" \
|
|
|| echo " (instance deploy reported an issue — check 'deploy status')"
|
|
else
|
|
echo " (MxAlarmDemo-1 not found — skipping instance deploy)"
|
|
fi
|
|
|
|
echo ""
|
|
echo "=== Env2 site seeding complete ==="
|
|
echo ""
|
|
echo "Verify with: $CLI $URL $AUTH site list"
|
|
echo "Verify connections: $CLI $URL $AUTH data-connection list"
|
|
echo "Verify MxGateway is live: docker logs scadabridge-env2-site-x-a 2>&1 | grep -i mxgateway"
|
|
echo "Multi-role test user now has Admin + Design + Deployment in env2."
|
|
echo "Sign out and back in to refresh session role claims."
|