From dba936b736f9633a259aa63dedb493d3da4ef226 Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Sun, 8 Nov 2026 10:32:31 +0100 Subject: [PATCH] feat(otbr): run an own Thread network on the SLZB-06P7 otbr comes back with its own network instead of joining the Nest's NEST-PAN-339C, so the Nest Hub can leave the IoT VLAN (S28-46/S28-48). The bootstrap Job forms it (dataset init new, channel 15, name Suedstr28-Thread) on a fresh otbr-thread PVC; the Nest dataset ExternalSecret is dropped and the Flux Kustomization un-suspended. Co-Authored-By: Claude Opus 5.5 --- kubernetes/apps/otbr/dataset-bootstrap.yaml | 48 +++++++++++-------- kubernetes/apps/otbr/deployment.yaml | 17 +++---- kubernetes/apps/otbr/externalsecret.yaml | 15 ------ kubernetes/apps/otbr/kustomization.yaml | 1 - kubernetes/apps/otbr/pvc.yaml | 2 +- .../clusters/homelab/home-assistant.yaml | 2 - 6 files changed, 34 insertions(+), 51 deletions(-) delete mode 100644 kubernetes/apps/otbr/externalsecret.yaml diff --git a/kubernetes/apps/otbr/dataset-bootstrap.yaml b/kubernetes/apps/otbr/dataset-bootstrap.yaml index 8578fd02..25964ef7 100644 --- a/kubernetes/apps/otbr/dataset-bootstrap.yaml +++ b/kubernetes/apps/otbr/dataset-bootstrap.yaml @@ -1,14 +1,18 @@ --- -# One-shot seed Job. Idempotent — exits 0 immediately if the OTBR pod -# already has an active dataset configured. On a clean install (PVC -# empty, otbr-agent loaded with --auto-attach=0 so it boots into the -# `disabled` state without crashing), it runs the proven bring-up -# sequence: +# One-shot Job that forms otbr's own Thread network. Idempotent — exits 0 +# immediately if the OTBR pod already has an active dataset. On a clean +# install (PVC empty, otbr-agent loaded with --auto-attach=0 so it boots +# into the `disabled` state without crashing), it runs: # 1. ot-ctl ifconfig up (enables PHY on the radio) -# 2. ot-ctl dataset set active (commits dataset; SetMacKey -# now succeeds because the radio is enabled) -# 3. ot-ctl thread start (MLE attaches; state goes detached → child -# → router → leader) +# 2. ot-ctl dataset init new, channel, networkname, commit active +# (random network key, PAN ID, extended PAN ID and mesh-local +# prefix; SetMacKey succeeds because the radio is enabled) +# 3. ot-ctl thread start (MLE attaches; state goes detached → leader) +# +# Channel 15 sits between Wi-Fi channels 1 and 6, clear of the 2.4 GHz +# APs (channels 1, 6, 11) and of Zigbee on channel 25. The dataset, +# including the network key, lives only on the otbr-thread PVC and in +# Home Assistant's thread.datasets (read over the OTBR REST API). # # This sequence is the workaround for SLZB-06P7 firmware behavior where # `dataset set active` triggers an immediate KeyManager push that fails @@ -76,11 +80,10 @@ spec: # (mismatched skew can break exec streaming). image: docker.io/alpine/kubectl:1.37.2@sha256:5e2de75d37e811ba6eb92e06890b1b3263eae4f0a1c17b49269edec589b1352a env: - - name: DATASET_TLV - valueFrom: - secretKeyRef: - name: otbr-active-dataset - key: active-dataset.tlv + - name: THREAD_CHANNEL + value: "15" + - name: THREAD_NETWORK_NAME + value: "Suedstr28-Thread" command: - sh - -c @@ -89,8 +92,8 @@ spec: NS=home-assistant SEL=app.kubernetes.io/name=otbr - # otbr is disabled (replicas: 0, see deployment.yaml). With - # the force annotation, every image bump re-creates this Job; + # With otbr scaled to 0 there is no pod to seed. With the + # force annotation, every image bump re-creates this Job; # exit 0 instead of waiting for a pod that never comes. replicas=$(kubectl -n "$NS" get deployment otbr \ -o jsonpath='{.spec.replicas}') @@ -132,16 +135,19 @@ spec: exit 0 fi - echo "no active dataset; seeding..." + echo "no active dataset; forming ${THREAD_NETWORK_NAME} on channel ${THREAD_CHANNEL}..." # Order matters: ifconfig up first so the radio's PHY is # ENABLED before dataset commit triggers SetMacKey. Without # this, SetMacKey returns INVALID_STATE and otbr-agent # fatal-exits. - kubectl -n "$NS" exec "$pod" -c otbr -- ot-ctl ifconfig up - kubectl -n "$NS" exec "$pod" -c otbr -- \ - ot-ctl dataset set active "$DATASET_TLV" - kubectl -n "$NS" exec "$pod" -c otbr -- ot-ctl thread start + ot() { kubectl -n "$NS" exec "$pod" -c otbr -- ot-ctl "$@"; } + ot ifconfig up + ot dataset init new + ot dataset channel "$THREAD_CHANNEL" + ot dataset networkname "$THREAD_NETWORK_NAME" + ot dataset commit active + ot thread start # Verify Thread came up. for i in $(seq 1 30); do diff --git a/kubernetes/apps/otbr/deployment.yaml b/kubernetes/apps/otbr/deployment.yaml index ebf876a6..82b011e5 100644 --- a/kubernetes/apps/otbr/deployment.yaml +++ b/kubernetes/apps/otbr/deployment.yaml @@ -5,16 +5,11 @@ metadata: labels: app.kubernetes.io/name: otbr spec: - # 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. - # - # 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 + # otbr runs its own Thread network, formed by otbr-dataset-bootstrap. It is + # not a co-BR of the Nest's NEST-PAN-339C: its radio does not hear that mesh + # well enough to merge partitions, and partitions bridged only over + # Ethernet are too slow for Matter's CASE handshake. + replicas: 1 strategy: type: Recreate selector: @@ -344,4 +339,4 @@ spec: volumes: - name: data persistentVolumeClaim: - claimName: otbr-data + claimName: otbr-thread diff --git a/kubernetes/apps/otbr/externalsecret.yaml b/kubernetes/apps/otbr/externalsecret.yaml deleted file mode 100644 index cdc5113c..00000000 --- a/kubernetes/apps/otbr/externalsecret.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: external-secrets.io/v1 -kind: ExternalSecret -metadata: - name: otbr-active-dataset - namespace: home-assistant -spec: - refreshInterval: 1h - secretStoreRef: - kind: ClusterSecretStore - name: openbao - target: - name: otbr-active-dataset # keeps the old Secret's name — consumers unchanged - dataFrom: - - extract: - key: apps/otbr diff --git a/kubernetes/apps/otbr/kustomization.yaml b/kubernetes/apps/otbr/kustomization.yaml index bf9aa83f..98d2f476 100644 --- a/kubernetes/apps/otbr/kustomization.yaml +++ b/kubernetes/apps/otbr/kustomization.yaml @@ -3,7 +3,6 @@ kind: Kustomization namespace: home-assistant resources: - dataset-bootstrap.yaml - - externalsecret.yaml - deployment.yaml - network-attachment.yaml - pvc.yaml diff --git a/kubernetes/apps/otbr/pvc.yaml b/kubernetes/apps/otbr/pvc.yaml index 97ea1526..a0bc49d8 100644 --- a/kubernetes/apps/otbr/pvc.yaml +++ b/kubernetes/apps/otbr/pvc.yaml @@ -9,7 +9,7 @@ apiVersion: v1 kind: PersistentVolumeClaim metadata: - name: otbr-data + name: otbr-thread spec: accessModes: - ReadWriteOnce diff --git a/kubernetes/clusters/homelab/home-assistant.yaml b/kubernetes/clusters/homelab/home-assistant.yaml index 062cb90e..11d72051 100644 --- a/kubernetes/clusters/homelab/home-assistant.yaml +++ b/kubernetes/clusters/homelab/home-assistant.yaml @@ -91,8 +91,6 @@ spec: name: flux-system path: ./kubernetes/apps/otbr prune: true - # Not in use. - suspend: true wait: true # decryption block dropped 2026-07-13: path is sops-free — secrets come # from OpenBao via ESO (MIGRATION-sops-openbao.md). -- 2.54.0