deployment.md / CLAUDE.md / env_vars.md: the per-app LDAP (scadabridge-ldap container, OtOpcUa DevStubMode, per-box C:\publish\glauth) is replaced by one shared zb-shared-glauth on 10.100.0.35:3893 (dc=zb,dc=local); source of truth infra/glauth/. Fixed stale baseDNs (dc=lmxopcua/dc=otopcua -> dc=zb).
20 KiB
Environment Variables — SCADA/OT family
Cross-project audit of every environment variable used or read by the sister projects and the shared
ZB.MOM.WW.*libraries. Compiled 2026-06-03 by sweeping C# reads, Docker/compose,launchSettings.json, shell/PowerShell scripts, and CI for each repo. This is a summary index — when a value matters operationally, confirm against the citedfile:linein the owning repo (paths below are relative to each project root).
Scope
| Project | Root | Covered |
|---|---|---|
| OtOpcUa | ~/Desktop/OtOpcUa |
Host, Galaxy/Historian drivers, docker-dev, tests, CI |
| MxAccessGateway | ~/Desktop/MxAccessGateway |
Server (x64), Worker (x86), client CLI, tests, pack script |
| ScadaBridge | ~/Desktop/ScadaBridge |
Host (Central/Site), CLI, docker/, docker-env2/, infra/, tests |
| Shared libs | ~/Desktop/scadaproj/ZB.MOM.WW.* |
Auth, Theme, Health, Telemetry, Configuration, Audit (code + build scripts) |
How env vars reach these apps
All four .NET apps call AddEnvironmentVariables(), so any configuration key is overridable
from the environment using the double-underscore (__) → colon (:) convention
(ScadaBridge__InboundApi__ApiKeyPepper overrides ScadaBridge:InboundApi:ApiKeyPepper). Array
indices use a trailing __0, __1 (Cluster__SeedNodes__0). Because every options key is
technically settable this way, the tables below split into:
- Direct reads / operationally-set —
Environment.GetEnvironmentVariable(...)in code, or values actually set in compose/launchSettings/scripts. These are the ones you'll really touch. - Config keys overridable via
__— the validated/notable options that are normally inappsettings*.jsonbut are commonly (or required to be) supplied via environment in containers. Not every options key is reproduced — only validated, secret, or container-set ones.
Secrets: rows marked 🔒 are secrets. Per the Auth/Config normalization, peppers/keys/passwords are per-environment secrets injected out-of-band (secret store / orchestrator), never committed. The dev-only values that do appear in compose are explicitly insecure placeholders for the local clusters — see
scadabridge-local-deploy-gotchasanddocs/operations/inbound-api-key-reissue.md.
1. OtOpcUa
1.1 Direct reads / operationally-set (runtime)
| Variable | Where | Purpose | Req? / default | Process |
|---|---|---|---|---|
OTOPCUA_ROLES |
src/Server/.../Host/Program.cs:31 |
Comma-list of roles (admin, driver) for conditional wiring | optional | Host |
ASPNETCORE_ENVIRONMENT |
Host/Properties/launchSettings.json:9 |
ASP.NET Core environment | optional / Production |
Host |
ASPNETCORE_URLS |
docker-dev/docker-compose.yml |
Kestrel bind address/port | optional | Host |
GALAXY_MXGW_API_KEY 🔒 |
docker-dev/docker-compose.yml; resolved in GalaxyDriver.cs:466 |
mxaccessgw API key for the Galaxy driver | required if Galaxy driver deployed | Driver (Galaxy) |
OTOPCUA_CONFIG_CONNECTION 🔒 |
Configuration/DesignTimeDbContextFactory.cs:25 |
SQL connection for EF design-time (dotnet ef) |
required for migrations tooling | build-time |
OTOPCUA_HISTORIAN_PIPE |
Driver.Historian.Wonderware/Program.cs:32 |
Named-pipe name for the Historian sidecar IPC | required | Historian sidecar |
OTOPCUA_ALLOWED_SID |
…/Program.cs:34 |
Windows SID allowed to connect to the sidecar | required | Historian sidecar |
OTOPCUA_HISTORIAN_SECRET 🔒 |
…/Program.cs:36 |
Shared secret for named-pipe auth | required | Historian sidecar |
OTOPCUA_HISTORIAN_ENABLED |
…/Program.cs:48 |
Init the Historian SDK (else pipe-only) | optional / false |
Historian sidecar |
OTOPCUA_HISTORIAN_SERVER |
…/Program.cs:89 |
Wonderware Historian host | optional / localhost |
Historian sidecar |
OTOPCUA_HISTORIAN_PORT |
…/Program.cs:90 |
Historian port | optional / 32568 |
Historian sidecar |
OTOPCUA_HISTORIAN_INTEGRATED |
…/Program.cs:91 |
Use Windows Integrated Security | optional / true |
Historian sidecar |
OTOPCUA_HISTORIAN_USER |
…/Program.cs:92 |
SQL user (when not integrated) | optional | Historian sidecar |
OTOPCUA_HISTORIAN_PASS 🔒 |
…/Program.cs:93 |
SQL password (when not integrated) | optional | Historian sidecar |
OTOPCUA_HISTORIAN_TIMEOUT_SEC |
…/Program.cs:94 |
SQL command timeout (s) | optional / 30 |
Historian sidecar |
OTOPCUA_HISTORIAN_MAX_VALUES |
…/Program.cs:95 |
Max values per read query | optional / 10000 |
Historian sidecar |
OTOPCUA_HISTORIAN_COOLDOWN_SEC |
…/Program.cs:96 |
Failure cooldown before retry (s) | optional / 60 |
Historian sidecar |
OTOPCUA_HISTORIAN_SERVERS |
…/Program.cs:99 |
Comma-list of historian servers for failover | optional | Historian sidecar |
OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED |
…/Program.cs:125 |
Enable alarm-event writer to historian | optional / true when enabled |
Historian sidecar |
The Galaxy API key supports prefix resolution (
GalaxyDriver.cs:466):env:VAR(read another env var),file:PATH(read a file),dev:LITERAL(dev only), or an unprefixed literal.
1.2 Config keys overridable via __ (set in docker-dev/docker-compose.yml)
| Variable | Purpose | Req? |
|---|---|---|
ConnectionStrings__ConfigDb 🔒 |
SQL connection for the OtOpcUa config DB | required |
Cluster__Hostname / Cluster__Port / Cluster__PublicHostname |
Akka remoting bind/advertise | required |
Cluster__SeedNodes__0 |
Initial seed node URL | required |
Cluster__Roles__0 (__1) |
Cluster role assignment (admin/driver) | required |
Security__Jwt__SigningKey 🔒 |
JWT signing key (≥32 bytes) | required |
Security__Jwt__Issuer / Security__Jwt__Audience |
JWT claims | required |
Security__Ldap__* |
LDAP host/bind (in appsettings.admin*.json) |
per-deploy |
Authentication__Ldap__DevStubMode |
Dev LDAP stub (any user → FleetAdmin) — removed from docker-dev; docker-dev now binds the shared GLAuth on 10.100.0.35:3893 |
optional / false |
1.3 Docker infra & test fixtures (OtOpcUa)
- docker-dev SQL:
ACCEPT_EULA=Y,SA_PASSWORD🔒 (OtOpcUa!Dev123dev-only),MSSQL_PID=Developer. - docker-dev seed (
seed/entrypoint.sh):SQL_HOST/SQL_USER/SQL_PASSWORD🔒 /SQL_DATABASE(defaultssql/sa/OtOpcUa!Dev123/OtOpcUa). - Integration compose: SQL (
SA_PASSWORD🔒OtOpcUa!Harness123) + OpenLDAP (LDAP_ROOT,LDAP_ADMIN_USERNAME,LDAP_ADMIN_PASSWORD🔒,LDAP_USERS,LDAP_PASSWORDS🔒,LDAP_USER_DC) — this OpenLDAP instance is integration-test-only; the standard DEV auth is the shared GLAuth at10.100.0.35:3893(dc=zb,dc=local, seescadaproj/infra/glauth/). - Test-fixture overrides (all optional, default to the shared host
10.100.0.35):OPCUA_SIM_ENDPOINT,MODBUS_SIM_ENDPOINT,MODBUS_SIM_PROFILE,AB_SERVER_ENDPOINT,AB_SERVER_PROFILE,S7_SIM_ENDPOINT,OTOPCUA_FOCAS_SIM_ENDPOINT,OTOPCUA_FOCAS_SIM_PROFILE,TWINCAT_HOST,TWINCAT_NETID,TWINCAT_PORT,AB_LEGACY_ENDPOINT,AB_LEGACY_CIP_PATH,AB_LEGACY_COMPOSE_PROFILE,OTOPCUA_CONFIG_TEST_SERVER,OTOPCUA_CONFIG_TEST_SA_PASSWORD🔒,OTOPCUA_HARNESS_USE_SQL,OTOPCUA_HARNESS_USE_LDAP,MXGW_ENDPOINT,D1_SMOKE_OUT. - CI (
.github/workflows/v2-*.yml):DOTNET_NOLOGO=1,DOTNET_CLI_TELEMETRY_OPTOUT=1.
2. MxAccessGateway
Ships no Docker/compose assets — it's a Windows-native app (x64 .NET 10 Server + x86 .NET 4.8 Worker). The Server spawns the Worker via
ProcessStartInfo.Environment, passing the twoMXGATEWAY_WORKER_*vars below.
2.1 Direct reads / operationally-set (runtime)
| Variable | Where | Purpose | Req? / default | Process |
|---|---|---|---|---|
MXGATEWAY_WORKER_NONCE 🔒 |
Server WorkerProcessLauncher.cs:180 → Worker WorkerOptionsParser.cs:78 |
Per-session handshake nonce (kept off the command line) | required (generated per session) | Server→Worker |
MXGATEWAY_WORKER_PIPE_CONNECT_ATTEMPT_TIMEOUT_MS |
Server WorkerProcessLauncher.cs:181 → Worker WorkerPipeClient.cs:255 |
Per-attempt named-pipe connect timeout | optional / 2000 |
Server→Worker |
ASPNETCORE_CONTENTROOT |
GatewayApplication.cs:130 |
Content-root override (logs/wwwroot) | optional / auto | Server |
ASPNETCORE_ENVIRONMENT |
Server/Properties/launchSettings.json:10 |
Environment selector | optional / Production |
Server |
2.2 Config keys overridable via __ (notable)
| Variable | Purpose | Req? |
|---|---|---|
MxGateway__ApiKeyPepper 🔒 |
HMAC pepper for API-key secrets in the SQLite auth DB | required when auth Mode=ApiKey |
MxGateway__Authentication__Mode / __SqlitePath / __PepperSecretName |
Auth mode, auth DB path, pepper config-key name | per-deploy |
MxGateway__Worker__ExecutablePath / __WorkingDirectory / __StartupTimeoutSeconds / __PipeConnectAttemptTimeoutMilliseconds |
x86 worker launch config | per-deploy |
MxGateway__Sessions__* / MxGateway__Events__QueueCapacity |
Session pool & event queue tuning | optional |
MxGateway__Galaxy__ConnectionString 🔒 |
SQL connection for Galaxy browse RPCs | per-deploy |
MxGateway__Alarms__* / MxGateway__Dashboard__* |
Alarm monitor & dashboard config | optional |
MxGateway__Telemetry__Exporter / __OtlpEndpoint |
OpenTelemetry exporter selection / OTLP endpoint | optional |
MxGateway__Tls__SelfSignedCertPath |
Self-signed PFX path | optional |
Kestrel__Endpoints__Http__Url / __Protocols, Kestrel__Endpoints__Dashboard__Url |
gRPC (h2c) + dashboard endpoints | per-deploy |
2.3 Client CLI, tests & build script (MxGateway)
- Client CLI / smoke tests:
MXGATEWAY_ENDPOINT(defaulthttp://localhost:5000),MXGATEWAY_API_KEY🔒 (MxGatewayClientCli.cs:289). - Live-test opt-in gates (set to
1to enable; otherwise skipped):MXGATEWAY_RUN_LIVE_MXACCESS_TESTS,MXGATEWAY_RUN_LIVE_LDAP_TESTS. - Live-test params (optional):
MXGATEWAY_LIVE_MXACCESS_WORKER_EXE,_ITEM,_CLIENT_NAME,_EVENT_TIMEOUT_SECONDS,_WRITE_SECURED_USER,_WRITE_SECURED_PASSWORD🔒,MXGATEWAY_LIVE_GALAXY_CONN🔒. - Pack/publish (
scripts/pack-clients.ps1):GITEA_USERNAME,GITEA_TOKEN🔒 (required with-Publish),JAVA_HOME(Java client build).
3. ScadaBridge
Role is selected by
SCADABRIDGE_CONFIG(Central|Site), which picksappsettings.{role}.json(falls back toDOTNET_ENVIRONMENT, thenProduction). The pre-hostStartupValidator/ConfigPreflightenforces the required keys below and fails fast if any are missing/invalid.
3.1 Direct reads (C#)
| Variable | Where | Purpose | Req? / default | Scope |
|---|---|---|---|---|
SCADABRIDGE_CONFIG |
Host/Program.cs:31 |
Role selector → appsettings file | optional (→ DOTNET_ENVIRONMENT → Production) |
both |
DOTNET_ENVIRONMENT |
Host/Program.cs:32 |
Fallback role/env selector | optional | both |
ASPNETCORE_ENVIRONMENT |
Host/Program.cs:245 |
Dev-mode check | optional | both |
SCADABRIDGE_DESIGNTIME_CONNECTIONSTRING 🔒 |
ConfigurationDatabase/DesignTimeDbContextFactory.cs:48 |
EF tooling connection (dotnet ef) |
optional (build-time) | design-time |
SCADABRIDGE_MANAGEMENT_URL |
CLI/CliConfig.cs:72 |
Management API URL for the CLI | optional | CLI |
SCADABRIDGE_FORMAT |
CLI/CliConfig.cs:76 |
CLI default output format | optional / json |
CLI |
SCADABRIDGE_USERNAME |
CLI/CliConfig.cs:81 |
CLI LDAP username (safer than --password) |
optional | CLI |
SCADABRIDGE_PASSWORD 🔒 |
CLI/CliConfig.cs:85 |
CLI LDAP password | optional | CLI |
3.2 Config keys required at startup (__ form, enforced by StartupValidator)
| Variable | Purpose | Constraint | Scope |
|---|---|---|---|
ScadaBridge__Node__Role |
Central or Site |
required | all |
ScadaBridge__Node__NodeHostname |
Hostname advertised to cluster | required, non-empty | all |
ScadaBridge__Node__RemotingPort |
Akka remoting TCP port | required, 1–65535 (default 8081) | all |
ScadaBridge__Cluster__SeedNodes__0 / __1 |
Akka seed addresses | required, ≥2 entries | all |
ScadaBridge__Database__ConfigurationDb 🔒 |
SQL config-DB connection | required | Central |
ScadaBridge__Security__JwtSigningKey 🔒 |
Cookie-JWT HMAC key | required, ≥32 chars | Central |
ScadaBridge__Security__Ldap__Server |
LDAP host | required | Central |
ScadaBridge__InboundApi__ApiKeyPepper 🔒 |
Peppered-HMAC inbound API-key pepper | required, ≥16 chars | Central |
ScadaBridge__Node__SiteId |
Site identifier | required | Site |
ScadaBridge__Database__SiteDbPath |
Site-local SQLite path | required | Site |
ScadaBridge__Node__GrpcPort |
gRPC streaming port | default 8083; ≠ remoting/metrics | Site |
ScadaBridge__Node__MetricsPort |
Prometheus /metrics port |
default 8084; ≠ remoting/grpc | Site |
LDAP service-account fields (
ScadaBridge__Security__Ldap__ServiceAccountDn,__ServiceAccountPassword🔒,__SearchBase) are validated post-host byLdapOptionsValidatorfor Central, not in the fail-fast pre-host pass.
3.3 Config keys overridable via __ (optional, not validated)
Large surface — all bound from the role appsettings and overridable via env. Grouped by module
(each prefixed ScadaBridge__):
- Node:
Node__NodeName. Database:Database__MachineDataDb🔒,Database__SkipMigrations. - Security:
Security__Ldap__Port|Transport|AllowInsecure,Security__JwtExpiryMinutes,Security__IdleTimeoutMinutes,Security__JwtRefreshThresholdMinutes,Security__RequireHttpsCookie. - Cluster (SBR):
Cluster__SplitBrainResolverStrategy|StableAfter|HeartbeatInterval|FailureDetectionThreshold|MinNrOfMembers|DownIfAlone. - Communication:
Communication__DeploymentTimeout|LifecycleTimeout|QueryTimeout|TransportHeartbeatInterval|TransportFailureThreshold,Communication__CentralContactPoints__0…(Site→Central ClusterClient). - HealthMonitoring:
HealthMonitoring__ReportInterval|OfflineTimeout|CentralOfflineTimeout. - InboundApi:
InboundApi__DefaultMethodTimeout|MaxRequestBodyBytes. - Notification (SMTP):
Notification__SmtpServer|SmtpPort|AuthMode|FromAddress|ConnectionTimeoutSeconds|MaxConcurrentConnections. - NotificationOutbox (Central):
NotificationOutbox__DispatchInterval|DispatchBatchSize|StuckAgeThreshold|TerminalRetention|PurgeInterval|DeliveredKpiWindow. - Transport (Central):
Transport__SourceEnvironment|BundleSessionTtlMinutes|MaxBundleSizeMb|MaxBundleEntryDecompressedMb|MaxBundleEntryCount|MaxBundleEntryCompressionRatio|MaxUnlockAttemptsPerSession|MaxUnlockAttemptsPerIpPerHour|Pbkdf2Iterations. - Logging:
Logging__MinimumLevel. - DataConnection (Site):
DataConnection__ReconnectInterval|TagResolutionRetryInterval|WriteTimeout|StableConnectionThreshold. - StoreAndForward (Site):
StoreAndForward__SqliteDbPath|ReplicationEnabled|DefaultRetryInterval|DefaultMaxRetries. - SiteEventLog (Site):
SiteEventLog__RetentionDays|MaxStorageMb|DatabasePath|PurgeInterval. - SiteRuntime (Site):
SiteRuntime__StartupBatchSize|StartupBatchDelayMs|MaxScriptCallDepth|ScriptExecutionTimeoutSeconds|StreamBufferSize|ScriptExecutionThreadCount. - Telemetry (Site):
Telemetry__Exporter|OtlpEndpoint.
3.4 Docker / infra / build (ScadaBridge)
docker/docker-compose.yml(3-site cluster) — Central:SCADABRIDGE_CONFIG=Central,ASPNETCORE_ENVIRONMENT=Development,ASPNETCORE_URLS=http://+:5000,ScadaBridge__InboundApi__ApiKeyPepper🔒 =dev-only-insecure-pepper-docker-cluster-0001; Sites:SCADABRIDGE_CONFIG=Site.docker-env2/docker-compose.yml(1-site cluster) — same shape, pepper 🔒 =dev-only-insecure-pepper-env2-cluster-0001(distinct per environment).infra/docker-compose.yml— MSSQL (ACCEPT_EULA=Y,MSSQL_SA_PASSWORD🔒ScadaBridge_Dev1#,MSSQL_PID=Developer), Mailpit SMTP (MP_SMTP_AUTH_ACCEPT_ANY=1,MP_SMTP_AUTH_ALLOW_INSECURE=1,MP_MAX_MESSAGES=500), REST API (API_NO_AUTH=0,PORT=5200).docker/Dockerfilebuild args:NUGET_GITEA_USER/NUGET_GITEA_PASS🔒 → injected asNuGetPackageSourceCredentials_dohertj2-gitea.docker/build.shreadsMXGW_NUGET_USER/MXGW_NUGET_PASS🔒 from host env (blank ⇒ anonymous feed).- Seed/init scripts (
docker*/init-db.sh,seed-sites.sh) hardcode the dev SA password 🔒ScadaBridge_Dev1#. launchSettings.jsonprofiles setDOTNET_ENVIRONMENT,ASPNETCORE_ENVIRONMENT,SCADABRIDGE_CONFIG(Central/Site).
4. Shared libraries (ZB.MOM.WW.*)
The libraries deliberately read almost nothing from the environment directly — config flows through strongly-typed options bound by the consuming app. Notable exceptions:
| Variable | Where | Purpose | Req? / default |
|---|---|---|---|
GITEA_NUGET_SOURCE |
ZB.MOM.WW.{Auth,Theme,Audit}/build/push.sh:16 |
Gitea NuGet feed URL for dotnet nuget push |
required to publish |
GITEA_NUGET_KEY 🔒 |
…/build/push.sh:17 |
Gitea token (package:write) |
required to publish |
ZB_LDAP_IT |
ZB.MOM.WW.Auth/tests/.../GLAuthIntegrationTests.cs:48 |
Gate flag (1) to run the live LDAP test |
optional (skipped if unset) |
ZB_LDAP_SERVER / _PORT / _BASE / _SVC_DN / _SVC_PW 🔒 / _USER / _PW 🔒 / _USERATTR |
…/GLAuthIntegrationTests.cs:52-59 |
Live LDAP test connection params | optional (defaults: localhost/3893/dc=zb,dc=local/…); point at the shared GLAuth (10.100.0.35:3893, dc=zb,dc=local) for the live test |
Telemetry: ZB.MOM.WW.Telemetry does not read standard OTEL_* env vars — OTel identity/exporter
come from ZbTelemetryOptions passed to AddZbTelemetry(). It only reads system properties
(Environment.MachineName, Environment.ProcessId in ZbResource.cs:20) to form
service.instance.id / host.name. Consuming apps that want OTLP wire it via their own
…Telemetry__OtlpEndpoint config key (see MxGateway §2.2, ScadaBridge §3.3).
Health, Configuration, Audit, Theme: no direct environment-variable reads (code or build).
5. Cross-cutting cheat sheet
Standard framework vars (honored everywhere)
ASPNETCORE_ENVIRONMENT, ASPNETCORE_URLS, ASPNETCORE_CONTENTROOT, DOTNET_ENVIRONMENT,
DOTNET_NOLOGO, DOTNET_CLI_TELEMETRY_OPTOUT.
Build/publish (Gitea NuGet feed) — naming differs per repo
| Repo | Vars |
|---|---|
| Shared libs | GITEA_NUGET_SOURCE, GITEA_NUGET_KEY 🔒 |
| MxGateway pack | GITEA_USERNAME, GITEA_TOKEN 🔒 |
| ScadaBridge image build | MXGW_NUGET_USER, MXGW_NUGET_PASS 🔒 (→ NUGET_GITEA_USER/NUGET_GITEA_PASS build args) |
Secrets inventory (🔒) — inject out-of-band, never commit real values
- Peppers:
ScadaBridge__InboundApi__ApiKeyPepper(≥16, Central-only),MxGateway__ApiKeyPepper. - Signing/JWT keys:
Security__Jwt__SigningKey(OtOpcUa),ScadaBridge__Security__JwtSigningKey. - API keys / nonces:
GALAXY_MXGW_API_KEY,MXGATEWAY_API_KEY,MXGATEWAY_WORKER_NONCE,OTOPCUA_HISTORIAN_SECRET. - DB / LDAP / SMTP passwords: all
*SA_PASSWORD/MSSQL_SA_PASSWORD,ConnectionStrings__ConfigDb,ScadaBridge__Database__ConfigurationDb,*Ldap*Password,SCADABRIDGE_PASSWORD,OTOPCUA_HISTORIAN_PASS. - Feed tokens:
GITEA_NUGET_KEY,GITEA_TOKEN,MXGW_NUGET_PASS/NUGET_GITEA_PASS.
The only secret-typed values that legitimately appear in source are the dev-only, insecure local-cluster placeholders in
docker*/docker-compose.ymland the dev SA passwords in theinfra/seed scripts — usable for the local stacks only, never as real secrets.