Commit Graph

58 Commits

Author SHA1 Message Date
julian 3294f188dd Dingtian input push: HTTP Digest auth + auto-config on assign
Secure the device→backend input push, and configure it automatically when the
admin assigns the device (no manual URL/secret entry).

Auth — HTTP Digest (chosen by hardware testing: the device can't push to a
self-signed HTTPS backend, but does Digest correctly; a URL token is sniffable/
logged):
- digest-auth.ts: MD5 qop=auth challenge/verify, single-use nonces (replay
  resistance). Password never crosses the wire.
- push route: Digest + source-IP allowlist; per-device pushUser/pushPassword from
  lane_devices. Still not behind the SPA cookie/CSRF auth (machine call). The
  signed event log remains the real anti-fraud guarantee.

Auto-config on assign:
- setup assign: for push-capable devices, generate Digest creds, call
  configureInputPush to write them + the push URLs to the device, store the creds
  (password not echoed back). net.ts derives the backend IP on the device's
  subnet (BACKEND_HOST_IP override).
- driver configureInputPush sets auth=2 + creds; PushConfig carries the creds.
- removed the earlier URL-token approach.

Two hard-won device-write bugs fixed in the driver:
- configApi now sets an explicit Content-Length — the device silently ignores
  chunked request bodies (Node's default without Content-Length), so every config
  write looked successful ({"status":0}) but did nothing. This was the root cause
  of the session's "writes don't apply" mystery.
- #writeConfig polls until the change is verified, retrying (the device reboots on
  apply; back-to-back writes were lost). The `pass` field caps at 31 chars, so the
  generated password is 24 hex chars.

Verified on hardware: assign auto-configures the device; all 4 inputs then push
with Digest auth, zero failures. wiki/device-input-flow updated.
2026-06-14 16:39:08 +02:00
julian 23919164ee Dingtian input HTTP-push to backend (no polling)
The device pushes button events to the backend via its Input Link URL feature;
the backend decides. No polling — the chosen entry architecture.

packages/devices:
- dingtian driver: configureInputPush() writes the device's input_link_url
  config (per-input server/port/path, en=1, active-LOW, plain HTTP) so each
  input HTTP-GETs the backend on press/release. Extracted #readConfig/#writeConfig
  (with the required command:setconfig injection + post-write reset tolerance).

apps/server:
- routes/devices.ts: public GET/POST
  /api/devices/dingtian/:deviceId/input/:n/{on,off} — translates a device push
  into an internal device event. Not behind cookie/CSRF (machine call from the
  device); trust comes from the signed event log, not this request.
- device-events.ts: internal EventEmitter bus so the entry flow subscribes to
  input events without coupling to HTTP. Wired into the server.

Verified on hardware: configured the device, then real presses on all 4 inputs
pushed to the backend (input N on+off, source = device IP). No polling.

wiki: device-input-flow concept (path + trust model for the flat/no-VLAN
network); dingtian-relay updated; index + log.
2026-06-14 15:10:50 +02:00
julian 355026dcf7 Remove UHPPOTE/ZKTeco; Dingtian is the only access driver
Neither UHPPOTE nor ZKTeco is used — the Dingtian relay controller was chosen
and verified. Remove their code and re-scope the wiki.

Code:
- delete access-uhppote.ts, uhppoted.d.ts, access.ts (zkteco/esp32-relay stubs),
  and the three uhppote-*.mjs hardware test scripts.
- remove the `uhppoted` npm dependency from @parking/devices and @parking/server.
- unregister uhppote/zkteco/esp32-relay from the driver registry; drop their
  exports. Catalog access drivers = dingtian only. Build green (5/5).
- refresh now-stale example comments (registry/interfaces/setup/api) to use
  current examples; keep the two "UHPPOTE blocker" references that explain why
  the precondition capability exists.

Wiki (kept pages, re-scoped):
- uhppote-controller, zkteco-controller -> rejected/historical with callouts;
  uhppote-vs-esp32 -> historical (detection-vs-prevention lens still useful).
- re-point all "current device" framing (standing-decisions, bom, overview,
  open-questions, device-registry, device-discovery, index) to dingtian-relay.
- transferable concepts (network-isolation, event-log-ingestion, barrier-not-a-
  door, threat-model) untouched. Raw source immutable. Links lint clean.
2026-06-14 14:28:52 +02:00
julian 4319fb86dc wiki: Dingtian relay decision, HTTP-over-MQTT, unmanned direction
- dingtian-relay: relay+input controller (4ch on hand). Inputs are decoupled
  from relays (configurable via input_link_relay) — solves the
  access-controller-button-flow blocker the UHPPOTE couldn't. Full protocol from
  the SDK (UDP string control :60001, `00` status parse, input_link_url push,
  multicast discovery). Driver + hardware test still to build.
- dingtian-vs-mqtt: use direct HTTP/UDP now; MQTT skipped (broker = extra infra
  + failure mode + overkill at one-host/few-devices scale) but kept for later
  multi-lane scale.
- autonomous-direction: record the roadmap to fully unmanned (no booth) and how
  it reshapes the threat model (operator-fraud -> unattended-machine threats),
  makes host-in-the-loop entry mandatory, and raises fail-state stakes.
- threat-model: note the unmanned shift. index + log.

gitignore the vendor SDK (dingtian/, 71MB of binaries/examples) — reference
only, protocol captured in the wiki.
2026-06-14 13:27:01 +02:00
julian dbf1fa17d7 wiki: document dev environment (WSL networking, workflow)
Capture hard-won dev knowledge that was only in commit messages:

- wsl-dev-networking: WSL2 NAT blocks UDP broadcast (device discovery can't
  reach the LAN); fix is mirrored networking (.wslconfig, Win11 22H2+), plus the
  gotchas that remained after — multiple interfaces, subnet-directed broadcast,
  localhost->IPv6 stall. Alternatives for non-mirrored setups.
- local-dev-workflow: first-time setup, pnpm dev, and the gotchas (the
  strip-types dev-server hang -> tsx, the 127.0.0.1 proxy fix, .env loading,
  seeding into the right DB).
- device-discovery: corrected the old "broadcast permission (EACCES)" note — the
  real cause was the lib not enabling SO_BROADCAST for global 255.255.255.255;
  documented the three verified broadcast gotchas + I/O serialization.
- schema: add a `reference` page type; new "Dev environment" index section; log.

Links lint clean; both new pages well-connected.
2026-06-14 13:09:35 +02:00
julian a0e0fd9118 Add device discovery (UHPPOTE LAN scan) to setup
UHPPOTE controllers self-announce via UDP broadcast, but the frontend had no way
to find them — the admin had to type the serial blind. Add a generic discovery
capability and surface it in the setup wizard.

packages/devices:
- DiscoverableDriver capability + DiscoveredDevice type + isDiscoverable() guard
  on the registry (optional, so any driver can opt in).
- uhppote driver implements discover() via uhppoted getDevices (UDP broadcast),
  mapping each controller's serial/IP/firmware into a DiscoveredDevice; extract
  shared buildCtx().

apps/server:
- GET /api/setup/discover/:driverId (admin-only): runs discover() and
  health-checks each found device so reachability shows before assigning.
- catalog now returns a `discoverable` driver-id list.

apps/web:
- SetupWizard "Scan for controllers" button for discoverable drivers; lists found
  devices with health badges; selecting one auto-fills serial + host. api client
  gains discoverDevices().

wiki: new device-discovery concept; cross-link from registry/setup/uhppote;
note the broadcast-permission (EACCES) deployment caveat; index + log.

Verified: catalog flags uhppote discoverable; discover runs and fails gracefully
without hardware; non-discoverable driver -> 400; missing token -> 401.
2026-06-14 08:21:27 +02:00
julian 72ba4099ea Device-agnostic driver registry + first-run setup
Make the device-adapter pattern selectable so the admin chooses hardware at
install — per lane, from a catalog of supported drivers. Adding a device =
registering one more driver; no business-logic change.

packages/devices:
- interfaces.ts: AccessControlDevice / ReaderDevice / CameraDevice / PrinterDevice
  (adds CameraDevice for entry/exit snapshot-on-event; access relay stays
  intent-only per "a barrier is not a door").
- registry.ts: driver catalog with per-driver config fields + factory, config
  validation, and a catalog payload for the setup UI.
- drivers/: stub adapters — access (zkteco, esp32-relay), reader (wiegand,
  tcp-ip), camera (hikvision, dahua). Real vendor protocols TBD.

packages/db:
- lane_devices + setup_state tables (migration 0001); re-export query helpers.

apps/server:
- routes/setup.ts: GET /api/setup/catalog (public schema), and admin-only
  /assign, /state, /complete with registry validation before persisting.
- extract auth.ts (requireJwtSecret, requireRole, JWT type aug).

apps/web:
- SetupWizard scaffold + api client: pick a driver per category for a lane,
  render its config fields.

wiki: device-registry + first-run-setup concept pages; cross-link from
device-adapter-pattern; index + log updated.

Verified: full turbo build (5/5); catalog lists all drivers; admin assign
persists; missing-config and no-token requests are rejected.
2026-06-14 07:59:46 +02:00
julian bfe64032d8 Initial scaffold: Turborepo monorepo + design wiki
Turborepo (pnpm workspaces) with all dependencies pinned to latest
mutually-compatible versions: turbo 2.9, TypeScript 6, Fastify 5,
React 19, Vite 8, better-sqlite3 12 + Drizzle ORM 0.45.

Layout:
- apps/server   Fastify backend (local JWT auth + role guard, /health)
- apps/web      React 19 + Vite 8 operator SPA
- packages/db   Drizzle schema on SQLite/WAL; append-only events + users
- packages/devices  reader/printer/relay adapter interfaces (intent-only relay)
- packages/shared   shared domain types

Architecture constraints from the design wiki are encoded in the scaffold:
append-only hash-chained + signed event log, device-agnostic adapters,
"a barrier is not a door" (relay expresses intent only), fully-local
offline-first auth.

wiki/ is an LLM-maintained Obsidian knowledge base (28 pages) ingested
from the architecture & design notes, with its own maintenance schema.

Verified: pnpm install, full turbo build (5/5), server boots and serves
/health, drizzle-kit generates the initial migration.
2026-06-14 00:34:11 +02:00