Files
app/docs/reference/configuration.md
T
stroblmeandClaude Opus 5 0ffcabfdb9
Docs / docs (push) Successful in 30s
Playwright Tests / test-playwright (1, 2) (push) Successful in 3m7s
Playwright Tests / test-playwright (2, 2) (push) Successful in 1m54s
pre-commit / pre-commit (push) Failing after 4m24s
Test Backend / test-backend (push) Successful in 3m8s
Compose Smoke Test / test-compose (push) Successful in 40s
Playwright Tests / merge-reports (push) Successful in 1m33s
Media dtypes: image, audio and video as narrowed artifact references
A port may now declare `image`, `audio` or `video`. Each is the artifact
reference the engine already had, narrowed by the `media_type` on it, so a
speech recogniser declares what it eats rather than taking any bytes at all and
finding out. Bytes still never travel as a message and nothing on the wire
stops being JSON: a camera publishes one reference per frame, a microphone one
per chunk, and a reference may carry a `meta` dict nothing here interprets.

Streaming media is therefore an ordinary streaming port — with one change to
what that means. An emission used to journal an item with no payload, so
downstream read whatever was current when the item was claimed; a consumer
slower than its producer saw only the newest chunk and the ones between were
lost. That is right for a training curve and wrong for a second of speech, so
an emission now journals a `kind="emission"` item carrying its values, and the
executor hands them to the nodes reading that message instead of writing them
to state again. The value in state stays the latest, which is what everything
else reads, and the wave is filtered by what actually changed rather than
walking everything reachable. No queue serialization change — the existing
`outputs` field carries it.

Continuous media makes the store's missing GC a real problem, so this closes
it: `sweep_artifacts` runs hourly, keeps every digest a `run_artifact` row
records or a live message holds, spares anything written in the last hour, and
stands aside entirely while a run is in flight, since a node may store a
checkpoint long before it returns the reference to it. That also collects the
orphans a deleted flow has always left behind. `ARTIFACT_GC_INTERVAL_S=0` turns
it off.

Around the edges: `GET /artifacts/{digest}` serves the media type the caller
passes and answers ranged requests, so a browser plays a clip rather than
downloading it; `PUT` spools to disk instead of holding the whole body in
memory, as does `save_artifact` given a path; a Media widget draws whatever its
message points at, and a wall panel may fetch the bytes its own tiles are
showing and nothing else; and a connector gets `save_artifact`, for a device
whose readings are bytes.

What this cannot do is live video: a frame every second or two is a glance, and
the honest answer above that is the camera's own stream, which the widget takes
as a URL and the browser plays from source.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 23:44:55 +02:00

191 lines
8.7 KiB
Markdown

