Commit Graph
4354 Commits
Author SHA1 Message Date
security-bot 666b3d1257 chore(image-automation): bump ghcr.io/music-assistant/server:2.8.9
Updates managed by image-automation-controller.
2026-06-03 14:50:39 +00:00
security-bot 63c27565f2 chore(image-automation): bump ghcr.io/zwave-js/zwave-js-ui:11.19.1
ci/woodpecker/cron/e2e Pipeline failed
ci/woodpecker/cron/security-scan Pipeline was successful
Updates managed by image-automation-controller.
2026-06-03 11:50:46 +00:00
security-bot 1d91ee3149 chore(image-automation): bump everythingsmarthome/everything-presence-mmwave-configurator:2.2.1
Updates managed by image-automation-controller.
2026-06-03 01:50:29 +00:00
security-bot 6b76f36334 chore(image-automation): bump everythingsmarthome/everything-presence-mmwave-configurator:2.2.0
Updates managed by image-automation-controller.
2026-06-02 15:50:22 +00:00
security-bot 7f72a7d5e4 chore(image-automation): bump quay.io/ceph/ceph:v19.2.4
ci/woodpecker/cron/e2e Pipeline failed
ci/woodpecker/cron/security-scan Pipeline was successful
Updates managed by image-automation-controller.
2026-06-01 14:52:16 +00:00
security-bot f22927bf49 security: agent-proposed fixes (2026-05-31) 2026-05-31 12:27:30 +00:00
security-bot 586221b1ae chore(image-automation): bump gotenberg/gotenberg:8.33.0
ci/woodpecker/cron/update-all Pipeline failed
ci/woodpecker/cron/e2e Pipeline failed
ci/woodpecker/cron/security-scan Pipeline was successful
Updates managed by image-automation-controller.
2026-05-29 14:49:17 +00:00
security-bot c82551c88d chore(image-automation): bump kasmweb/firefox:1.19.0-rolling-daily
ci/woodpecker/cron/e2e Pipeline failed
ci/woodpecker/cron/security-scan Pipeline was successful
Updates managed by image-automation-controller.
2026-05-28 21:49:05 +00:00
security-bot 0eb877925f chore(image-automation): bump ghcr.io/advplyr/audiobookshelf:2.35.1
Updates managed by image-automation-controller.
2026-05-28 20:49:05 +00:00
security-bot 4a01579a5d chore(image-automation): bump ghcr.io/zwave-js/zwave-js-ui:11.19.0
ci/woodpecker/cron/e2e Pipeline failed
ci/woodpecker/cron/security-scan Pipeline was successful
Updates managed by image-automation-controller.
2026-05-27 15:48:47 +00:00
security-bot 50667250ef chore(image-automation): bump apache/tika:3.3.1.0
Updates managed by image-automation-controller.
2026-05-27 14:48:44 +00:00
security-bot df31d2520e chore(image-automation): bump cloudflare/cloudflared:2026.5.2
ci/woodpecker/cron/e2e Pipeline failed
ci/woodpecker/cron/security-scan Pipeline was successful
Updates managed by image-automation-controller.
2026-05-27 11:48:43 +00:00
security-bot be518b475c chore(image-automation): bump redis:8.8.0-alpine
Updates managed by image-automation-controller.
2026-05-26 21:48:34 +00:00
k.rennerandClaude Opus 4.7 f52b69152a authentik: clean up duplicate unifi PolicyBindings
Blueprint identifiers for PolicyBindings include `order`, so changing
the order value between commits created orphan bindings instead of
updating in-place. UI showed four bindings on the unifi Application:
two Admins (order 0 + 1) and two Users (order 0 + 10). Add explicit
state:absent entries for the order-0 Admins and order-10 Users
orphans; the keeper bindings (Users/0, Admins/1) are unchanged.
Entries left in the blueprint permanently — state:absent is
idempotent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 22:54:00 +02:00
k.rennerandClaude Opus 4.7 98e5608c36 authentik: admit Unifi Users (not only Unifi Admins) on UDM LDAP
Existing Synology Application admits both `Synology Users` and
`Synology Admins`; I only added the admins binding for Unifi which
left the pre-migration `Unifi Users` group (g/j/s.renner, t.geiger,
…) unable to bind via the unifi Application policy. Mirror the
Synology pattern with two PolicyBindings — Users at order 0, Admins
at order 1. The Unifi Users group is declared `state: present`
identifier-only so the M2M membership is preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 22:48:42 +02:00
k.rennerandClaude Opus 4.7 ecdba251c5 authentik: pin shared LE cert on dormant LDAP Jellyfin too
LDAP outpost dispatch in ldap_tls.go falls back to the outpost's
self-signed default whenever any attached provider has cert=nil — so
attaching the LE cert to Unifi and Synology alone wasn't enough,
because the all-identical-certs branch keeps failing on Jellyfin's
empty cert. Set Jellyfin's certificate + tls_server_name to the same
shared keypair; provider stays otherwise dormant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 21:48:47 +02:00
k.rennerandClaude Opus 4.7 47163f204c authentik: wire LDAP Unifi on sibling DN + LDAPS via cert-manager
Activates the dormant `LDAP Unifi` provider for UniFi Network admin
auth: rebases onto sibling `DC=unifi,DC=renner,DC=uno` to dodge the
existing Jellyfin/Unifi base-DN dispatch collision (same fix Synology
got in 90906e2), adds Unifi Admins group / unifi Application /
PolicyBinding. Exposes LDAPS on 636 using an LE cert from
cert-manager (DNS-01 via letsencrypt-cluster), imported into Authentik
as a CertificateKeyPair from env vars on the server + worker pods.
Cert is also attached to LDAP Synology so DSM's eventual 389→636
cutover only needs a DSM-side reconfigure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 21:27:02 +02:00
security-bot ddf2e9b2e3 chore(image-automation): bump cloudflare/cloudflared:2026.5.1
ci/woodpecker/cron/e2e Pipeline failed
ci/woodpecker/cron/security-scan Pipeline was successful
Updates managed by image-automation-controller.
2026-05-25 10:48:11 +00:00
security-bot d093fbdda0 chore(image-automation): bump ghcr.io/home-assistant/home-assistant:2026.5.4
ci/woodpecker/cron/update-all Pipeline failed
ci/woodpecker/cron/security-scan Pipeline was successful
ci/woodpecker/cron/e2e Pipeline failed
Updates managed by image-automation-controller.
2026-05-22 18:48:27 +00:00
security-bot c8ea22abc2 chore(image-automation): bump ghcr.io/music-assistant/server:2.8.8
Updates managed by image-automation-controller.
2026-05-22 12:47:24 +00:00
k.renner af9c58a890 authentik: bump bridge-server tag — synthesise uid=<username>
ci/woodpecker/cron/e2e Pipeline failed
ci/woodpecker/cron/security-scan Pipeline was successful
DSM's smbd hardcodes the user lookup filter to
(&(uid=%u)(objectClass=sambaSamAccount)). Authentik's LDAP outpost
emits uid=<random User.uid hash> by default, so that filter never
matched and SMB authentication was failing at user lookup —
before NTLM verification even ran.

