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
docker/seed-sites.sh and docker-env2/seed-sites.sh hard-coded the windev
gateway API key. They now read MXGW_APIKEY from the environment, or from
a private file (default ~/.config/scadabridge/windev-mxgw-apikey,
override MXGW_APIKEY_FILE). With no key the MxGateway connection step is
skipped and the rest of the seed runs. READMEs note where to put it.
The key remains in git history (2 commits), so it should be rotated on
the windev gateway.
Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
Unreplicated, the site-x survivor came up empty (by design: no LocalDb peer);
replicated, kill and freeze each lost only the 26-30 s takeover window, every
buffered message was delivered once, and the 4 duplicate ticks came from the
frozen victim's 6 s zombie window. Central kill: 0 gap.
Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
P8-7: docker/README.md and docker-env2/README.md still listed the retired
scadabridge-ldap container. Verified against the running config
(central-node-*/appsettings.Central.json): both rigs actually bind to the
local redundant GLAuth pair (zb-ldap-primary :3893 + zb-ldap-backup :3894 on
host.docker.internal, source of truth scadaproj/infra/glauth-redundant), not
the shared 10.100.0.35 GLAuth the infra/docker-compose.yml retirement comment
claimed — that binding is itself stale, its serviceaccount password having
been rotated away by SEC-36. Corrected all three docs to name the actual
config each rig uses.
P8-10: docker-env2 mapped no host port for the central gRPC control plane
(docker/ maps 9013/9014->8083); added 9113/9114->8083 on both env2 centrals,
matching the free 91XX slots, plus the README port table. teardown.sh's
"preserved in node directories" data note predates the named-volume
migration; rewrote it honestly and added an explicit `docker volume rm`
recipe scoped to the env2 volume prefix, clearly labeled destructive,
mirroring docker/teardown.sh's existing pattern.
Gate: bash docker-env2/deploy.sh from this worktree — both centrals + both
site-x nodes recreated and came up with the new 9113/9114 ports open. The
simultaneous force-recreate split both pairs into independent 1-node
clusters (expected: BootstrapGuard is default-OFF on docker-env2, per
CLAUDE.md); a single restart of the b-side of each pair joined it to its
already-formed peer and both pairs converged (central: memberCount=2,
leader=central-a; site-x: node-b Welcome + singleton Younger). docker/ (the
primary rig) was not touched.