The data model was already multi-instance (lane_devices = one row per instance; assign always inserts) -- the limitation was UI-only. Make the whole flow support more than one of every category: - Backend: add DELETE /api/setup/assign/:id (unassign by id). /state now redacts secrets (pushPassword/webPassword/relayPassword) via a shared redactSecrets() also used by /assign -- it was returning raw config rows. - Web: SetupWizard reworked from one fixed slot per category into a list of assigned instances (driver/role/host + Remove) plus an "Add another" form. select-type config fields (e.g. printer role) now render as dropdowns. - api.ts: add fetchState(), unassignDevice(), Assignment/SetupState types. Verified via Fastify inject: two printers assigned to one lane both list, no secret leak, delete -> 204, delete unknown -> 404, count drops to 1. Full repo typechecks. Wiki: first-run-setup documents multi-instance + delete + redaction.
3.4 KiB
type, tags, sources, updated
| type | tags | sources | updated | |||||
|---|---|---|---|---|---|---|---|---|
| concept |
|
|
2026-06-15 |
First-Run Setup (device selection)
The admin install flow that makes the system device-agnostic in practice: on first run, an admin assigns devices per lane by choosing from the device-registry catalog and entering each device's connection config.
Implementation-derived (from
apps/server+apps/web), not the source doc.
Flow
- Read the catalog —
GET /api/setup/catalogreturns supported drivers per category (no secrets, just schema) plus adiscoverablelist. The webSetupWizardrenders a picker + the driver's config fields, and a Scan button for discoverable drivers (device-discovery). - Test (optional, no save) —
POST /api/setup/test(admin-only). Validates the config, probes reachability (healthCheck), and reports preconditions (e.g.input_link_relay) — without saving or changing the device. The wizard's Test connection button shows a health badge + any precondition warnings. - Save & configure —
POST /api/setup/assign(admin-only). Validates, then configures the device: fixes preconditions (e.g. disablesinput_link_relay) and sets up the Digest- authenticated input push (device-input-flow) — the admin never touches the device's own web UI. Fails the save (no DB row) if the device can't be configured, so there are no orphan/half-configured rows. On success persists tolane_devices. - Remove —
DELETE /api/setup/assign/:id(admin-only) drops one instance's row. Only our row is removed; the device itself is not un-hardened/un-configured (a stale push from an unknown device id is already rejected, and re-assigning reconfigures it). - Complete —
POST /api/setup/completemarks the single-rowsetup_state.
Config granularity — multi-instance per category
The data model is multi-instance: lane_devices holds one row per instance, keyed by a
generated id, with no one-per-(lane, category) constraint. So a lane can have more than one of
every category — e.g. two printers (an entry dispenser + a booth printer; see
printer-roles-failover), multiple readers, multiple cameras. assign always inserts a new row
(never an upsert), and state returns the full list.
The SetupWizard reflects this: each category shows the list of assigned instances for the
current lane (with Remove) plus an Add another form — not a single fixed slot. select-type
config fields (e.g. a printer's role) render as dropdowns.
Organized per lane — each lane gets its access controller(s), reader(s), camera(s), and printer(s), each with its own connection settings. Matches the architecture's "mixable per lane" reality (a lane can serve permit holders via wiegand and casual via host-side reads on one relay — see entry-exit-readers).
Security notes
- The assign/state/delete/complete endpoints require the admin role (local-jwt-auth).
- Device credentials are stored in
lane_devices.config— protect at rest (disk-os-hardening); device hosts belong on the isolated VLAN (network-isolation). - Secrets are stripped on the way out:
assignandstateboth redactpushPassword,webPassword, andrelayPasswordfrom the returned config (the UI lists devices; it never needs the stored secrets).