bridge commit b06668e: synthesise "uid": user.username into
user.attributes, so the outpost's AttributesToLDAP path overrides
the EnsureAttributes default. The override is gated by
SAMBA_BRIDGE_ENABLED_GROUP ("Synology Users"); users outside that
group still emit upstream's uid=<hash>.

Verified live before this rollout: smbclient -L //nas-1.renner.uno
-U k.renner authenticates and lists shares including the personal
"home" share, after directly injecting uid=k.renner via SQL into
the same JSONField the bridge writes. Bumping the bridge image so
the next password-change cycle (and the migration backfill for
existing users) writes this value automatically.

The ldap-outpost image tag is unchanged (sambaSamAccount +
sambaDomain GetUserOCs() patches already in 0140972; this commit
only updates Python in the server image).
2026-05-22 13:42:17 +02:00
k.rennerandClaude Opus 4.7 b9854efb49 authentik: bump patched LDAP outpost + add sambaDomain marker
DSM smbd live diagnostic on /var/log/samba/log.smbd revealed a
second LDAP gate after the sambaSamAccount one: every SMB session
attempt fires (&(sambaDomainName=synology)(objectClass=sambaDomain))
before user auth. Without a matching entry, smbd's
smbldap_search_domain_info tries to ADD one (outpost is read-only)
and bails with NT_STATUS_UNSUCCESSFUL, blocking SMB auth entirely.

