Skip to main content
Version: 0.1

Tools

Everything that runs on the command line rather than in the dashboard. All commands run from the repository root. Docker is enough for all of them: the examples run the .NET SDK and Node in containers.

scripts/stage-modules.sh​

Publishes modules as drop-in packages, one folder per module id with its DLLs, module.json and LICENSE. The Docker build uses it to put modules into the image; you use it to fill a /modules mount or to make a zip to upload.

scripts/stage-modules.sh [configuration] [target] [modules]
ArgumentDefaultMeaning
configurationDebugThe build configuration passed to dotnet publish (Debug or Release).
targetsrc/Scadex.Core/bin/<configuration>/net10.0/modulesThe folder that receives one subfolder per module id. That default is the gateway's own module path when run from source.
modulesallall, or a space- or comma-separated list of folder names under modules/ (mqtt opcua), or an empty string for none.

A name without a modules/<name>/module.json stops the script with "Unknown module". For each selected module it removes <target>/<id> and publishes afresh, printing Staged <id> from modules/<name>. SCADEX_PUBLISH_ARGS in the environment is appended to every dotnet publish (the Docker build passes --no-restore).

Without the .NET SDK on the host, run it in the SDK container the image is built with:

mkdir -p .local/modules
docker run --rm -u "$(id -u):$(id -g)" -e HOME=/tmp -v "$PWD:/src" -w /src \
mcr.microsoft.com/dotnet/sdk:10.0.403 scripts/stage-modules.sh Release .local/modules mqtt
docker compose restart gateway

To make a package for uploading from Gateway > System > Modules or POST /api/v1/runtime/modules:

(cd .local/modules && zip -r ../mqtt.zip io.scadex.driver.mqtt)

tools/Scadex.LicenseTool​

Issues signed licence grants and signs module packages. It uses ECDSA on the NIST P-256 curve. The private key must stay off every gateway; gateways trust only the public keys compiled in from src/Scadex.Core/Licensing/trusted-keys.txt. See Licensing for what the gateway does with the result.

keygen --out <private-key.pem>
pubkey --key <private-key.pem>
issue --key <private-key.pem> --customer <id> --module <id> [--expires yyyy-MM-dd] [--features a,b] --out <file.license>
sign-module --key <private-key.pem> --dir <published-module-folder>
CommandWhat it does
keygenCreates a new private key as PKCS#8 PEM at --out. The file must not exist yet, and on Unix it is created readable and writable by the owner only. Prints the base64 public key with the line to add to trusted-keys.txt.
pubkeyPrints the base64 public key (SubjectPublicKeyInfo) of the private key at --key.
issueWrites a signed grant to --out naming --customer, one --module id, the time of issue, and optionally --expires (valid through the end of that day, 23:59:59Z) and a comma-separated --features list. Prints Issued <module> to <customer>.
sign-moduleReads module.json in --dir for the module id and writes module.sig beside it: the id and the SHA-256 of every file in the folder, sorted by relative path, signed with the key. Any later change to the folder invalidates it, so sign after publishing.

Options are given as --name value pairs after the command. A missing required option, an unreadable key, a module.json without an id, or an I/O error prints the message and exits with code 1; no command or an unknown one prints the usage and exits with 2.

Run it in the SDK container with the key mounted read-only:

docker run --rm -u "$(id -u):$(id -g)" -e HOME=/tmp -v "$PWD:/src" -v "$HOME/.config/scadex:/keys:ro" -w /src \
mcr.microsoft.com/dotnet/sdk:10.0.403 dotnet run --project tools/Scadex.LicenseTool -- \
issue --key /keys/license-signing-key.pem --customer acme --module io.scadex.driver.mqtt \
--expires 2027-12-31 --out .local/acme-mqtt.license

docker compose cp .local/acme-mqtt.license gateway:/data/licenses/
docker compose restart gateway

Signing a staged module:

scripts/stage-modules.sh Release .local/modules mymodule
docker run --rm -u "$(id -u):$(id -g)" -e HOME=/tmp -v "$PWD:/src" -v "$HOME/.config/scadex:/keys:ro" -w /src \
mcr.microsoft.com/dotnet/sdk:10.0.403 dotnet run --project tools/Scadex.LicenseTool -- \
sign-module --key /keys/license-signing-key.pem --dir .local/modules/com.acme.mymodule

Grants are checked when a module loads, not while it runs, so an expired grant stops the module at the next restart with no grace period. Grants are not tied to one gateway and cannot be revoked.

tools/wincc-import​

Rebuilds a WinCC Unified HMI on a Scadex gateway from the folder a TIA Openness export produces: the OPC UA connection to the PLC, its tags and alarms, tag type instances for the devices, and the screens, faceplates and popups as Scadex screens. It was written against one export (HMI_RT_NIRO on S7_VN_NIRO); the full walk-through is Importing a WinCC project.

node_modules/.bin/tsx tools/wincc-import/cli.ts <command> <export-folder> [options]
CommandWhat it does
planReads the export and writes what it would create to --out. Touches no gateway.
applyCreates the OPC UA connection, its tags and their alarms. Running it again keeps what exists. On a gateway that has the project's tag types it runs convert too.
rebindPoints the existing OPC UA tags at the nodes the plan now names, after a --namespace change. Instances take their nodes from their types, so it does not reach them.
verifyReads every OPC UA tag once and lists those whose quality is not Good. Run it once the PLC is online.
convertReplaces the imported device tags with instances of the project's tag types (CswMotor, CswValve, CswPID, ...), alarms carried over, deleting the old tags block by block only when the instance reads Good. Needs --url.
type-alarmsPuts the export's alarm texts on the types as {Description}: ... templates and sets per-instance overrides. Works from the export, so it replaces alarm edits made since; use --dry-run first.
viewsBuilds the screens, faceplates and popups as Scadex screens and makes the pages start screens. Without --url it only reports. Screens that already have content are kept unless --force.

Options: --url <gateway>, --api-key scx_... (engineer role) or --email/--password, --endpoint opc.tcp://host:4840, --namespace <n> (default 3), --enable (switch the connection on at once; otherwise it is created off), --keep-old (convert and apply: make the instances, delete nothing), --flat (no tag types), --out, --dry-run (type-alarms), --family motor,pid,... (type-alarms), --only "Screen A,Screen B" (views), --force (views). Options are checked: an unknown one, a missing value, or one given to a command it does nothing for is an error and nothing runs.

Docker build targets​

deploy/Dockerfile is a multi-stage build. Two stages are useful on their own:

TargetCommandWhat it does
testdocker build -f deploy/Dockerfile --target test .Restores and runs dotnet test Scadex.slnx in Release, including the module boundary tests. Needs no SDK on the host.
builddocker build -f deploy/Dockerfile --target build --build-arg SCADEX_MODULES=all .Publishes the gateway to /out with the dashboard built in and the selected modules staged under /out/modules.

The default target is the runtime image, registry.gitlab.com/scadex.io/platform/gateway, which docker compose up --build produces. The build argument SCADEX_MODULES picks the modules in the image (all, a list of folder names, or empty).

Other test entry points:

CommandWhat it runs
tests/integration/run.shThe end-to-end tests against a real MQTT broker and OPC UA server in containers. Needs Node on the host.
pnpm install && pnpm turbo build lint testThe frontend the way CI does: type-checking builds, lint and unit tests for the dashboard, the SDK and the UI library.