mrcharles 1613dd5332 fix(doorman): stdbuf cannot exec shell functions — buffer inside decode_stream [#355]
Deployment rehearsal on ultix-field 2026-09-02 caught it live: the
listener showed active but decode_stream never ran (stdbuf exec's
binaries only). Line-buffering moved inside decode_stream; the od
stage keeps its own stdbuf.

Rehearsal note: https://projects.knownelement.com/issues/355
2026-09-02 21:45:44 -05:00

doorman — badge access, modernized

Physical access control for the server-room door: badge-reader listener for the reader host, dispatching scans to Home Assistant, which owns the whitelist, logging, alerts, and unlock decision.

Dedicated docs topic: Discourse t/318 · Tracking: Redmine #345 (umbrella) → #355 (code & testing) → #356 (deployment) · Ops status: Discourse t/297 · Fleet context: KNEL/PFVCluster

Lineage

Successor of the 2018 Perl doorman (KNEL/LegacyTechops doorman/), carried verbatim under legacy/. Same reader class (keyboard-emulating USB RFID), same decode semantics — but the defunct plain-HTTP auth portal (doors.pfv.turnsys.net) is NOT replicated: HA is the single authentication brain.

Architecture

USB RFID reader (HID keyboard)
  └─> /dev/input/by-id/*-event-kbd        (reader host: pfvsvrpi prod,
        |                                  ultix-field dev)
        v
   bin/doorman.sh                     od(1) + awk(1) decode — bash and
        |                             coreutils ONLY, no package installs
        v
   POST {badge_id, reader, ts} ----->  Home Assistant webhook
                                          | whitelist decision
                                          | logbook/recorder history
                                          | unknown badge -> instant alert
                                          '- unlock (e-lock or relay)

Fail-closed by construction: if HA is unreachable, the scan is logged and dropped — the door does not open. The optional local relay bridge (usbrelay, 2018 lineage) exists behind DOORMAN_UNLOCK_ON_2XX (default off) for the transition period; native HA webhooks always answer 200, so that flag must stay off until HA returns real accept/deny codes.

Webhook contract (for the HA side)

POST <DOORMAN_WEBHOOK_URL> with JSON:

{"badge_id": "0009399422", "reader": "pfvsvrpi", "ts": "2026-09-02T19:12:44-0500"}
  • Values are digits + env-provided strings; the JSON cannot carry user-controlled quotes/backslashes.
  • Any 2xx satisfies the listener; HA automations do the real work.
  • Configure the HA webhook trigger to this URL; the token lives in the URL path — treat it as a secret, keep it in /etc/default/doorman (0600) on the reader host, never in git.

Deploy (reader host)

sudo mkdir -p /opt/doorman
sudo cp bin/doorman.sh /opt/doorman/bin/          # + this repo, or rsync
sudo cp deploy/doorman.service /etc/systemd/system/
sudo cp .env.example /etc/default/doorman         # fill in, chown root:root
sudo chmod 600 /etc/default/doorman
sudo systemctl daemon-reload && sudo systemctl enable --now doorman

Record width auto-detects (24 B on 64-bit kernels, 16 B on 32-bit Pis). Logs go to syslog/journald (-t doorman); DOORMAN_DEBUG=true mirrors to stderr. Deployment discipline: ultix-field (dev) first, verify end-to-end, then pfvsvrpi (prod) — never both at once (#356).

Testing (no hardware needed)

bash scripts/test.sh            # decoder suite: synthetic event fixtures
bash tests/shellcheck.sh        # lint gate (docker, zero-warning)
bash scripts/check-rules.sh     # full rule audit

--selftest mode decodes a fixture file and prints one badge ID per line — that is the entire test surface of the decoder, including the 2018 quirks we intentionally preserve (usage passthrough 0-9, bad-usage parity-anchor behavior, empty-scan suppression).

Repo map

Path What
bin/doorman.sh The listener daemon
deploy/doorman.service systemd unit (/etc/default/doorman env file)
tests/run-tests.sh Offline decoder suite (9 tests)
legacy/ Verbatim 2018 Perl snapshot (not maintained)
scripts/ Rule engine + hooks (adopted from TSYSGroupAIOS)
questions-v1.md Points to the active round (PFVCluster questions-v4.md)
S
Description
Physical access control: badge-reader listener + tooling; modern successor of LegacyTechops doorman (2018 Perl). Scans -> Home Assistant webhook. Tickets #345/#355/#356.
Readme AGPL-3.0
124 KiB
Languages
Shell 85.9%
Perl 14.1%