diff --git a/persistent-memory.md b/persistent-memory.md index c13acf5..f1b597e 100644 --- a/persistent-memory.md +++ b/persistent-memory.md @@ -174,6 +174,18 @@ _As of 2026-09-27 ~0900 PT._ `01M3HXMXN27F3K534Q6QS45AHV`). Acceptance 144/144; the one shared-vs-direct miss was a bf16 tie that flipped across a plain restart, so "deterministic" holds within a process only. +### Zigbee2MQTT on esh-docker-vm (2026-09-27, Prime go-ahead; ha-dev request) + +- **LIVE since 1240:** Z2M 2.14.1 (digest-pinned) on `http://10.0.50.45:8099` (auth token), radio + SLZB-MR1U chip 0 `tcp://10.0.90.10:6638` (ember), channel 25, **PAN 0xCFF4**. State and the network + key are in `/opt/docker/data/zigbee2mqtt` (root 0700, restic via /opt/docker, never in git). Vault: + `esh-docker-vm/zigbee2mqtt-{network-key,frontend-token,mqtt-password}`. Broker user `zigbee2mqtt` + added to mosquitto (passwd backup `.bak-20260927-z2m`). +- **Next is ha-dev's:** confirm the bridge device in HA, delete the ZHA entry, and pair the Aqara T1. +- ⚠ The first credential-mint attempt was blocked by the auto-mode classifier on a peer-relayed + approval. It went ahead only on Prime's own go-ahead in this session. Treat that as correct. + → `stacks/zigbee2mqtt/README.md` + ### restic: credential leak fixed (2026-09-27, Prime) - Seven hosts were moved from `env-file` to `repository-file` (`playbooks/restic-repository-file.yaml`), @@ -228,6 +240,7 @@ _As of 2026-09-27 ~0900 PT._ - `[2026-09-27]` **hermes-gateway restarted 0401 for highseat-dev** (SVOS v2.1.12: `propose_decision` gained `seat_up`, and Hermes reads the plugin only at start). The plugin load was verified at file level; the end-to-end proof is Miranda's first seat_up card. Enabling `zellij-fleet@Claude` at boot remains Prime's call. - `[2026-09-27]` **SemIf LIVE on fv-ml1 GPU 1 (semif-serve 0.1.2, Prime):** wrapper + contract + 39 tests, 142/144 upstream parity, two card-only memory defects fixed. → `persistent-memory.d/2026-09-27-semif-live-on-fv-ml1-gpu1.md` +- `[2026-09-27]` **Zigbee2MQTT live on esh-docker-vm :8099 (PAN 0xCFF4, ch 25) replacing ZHA; network key vaulted, data host-only.** Prime go-ahead in this session; a peer-relayed approval was blocked by the permission gate. → `stacks/zigbee2mqtt/README.md` - `[2026-09-27]` **semif-serve 0.1.4: object states ending in `)`, `;` or `}` no longer 422 (INV-7, a prefix wrapper proven at startup); numerics are deterministic within a process but a bf16 near-tie can flip across a restart.** Prime ruled; heid bug hunt folded. → `stacks/semif/README.md` - `[2026-09-27]` **SemIf as Cicada's mood source: slower (+32 ms async, +94 ms sequential) and worse (67% vs 92% apt; carry 7/15 vs 14/15); only the gesture restraint is a win.** Build nothing (Prime). Henge 88 carries it. → `persistent-memory.d/2026-09-27-semif-consumer-fit-spikes.md` - `[2026-09-27]` **SemIf consumer-fit spikes (Prime): Cicada affect gate 30/31 with descriptive wording and 19/31 terse; Wyrd "left this place?" 21/21 on the second wording, exit choice 18/21.** Recommendations await Prime. → `persistent-memory.d/2026-09-27-semif-consumer-fit-spikes.md` diff --git a/servers/esh-docker-vm/README.md b/servers/esh-docker-vm/README.md index 459ae5f..20dbd49 100644 --- a/servers/esh-docker-vm/README.md +++ b/servers/esh-docker-vm/README.md @@ -44,6 +44,7 @@ General-purpose Docker host at the **ESH home-lab site** (`esteban.net` / `10.0. | homeassistant | macvlan `10.0.50.46:8123` | Home automation (direct LAN IP via macvlan on `ens18`) | | esphome | host net / 6052 | ESPHome firmware dashboard | | mosquitto | 1883 | MQTT broker | +| zigbee2mqtt | 8099 | Zigbee stack (SLZB-MR1U radio at `10.0.90.10:6638`); replaces HA's ZHA since 2026-09-27. State + network key in `/opt/docker/data/zigbee2mqtt` (root 0700), never in git. See `stacks/zigbee2mqtt/README.md` | | calibre-web-automated | 8083 | All-in-one ebook library + web UI; replaced `calibre` + `calibre-web` pair on 2026-04-20 | | paperless-ngx (+ redis broker + volume-backup sidecar) | 8200 | Document archive; Postgres on `10.0.50.60:5432` | | pgadmin (+ volume-backup sidecar) | 5050 | Postgres admin UI | diff --git a/stacks/zigbee2mqtt/.env.example b/stacks/zigbee2mqtt/.env.example new file mode 100644 index 0000000..6bb613f --- /dev/null +++ b/stacks/zigbee2mqtt/.env.example @@ -0,0 +1,4 @@ +# zigbee2mqtt — copy to /opt/docker/compose/zigbee2mqtt/.env on esh-docker-vm. +# No secrets here: they live in /opt/docker/data/zigbee2mqtt/secret.yaml (root 0600) and the vault. +IMAGE=koenkk/zigbee2mqtt:2.14.1@sha256:fef0de769dcd04c27b3a6d277b61046eb96284bdd4198dcb1687c3a01b3020f3 +FRONTEND_PORT=8099 diff --git a/stacks/zigbee2mqtt/README.md b/stacks/zigbee2mqtt/README.md new file mode 100644 index 0000000..2f0b19c --- /dev/null +++ b/stacks/zigbee2mqtt/README.md @@ -0,0 +1,67 @@ +# zigbee2mqtt + +The **house Zigbee stack** on esh-docker-vm, replacing Home Assistant's ZHA. It was +requested by ha-dev and approved by Prime on 2026-09-27. It started greenfield: the +Zigbee network had no devices, so nothing was migrated. + +| | | +|---|---| +| **Frontend** | `http://10.0.50.45:8099`, protected by an auth token (vault `esh-docker-vm/zigbee2mqtt-frontend-token`) | +| **Image** | `koenkk/zigbee2mqtt:2.14.1@sha256:fef0de76…` (`.env` `IMAGE`, digest-pinned) | +| **Radio** | SLZB-MR1U `10.0.90.10` (VLAN 90), chip 0 = EFR32MG21 / EmberZNet 8.0.2, `tcp://10.0.90.10:6638`, adapter `ember`. Chip 1 (CC2652P7, :6639) is unused. | +| **Network** | channel 25, PAN ID **0xCFF4** (53236), extended PAN ID `0xd0cbe1735919b497`, coordinator IEEE `0x187a3efffe99a487`. Formed fresh on 2026-09-27 (the EFR32 left the abandoned ZHA network: channel 25, PAN 0xF09F). | +| **MQTT** | `mqtt://mosquitto:1883` over `traefik-net`, as broker user `zigbee2mqtt` (vault `esh-docker-vm/zigbee2mqtt-mqtt-password`). Base topic `zigbee2mqtt`, HA discovery on `homeassistant/`. | +| **State** | `/opt/docker/data/zigbee2mqtt` (root 0700): `configuration.yaml`, `secret.yaml`, `coordinator_backup.json`, `database.db`. Backed up by the host's restic (`/opt/docker`). | + +## ⚠ The network key + +`secret.yaml` holds the Zigbee **network key**, which encrypts the whole mesh. The same +key is in the vault as `esh-docker-vm/zigbee2mqtt-network-key`. If it is lost, every +device must be re-paired. It must never be in git. That is why this repo holds only +`compose.yaml`, `.env.example` and this README; the data dir is host-only and +`deploy-stack.sh` never touches it. `configuration.yaml` refers to the secrets as +`'!secret.yaml '`. + +## Notes + +- **One client per radio socket.** HA's ZHA must stay disabled or deleted while Z2M + owns 6638. +- **Configured by IP.** Containers here cannot resolve `*.internal`. The SLZB's mDNS + TXT record advertises `radio_type znp` for 6638, which is wrong: 6638 speaks EZSP/Ember. +- **Pairing is timed.** Z2M 2.x has no `permit_join` setting. Joining is opened from + the frontend or over MQTT, and closes on a timer. +- The broker user was added with `mosquitto_passwd` + `SIGHUP`; the password file's + owner and mode are unchanged (1883, 0600). The broker still has + `allow_anonymous true`, a pre-existing setting that is not this stack's to change. + +## Acceptance (2026-09-27, first start at 1240) + +- Z2M 2.14.1 on EmberZNet 8.0.2 (EZSP 14). herdsman reported "Adapter network does not match + config. Leaving network...", found no backup, and formed a new network on channel 25: + PAN 0xCFF4. It wrote `coordinator_backup.json`. +- `zigbee2mqtt/bridge/state` = `{"state":"online"}`, and the bridge's 8 HA discovery configs are + retained under `homeassistant/+/1221051039810110150109113116116_0x187a3efffe99a487/…`. Whether + the device shows up in HA is ha-dev's to confirm. +- On startup Z2M migrated the settings to `version: 5`. All three `!secret.yaml` references + survived the rewrite, so no key is in plaintext in `configuration.yaml`. The pre-migration file + is kept as `configuration_backup_v4.yaml`. +- Frontend: HTTP 200. The websocket accepts the vault token, and closes with `4401 Unauthorized` + on a wrong token or no token. +- Broker: the new user is accepted, a wrong password is refused, and the reload disconnected no + one. Afterwards 3 clients were connected (HA, the `denspots` Tasmota, Z2M). The Tasmotas + `consoletree`, `fireplacetree` and `mantlelights` show LWT `Offline`, but they have not connected + at any point in the broker's current log (back to 2026-09-09), so that predates this change. + +## Deploy + +```bash +scripts/deploy-stack.sh esh-docker-vm zigbee2mqtt --compose +# on the host, first time only: +# /opt/docker/compose/zigbee2mqtt is lkraven:docker 2755, so infra-ops cannot `cp` into it +cd /opt/docker/compose/zigbee2mqtt && sudo -n install -o lkraven -g docker -m 644 .env.example .env \ + && docker compose config -q && docker compose up -d +``` + +To upgrade, change `IMAGE` in the host `.env` to a new `tag@digest`, then run +`docker compose up -d`. Read the release notes first: an adapter or firmware change can +require re-pairing. diff --git a/stacks/zigbee2mqtt/compose.yaml b/stacks/zigbee2mqtt/compose.yaml new file mode 100644 index 0000000..dbbb790 --- /dev/null +++ b/stacks/zigbee2mqtt/compose.yaml @@ -0,0 +1,48 @@ +# zigbee2mqtt: the house Zigbee stack on esh-docker-vm, replacing Home Assistant's ZHA. +# Requested by ha-dev, operator-approved (Prime, 2026-09-27). Greenfield: no devices migrated. +# +# Radio: SLZB-MR1U at 10.0.90.10 (VLAN 90), chip 0 = EFR32MG21 / EmberZNet 8.0.2 on TCP 6638, +# so adapter `ember`. Chip 1 (CC2652P7, :6639) is unused. By IP on purpose: containers here +# cannot resolve *.internal, and the SLZB's mDNS TXT wrongly advertises radio_type znp for 6638. +# ONE client per radio socket: HA's ZHA entry must stay disabled/deleted while this runs. +# +# ⚠ STATE, and the Zigbee NETWORK KEY, live in /opt/docker/data/zigbee2mqtt on the host (root +# 0700): configuration.yaml, secret.yaml (network key, MQTT password, frontend token), +# coordinator_backup.json, database.db. Covered by the host's restic (/opt/docker). NEVER in +# git: this repo holds only this file, .env.example and the README. Losing the network key +# means re-pairing every device, so the vault holds a copy (see README). +# +# .env (tunables): IMAGE, FRONTEND_PORT. + +name: zigbee2mqtt + +services: + zigbee2mqtt: + image: ${IMAGE:?set IMAGE} + container_name: zigbee2mqtt + restart: unless-stopped + environment: + TZ: America/Los_Angeles + volumes: + - /opt/docker/data/zigbee2mqtt:/app/data + ports: + - "${FRONTEND_PORT:-8099}:8080" + networks: + - tnet # reaches the broker as mqtt://mosquitto:1883 + labels: + - homepage.group=Apps + - homepage.name=Zigbee2MQTT + - homepage.icon=si-zigbee2mqtt + - homepage.description=Zigbee stack (SLZB-MR1U, channel 25) + - homepage.href=http://10.0.50.45:${FRONTEND_PORT:-8099} + healthcheck: + test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8080"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + +networks: + tnet: + name: traefik-net + external: true