diff --git a/docker-dev/README.md b/docker-dev/README.md
index a7eede8..be7ade1 100644
--- a/docker-dev/README.md
+++ b/docker-dev/README.md
@@ -60,6 +60,12 @@ The SQL lives at `seed/seed-clusters.sql`; the wait-and-apply wrapper lives at `
docker compose -f docker-dev/docker-compose.yml run --rm cluster-seed
```
+### Galaxy / MxAccess gateway
+
+The seed also pre-creates a `SystemPlatform` Namespace + a `GalaxyMxGateway` DriverInstance in the MAIN cluster pointing at `http://10.100.0.48:5120`. The API key is resolved from the `GALAXY_MXGW_API_KEY` env var set on every driver-role container in compose; override via `GALAXY_MXGW_API_KEY=... docker compose up -d` to swap keys without editing the compose file.
+
+The DriverHost actor doesn't spawn drivers from raw DriverInstance rows on its own — the v2 deploy lifecycle requires a *sealed Deployment* before drivers materialise. After first bring-up, sign in to the Admin UI and click **Deploy current configuration** on `/deployments` to compose the seeded rows into an artifact and dispatch it. The Galaxy driver instance will start its gRPC connection to the gateway on the next deploy ack.
+
## Bring up
```bash
diff --git a/docker-dev/docker-compose.yml b/docker-dev/docker-compose.yml
index f091e02..2091ce7 100644
--- a/docker-dev/docker-compose.yml
+++ b/docker-dev/docker-compose.yml
@@ -98,6 +98,7 @@ services:
Security__Jwt__Issuer: "otopcua-dev"
Security__Jwt__Audience: "otopcua-dev"
Authentication__Ldap__DevStubMode: "true"
+ GALAXY_MXGW_API_KEY: "${GALAXY_MXGW_API_KEY:-mxgw_otopcua__UY_NKlBl3vWuZt8HD7usfZsU76eibMKB6CufwzabUI}"
admin-b:
<<: *otopcua-host
@@ -114,6 +115,7 @@ services:
Security__Jwt__Issuer: "otopcua-dev"
Security__Jwt__Audience: "otopcua-dev"
Authentication__Ldap__DevStubMode: "true"
+ GALAXY_MXGW_API_KEY: "${GALAXY_MXGW_API_KEY:-mxgw_otopcua__UY_NKlBl3vWuZt8HD7usfZsU76eibMKB6CufwzabUI}"
driver-a:
<<: *otopcua-host
@@ -125,6 +127,9 @@ services:
Cluster__PublicHostname: "driver-a"
Cluster__SeedNodes__0: "akka.tcp://otopcua@admin-a:4053"
Cluster__Roles__0: "driver"
+ # Resolved at runtime by GalaxyDriver.ResolveApiKey when a DriverInstance's
+ # Gateway.ApiKeySecretRef = "env:GALAXY_MXGW_API_KEY".
+ GALAXY_MXGW_API_KEY: "${GALAXY_MXGW_API_KEY:-mxgw_otopcua__UY_NKlBl3vWuZt8HD7usfZsU76eibMKB6CufwzabUI}"
ports:
- "4840:4840"
@@ -138,6 +143,7 @@ services:
Cluster__PublicHostname: "driver-b"
Cluster__SeedNodes__0: "akka.tcp://otopcua@admin-a:4053"
Cluster__Roles__0: "driver"
+ GALAXY_MXGW_API_KEY: "${GALAXY_MXGW_API_KEY:-mxgw_otopcua__UY_NKlBl3vWuZt8HD7usfZsU76eibMKB6CufwzabUI}"
ports:
- "4841:4840"
@@ -162,6 +168,7 @@ services:
Security__Jwt__Issuer: "otopcua-dev"
Security__Jwt__Audience: "otopcua-dev"
Authentication__Ldap__DevStubMode: "true"
+ GALAXY_MXGW_API_KEY: "${GALAXY_MXGW_API_KEY:-mxgw_otopcua__UY_NKlBl3vWuZt8HD7usfZsU76eibMKB6CufwzabUI}"
ports:
- "4842:4840"
@@ -184,6 +191,7 @@ services:
Security__Jwt__Issuer: "otopcua-dev"
Security__Jwt__Audience: "otopcua-dev"
Authentication__Ldap__DevStubMode: "true"
+ GALAXY_MXGW_API_KEY: "${GALAXY_MXGW_API_KEY:-mxgw_otopcua__UY_NKlBl3vWuZt8HD7usfZsU76eibMKB6CufwzabUI}"
ports:
- "4843:4840"
@@ -205,6 +213,7 @@ services:
Security__Jwt__Issuer: "otopcua-dev"
Security__Jwt__Audience: "otopcua-dev"
Authentication__Ldap__DevStubMode: "true"
+ GALAXY_MXGW_API_KEY: "${GALAXY_MXGW_API_KEY:-mxgw_otopcua__UY_NKlBl3vWuZt8HD7usfZsU76eibMKB6CufwzabUI}"
ports:
- "4844:4840"
@@ -227,6 +236,7 @@ services:
Security__Jwt__Issuer: "otopcua-dev"
Security__Jwt__Audience: "otopcua-dev"
Authentication__Ldap__DevStubMode: "true"
+ GALAXY_MXGW_API_KEY: "${GALAXY_MXGW_API_KEY:-mxgw_otopcua__UY_NKlBl3vWuZt8HD7usfZsU76eibMKB6CufwzabUI}"
ports:
- "4845:4840"
diff --git a/docker-dev/seed/seed-clusters.sql b/docker-dev/seed/seed-clusters.sql
index ff74996..0b7a1e5 100644
--- a/docker-dev/seed/seed-clusters.sql
+++ b/docker-dev/seed/seed-clusters.sql
@@ -95,6 +95,56 @@ IF NOT EXISTS (SELECT 1 FROM dbo.ClusterNode WHERE NodeId = 'site-b-2')
(NodeId, ClusterId, Host, OpcUaPort, DashboardPort, ApplicationUri, ServiceLevelBase, Enabled, CreatedBy)
VALUES ('site-b-2', 'SITE-B', 'site-b-2', 4840, 8081, 'urn:OtOpcUa:site-b-2', 150, 1, 'docker-dev-seed');
+------------------------------------------------------------------------------
+-- Galaxy MxAccess gateway — MAIN cluster
+--
+-- Namespace.Kind=SystemPlatform is required for Galaxy/MXAccess data per
+-- decision #107; raw equipment drivers use Equipment. DriverInstance points
+-- at the external mxaccessgw process. The driver code lives in this repo
+-- (.NET 10, cross-platform); only the gateway worker needs Windows.
+--
+-- ApiKeySecretRef = env:GALAXY_MXGW_API_KEY → resolved at runtime by
+-- GalaxyDriver.ResolveApiKey. The env var is set on every driver-role
+-- container in docker-compose.yml.
+------------------------------------------------------------------------------
+
+IF NOT EXISTS (SELECT 1 FROM dbo.Namespace WHERE NamespaceId = 'MAIN-galaxy')
+ INSERT INTO dbo.Namespace
+ (NamespaceRowId, NamespaceId, ClusterId, Kind, NamespaceUri, Enabled, Notes)
+ VALUES
+ (NEWID(), 'MAIN-galaxy', 'MAIN', 'SystemPlatform',
+ 'urn:zb:docker-dev:galaxy', 1,
+ 'docker-dev seed — Galaxy / MXAccess namespace served by the MAIN cluster.');
+
+IF NOT EXISTS (SELECT 1 FROM dbo.DriverInstance WHERE DriverInstanceId = 'MAIN-galaxy-mxgw')
+ INSERT INTO dbo.DriverInstance
+ (DriverInstanceRowId, DriverInstanceId, ClusterId, NamespaceId, Name, DriverType, Enabled, DriverConfig)
+ VALUES
+ (NEWID(), 'MAIN-galaxy-mxgw', 'MAIN', 'MAIN-galaxy',
+ 'MxAccess gateway (10.100.0.48:5120)', 'GalaxyMxGateway', 1,
+ N'{
+ "Gateway": {
+ "Endpoint": "http://10.100.0.48:5120",
+ "ApiKeySecretRef": "env:GALAXY_MXGW_API_KEY",
+ "UseTls": false,
+ "ConnectTimeoutSeconds": 10,
+ "DefaultCallTimeoutSeconds": 30
+ },
+ "MxAccess": {
+ "ClientName": "OtOpcUa-MAIN-docker-dev",
+ "PublishingIntervalMs": 1000
+ },
+ "Repository": {
+ "DiscoverPageSize": 5000,
+ "WatchDeployEvents": true
+ },
+ "Reconnect": {
+ "InitialBackoffMs": 500,
+ "MaxBackoffMs": 30000,
+ "ReplayOnSessionLost": true
+ }
+}');
+
COMMIT TRANSACTION;
------------------------------------------------------------------------------
@@ -104,3 +154,6 @@ COMMIT TRANSACTION;
SELECT ClusterId, Name, NodeCount, RedundancyMode FROM dbo.ServerCluster ORDER BY ClusterId;
SELECT NodeId, ClusterId, Host, OpcUaPort, ApplicationUri, ServiceLevelBase
FROM dbo.ClusterNode ORDER BY ClusterId, NodeId;
+SELECT NamespaceId, ClusterId, Kind, NamespaceUri FROM dbo.Namespace ORDER BY ClusterId, NamespaceId;
+SELECT DriverInstanceId, ClusterId, DriverType, NamespaceId, Name
+ FROM dbo.DriverInstance ORDER BY ClusterId, DriverInstanceId;
diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverInstanceActor.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverInstanceActor.cs
index f8dbcd4..63b5c39 100644
--- a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverInstanceActor.cs
+++ b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverInstanceActor.cs
@@ -66,9 +66,15 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
///
/// Returns true when the driver should boot in DEV-STUB mode based on host platform and
- /// configured roles. Mirrors plan §Task 55: Windows-only driver types (Galaxy, Wonderware
- /// Historian) are stubbed when running on non-Windows OR when the host carries the
- /// dev role.
+ /// configured roles. Only the v1 in-process types stay Windows-only:
+ ///
+ /// - "Galaxy" — legacy MXAccess COM proxy (retired in PR 7.2; gated for any
+ /// leftover DriverInstance rows that still reference the old type name).
+ /// - "Historian.Wonderware" — Wonderware Historian sidecar over Windows-only
+ /// named pipes.
+ ///
+ /// The v2 "GalaxyMxGateway" driver talks gRPC to an external mxaccessgw process,
+ /// so it runs on any platform .NET 10 supports — Linux containers included. Not stubbed.
///
public static bool ShouldStub(string driverType, IEnumerable roles)
{