Skip to content

Deployment

Run OpexFlow under a hardened systemd unit with a service user, state directory and secrets in an EnvironmentFile; or deploy to ARM64 edge devices with one script.

Updated View as Markdown

One-command edge deploy

./scripts/deploy.sh pi@edge-01 config.yaml

The script builds the UI first (it is embedded in the binary), cross-compiles with cargo zigbuild (TARGET_TRIPLE=x86_64-unknown-linux-musl for x86 hosts), creates the opexmx service user, verifies the uploaded binary’s SHA-256, swaps it in atomically, keeps the previous one as opex-flow.prev, and rolls back automatically if the service fails to start.

systemd (manual)

Create the user and directories, put secrets in a root-only env file, and install the unit from deploy/opex-flow.service:

sudo useradd --system --home-dir /var/lib/opex-flow --shell /usr/sbin/nologin opexmx
sudo install -d /opt/opex-flow /etc/opex-flow
sudo install -m 0755 opex-flow /opt/opex-flow/opex-flow
sudo install -m 0640 -g opexmx config.yaml /etc/opex-flow/config.yaml
sudo install -m 0600 /dev/null /etc/opex-flow/env
sudoedit /etc/opex-flow/env     # CONNECTOR_HMAC_SECRET=...  CONNECTOR_WEBHOOK_TOKEN=...
sudo cp deploy/opex-flow.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now opex-flow

Set connector.data_dir: /var/lib/opex-flow in the config. Key unit settings:

Setting Why
EnvironmentFile=-/etc/opex-flow/env Secrets stay out of the world-readable unit.
StateDirectory=opex-flow Writable, service-owned home for data.db, push_queue.db, api_token, license cache and runtime files.
ProtectSystem=strict, ProtectHome, PrivateTmp, NoNewPrivileges Sandbox: only the state directory is writable.
SupplementaryGroups=dialout Modbus RTU on /dev/ttyUSB*.
TimeoutStopSec=45 The connector handles SIGTERM and drains for up to 30 s (adapters stopped, push queue / sinks / batches flushed).
StartLimitIntervalSec=300, StartLimitBurst=5 A bad config or license cannot restart-loop forever.

Find the generated API token with sudo cat /var/lib/opex-flow/api_token (see Security).

Docker

cp apps/connector/config.example.yaml config.yaml    # set connector.health_check_port: 3000
printf 'CONNECTOR_HMAC_SECRET=%s\nCONNECTOR_WEBHOOK_TOKEN=...\nCONNECTOR_API_TOKEN=%s\n' \
  "$(openssl rand -hex 32)" "$(openssl rand -hex 32)" > .env
GH_TOKEN=ghp_xxx docker build --secret id=gh_token,env=GH_TOKEN -t opex-flow .
docker compose up -d

The image runs as non-root user app, binds 0.0.0.0 inside the container (the compose file publishes the port on host loopback only), and keeps all state in the /data volume. With no config mounted it starts from a built-in receiver-ready default (/etc/opex-flow/config.yaml). The source build needs a read-only GitHub token because opex-contracts is a private dependency.

Without a compiler or token, build the same image from the published static release: docker build --target runtime-release -t opex-flow . (--build-arg OPEX_FLOW_VERSION=2.1.0 --build-arg OPEX_FLOW_SHA256=<digest> to pin and verify; amd64 and arm64). That is what Hosted deployment uses for a VPS behind a Cloudflare Tunnel.

Edge (ARM64)

The same static binary cross-compiles to aarch64-unknown-linux-musl. Static musl binaries cannot load Fanuc’s FOCAS library, but they do not need it: FOCAS works there through the built-in native client (adapters.focas.mode: native, beta). To use Fanuc’s own library on Linux download the glibc release (linux-x86_64-gnu, built with cargo zigbuild --release --target x86_64-unknown-linux-gnu.2.17, runs on glibc 2.17+) or use the Windows release. Releases include SHA256SUMS, and every download carries an X-Checksum-Sha256 header; verify before installing.

High availability

For a two-node active-standby pair (lease in NATS KV or Postgres, /api/ready load-balancer check, failover in about one lease TTL) see High availability.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close