webhook ref-guard + sha-watch sensor pinned to ?sha=release; sensor renamed pfv_deploy_gitea_release_head; master frozen. https://projects.knownelement.com/issues/779
183 lines
7.5 KiB
Markdown
183 lines
7.5 KiB
Markdown
# 🏠 pfv-bms — the house brain
|
||
|
||
> **Datacenter-grade monitoring and access control for a private residence + server room** —
|
||
> 372 entities, ~260 sensors, and a badge-actuated door, all deployed by `git push`.
|
||
|
||

|
||

|
||

|
||

|
||

|
||
|
||
Production Home Assistant configuration for the Turnsys PFV —
|
||
environmental monitoring (temperatures, power, UPS) across a seven-node
|
||
Proxmox fleet plus single-board computers, server-room badge access
|
||
control, and tiered alerting to mobile devices.
|
||
|
||
Work tracking: [Redmine](https://projects.knownelement.com)
|
||
([#344](https://projects.knownelement.com/issues/344) umbrella) ·
|
||
Docs: [VP TechOps on Discourse](https://community.turnsys.com/c/vp-techops)
|
||
|
||
---
|
||
|
||
## 🗺️ System architecture
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
subgraph FIELD[📡 The field]
|
||
TS[🖥️ 7× Proxmox hosts<br/>lm-sensors + iDRAC extends]
|
||
BOARDS[🥧 SBCs<br/>pfvsvrpi · jetson · 3× subopi]
|
||
UPS[🔋 UPS on tsys1<br/>NUT server :3493]
|
||
PDU[🔌 Garage PDU<br/>SNMPv1 relay]
|
||
METER[⚡ Utility meter<br/>Smart Meter Texas]
|
||
READER[🪪 Badge readers<br/>pfvsvrpi · ultix-field]
|
||
end
|
||
|
||
subgraph HA[🏠 pfv-bms — this repo]
|
||
INGEST[📦 packages/plant_snmp.yaml<br/>SNMP pollers → raw sensors]
|
||
TEMPLATE[🧮 template sensors<br/>raw → °F/units + thresholds]
|
||
DOOR[🚪 packages/doorman.yaml<br/>scan ingest + decision engine]
|
||
ROSTER[🔒 roster sensors<br/>REST poll of private access-roster]
|
||
ENERGY[📊 utility_meter +<br/>Riemann-sum energy]
|
||
AUTO[🚨 automations.yaml<br/>tiered thermal + UPS alerts]
|
||
end
|
||
|
||
subgraph OUT[📲 Outcomes]
|
||
ALERT[📱 push alerts<br/>companion apps]
|
||
LOG[📓 logbook + history<br/>every scan, every degree]
|
||
DASH[🖥️ dashboards<br/>server-room + plant]
|
||
RELAY[⚡ doorctl → usbrelay<br/>→ door strike]
|
||
end
|
||
|
||
READER -->|webhook| DOOR
|
||
DOOR -->|whitelist| ROSTER
|
||
DOOR -->|armed + valid| RELAY
|
||
TS & BOARDS -->|SNMP| INGEST
|
||
UPS -->|NUT| INGEST
|
||
PDU -->|SNMP relay| INGEST
|
||
METER -->|REST| INGEST
|
||
INGEST --> TEMPLATE --> AUTO
|
||
INGEST --> ENERGY
|
||
AUTO --> ALERT
|
||
TEMPLATE & DOOR --> LOG & DASH
|
||
```
|
||
|
||
### 🛡️ Badge access lane (server-room door)
|
||
|
||
| Component | Role | Lives |
|
||
|-----------|------|-------|
|
||
| USB HID readers | 13.56 MHz badge readers (keyboard-emulating) | door + dev bench |
|
||
| [`KNEL/doorman`](https://git.knownelement.com/KNEL/doorman) | pure-bash listener, decode, webhook, doorctl | `pfvsvrpi` (prod), `ultix-field` (dev) |
|
||
| `packages/doorman.yaml` | scan ingest, whitelist decision, alerts, unlock dispatch | **this repo** |
|
||
| [`KNEL/access-roster`](https://git.knownelement.com/KNEL/access-roster) 🔒 | badge IDs + holders — **private**, two-human PR gate | gitea (private) |
|
||
| `doorctl` + usbrelay | socket-activated, token + source-IP ACL, EXIT-trap secured | `pfvsvrpi` |
|
||
|
||
Fail-closed end to end: HA unreachable → the door does not open.
|
||
Break-glass badges fire the relay locally (works with HA down) and
|
||
report `local_unlock` so HA never double-fires. Arm switch
|
||
(`input_boolean.doorman_unlock_enabled`) is founder-level control.
|
||
Verified end-to-end 2026-09-03: real armed scan → dispatch → relay.
|
||
|
||
---
|
||
|
||
## 🌡️ Environmental monitoring
|
||
|
||
Raw SNMP pollers (`packages/plant_snmp.yaml`) → unit-normalized
|
||
template sensors → tiered automations. Every host contributes
|
||
lm-sensors extends; iDRAC hosts add inlet/exhaust/CPU; NUT feeds UPS
|
||
electricals.
|
||
|
||
| Family | Coverage |
|
||
|--------|----------|
|
||
| 🖥️ Host silicon | CPU/drive/NVMe/PCH temps on 7 Proxmox hosts |
|
||
| 🌡️ Thermal zones | rack ambient, DIMM banks, GPU, tsys7 iDRAC inlet→exhaust ΔT |
|
||
| 🥧 Boards | pfvsvrpi, jetson, 3× subopi |
|
||
| 🔋 UPS | tsys1 battery charge/voltage, on-battery + runtime alerts |
|
||
| ⚡ Power | whole-house kWh (utility), tsys6/7 node watts + kWh integrals, garage PDU amps |
|
||
| 🏠 Rooms | temp/humidity/battery sensors (Govee, cloud-fed) |
|
||
|
||
### 🚨 Alert tiers (automations.yaml)
|
||
|
||
| Trigger | Threshold |
|
||
|---------|-----------|
|
||
| Drive / DIMM temps | > 140 °F |
|
||
| NVMe | > 150 °F |
|
||
| PCH | > 160 °F |
|
||
| GPU | > 185 °F |
|
||
| Ambient | > 104 °F |
|
||
| UPS | on-battery transitions + runtime |
|
||
| Watchdog | plant sensor freshness sweep every 30 min |
|
||
|
||
### 📊 Snapshot (2026-09-03)
|
||
|
||
372 entities · ~260 sensors · 45 temperature sensors · 21
|
||
electrical/energy sensors · 8 todo lists · 2-way alerting to companion
|
||
apps.
|
||
|
||
---
|
||
|
||
## 🔐 Security model (read this before forking)
|
||
|
||
- **No secrets in git, ever.** Everything sensitive is `!secret`
|
||
indirection into an on-box `secrets.yaml` (gitignored). Verified by
|
||
full-history scans.
|
||
- **Badge IDs are NOT in this repo.** The roster lives in the *private*
|
||
[`KNEL/access-roster`](https://git.knownelement.com/KNEL/access-roster)
|
||
repo (two-human PR gate) and is fetched live at runtime.
|
||
- **Fail-closed access control** — see the badge lane above.
|
||
- Battery room sensors and locks are cloud-fed; credentials also live
|
||
in `secrets.yaml`.
|
||
- **HomeKit is controller-only (founder ruling 2026-09-04).** HA pulls
|
||
*from* HomeKit accessories when one enters the fleet; HA entities are
|
||
NEVER exposed *to* Apple Home — no `homekit` bridge integration, no
|
||
outward QR pairing.
|
||
|
||
## 🚀 Deployment pipeline
|
||
|
||
**Branch model (2026-09-04):** `dev` = WIP integration — the pipeline
|
||
IGNORES it. `release` = what the box runs — pushing release IS the deploy.
|
||
`master` is frozen (pre-branch-model history). A PR process with AI +
|
||
human reviewers will gate dev → release (founder, upcoming).
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
PUSH[📝 push to dev] --> NOOP[🚫 pipeline ignores dev]
|
||
REL[🚀 merge dev → release<br/>and push] --> HOOK[gitea webhook<br/>fast path]
|
||
REL --> SHA[sha-watch<br/>release sha · 300 s poll]
|
||
HOOK & SHA --> CHECK{changed files?}
|
||
CHECK -->|configuration.yaml,<br/>packages/**| RESTART[♻️ core restart<br/>webhook triggers need it]
|
||
CHECK -->|other YAML| RELOAD[⚡ reload only<br/>~5 min to live]
|
||
```
|
||
|
||
Gate before pushing `release` (and after any `packages/**` change on
|
||
`dev`): `ha core check` must pass on-box.
|
||
Measured end-to-end: 4 m 52 s commit → live.
|
||
|
||
## 🗺️ Repo map
|
||
|
||
| Path | What |
|
||
|------|------|
|
||
| `configuration.yaml` | entrypoint + package includes |
|
||
| `packages/doorman.yaml` | 🚪 badge scan ingest + decisions |
|
||
| `packages/plant_snmp.yaml` | 🌡️ SNMP pollers + templates |
|
||
| `automations.yaml` | 🚨 tiered alerts + watchdogs |
|
||
| `dashboards/` | Lovelace dashboards |
|
||
| `tools/` | operator tooling (websocket CLI) |
|
||
| `docs/reference-packs/` | historical YAML references — NOT loaded |
|
||
|
||
## 🔗 Related
|
||
|
||
- Badge listener + relay code: [`KNEL/doorman`](https://git.knownelement.com/KNEL/doorman)
|
||
- Wider fleet (Proxmox, DNS, monitoring): [`KNEL/PFVCluster`](https://git.knownelement.com/KNEL/PFVCluster)
|
||
- Badge roster (private): `KNEL/access-roster` 🔒
|
||
|
||
## License
|
||
|
||
Copyright © 2026 Known Element Enterprises / Charles Wyble.
|
||
|
||
This program is free software: you can redistribute it and/or modify
|
||
it under the terms of the GNU Affero General Public License as
|
||
published by the Free Software Foundation, either version 3 of the
|
||
License, or (at your option) any later version. See [LICENSE](LICENSE)
|
||
for the full text.
|