nodered: settings are files the mesh writes; editor locked with adminAuth; pin 5.0.7 #149

Open
mesh-admin wants to merge 3 commits from feat/nodered-for-ace into main
Contributor

The catalogue ran the image's defaults: no adminAuth, so a routed Node-RED
editor (which runs arbitrary code) was open to anyone who reached it, and
the module's own tools had no token to present to an install that was locked.

  • settings.js (fixed, 0600, uid 1000) carries adminAuth: user admin checked
    against the admin secret -- a minted password, or the bcrypt hash an
    existing install held (accepted), so current logins keep working -- and a
    static bearer token (api-token) the sidecar presents. It loads settings.json
    beside it, the one mergeable file; endpoints is dropped there, and an
    optional timeZone sets process.env.TZ (assignments cannot set env).
  • The sidecar's runtime config is no longer merged; it carries the token.
  • Directories are placed (state, data), the route binds into state.
  • Image pinned to 5.0.7 (a649dd71), what ace runs; the old pin was 5.0.6.
  • deployFlows asks for API v2: v1 answers 204 with no body, which the client
    tried to parse as JSON.

Verified: catalogue tests pass against this tree. A throwaway 5.0.7 container
started with the generated files: anonymous /flows 401, bearer api-token 200,
bad token 401, password grant 200/403 with a minted password and with a
bcrypt-hash-accepted one; endpoints and timeZone do not reach /settings;
timeZone Europe/Brussels overrides TZ=Etc/UTC; v1 deploy 204, v2 deploy
answers {rev}.

Migration notes for ace (assignment draft in the migration repo):

  • ACCEPT admin = the bcrypt hash in ace's /data/settings.js adminAuth (so the current password keeps working); MINT api-token.
  • The flow credential key stays in the data dir (.config.runtime.json); credentialSecret deliberately not set.
  • ace's HAL settings.js also set httpNodeAuth/httpStaticAuth; no flow has an http-in node and httpStatic is unset, so nothing depended on them. Not carried; say if they should be.
  • Finding: ace's only mqtt-broker config points at zurag.be:1884, where nothing listens; it has failed every 15 s since at least 2026-09-20, so the MQTT flows are idle today.
The catalogue ran the image's defaults: no adminAuth, so a routed Node-RED editor (which runs arbitrary code) was open to anyone who reached it, and the module's own tools had no token to present to an install that was locked. - settings.js (fixed, 0600, uid 1000) carries adminAuth: user admin checked against the admin secret -- a minted password, or the bcrypt hash an existing install held (accepted), so current logins keep working -- and a static bearer token (api-token) the sidecar presents. It loads settings.json beside it, the one mergeable file; endpoints is dropped there, and an optional timeZone sets process.env.TZ (assignments cannot set env). - The sidecar's runtime config is no longer merged; it carries the token. - Directories are placed (state, data), the route binds into state. - Image pinned to 5.0.7 (a649dd71), what ace runs; the old pin was 5.0.6. - deployFlows asks for API v2: v1 answers 204 with no body, which the client tried to parse as JSON. Verified: catalogue tests pass against this tree. A throwaway 5.0.7 container started with the generated files: anonymous /flows 401, bearer api-token 200, bad token 401, password grant 200/403 with a minted password and with a bcrypt-hash-accepted one; endpoints and timeZone do not reach /settings; timeZone Europe/Brussels overrides TZ=Etc/UTC; v1 deploy 204, v2 deploy answers {rev}. Migration notes for ace (assignment draft in the migration repo): - ACCEPT admin = the bcrypt hash in ace's /data/settings.js adminAuth (so the current password keeps working); MINT api-token. - The flow credential key stays in the data dir (.config.runtime.json); credentialSecret deliberately not set. - ace's HAL settings.js also set httpNodeAuth/httpStaticAuth; no flow has an http-in node and httpStatic is unset, so nothing depended on them. Not carried; say if they should be. - Finding: ace's only mqtt-broker config points at zurag.be:1884, where nothing listens; it has failed every 15 s since at least 2026-09-20, so the MQTT flows are idle today.
mesh-admin added 1 commit 2026-09-29 21:41:28 +00:00
The catalogue ran the image's defaults: no adminAuth, so a routed Node-RED
editor (which runs arbitrary code) was open to anyone who reached it, and
the module's own tools had no token to present to an install that was locked.

- settings.js (fixed, 0600, uid 1000) carries adminAuth: user admin checked
  against the admin secret -- a minted password, or the bcrypt hash an
  existing install held (accepted), so current logins keep working -- and a
  static bearer token (api-token) the sidecar presents. It loads settings.json
  beside it, the one mergeable file; endpoints is dropped there, and an
  optional timeZone sets process.env.TZ (assignments cannot set env).
- The sidecar's runtime config is no longer merged; it carries the token.
- Directories are placed (state, data), the route binds into state.
- Image pinned to 5.0.7 (a649dd71), what ace runs; the old pin was 5.0.6.
- deployFlows asks for API v2: v1 answers 204 with no body, which the client
  tried to parse as JSON.