Two changes:

- Patched outpost bumped to 2026.2.3-0140972, which extends
  GetUserOCs() to recognise sambaDomain (in addition to
  sambaSamAccount). Without this the outpost's search dispatcher
  short-circuits sambaDomain queries at the gate before consulting
  any user entry.

- New service-account user "samba-domain-synology" with
  user.attributes carrying objectClass=sambaDomain and the required
  sambaDomainName / sambaSID / sambaNextRid fields. The outpost
  emits this entry on DSM's sambaDomain query; DSM accepts it as
  the domain-info answer and proceeds with auth.

sambaSID on the marker MUST match SAMBA_BRIDGE_DOMAIN_SID and the
SID DSM has been pinned at, or DSM rejects the domain. All three
share S-1-5-21-700609483-19441523-1711414274 .

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 12:26:22 +02:00
k.renner c8035fcfc0 authentik: bump samba-bridge-server tag to 2026.2.3-09b29dd
09b29dd carries default=True on SambaBridgeConfig so Django actually
picks our AppConfig subclass instead of falling back to the vanilla
django.apps.AppConfig (which has no tenant_schedule_specs property
and caused the scheduler exception-loop in the previous deploy).
2026-05-22 11:55:38 +02:00
k.rennerandClaude Opus 4.7 723d30dbea authentik: pin samba-bridge-server tag to 2026.2.3-279da54
Floating :latest doesn't trigger re-pull on Kubernetes reschedule —
need an explicit tag bump to roll the deployment. 279da54 carries the
ManagedAppConfig fix that resolves the tenant_schedule_specs
AttributeError seen in the 62f9540 rollout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 11:46:48 +02:00
k.rennerandClaude Opus 4.7 ac011223ec authentik: wire samba-bridge for DSM SMB authentication
Deploys the authentik-samba-bridge stack:
gitea.renner.uno/k.renner/authentik-samba-bridge

Three pieces:

1. Server image override (helmrelease.yaml). global.image points at
   the bridge-server image (Authentik server + our Django app that
   subscribes to password_changed and writes Samba schema attrs to
   user.attributes). global.envFrom adds SAMBA_BRIDGE_DOMAIN_SID,
   SAMBA_BRIDGE_ENABLED_GROUP, SAMBA_BRIDGE_UID_OFFSET from a new
   SOPS secret. Both server AND worker pods inherit the bridge —
   the signal receiver registers in any Authentik server process
   that calls User.set_password().

2. LDAP outpost image override (blueprints-configmap.yaml).
   container_image on the LDAP Outpost record swaps the upstream
   outpost for our patched build that adds "sambaSamAccount" to
   GetUserOCs(). Without this, DSM's smbd filter
   (&(uid=...)(objectClass=sambaSamAccount)) short-circuits to zero
   entries inside the upstream outpost regardless of what's in
   user.attributes.

