Harden Dingtian: authenticated binary relay + disable unused channels

Lock down the relay device for the flat (no-VLAN) network.

Relay control:
- pulseOpen/setRelay now use the Dingtian BINARY protocol (:60000) with a
  relay password — the only relay option with auth (string :60001 has none, and
  is kept only for the read-only status query). Frame verified on hardware.

HardenableDevice capability (driver harden()):
- set a random relay_pw (1-9999); disable unused channels (rs485/can/tcp x2/mqtt
  -> p:255), keeping UDP1 binary (control) + UDP2 string (status).
- write-verified (device reboots on apply).

Assign/Save flow now does: fix preconditions -> harden -> set up input push;
the relay password is stored in lane_devices so the runtime device can command
the relay.

DELIBERATELY NOT touching the device's HTTP CGI session check (session_en):
enabling it on this firmware breaks the config-READ API (ECONNRESET) and locked
the backend out — required a factory reset to recover. The open CGI API is
accepted as flat-network reality; the signed event log is the real guarantee.

Verified end to end on hardware: assign hardens + configures the device, config
API stays reachable, pulseOpen with the stored password fires the relay, without
it is rejected. wiki: device-input-flow + dingtian-relay updated.
This commit is contained in:
2026-06-14 18:34:35 +02:00
parent 0375227a16
commit 7fd407ac82
6 changed files with 259 additions and 32 deletions
+10 -2
View File
@@ -5,6 +5,7 @@ import {
hasPreconditions,
hasPushConfig,
isDiscoverable,
isHardenable,
registerBuiltinDrivers,
registry,
setDeviceLogSink,
@@ -139,8 +140,10 @@ export async function setupRoutes(app: FastifyInstance, db: Db): Promise<void> {
// Configure the device on save (before persisting, so we don't store a row
// for a device we couldn't configure):
// 1. fix preconditions (e.g. disable input_link_relay so a button press
// doesn't auto-fire its relay — host must decide first), and
// 2. set up input push (Digest creds + push URLs).
// doesn't auto-fire its relay — host must decide first),
// 2. harden (relay password + disable unused protocol channels), and
// 3. set up input push (Digest creds + push URLs).
// Each step is a device config write (the device reboots on apply).
try {
if (hasPreconditions(device)) {
const fixed = await device.fixPreconditions();
@@ -152,6 +155,11 @@ export async function setupRoutes(app: FastifyInstance, db: Db): Promise<void> {
}
}
if (isHardenable(device)) {
const { secrets } = await device.harden();
Object.assign(fullConfig, secrets); // e.g. relayPassword
}
if (hasPushConfig(device)) {
const host = String(config.host ?? "");
const backendIp = backendIpForDevice(host);