feat(esh-matter): Matter server (matter.js 1.4.0) on a VLAN-90-only LXC for Home Assistant
For ha-dev (operator-approved 2026-09-26). CT 111 on esh-pve at 10.0.90.20: Matter/Thread IPv6 (Echo ULA + RA route-information) is link-only, so the server sits on esh-iot and HA reaches it over routed IPv4 ws :5580. - playbooks/esh-matter-lxc.yaml: kernel RA (accept_ra=1, rt_info_max_plen=64), forwarding off, Docker ip-forward/iptables off; nftables admits 5580 from HA 10.0.50.46 only and SSH from mgmt ranges; the CT is added to esh-pve's vzdump job (fabric credentials). - stacks/matter-server: ghcr.io/matter-js/matterjs-server:1.4.0 (digest), host networking, /data on the CT. - Acceptance: fdad:: SLAAC, ping6 thermostat, 2 Thread RIO routes learned, ws server_info from inside the HA container; 5580 refused from 10.0.50.45, nh3-dev and a temporary VLAN 90 netns vantage.
This commit is contained in:
@@ -367,6 +367,7 @@ Observed and standardized across servers:
|
||||
| esh-pve-nas | 10.0.50.55 | ESH home lab | Proxmox VE hypervisor (storage / media) | `servers/esh-pve-nas/README.md` |
|
||||
| esh-vm-db | 10.0.50.60 | ESH home lab | DB VM — PostgreSQL (paperless-ng) + MongoDB; bare-metal VM, no Docker | `servers/esh-vm-db/README.md` |
|
||||
| esh-ml1 | 10.0.50.80 | ESH home lab | GPU LXC (CT 110 on esh-pve, RTX 2000E Ada 16 GB, host-side NVIDIA driver) — **the fleet's embedding + reranking service** (TEI), sole gateway backend since 2026-09-25 | `servers/esh-ml1/README.md` |
|
||||
| esh-matter | 10.0.90.20 | ESH home lab (VLAN 90, esh-iot) | LXC (CT 111 on esh-pve), VLAN 90 only — Matter server (matter.js) for Home Assistant; :5580 firewalled to HA; in vzdump (fabric credentials) | `servers/esh-matter/README.md` |
|
||||
| nh3-ml1 | 10.100.50.80 | NH3 | GPU LXC (CT 109 on nh3-pve, RTX 2000E Ada 16 GB, host-side NVIDIA driver) — second embed/rerank (TEI) backend, twin of esh-ml1; not yet behind the gateway (2026-09-25) | `servers/nh3-ml1/README.md` |
|
||||
|
||||
**Placement rules:**
|
||||
|
||||
@@ -101,6 +101,7 @@ hosts:
|
||||
- {name: esh-pve-nas, site: esh, v4: 10.0.50.55, note: Proxmox hypervisor — storage/media}
|
||||
- {name: esh-vm-db, site: esh, v4: 10.0.50.60, note: PostgreSQL + MongoDB}
|
||||
- {name: esh-ml1, site: esh, v4: 10.0.50.80, note: CT110 on esh-pve — RTX 2000E Ada, embedding + reranking (TEI) + reward seat}
|
||||
- {name: esh-matter, site: esh, v4: 10.0.90.20, note: CT111 on esh-pve — VLAN 90 (esh-iot) only; Matter server (matter.js) for HA; :5580 open to HA 10.0.50.46 only}
|
||||
- {name: vm-esh-nas, site: esh, v4: 10.0.50.154, note: NAS-adjacent docker host}
|
||||
- {name: esh-filebot, site: esh, v4: 10.0.50.70, note: restic / file-sync VM}
|
||||
- {name: esh-gw, site: esh, v4: 10.0.250.1, note: esh-gw}
|
||||
|
||||
@@ -218,6 +218,7 @@ in `servers/pfi-gx10/README.md`. → `persistent-memory.d/2026-09-24-gx10-ac-res
|
||||
|
||||
## Recent decisions
|
||||
|
||||
- `[2026-09-26]` **esh-matter LIVE: a Matter server (matter.js 1.4.0) on CT 111 @ 10.0.90.20, VLAN 90 only**, for ha-dev (operator-approved, relayed). It is on the IoT link because Matter/Thread IPv6 (Echo ULA + RIO routes) is link-only. Kernel RA with rt_info_max_plen=64, forwarding off; Docker ip-forward/iptables off. The in-CT nftables admits :5580 from HA 10.0.50.46 only and SSH from mgmt ranges only, and accepts everything else so Matter UDP never meets conntrack. The CT is in esh-pve vzdump (fabric creds). Acceptance all green: fdad:: SLAAC; ping6 thermostat; 2 Thread routes learned (Aqara + Echo); ws OK from inside the HA container; refused from 10.0.50.45, nh3-dev and a temp VLAN 90 netns. → `servers/esh-matter/README.md`
|
||||
- `[2026-09-26]` **Embed/rerank LOAD-SHARED across esh-ml1 + nh3-ml1 (Prime).** Second deployments were added for qwen3-embedding and reranker (config) and for reranker-a3-bge-v2-m3 (DB, `/model/new`). Without failover, a dead backend failed 7/40 embeds; `router_settings.enable_weighted_failover: true` (affects only multi-deployment groups) brought it to rerank 80/80 and embed 38/40 at onset, 60/60 sustained. In LiteLLM 1.97 the embedding path does not stamp the failed deployment, so it relies on cooldown. LiteLLM restarted 3× tonight, ~36 s each.
|
||||
- `[2026-09-26]` **Two dataset-foundry utility seats LIVE on nh3-ml1 (brokkr; operator approval relayed):** LFM2.5-VL-3B on llama.cpp `:8030` (gateway `lfm25-vl-3b`, LiteLLM restarted 36 s at 0039) and VibeVoice-ASR-Streaming-1.5B on **audio.cpp** `:8031`, not vibevoice.cpp as specced; the GGUF card names audio.cpp. Controls: VL read a synthetic image exactly, but hallucinates when no image is sent; ASR WER 3/69 on the bundled LibriSpeech clips. libsoxr added (linear resampling misheard a word). First ASR request ~31 s cold. Answered brokkr: the old 27B image seat (10.250.50.54:8015) was retired 09-14; baseline = gateway `image-judge` (Flash-Next).
|
||||
- `[2026-09-26]` **Coder seat STAYS on fv-ml1 (Prime).** The nh3-ml1 copy gave the same quality (teacher-forced true-code logprob diff +0.008 ± 0.019) but ran ~5× slower (64-tok FIM ~1.0 s vs 0.2 s; 63 vs 338 tok/s), and freeing 6.3 GB on fv-ml1 GPU 1 (20 GB spare) bought little. Copy removed; recipe kept in `stacks/coder-seat/`. The RTX 2000E suits embed/rerank/classify, not latency-sensitive generation. Only coder was a candidate: parakeet stays (earlier ruling), voices is a generation seat.
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
# esh-matter — a small LXC on esh-pve whose ONLY network leg is VLAN 90 (esh-iot),
|
||||
# running the Matter server (matter.js, stacks/matter-server) for Home Assistant.
|
||||
# Requested by ha-dev, operator-approved 2026-09-26.
|
||||
#
|
||||
# WHY IT SITS ON VLAN 90: Matter's operational traffic is IPv6, and VLAN 90's only
|
||||
# IPv6 is the Thread ULA fdad:29e:d492:fd87::/64. It is advertised by an Echo
|
||||
# border router, not the UDM, and is visible only on that link. The Thread routes
|
||||
# (RA route-information options from the border routers) are likewise link-only.
|
||||
# HA core reaches the server over an IPv4 websocket (routed VLAN 50 → 90; the UDM
|
||||
# policy InternalToIOT already allows it), so HA itself does not change.
|
||||
#
|
||||
# WHY AN LXC, NOT A MACVLAN ON esh-docker-vm: it keeps the untrusted IoT leg off the
|
||||
# host that runs HA and everything else (a VM with a history of wedges). It gets
|
||||
# its own firewall and its own vzdump backup.
|
||||
#
|
||||
# IPv6: kernel RA processing (no NetworkManager/networkd; the Debian 12 template
|
||||
# uses ifupdown). accept_ra=1 plus accept_ra_rt_info_max_plen=64 learns Thread
|
||||
# routes. IPv6 forwarding stays OFF (matter.js os_requirements: forwarding
|
||||
# disables RFC 4191 reachability probing). Docker is told not to touch forwarding
|
||||
# or iptables; the server runs with host networking, so it needs neither.
|
||||
#
|
||||
# FIREWALL (in-CT nftables): 5580 (the websocket and dashboard, UNAUTHENTICATED) is
|
||||
# accepted only from HA at {{ ha_ip }}, then dropped for everyone else, v4 and v6.
|
||||
# SSH is accepted only from non-IoT management ranges. Everything else, Matter
|
||||
# UDP 5540 and mDNS 5353 included, is left alone on purpose. That keeps conntrack
|
||||
# out of the Matter path, so sleepy-device reports are not dropped by a 120 s UDP
|
||||
# timeout (matter.js "Stateful firewalls" note).
|
||||
#
|
||||
# BACKUP: the CT is added to esh-pve's vzdump job (PBS-ANA, mirrored to PBS-NH3).
|
||||
# The data dir holds the Matter fabric root credentials; losing it means
|
||||
# re-commissioning every device.
|
||||
#
|
||||
# Run: scripts/elway root@esh-pve --playbook playbooks/esh-matter-lxc.yaml
|
||||
# Then: scripts/deploy-stack.sh esh-matter matter-server (+ docker compose up -d)
|
||||
|
||||
vars:
|
||||
ctid: 111
|
||||
hostname: esh-matter
|
||||
ip_cidr: 10.0.90.20/24
|
||||
ip_addr: 10.0.90.20
|
||||
gateway: 10.0.90.1
|
||||
vlan: 90
|
||||
rootfs_storage: local-lvm
|
||||
rootfs_gb: 8
|
||||
cores: 2
|
||||
memory_mb: 1024
|
||||
swap_mb: 512
|
||||
startup_order: 30
|
||||
template: debian-12-standard_12.12-1_amd64.tar.zst
|
||||
ha_ip: 10.0.50.46
|
||||
# Non-IoT sources allowed to SSH: esh-server, esh-userland, esh-mgmt, NH3, mesh.
|
||||
ssh_sources: "10.0.50.0/24, 10.0.10.0/24, 10.0.250.0/24, 10.100.0.0/16, 100.64.0.0/10"
|
||||
infra_ops_pubkey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN+1HBwfXrkfTYWdcnWCjLJ6VLAGC87gxH5h5vKaaA3c infra-ops@pfi-fleet"
|
||||
|
||||
steps:
|
||||
- name: Create CT {{ ctid }} ({{ hostname }}) with its only leg on VLAN {{ vlan }}, IPv6 SLAAC
|
||||
shell: |
|
||||
pct create {{ ctid }} local:vztmpl/{{ template }} \
|
||||
--hostname {{ hostname }} --unprivileged 1 --features nesting=1,keyctl=1 \
|
||||
--cores {{ cores }} --memory {{ memory_mb }} --swap {{ swap_mb }} \
|
||||
--rootfs {{ rootfs_storage }}:{{ rootfs_gb }} \
|
||||
--net0 name=eth0,bridge=vmbr0,firewall=0,gw={{ gateway }},ip={{ ip_cidr }},ip6=auto,tag={{ vlan }},type=veth \
|
||||
--nameserver {{ gateway }} \
|
||||
--onboot 1 --startup order={{ startup_order }},up=10 \
|
||||
--description "{{ hostname }} — Matter server (matter.js) on VLAN {{ vlan }} for Home Assistant. Built by eshpfi playbooks/esh-matter-lxc.yaml; stack stacks/matter-server. IN vzdump (fabric credentials)."
|
||||
when: "! pct status {{ ctid }} >/dev/null 2>&1"
|
||||
|
||||
# esh-pve's job names its vmids explicitly. Append ours; leave the rest alone.
|
||||
- name: Add CT {{ ctid }} to esh-pve's vzdump job
|
||||
shell: |
|
||||
set -e
|
||||
CT={{ ctid }} JOBS="$(pvesh get /cluster/backup --output-format json)" python3 - <<'PY'
|
||||
import json, os, subprocess
|
||||
ct = os.environ["CT"]
|
||||
for j in json.loads(os.environ["JOBS"]):
|
||||
ids = [x for x in str(j.get("vmid", "")).split(",") if x]
|
||||
if not ids or ct in ids:
|
||||
continue
|
||||
subprocess.run(["pvesh", "set", "/cluster/backup/" + j["id"], "--vmid", ",".join(ids + [ct])], check=True)
|
||||
print("added", ct, "to", j["id"])
|
||||
PY
|
||||
when: "! grep -E '^\\s+vmid .*\\b{{ ctid }}\\b' /etc/pve/jobs.cfg"
|
||||
|
||||
- name: Start the container
|
||||
shell: pct start {{ ctid }} && sleep 6
|
||||
when: "! pct status {{ ctid }} | grep -q running"
|
||||
|
||||
- name: IPv6 RA processing with Thread route-information options, forwarding off
|
||||
shell: |
|
||||
pct exec {{ ctid }} -- bash -s <<'EOF'
|
||||
set -euo pipefail
|
||||
cat > /etc/sysctl.d/60-matter-ipv6.conf <<'EOC'
|
||||
# Matter server — see eshpfi playbooks/esh-matter-lxc.yaml
|
||||
net.ipv6.conf.all.forwarding = 0
|
||||
net.ipv6.conf.eth0.accept_ra = 1
|
||||
net.ipv6.conf.eth0.accept_ra_rt_info_max_plen = 64
|
||||
EOC
|
||||
sysctl -q -p /etc/sysctl.d/60-matter-ipv6.conf
|
||||
EOF
|
||||
when: "! pct exec {{ ctid }} -- sh -c 'test $(cat /proc/sys/net/ipv6/conf/eth0/accept_ra_rt_info_max_plen) = 64'"
|
||||
|
||||
- name: Base packages + bookworm point upgrade (+ nftables)
|
||||
shell: |
|
||||
pct exec {{ ctid }} -- bash -s <<'EOF'
|
||||
set -euo pipefail
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
for i in $(seq 1 30); do getent hosts deb.debian.org >/dev/null && break; sleep 1; done
|
||||
apt-get update -qq
|
||||
apt-get -y -qq full-upgrade
|
||||
apt-get install -y -qq --no-install-recommends ca-certificates curl gnupg sudo jq less rsync locales nftables iputils-ping iproute2
|
||||
sed -i 's/^# *en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen >/dev/null
|
||||
EOF
|
||||
when: "! pct exec {{ ctid }} -- sh -c 'command -v nft && command -v rsync && command -v jq && locale -a | grep -qi en_US.utf8' >/dev/null 2>&1"
|
||||
|
||||
- name: Fleet identities (docker 851, infra-ops 850, vh 1000) + /opt/docker tree
|
||||
shell: |
|
||||
pct exec {{ ctid }} -- bash -s <<'EOF'
|
||||
set -euo pipefail
|
||||
getent group docker >/dev/null || groupadd -g 851 docker
|
||||
getent group infra-ops >/dev/null || groupadd -g 850 infra-ops
|
||||
id infra-ops >/dev/null 2>&1 || useradd -u 850 -g 850 -G docker -m -s /bin/bash infra-ops
|
||||
chmod 0700 /home/infra-ops
|
||||
install -d -m 0700 -o infra-ops -g infra-ops /home/infra-ops/.ssh
|
||||
echo '{{ infra_ops_pubkey }}' > /home/infra-ops/.ssh/authorized_keys
|
||||
chown infra-ops:infra-ops /home/infra-ops/.ssh/authorized_keys
|
||||
chmod 0600 /home/infra-ops/.ssh/authorized_keys
|
||||
echo 'infra-ops ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/infra-ops
|
||||
chmod 0440 /etc/sudoers.d/infra-ops
|
||||
id vh >/dev/null 2>&1 || useradd -u 1000 -U -G docker,sudo -m -s /bin/bash vh
|
||||
chmod 0700 /home/vh
|
||||
install -d -m 2775 -o root -g docker /opt/docker /opt/docker/compose /opt/docker/conf
|
||||
EOF
|
||||
when: "! pct exec {{ ctid }} -- sh -c 'test \"$(id -u infra-ops)\" = 850 && test \"$(getent group docker | cut -d: -f3)\" = 851 && test -d /opt/docker/compose'"
|
||||
|
||||
# Applied BEFORE Docker, so it is already in force when dockerd first starts.
|
||||
- name: Firewall — 5580 from HA only, SSH from management ranges only
|
||||
shell: |
|
||||
pct exec {{ ctid }} -- bash -s <<'EOF'
|
||||
set -euo pipefail
|
||||
cat > /etc/nftables.conf <<'EOC'
|
||||
#!/usr/sbin/nft -f
|
||||
# esh-matter guard — eshpfi playbooks/esh-matter-lxc.yaml. Policy ACCEPT on
|
||||
# purpose: only the two ports below are filtered, so Matter UDP and mDNS never
|
||||
# touch conntrack-based rules.
|
||||
flush ruleset
|
||||
table inet matter_guard {
|
||||
chain input {
|
||||
type filter hook input priority 0; policy accept;
|
||||
iif "lo" accept
|
||||
tcp dport 5580 ip saddr {{ ha_ip }} accept
|
||||
tcp dport 5580 counter drop
|
||||
tcp dport 22 ip saddr { {{ ssh_sources }} } accept
|
||||
tcp dport 22 counter drop
|
||||
}
|
||||
}
|
||||
EOC
|
||||
systemctl enable -q nftables
|
||||
systemctl restart nftables
|
||||
EOF
|
||||
when: "! pct exec {{ ctid }} -- sh -c 'nft list table inet matter_guard 2>/dev/null | grep -q \"5580 ip saddr {{ ha_ip }} accept\"'"
|
||||
|
||||
- name: docker-ce, hands off forwarding and iptables (host networking only)
|
||||
shell: |
|
||||
pct exec {{ ctid }} -- bash -s <<'EOF'
|
||||
set -euo pipefail
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
install -d /etc/docker
|
||||
printf '{\n "ip-forward": false,\n "iptables": false,\n "ip6tables": false\n}\n' > /etc/docker/daemon.json
|
||||
install -m 0755 -d /etc/apt/keyrings
|
||||
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
|
||||
chmod a+r /etc/apt/keyrings/docker.asc
|
||||
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian bookworm stable" \
|
||||
> /etc/apt/sources.list.d/docker.list
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||||
EOF
|
||||
when: "! pct exec {{ ctid }} -- sh -c 'command -v docker' >/dev/null 2>&1"
|
||||
|
||||
- name: Matter data dir (fabric credentials) owned by the container's uid 1000
|
||||
shell: pct exec {{ ctid }} -- install -d -m 0750 -o 1000 -g 1000 /opt/docker/data /opt/docker/data/matter-server
|
||||
when: "! pct exec {{ ctid }} -- test -d /opt/docker/data/matter-server"
|
||||
|
||||
verify:
|
||||
- name: Running, onboot, in the vzdump job
|
||||
shell: "pct status {{ ctid }} | grep -q running && pct config {{ ctid }} | grep -q '^onboot: 1' && grep -Eq '^\\s+vmid .*\\b{{ ctid }}\\b' /etc/pve/jobs.cfg"
|
||||
changed_when: "false"
|
||||
|
||||
- name: IPv6 — RA route-info on, forwarding off, a SLAAC address on eth0
|
||||
shell: |
|
||||
pct exec {{ ctid }} -- sh -c 'test $(cat /proc/sys/net/ipv6/conf/eth0/accept_ra_rt_info_max_plen) = 64 && test $(cat /proc/sys/net/ipv6/conf/all/forwarding) = 0 && ip -6 addr show dev eth0 scope global | grep -q inet6'
|
||||
changed_when: "false"
|
||||
|
||||
- name: Firewall loaded and persistent
|
||||
shell: pct exec {{ ctid }} -- sh -c 'nft list table inet matter_guard | grep -Eq "dport 5580 counter .*drop" && systemctl is-enabled --quiet nftables'
|
||||
changed_when: "false"
|
||||
|
||||
- name: Docker left forwarding alone
|
||||
shell: pct exec {{ ctid }} -- sh -c 'docker info >/dev/null && test $(cat /proc/sys/net/ipv6/conf/all/forwarding) = 0'
|
||||
changed_when: "false"
|
||||
|
||||
- name: Fleet identities are the pinned ids
|
||||
shell: |
|
||||
pct exec {{ ctid }} -- sh -c 'test "$(id -u infra-ops)" = 850 && test "$(id -u vh)" = 1000 && test "$(getent group docker | cut -d: -f3)" = 851'
|
||||
changed_when: "false"
|
||||
@@ -0,0 +1,62 @@
|
||||
# esh-matter
|
||||
|
||||
LXC **CT 111 on esh-pve** whose **only network leg is VLAN 90 (esh-iot)**. It runs
|
||||
the **Matter server** (`stacks/matter-server`, matter.js 1.4.0) for Home Assistant.
|
||||
Built 2026-09-26 for ha-dev (operator-approved) by
|
||||
[`playbooks/esh-matter-lxc.yaml`](../../playbooks/esh-matter-lxc.yaml).
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **IPv4** | `10.0.90.20/24`, gateway `10.0.90.1`. Static, below the UDM's `.40–.250` DHCP pool |
|
||||
| **IPv6** | SLAAC from the Echo's RA: `fdad:29e:d492:fd87:be24:11ff:fe3d:c6c7` (Thread ULA) + link-local. No global prefix exists on VLAN 90 |
|
||||
| **DNS** | `esh-matter.esh.internal` (HA uses the IP: its container can't resolve `.internal`) |
|
||||
| **SSH** | `ssh esh-matter` → `infra-ops@10.0.90.20`, or `pct enter 111` on esh-pve |
|
||||
| **OS** | Debian 12, unprivileged, `nesting=1,keyctl=1` (Docker), 2 cores / 1 GB / 8 GB |
|
||||
| **Backup** | **in esh-pve's vzdump job** (PBS-ANA 03:30, synced to PBS-NH3). `/opt/docker/data/matter-server` holds the Matter **fabric root credentials**; losing it means re-commissioning every device |
|
||||
|
||||
## Why it is built this way
|
||||
|
||||
- **It must be on the devices' link.** Matter's operational traffic is IPv6, and
|
||||
VLAN 90's only IPv6 is the Thread ULA advertised by an Echo border router.
|
||||
Thread routes arrive as RA route-information options, which are link-only. HA
|
||||
(VLAN 50) reaches the server over IPv4 `ws://10.0.90.20:5580/ws`, routed by the
|
||||
UDM (policy `InternalToIOT`), so the HA container did not change.
|
||||
- **Kernel RA processing, forwarding off.** Set in `/etc/sysctl.d/60-matter-ipv6.conf`:
|
||||
`accept_ra=1`, `accept_ra_rt_info_max_plen=64`, `all.forwarding=0`. With
|
||||
forwarding on, RFC 4191 reachability probing stops (matter.js OS requirements).
|
||||
Docker is configured with `ip-forward/iptables/ip6tables: false`; the server
|
||||
uses host networking.
|
||||
- **Firewall (in-CT nftables, `/etc/nftables.conf`):**
|
||||
- TCP **5580** (websocket + dashboard, **unauthenticated**) is accepted only
|
||||
from **HA at 10.0.50.46**, then dropped.
|
||||
- SSH is accepted only from 10.0.50/24, 10.0.10/24, 10.0.250/24, 10.100/16 and
|
||||
100.64/10.
|
||||
- Everything else is accepted on purpose, so Matter UDP and mDNS never meet
|
||||
conntrack. The matter.js docs warn that a stateful firewall's 120 s UDP
|
||||
timeout drops sleepy-device reports.
|
||||
- The PVE firewall is disabled on esh-pve, so this in-CT table is the control.
|
||||
- **An LXC, not a macvlan on esh-docker-vm:** the untrusted IoT leg stays off the
|
||||
VM that runs HA and everything else.
|
||||
|
||||
## Acceptance (2026-09-26 ~1305)
|
||||
|
||||
- `ip -6 addr`: link-local + `fdad:29e:d492:fd87:…` ✓. `ping -6` to the Aqara
|
||||
thermostat `fdad:29e:d492:fd87:56ef:44ff:fea1:7a36`: 3/3 ✓.
|
||||
- `ip -6 route` learned **two Thread routes by RA**:
|
||||
- `fd0e:3007:2ebd::/64 via fe80::56ef:44ff:fea1:7a36` (the Aqara W200, Apple
|
||||
Thread network);
|
||||
- `fdb6:ee32:f4c2:1::/64 via fe80::5a9a:3eff:fe49:6d36` (the Echo).
|
||||
- `ws://10.0.90.20:5580/ws` from **inside the homeassistant container**: answered
|
||||
`server_info` (schema 13, `matter-server/1.4.0 (matter.js/0.17.9)`, fabric 1) ✓.
|
||||
- **Negative controls:** 5580 is refused from the esh-docker-vm host (10.0.50.45),
|
||||
from nh3-dev, and from a **VLAN 90 vantage** (a temporary netns on esh-pve at
|
||||
10.0.90.39, which can ping the CT). SSH is also refused from that VLAN 90
|
||||
vantage. The drop counters incremented.
|
||||
|
||||
## Not yet wired
|
||||
|
||||
- **Monitoring.** Kuma can't probe 5580 (only HA may reach it), and there is no
|
||||
Beszel agent yet. HA's Matter integration surfaces an outage.
|
||||
- **UniFi mDNS reflector is ON for esh-iot** (`mdns_enabled`). matter.js warns
|
||||
mDNS forwarders can hinder Matter. It is left alone because other devices may
|
||||
rely on it; revisit only if commissioning or discovery misbehaves.
|
||||
@@ -0,0 +1 @@
|
||||
infra-ops@10.0.90.20
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,4 @@
|
||||
# matter-server tunables (esh-matter). Copy to `.env` on the server.
|
||||
# matter.js server 1.4.0 (stable, 2026-08-07), pinned by digest.
|
||||
MATTER_IMAGE=ghcr.io/matter-js/matterjs-server:1.4.0@sha256:54232d0d3e7dff5a54759469d2753399270412b4c30c55b31750a4595e4cb236
|
||||
MATTER_LOG_LEVEL=info
|
||||
@@ -0,0 +1,18 @@
|
||||
# matter-server
|
||||
|
||||
The **Matter controller for Home Assistant**: matter.js server
|
||||
(`ghcr.io/matter-js/matterjs-server`, **1.4.0**, digest-pinned) on **esh-matter**
|
||||
(CT 111, VLAN 90 only, `10.0.90.20`), with host networking. HA's `matter`
|
||||
integration connects to `ws://10.0.90.20:5580/ws`.
|
||||
|
||||
- matter.js server is the drop-in successor to python-matter-server (HA has used
|
||||
it since 2026.2), with the same websocket API.
|
||||
- `/data` → `/opt/docker/data/matter-server` (owned by uid 1000, the image's
|
||||
unprivileged user) holds the **fabric root credentials**. It is covered by the
|
||||
CT's vzdump backup. Never commit it.
|
||||
- :5580 is unauthenticated, so the CT firewall admits HA (10.0.50.46) only.
|
||||
- Host requirements (IPv6 RA with route-info, forwarding off) and acceptance
|
||||
results: [`servers/esh-matter/README.md`](../../servers/esh-matter/README.md).
|
||||
|
||||
Deploy: `scripts/deploy-stack.sh esh-matter matter-server`, then on the host
|
||||
`cp -n .env.example .env && docker compose config -q && docker compose up -d`.
|
||||
@@ -0,0 +1,30 @@
|
||||
# matter-server — the Matter controller for Home Assistant, on esh-matter (CT 111
|
||||
# on esh-pve, whose ONLY network leg is VLAN 90 / esh-iot, 10.0.90.20).
|
||||
# Requested by ha-dev, operator-approved 2026-09-26.
|
||||
#
|
||||
# matter.js server (matter-js/matterjs-server), the drop-in successor to
|
||||
# python-matter-server that HA has used since 2026.2. Same websocket API on
|
||||
# :5580/ws. Pinned: 1.4.0 (stable, 2026-08-07) by digest.
|
||||
#
|
||||
# host networking is REQUIRED: Matter is IPv6 link-local and mDNS. The server must
|
||||
# see eth0's Thread ULA and the border routers' RAs directly.
|
||||
#
|
||||
# ⚠ :5580 (websocket + dashboard) is UNAUTHENTICATED. The CT's nftables admits it
|
||||
# only from HA at 10.0.50.46 (playbooks/esh-matter-lxc.yaml).
|
||||
# ⚠ /data holds the Matter fabric ROOT CREDENTIALS. Losing it means re-commissioning
|
||||
# every device. The whole CT is in esh-pve's vzdump job (PBS-ANA → PBS-NH3). Never
|
||||
# put it in git.
|
||||
name: matter-server
|
||||
services:
|
||||
matter-server:
|
||||
image: ${MATTER_IMAGE}
|
||||
container_name: matter-server
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
volumes:
|
||||
- /opt/docker/data/matter-server:/data
|
||||
environment:
|
||||
- STORAGE_PATH=/data
|
||||
- PRIMARY_INTERFACE=eth0
|
||||
- TZ=America/Los_Angeles
|
||||
- LOG_LEVEL=${MATTER_LOG_LEVEL:-info}
|
||||
Reference in New Issue
Block a user