#!/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."