Flux Operator Guide
This guide covers the local operator workflow built around the top-level flux command.
CLI
Install or refresh the user service and CLI symlink:
./scripts/flux-service-install.sh
This installs:
~/.config/systemd/user/flux-stack.service~/.local/bin/fluxsymlinked toscripts/flux
Show the getting-started intro:
flux
flux intro
Common commands:
flux start
flux stop
flux status
flux logs
flux open
flux doctor
Ignition dev-cell commands:
flux ignition info
flux ignition doctor
flux ignition deploy-fluxy
flux ignition request-scan
flux ignition open
Field simulation commands:
flux field import-tag-data Tag_02 \
--devices "tag_data/tag_data/tag_02 devices.txt" \
--tags tag_data/tag_data/tags02.json
flux field materialize --provider Tag_02
FLUX_FIELD_AGENT_MODE=supervised flux start
flux field configure-ignition --tag-provider default --tag-folder FieldAgent
flux doctor
This is the local operator path from a checked-in tag_data export to Ignition-readable simulated OPC tags. Do not use --skip-raw-config for provider reconstruction unless you intentionally do not need UDT OPC bindings.
Background Service
flux-stack.service runs the local development stack in the background:
- Django web app on
http://localhost:8000/ - QuestDB Trace data plane on
postgresql://admin:quest@localhost:8812/qdb - FieldAgent OPC UA adapter process or processes, depending on
FLUX_FIELD_AGENT_MODE - Fluxolot Spot sampler that reads Ignition through Fluxy and writes latest values into Flux
The service runs scripts/flux-start.sh.
The launcher intentionally:
- runs migrations
- repairs Postgres sequences for copied legacy IDs
- exports FieldAgent config for the legacy single-process FieldAgent path
- starts QuestDB from
.runtime/questdb-distwith data in.runtime/questdb-data - starts Django through Gunicorn on Linux with
FLUX_WEB_WORKERS=8andFLUX_WEB_THREADS=2by default - starts FieldAgent in
FLUX_FIELD_AGENT_MODE=supervisedby default, withFLUX_FIELD_AGENT_MODE=legacyretained for the single-process compatibility path - waits until Django responds before declaring the stack ready
- keeps all child services under one cleanup boundary
FieldAgent modes:
FLUX_FIELD_AGENT_MODE=legacystarts one FieldAgent fromweb/Flux/field/field-config.jsononopc.tcp://localhost:4840/flux/field.FLUX_FIELD_AGENT_MODE=supervisedstartsmanage.py flux_field_supervisor, which writes per-device runtime configs under.runtime/field-agentand starts one FieldAgent process per enabled field device.- Run the supervisor directly with
uv run python web/Flux/manage.py flux_field_supervisor --runtime-dir .runtime/field-agent.
Architecture boundary:
Flux.simowns the simulated device/tag domain configuration imported fromtag_dataor built through the UI.Flux.basepersists both the simulation catalog and the materialized runtime endpoint/device/tag configuration.Flux.servesupervises FieldAgent as the OPC UA runtime adapter, with one FieldAgent process per enabled materialized runtime device in supervised mode.Flux.webconfigures and displays the state; it does not directly own long-lived FieldAgent processes from request handlers.fluxyconfigures Ignition through WebDev by creating OPC UA connections and tags that point at the FieldAgent endpoints.
Field Simulation Operator Workflow
Use this workflow when the source is an Ignition provider export plus device inventory under tag_data/.
- Import the provider export and device inventory into the Django simulation catalog:
flux field import-tag-data Tag_02 \
--devices "tag_data/tag_data/tag_02 devices.txt" \
--tags tag_data/tag_data/tags02.json
This runs manage.py import_tag_data_catalog. It imports the provider tree, correlates device inventory, and creates enabled SimDevice and SimDeviceTag rows. Keep raw config for normal provider reconstruction because UDT OPC bindings live there.
- Materialize enabled simulation catalog rows into FieldAgent runtime tables:
flux field materialize --provider Tag_02
This runs manage.py materialize_sim_field_config. It creates enabled sim.Endpoint, sim.DeviceConfig, and sim.TagConfig rows from the imported simulation catalog.
- Start supervised FieldAgent processes:
FLUX_FIELD_AGENT_MODE=supervised flux start
Supervised mode starts one FieldAgent OPC UA adapter process per enabled materialized runtime device. Runtime configs are written under .runtime/field-agent. To inspect the supervisor plan without starting processes, run:
flux field supervisor --dry-run
- Configure Ignition through Fluxy:
flux field configure-ignition \
--tag-provider default \
--tag-folder FieldAgent
This runs manage.py configure_field_ignition. It uses Fluxy WebDev to create OPC UA connections for the enabled FieldAgent endpoints and OPC tags under [default]FieldAgent. By default it removes the target generated folder and generated OPC UA connections before writing fresh config.
Current limitations:
- Legacy mode remains available and still represents a single-process FieldAgent path; supervised mode is the target architecture.
- Supervised mode is local-service oriented and writes generated process configs under
.runtime/field-agent. - Device delay metadata can be materialized, but FieldAgent does not yet enforce request-level delay at runtime.
- Large provider exports can produce large FieldAgent and Ignition configurations; start with a small closed-loop trial before full ACM02 scale.
- Ignition configuration cleanup targets generated folders and generated OPC UA connections, not arbitrary gateway history or unrelated operator-created tags.
Next tests:
- Closed-loop lifecycle: build a test-specific simulated device, materialize it, start supervised FieldAgent, configure Ignition through
fluxy, read a tag, confirm value change, delete the tag/folder, verify missing, and remove the device. - Multi-device supervision: verify two enabled materialized devices produce two distinct FieldAgent processes and endpoints.
- Restart recovery: restart
flux-stack.servicewithFLUX_FIELD_AGENT_MODE=supervisedand verify enabled materialized devices return online. -
Runtime behavior: add a FieldAgent-backed test for
slow_networkafter the adapter consumes device delay metadata. -
Verify the whole stack:
flux doctor
flux doctor should show the Flux service, Flux web, FieldAgent OPC UA, runtime reads, Live Ignition Bridge, historian, QuestDB, and Ignition dev cell checks. If Ignition is not ready, run flux ignition doctor --open and resolve Gateway or Fluxy WebDev readiness first.
For direct Django access, the equivalent commands are:
uv run python web/Flux/manage.py import_tag_data_catalog Tag_02 \
--devices "tag_data/tag_data/tag_02 devices.txt" \
--tags tag_data/tag_data/tags02.json
uv run python web/Flux/manage.py materialize_sim_field_config --provider Tag_02
uv run python web/Flux/manage.py configure_field_ignition --tag-provider default --tag-folder FieldAgent
uv run python web/Flux/manage.py flux_field_supervisor --runtime-dir .runtime/field-agent
Start/stop/status wrappers:
./scripts/flux-service-start.sh
./scripts/flux-service-stop.sh
./scripts/flux-service-status.sh
./scripts/flux-service-logs.sh
The desktop app launcher entries are:
Flux Stack StartFlux Stack Stop
Dashboard
The main page is an operator console, not a branding page.
It shows:
- runtime tag readiness
- latest read freshness
- FieldAgent port/config state
- Live Ignition Bridge state
- stale tag recovery actions
The stale recovery action performs one Fluxy read_blocking([...]) block read for the stale set, then updates LatestTagValue and TagSample. Avoid per-tag read loops.
Runtime health is cached state. In steady state a Flux.serve sampler should keep runtime tags fresh through Flux.opt block reads. A page reload or HTMX refresh may update the display, but it should not be the mechanism that performs Ignition reads.
The Live Ignition Bridge configuration is stored in Django as dashboard.IgnitionBridgeConfig.
Token behavior:
- token is never rendered back into HTML
- blank token input keeps the existing token
Clear stored tokenexplicitly clears itTest connectioncalls Fluxyutil_get_versionIgnition 8.3.6 (b2026042713)is the Ignition product version plus vendor build identifier returned by the bridge test
Health
Run:
flux doctor
The health check currently covers:
- user systemd service state
- Flux web response
- FieldAgent OPC UA port
- runtime tag count, stale count, bad quality count, and latest read age
- Live Ignition Bridge token/config and live Ignition version probe
- historian datasource type/status
- QuestDB Trace data-plane reachability,
plane_samplescount, and latest timestamp - Ignition Gateway and Fluxy WebDev readiness
Architecture boundary:
scripts/fluxis the operator CLI and process-orchestration boundary.dashboard.management.commands.flux_doctor_stateemits Django/app health as JSON.dashboard.servicesowns runtime/bridge state calculations used by both the dashboard and doctor-state command.- Fluxy owns Gateway and datasource probes.
Flux.serveowns FieldAgent adapter supervision; FieldAgent is the OPC UA process that services materializedFlux.simdevices.- Django request handlers should not directly own long-lived process supervision.
Chart Worker
Flux.chart has a dedicated flux.serve worker command for keeping local rolling-history cache current from Ignition/Fluxy.
Run one generic cache sync:
uv run python web/Flux/manage.py flux_charts_worker --once
Run continuously every minute:
uv run python web/Flux/manage.py flux_charts_worker --interval 60
The dedicated Trace worker performs service/process work. Trace views should only read local Plane sample payloads.
QuestDB Trace Data Plane
Flux.chart can export local plane.sample rows into QuestDB as its high-volume HTTP payload data plane. Postgres plane.sample remains the control-plane source/export staging area; browser payloads are served from QuestDB when available.
Start QuestDB directly:
scripts/questdb-start.sh
The scripts download QuestDB 9.3.5 into .runtime/questdb-dist when missing and store data under .runtime/questdb-data. Override with QUESTDB_VERSION, FLUX_QUESTDB_DIST, or FLUX_QUESTDB_DATA if needed.
Export current enabled chart Plane sample rows into QuestDB:
uv run python web/Flux/manage.py sync_charts_questdb --replace
Known good output ends with:
Flux is healthy.
Optional Browser Tests
Trace interaction tests use Playwright and are gated behind FLUX_PLAYWRIGHT=1.
uv run python -m playwright install chromium
FLUX_PLAYWRIGHT=1 DATABASE_URL= uv run pytest web/Flux/src/flux/trace/test_e2e_playwright.py -q