From c90406cca714855aebd2f02db771978ec479ad85 Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Fri, 28 Aug 2026 06:59:04 +0200 Subject: [PATCH] =?UTF-8?q?docs(apps):=20trim=20otbr,=20trmnl,=20zigbee2mq?= =?UTF-8?q?tt=20=E2=80=94=20and=20correct=20a=20copy-pasted=20falsehood?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Comments only across otbr, trmnl/renderer, zigbee2mqtt-deployment and the smartctl ServiceMonitor. All four still parse; monitoring-rules still renders. Each keeps the instruction and loses the investigation. otbr stays disabled with the condition for re-enabling it — verified partition merge, one OMR prefix, the Nest routers visible — instead of the paragraph reconstructing how the split partition was diagnosed. Its socat bridge keeps both dead ends, because they are what stops someone simplifying it: forkpty needs a socat the image does not ship, and a sidecar cannot supply the PTY because devpts is per-container. zigbee2mqtt keeps "never run alongside ZHA, and never start without the restored coordinator backup or you form a new network and drop every device", and loses the cutover diary around it. The ServiceMonitor correction is the useful part. Four monitors carry the claim that kube-prometheus-stack's Prometheus "selects ServiceMonitors by this label; without it the resource exists but is never discovered". The live resource has serviceMonitorSelector {} and podMonitorSelector {} — empty, so every monitor is picked up regardless of labels. Only ruleSelector matches on release=kube-prometheus-stack, which is why the same sentence on the disk-wear PrometheusRule is correct and this one is not. Corrected here for the monitor I added yesterday, which is where I introduced it by copying a neighbour. The other three carry the same wrong sentence and are left for a pass that can check each rather than sed across them. --- .../zigbee2mqtt-deployment.yaml | 21 +++----- .../smartctl-exporter-servicemonitor.yaml | 5 +- kubernetes/apps/otbr/deployment.yaml | 52 ++++++------------- kubernetes/apps/trmnl/renderer.yaml | 21 +++----- 4 files changed, 34 insertions(+), 65 deletions(-) diff --git a/kubernetes/apps/home-assistant/zigbee2mqtt-deployment.yaml b/kubernetes/apps/home-assistant/zigbee2mqtt-deployment.yaml index f3271641..e8becba0 100644 --- a/kubernetes/apps/home-assistant/zigbee2mqtt-deployment.yaml +++ b/kubernetes/apps/home-assistant/zigbee2mqtt-deployment.yaml @@ -6,12 +6,9 @@ metadata: labels: app.kubernetes.io/name: zigbee2mqtt spec: - # CUTOVER DONE (2026-07-23): ZHA disabled, the coordinator-backup Secret is - # populated in OpenBao, and Z2M is now the live Zigbee stack — so replicas is - # 1. It shipped at 0 as a safety interlock (Z2M contends with ZHA for the - # coordinator's single TCP session, and starting without the restored backup - # would form a NEW network and drop every device); that interlock is spent - # once the migration is committed. To roll back, set 0 and re-enable ZHA. + # Z2M is the live Zigbee stack. Never run it alongside ZHA: they contend for + # the coordinator's single TCP session, and a Z2M start without the restored + # coordinator backup forms a NEW network and drops every paired device. replicas: 1 # RWO PVC → single writer; never run two pods against the same /app/data. strategy: @@ -39,14 +36,10 @@ spec: # network_key, the latter extracted from the OpenBao coordinator backup # as a natural-byte-order int array. - name: seed-data - # alpine + jq, built by .woodpecker/build-ci-images.yml from - # ci/images/alpine-jq/. Replaces `apk add --no-cache jq` on every pod - # start: tools belong in the image, not installed at runtime. - # Referenced by tag rather than digest — these are our own images. - # DATED tag, not :latest, matching the ci-ansible:2026-04-29 precedent - # in kubernetes/infrastructure/netbird-backup/cronjob.yaml. With - # :latest, rebuilding this base for some unrelated consumer would - # silently change what this init container runs on its next pod + # Our own alpine + jq image, built by build-ci-images.yml. DATED tag, + # never :latest — rebuilding this base for an unrelated consumer + # would otherwise silently change what this init container runs on + # its next pod # restart, with no diff here to review — and this is the container that # renders the Zigbee network key. Bumping the tag is a deliberate, # reviewable act; the cost is remembering to bump it. diff --git a/kubernetes/apps/monitoring-rules/smartctl-exporter-servicemonitor.yaml b/kubernetes/apps/monitoring-rules/smartctl-exporter-servicemonitor.yaml index 50df3f64..67adf8bf 100644 --- a/kubernetes/apps/monitoring-rules/smartctl-exporter-servicemonitor.yaml +++ b/kubernetes/apps/monitoring-rules/smartctl-exporter-servicemonitor.yaml @@ -5,9 +5,8 @@ metadata: namespace: monitoring labels: app.kubernetes.io/name: smartctl-exporter - # kube-prometheus-stack's Prometheus resource selects ServiceMonitors - # by this label; without it the resource exists but is never - # discovered. + # Not required for discovery — serviceMonitorSelector is empty — but kept + # for consistency with the other monitors. release: kube-prometheus-stack spec: selector: diff --git a/kubernetes/apps/otbr/deployment.yaml b/kubernetes/apps/otbr/deployment.yaml index 27eda3b1..05013347 100644 --- a/kubernetes/apps/otbr/deployment.yaml +++ b/kubernetes/apps/otbr/deployment.yaml @@ -5,25 +5,15 @@ metadata: labels: app.kubernetes.io/name: otbr spec: - # Disabled 2026-07-11 (replicas: 0) after otbr broke Matter. + # DISABLED (replicas: 0). Running otbr broke Matter: its radio is RF-isolated + # enough from the Nest mesh that it forms a rival partition of the same + # network, and the two bridge only over Ethernet — fast enough for a ping, + # too slow for the fragmented CASE handshake, so devices stay discoverable + # while every commissioning attempt times out. # - # otbr is seeded with the Nest network's dataset so it should join the - # existing partition as an additive co-BR. In practice it does not merge - # cleanly: its SLZB-06 radio is RF-isolated enough from the Nest mesh that - # otbr elects itself leader of a rival partition (or the network re-addresses - # under a new OMR prefix, observed fd5f -> fdd5). The two same-network - # partitions then bridge only over the Ethernet backbone, which carries a - # ping but is too slow for the large fragmented CASE handshake -- every - # Matter device stays mDNS-discoverable while CASE times out. Removing otbr - # re-merges the mesh onto the Nest hubs and Matter recovers. - # - # This is NOT a diagnosis of a specific config bug: the DHCP/sbr routing - # changes (19a4482, df61407) were investigated and matter-server's IPv6 path - # to the mesh works (reaches devices, ping ok); sbr's v6 table 100 is an - # inert orphan and otbr's eth0 default is outranked by net1's pref-high RA - # default. The instability is at the Thread partition layer. Re-enable only - # after the co-BR integration is verified to MERGE (one partition, one OMR, - # otbr's router table shows the Nest routers) instead of split. + # The instability is at the Thread partition layer, not in the config below. + # Re-enable only once co-BR integration is verified to MERGE: one partition, + # one OMR prefix, and the Nest routers visible in otbr's router table. replicas: 0 strategy: type: Recreate @@ -46,25 +36,17 @@ spec: # (slzb-thread.renner.uno). Dropping the workload-class=pinned # toleration lets the edge taint keep it off the tiny edge box. containers: - # The Thread RCP lives on a SLZB-06 over Ethernet at - # slzb-thread.renner.uno:6638 (Serial-over-IP). otbr-agent's - # spinel transport supports `spinel+hdlc+forkpty://?` - # which forks an exec whose stdio becomes the spinel-HDLC - # transport, but the upstream openthread/otbr image does NOT - # ship socat (or any TCP-stream tool), so that URL form ends in - # silent ENOENT and otbr-agent dies right after logging the - # radio URL. + # The Thread RCP is a network-attached radio (Serial-over-IP), so the + # spinel transport needs a TCP-to-PTY bridge. Two obvious approaches + # do not work and should not be retried: # - # A sidecar socat won't work either: each container has its own - # devpts namespace, so a PTY (e.g. /dev/pts/0) created in a - # sibling container is invisible from this container — a shared - # emptyDir symlink to /dev/pts/0 is dangling. + # - `spinel+hdlc+forkpty://` needs socat, which the upstream image + # does not ship; the URL fails with a silent ENOENT and otbr-agent + # dies right after logging it. + # - A sidecar cannot provide it either: devpts is per-container, so + # a PTY created next door is invisible here and the symlink dangles. # - # The bridge has to live in the otbr container's own namespace. - # Install socat at startup via apt (Ubuntu 18.04 base, no egress - # restrictions on this namespace), run it in the background as - # the spinel-HDLC TCP↔PTY bridge, then exec the upstream - # entrypoint with RADIO_URL pointing at the PTY symlink. + # Hence socat is installed at startup and run inside this container. - name: otbr image: openthread/otbr:latest command: diff --git a/kubernetes/apps/trmnl/renderer.yaml b/kubernetes/apps/trmnl/renderer.yaml index a0aff539..a29baee5 100644 --- a/kubernetes/apps/trmnl/renderer.yaml +++ b/kubernetes/apps/trmnl/renderer.yaml @@ -1,20 +1,15 @@ # Headless renderer: screenshots the Home Assistant e-ink dashboard, # dithers it to 16 greys and hands it to Terminus. # -# Upstream ships this as a Home Assistant add-on. Our HA is a plain -# container with no Supervisor, so it runs standalone here instead — -# viable because the app prefers HOME_ASSISTANT_URL/ACCESS_TOKEN env -# vars over /data/options.json and authenticates with an ordinary -# long-lived token. SUPERVISOR_TOKEN is deliberately NOT set: it only -# flips an internal isAddOn flag, and setting it would switch the app -# to add-on behaviour (fixed port, /data assumptions). +# Upstream ships this as a Home Assistant add-on; it runs standalone here +# because the app prefers the env vars over /data/options.json. Do NOT set +# SUPERVISOR_TOKEN — it flips an internal isAddOn flag and switches the app to +# add-on behaviour (fixed port, /data assumptions). # -# DIRECTION OF TRAVEL, which is the non-obvious part: with the -# byos-hanami webhook in `uri` delivery mode the renderer does NOT -# push image bytes. It POSTs a URL pointing at its own /output/ -# endpoint, and TERMINUS FETCHES THE IMAGE BACK from it. That is why -# both live in this namespace — the pull is intra-namespace and is -# already permitted by the default-deny-ingress rule. +# DIRECTION OF TRAVEL, the non-obvious part: in `uri` delivery mode the +# renderer does not push image bytes. It POSTs a URL to its own /output/ +# and Terminus fetches the image back. Both therefore live in this namespace, +# where that pull is already permitted. apiVersion: v1 kind: ConfigMap metadata: