Files
ScadaBridge/docker/seed-sites.sh
T
Joseph Doherty e2f433e849 feat(cli): read secret-bearing option values from a file or stdin (#137)
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
2026-11-13 00:46:17 -05:00

159 lines
7.0 KiB
Bash
Executable File

#!/bin/bash
set -euo pipefail
# Seed the three test sites with Akka and gRPC addresses.
# Run after deploy.sh once the central cluster is healthy.
#
# Prerequisites:
# - Infrastructure services running (infra/docker-compose up -d)
# - Application containers running (docker/deploy.sh)
# - Central cluster healthy (curl http://localhost:9000/health/ready)
#
# Usage:
# docker/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:9000"
echo "=== Seeding ScadaBridge Sites ==="
echo ""
echo "Creating Site-A (Test Plant A)..."
$CLI $URL $AUTH site create \
--name "Test Plant A" \
--identifier "site-a" \
--description "Test site A - two-node cluster" \
--node-a-address "akka.tcp://scadabridge@scadabridge-site-a-a:8082" \
--node-b-address "akka.tcp://scadabridge@scadabridge-site-a-b:8082" \
--grpc-node-a-address "http://scadabridge-site-a-a:8083" \
--grpc-node-b-address "http://scadabridge-site-a-b:8083" \
|| echo " (Site-A may already exist)"
echo ""
echo "Creating Site-B (Test Plant B)..."
$CLI $URL $AUTH site create \
--name "Test Plant B" \
--identifier "site-b" \
--description "Test site B - two-node cluster" \
--node-a-address "akka.tcp://scadabridge@scadabridge-site-b-a:8082" \
--node-b-address "akka.tcp://scadabridge@scadabridge-site-b-b:8082" \
--grpc-node-a-address "http://scadabridge-site-b-a:8083" \
--grpc-node-b-address "http://scadabridge-site-b-b:8083" \
|| echo " (Site-B may already exist)"
echo ""
echo "Creating Site-C (Test Plant C)..."
$CLI $URL $AUTH site create \
--name "Test Plant C" \
--identifier "site-c" \
--description "Test site C - two-node cluster" \
--node-a-address "akka.tcp://scadabridge@scadabridge-site-c-a:8082" \
--node-b-address "akka.tcp://scadabridge@scadabridge-site-c-b:8082" \
--grpc-node-a-address "http://scadabridge-site-c-a:8083" \
--grpc-node-b-address "http://scadabridge-site-c-b:8083" \
|| echo " (Site-C 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 connections (shared gateway) on each site..."
# A shared MxGateway data connection pointing at the MxAccess Gateway. Data
# connections are site-scoped (the DCL runs on site clusters only — central
# nodes do not host data connections), so one is created per site. The same
# gateway endpoint + API key is reused across all sites.
#
# 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
if [ -z "$MXGW_APIKEY" ]; then
echo " (no MXGW_APIKEY / $MXGW_APIKEY_FILE — skipping MxGateway connections)"
else
for ident in site-a site-b site-c; do
SITE_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')=='$ident'), ''))" 2>/dev/null)
if [ -z "$SITE_ID" ]; then
echo " ($ident not found — skipping MxGateway connection)"
continue
fi
# Per-site connection name (e.g. site-a -> "ScadaBridge Site A").
SITE_LETTER=$(printf '%s' "${ident#site-}" | tr '[:lower:]' '[:upper:]')
CONN_NAME="ScadaBridge Site ${SITE_LETTER}"
echo " $ident (id=$SITE_ID): creating '$CONN_NAME'..."
$CLI $URL $AUTH data-connection create \
--site-id "$SITE_ID" \
--name "$CONN_NAME" \
--protocol "MxGateway" \
--primary-config-file "$MXGW_CONFIG_FILE" \
|| echo " ('$CONN_NAME' may already exist on $ident)"
done
fi
echo ""
echo "Seeding LDAP group mappings (Designer + Deployer)..."
# SecurityConfiguration.HasData declares 4 mappings but the InitialSchema
# migration only inserts the Admin row, so a fresh ScadaBridgeConfig starts
# with multi-role getting Admin only -- no Design and no Deployment access.
# Insert the missing three idempotently.
docker exec -i scadabridge-mssql /opt/mssql-tools18/bin/sqlcmd \
-S localhost -U sa -P 'ScadaBridge_Dev1#' -C \
-d ScadaBridgeConfig -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', 'Designer');
IF NOT EXISTS (SELECT 1 FROM LdapGroupMappings WHERE Id = 3)
INSERT INTO LdapGroupMappings (Id, LdapGroupName, Role) VALUES (3, 'SCADA-Deploy-All', 'Deployer');
IF NOT EXISTS (SELECT 1 FROM LdapGroupMappings WHERE Id = 4)
INSERT INTO LdapGroupMappings (Id, LdapGroupName, Role) VALUES (4, 'SCADA-Deploy-SiteA', 'Deployer');
-- Role strings MUST match the canonical vocabulary in
-- src/ZB.MOM.WW.ScadaBridge.Security/Roles.cs ('Designer' / 'Deployer').
-- These rows previously carried the pre-rename 'Design' / 'Deployment', which
-- authorized nothing: every Designer/Deployer-gated management command failed
-- UNAUTHORIZED on a freshly reseeded rig.
SET IDENTITY_INSERT LdapGroupMappings OFF;
"
echo ""
echo "Deploying artifacts to all sites (pushes data connections so the sites"
echo "establish them — the MxGateway DataConnectionActor connects eagerly)..."
$CLI $URL $AUTH deploy artifacts \
|| echo " (artifact deploy reported an issue — check 'deploy status')"
echo ""
echo "=== 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 (per site container):"
echo " docker logs scadabridge-site-a-a 2>&1 | grep -i mxgateway"
echo "Multi-role test user has Admin + Design + Deployment."
echo "Sign out and back in to refresh session role claims."