Compare commits

...
Author SHA1 Message Date
security-bot 23c089c8b7 chore(image-automation): bump ghcr.io/paperless-ngx/paperless-ngx:3.3.4
Updates managed by image-automation-controller.
2026-10-22 22:49:23 +00:00
k.renner c6cb6b3aec Merge pull request 'fix(netbird-server): keep the host mesh client out of DNS' (#1427) from fix/netbird-controller-disable-dns into main
ci/woodpecker/push/deploy-netbird-server Pipeline was successful
Reviewed-on: #1427
2026-10-22 19:56:51 +00:00
claude-botandClaude Opus 5.5 29c299c0fe fix(netbird-server): keep the host mesh client out of DNS
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
The controller's own NetBird client registered ~renner.uno in
systemd-resolved, pointing at the kube-dns-lb nameserver group, which is
unreachable from this peer. Every *.renner.uno lookup on the host returned
SERVFAIL, and Uptime Kuma, resolving through Docker's embedded DNS and the
host stub, marked all HTTP monitors DOWN after the 2026-10-22 update-os
reboot.

The role now reconnects the client with --disable-dns when its config has
DNS management enabled. The flag is only honoured on a fresh up, so the
task runs netbird down + up; it is a no-op once disableDns is true.
Applied live and verified 2026-10-22 before this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 21:55:11 +02:00
k.renner a3efc511c8 Merge pull request 'feat(dns): one consumer per label; public DNS as CNAME to proxy.renner.uno (step 2/2)' (#1426) from feat/dns-labels-split-2 into main
ci/woodpecker/push/build-ci-images Pipeline was successful
Reviewed-on: #1426
2026-10-22 19:52:53 +00:00
k.renner 924b6e9e88 Merge pull request 'feat(dns): add dns.renner.uno/internal and netbird.renner.uno/public labels (step 1/2)' (#1425) from feat/dns-labels-split-1 into main
Reviewed-on: #1425
2026-10-22 19:46:56 +00:00
claude-botandClaude Opus 5.5 5bfa957f29 feat(dns): one consumer per label; public DNS as CNAME to proxy.renner.uno
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
Step 2 of 2 of the label split (owner decision 2026-10-22). Requires
#1425 (step 1) to be applied first.

- external-dns-unifi: labelFilter dns.renner.uno/internal=true (opt-in).
  Every object it publishes today carries the label since step 1, so the
  UDM record set is unchanged.
- external-dns-cloudflare: labelFilter dns.renner.uno/public=true, default
  target proxy.renner.uno (the cloudflare-lb-sync load balancer) instead of
  the retired tunnel, no --cloudflare-proxied, policy sync. Each public
  host gets a DNS-only CNAME to proxy.renner.uno plus its TXT registry
  record; this is the prerequisite for removing the *.renner.uno wildcard.
- netbird-service-sync: reads netbird.renner.uno/public only. Every
  dns-public Ingress carries it since step 1, so the 21 proxy services are
  unchanged.
- git/gitea/gitea-mcp: add dns.renner.uno/public (public today via the
  wildcard; keep their LAN record via dns.renner.uno/internal).
- Drop comments describing the old public!=true filter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 21:45:20 +02:00
claude-botandClaude Opus 5.5 0806bdfab5 feat(dns): add dns.renner.uno/internal and netbird.renner.uno/public labels
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
Step 1 of 2 of the label split, owner decision 2026-10-22:
  dns.renner.uno/internal  -> UniFi (LAN) DNS via external-dns-unifi
  dns.renner.uno/public    -> Cloudflare DNS via external-dns-cloudflare
  netbird.renner.uno/public -> NetBird reverse-proxy service (netbird-service-sync)

Additive only; no consumer reads the new labels yet:
- dns.renner.uno/internal on every object external-dns-unifi publishes
  today (24 Ingresses incl. Grafana via chart values, 6 LoadBalancer
  Services = the 31 live UDM hostnames).
- netbird.renner.uno/public next to every dns.renner.uno/public label
  (20 Ingresses). netbird-service-sync already ORs both labels, so its
  desired set is unchanged.
authentik-gate-sync copies source labels to the generated *-gate Ingresses.

Step 2 flips external-dns-unifi to dns.renner.uno/internal=true,
netbird-service-sync to netbird.renner.uno/public only, and
external-dns-cloudflare to dns.renner.uno/public=true. It must merge only
after this change is applied, or external-dns-unifi (policy sync) would
delete LAN records.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 21:43:33 +02:00
security-bot 9d1b034d0f chore(image-automation): bump gitea.renner.uno/gitea_admin/ci-backup-verify:2026-10-22
Updates managed by image-automation-controller.
2026-10-22 19:23:34 +00:00
security-bot 46af8158ca chore(image-automation): bump gitea.renner.uno/gitea_admin/kuma-sync:2026-10-22-1858
Updates managed by image-automation-controller.
2026-10-22 19:23:31 +00:00
security-bot d30afa09c2 chore(image-automation): bump gitea.renner.uno/gitea_admin/secret-rotator:2026-10-22
Updates managed by image-automation-controller.
2026-10-22 19:23:29 +00:00
security-bot 6a82260500 chore(image-automation): bump gitea.renner.uno/gitea_admin/review-bot:2026-10-22-1854
Updates managed by image-automation-controller.
2026-10-22 19:23:28 +00:00
security-bot ab55215532 chore(image-automation): bump gitea.renner.uno/gitea_admin/authentik-gate-sync:2026-10-22-1858
Updates managed by image-automation-controller.
2026-10-22 19:23:26 +00:00
k.renner 7b9886545f Merge pull request 'fix(cloudflare-lb-sync): point deployment at the built image 2026-10-22-1904' (#1424) from fix/cloudflare-lb-sync-image-tag into main
Reviewed-on: #1424
2026-10-22 19:15:15 +00:00
claude-botandClaude Opus 5.5 5be8ee6e0e fix(cloudflare-lb-sync): point deployment at the built image 2026-10-22-1904
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
#1423 shipped a placeholder tag (2026-10-22-0000) that was never pushed,
so the pod sits in ImagePullBackOff until image automation rescans.
2026-10-22-1904 is the tag pipeline 14713 pushed
(sha256:bef768a88c7b7bd30f7fcbf3f3fecfc58e8f7ff5e08e1c5f576f8043a9465f83).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 21:14:36 +02:00
k.renner b9d237562e Merge pull request 'feat(cloudflare-lb-sync): manage proxy.renner.uno load balancer from the cluster' (#1423) from feat/cloudflare-lb-sync into main
ci/woodpecker/push/build-ci-images Pipeline was successful
Reviewed-on: #1423
2026-10-22 18:51:01 +00:00
54 changed files with 121 additions and 45 deletions
@@ -157,6 +157,29 @@
ansible.builtin.pause:
seconds: 10
# The host's mesh client must not manage DNS: containers here resolve through
# the host stub, and the renner.uno nameserver group is unreachable from it.
- name: Check for the host NetBird client
ansible.builtin.stat:
path: /usr/bin/netbird
register: _netbird_client
- name: Read the host NetBird client config
ansible.builtin.command: netbird debug config
register: _netbird_client_config
changed_when: false
when: _netbird_client.stat.exists
# disable-dns is only applied on a fresh `up`, not to a connected client.
- name: Reconnect the host NetBird client with DNS management disabled
ansible.builtin.shell: |
set -eu
netbird down
netbird up --disable-dns
when:
- _netbird_client.stat.exists
- not (_netbird_client_config.stdout | from_json).disableDns
- name: Display deployment status
ansible.builtin.debug:
msg: |
+4 -5
View File
@@ -1,10 +1,9 @@
#!/usr/bin/env python3
"""netbird-service-sync: reconcile NetBird reverse-proxy services from Ingresses.
Source of truth: every Ingress carrying one of the public labels.
dns.renner.uno/public also takes the host out of the LAN DNS (external-dns-unifi
skips it); netbird.renner.uno/public publishes on the proxy only and leaves the
LAN record in place.
Source of truth: every Ingress labeled netbird.renner.uno/public=true. DNS is
independent of it: dns.renner.uno/internal (UDM) and dns.renner.uno/public
(Cloudflare) are read by the two external-dns instances.
For each host on such an Ingress the NetBird management gets one reverse-proxy
service in TLS-passthrough mode on :443, targeting Traefik's ClusterIP through a
subnet resource that covers the cluster's service CIDR. SNI and Host reach
@@ -45,7 +44,7 @@ NETWORK_NAME = os.environ.get("NETWORK_NAME", "k8s public ingress")
ROUTER_GROUP = os.environ.get("ROUTER_GROUP_NAME", "k8s-ingress-router")
SOURCE_GROUP = os.environ.get("SOURCE_GROUP_NAME", "All")
TRAEFIK_NS, TRAEFIK_SVC = os.environ.get("TRAEFIK_SERVICE", "ingress/traefik").split("/", 1)
PUBLIC_LABELS = os.environ.get("PUBLIC_LABELS", "dns.renner.uno/public,netbird.renner.uno/public").split(",")
PUBLIC_LABELS = os.environ.get("PUBLIC_LABELS", "netbird.renner.uno/public").split(",")
LISTEN_PORT = int(os.environ.get("LISTEN_PORT", "443"))
INTERVAL = int(os.environ.get("INTERVAL_SECONDS", "60"))
HTTP_TIMEOUT = 20
@@ -7,6 +7,7 @@ metadata:
# Registered publicly (Cloudflare wildcard -> NetBird proxy); the UDM
# external-dns instance skips it, LAN reaches it via the hairpin NAT.
dns.renner.uno/public: "true"
netbird.renner.uno/public: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
spec:
+1 -1
View File
@@ -114,7 +114,7 @@ spec:
type: RuntimeDefault
containers:
- name: controller
image: gitea.renner.uno/gitea_admin/authentik-gate-sync:2026-10-21-0755 # {"$imagepolicy": "flux-system:authentik-gate-sync"}
image: gitea.renner.uno/gitea_admin/authentik-gate-sync:2026-10-22-1858 # {"$imagepolicy": "flux-system:authentik-gate-sync"}
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
@@ -164,6 +164,7 @@ spec:
# Registered publicly (Cloudflare wildcard -> NetBird proxy); the
# UDM external-dns instance skips it, LAN uses the hairpin NAT.
dns.renner.uno/public: "true"
netbird.renner.uno/public: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
hosts:
@@ -27,6 +27,7 @@ metadata:
namespace: authentik
labels:
lb-pool: bgp
dns.renner.uno/internal: "true"
annotations:
# Pinned to .88 / fd99::88 (next free in the .80-.89 infra range
# alongside traefik .86, ha .87, syslog .84, syncthing .85). Pinning
@@ -30,7 +30,7 @@ spec:
type: RuntimeDefault
containers:
- name: controller
image: gitea.renner.uno/gitea_admin/cloudflare-lb-sync:2026-10-22-0000 # {"$imagepolicy": "flux-system:cloudflare-lb-sync"}
image: gitea.renner.uno/gitea_admin/cloudflare-lb-sync:2026-10-22-1904 # {"$imagepolicy": "flux-system:cloudflare-lb-sync"}
env:
- name: CLOUDFLARE_API_TOKEN
valueFrom:
+2
View File
@@ -3,6 +3,8 @@ kind: Ingress
metadata:
name: evcc
namespace: evcc
labels:
dns.renner.uno/internal: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
spec:
@@ -43,16 +43,16 @@ spec:
name: cloudflare-api-token
key: api-token
extraArgs:
- --cloudflare-proxied
- --cloudflare-dns-records-per-page=5000
- --default-targets=d2386ccd-a665-41e7-8631-603fdafcb006.cfargotunnel.com
# Every public host is a DNS-only CNAME to the proxy load balancer (kubernetes/apps/cloudflare-lb-sync).
- --default-targets=proxy.renner.uno
- --force-default-targets
domainFilters:
- renner.uno
sources:
- ingress
txtOwnerId: k8s-homelab
policy: upsert-only
policy: sync
# Filter by label, not annotation. external-dns translates annotations
# in its prefix namespace into ProviderSpecific properties on desired
# endpoints; the UniFi webhook does not implement AdjustEndpoints, so
@@ -60,4 +60,4 @@ spec:
# records and cause every reconcile to delete-and-recreate the same
# values. Labels are not translated, so they are safe to use as a
# selector without leaking provider-specific state to the UniFi side.
labelFilter: external-dns/cloudflare=true
labelFilter: dns.renner.uno/public=true
+1
View File
@@ -7,6 +7,7 @@ metadata:
# Registered publicly (Cloudflare wildcard -> NetBird proxy); the UDM
# external-dns instance skips it, LAN reaches it via the hairpin NAT.
dns.renner.uno/public: "true"
netbird.renner.uno/public: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
spec:
+2
View File
@@ -7,6 +7,8 @@ metadata:
app.kubernetes.io/name: gitea-mcp
# Published on the NetBird proxy; LAN DNS stays on the Traefik VIP.
netbird.renner.uno/public: "true"
dns.renner.uno/internal: "true"
dns.renner.uno/public: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
spec:
+2
View File
@@ -8,6 +8,8 @@ metadata:
# Published on the NetBird proxy; LAN DNS stays on the Traefik VIP so
# SSH (gitea-ssh) and node image pulls stay local.
netbird.renner.uno/public: "true"
dns.renner.uno/internal: "true"
dns.renner.uno/public: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
# Long-running git fetch/push and container-registry blob uploads:
+4
View File
@@ -11,6 +11,8 @@ kind: Ingress
metadata:
name: hermes
namespace: hermes
labels:
dns.renner.uno/internal: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
spec:
@@ -38,6 +40,8 @@ kind: Ingress
metadata:
name: hermes-api
namespace: hermes
labels:
dns.renner.uno/internal: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
spec:
@@ -12,6 +12,8 @@ kind: Ingress
metadata:
name: shelly-manager
namespace: home-assistant
labels:
dns.renner.uno/internal: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
auth.renner.uno/group: Shelly Managers
@@ -16,6 +16,7 @@ metadata:
labels:
app.kubernetes.io/name: home-assistant
lb-pool: bgp
dns.renner.uno/internal: "true"
annotations:
lbipam.cilium.io/ips: "10.2.99.87,fd99::87"
external-dns.kubernetes.io/hostname: ha.renner.uno
@@ -3,6 +3,8 @@ kind: Ingress
metadata:
name: esphome
namespace: home-assistant
labels:
dns.renner.uno/internal: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
auth.renner.uno/group: ESPHome Managers
@@ -7,6 +7,7 @@ metadata:
# Registered publicly (Cloudflare wildcard -> NetBird proxy); the UDM
# external-dns instance skips it, LAN reaches it via the hairpin NAT.
dns.renner.uno/public: "true"
netbird.renner.uno/public: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
spec:
@@ -13,6 +13,7 @@ metadata:
labels:
app.kubernetes.io/name: mosquitto
lb-pool: bgp
dns.renner.uno/internal: "true"
annotations:
lbipam.cilium.io/ips: "10.2.99.81,fd99::81"
# Publish an internal A record to UniFi DNS so workstations on the
@@ -10,6 +10,8 @@ kind: Ingress
metadata:
name: home-assistant-vscode
namespace: home-assistant
labels:
dns.renner.uno/internal: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
auth.renner.uno/group: Home Assistant Config Editors
@@ -3,6 +3,8 @@ kind: Ingress
metadata:
name: zigbee2mqtt
namespace: home-assistant
labels:
dns.renner.uno/internal: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
auth.renner.uno/group: Zigbee2MQTT Managers
@@ -104,6 +104,8 @@ kind: Ingress
metadata:
name: ical-proxy
namespace: apps
labels:
dns.renner.uno/internal: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
spec:
+5 -3
View File
@@ -7,6 +7,7 @@ metadata:
# Registered publicly (Cloudflare wildcard -> NetBird proxy); the UDM
# external-dns instance skips it, LAN reaches it via the hairpin NAT.
dns.renner.uno/public: "true"
netbird.renner.uno/public: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
spec:
@@ -27,9 +28,8 @@ spec:
hosts:
- immich.renner.uno
---
# photos.renner.uno is LAN-registered (no proxy tunnel), so it lives on its
# own unlabeled Ingress: the dns.renner.uno/public label-filter is
# per-resource, and this name must keep its UDM record. It was the LXC alias
# photos.renner.uno is LAN-only (UDM record, no proxy), so it lives on its
# own Ingress: DNS label filters are per-resource. It was the LXC alias
# served by Caddy; preserved so external-dns-unifi can take over once the
# tofu CNAME on lxc_225 is removed.
apiVersion: networking.k8s.io/v1
@@ -37,6 +37,8 @@ kind: Ingress
metadata:
name: immich-photos
namespace: immich
labels:
dns.renner.uno/internal: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
spec:
+1 -1
View File
@@ -30,7 +30,7 @@ spec:
type: RuntimeDefault
containers:
- name: controller
image: gitea.renner.uno/gitea_admin/kuma-sync:2026-10-21-0756 # {"$imagepolicy": "flux-system:kuma-sync"}
image: gitea.renner.uno/gitea_admin/kuma-sync:2026-10-22-1858 # {"$imagepolicy": "flux-system:kuma-sync"}
env:
- name: KUMA_URL
value: https://kuma.renner.uno
@@ -19,6 +19,7 @@ metadata:
# external-dns instance skips anything carrying this, and LAN clients reach
# the same hostname through the hairpin NAT.
dns.renner.uno/public: "true"
netbird.renner.uno/public: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
# Manifest and service worker are fetched without credentials; neither
@@ -35,6 +35,8 @@ kind: Ingress
metadata:
name: wiki-embed
namespace: ai
labels:
dns.renner.uno/internal: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
traefik.ingress.kubernetes.io/router.middlewares: ai-wiki-embed-basic-auth@kubernetescrd
+3
View File
@@ -16,6 +16,7 @@ metadata:
# Registered publicly (Cloudflare wildcard -> NetBird proxy); the UDM
# external-dns instance skips it, LAN reaches it via the hairpin NAT.
dns.renner.uno/public: "true"
netbird.renner.uno/public: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
spec:
@@ -125,6 +126,7 @@ metadata:
# Registered publicly (Cloudflare wildcard -> NetBird proxy); the UDM
# external-dns instance skips it, LAN reaches it via the hairpin NAT.
dns.renner.uno/public: "true"
netbird.renner.uno/public: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
spec:
@@ -155,6 +157,7 @@ metadata:
# Registered publicly (Cloudflare wildcard -> NetBird proxy); the UDM
# external-dns instance skips it, LAN reaches it via the hairpin NAT.
dns.renner.uno/public: "true"
netbird.renner.uno/public: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
spec:
+2
View File
@@ -317,6 +317,7 @@ metadata:
namespace: matrix
labels:
dns.renner.uno/public: "true"
netbird.renner.uno/public: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
traefik.ingress.kubernetes.io/router.middlewares: matrix-lk-jwt-strip@kubernetescrd
@@ -369,6 +370,7 @@ metadata:
namespace: matrix
labels:
dns.renner.uno/public: "true"
netbird.renner.uno/public: "true"
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
@@ -9,6 +9,8 @@ kind: Ingress
metadata:
name: alertmanager
namespace: monitoring
labels:
dns.renner.uno/internal: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
auth.renner.uno/group: Monitoring Access
@@ -229,6 +229,7 @@ metadata:
namespace: monitoring
labels:
lb-pool: bgp
dns.renner.uno/internal: "true"
annotations:
io.cilium/lb-ipam-ips: "10.2.99.84,fd99::84"
external-dns.kubernetes.io/hostname: syslog.renner.uno
@@ -132,6 +132,8 @@ spec:
ingress:
enabled: true
ingressClassName: traefik
labels:
dns.renner.uno/internal: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
hosts:
@@ -3,6 +3,8 @@ kind: Ingress
metadata:
name: loki
namespace: monitoring
labels:
dns.renner.uno/internal: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
# nginx's auth-type/auth-secret/auth-realm annotation triple becomes
@@ -9,6 +9,8 @@ kind: Ingress
metadata:
name: prometheus
namespace: monitoring
labels:
dns.renner.uno/internal: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
auth.renner.uno/group: Monitoring Access
@@ -7,6 +7,7 @@ metadata:
# Registered publicly (Cloudflare wildcard -> NetBird proxy); the UDM
# external-dns instance skips it, LAN reaches it via the hairpin NAT.
dns.renner.uno/public: "true"
netbird.renner.uno/public: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
spec:
+2
View File
@@ -6,6 +6,7 @@ metadata:
namespace: navidrome
labels:
dns.renner.uno/public: "true"
netbird.renner.uno/public: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
spec:
@@ -45,6 +46,7 @@ metadata:
namespace: navidrome
labels:
dns.renner.uno/public: "true"
netbird.renner.uno/public: "true"
annotations:
traefik.ingress.kubernetes.io/router.middlewares: navidrome-navidrome-strip-identity@kubernetescrd
spec:
+1
View File
@@ -194,6 +194,7 @@ metadata:
namespace: navidrome
labels:
dns.renner.uno/public: "true"
netbird.renner.uno/public: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
auth.renner.uno/group: Music Librarians
@@ -27,6 +27,7 @@ metadata:
# Registered publicly (Cloudflare wildcard -> NetBird proxy); the UDM
# external-dns instance skips it, LAN reaches it via the hairpin NAT.
dns.renner.uno/public: "true"
netbird.renner.uno/public: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
traefik.ingress.kubernetes.io/router.middlewares: paperless-docs-to-paperless-redirect@kubernetescrd
@@ -113,6 +113,7 @@ metadata:
namespace: paperless
labels:
lb-pool: bgp
dns.renner.uno/internal: "true"
annotations:
io.cilium/lb-ipam-ips: "10.2.99.82,fd99::82"
# LAN-only DNS via the UniFi external-dns instance. No cloudflare
+2 -1
View File
@@ -182,7 +182,7 @@ spec:
drop: ["ALL"]
containers:
- name: paperless
image: ghcr.io/paperless-ngx/paperless-ngx:3.3.3 # {"$imagepolicy": "flux-system:paperless-ngx"}
image: ghcr.io/paperless-ngx/paperless-ngx:3.3.4 # {"$imagepolicy": "flux-system:paperless-ngx"}
ports:
- name: http
containerPort: 8000
@@ -357,6 +357,7 @@ metadata:
# Registered publicly (Cloudflare wildcard -> NetBird proxy); the UDM
# external-dns instance skips it, LAN reaches it via the hairpin NAT.
dns.renner.uno/public: "true"
netbird.renner.uno/public: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
auth.renner.uno/group: Paperless Users
+1
View File
@@ -143,6 +143,7 @@ metadata:
namespace: paperless
labels:
lb-pool: bgp
dns.renner.uno/internal: "true"
annotations:
io.cilium/lb-ipam-ips: "10.2.99.85,fd99::85"
external-dns.kubernetes.io/hostname: syncthing.paperless.renner.uno
+1 -1
View File
@@ -32,7 +32,7 @@ spec:
type: RuntimeDefault
containers:
- name: review-bot
image: gitea.renner.uno/gitea_admin/review-bot:2026-10-21-0752 # {"$imagepolicy": "flux-system:review-bot"}
image: gitea.renner.uno/gitea_admin/review-bot:2026-10-22-1854 # {"$imagepolicy": "flux-system:review-bot"}
env:
- name: REPOS
value: konairius/homelab,konairius/homeassistant,konairius/authentik-samba-bridge,konairius/shelly-manager
+1
View File
@@ -6,6 +6,7 @@ metadata:
namespace: review-bot
labels:
app.kubernetes.io/name: review-bot
dns.renner.uno/internal: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
spec:
@@ -28,6 +28,8 @@ kind: Ingress
metadata:
name: terminus-device
namespace: trmnl
labels:
dns.renner.uno/internal: "true"
# No external-dns hostname annotation: external-dns-unifi runs with
# --source=ingress, so it registers the hosts in spec.rules on its own.
# Same as the terminus Ingress next door.
+2
View File
@@ -7,6 +7,8 @@ kind: Ingress
metadata:
name: terminus
namespace: trmnl
labels:
dns.renner.uno/internal: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
spec:
+4
View File
@@ -6,6 +6,8 @@ kind: Ingress
metadata:
name: wiki
namespace: ai
labels:
dns.renner.uno/internal: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
spec:
@@ -43,6 +45,8 @@ kind: Ingress
metadata:
name: wiki-api
namespace: ai
labels:
dns.renner.uno/internal: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
traefik.ingress.kubernetes.io/router.middlewares: ai-wiki-api-basic-auth@kubernetescrd
+1
View File
@@ -5,6 +5,7 @@ metadata:
namespace: woodpecker
labels:
app.kubernetes.io/name: woodpecker
dns.renner.uno/internal: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
# Live log streams (SSE/WebSocket) hold the connection open for the
+2 -3
View File
@@ -16,6 +16,7 @@ metadata:
namespace: wordpress
labels:
dns.renner.uno/public: "true"
netbird.renner.uno/public: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
spec:
@@ -50,10 +51,8 @@ metadata:
name: wordpress-admin
namespace: wordpress
labels:
# external-dns-unifi runs with --label-filter=dns.renner.uno/public!=true,
# so an unlabelled Ingress on this host would gain a UDM A-record pointing
# LAN clients at the Traefik VIP.
dns.renner.uno/public: "true"
netbird.renner.uno/public: "true"
annotations:
auth.renner.uno/group: WordPress Authors
spec:
@@ -9,6 +9,8 @@ kind: Ingress
metadata:
name: zone-configurator
namespace: home-assistant
labels:
dns.renner.uno/internal: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
auth.renner.uno/group: Zone Configurators
@@ -1,6 +1,5 @@
# Reconciles NetBird reverse-proxy services from Ingresses that carry
# dns.renner.uno/public or netbird.renner.uno/public (proxy only, LAN DNS
# untouched): one TLS-passthrough service per host on :443,
# netbird.renner.uno/public: one TLS-passthrough service per host on :443,
# targeting Traefik's ClusterIP through a subnet resource over the service
# CIDR (ci/images/netbird-service-sync/controller.py).
#
@@ -46,6 +46,7 @@ metadata:
# Public DNS via Cloudflare; the proxies resolve it from outside. The UDM
# external-dns instance skips it, LAN reaches it via the hairpin NAT.
dns.renner.uno/public: "true"
netbird.renner.uno/public: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
# NOT behind the Authentik gate: the client is a daemon with an API key,
@@ -82,25 +82,12 @@ spec:
# once via the UDM UI instead.
domainFilters:
- renner.uno
# The label controls where a domain is registered: resources labeled
# dns.renner.uno/public=true are publicly-served (Cloudflare wildcard ->
# NetBird proxy) and must NOT get a LAN-only A/AAAA on the UDM — a phone
# that caches 10.2.99.86 / fd99::86 on wifi and then roams to cellular
# blackholes on it (RFC1918/ULA gives no RST and no ICMP, so the app
# hangs until TCP timeout). This instance therefore skips labeled
# resources; LAN clients follow the public answer and the UDM hairpins
# the proxy addresses back to the cluster (opentofu/unifi_nat_hairpin.tf).
#
# The filter is per-RESOURCE: every Ingress/IngressRoute/Service that
# carries a public host must be labeled, and a public host must not share
# a resource with a LAN-registered host (split it, see immich-photos).
#
# Unlike a domain filter, the label filter applies only to the SOURCE
# side: records already on the UDM stay visible through the TXT registry,
# so labeling a resource drops its names from desired state and
# policy: sync deletes the now-stale records automatically. No manual
# cleanup step exists in this design.
labelFilter: dns.renner.uno/public!=true
# Opt-in: only resources labeled dns.renner.uno/internal=true get a UDM
# record. Public hosts without it resolve on-LAN via the public answer and
# the UDM hairpin (opentofu/unifi_nat_hairpin.tf). The filter is
# per-resource; policy: sync deletes the records of resources that lose
# the label.
labelFilter: dns.renner.uno/internal=true
sources:
- ingress
- service
@@ -3,6 +3,8 @@ kind: Ingress
metadata:
name: openbao
namespace: openbao
labels:
dns.renner.uno/internal: "true"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
spec:
@@ -71,7 +71,7 @@ spec:
type: RuntimeDefault
containers:
- name: rotate
image: gitea.renner.uno/gitea_admin/secret-rotator:2026-10-21 # {"$imagepolicy": "flux-system:secret-rotator"}
image: gitea.renner.uno/gitea_admin/secret-rotator:2026-10-22 # {"$imagepolicy": "flux-system:secret-rotator"}
args: ["ha-token"]
securityContext:
allowPrivilegeEscalation: false
@@ -133,7 +133,7 @@ spec:
type: RuntimeDefault
containers:
- name: rotate
image: gitea.renner.uno/gitea_admin/secret-rotator:2026-10-21 # {"$imagepolicy": "flux-system:secret-rotator"}
image: gitea.renner.uno/gitea_admin/secret-rotator:2026-10-22 # {"$imagepolicy": "flux-system:secret-rotator"}
args: ["ha-token"]
securityContext:
allowPrivilegeEscalation: false
@@ -195,7 +195,7 @@ spec:
type: RuntimeDefault
containers:
- name: rotate
image: gitea.renner.uno/gitea_admin/secret-rotator:2026-10-21 # {"$imagepolicy": "flux-system:secret-rotator"}
image: gitea.renner.uno/gitea_admin/secret-rotator:2026-10-22 # {"$imagepolicy": "flux-system:secret-rotator"}
args: ["ha-token"]
securityContext:
allowPrivilegeEscalation: false
@@ -32,7 +32,7 @@ spec:
type: RuntimeDefault
containers:
- name: backup-verify
image: gitea.renner.uno/gitea_admin/ci-backup-verify:2026-10-21 # {"$imagepolicy": "flux-system:backup-verify"}
image: gitea.renner.uno/gitea_admin/ci-backup-verify:2026-10-22 # {"$imagepolicy": "flux-system:backup-verify"}
command: ["bash", "/script/backup-verify.sh"]
env:
- name: HOME