# Configuration
Every setting comes from the environment, or from an env file. Which file
depends on how the installation was started:
| Started with | Reads |
|---|---|
| `fluksio serve` | `env` inside the data directory (`$FLUKSIO_ENV_FILE`) |
| the Docker stack | `.env` beside `docker/` |
Anything already exported wins over the file.
## Storage
| Variable | Default | Notes |
|---|---|---|
| `DATA_DIR` | `flow-data` (`./.fluksio` via the CLI; `~/.fluksio` with `--global`) | everything below derives from this |
| `DATABASE_URL` | SQLite in `DATA_DIR` | any SQLAlchemy URL |
| `FLOWS_DIR` | `$DATA_DIR/flows` | the git repository holding flows |
| `SECRETS_FILE` | `$DATA_DIR/secrets.enc` | encrypted credentials, deliberately outside the repo |
| `ALERTS_FILE` | `$DATA_DIR/alerts.json` | channels and rules |
| `PANELS_FILE` | `$DATA_DIR/panels.json` | wall-panel pairings |
| `OAUTH_PRIVATE_KEY_FILE` | `$DATA_DIR/oauth-key.pem` | signs agent and worker tokens |
| `CLOUD_CONFIG_FILE` | `$DATA_DIR/cloud.json` | the portal enrolment, if any |
| `NODE_VENV` | `auto` | which interpreter node code runs on — see below |
Set `DATA_DIR` and the rest follow. Set one explicitly and it wins — which is
what the container images do to pin everything onto `/data`.
`NODE_VENV` is the exception, being about an environment rather than a path:
| Value | What node code runs on |
|---|---|
| `auto` (default) | the venv Fluksio was installed into, when it was installed into one and there is no venv of its own already built. `pip install fluksio` beside your own packages is this case, and the packages are then already there — the Modules screen turns read-only, because that environment is not Fluksio's to install into. |
| `managed` | a venv the engine builds under `DATA_DIR` and owns, which the Modules screen installs into with `uv pip sync`. The container images set this: the venv in them holds the app and nothing of anybody else's. |
| a path | that interpreter, or that venv, whatever it is. |
An installation that already has a managed venv keeps it on upgrade under
`auto`, because it may hold packages somebody installed on purpose.
!!! warning "The four files that must be on persistent storage"
`secrets.enc`, `alerts.json`, `panels.json` and `oauth-key.pem` are written
at runtime. In a container, anything not on a volume lands in the writable
layer and is lost on the next rebuild — un-pairing every screen and
revoking every agent.
## State
| Variable | Default | Notes |
|---|---|---|
| `REDIS_HOST` | unset | without it, flow state lives in memory and does not survive a restart |
| `REDIS_PORT` | `6379` | |
Flow state is the last value of every message, node memory, and the run queue.
Redis here is persistence, not a cache — run it with append-only persistence
on.
## Identity and access
| Variable | Default | Notes |
|---|---|---|
| `SECRET_KEY` | generated | signs sessions and derives the secrets-store key |
| `ACCESS_TOKEN_EXPIRE_MINUTES` | `11520` (8 days) | |
| `FIRST_SUPERUSER` | — | absent means the CLI creates one on first run |
| `FIRST_SUPERUSER_PASSWORD` | — | absent means one is generated and printed once |
| `DOMAIN` | `localhost` | what the API and OAuth issuer are built from |
| `FRONTEND_HOST` | `http://localhost:5173` | used in mails, OAuth metadata and panel pairing links |
| `BACKEND_CORS_ORIGINS` | `[]` | comma-separated; `FRONTEND_HOST` is always allowed |
## Where the interface looks for the API
The dashboard is a static bundle, so this one is a **build** argument of the
`frontend` image rather than a setting the running stack reads.
| Argument | Used by | Effect |
|---|---|---|
| `VITE_API_URL` | `frontend` at build time | the address the interface calls |
The compose stack takes it from `.env`, falling back to `https://api.${DOMAIN}`.
`scripts/setup.sh` writes it there with the scheme `ENVIRONMENT` implies, so a
local build calls `http://` and does not fail a certificate check nothing is
there to satisfy.
Empty is the useful value: the interface then addresses the API relative to
whichever origin served the page, so one image answers on a hostname, on a
`http://<host-ip>:<port>`, and through an ssh tunnel alike — and no origin has
to be added to `BACKEND_CORS_ORIGINS`, because there is only one.
`docker/compose.lan.yml` builds it that way and puts an `/api` proxy in front
of the backend to complete it; see
[getting started](../getting-started/facility-automation.md#on-your-own-network-by-address).
Set it to an absolute URL only when the API genuinely lives somewhere else, and
remember it is fixed at build time: changing it means rebuilding that image.
!!! danger "Rotating `SECRET_KEY`"
The secrets store is encrypted with a key derived from it. Change it and
the store stops decrypting, and every session is signed out. Re-enter your
secrets, or plan the rotation properly.
## Environment
| Variable | Default | Notes |
|---|---|---|
| `ENVIRONMENT` | `local` | `local`, `staging` or `production` |
| `PRIVATE_API_ENABLED` | `false` | unauthenticated test-only endpoints; needs `ENVIRONMENT=local` too |
| `TZ` | `UTC` | the timezone every schedule is written in |
`TZ` is the container's own, not a setting the code reads: an `inject` or a
`delay` with a cron expression fires on local time. Left at `UTC`, "off at
02:00" means two in the morning UTC, which in most of the world is neither two
o'clock nor the same hour in summer as in winter. Set it to where the
installation is.
`production` closes `/docs`, `/redoc` and the OpenAPI document, because the
schema enumerates every endpoint the installation serves — including the paths
webhook nodes mounted at runtime. It also turns a `changethis` secret from a
warning into a refusal to start.
## The engine
| Variable | Default | Notes |
|---|---|---|
| `FLOW_MAX_WORKERS` | `4` | node-code subprocesses run in parallel |
| `FLOW_MAX_CASCADES` | `4` | cascades in flight at once; throughput is this over the mean cascade time, so raise it where nodes wait on a network rather than a CPU |
| `FLOW_NODE_TIMEOUT` | `0` | seconds a node may be silent, unless it sets its own; 0 is no limit |
| `FLOW_CPUS` | `0` | cores nodes that declare `resources` may be given; 0 works it out as every core but two, which are what keeps the engine answering while the machine is busy |
| `FLOW_GPUS` | `0` | GPUs on this machine, each held by one node at a time. Not detected — say how many there are |
| `OBS_RETENTION_DAYS` | `30` | how long metrics, events and run records are kept |
| `ARTIFACT_GC_INTERVAL_S` | `3600` | how often artifact bytes nothing refers to are swept away; 0 never sweeps |
| `ARTIFACT_GC_GRACE_S` | `3600` | how long a freshly written artifact is spared, whatever refers to it |
An artifact is referred to by a run that recorded it or by a message currently
holding it; anything else is what a camera published four hours ago, and the
sweep is what keeps a flow streaming media from filling the disk. It stands
aside entirely while a run is in flight, since a node may store a checkpoint
long before it returns the reference to it.
A node that declares nothing is not accounted against `FLOW_CPUS`; it runs on
the shared pool and is given `FLOW_CPUS / FLOW_MAX_WORKERS` as a thread cap, so
several at once cannot each size themselves to the whole machine. Setting
`OMP_NUM_THREADS` (or any of its siblings) on the engine yourself overrides
that default. What is free, and which nodes are queued for it, is
`GET /api/v1/workers/resources`. See
[declaring resources](../getting-started/data-science.md#declaring-what-a-node-needs).
## Agents
| Variable | Default | Notes |
|---|---|---|
| `MCP_ENABLED` | `false` | opens the `/mcp` endpoint **and** OAuth client registration |
| `MCP_TOKEN_EXPIRE_MINUTES` | `60` | an agent's token is a bearer secret held by a program |
| `MCP_REFRESH_EXPIRE_DAYS` | `30` | |
| `OAUTH_CODE_EXPIRE_SECONDS` | `60` | |
See [Agents over MCP](../code/agents.md).
## Mail
Needed for password-reset mails. Without `SMTP_HOST` and `EMAILS_FROM_EMAIL`,
mail is simply off.
| Variable | Default |
|---|---|
| `SMTP_HOST` | — |
| `SMTP_PORT` | `587` |
| `SMTP_USER` / `SMTP_PASSWORD` | — |
| `SMTP_TLS` / `SMTP_SSL` | `true` / `false` |
| `EMAILS_FROM_EMAIL` | — |
| `EMAILS_FROM_NAME` | `Fluksio` |
| `EMAIL_RESET_TOKEN_EXPIRE_HOURS` | `48` |
## Monitoring
| Variable | Default | Notes |
|---|---|---|
| `SENTRY_DSN` | — | error reporting, if you want it |
## Health check
`GET /api/v1/utils/health/` is a *deep* check: it fails when the event loop is
wedged or the state backend is gone, not just when the process is up. That is
what the container healthcheck probes, and what an autoheal sidecar restarts
on.
## See also
- [The `fluksio` command](../code/cli.md) — what the data directory holds
- [Getting started: facility automation](../getting-started/facility-automation.md)