3. samba-bridge-secret.sops.yaml — the bridge's env contract. The
   domain SID was generated once from /dev/urandom and is now
   frozen forever (must match the SID typed into DSM's "Configure
   Samba Authentication" wizard).

Post-deploy: existing Synology Users members need to change their
password once via auth.renner.uno for the bridge to capture the
plaintext and synthesise the NT hash. Argon2 hashes already on
file can't be reverse-engineered into NT hashes; password reset
is the only path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 11:37:42 +02:00
k.rennerandClaude Opus 4.7 90906e23b6 authentik: move LDAP Synology base DN to sibling subtree
DC=synology,DC=authentik,DC=renner,DC=uno is a child of the existing
Unifi/Jellyfin base; the outpost's HasSuffixNoCase dispatch matches
on first iteration without longest-match preference, so binds still
landed on Jellyfin's policy. Moving Synology to DC=synology,DC=renner,
DC=uno makes it a sibling subtree the others' suffix check can't claim.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:37:45 +02:00
k.rennerandClaude Opus 4.7 e563cbfecd authentik: scope LDAP Synology under its own base DN
Live binds from nas-1 land in the Jellyfin Application's policy
evaluation because the outpost dispatches by base_dn suffix match —
with LDAP Unifi/Jellyfin/Synology all sharing DC=authentik,DC=renner,
DC=uno, the alphabetically-first provider wins every request. Moving
Synology under DC=synology,DC=authentik,DC=renner,DC=uno gives the
outpost a unique suffix to route on. Unifi/Jellyfin are left alone;
neither is LAN-reachable yet so the duplicate-base-DN there is dormant
config, not in this change's scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:26:54 +02:00
k.rennerandClaude Opus 4.7 3ededebfdd authentik: declare LDAP Synology provider + expose outpost on ldap.renner.uno
Blueprint-manages a new LDAP integration for nas-1 DSM: dedicated
LDAPProvider, Application, Synology Users/Admins groups, and PolicyBindings.
Standalone LoadBalancer Service on the BGP pool (pinned 10.2.99.88/fd99::88,
LAN hostname ldap.renner.uno via external-dns-unifi) sidesteps the operator-
owned ClusterIP; CNP scopes :3389 ingress to the mgmt LAN so the cleartext
bind stays on a trusted segment until LDAPS is wired.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:09:04 +02:00
k.rennerandClaude Opus 4.7 65bbb40ad4 paperless: drop unused .k. aliases (fixes ERR_ECH_FALLBACK_CERTIFICATE_INVALID)
ci/woodpecker/cron/e2e Pipeline failed
ci/woodpecker/cron/security-scan Pipeline was successful
paperless.k.renner.uno and docs.k.renner.uno were briefly stood up as
migration aliases and never put into use. Because they were orange-cloud
proxied via external-dns/cloudflare, Cloudflare advertised ECH for them,
but Cloudflare's Universal SSL cert only covers <zone> + *.<zone> — it
does not cover two-level subdomains like *.k.renner.uno. Chrome's ECH
handshake therefore reached Cloudflare, got back an empty/invalid
fallback cert, and surfaced ERR_ECH_FALLBACK_CERTIFICATE_INVALID
whenever someone landed on the .k. alias (bookmark, autocomplete, etc.).

Removed: paperless-aliases-ingress.yaml (Ingress + RedirectRegex
Middleware), the kustomization entry, both .k. URLs from
PAPERLESS_CSRF_TRUSTED_ORIGINS, the two paperless.k/docs.k checks in
e2e-test.sh, and stale alias references in surrounding comments.

Note: external-dns is upsert-only (no record cleanup), so the
paperless.k and docs.k CNAMEs in the Cloudflare zone need manual
deletion from the Cloudflare dashboard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 11:47:51 +02:00
k.rennerandClaude Opus 4.7 4acfbfcedd gatus: prune dead endpoints (Jellyfin/Wiki/InfluxDB/Postgres HA VIP)
These four endpoints have been reporting failure every monitor cycle:
the three HTTP hostnames (media/wiki/influx.renner.uno) are NXDOMAIN
with no Ingress in any namespace, and tcp://10.2.40.135:5432 no longer
responds (no TCP, no ICMP). Removed from in-cluster gatus to stop the
continuous false-failure noise; tombstones left in place for context.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 09:46:10 +02:00
k.renner 9088f27922 Revert "shelly-manager: silence .NET HttpClient Info-level access logs"
This reverts commit 731dcb033f.
2026-05-21 08:00:54 +02:00
k.rennerandClaude Opus 4.7 731dcb033f shelly-manager: silence .NET HttpClient Info-level access logs
At Information level .NET emits 4 lines per outbound HTTP call
(Start/Sending/Received/End). With ~13 polls/sec to the Shelly fleet
that's ~53 log lines/sec — roughly 40% of total cluster log volume,
all of it duplicate per-request bookkeeping. Pin the named-handler
log level to Warning so real failures still surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 07:59:21 +02:00
k.rennerandClaude Opus 4.7 fee6a58a47 authentik: scrape radius outpost via standalone Service + ServiceMonitor
The radius binary listens on :9300 like ldap/proxy do — verified by
port-forward, returns authentik_outpost_* metrics. But authentik's
Python operator at providers/radius/controllers/kubernetes.py omits
the DeploymentPort(9300, "http-metrics", "tcp", 9300) entry from its
`deployment_ports` list. ldap's controller has it, radius does not.
Net effect: the operator-managed Service exposes only UDP 1812 and
the operator-created ServiceMonitor points at a port that does not
exist on the Service it selects.

Add a sibling headless Service that selects the radius pod labels
and exposes 9300/TCP, plus a ServiceMonitor with the release label
so Prometheus scrapes it. Zero pod changes — the listener is
already there.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 07:43:39 +02:00
k.rennerandClaude Opus 4.7 a956529a2d authentik: standalone ServiceMonitors for outposts (ldap + paperless-gate)
The goauthentik.io operator auto-creates ServiceMonitors per outpost
but does not stamp `release: kube-prometheus-stack`, so this cluster's
serviceMonitorSelector silently ignores them. Declare standalone SMs
with the correct label, following the Velero precedent.

Radius is omitted on purpose: its container exposes only UDP 1812,
no /metrics endpoint. The operator-created SM for radius is dead
regardless of label.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 07:30:28 +02:00
k.rennerandClaude Opus 4.7 718840dc20 authentik: bump server CPU req to 200m, expose /metrics + ServiceMonitor
Server pods read 75m sustained CPU on a 100m request, tripping
KubeHpaMaxedOut without the cluster being under real load. The
asymmetry between pods correlates with outpost HTTP/2 connection
pinning + a brand_tls thundering herd in the Go router, but the
log-visible work only accounts for ~30m of the 75m. Authentik's
native /metrics endpoint (port 9300) was never enabled and the
chart's built-in ServiceMonitor was off, so per-handler and queue
metrics were invisible. Enable both and add the release label so
kube-prometheus-stack's serviceMonitorSelector picks it up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 07:14:41 +02:00
k.rennerandClaude Opus 4.7 17dc6c26c8 talos: one-shot cleanup script for accumulated machineconfig cruft
Applied live to k8s-{1,2,3}:
1. Removed stale `driver: virtio_net` interface entry — leftover from
   before the 2026-05-13 busPath migration. Strategic merge keys
   interfaces by deviceSelector, so changing the key from
   `driver: virtio_net` to `busPath: 0000:06:12.0` left both entries
   in the machineconfig, both claiming VIP 10.2.40.60.
2. Deduped the i40e Ceph `addresses` list. The strategic merge for
   the addresses array appends rather than dedupes, so repeated
   talosctl patch applications had accumulated 4× the same /29 entry.

k8s-edge was already clean — no Ceph i40e, never had a driver-key
entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 22:55:27 +02:00
k.rennerandClaude Opus 4.7 d45ab2bad0 Revert: dummy0 AnyIP + rp_filter loose (bf38990 + a637483)
Both changes broke ingress: dummy0 claiming the LB pool prefixes
(10.2.99.0/24 + fd99::/64) on every node intercepted TCP destined for
Service VIPs at the kernel routing layer, beating Cilium's eBPF LB to
the punch and breaking Traefik / external-dns / cert-manager / many
others that talk to the K8s API ClusterIP. Repeated talosctl patch
applications also accumulated cruft in the live machineconfig
(duplicate addresses on i40e, two interface entries claiming the
control-plane VIP).

dummy0 has been removed from all four nodes live via JSON-patch
op:remove. sysctls block removed similarly. ICMP-to-LB-IP loop trip
is back on the table — Cilium 1.19's eBPF LB still doesn't intercept
ICMP, and the AnyIP fix needs to be designed not to interfere with
the eBPF LB's TCP/UDP path. The simple "claim the whole pool prefix
on dummy0" pattern does interfere; a per-VIP /32+/128 claim per
endpoint-hosting node is the next thing to try, but that's a separate
follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 22:46:06 +02:00
k.rennerandClaude Opus 4.7 a637483800 talos: loose rp_filter for dummy0 / AnyIP asymmetric paths
Talos defaults net.ipv4.conf.*.rp_filter to 1 (strict). Strict RPF
drops packets whose return path doesn't match the ingress interface,
which kills the dummy0 AnyIP trick: ICMP to an LB VIP arrives on eth0
but the dst is claimed by dummy0, so strict RPF treats it as spoofed
and drops silently. Loose (2) accepts the packet as long as the source
is routable via any interface — standard for Kubernetes BGP setups.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 22:30:02 +02:00
k.rennerandClaude Opus 4.7 bf38990cd3 talos: add dummy0 interface claiming BGP-pool prefixes (AnyIP for ICMP)
Cilium's eBPF LB intercepts TCP/UDP destined for Service VIPs before
the kernel routing path, but ICMP/ICMPv6 isn't intercepted — packets
hit the kernel, the kernel has no interface owning the LB IP, default
route forwards back to the UDM, BGP routes back to a node, loop until
hop-limit exceeded. The "AnyIP" routing fix is to put the LB pool
prefixes on a dummy interface so the kernel claims them as local and
answers ICMP directly. Documented in the MetalLB BGP guide and in
Talos's machine.network.interfaces.dummy schema.

Applied to all four nodes (k8s-1/2/3/edge) since traffic may land on
any of them; the dummy interface is per-node. The /24 + /64 covers
the full bgp-pool range, future-proofing against new LB allocations
without further node config changes.

This is a kernel-network change — applying the patch requires a
rolling reboot of each node (talosctl patch --mode=auto + drain,
wait for Ceph HEALTH_OK between nodes per the wait-for-ceph rule).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 22:18:59 +02:00
k.rennerandClaude Opus 4.7 195026114d mosquitto: drop 0.0.0.0 bind so broker listens on v6 too
The 0.0.0.0 explicit bind constrained the listeners to IPv4 only,
which made TCP to the v6 LB VIP fd99::81 return Connection refused.
Without a bind address mosquitto binds to all interfaces (v4 + v6
wildcard), matching the dual-stack Service we just shipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 22:01:14 +02:00
k.rennerandClaude Opus 4.7 e95dba9049 bgp: split into parallel v6 + v4 sessions; v6 peers at fd40::1
The single v4-transport multiprotocol session couldn't actually deliver
v6 routes: Cilium 1.19 BGPv2 advertises extended-nexthop capability
only for the IPv4 Unicast AFI/SAFI tuple, so v6 routes sent with the
IPv4-mapped MpReach next-hop landed in the UDM's BGP RIB but never
made it to the kernel FIB. The kernel can't resolve an IPv4-mapped v6
address as a v6 next-hop on a v6 interface, and there's no CRD field
in Cilium 1.19 to fix the capability advertisement.

Solution is structural: run two parallel sessions, one per family.
fd40::/64 already has SLAAC addresses on every cluster node and a
ULA fd40::1 on UDM's br40, so peering natively over v6 needs no new
addressing — just a second peer entry on each side and an additional
listen range on UDM. v6 routes flow with v6 next-hops, v4 routes
flow with v4 next-hops, neither path depends on extended-nexthop.

fd40::1 (ULA) is the v6 peer address rather than the GUA so the
session survives ISP renumber events.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 21:55:57 +02:00
k.rennerandClaude Opus 4.7 8e26899e89 unifi(bgp): enable extended-nexthop capability for IPv6 over v4 session
Cilium BGPv2 carries v6 unicast routes over the single IPv4 BGP session
with the peer's v4 source as the MpReach Nexthop (RFC 5549/8950). FRR
must advertise the extended-nexthop capability to accept and install
those routes; without it the v6 SAFI completed negotiation, Cilium sent
the /128s, but the UDM either rejected them or installed them as
unresolvable. Result: ping -6 mqtt.renner.uno fell through to the
default route and the ISP gateway returned ICMPv6 No Route.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 21:40:23 +02:00
k.rennerandClaude Opus 4.7 a791030866 lb: add IPv6 to remaining LB Services on bgp-pool
home-assistant-bgp, mosquitto-iot, ftp-scanner, syncthing-sync, and
alloy-syslog flipped from SingleStack/IPv4 to PreferDualStack with
fd99::<tail> pinned via the lbipam annotation. external-dns will
publish AAAA records alongside the existing A records; v6-capable
clients pick IPv6 first via Happy Eyeballs while legacy v4-only
clients keep working unchanged. ipFamilies is [IPv4, IPv6] because
Kubernetes forbids reordering the field on an existing Service —
flipping the primary stack would require deleting and recreating
each Service, deferred.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 21:30:46 +02:00
k.rennerandClaude Opus 4.7 a08ac71f29 ha: switch home-assistant-vscode Service from LoadBalancer to ClusterIP
vscode reaches the cluster only via Traefik at vscode.home.renner.uno;
the LoadBalancer at 10.2.99.0 was never consumed by anything else.
Traefik routes equally well to a ClusterIP, so the LB IP is dead
weight and the BGP advertisement for 10.2.99.0/32 goes away. Dropped
the lb-pool=bgp label and externalTrafficPolicy: Local along with it
since neither applies to ClusterIP.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 21:25:21 +02:00
k.rennerandClaude Opus 4.7 1606b9d874 macvlan: strip iot-pool references from NetworkAttachment comments
iot-pool no longer exists (removed in e1d8ecd). The static macvlan
addresses on v30iot — shelly-manager (.228), matter-server (.226),
otbr (.227) — live outside Cilium IPAM and don't need to reference a
deleted pool. Also dropped stale .224/.225 home-assistant/mosquitto
allocation numbers from the matter-server and otbr comments — those
Services were on .80/.81 (now retired/migrated), never on .224/.225.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 21:16:03 +02:00
k.rennerandClaude Opus 4.7 e1d8ecd9e0 cilium: remove L2 announcement pool and policy
The iot-pool LoadBalancerIPPool and iot CiliumL2AnnouncementPolicy
became orphaned when home-assistant-iot (the only L2-announced
Service) was retired in ac57502. No remaining Service uses the
iot-vlan label, so the policy never elects a lease holder and the
pool never allocates an IP.

Moved bgp-pool into cilium-bgp.yaml (it was the only other resource
in cilium-l2.yaml) and deleted cilium-l2.yaml entirely; the file's
name no longer described its contents. The matter-server, otbr, and
shelly-manager macvlan NetworkAttachments still hand-allocate IPs
from the 10.2.30.0/24 block — those are outside Cilium IPAM and
unaffected by this cleanup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 21:11:25 +02:00
k.rennerandClaude Opus 4.7 ac57502911 ha: retire home-assistant-iot LoadBalancer at 10.2.30.80
All Shellies (Gen1 and Gen2) are on MQTT to mosquitto-iot, so nothing
on v30iot needs the L2-announced HTTP entry point at 10.2.30.80 any
more. ha.renner.uno (10.2.99.87, bgp-pool, ETP=Local) covers the
remaining LAN-side HA HTTP traffic. Removing this also eliminates the
Cilium L2-lease-on-wrong-node trap that broke TTS earlier today.

iot-pool LoadBalancerIPPool and the iot CiliumL2AnnouncementPolicy in
infrastructure/post-crds/cilium-l2.yaml are now orphaned (no Services
match their selectors). Left in place: the macvlan NetworkAttachments
for shelly-manager, matter-server, and otbr still hand-allocate IPs
from the 10.2.30.0/24 block and reference iot-pool in comments. Pool
cleanup is a separate decision.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 20:59:02 +02:00
k.rennerandClaude Opus 4.7 d5733cbec3 ha: add BGP-routed LAN entry point at 10.2.99.87 (ha.renner.uno)
Adds home-assistant-bgp Service on the bgp-pool with ETP=Local and
external-dns publishing ha.renner.uno via the UniFi LAN zone. Avoids
the Cilium L2-announce path that was firing the ETP=Local-on-L2 trap
(lease landed on a non-pod-hosting node, traffic dropped). With BGP,
the VIP is advertised only from the HA pod's host, so v30iot and LAN
clients reach the correct node directly — useful for ESPHome devices
fetching TTS audio via a DNS hostname.

home-assistant-iot at 10.2.30.80 stays as the legacy entry for devices
with hard-coded IPs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 20:47:14 +02:00
k.rennerandClaude Opus 4.7 95995aa561 k8s(traefik): re-enable externalTrafficPolicy=Local
HA's ip_bans cleanup is done — the 50 stale 10.2.x.x entries that
collided with real per-device LAN IPs have been cleared, so flipping
Traefik back to ETP=Local no longer 403s LAN clients. The other six
BGP-pool Services have been on ETP=Local since 1146bc6 without
regression; Traefik now joins them. Source IPs at backends behind
Traefik are now real client IPs (preserved through the HA sidecar
chain via X-Forwarded-For).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 20:25:27 +02:00
k.rennerandClaude Opus 4.7 ee365b8593 k8s(traefik): correct ETP=Cluster comment — sidecar-trust, not pod CIDR
Previous comment implied the fix was to add LAN CIDRs to HA's
trusted_proxies; that misses the nginx-sidecar architecture HA uses
specifically to keep trusted_proxies loopback-only and avoid restart
churn on every ingress reschedule. Real fix path is ip_bans cleanup
plus login_attempts_threshold tuning in the HA config repo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 20:17:39 +02:00