Publish the documentation site: docs.fluksio.com

A zensical site under docs/, served by a new `docs` compose service behind
Traefik, built with --strict in CI. Same pattern the sibling n3xd workspace
uses.

Getting started splits the way the landing page does — one path is
`pip install fluksio` and a training script, the other is a Docker stack and
an afternoon in the browser — because the two audiences will not spend the same
amount of time. Everything after that is shared: the concepts, the web
interface (app and portal), the CLI and the API, and a reference for node types,
payload types and configuration.

The three flow guides move here from the docs submodule rather than being
copied, so there is one version of them.

Styling mirrors DESIGN-GUIDELINES.md: the app's token palette remapped onto
Material's variables in both schemes, Inter, the 16px panel radius, and the one
terracotta accent spent on the facility lane of the audience split.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M7Xv3cJEW5c8AXxn2hoojV
This commit is contained in:
2026-08-22 05:55:34 +02:00
co-authored by Claude Opus 5
parent 8632d975e6
commit 11e032386b
34 changed files with 4167 additions and 2 deletions
+272
View File
@@ -0,0 +1,272 @@
# Getting started: facility automation
You have a box in a cupboard, a handful of sensors that already publish
somewhere, and an ambition to make the house do something about them. This
page brings up a Fluksio instance you can leave running for years, then wires
the first sensor through to a dashboard.
Budget an afternoon. Most of it is the browser, which is the point: you will be
in this interface a lot, so it is worth learning it properly.
## What you are standing up
```text
sensors ──MQTT──▶ ┌──────────────┐ ──▶ InfluxDB (history)
│ Fluksio │
HTTP / webhooks ──▶│ flow engine │ ──▶ dashboards (what you look at)
│ │
schedules ────────▶└──────────────┘ ──▶ MQTT / HTTP (what you control)
└── workers on other boxes (optional)
```
One engine holds every flow. Nothing here is a plugin you install separately —
the broker client, the time-series writer, the dashboards and the alerting are
all part of the same process, editing the same graph.
## Prerequisites
- Docker and Compose v2 on the host
- A hostname you can point at it. `fluksio.local`, a subdomain, or just
`localhost` if you only ever reach it from that machine
- Optionally: an MQTT broker and an InfluxDB you already run. If not, the stack
can start both for you
## Bring up the stack
Clone the app repository and start it:
```sh
git clone https://git.stroblme.de/Fluksio/app.git ~/fluksio
cd ~/fluksio
cp .env.example .env
$EDITOR .env # DOMAIN, FIRST_SUPERUSER, ENVIRONMENT=production
make up
```
`.env` is the whole configuration. The four settings that matter on day one:
| Setting | What it does |
|---|---|
| `DOMAIN` | the hostname everything is served under; the SPA lands on `app.${DOMAIN}` and the API on `api.${DOMAIN}` |
| `FIRST_SUPERUSER` | the account you sign in with |
| `FIRST_SUPERUSER_PASSWORD` | leave it as `changethis` and one is generated for you |
| `ENVIRONMENT` | `production` closes the interactive API schema; `local` leaves it open |
Everything the installation owns — the database, your flows, secrets,
artifacts, the packages your node code imports — is on one Docker volume.
Backing that volume up is backing up the installation.
!!! tip "Reverse proxy"
The stack emits Traefik labels and ships a Traefik you can bring up
alongside it (`docker/compose.traefik.yml`). If you already run Nginx
Proxy Manager or Caddy, attach it to the `proxy` network instead and
forward `app.${DOMAIN}``fluksio-app:80` and `api.${DOMAIN}`
`fluksio-api:8000`.
??? note "Even smaller: no Docker at all"
`pip install fluksio && fluksio serve` gives you the same engine with no
containers, keeping its data in `~/.fluksio`. What it does not give you is
the web interface, which the SPA container serves — so you would drive it
from the API, or [pair it with a portal](../interface/portal.md) that
serves the dashboard for you. Good for a Raspberry Pi that only runs flows;
less good as your main instance.
Open `http://app.${DOMAIN}` and sign in. You should be looking at Home: an
empty brain graph, a health summary, and a flow list with nothing in it.
## Your first flow
Go to **Flows → New flow** and call it `house`. You land on the canvas.
A flow is a set of nodes that talk to each other through **named messages**.
You do not draw wires: a node says which messages it needs and which it
produces, and the canvas draws the graph that follows from those names. That
sounds like a small difference and turns out to be a large one — renaming is
safe, fan-in is free, and two flows can share a value by naming it.
### Read a sensor
Press **Add node** (or ⌘K / Ctrl-K, which opens the command palette) and pick
**MQTT**. In its panel on the right:
- **Broker host** — your broker's hostname, `mosquitto` if you are using the
one the stack can start
- **Topic** — map each output to a topic:
`{"living_temperature": "zigbee2mqtt/living/temperature"}`
- **Provides** — add one output port named `living_temperature`, type `float`
That is a working node. Press **Publish** (⌘S) and the engine picks it up.
The canvas now draws your node with a live value on its output as soon as the
broker sends one. Click the wire to see the last payload and its history.
### Do something with it
Add a **Function** node. This is a Python node — the code editor opens in its
panel:
```python
def process(living_temperature, comfortable=21.0):
"""Ask for heat when the room is below the comfort point."""
return {"heat_wanted": living_temperature < comfortable}
```
Declare `living_temperature` as an input (type `float`) and `heat_wanted` as an
output (type `bool`). `comfortable` is not a port — it is a **setting**,
because it is a constant of this node rather than something the graph carries.
It shows up as a field in the node's Settings section.
The canvas now draws MQTT → your function, because the message names line up.
Nothing else was needed.
### Act on it
Add a second **MQTT** node, this time with `heat_wanted` as an *input*, and a
topic mapping to whatever your relay listens on. A node with inputs publishes;
a node with outputs subscribes.
Publish the flow. You have a thermostat.
!!! warning "Test before it touches a relay"
A flow can be **paused** (it holds messages instead of running them) and
**stepped** (release exactly one). Together with the run button — which
injects a value by hand — that is how you convince yourself the logic is
right before the contactor finds out. Both live on the dock at the bottom
of the canvas.
## Store the history
A live value is enough to control something and useless for answering "was last
February colder?". That is what the **InfluxDB** node is for.
Add one, and configure it to write the message you already have:
```json
{
"url": "http://influxdb:8086",
"token": {"$secret": "influx-token"},
"org": "home",
"bucket": "sensors",
"writes": {
"living_temperature": {
"measurement": "environment",
"field": "temp_c",
"tags": {"room": "living"}
}
}
}
```
Give it `living_temperature` as an input and every value that passes gets a
point.
Note the token. Credentials never sit in a flow: `{"$secret": "influx-token"}`
is a reference into an encrypted store, and the editor renders those fields as
a secret picker. Add the actual value once under **Secrets**. Flows are a git
repository you might well push somewhere — this is what keeps a password out of
it.
Reading back is the same node with `queries` instead of `writes`, or — for
anything a chart asks for — a pair of small Python nodes on either side that
build a Flux query and shape its rows. That indirection is deliberate: the
database node holds the connection and nothing else, so a dashboard widget
never learns which database answered it.
## Put it on a screen
**Dashboards → New dashboard**, then drag widgets onto the grid and bind each
one to a message. A gauge on `house.living_temperature`, a switch on
`house.heat_wanted`, a chart on the history.
Widgets are typed the same way ports are: a switch binds to a `bool`, a gauge
to a number, an agenda to a `list`. Bind it wrong and the editor says so rather
than drawing nothing.
Controls work in the other direction — a switch on a dashboard publishes the
message it is bound to, exactly as a node would. The canvas draws it as a
labelled endpoint feeding the nodes that read it, so a value never appears from
nowhere.
For a tablet on the wall, see [Dashboards and panels](../interface/dashboards.md):
a panel is a named device, it pairs with a six-character code instead of a
login, and it can only reach the dashboards you gave it.
## Spread it across machines
You now have one box doing everything. Two reasons to change that: something
lives on a different network, or something needs hardware the engine's host
does not have.
The unit of distribution is the **worker**. It runs the code of nodes you mark
for it, and it dials *out* to the engine — so the Pi in the shed does not need
an inbound route, and the engine does not need to reach it.
On the engine, mint a token:
```sh
curl -X POST https://api.${DOMAIN}/api/v1/workers/tokens \
-H "Authorization: Bearer $TOKEN" -d '{"name": "shed-pi"}'
```
On the other machine:
```sh
pip install fluksio-worker
fluksio-worker \
--url wss://api.${DOMAIN}/api/v1/workers/attach \
--token "$WORKER_TOKEN" \
--labels shed,gpio \
--parallel 2
```
Then mark the node that talks to the shed's GPIO with `device: shed`, and it
runs there. Everything else stays where it is. A node bound to a label no
attached worker carries simply waits rather than failing, so you can write the
flow before the hardware arrives.
!!! note "What a worker is not"
It is not a second engine. Subscriptions, schedules, webhooks and the
dashboards all stay in one process — that is what keeps a value having one
definition. A worker executes node bodies, nothing else. Scaling *the
engine* to several processes is not supported: run one.
## Make it tell you when something breaks
Under **Alerts**, add a channel and a rule. Channels are ntfy (a push
notification on your phone), SMTP, a webhook, or a message a dashboard
notification widget reads.
What you probably want on day one is everything, to ntfy:
- add an ntfy channel with your server and topic
- add a rule with no events ticked, which means *all of them*
The engine deduplicates aggressively — the same node failing every second is
one alert, not thirty-six thousand — and caps the total at ten an hour however
bad it gets. See [Secrets, modules and alerts](../interface/operations.md).
## Keep it alive
- **Back up the data volume.** That is the database, the flows, the secrets and
the artifacts. Everything else is rebuildable.
- **Watch Home.** The health summary names what is wrong — a quarantined flow, a
node that will not load, a stalled queue, a flow that cannot run because its
graph does not validate.
- **Flows are git.** `git log` inside the flow store is the history of every
change anyone made, and reverting one is a revert.
## Where to go next
- [Flows, nodes and messages](../concepts/flows.md) — the model, properly
- [Node types](../reference/node-types.md) — everything you can put on a canvas
without writing Python
- [The flow editor](../interface/flow-editor.md) — the canvas, in detail
- [Keeping state in a flow](../concepts/state.md) — running totals, debounces,
and the one rule that makes them safe
- [Accounts and the portal](../interface/portal.md) — reach the installation
from outside the house without opening a port