Verified: catalogue tests pass against this tree. A throwaway 5.0.7 container
started with the generated files: anonymous /flows 401, bearer api-token 200,
bad token 401, password grant 200/403 with a minted password and with a
bcrypt-hash-accepted one; endpoints and timeZone do not reach /settings;
timeZone Europe/Brussels overrides TZ=Etc/UTC; v1 deploy 204, v2 deploy
answers {rev}.
jschoubben added 1 commit 2026-09-29 21:50:13 +00:00
The sidecar runs on the host network and dialled 127.0.0.1:1880, the
software's port; the mesh publishes nodered on a machine port it assigns,
so the tools reached whatever else holds 1880, or nothing (hq 088).
jschoubben added 1 commit 2026-09-30 11:11:59 +00:00
Node-RED's one broker node pointed at zurag.be:1884, where nothing listens. nodered now requires
mqtt-topic (asking for every topic: flows follow the devices' own) and a run-once `mqtt` step —
declared last, restarted when the binding, credential or settings change — points the mesh's broker
nodes at the bound broker through Node-RED's admin API with the module's api-token: the node the
step makes itself when none is named, or the ones an assignment names in `mqtt.brokers`. Only host,
port, TLS and the login change; the broker is asked first whether it takes the login; the deploy is
against the revision read ("nodes", so only that node restarts) and a digest makes a rerun a no-op.
A broker node nobody named is never touched. settings.js keeps `mqtt` and `topics` out of Node-RED.
Author
Contributor

nodered now gets its MQTT broker from the mesh (commit 3c7aafd)

  • requires mqtt-topic (contributes topics: ["#"], because flows follow the devices' topics), with binds and secrets.
  • settings.js now keeps the mqtt and topics keys out of Node-RED.
  • A new run-once mqtt step is declared last. It restarts on the binding, the credential or settings, and works through Node-RED's admin API with the module's api-token:
    • It owns only the broker nodes the settings name under mqtt.brokers, or, when none are named, a node it creates itself (mesh-mqtt-topic, named "mesh: mqtt-topic"). A broker node nobody named is never touched.
    • It first asks the broker whether it takes the login, and refuses a loopback at (from nodered's container that would be nodered itself).
    • It sets broker, port, usetls:false and credentials. It deploys against the revision it read (a 409 is re-read once) with type nodes, so only that node restarts.
    • A digest makes a rerun a no-op.
  • client.ts gains flowsWithRev, credentials and deployFlowsAt.
  • Tests: test/mqtt.test.ts (6 passing). tsc typecheck and build are clean. The catalogue tests pass with #144/#147/#149 merged. A scratch resolution for ace binds mqtt-topic → ace.internal:1883 as mesh_ace_nodered, and the step dials 127.0.0.1:${port:1880}.
  • E2E against throwaway nodered 5.0.7 running the module's own settings.js, plus a throwaway mosquitto (all removed):
    • The flows were ace's shape: broker node 2b0aece9c5f3b307 at zurag.be:1884 (dead), an mqtt in → mqtt out pair, and an unrelated broker node.
    • The step wrote broker, port, user and password. Node-RED logged "Connected to broker: mqtt://ace.internal:19381".
    • A legacy-login "device" publishing e2e/ping got e2e/pong back through the flow.
    • A rerun was unchanged. The unrelated node was untouched.
    • With nothing named, the step created mesh-mqtt-topic.
  • ace: ace-assignments/nodered.json now names "mqtt": {"brokers": ["2b0aece9c5f3b307"]}. This fixes the MQTT flows that have been dead since 2026-09-20 (they pointed at :1884).
  • Dependency: nodered's bridge container must resolve ace.internal, which is ADR 0148's machine-side name resolution. Today a bridge container on ace does not resolve it.
**nodered now gets its MQTT broker from the mesh** (commit 3c7aafd) - **requires** `mqtt-topic` (contributes `topics: ["#"]`, because flows follow the devices' topics), with `binds` and `secrets`. - `settings.js` now keeps the `mqtt` and `topics` keys out of Node-RED. - A new **run-once `mqtt` step** is declared last. It restarts on the binding, the credential or `settings`, and works through Node-RED's admin API with the module's `api-token`: - It owns only the broker nodes the settings name under `mqtt.brokers`, or, when none are named, a node it creates itself (`mesh-mqtt-topic`, named "mesh: mqtt-topic"). **A broker node nobody named is never touched.** - It first asks the broker whether it takes the login, and refuses a loopback `at` (from nodered's container that would be nodered itself). - It sets broker, port, `usetls:false` and credentials. It deploys against the revision it read (a 409 is re-read once) with type `nodes`, so only that node restarts. - A digest makes a rerun a no-op. - `client.ts` gains `flowsWithRev`, `credentials` and `deployFlowsAt`. - Tests: `test/mqtt.test.ts` (6 passing). tsc typecheck and build are clean. The catalogue tests pass with #144/#147/#149 merged. A scratch resolution for ace binds `mqtt-topic` → ace.internal:1883 as `mesh_ace_nodered`, and the step dials `127.0.0.1:${port:1880}`. - **E2E** against throwaway nodered 5.0.7 running the module's own `settings.js`, plus a throwaway mosquitto (all removed): - The flows were ace's shape: broker node `2b0aece9c5f3b307` at zurag.be:1884 (dead), an `mqtt in` → `mqtt out` pair, and an unrelated broker node. - The step wrote broker, port, user and password. Node-RED logged "Connected to broker: mqtt://ace.internal:19381". - A legacy-login "device" publishing `e2e/ping` got `e2e/pong` back through the flow. - A rerun was unchanged. The unrelated node was untouched. - With nothing named, the step created `mesh-mqtt-topic`. - **ace:** `ace-assignments/nodered.json` now names `"mqtt": {"brokers": ["2b0aece9c5f3b307"]}`. This fixes the MQTT flows that have been dead since 2026-09-20 (they pointed at :1884). - **Dependency:** nodered's bridge container must resolve `ace.internal`, which is ADR 0148's machine-side name resolution. Today a bridge container on ace does not resolve it.
You are not authorized to merge this pull request.
This pull request can be merged automatically.
This branch is out-of-date with the base branch
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/nodered-for-ace:feat/nodered-for-ace
git checkout feat/nodered-for-ace
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: novox/mesh-catalog#149