Compare commits

..
Author SHA1 Message Date
claude-botandClaude Opus 5.5 dfb8a0834c fix(otbr): bridge the radio from a socat sidecar over a Unix socket
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
otbr installed socat from apt at every start, which the namespace
egress baseline denies, so it crash-looped. otbr-agent now speaks
spinel+hdlc+unix to a socket served by a native alpine/socat sidecar
that bridges to slzb-thread:6638 (openthread/otbr image of 2026-11-07;
Unix stream sockets landed in OpenThread #13785). otbr-egress allows
only that TCP link (S28-48).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 10:52:15 +01:00
k.renner 04176ba165 Merge pull request 'feat(otbr): run an own Thread network on the SLZB-06P7' (#1606) from feat/otbr-own-thread-network into main
Reviewed-on: #1606
2026-11-08 09:35:16 +00:00
claude-botandClaude Opus 5.5 dba936b736 feat(otbr): run an own Thread network on the SLZB-06P7
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
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 <noreply@anthropic.com>
2026-11-08 10:32:31 +01:00
k.renner 33c04895f2 Merge pull request 'fix(authentik): turn off error reporting' (#1604) from fix/authentik-error-reporting-off into main
ci/woodpecker/cron/ci-claude-refresh Pipeline was successful
ci/woodpecker/cron/e2e Pipeline failed
Reviewed-on: #1604
2026-11-07 21:39:45 +00:00
claude-botandClaude Opus 5.5 7ecd10702e fix(authentik): turn off error reporting
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
error_reporting.enabled has been true since the first deploy. The server
hands the Sentry DSN (authentik.error-reporting.a7k.io) to every outpost
through /api/v3/root/config/, and the RADIUS and LDAP outposts then try
to send reports there; egress policy drops all of it. Reporting to a
third party was never intended to work here.

S28-43

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 22:38:11 +01:00
security-bot a6fdcf54f7 chore(image-automation): bump gitea.renner.uno/konairius/shelly-manager:2026-11-07-2052
Updates managed by image-automation-controller.
2026-11-07 21:21:33 +00:00
k.renner 1a3368e70d Merge pull request 'feat(home-assistant): let matter-server and zigbee2mqtt reach their update sources' (#1603) from feat/matter-z2m-egress into main
Reviewed-on: #1603
2026-11-07 21:21:19 +00:00
claude-botandClaude Opus 5.5 82165d6704 feat(home-assistant): let matter-server and zigbee2mqtt reach their update sources
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
matter-server fetches PAA root certificates, vendor names and OTA
metadata from the CSA DCL (on.dcl.csa-iot.org) and the development PAA
certificates from project-chip on GitHub (python-matter-server
helpers/paa_certificates.py, vendor_info.py, ota/dcl.py). zigbee2mqtt
fetches the Koenkk/zigbee-OTA index and images from
raw.githubusercontent.com (zigbee-herdsman ota.ts). The namespace
baseline denied all of it (~350 and ~200 drops a day), so certificate
refresh, vendor names and OTA silently failed. FQDN-scoped, 443 only.

S28-42

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 22:20:15 +01:00
k.renner 2ff8e216e1 Merge pull request 'fix(monitoring): turn off Alloy and InfluxDB usage telemetry' (#1602) from fix/disable-telemetry into main
Reviewed-on: #1602
2026-11-07 21:12:09 +00:00
k.renner dee425fd2f Merge pull request 'fix(image-automation): scan the Immich repositories every 6h' (#1601) from fix/immich-image-scan-interval into main
Reviewed-on: #1601
2026-11-07 21:09:12 +00:00
claude-botandClaude Opus 5.5 59cc813a87 fix(image-automation): scan the Immich repositories every 6h
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
immich-machine-learning has ~222k tags and immich-server ~33k, nearly
all PR builds. Each anonymous scan pages the full list (about 220
requests of 1000 tags for ML) and hourly scans hit ghcr.io's
TOOMANYREQUESTS. Immich releases a few times a week at most, so 6h
costs nothing.

S28-40

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 22:07:33 +01:00
10 changed files with 162 additions and 105 deletions
+3 -1
View File
@@ -113,8 +113,10 @@ spec:
value: "10.244.0.0/16,fd10:244::/48,127.0.0.0/8,::1/128"
authentik:
log_level: info
# Off: reports would go to authentik.error-reporting.a7k.io (Sentry),
# which egress policy denies. The outposts take this from the server.
error_reporting:
enabled: true
enabled: false
postgresql:
host: "ak-pg-rw.authentik.svc.cluster.local"
name: "authentik"
@@ -64,7 +64,7 @@ spec:
type: RuntimeDefault
containers:
- name: shelly-manager
image: gitea.renner.uno/konairius/shelly-manager:2026-11-07-1918 # {"$imagepolicy": "flux-system:shelly-manager"}
image: gitea.renner.uno/konairius/shelly-manager:2026-11-07-2052 # {"$imagepolicy": "flux-system:shelly-manager"}
imagePullPolicy: IfNotPresent
ports:
- name: http
+27 -21
View File
@@ -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 <hex> (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
+50 -60
View File
@@ -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:
@@ -34,49 +29,48 @@ spec:
# RCP is reached over the network (slzb-thread.renner.uno).
nodeSelector:
net.renner.uno/iot-vlan: "true"
initContainers:
# The Thread RCP is a network-attached radio (spinel over TCP).
# otbr-agent speaks spinel+hdlc over a Unix stream socket, which this
# native sidecar serves on the shared emptyDir and bridges to the
# radio, one TCP connection per agent connection. A PTY would not
# work here: devpts is per-container.
- name: radio-bridge
image: alpine/socat:1.8.1.3@sha256:466c8a0383e16463fabdb7df01008292fb78d07da60224a46cb5d3142ce122ed
imagePullPolicy: IfNotPresent
restartPolicy: Always
args:
- -d
- UNIX-LISTEN:/run/thread/spinel.sock,fork,unlink-early,mode=600
- TCP:slzb-thread.renner.uno:6638
startupProbe:
exec:
command: ["test", "-S", "/run/thread/spinel.sock"]
periodSeconds: 1
failureThreshold: 30
resources:
requests:
cpu: 10m
memory: 16Mi
limits:
memory: 64Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
volumeMounts:
- name: radio
mountPath: /run/thread
containers:
# 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:
#
# - `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.
#
# Hence socat is installed at startup and run inside this container.
- name: otbr
image: openthread/otbr:latest@sha256:51f9af250252f4faae85d0197574a7c17ada873c78799854461a93e3e42cf324
image: openthread/otbr:latest@sha256:cada244b39d1f43cb12d5f93694b4621db3b51c28219a05bbffac0f898d1403e
command:
- bash
- -c
- |
set -euo pipefail
if ! command -v socat >/dev/null; then
echo "otbr: installing socat from apt"
apt-get update -qq
apt-get install -y -qq socat
fi
echo "otbr: starting TCP↔PTY bridge to ${SLZB_HOST}:${SLZB_PORT}"
# Match the HA addon's socat invocation byte-for-byte
# (rootfs/etc/s6-overlay/s6-rc.d/socat-otbr-tcp/run): the
# known-working reference for SMLight network-attached
# Thread radios. ignoreeof keeps the data loop alive when
# otbr-agent transiently closes the PTY (e.g. on agent
# restart from livenessProbe), instead of socat exiting
# and racing to recreate the PTY symlink.
( while true; do
socat -d \
"pty,raw,echo=0,link=/tmp/ttyThread,ignoreeof" \
"tcp:${SLZB_HOST}:${SLZB_PORT}"
echo "otbr: socat bridge exited; restarting in 2s" >&2
sleep 2
done ) &
until [ -e /tmp/ttyThread ]; do
echo "otbr: waiting for serial bridge PTY at /tmp/ttyThread"
sleep 1
done
# SLZB-06P7 firmware crashes otbr-agent if a saved dataset
# is loaded before the radio's PHY is enabled: dataset
# commit triggers KeyManager → otPlatRadioSetMacKey, which
@@ -99,8 +93,8 @@ spec:
/app/etc/docker/test/docker_entrypoint.sh &
ENTRYPOINT_PID=$!
# Wait for the daemon socket to come up. Up to 90s of
# attempts at 2s intervals — covers slow apt installs,
# spinel handshake, and agent init on this radio.
# attempts at 2s intervals — covers the spinel handshake
# and agent init on this radio.
for i in $(seq 1 45); do
if ot-ctl state >/dev/null 2>&1; then
break
@@ -172,16 +166,9 @@ spec:
fi
wait "$ENTRYPOINT_PID"
env:
- name: SLZB_HOST
value: "slzb-thread.renner.uno"
- name: SLZB_PORT
value: "6638"
# Match the HA addon's RADIO_URL byte-for-byte. baud is
# omitted because PTYs ignore termios speed; uart-flow-
# control is a no-op on a PTY but stays in to mirror the
# known-working reference exactly.
# The radio-bridge sidecar's socket.
- name: RADIO_URL
value: "spinel+hdlc+uart:///tmp/ttyThread?uart-flow-control"
value: "spinel+hdlc+unix:///run/thread/spinel.sock"
# Use the macvlan attachment as the backbone interface. mDNS
# advertisements, SRP server listening, and RA reception all
# happen on this NIC. eth0 (the Cilium pod CIDR) is the
@@ -254,6 +241,8 @@ spec:
# and re-election on every restart).
- name: data
mountPath: /var/lib/thread
- name: radio
mountPath: /run/thread
# Probes must watch the *protocol*, not just the process.
#
# The upstream entrypoint backgrounds otbr-agent and ends in
@@ -271,8 +260,7 @@ spec:
# from this image, so probes go through ot-ctl, not REST.
#
# startupProbe: the agent's control socket answers. Covers the
# apt install, the socat TCP↔PTY bridge to the SLZB radio, and
# the spinel handshake. Generous: 10s × 60 = 10 minutes. While
# spinel handshake with the SLZB radio and agent init. Generous: 10s × 60 = 10 minutes. While
# it runs, the other two probes are suspended, so the entrypoint
# gets to finish activating Thread before liveness may act.
startupProbe:
@@ -344,4 +332,6 @@ spec:
volumes:
- name: data
persistentVolumeClaim:
claimName: otbr-data
claimName: otbr-thread
- name: radio
emptyDir: {}
-15
View File
@@ -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
-1
View File
@@ -3,7 +3,6 @@ kind: Kustomization
namespace: home-assistant
resources:
- dataset-bootstrap.yaml
- externalsecret.yaml
- deployment.yaml
- network-attachment.yaml
- pvc.yaml
+1 -1
View File
@@ -9,7 +9,7 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: otbr-data
name: otbr-thread
spec:
accessModes:
- ReadWriteOnce
@@ -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).
@@ -1,11 +1,14 @@
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageRepository
# 6h, not 1h: both repositories carry tens to hundreds of thousands of PR
# build tags, and every scan pages the full list anonymously, which runs
# into ghcr.io's rate limit.
metadata:
name: immich-server
namespace: flux-system
spec:
image: ghcr.io/immich-app/immich-server
interval: 1h
interval: 6h
---
# v-prefix tags only, semver 3.x. The 4.x cutover would need manual
# review — Immich major bumps historically require migration.
@@ -31,7 +34,7 @@ metadata:
namespace: flux-system
spec:
image: ghcr.io/immich-app/immich-machine-learning
interval: 1h
interval: 6h
---
# Same policy as immich-server; machine-learning image must stay in
# version-sync with the server image (Immich docs require it).
@@ -467,7 +467,8 @@ spec:
- port: "443"
protocol: TCP
---
# zigbee2mqtt: Zigbee coordinator (SLZB) TCP socket.
# zigbee2mqtt: Zigbee coordinator (SLZB) TCP socket, and the OTA index and
# firmware images (Koenkk/zigbee-OTA) on raw.githubusercontent.com.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
@@ -498,6 +499,79 @@ spec:
- ports:
- port: "6638"
protocol: TCP
- toFQDNs:
- matchName: "raw.githubusercontent.com"
toPorts:
- ports:
- port: "443"
protocol: TCP
---
# matter-server: the CSA DCL (PAA root certificates, vendor names, OTA
# metadata) and the development PAA certificates of project-chip on GitHub.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: matter-server-egress
namespace: home-assistant
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: matter-server
egress:
# rules.dns lets Cilium learn the toFQDNs mapping
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
- toFQDNs:
- matchName: "on.dcl.csa-iot.org"
- matchName: "api.github.com"
- matchName: "raw.githubusercontent.com"
toPorts:
- ports:
- port: "443"
protocol: TCP
---
# otbr: the radio-bridge sidecar's spinel link to the SLZB-06P7 Thread radio.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: otbr-egress
namespace: home-assistant
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: otbr
egress:
# rules.dns lets Cilium learn the toFQDNs mapping
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
- toFQDNs:
- matchName: "slzb-thread.renner.uno"
toPorts:
- ports:
- port: "6638"
protocol: TCP
---
# mqtt-account-controller: credential delivery to deliver.esp32 targets in mqtt-accounts.yaml.
apiVersion: cilium.io/v2