Doorman — badge access modernization (KNEL/doorman)
Dedicated repo for the physical-access badge work, spun out of the
2026-09-02 founder session (“badge access via Home Assistant”):
Repo: Sign In - Gitea (public, AGPLv3)
Lineage
The 2018 system was Perl (doorman.pl, keyboard-emulating USB RFID
reader → HTTP auth portal → usbrelay door strike). It lived unlisted in
KNEL/LegacyTechops doorman/;
the repo carries it verbatim under legacy/ for reference. The defunct
doors.pfv.turnsys.net auth portal is NOT replicated — plain-HTTP auth
is gone for good.
Modern shape (non-HA side shipped; HA side owned by the pfv-bms session)
bin/doorman.sh — bash + coreutils ONLY (no CPAN, no package installs
on the reader Pi). Reads /dev/input/by-id/*-event-kbd with od(1),
decodes MSC_SCAN HID usages with the 2018 state machine bit-for-bit
(incl. its quirks: usages 0-9 passthrough, bad-usage parity anchor
behavior), 24-byte (64-bit) and 16-byte (32-bit Pi) record widths.
- Each completed scan →
POST {badge_id, reader, ts} to the HA webhook
(contract in the repo README). Fail-closed: HA down ⇒ scan logged,
door does NOT open.
- Auth whitelist, logging, unknown-badge alerts, unlock actuation: all
Home Assistant side (#345 sketch, 2026-09-02 note).
- Optional legacy relay bridge (
usbrelay) behind
DOORMAN_UNLOCK_ON_2XX, default off, pending the actuator decision.
deploy/doorman.service + /etc/default/doorman env refs (token in
URL path = secret; material stays out of git).
- Targets: pfvsvrpi (prod reader) / ultix-field (dev reader,
hands-on in San Antonio).
Verification (2026-09-02, #355 scope — laptop, offline)
- Decoder suite: 9/9 green (synthetic event fixtures incl. repeated
digits, garbage usages, truncated tails, both record widths).
- shellcheck: 7/7 scripts clean, zero warnings incl. info level.
- Full rule audit: PASS (enforcement scripts adopted from
TSYSGroupAIOS/PFVCluster).
Tickets
- #345 — umbrella: physical access control, server room door badge
- #355 — code & testing (this repo’s active work)
- #356 — deployment & onsite testing (after #355; serial, health-gated:
ultix-field first, verify, then pfvsvrpi)
Open items
Reader model/format, actuator path (usbrelay vs new e-lock), whitelist
home — tracked in PFVCluster questions-v4.md (founder answers gate
#356, not the listener code).
Update 2026-09-02 — two founder rulings recorded:
1. Scope containment: badge access is fully contained to the
server-room door. No other smart locks in the house are in scope —
any such work is a separate ticket for founder approval. Now written
into KNEL/doorman AGENTS.md (c4f1015) and Redmine #345.
2. How HA manages valid badges — git YAML roster (recommended and
recorded): the roster lives as a git-tracked YAML mapping in the
KNEL/pfv-bms HA config (deployed by the existing git_pull pipeline),
not a UI-only helper:
- webhook-triggered automation receives
{badge_id, reader, ts}
- automation-level
variables: carries the roster:
badge_id → identity, seeded with the two 2018 badges
- membership condition → log + unlock path; default → unknown-badge
alert; night schedule = alert-only
- every grant/revoke = commit (+ ticket ref) = audit trail, matching
the IaC mandate (#454) and the “user makes policy, agent implements”
rule
input_text UI helper rejected as primary store (drift, no audit
trail); fine later as a read-only dashboard mirror
The listener stays dumb on purpose: the reader host holds no
whitelist, so a compromised Pi cannot open anything by itself.
Update 2026-09-02 #2 — PR gate live + hardware confirmed:
Two-approver PR process is ENFORCED on
KNEL/doorman: main is
PR-only, merges require 2 human approvals (server-side verified —
direct pushes are rejected), rejected reviews block, stale approvals
dismiss. A PR sign-off template (.gitea/pull_request_template.md)
carries the ticket ref, access-control checklist, verification box,
and named Approver 1/2 lines. Agents author; only humans approve and
merge. Named approvers: pending founder (questions-v5 Q4).
Reader hardware probed (founder cleared): both hosts run the exact
2018 reader — 13ba:0018 PCPlay Barcode PCP-BCG4209 keyboard-emulating
HID. pfvsvrpi (prod): aarch64, reader on event0, 2018 usbrelay dongle
(16c0:05df) present and accounted for. ultix-field (dev): x86_64,
reader on event15, scan-only. Both 64-bit → 24-byte event records,
matching the shipped decoder defaults. usbrelay userspace tool not yet
installed on pfvsvrpi — ticket-gated deploy item for #356.
Update 2026-09-02 #3 — deployed on both hosts + roster repo born:
- Stage-1 listener is LIVE on pfvsvrpi
(prod, /dev/input/event0) and ultix-field (dev, event15): services
active, reader streams open, decoder suite 9/9 on-host on both
(incl. aarch64). Log-only until the HA webhook flips on (env line
commented — no redeploy needed). Founder scan UAT: journalctl -u doorman -f on ultix-field.
- Gate re-scope (founder): code + host work = no gates, fast
movement; the two-human gate belongs to badge IDs only.
- KNEL/access-roster (private) is that gate, materialized:
PR-only + two human approvals enforced; roster/badges.yaml is the
single home of badge IDs (Charles NW seeded enabled, Josef C
disabled pending re-enrollment PR). Fail-safe: no second human =
no roster merges, by design.
- HA consumption wiring (submodule of pfv-bms via git_pull vs sync
step): pfv-bms session’s call; contract unchanged.