Compare commits
55
Commits
@@ -37,6 +37,10 @@ when:
|
||||
- ".woodpecker/**"
|
||||
- "scripts/ci/promtool-rules.sh"
|
||||
- "scripts/ci/yaml-citations.sh"
|
||||
- "scripts/ci/site-render.sh"
|
||||
- "scripts/ci/site-render-lib.sh"
|
||||
- "scripts/ci/test-site-render.sh"
|
||||
- "scripts/ci/testdata/site-render/**"
|
||||
|
||||
# THE THIRD DISCOVERY CHECK WENT TO A SCRIPT. kustomize-build and shellcheck
|
||||
# both hand-roll the same shape — `find`, a floor guard against empty discovery,
|
||||
@@ -341,3 +345,16 @@ steps:
|
||||
image: gitea.renner.uno/gitea_admin/ci-k8s:2026-09-08
|
||||
commands:
|
||||
- sh scripts/ci/yaml-citations.sh
|
||||
|
||||
# Per-site values (${SITE_*}, substituted by Flux from site-settings) must
|
||||
# render, and must not change what the homelab site renders today.
|
||||
- name: site-render
|
||||
depends_on: [] # see note above `steps:`
|
||||
image: gitea.renner.uno/gitea_admin/ci-k8s:2026-09-08
|
||||
commands:
|
||||
- bash scripts/ci/test-site-render.sh
|
||||
- |
|
||||
if [ "$CI_PIPELINE_EVENT" = "pull_request" ]; then
|
||||
git fetch --depth=1 origin "$CI_COMMIT_TARGET_BRANCH"
|
||||
bash scripts/ci/site-render.sh --changed homelab FETCH_HEAD
|
||||
fi
|
||||
|
||||
@@ -14,7 +14,8 @@ RUN apk add --no-cache \
|
||||
git \
|
||||
openssh-client \
|
||||
openssl \
|
||||
sops
|
||||
sops \
|
||||
yq
|
||||
|
||||
RUN curl -sfL -o /usr/local/bin/talosctl \
|
||||
"https://github.com/siderolabs/talos/releases/download/${TALOS_VERSION}/talosctl-linux-amd64" \
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
# Site settings: production rollout
|
||||
|
||||
The stack (PR0 ci-k8s image, PR1 render check, PR2 wiring, PR3 suspends,
|
||||
PR4 base domain, PR5 service CIDR, PR6 NetBird identity) needs the
|
||||
`flux-system/site-settings` ConfigMap on the production cluster before PR2
|
||||
merges: PR2 makes the root sync and the child Kustomizations name it in
|
||||
`postBuild.substituteFrom`, and Flux fails a Kustomization whose
|
||||
substituteFrom ConfigMap is missing.
|
||||
|
||||
Talos creates the ConfigMap from the `site-settings` inline manifest in
|
||||
`kubernetes/talos/patches/site-homelab.yaml`. The owner runs every step below;
|
||||
nothing here is automated.
|
||||
|
||||
## 1. Patch file
|
||||
|
||||
All 12 keys, exactly as in `site-homelab.yaml`:
|
||||
|
||||
```sh
|
||||
cat > site-settings-patch.yaml <<'EOF'
|
||||
cluster:
|
||||
inlineManifests:
|
||||
- name: site-settings
|
||||
contents: |
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: flux-system
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: site-settings
|
||||
namespace: flux-system
|
||||
data:
|
||||
SITE_DOMAIN: renner.uno
|
||||
SITE_DOMAIN_RE: renner\.uno
|
||||
SITE_ACME_SERVER: https://acme-v02.api.letsencrypt.org/directory
|
||||
SITE_SERVICE_CIDR: 10.96.0.0/12
|
||||
SITE_DNS_GATEWAY_IP: 10.96.0.11
|
||||
SITE_NETBIRD_NETWORK: k8s public ingress
|
||||
SITE_NETBIRD_ROUTER_GROUP: k8s-ingress-router
|
||||
SITE_NETBIRD_INGRESS_KEY: infrastructure/netbird-ingress
|
||||
SITE_SUSPEND_IGPU: "false"
|
||||
SITE_SUSPEND_IOT_VLAN: "false"
|
||||
SITE_SUSPEND_UNIFI: "false"
|
||||
SITE_SUSPEND_UPTIME_MONITORING: "false"
|
||||
EOF
|
||||
```
|
||||
|
||||
Check it against the repo (no output means equal):
|
||||
|
||||
```sh
|
||||
diff \
|
||||
<(yq -r 'select(.cluster) | .cluster.inlineManifests[] | select(.name == "site-settings") | .contents' kubernetes/talos/patches/site-homelab.yaml) \
|
||||
<(yq -r '.cluster.inlineManifests[0].contents' site-settings-patch.yaml)
|
||||
```
|
||||
|
||||
## 2. Dry run, k8s-1
|
||||
|
||||
Only changed lines are printed, so no other part of the machine config (and no
|
||||
secret in it) reaches the terminal:
|
||||
|
||||
```sh
|
||||
talosctl -n k8s-1.srv.renner.uno patch machineconfig \
|
||||
--patch @site-settings-patch.yaml --mode=no-reboot --dry-run \
|
||||
| grep -E '^[+-]'
|
||||
```
|
||||
|
||||
Expected: only `+` lines of the `site-settings` inline manifest (plus the
|
||||
`---`/`+++` diff headers). Anything else: stop.
|
||||
|
||||
## 3. Apply, one node at a time
|
||||
|
||||
```sh
|
||||
talosctl -n k8s-1.srv.renner.uno patch machineconfig \
|
||||
--patch @site-settings-patch.yaml --mode=no-reboot
|
||||
```
|
||||
|
||||
Verify (section 4), then repeat steps 2 and 3 for `k8s-2.srv.renner.uno`, then
|
||||
`k8s-3.srv.renner.uno`. `--mode=no-reboot` fails instead of rebooting if
|
||||
Talos ever wanted a reboot for this change.
|
||||
|
||||
## 4. Verify
|
||||
|
||||
```sh
|
||||
kubectl -n flux-system get cm site-settings -o yaml
|
||||
```
|
||||
|
||||
`data` must equal the homelab column of `site-homelab.yaml`: the 12 keys and
|
||||
values of section 1, nothing else. All three nodes must carry the patch before
|
||||
PR2 merges (a node rebuilt from its own config must not drop the entry).
|
||||
|
||||
## 5. Later changes
|
||||
|
||||
Talos only creates inline manifests: it does not update an object that already
|
||||
exists. Changing a value or adding a key later needs either the delete
|
||||
pattern in the machine config (`$patch: delete` on the `site-settings` entry
|
||||
and the new entry, in one `talosctl patch` call) or `talosctl upgrade-k8s`
|
||||
to sync the bootstrap manifests. Editing the live ConfigMap is drift: the next
|
||||
rebuild restores the Talos value.
|
||||
|
||||
## 6. Before each PR merges
|
||||
|
||||
Every key the PR substitutes must exist live:
|
||||
|
||||
| PR | Keys |
|
||||
|---|---|
|
||||
| PR2 wiring | the ConfigMap itself (no key substituted yet) |
|
||||
| PR3 suspends | `SITE_SUSPEND_IGPU`, `SITE_SUSPEND_IOT_VLAN`, `SITE_SUSPEND_UNIFI`, `SITE_SUSPEND_UPTIME_MONITORING` |
|
||||
| PR4 base domain | `SITE_DOMAIN`, `SITE_DOMAIN_RE`, `SITE_ACME_SERVER` |
|
||||
| PR5 service CIDR | `SITE_SERVICE_CIDR`, `SITE_DNS_GATEWAY_IP` |
|
||||
| PR6 NetBird | `SITE_NETBIRD_NETWORK`, `SITE_NETBIRD_ROUTER_GROUP`, `SITE_NETBIRD_INGRESS_KEY` |
|
||||
|
||||
```sh
|
||||
for k in SITE_DOMAIN SITE_DOMAIN_RE SITE_ACME_SERVER; do # the PR's keys
|
||||
printf '%s=%s\n' "$k" "$(kubectl -n flux-system get cm site-settings -o go-template="{{index .data \"$k\"}}")"
|
||||
done
|
||||
```
|
||||
|
||||
Each line must show the homelab value; an empty value or `<no value>` blocks
|
||||
the merge. Flux on the cluster substitutes without `--strict`: a missing key
|
||||
becomes an empty string in the applied object, it does not fail.
|
||||
|
||||
The site-render CI step (`--changed homelab` against the target branch) shows
|
||||
each PR's render differences; the PR body lists the expected ones.
|
||||
@@ -0,0 +1,163 @@
|
||||
# Site-specific base domain and service CIDR Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** The Hetzner restore-test site runs production's tree under its own base domain, service CIDR, NetBird identity and ACME server, with the features it lacks suspended, so it can be reached through the NetBird proxy without touching production.
|
||||
|
||||
**Architecture:** Each Talos site patch carries one more inline manifest, ConfigMap `flux-system/site-settings` (plus the `flux-system` Namespace), holding the site's values as `SITE_*` variables. Flux reads it through `postBuild.substituteFrom`: the root sync substitutes it into the Kustomization objects (for `spec.suspend`), and every Kustomization whose manifests carry a site value substitutes it into its manifests. Production's values reproduce today's literals, so its rendered output is byte-identical; a render check enforces that per PR.
|
||||
|
||||
**Tech Stack:** Flux v2 (kustomize-controller v1.9.5, flux CLI ≥ v2.8 for `flux envsubst --strict`), kustomize via `kubectl kustomize`, Talos inline manifests, bash, yq (both flavours must work), Woodpecker CI.
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-10-18-site-base-domain-design.md`
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- Only what must differ, differs. Every `SITE_*` variable exists because of a collision named in the spec.
|
||||
- Production (`site-homelab`) renders byte-identical to `origin/master` for every changed path, except the two intended pod-spec additions named in Task 3 (Gitea `GITEA__server__ROOT_URL`, gate-sync `COOKIE_DOMAIN`), each set to the value already in effect.
|
||||
- Site values live in the Talos site patches (`kubernetes/talos/patches/site-{homelab,hcloud,qemu}.yaml`). No test-only patch in `restore-test/flux-root.yaml` may introduce a site difference.
|
||||
- Never force-push; stacked PRs, each based on the previous; never self-merge.
|
||||
- Moving objects between pruned Kustomizations is not part of this plan; if a task finds it needs one, stop and use the #1307 prune handover.
|
||||
- Talos changes reach production as incremental `talosctl patch machineconfig` (never the whole repo file), `--dry-run` output filtered so no machine-config secret is printed, k8s-1 first, then k8s-2, k8s-3.
|
||||
- Matrix stays hardcoded (`server_name: matrix.renner.uno`); out of scope.
|
||||
- Comments state facts, not history. No prose in config files.
|
||||
|
||||
Site values (exact):
|
||||
|
||||
| Variable | homelab | hcloud | qemu |
|
||||
|---|---|---|---|
|
||||
| `SITE_DOMAIN` | `renner.uno` | `rtest.renner.uno` | `renner.uno` |
|
||||
| `SITE_ACME_SERVER` | `https://acme-v02.api.letsencrypt.org/directory` | `https://acme-staging-v02.api.letsencrypt.org/directory` | `https://acme-v02.api.letsencrypt.org/directory` |
|
||||
| `SITE_SERVICE_CIDR` | `10.96.0.0/12` | `10.112.0.0/12` | `10.96.0.0/12` |
|
||||
| `SITE_DNS_GATEWAY_IP` | `10.96.0.11` | `10.112.0.11` | `10.96.0.11` |
|
||||
| `SITE_NETBIRD_NETWORK` | `k8s public ingress` | `k8s public ingress rtest` | `k8s public ingress` |
|
||||
| `SITE_NETBIRD_ROUTER_GROUP` | `k8s-ingress-router` | `k8s-ingress-router-rtest` | `k8s-ingress-router` |
|
||||
| `SITE_NETBIRD_INGRESS_KEY` | `infrastructure/netbird-ingress` | `infrastructure/netbird-ingress-rtest` | `infrastructure/netbird-ingress` |
|
||||
| `SITE_SUSPEND_IGPU` | `false` | `true` | `true` |
|
||||
| `SITE_SUSPEND_IOT_VLAN` | `false` | `true` | `true` |
|
||||
| `SITE_SUSPEND_UNIFI` | `false` | `true` | `true` |
|
||||
| `SITE_SUSPEND_UPTIME_MONITORING` | `false` | `true` | `true` |
|
||||
|
||||
`10.112.0.0/12` overlaps nothing in the repo (checked: Ceph SAN `10.99.0.0/29` sits inside `10.96.0.0/12`, Hetzner nodes use `10.90.0.0/16`, NetBird `100.64.0.0/10`).
|
||||
|
||||
## Review Focus
|
||||
|
||||
1. A manifest in a newly substituting Kustomization contains unrelated `${...}` (shell scripts, Grafana dashboards, proxy configs, CEL) — Flux would blank it silently. Expected: `flux envsubst --strict` in the render check fails on it; the object carries `kustomize.toolkit.fluxcd.io/substitute: disabled` or the `$` is escaped as `$${...}`. Test: Task 1's `test-site-render.sh` case `strict_fails_on_unknown_var`.
|
||||
2. A suspended Kustomization is a `dependsOn` target of one that is not suspended on the same site — the dependent waits forever. Expected: nothing unsuspended depends on a suspended one. Test: Task 2 case `no_dependents_of_suspended`.
|
||||
3. A served name left hardcoded on the hcloud site (an Ingress host, redirect URI, probe target) — the test publishes or calls a production name. Expected: the hcloud render contains `renner.uno` only as `rtest.renner.uno`, in the allow-list (label/annotation keys, image references, the Git source, email addresses, external hosts, Matrix). Test: Task 3 case `hcloud_has_no_production_names`.
|
||||
4. The restore-test root (`restore-test/flux-root.yaml`) lacks the substitution the production root has, so suspends and domains render empty on the test. Expected: both roots substitute `site-settings`. Test: Task 2 case `restore_root_substitutes`.
|
||||
5. The test's NetBird peers gain access to production through NetBird's "All" group policies. Expected: verified against the live NetBird policies before run 31 (Task 6 Step 2).
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Render check for site values
|
||||
|
||||
**Files:**
|
||||
- Create: `scripts/ci/site-render.sh`
|
||||
- Create: `scripts/ci/test-site-render.sh`
|
||||
- Create: `scripts/ci/testdata/site-render/` (fixtures)
|
||||
- Modify: `.woodpecker/ci-manifests-test.yml` (new step `site-render`)
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: `scripts/ci/site-render.sh <site> <path>` — prints `kubectl kustomize <path>` piped through `flux envsubst --strict` with the `SITE_*` values read from the `site-settings` inline manifest of `kubernetes/talos/patches/site-<site>.yaml`; exit non-zero on an undefined variable. With `--root`, renders `kubernetes/clusters/homelab` the way the root sync does.
|
||||
- Produces: `scripts/ci/site-render.sh --changed <site> <base-ref>` — for every Flux Kustomization path whose object has `postBuild.substituteFrom` naming `site-settings`, diff the render at HEAD against the plain render at `<base-ref>`; exit 1 on a difference, printing it.
|
||||
|
||||
- [ ] **Step 1: Write the failing tests** in `scripts/ci/test-site-render.sh` (same style as `scripts/ci/test-yaml-citations.sh`), with fixtures under `scripts/ci/testdata/site-render/`:
|
||||
- `renders_site_value`: fixture kustomization with `host: app.${SITE_DOMAIN}` and a fixture site patch with `SITE_DOMAIN: example.test` → output contains `host: app.example.test`.
|
||||
- `strict_fails_on_unknown_var`: fixture containing `${FOO}` → exit status non-zero, stderr names `FOO`.
|
||||
- `escaped_dollar_survives`: fixture containing `$${FOO}` → output contains `${FOO}`.
|
||||
- `disabled_object_untouched`: object annotated `kustomize.toolkit.fluxcd.io/substitute: disabled` containing `${FOO}` → output keeps `${FOO}`, exit 0.
|
||||
- [ ] **Step 2: Run** `bash scripts/ci/test-site-render.sh` — expected FAIL (`site-render.sh` missing).
|
||||
- [ ] **Step 3: Implement `site-render.sh`.** Read the ConfigMap from the site patch with the same yq expression bootstrap.sh uses for `cilium-site` (`select(.cluster) | .cluster.inlineManifests[] | select(.name == "site-settings") | .contents`), no `-y`; export its `data` keys; honour the `substitute: disabled` annotation by rendering those objects untouched (split the stream with yq, substitute the rest). Never `2>/dev/null`.
|
||||
- [ ] **Step 4: Run** `bash scripts/ci/test-site-render.sh` — expected PASS; `shellcheck -S warning scripts/ci/site-render.sh scripts/ci/test-site-render.sh` clean.
|
||||
- [ ] **Step 5: Add CI step** `site-render` to `.woodpecker/ci-manifests-test.yml` running the test script, then `site-render.sh --changed homelab "$CI_COMMIT_TARGET_BRANCH"` on pull requests. Image must carry flux and yq (check `ci/images/ci-k8s`; add flux there if missing, in this task).
|
||||
- [ ] **Step 6: Commit** `ci(manifests): render check for per-site values`.
|
||||
|
||||
### Task 2: site-settings ConfigMap, root substitution, feature suspension
|
||||
|
||||
**Files:**
|
||||
- Modify: `kubernetes/talos/patches/site-homelab.yaml`, `site-hcloud.yaml`, `site-qemu.yaml` (new inline manifest `site-settings`: Namespace `flux-system` + ConfigMap with all `SITE_*` keys from the table)
|
||||
- Modify: `kubernetes/clusters/homelab/flux-system/kustomization.yaml` (patch adding `postBuild.substituteFrom: [{kind: ConfigMap, name: site-settings}]` to Kustomization `flux-system`; bootstrap keeps this file)
|
||||
- Modify: `kubernetes/restore-test/flux-root.yaml` (same `postBuild` on Kustomization `restore-test`)
|
||||
- Modify: `kubernetes/clusters/homelab/llama-server.yaml`, `wiki.yaml` (`suspend: ${SITE_SUSPEND_IGPU}`), `home-assistant.yaml` (`matter-server`, `home-assistant-iot`: `${SITE_SUSPEND_IOT_VLAN}`), `music-assistant.yaml` (`${SITE_SUSPEND_IOT_VLAN}`), `infrastructure-services.yaml` (`external-dns-unifi`: `${SITE_SUSPEND_UNIFI}`), `kuma-sync.yaml` (`${SITE_SUSPEND_UPTIME_MONITORING}`; owner, 2026-10-20: uptime monitoring does not run on the test cluster)
|
||||
- Test: `scripts/ci/test-site-render.sh`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `site-render.sh` (Task 1).
|
||||
- Produces: ConfigMap `flux-system/site-settings` with the keys in the table; root substitution available to every Kustomization object in `kubernetes/clusters/homelab`.
|
||||
|
||||
- [ ] **Step 1: Write the failing tests:**
|
||||
- `root_suspends_per_site`: `site-render.sh --root hcloud` shows `suspend: true` on the six Kustomizations listed above; `--root homelab` shows `suspend: false` on them and `otbr` unchanged (`true`).
|
||||
- `restore_root_substitutes`: `kubernetes/restore-test/flux-root.yaml`'s `restore-test` Kustomization has `postBuild.substituteFrom` naming `site-settings` (yq query, exit 0).
|
||||
- `no_dependents_of_suspended`: for each site, no Kustomization with `suspend: false` lists a suspended one in `dependsOn`.
|
||||
- `homelab_root_unchanged`: `--root homelab` render equals the plain render at `origin/master` except `suspend: false` on the six objects.
|
||||
- [ ] **Step 2: Run tests** — expected FAIL.
|
||||
- [ ] **Step 3: Add the inline manifest and the suspend fields** listed under Files. Production values make `suspend: false` explicit; Flux treats it like the absent field.
|
||||
- [ ] **Step 4: Run tests** — expected PASS; `site-render.sh --root homelab` exits 0 (no undefined variable anywhere in `kubernetes/clusters/homelab`).
|
||||
- [ ] **Step 5: Commit** `feat(flux): site-settings from the Talos site patch; features a site lacks are suspended`.
|
||||
- [ ] **Step 6: Production rollout before merge** (PR body): incremental Talos patch adding only the `site-settings` inline manifest, dry-run filtered, k8s-1 then k8s-2, k8s-3; verify `kubectl -n flux-system get cm site-settings -o yaml` equals the homelab column.
|
||||
|
||||
### Task 3: Base domain and ACME server per site
|
||||
|
||||
**Files (served names only; the spec's inventory lists them, about 120 files):**
|
||||
- Modify: every Ingress/TLS host, cert-manager `dnsZones` and `dnsNames`, external-dns `domainFilters` and hostname annotations, k8s-gateway zone (`infrastructure/k8s-gateway/helmrelease.yaml:56,61`), CoreDNS zone (`infrastructure/coredns-config/configmap.yaml:28`), app URLs, authentik blueprint redirect URIs, `authentik_host_browser`, brand domain, LDAP names, monitoring probes, gatus, uptime-monitor — `renner.uno` → `${SITE_DOMAIN}`.
|
||||
- Modify: `infrastructure/post-crds/clusterissuer.yaml` `${ACME_SERVER}` → `${SITE_ACME_SERVER}`; `infrastructure/cluster-settings/cluster-settings.yaml` drops `ACME_SERVER`; `clusters/homelab/infrastructure.yaml` `infrastructure-crds` adds `site-settings` to `substituteFrom`.
|
||||
- Modify: `apps/gitea/statefulset.yaml` add `GITEA__server__ROOT_URL: https://gitea.${SITE_DOMAIN}`.
|
||||
- Modify: `apps/authentik/gate-sync.yaml` add `COOKIE_DOMAIN: ${SITE_DOMAIN}`.
|
||||
- Modify: each `kubernetes/clusters/homelab/*.yaml` Kustomization whose path now contains `${SITE_DOMAIN}` — add `postBuild.substituteFrom: [{kind: ConfigMap, name: site-settings}]`.
|
||||
- Modify: objects in those paths with unrelated `${...}` (list in the spec's review and `git grep -l '\${[A-Za-z_]'`: home-assistant deployment/proxy.conf/config-sync.sh/zigbee2mqtt scripts, home-assistant-iot shelly proxy conf, otbr, monitoring dashboards and helmrelease-prometheus, multus cni-plugins-installer, openbao snapshot-cronjob, rook toolbox, image-automation lidarr, velero backup-verify) — annotate `kustomize.toolkit.fluxcd.io/substitute: disabled` where the object needs no site value, else escape as `$${...}`.
|
||||
- Unchanged by rule: label/annotation keys (`dns.renner.uno/*`, `auth.renner.uno/*`, `net.renner.uno/*`, `cnpg.renner.uno/*`, …), image references, `gotk-sync.yaml` Git URL, email addresses, names of hosts outside the cluster (udm, nas-1/2, pve, jupiter/saturn/neptune, `netbird.renner.uno`, `slzb-*`, `*.iot.renner.uno`, `edge-1`), Matrix.
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `site-settings` keys `SITE_DOMAIN`, `SITE_ACME_SERVER` (Task 2); `site-render.sh` (Task 1).
|
||||
|
||||
- [ ] **Step 1: Write the failing test** `hcloud_has_no_production_names` in `test-site-render.sh`: render every substituting path with `hcloud`; every `renner.uno` occurrence must be `rtest.renner.uno` or match the allow-list regex file `scripts/ci/testdata/site-render/allowed-production-names.txt` (one regex per line, each with a comment naming why it stays).
|
||||
- [ ] **Step 2: Run** — expected FAIL with the list of leftover names.
|
||||
- [ ] **Step 3: Replace** per the Files list, in reviewable commits by area: infrastructure (DNS, cert-manager, ACME), authentik, apps, monitoring.
|
||||
- [ ] **Step 4: Run** `test-site-render.sh` — PASS; `site-render.sh --changed homelab origin/master` — exits 0 except the two intended env additions, which it prints; confirm their values equal production's (`gitea.renner.uno`, `renner.uno`).
|
||||
- [ ] **Step 5: Commit** per area, `feat(site): <area> reads the base domain from site-settings`.
|
||||
|
||||
### Task 4: Service CIDR per site
|
||||
|
||||
**Files:**
|
||||
- Modify: `kubernetes/talos/patches/cluster.yaml:80-81` (remove `serviceSubnets`), `site-homelab.yaml`, `site-qemu.yaml` (`serviceSubnets: [10.96.0.0/12]`), `site-hcloud.yaml` (`[10.112.0.0/12]`, `ResolverConfig` nameserver `10.112.0.11` at :78)
|
||||
- Modify: `infrastructure/k8s-gateway/helmrelease.yaml:104` `clusterIP: ${SITE_DNS_GATEWAY_IP}`; `infrastructure/coredns-config/configmap.yaml:35` `forward . ${SITE_DNS_GATEWAY_IP}`; `infrastructure-inhouse/netbird-service-sync/deployment.yaml:99` `${SITE_SERVICE_CIDR}`; their Kustomizations get `site-settings` in `substituteFrom` if Task 3 did not add it.
|
||||
- Modify: `kubernetes/bootstrap.sh` k8s-gateway install (~1459): pass the site's `SITE_DNS_GATEWAY_IP` as the Service clusterIP (read from `site-settings` with the same yq expression as Task 1).
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `SITE_SERVICE_CIDR`, `SITE_DNS_GATEWAY_IP`.
|
||||
|
||||
- [ ] **Step 1: Failing tests:** `service_cidr_per_site` — `yq` over the merged Talos patches for each site yields the table's CIDR exactly once; `hcloud` render of k8s-gateway has `clusterIP: 10.112.0.11` and CoreDNS forwards there.
|
||||
- [ ] **Step 2: Run** — FAIL.
|
||||
- [ ] **Step 3: Implement** per Files.
|
||||
- [ ] **Step 4: Run** — PASS; homelab render unchanged; `shellcheck -S warning kubernetes/bootstrap.sh`.
|
||||
- [ ] **Step 5: Commit** `feat(site): service CIDR and DNS gateway address per site`.
|
||||
- [ ] **Step 6: Production rollout:** none on the nodes — production's `serviceSubnets` value is unchanged, only moved between patch files; the live machine config already holds it.
|
||||
|
||||
### Task 5: NetBird identity per site
|
||||
|
||||
**Files:**
|
||||
- Modify: `infrastructure-inhouse/netbird-service-sync/deployment.yaml` env `NETWORK_NAME: ${SITE_NETBIRD_NETWORK}`, `ROUTER_GROUP_NAME: ${SITE_NETBIRD_ROUTER_GROUP}`.
|
||||
- Modify: `infrastructure/netbird-ingress/externalsecret.yaml` `key: ${SITE_NETBIRD_INGRESS_KEY}`; `clusters/homelab/infrastructure-services.yaml` `netbird-ingress` gets `site-settings` substitution.
|
||||
- Create: `infrastructure/openbao/rotation/netbird-ingress-rtest.yaml` — a second CronJob like `netbird-ingress.yaml`, `BAO_PATH: homelab/infrastructure/netbird-ingress-rtest`, `NB_AUTO_GROUPS: <id of group k8s-ingress-router-rtest>`; add it to `infrastructure/openbao/rotation/kustomization.yaml`; extend the `rotator-netbird` OpenBao policy (`infrastructure/openbao/policies/`) to write that path.
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `SITE_NETBIRD_*` (Task 2).
|
||||
- Produces: OpenBao key `homelab/infrastructure/netbird-ingress-rtest` holding a setup key whose only auto-group is `k8s-ingress-router-rtest`.
|
||||
|
||||
- [ ] **Step 1: Owner step (blocking):** the owner creates NetBird group `k8s-ingress-router-rtest` with no policies granting it access to production resources and gives its ID. Do not create it with credentials read from OpenBao.
|
||||
- [ ] **Step 2: Failing tests:** hcloud render of netbird-service-sync has `NETWORK_NAME=k8s public ingress rtest`, `ROUTER_GROUP_NAME=k8s-ingress-router-rtest`; the netbird-ingress ExternalSecret extracts `infrastructure/netbird-ingress-rtest`; homelab render unchanged.
|
||||
- [ ] **Step 3: Implement** per Files.
|
||||
- [ ] **Step 4: Run tests** — PASS.
|
||||
- [ ] **Step 5: Commit** `feat(site): NetBird network, router group and setup key per site`.
|
||||
- [ ] **Step 6: Production rollout after merge:** create one Job from the new CronJob (`kubectl -n openbao create job --from=cronjob/netbird-secret-rotate-rtest …`), confirm the OpenBao key exists (metadata only, never the value), so the next raft snapshot carries it into the test.
|
||||
|
||||
### Task 6: Restore test with the new site (run 30, then run 31)
|
||||
|
||||
**Files:**
|
||||
- Modify: `kubernetes/restore-test/flux-root.yaml` — run 31 only: remove patches 3f and 3g; renumber.
|
||||
|
||||
- [ ] **Step 0: Before run 30:** list every object carrying the label `external-dns/cloudflare=true` (the Cloudflare instance's `labelFilter`) and the names it would publish on hcloud. With `--default-targets` set to a `*.cfargotunnel.com` name and `--force-default-targets` (`apps/external-dns/helmrelease.yaml`), each such record would shadow the public wildcard with an unreachable target; if any exists, stop and bring it to the owner.
|
||||
- [ ] **Step 1: Run 30** with patches 3f/3g still in place, hands-off, stall detector armed. Pass criteria: no undefined-variable errors; the six features show suspended; every other Kustomization Ready; all names on the test are `*.rtest.renner.uno`; certificates come from LE staging; production unaffected (production probes and `flux get ks` on production stay green); stale production URLs in restored data fail from the test (a pod on the test reaching `https://ci.renner.uno` and `https://auth.renner.uno` times out at the egress firewall). Record results in the wiki.
|
||||
- [ ] **Step 2: Before run 31:** owner and builder read the live NetBird policies for group "All" and `k8s-ingress-router-rtest`; the test's peers must reach nothing of production. Check whether the NetBird proxy accepts services under `rtest.renner.uno` without a separately registered custom domain; if registration is needed, it is the owner's step.
|
||||
- [ ] **Step 3: Remove patches 3f/3g**, commit `fix(restore-test): NetBird sync and routing peers run on the test site`, PR.
|
||||
- [ ] **Step 4: Run 31** hands-off. Production checks every 5 min (NetBird service list for production hosts unchanged, `netbird-service-sync` log in production, public probes). Stop and tear down on the first production deviation. Pass criteria additionally: the owner opens `https://<app>.rtest.renner.uno` from the internet and logs in with the password, and an app behind the passkey step-up (`passkey-stepup.yaml`: e.g. Gitea) lets them register a new passkey on the test host after password login.
|
||||
@@ -0,0 +1,231 @@
|
||||
# Site-specific base domain and address ranges for the restore test
|
||||
|
||||
Date: 2026-10-18. Status: design, awaiting the owner's approval.
|
||||
|
||||
## Intent
|
||||
|
||||
The Hetzner restore test (`bootstrap.sh hcloudtest` + `FLUX_SOURCE=restore-test bootstrap.sh flux`)
|
||||
boots production's configuration on throwaway machines and restores production's data into it.
|
||||
Today it has to run with parts of production switched off (`kubernetes/restore-test/flux-root.yaml`
|
||||
patches 3f and 3g, external-dns left unresolved), because it uses production's names and would
|
||||
overwrite production's NetBird services, routing and DNS. In run 29 production ingress broke during
|
||||
the test and the cause was never found.
|
||||
|
||||
The fix is to give the test cluster its own identity where, and only where, it collides with
|
||||
production: a subdomain as its base domain and its own address ranges. Then the test can run with
|
||||
everything production runs, including the internet path through the NetBird proxy, and the owner can
|
||||
open the restored services from the internet to check that they actually work.
|
||||
|
||||
## Goals
|
||||
|
||||
- The base domain is a site fact. Production (`site-homelab`) keeps `renner.uno`. The restore-test
|
||||
site (`site-hcloud`) uses a subdomain of `renner.uno` (working name `rtest.renner.uno`; the builder
|
||||
confirms the final label with the owner only if it matters).
|
||||
- The address range that collides is a site fact: the service CIDR, and every fixed address derived
|
||||
from it. Pod CIDR and LoadBalancer pool are not routed into NetBird for the test site (see 3), so
|
||||
they collide nowhere and stay identical.
|
||||
- The NetBird identity of the cluster is per site, so production and test live side by side in the
|
||||
same NetBird account.
|
||||
- The test cluster is reachable from the internet through the same NetBird proxy as production, under
|
||||
`*.<test domain>` names, and works like production there.
|
||||
- With these in place, the restore-test patches that exist only because of name or range collisions
|
||||
are removed (3f netbird-service-sync at zero replicas, 3g netbird-ingress on no node), and the
|
||||
Cloudflare external-dns runs on the test site. external-dns-unifi does not, and neither do the
|
||||
features the Hetzner site lacks: they are suspended there (see Decided).
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Making production serve several domains.
|
||||
- Matrix (Synapse, MAS, Element): out of scope (owner, 2026-10-18). Synapse refuses to start if
|
||||
`server_name` changes (upstream `synapse/storage/databases/main/__init__.py`), so it stays
|
||||
`matrix.renner.uno`; Matrix may be broken or unreachable on the test site.
|
||||
- Passkeys on the test site: authentik's WebAuthn RP ID is the request host
|
||||
(`authentik/stages/authenticator_webauthn/utils.py:6-11`, version 2026.8.3, not configurable), so
|
||||
passkeys registered on `auth.renner.uno` do not work on the test host. The owner logs in with
|
||||
the password; passkeys are not a design criterion (owner, 2026-10-18).
|
||||
- Rewriting production URLs inside restored data (see Constraints, "Stale production URLs").
|
||||
|
||||
## Anti-goals (a failure even if it technically works)
|
||||
|
||||
- Any difference between the sites that is not needed. "Only things that must differ should differ"
|
||||
(owner). Each per-site value must be justified by a collision.
|
||||
- A harness or script that changes the test nodes or network to make the test pass. The test must
|
||||
boot production's configuration; per-site facts live in the site and node files
|
||||
(`site-homelab` describes the local cluster, `site-hcloud` the Hetzner cluster, node specifics in
|
||||
their own files — owner's rule).
|
||||
- The test cluster writing anything to production: production's NetBird services, routes, peers or
|
||||
groups; production's DNS names; production's Woodpecker, Gitea or other services.
|
||||
- The cluster depending on external DNS for its own services (owner's design rule).
|
||||
- Any change in production's behaviour. For production, the change is a pure move of values into
|
||||
the site files; rendered manifests stay identical.
|
||||
|
||||
## What has to differ per site, and why
|
||||
|
||||
### 1. Base domain
|
||||
|
||||
Collision: the test's Ingress hosts, NetBird proxy services (keyed by domain) and DNS records would be
|
||||
production's.
|
||||
|
||||
Where the domain is written (inventory at origin/master, 2026-10-18, about 160 files contain
|
||||
`renner.uno`; only the served names below change per site):
|
||||
|
||||
- Ingress and TLS hosts: about 34 files under `kubernetes/apps` and `kubernetes/infrastructure`
|
||||
(e.g. `apps/gitea/ingress.yaml:19,29`), including nested names `*.home.renner.uno`.
|
||||
- cert-manager: `infrastructure/post-crds/clusterissuer.yaml` (`dnsZones: renner.uno` at 19, 39, 66;
|
||||
the ACME email at 8, 28, 55 is an address, not a served name); Certificates with `dnsNames`
|
||||
(`apps/authentik/ldap-outpost-cert.yaml:28`, `apps/home-assistant/mosquitto-certificate.yaml:12`,
|
||||
`apps/monitoring/helmrelease-alloy-syslog.yaml:12`).
|
||||
- DNS: both external-dns instances (`apps/external-dns/helmrelease.yaml:51` Cloudflare;
|
||||
`infrastructure/external-dns-unifi/helmrelease.yaml:84`), the `external-dns.kubernetes.io/hostname`
|
||||
annotations (6 files), k8s-gateway zone (`infrastructure/k8s-gateway/helmrelease.yaml:56,61`),
|
||||
CoreDNS zone (`infrastructure/coredns-config/configmap.yaml:28`).
|
||||
- Application config with URLs: Gitea, Woodpecker, Immich, Paperless, WordPress, TRMNL, gitea-mcp,
|
||||
review-bot, wiki, renovate, Grafana and the monitoring probes, gatus, uptime-monitor.
|
||||
- authentik blueprints (`apps/authentik/blueprints-configmap.yaml`): OAuth redirect URIs (~813-951),
|
||||
LDAP name and `tls_server_name`, `authentik_host_browser`, brand domain (1170).
|
||||
- authentik gate-sync: `AUTHENTIK_HOST_BROWSER` (`apps/authentik/gate-sync.yaml:127`) and
|
||||
`COOKIE_DOMAIN`, which is unset and defaults to `renner.uno` in the controller (`controller.py:57`);
|
||||
it must be set explicitly.
|
||||
- Gitea `ROOT_URL` lives in `app.ini` on the PVC (restored data), not in git; git must set it
|
||||
(`GITEA__server__ROOT_URL`) so the test serves its own URL.
|
||||
|
||||
Stays the same on every site (not served names): label and annotation keys such as
|
||||
`dns.renner.uno/public`, `auth.renner.uno/*`, `net.renner.uno/*`, `cnpg.renner.uno/*`; image
|
||||
references and the registry mirror (`gitea.renner.uno` as a pull source); Flux's Git source;
|
||||
email addresses; comments and docs. Names of things outside the cluster (UDM, NAS, PVE hosts,
|
||||
IoT devices, `netbird.renner.uno`) stay too.
|
||||
|
||||
### 2. Service CIDR
|
||||
|
||||
Collision: the NetBird proxy reaches a cluster through a route to its service CIDR and connects to
|
||||
Traefik's ClusterIP inside it (`ci/images/netbird-service-sync/controller.py`, `SERVICE_CIDR`,
|
||||
`service_body()`). Two clusters with the same CIDR give the proxy two routes to the same range; it
|
||||
cannot tell the clusters apart.
|
||||
|
||||
Currently shared, to become site facts:
|
||||
|
||||
- `serviceSubnets: 10.96.0.0/12` in `kubernetes/talos/patches/cluster.yaml:80-81`.
|
||||
- Fixed service addresses: k8s-gateway `10.96.0.11` (`infrastructure/k8s-gateway/helmrelease.yaml:104`),
|
||||
CoreDNS forward to it (`infrastructure/coredns-config/configmap.yaml:35`), the Talos
|
||||
`ResolverConfig` in the site patches, kube-dns `10.96.0.10`.
|
||||
- `netbird-service-sync` `SERVICE_CIDR` env in its Deployment.
|
||||
|
||||
Stay identical (no collision, because the test site routes nothing else into NetBird): pod CIDR
|
||||
`10.244.0.0/16`, LoadBalancer pool `10.2.99.0/24` / `fd99::/64`
|
||||
(`infrastructure/post-crds/cilium-bgp.yaml:22-25`) and the VIPs in it (Traefik `10.2.99.86`, Home
|
||||
Assistant `10.2.99.87`). The builder confirms nothing else routes these between the sites; if
|
||||
something does, that is a collision and the range becomes a site fact too.
|
||||
|
||||
Production keeps its current value. Its service CIDR cannot change on a running cluster anyway.
|
||||
|
||||
### 3. NetBird identity
|
||||
|
||||
Collision (verified in `ci/images/netbird-service-sync/controller.py`): the controller finds its
|
||||
network by name (`NETWORK_NAME`, default `k8s public ingress`, line 44) and its resource by the
|
||||
service CIDR; it treats every TLS service targeting that resource as its own and deletes those no
|
||||
local Ingress carries (`is_managed` line 212, delete line 257). A second cluster with the same name
|
||||
would delete production's services. `netbird-ingress` peers enrol with a setup key whose auto-group
|
||||
is `k8s-ingress-router`, the router group of production's network; test peers would become routers
|
||||
for production's traffic.
|
||||
|
||||
Per site: `NETWORK_NAME`, `ROUTER_GROUP_NAME` (and the policy name derived from the network name), the
|
||||
`netbird-ingress` setup key and its auto-group.
|
||||
|
||||
Decided (owner, 2026-10-18): the test site gets its own setup key that only allows access to where it
|
||||
is needed. The secret rotator creates it (production's key is minted by
|
||||
`infrastructure/openbao/rotation/netbird-ingress.yaml`, `NB_AUTO_GROUPS`). The test's peers get no
|
||||
production group, no production policy, no mesh routes and no mesh DNS: only what the proxy path to
|
||||
the test's own services needs. Production's routes and nameserver groups for `k8s-ingress-router` are
|
||||
set up by hand in NetBird, not in the repo; the test's peers must not be in that group. Peers join
|
||||
NetBird's "All" group automatically; the builder checks what "All" grants in production and makes
|
||||
sure the test's peers gain no access to production through it.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Production config is the spec. Per-site values are expressed in the site files (`site-homelab.yaml`,
|
||||
`site-hcloud.yaml`, `site-qemu.yaml`), the way `cilium-site` and `k8s-gateway-site` already are
|
||||
(inline ConfigMaps read via `valuesFrom`, Talos-created). The builder chooses the mechanism by which
|
||||
Flux-applied manifests read the site's domain and ranges (e.g. postBuild substitution from a
|
||||
site ConfigMap). Known trap: postBuild substitution blanks every undefined `${var}`, and some app
|
||||
manifests contain unrelated `${...}` syntax (`infrastructure/cluster-settings/cluster-settings.yaml:6-9`).
|
||||
- For production the change is a no-op: rendered manifests and live objects stay identical. Prove it
|
||||
(e.g. render before/after) per PR.
|
||||
- Changes reach production as reviewable, stacked PRs. Moving objects between pruned Kustomizations
|
||||
needs the prune handover used in #1307/#1317. Never force-push.
|
||||
- The test may create its own entries in production's NetBird account and Cloudflare zone, limited to
|
||||
its own names (owner, 2026-10-18). It must not modify or delete production's.
|
||||
- Egress containment stays: the test's Hetzner firewall blocks TCP to production's public edge
|
||||
(`proxy.renner.uno`, `bootstrap.sh` `hcloud_apply_egress`). NetBird management, signal and relay run
|
||||
on `netbird.renner.uno`, a different address, and stay reachable. Inbound traffic arrives through the
|
||||
NetBird tunnel. The builder must check that the test's routing peer works with TCP to the proxy
|
||||
hosts blocked (the proxy hosts' extra relays on TCP 33080 are then unreachable; UDP passes).
|
||||
- Stale production URLs in restored data stay as they are: Gitea's Woodpecker webhooks
|
||||
(`https://ci.renner.uno/api/hook?access_token=…`, valid tokens), Home Assistant's `.storage`
|
||||
(`external_url`, integrations), Immich's OIDC issuer in OpenBao `apps/immich`, WordPress post
|
||||
content. They must not reach production: the test resolves its own names in-cluster
|
||||
(k8s-gateway), and production names resolve publicly to the proxy, which the egress firewall
|
||||
blocks. The builder verifies this holds for each of these.
|
||||
- Let's Encrypt: the test site uses Let's Encrypt staging (owner, 2026-10-18: "staging certs are
|
||||
fine"), so test runs never count against the `renner.uno` limits production's renewals share (about
|
||||
40 Certificates per run). The ACME server therefore becomes a site fact; today `ACME_SERVER` is in
|
||||
the Flux-applied `infrastructure/cluster-settings/cluster-settings.yaml`, substituted into the
|
||||
ClusterIssuer. The owner accepts browser warnings when inspecting the test.
|
||||
- To verify, not assumed: what the Cloudflare external-dns instance would write for the test names.
|
||||
It runs `policy: upsert-only` with `--default-targets` set to a `*.cfargotunnel.com` name and
|
||||
`--force-default-targets` (`apps/external-dns/helmrelease.yaml`); the public wildcard `*.renner.uno`
|
||||
already resolves every test name to the proxy. A record it creates must not shadow the wildcard
|
||||
with an unreachable target.
|
||||
- Passkey step-up: some apps require a passkey step-up after login (blueprint `passkey-stepup.yaml`:
|
||||
Home Assistant, NetBird, Audiobookshelf, Immich, Gitea, Grafana). On the test site the owner would
|
||||
register a new passkey after password login; the builder checks the flow allows that.
|
||||
|
||||
## Decided (owner)
|
||||
|
||||
- Subdomain of `renner.uno` as the test base domain.
|
||||
- Reachable from the internet through the NetBird proxy, like production; 1:1 with production
|
||||
otherwise.
|
||||
- The test may create its own NetBird and Cloudflare entries, limited to its names.
|
||||
- Address ranges are site facts in the site files.
|
||||
- Only what must differ, differs.
|
||||
- Matrix out of scope; passkeys not a criterion, password login on the test.
|
||||
- The test's NetBird peers use their own rotator-minted setup key with access only where needed.
|
||||
- Features a site does not have are not applied there at all: their Kustomizations are suspended on
|
||||
that site, so Flux does not try them and `flux get ks` shows them as suspended instead of failing
|
||||
at a timeout ("noise", owner). The site file declares what the site has, like its other site facts:
|
||||
the Intel iGPU, the IoT VLAN, and the UniFi gateway. Talos creates these as a ConfigMap (the way
|
||||
`cilium-site` works), and Flux's root sync reads it to set `spec.suspend` on the affected
|
||||
Kustomizations. Affected today: llama-server and wiki (iGPU), home-assistant-iot, matter-server and
|
||||
music-assistant (IoT VLAN), external-dns-unifi (UniFi gateway; it does not run on the test
|
||||
cluster). otbr is suspended on every site already. The builder confirms the list against the
|
||||
current node-label gating (`hardware.renner.uno/intel-gpu`, `net.renner.uno/iot-vlan`, #1313); the
|
||||
node labels stay, they say which nodes of a site carry the hardware.
|
||||
- Uptime monitoring does not run on the test cluster (owner, 2026-10-20): the kuma-sync
|
||||
Kustomization follows `SITE_SUSPEND_UPTIME_MONITORING` like the other site exceptions. Its
|
||||
KumaMonitor objects stay with their apps and keep production URLs: they are monitors of the
|
||||
production Uptime Kuma, inert without the controller.
|
||||
- Let's Encrypt staging on the test site.
|
||||
|
||||
## Calls made in the design (not the owner's; check these)
|
||||
|
||||
- Only the service CIDR becomes a site range; pod CIDR and LoadBalancer pool stay identical, because
|
||||
with the restricted setup key nothing routes them between the sites.
|
||||
- The run-29 production ingress break was never explained. Run 30 turns on the parts that touch
|
||||
production's public path, so the plan enables them in stages and checks production after each:
|
||||
first `netbird-service-sync` for one pass (`ONCE`) against the test's own network, then the
|
||||
routing peers, and stops on the first sign of production impact.
|
||||
- The builder checks whether the NetBird proxy accepts services under the test subdomain without a
|
||||
separately registered custom domain (production services are custom domains `<app>.renner.uno`,
|
||||
wiki `[[netbird-reverse-proxy-custom-domains]]`). If a one-time registration is needed, it is the
|
||||
owner's step to approve.
|
||||
|
||||
## Left to the builder
|
||||
|
||||
- The exact test domain label and the test's service CIDR (must not overlap production's, the LAN, or
|
||||
NetBird's ranges).
|
||||
- The substitution / site-value mechanism and how to keep it safe against unrelated `${...}`,
|
||||
including on the root sync that sets `spec.suspend` (production must render unchanged: every
|
||||
feature present, nothing suspended that is not suspended today).
|
||||
- How the test's NetBird setup key and router group are created, within the constraints.
|
||||
- How the Let's Encrypt limit is respected.
|
||||
- Order and split of PRs, and the restore-test run that proves it (run 30), including inspecting the
|
||||
restored services through the internet.
|
||||
@@ -12,7 +12,7 @@ metadata:
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: audiobooks.renner.uno
|
||||
- host: audiobooks.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -25,4 +25,4 @@ spec:
|
||||
tls:
|
||||
- secretName: audiobookshelf-tls
|
||||
hosts:
|
||||
- audiobooks.renner.uno
|
||||
- audiobooks.${SITE_DOMAIN}
|
||||
|
||||
@@ -406,7 +406,7 @@ data:
|
||||
- id: ldap-outpost-cert
|
||||
model: authentik_crypto.certificatekeypair
|
||||
identifiers:
|
||||
name: ldap.renner.uno
|
||||
name: ldap.${SITE_DOMAIN}
|
||||
state: present
|
||||
attrs:
|
||||
certificate_data: !Env LDAP_OUTPOST_TLS_CRT
|
||||
@@ -427,7 +427,7 @@ data:
|
||||
state: present
|
||||
attrs:
|
||||
certificate: !KeyOf ldap-outpost-cert
|
||||
tls_server_name: ldap.renner.uno
|
||||
tls_server_name: ldap.${SITE_DOMAIN}
|
||||
|
||||
# Synology DSM (nas-1) LDAP integration. DSM's Directory Client
|
||||
# binds to the LDAP Outpost via the standalone LoadBalancer Service
|
||||
@@ -522,7 +522,7 @@ data:
|
||||
# one cert per handshake). DSM still binds on plaintext 389
|
||||
# today; this just makes 636 ready when DSM cuts over.
|
||||
certificate: !KeyOf ldap-outpost-cert
|
||||
tls_server_name: ldap.renner.uno
|
||||
tls_server_name: ldap.${SITE_DOMAIN}
|
||||
|
||||
- id: synology-application
|
||||
model: authentik_core.application
|
||||
@@ -608,7 +608,7 @@ data:
|
||||
gid_start_number: 5000
|
||||
mfa_support: false
|
||||
certificate: !KeyOf ldap-outpost-cert
|
||||
tls_server_name: ldap.renner.uno
|
||||
tls_server_name: ldap.${SITE_DOMAIN}
|
||||
|
||||
- id: unifi-application
|
||||
model: authentik_core.application
|
||||
@@ -703,7 +703,7 @@ data:
|
||||
log_level: info
|
||||
authentik_host: "http://authentik-server.authentik.svc.cluster.local"
|
||||
authentik_host_insecure: false
|
||||
authentik_host_browser: "https://auth.renner.uno/"
|
||||
authentik_host_browser: "https://auth.${SITE_DOMAIN}/"
|
||||
refresh_interval: "minutes=5"
|
||||
# Patched LDAP outpost: adds "sambaSamAccount" AND
|
||||
# "sambaDomain" to the hardcoded GetUserOCs() set so
|
||||
@@ -737,7 +737,7 @@ data:
|
||||
log_level: info
|
||||
authentik_host: "http://authentik-server.authentik.svc.cluster.local"
|
||||
authentik_host_insecure: false
|
||||
authentik_host_browser: "https://auth.renner.uno/"
|
||||
authentik_host_browser: "https://auth.${SITE_DOMAIN}/"
|
||||
refresh_interval: "minutes=5"
|
||||
container_image: null
|
||||
object_naming_template: "ak-outpost-%(name)s"
|
||||
@@ -820,7 +820,7 @@ data:
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
client_type: confidential
|
||||
redirect_uris:
|
||||
- url: https://home.renner.uno/auth/openid/callback
|
||||
- url: https://home.${SITE_DOMAIN}/auth/openid/callback
|
||||
matching_mode: strict
|
||||
- url: http://homeassistant.srv.renner.uno:8123/auth/openid/callback
|
||||
matching_mode: strict
|
||||
@@ -858,9 +858,9 @@ data:
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
client_type: confidential
|
||||
redirect_uris:
|
||||
- url: https://audiobooks.renner.uno/auth/openid/callback
|
||||
- url: https://audiobooks.${SITE_DOMAIN}/auth/openid/callback
|
||||
matching_mode: strict
|
||||
- url: https://audiobooks.renner.uno/auth/openid/mobile-redirect
|
||||
- url: https://audiobooks.${SITE_DOMAIN}/auth/openid/mobile-redirect
|
||||
matching_mode: strict
|
||||
|
||||
- model: authentik_providers_oauth2.oauth2provider
|
||||
@@ -875,9 +875,9 @@ data:
|
||||
redirect_uris:
|
||||
- url: app.immich:///oauth-callback
|
||||
matching_mode: strict
|
||||
- url: https://immich.renner.uno/auth/login
|
||||
- url: https://immich.${SITE_DOMAIN}/auth/login
|
||||
matching_mode: strict
|
||||
- url: https://immich.renner.uno/user-settings
|
||||
- url: https://immich.${SITE_DOMAIN}/user-settings
|
||||
matching_mode: strict
|
||||
|
||||
- model: authentik_providers_oauth2.oauth2provider
|
||||
@@ -890,7 +890,7 @@ data:
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
client_type: confidential
|
||||
redirect_uris:
|
||||
- url: https://gitea.renner.uno/user/oauth2/authentik/callback
|
||||
- url: https://gitea.${SITE_DOMAIN}/user/oauth2/authentik/callback
|
||||
matching_mode: strict
|
||||
|
||||
- model: authentik_providers_oauth2.oauth2provider
|
||||
@@ -903,7 +903,7 @@ data:
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
client_type: confidential
|
||||
redirect_uris:
|
||||
- url: https://dash.renner.uno/login/generic_oauth
|
||||
- url: https://dash.${SITE_DOMAIN}/login/generic_oauth
|
||||
matching_mode: strict
|
||||
|
||||
# Matrix (REN-567): upstream login for MAS (account.matrix.renner.uno).
|
||||
@@ -1013,7 +1013,7 @@ data:
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-email]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-offline_access]]
|
||||
redirect_uris:
|
||||
- url: https://hermes.renner.uno/auth/callback
|
||||
- url: https://hermes.${SITE_DOMAIN}/auth/callback
|
||||
matching_mode: strict
|
||||
|
||||
# The slug is the issuer path: https://auth.renner.uno/application/o/hermes/
|
||||
@@ -1025,7 +1025,7 @@ data:
|
||||
name: Hermes
|
||||
slug: hermes
|
||||
provider: !KeyOf hermes-provider
|
||||
meta_launch_url: https://hermes.renner.uno
|
||||
meta_launch_url: https://hermes.${SITE_DOMAIN}
|
||||
meta_description: "Hermes Agent dashboard"
|
||||
|
||||
# Only the Hermes Users group passes.
|
||||
@@ -1063,7 +1063,7 @@ data:
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-openid]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-profile]]
|
||||
redirect_uris:
|
||||
- url: https://wiki.renner.uno/signin-oidc
|
||||
- url: https://wiki.${SITE_DOMAIN}/signin-oidc
|
||||
matching_mode: strict
|
||||
|
||||
# The slug is the issuer path: https://auth.renner.uno/application/o/wiki/
|
||||
@@ -1075,7 +1075,7 @@ data:
|
||||
name: Wiki
|
||||
slug: wiki
|
||||
provider: !KeyOf wiki-provider
|
||||
meta_launch_url: https://wiki.renner.uno
|
||||
meta_launch_url: https://wiki.${SITE_DOMAIN}
|
||||
meta_description: "Homelab wiki"
|
||||
|
||||
# OpenProject, decommissioned. Application first: Application.provider
|
||||
|
||||
@@ -124,7 +124,9 @@ spec:
|
||||
- name: AK_URL
|
||||
value: http://authentik-server.authentik.svc.cluster.local
|
||||
- name: AUTHENTIK_HOST_BROWSER
|
||||
value: https://auth.renner.uno/
|
||||
value: https://auth.${SITE_DOMAIN}/
|
||||
- name: COOKIE_DOMAIN
|
||||
value: ${SITE_DOMAIN}
|
||||
- name: SERVICE_CONNECTION
|
||||
value: Local Kubernetes Cluster
|
||||
- name: DEFAULT_AUTHORIZATION_FLOW
|
||||
|
||||
@@ -167,11 +167,11 @@ spec:
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-cluster
|
||||
hosts:
|
||||
- auth.renner.uno
|
||||
- auth.${SITE_DOMAIN}
|
||||
tls:
|
||||
- secretName: authentik-tls
|
||||
hosts:
|
||||
- auth.renner.uno
|
||||
- auth.${SITE_DOMAIN}
|
||||
|
||||
worker:
|
||||
resources:
|
||||
|
||||
@@ -25,4 +25,4 @@ spec:
|
||||
name: letsencrypt-cluster
|
||||
kind: ClusterIssuer
|
||||
dnsNames:
|
||||
- ldap.renner.uno
|
||||
- ldap.${SITE_DOMAIN}
|
||||
|
||||
@@ -40,7 +40,7 @@ metadata:
|
||||
# so this Service produces no public record — LDAP stays
|
||||
# LAN-internal, which matches the threat model of the cleartext
|
||||
# bind on 389.
|
||||
external-dns.kubernetes.io/hostname: ldap.renner.uno
|
||||
external-dns.kubernetes.io/hostname: ldap.${SITE_DOMAIN}
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
externalTrafficPolicy: Local
|
||||
|
||||
@@ -9,10 +9,10 @@ spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- evcc.renner.uno
|
||||
- evcc.${SITE_DOMAIN}
|
||||
secretName: evcc-tls
|
||||
rules:
|
||||
- host: evcc.renner.uno
|
||||
- host: evcc.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
|
||||
@@ -48,7 +48,7 @@ spec:
|
||||
- --default-targets=d2386ccd-a665-41e7-8631-603fdafcb006.cfargotunnel.com
|
||||
- --force-default-targets
|
||||
domainFilters:
|
||||
- renner.uno
|
||||
- ${SITE_DOMAIN}
|
||||
sources:
|
||||
- ingress
|
||||
txtOwnerId: k8s-homelab
|
||||
|
||||
@@ -79,7 +79,7 @@ data:
|
||||
# Web Services
|
||||
- name: Web Service 1
|
||||
group: services
|
||||
url: https://auth.renner.uno/-/health/ready/
|
||||
url: https://auth.${SITE_DOMAIN}/-/health/ready/
|
||||
interval: 60s
|
||||
ui:
|
||||
hide-hostname: true
|
||||
@@ -93,7 +93,7 @@ data:
|
||||
|
||||
- name: Web Service 2
|
||||
group: services
|
||||
url: https://gitea.renner.uno/api/v1/settings/api
|
||||
url: https://gitea.${SITE_DOMAIN}/api/v1/settings/api
|
||||
interval: 60s
|
||||
ui:
|
||||
hide-hostname: true
|
||||
@@ -107,7 +107,7 @@ data:
|
||||
|
||||
- name: Web Service 3
|
||||
group: services
|
||||
url: https://immich.renner.uno/api/server/ping
|
||||
url: https://immich.${SITE_DOMAIN}/api/server/ping
|
||||
interval: 60s
|
||||
ui:
|
||||
hide-hostname: true
|
||||
@@ -121,7 +121,7 @@ data:
|
||||
|
||||
- name: Web Service 4
|
||||
group: services
|
||||
url: https://ci.renner.uno/healthz
|
||||
url: https://ci.${SITE_DOMAIN}/healthz
|
||||
interval: 60s
|
||||
ui:
|
||||
hide-hostname: true
|
||||
@@ -134,7 +134,7 @@ data:
|
||||
|
||||
- name: Web Service 5
|
||||
group: services
|
||||
url: https://dash.renner.uno/api/health
|
||||
url: https://dash.${SITE_DOMAIN}/api/health
|
||||
interval: 60s
|
||||
ui:
|
||||
hide-hostname: true
|
||||
@@ -148,7 +148,7 @@ data:
|
||||
|
||||
- name: Web Service 6
|
||||
group: services
|
||||
url: https://home.renner.uno
|
||||
url: https://home.${SITE_DOMAIN}
|
||||
interval: 60s
|
||||
ui:
|
||||
hide-hostname: true
|
||||
@@ -166,7 +166,7 @@ data:
|
||||
|
||||
- name: Web Service 8
|
||||
group: services
|
||||
url: https://audiobooks.renner.uno/healthcheck
|
||||
url: https://audiobooks.${SITE_DOMAIN}/healthcheck
|
||||
interval: 60s
|
||||
ui:
|
||||
hide-hostname: true
|
||||
@@ -179,7 +179,7 @@ data:
|
||||
|
||||
- name: Web Service 9
|
||||
group: services
|
||||
url: https://music.renner.uno
|
||||
url: https://music.${SITE_DOMAIN}
|
||||
interval: 60s
|
||||
ui:
|
||||
hide-hostname: true
|
||||
@@ -197,7 +197,7 @@ data:
|
||||
# unauthenticated request may redirect.
|
||||
- name: Web Service 10
|
||||
group: services
|
||||
url: https://trmnl.renner.uno
|
||||
url: https://trmnl.${SITE_DOMAIN}
|
||||
interval: 60s
|
||||
ui:
|
||||
hide-hostname: true
|
||||
@@ -220,7 +220,7 @@ data:
|
||||
# only because their targets share a namespace with the prober.
|
||||
- name: Web Service 11
|
||||
group: services
|
||||
url: https://paperless.renner.uno
|
||||
url: https://paperless.${SITE_DOMAIN}
|
||||
interval: 60s
|
||||
ui:
|
||||
hide-hostname: true
|
||||
|
||||
@@ -13,10 +13,10 @@ spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- status.renner.uno
|
||||
- status.${SITE_DOMAIN}
|
||||
secretName: gatus-tls
|
||||
rules:
|
||||
- host: status.renner.uno
|
||||
- host: status.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
|
||||
@@ -35,10 +35,10 @@ spec:
|
||||
# Only for the 401 on tokenless requests; clients send their own Gitea PAT.
|
||||
- -oauth
|
||||
- -oauth-public-url
|
||||
- https://gitea-mcp.renner.uno
|
||||
- https://gitea-mcp.${SITE_DOMAIN}
|
||||
# Also advertised to clients as the OAuth authorization server, so it must be the public URL.
|
||||
- -H
|
||||
- https://gitea.renner.uno
|
||||
- https://gitea.${SITE_DOMAIN}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
|
||||
@@ -12,7 +12,7 @@ metadata:
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: gitea-mcp.renner.uno
|
||||
- host: gitea-mcp.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -25,4 +25,4 @@ spec:
|
||||
tls:
|
||||
- secretName: gitea-mcp-tls
|
||||
hosts:
|
||||
- gitea-mcp.renner.uno
|
||||
- gitea-mcp.${SITE_DOMAIN}
|
||||
|
||||
@@ -16,7 +16,7 @@ metadata:
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: git.renner.uno
|
||||
- host: git.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -26,7 +26,7 @@ spec:
|
||||
name: gitea
|
||||
port:
|
||||
number: 3000
|
||||
- host: gitea.renner.uno
|
||||
- host: gitea.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -39,5 +39,5 @@ spec:
|
||||
tls:
|
||||
- secretName: gitea-tls
|
||||
hosts:
|
||||
- git.renner.uno
|
||||
- gitea.renner.uno
|
||||
- git.${SITE_DOMAIN}
|
||||
- gitea.${SITE_DOMAIN}
|
||||
|
||||
@@ -63,7 +63,9 @@ spec:
|
||||
- name: GITEA__server__SSH_PORT
|
||||
value: "22"
|
||||
- name: GITEA__server__SSH_DOMAIN
|
||||
value: gitea.renner.uno
|
||||
value: gitea.${SITE_DOMAIN}
|
||||
- name: GITEA__server__ROOT_URL
|
||||
value: https://gitea.${SITE_DOMAIN}
|
||||
# The LXC ran RUN_USER=gitea, so every existing remote/clone URL is
|
||||
# gitea@gitea.renner.uno. The rootless image runs as `git`, so the
|
||||
# built-in SSH server would only accept `git@` and reject `gitea@`.
|
||||
@@ -117,7 +119,7 @@ spec:
|
||||
value: "true"
|
||||
# Webhook targets Gitea may call; every configured webhook host.
|
||||
- name: GITEA__security__ALLOWED_HOST_LIST
|
||||
value: "ci.renner.uno,wiki.ai.svc.cluster.local,review-bot.review-bot.svc.cluster.local"
|
||||
value: "ci.${SITE_DOMAIN},wiki.ai.svc.cluster.local,review-bot.review-bot.svc.cluster.local"
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 3000
|
||||
|
||||
@@ -191,7 +191,7 @@ spec:
|
||||
- config
|
||||
- set
|
||||
- mcp_servers.homeassistant
|
||||
- '{"url":"https://home.renner.uno/api/mcp","headers":{"Authorization":"Bearer ${HA_TOKEN}"},"trust":"untrusted"}'
|
||||
- '{"url":"https://home.${SITE_DOMAIN}/api/mcp","headers":{"Authorization":"Bearer $${HA_TOKEN}"},"trust":"untrusted"}'
|
||||
env:
|
||||
- name: HERMES_HOME
|
||||
value: /opt/data
|
||||
@@ -219,7 +219,7 @@ spec:
|
||||
- -c
|
||||
- |
|
||||
H=/opt/hermes/.venv/bin/hermes
|
||||
# Mirror default's mcp_servers.homeassistant (config-mcp-homeassistant); ${HA_TOKEN} stays literal.
|
||||
# Mirror default's mcp_servers.homeassistant (config-mcp-homeassistant); $${HA_TOKEN} stays literal.
|
||||
HA_MCP=$(PYTHONPATH=/opt/hermes /opt/hermes/.venv/bin/python -c '
|
||||
import json
|
||||
import hermes_yaml as yaml
|
||||
@@ -359,7 +359,7 @@ spec:
|
||||
{
|
||||
echo 'logins:'
|
||||
echo ' - name: hermes-bot'
|
||||
echo ' url: https://gitea.renner.uno'
|
||||
echo ' url: https://gitea.${SITE_DOMAIN}'
|
||||
echo " token: $(cat /var/run/secrets/gitea/api-token)"
|
||||
echo ' default: true'
|
||||
echo ' user: hermes-bot'
|
||||
@@ -385,11 +385,11 @@ spec:
|
||||
- name: HERMES_DASHBOARD
|
||||
value: "1"
|
||||
- name: HERMES_DASHBOARD_PUBLIC_URL
|
||||
value: https://hermes.renner.uno
|
||||
value: https://hermes.${SITE_DOMAIN}
|
||||
# Dashboard login: Authentik OIDC, public PKCE client (blueprint
|
||||
# in apps/authentik/blueprints-configmap.yaml, group Hermes Users).
|
||||
- name: HERMES_DASHBOARD_OIDC_ISSUER
|
||||
value: https://auth.renner.uno/application/o/hermes/
|
||||
value: https://auth.${SITE_DOMAIN}/application/o/hermes/
|
||||
- name: HERMES_DASHBOARD_OIDC_CLIENT_ID
|
||||
value: hermes-dashboard
|
||||
# offline_access: Authentik issues a refresh token only when requested.
|
||||
@@ -399,6 +399,12 @@ spec:
|
||||
value: "true"
|
||||
- name: API_SERVER_HOST
|
||||
value: 0.0.0.0
|
||||
# Home Assistant for the ha-updates skill (scripts/ha.py).
|
||||
- name: HA_URL
|
||||
value: https://home.${SITE_DOMAIN}
|
||||
# Gitea issues API for the alert-monitor skill (scripts/alerts.py).
|
||||
- name: GITEA_API
|
||||
value: https://gitea.${SITE_DOMAIN}/api/v1/repos/konairius/homelab
|
||||
# bao: log in with the pod's ServiceAccount token, role hermes
|
||||
# (kubernetes/infrastructure/openbao/policies/hermes.hcl).
|
||||
- name: BAO_ADDR
|
||||
@@ -411,7 +417,7 @@ spec:
|
||||
- name: GIT_CONFIG_COUNT
|
||||
value: "3"
|
||||
- name: GIT_CONFIG_KEY_0
|
||||
value: credential.https://gitea.renner.uno.helper
|
||||
value: credential.https://gitea.${SITE_DOMAIN}.helper
|
||||
- name: GIT_CONFIG_VALUE_0
|
||||
value: '!f() { test "$1" = get && echo username=hermes-bot && echo "password=$(cat /var/run/secrets/gitea/api-token)"; }; f'
|
||||
- name: GIT_CONFIG_KEY_1
|
||||
|
||||
@@ -17,10 +17,10 @@ spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- hermes.renner.uno
|
||||
- hermes.${SITE_DOMAIN}
|
||||
secretName: hermes-tls
|
||||
rules:
|
||||
- host: hermes.renner.uno
|
||||
- host: hermes.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -44,10 +44,10 @@ spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- hermes-api.renner.uno
|
||||
- hermes-api.${SITE_DOMAIN}
|
||||
secretName: hermes-api-tls
|
||||
rules:
|
||||
- host: hermes-api.renner.uno
|
||||
- host: hermes-api.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
|
||||
@@ -16,7 +16,7 @@ configMapGenerator:
|
||||
- name: hermes-matrix-config
|
||||
namespace: hermes
|
||||
literals:
|
||||
- MATRIX_HOMESERVER=https://matrix.renner.uno
|
||||
- MATRIX_HOMESERVER=https://matrix.${SITE_DOMAIN}
|
||||
- MATRIX_USER_ID=@hermes:matrix.renner.uno
|
||||
- MATRIX_ALLOWED_USERS=@k.renner:matrix.renner.uno
|
||||
- MATRIX_E2EE_MODE=required
|
||||
|
||||
@@ -31,5 +31,8 @@ configMapGenerator:
|
||||
namespace: home-assistant
|
||||
files:
|
||||
- api.conf.template=shelly-manager-api-proxy.conf
|
||||
options:
|
||||
annotations:
|
||||
kustomize.toolkit.fluxcd.io/substitute: disabled
|
||||
generatorOptions:
|
||||
disableNameSuffixHash: true
|
||||
|
||||
@@ -18,7 +18,7 @@ metadata:
|
||||
spec:
|
||||
ingressClassName: authentik-gate
|
||||
rules:
|
||||
- host: shelly-manager.home.renner.uno
|
||||
- host: shelly-manager.home.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -31,4 +31,4 @@ spec:
|
||||
tls:
|
||||
- secretName: shelly-manager-tls
|
||||
hosts:
|
||||
- shelly-manager.home.renner.uno
|
||||
- shelly-manager.home.${SITE_DOMAIN}
|
||||
|
||||
@@ -18,7 +18,7 @@ metadata:
|
||||
lb-pool: bgp
|
||||
annotations:
|
||||
lbipam.cilium.io/ips: "10.2.99.87,fd99::87"
|
||||
external-dns.kubernetes.io/hostname: ha.renner.uno
|
||||
external-dns.kubernetes.io/hostname: ha.${SITE_DOMAIN}
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
externalTrafficPolicy: Local
|
||||
|
||||
@@ -352,7 +352,7 @@ spec:
|
||||
name: home-assistant-config-sync-token
|
||||
key: token
|
||||
- name: GITEA_URL
|
||||
value: https://gitea.renner.uno
|
||||
value: https://gitea.${SITE_DOMAIN}
|
||||
- name: GITEA_REPO
|
||||
value: konairius/homeassistant
|
||||
- name: GITEA_USER
|
||||
|
||||
@@ -18,7 +18,7 @@ spec:
|
||||
# proxy.
|
||||
ingressClassName: authentik-gate
|
||||
rules:
|
||||
- host: esphome.home.renner.uno
|
||||
- host: esphome.home.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -31,4 +31,4 @@ spec:
|
||||
tls:
|
||||
- secretName: esphome-tls
|
||||
hosts:
|
||||
- esphome.home.renner.uno
|
||||
- esphome.home.${SITE_DOMAIN}
|
||||
|
||||
@@ -12,7 +12,7 @@ metadata:
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: home.renner.uno
|
||||
- host: home.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -25,4 +25,4 @@ spec:
|
||||
tls:
|
||||
- secretName: home-assistant-tls
|
||||
hosts:
|
||||
- home.renner.uno
|
||||
- home.${SITE_DOMAIN}
|
||||
|
||||
@@ -67,6 +67,9 @@ configMapGenerator:
|
||||
namespace: home-assistant
|
||||
files:
|
||||
- default.conf.template=proxy.conf
|
||||
options:
|
||||
annotations:
|
||||
kustomize.toolkit.fluxcd.io/substitute: disabled
|
||||
# config-sync sidecar script. Hash suffix disabled (name stays stable), so
|
||||
# editing config-sync.sh does not auto-roll the pod — restart manually with
|
||||
# `kubectl rollout restart deploy/home-assistant -n home-assistant`.
|
||||
@@ -74,6 +77,9 @@ configMapGenerator:
|
||||
namespace: home-assistant
|
||||
files:
|
||||
- sync.sh=config-sync.sh
|
||||
options:
|
||||
annotations:
|
||||
kustomize.toolkit.fluxcd.io/substitute: disabled
|
||||
# Z2M re-interview watchdog sidecar script. Hash suffix disabled (name stays
|
||||
# stable), so editing zigbee2mqtt-interview-watch.sh does not auto-roll the
|
||||
# pod — restart manually with
|
||||
@@ -82,6 +88,9 @@ configMapGenerator:
|
||||
namespace: home-assistant
|
||||
files:
|
||||
- watch.sh=zigbee2mqtt-interview-watch.sh
|
||||
options:
|
||||
annotations:
|
||||
kustomize.toolkit.fluxcd.io/substitute: disabled
|
||||
# Z2M discovery guard sidecar script (gitea issue #381). Hash suffix disabled
|
||||
# (name stays stable), so editing zigbee2mqtt-discovery-guard.sh does not
|
||||
# auto-roll the pod — restart manually with
|
||||
@@ -90,6 +99,9 @@ configMapGenerator:
|
||||
namespace: home-assistant
|
||||
files:
|
||||
- guard.sh=zigbee2mqtt-discovery-guard.sh
|
||||
options:
|
||||
annotations:
|
||||
kustomize.toolkit.fluxcd.io/substitute: disabled
|
||||
# Z2M external converters, copied into /app/data/external_converters by the
|
||||
# seed-data init container on every start. Hash suffix disabled (name stays
|
||||
# stable), so editing a converter does not auto-roll the pod — restart
|
||||
|
||||
@@ -9,7 +9,7 @@ spec:
|
||||
name: letsencrypt-cluster
|
||||
kind: ClusterIssuer
|
||||
dnsNames:
|
||||
- mqtt.renner.uno
|
||||
- mqtt.${SITE_DOMAIN}
|
||||
---
|
||||
# Server certificate of the mutual-TLS listener :8884 (REN-802). Issued by
|
||||
# mqtt-ca because certificate clients and mqtt-account-controller trust only
|
||||
|
||||
@@ -19,7 +19,7 @@ metadata:
|
||||
# LAN can connect with `mqtt.renner.uno` (matches the broker cert
|
||||
# SAN) instead of the bare IP. No Cloudflare label — kept off the
|
||||
# public DNS; UniFi serves a split-DNS answer for LAN clients.
|
||||
external-dns.kubernetes.io/hostname: mqtt.renner.uno
|
||||
external-dns.kubernetes.io/hostname: mqtt.${SITE_DOMAIN}
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
externalTrafficPolicy: Local
|
||||
|
||||
@@ -19,7 +19,7 @@ metadata:
|
||||
spec:
|
||||
ingressClassName: authentik-gate
|
||||
rules:
|
||||
- host: vscode.home.renner.uno
|
||||
- host: vscode.home.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -32,4 +32,4 @@ spec:
|
||||
tls:
|
||||
- secretName: home-assistant-vscode-tls
|
||||
hosts:
|
||||
- vscode.home.renner.uno
|
||||
- vscode.home.${SITE_DOMAIN}
|
||||
|
||||
@@ -3,6 +3,8 @@ kind: Deployment
|
||||
metadata:
|
||||
name: zigbee2mqtt
|
||||
namespace: home-assistant
|
||||
annotations:
|
||||
kustomize.toolkit.fluxcd.io/substitute: disabled
|
||||
labels:
|
||||
app.kubernetes.io/name: zigbee2mqtt
|
||||
spec:
|
||||
|
||||
@@ -16,7 +16,7 @@ spec:
|
||||
# Traefik's websecure entrypoint passes it through.
|
||||
ingressClassName: authentik-gate
|
||||
rules:
|
||||
- host: zigbee2mqtt.home.renner.uno
|
||||
- host: zigbee2mqtt.home.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -29,4 +29,4 @@ spec:
|
||||
tls:
|
||||
- secretName: zigbee2mqtt-tls
|
||||
hosts:
|
||||
- zigbee2mqtt.home.renner.uno
|
||||
- zigbee2mqtt.home.${SITE_DOMAIN}
|
||||
|
||||
@@ -109,7 +109,7 @@ metadata:
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: ical-proxy.renner.uno
|
||||
- host: ical-proxy.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -122,4 +122,4 @@ spec:
|
||||
tls:
|
||||
- secretName: ical-proxy-tls
|
||||
hosts:
|
||||
- ical-proxy.renner.uno
|
||||
- ical-proxy.${SITE_DOMAIN}
|
||||
|
||||
@@ -12,7 +12,7 @@ metadata:
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: immich.renner.uno
|
||||
- host: immich.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -25,7 +25,7 @@ spec:
|
||||
tls:
|
||||
- secretName: immich-tls
|
||||
hosts:
|
||||
- immich.renner.uno
|
||||
- immich.${SITE_DOMAIN}
|
||||
---
|
||||
# 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
|
||||
@@ -42,7 +42,7 @@ metadata:
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: photos.renner.uno
|
||||
- host: photos.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -55,4 +55,4 @@ spec:
|
||||
tls:
|
||||
- secretName: immich-photos-tls
|
||||
hosts:
|
||||
- photos.renner.uno
|
||||
- photos.${SITE_DOMAIN}
|
||||
|
||||
@@ -13,7 +13,7 @@ data:
|
||||
IMMICH_MEDIA_LOCATION: "/data"
|
||||
UPLOAD_LOCATION: "/data/uploads"
|
||||
IMMICH_CONFIG_FILE: "/config/immich-config.json"
|
||||
IMMICH_PUBLIC_URL: "https://immich.renner.uno"
|
||||
IMMICH_PUBLIC_URL: "https://immich.${SITE_DOMAIN}"
|
||||
IMMICH_MACHINE_LEARNING_URL: "http://immich-machine-learning:3003"
|
||||
IMMICH_METRICS: "true"
|
||||
IMMICH_TELEMETRY_INCLUDE: "all"
|
||||
|
||||
@@ -28,10 +28,10 @@ spec:
|
||||
ingressClassName: authentik-gate
|
||||
tls:
|
||||
- hosts:
|
||||
- llm.renner.uno
|
||||
- llm.${SITE_DOMAIN}
|
||||
secretName: llama-server-tls
|
||||
rules:
|
||||
- host: llm.renner.uno
|
||||
- host: llm.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
|
||||
@@ -42,10 +42,10 @@ spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- wiki-embed.renner.uno
|
||||
- wiki-embed.${SITE_DOMAIN}
|
||||
secretName: wiki-embed-tls
|
||||
rules:
|
||||
- host: wiki-embed.renner.uno
|
||||
- host: wiki-embed.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
|
||||
@@ -17,6 +17,8 @@ kind: ServiceMonitor
|
||||
metadata:
|
||||
name: cilium-agent
|
||||
namespace: kube-system
|
||||
annotations:
|
||||
kustomize.toolkit.fluxcd.io/substitute: disabled
|
||||
labels:
|
||||
app.kubernetes.io/name: cilium-agent
|
||||
app.kubernetes.io/part-of: cilium
|
||||
|
||||
@@ -21,10 +21,10 @@ spec:
|
||||
targets:
|
||||
staticConfig:
|
||||
static:
|
||||
- https://gitea.renner.uno
|
||||
- https://immich.renner.uno
|
||||
- https://auth.renner.uno
|
||||
- https://home.renner.uno
|
||||
- https://gitea.${SITE_DOMAIN}
|
||||
- https://immich.${SITE_DOMAIN}
|
||||
- https://auth.${SITE_DOMAIN}
|
||||
- https://home.${SITE_DOMAIN}
|
||||
# prometheus.renner.uno and alerts.renner.uno are Authentik-gated
|
||||
# (PR #147 / #146). These EXTERNAL probes now measure the full
|
||||
# gated path — DNS, TLS, Traefik, the gate outpost — following the
|
||||
@@ -32,9 +32,9 @@ spec:
|
||||
# not app health: with the gate up, they stay green even if the app
|
||||
# behind it is dead (the probe never reaches the app). Real app
|
||||
# health comes from the two INTERNAL /-/healthy probes below.
|
||||
- https://prometheus.renner.uno
|
||||
- https://alerts.renner.uno
|
||||
- https://dash.renner.uno
|
||||
- https://prometheus.${SITE_DOMAIN}
|
||||
- https://alerts.${SITE_DOMAIN}
|
||||
- https://dash.${SITE_DOMAIN}
|
||||
# Offsite backup target (Hetzner Object Storage). Bare endpoint
|
||||
# returns 200. CNPG WAL archiving + Velero both depend on it;
|
||||
# this probe replaces the old GarageDown signal on nas-1.
|
||||
@@ -56,11 +56,11 @@ spec:
|
||||
# scans, the deploy targets — so an outage here is silent by
|
||||
# construction: the thing that would have told you is the thing that
|
||||
# is down.
|
||||
- https://ci.renner.uno
|
||||
- https://ci.${SITE_DOMAIN}
|
||||
# Hermes. /api/status is public and answers 200 while the gateway
|
||||
# runs. It runs the alert-monitor job that files the [alert] issues,
|
||||
# so its outage is silent the same way Woodpecker's is.
|
||||
- https://hermes.renner.uno/api/status
|
||||
- https://hermes.${SITE_DOMAIN}/api/status
|
||||
# OpenBao. Probes the health API rather than the UI on purpose: a
|
||||
# SEALED instance still serves the UI (307 -> /ui/ -> 200) while every
|
||||
# ExternalSecret in the cluster fails, which is exactly the
|
||||
@@ -71,12 +71,12 @@ spec:
|
||||
# standby, so the bare path would be permanently red. With the
|
||||
# parameter, 200 means initialized, unsealed and serving, on whichever
|
||||
# node answers; 503 sealed and 501 uninitialized both stay red.
|
||||
- https://openbao.renner.uno/v1/sys/health?standbyok=true
|
||||
- https://openbao.${SITE_DOMAIN}/v1/sys/health?standbyok=true
|
||||
# ical-proxy serves an unauthenticated /health that answers 200 with
|
||||
# {"status":"healthy"}. Unlike the gated hostnames this is real app
|
||||
# health, not reachability — the root path returns 404 by design, since
|
||||
# the service only answers on calendar paths.
|
||||
- https://ical-proxy.renner.uno/health
|
||||
- https://ical-proxy.${SITE_DOMAIN}/health
|
||||
# APP-HEALTH probes for the gated monitoring UIs, from the correct
|
||||
# in-cluster vantage (monitoring-vantage-point-rule): the gate is
|
||||
# only on the ingress, so the ClusterIP /-/healthy endpoints are
|
||||
|
||||
@@ -30,7 +30,7 @@ spec:
|
||||
# instance is the resolver and identical across these Probes, so the
|
||||
# queried name has to come from a label.
|
||||
labels:
|
||||
dns_name: ha.renner.uno
|
||||
dns_name: ha.${SITE_DOMAIN}
|
||||
---
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: Probe
|
||||
@@ -52,7 +52,7 @@ spec:
|
||||
- "[fd00::1]:53"
|
||||
- "10.2.40.1:53"
|
||||
labels:
|
||||
dns_name: mqtt.renner.uno
|
||||
dns_name: mqtt.${SITE_DOMAIN}
|
||||
---
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: Probe
|
||||
@@ -74,7 +74,7 @@ spec:
|
||||
- "[fd00::1]:53"
|
||||
- "10.2.40.1:53"
|
||||
labels:
|
||||
dns_name: ldap.renner.uno
|
||||
dns_name: ldap.${SITE_DOMAIN}
|
||||
---
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: Probe
|
||||
@@ -96,7 +96,7 @@ spec:
|
||||
- "[fd00::1]:53"
|
||||
- "10.2.40.1:53"
|
||||
labels:
|
||||
dns_name: syslog.renner.uno
|
||||
dns_name: syslog.${SITE_DOMAIN}
|
||||
---
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: Probe
|
||||
@@ -118,7 +118,7 @@ spec:
|
||||
- "[fd00::1]:53"
|
||||
- "10.2.40.1:53"
|
||||
labels:
|
||||
dns_name: ftp.paperless.renner.uno
|
||||
dns_name: ftp.paperless.${SITE_DOMAIN}
|
||||
---
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: Probe
|
||||
@@ -140,4 +140,4 @@ spec:
|
||||
- "[fd00::1]:53"
|
||||
- "10.2.40.1:53"
|
||||
labels:
|
||||
dns_name: syncthing.paperless.renner.uno
|
||||
dns_name: syncthing.paperless.${SITE_DOMAIN}
|
||||
|
||||
@@ -13,12 +13,12 @@ spec:
|
||||
rules:
|
||||
- alert: WoodpeckerDown
|
||||
# Probe target defined in probes-blackbox.yaml. Root, ungated, 200.
|
||||
expr: probe_success{job="blackbox-http", instance="https://ci.renner.uno"} == 0
|
||||
expr: probe_success{job="blackbox-http", instance="https://ci.${SITE_DOMAIN}"} == 0
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Woodpecker is not answering on ci.renner.uno"
|
||||
summary: "Woodpecker is not answering on ci.${SITE_DOMAIN}"
|
||||
description: >-
|
||||
Most scheduled jobs in this repo run here: issue-fix, the nightly
|
||||
scans, and every deploy target. Nothing else reports this outage,
|
||||
@@ -27,12 +27,12 @@ spec:
|
||||
this alert exists.
|
||||
- alert: HermesDown
|
||||
# Probe target defined in probes-blackbox.yaml. Public status API, 200.
|
||||
expr: probe_success{job="blackbox-http", instance="https://hermes.renner.uno/api/status"} == 0
|
||||
expr: probe_success{job="blackbox-http", instance="https://hermes.${SITE_DOMAIN}/api/status"} == 0
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Hermes is not answering on hermes.renner.uno"
|
||||
summary: "Hermes is not answering on hermes.${SITE_DOMAIN}"
|
||||
description: >-
|
||||
Hermes runs the alert-monitor job that files the [alert] issues,
|
||||
and the ha-updates job. While it is down no new alert becomes an
|
||||
|
||||
@@ -36,7 +36,7 @@ spec:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Public edge unreachable from the WAN (all members)"
|
||||
description: "All IP-pinned WAN-path probes of the reverse-proxy edge (proxy-2 v4+v6, proxy-1 v6) have failed for 5 minutes — external clients cannot reach home.renner.uno and the other published apps. Check the netcup proxies (netbird-proxy containers), the mesh path to the cluster, and cluster Traefik."
|
||||
description: "All IP-pinned WAN-path probes of the reverse-proxy edge (proxy-2 v4+v6, proxy-1 v6) have failed for 5 minutes — external clients cannot reach home.${SITE_DOMAIN} and the other published apps. Check the netcup proxies (netbird-proxy containers), the mesh path to the cluster, and cluster Traefik."
|
||||
|
||||
- alert: NetbirdPublicEdgeMemberDown
|
||||
# A single edge member failing WAN reachability: DNS round-robin
|
||||
|
||||
@@ -15,7 +15,7 @@ metadata:
|
||||
spec:
|
||||
ingressClassName: authentik-gate
|
||||
rules:
|
||||
- host: alerts.renner.uno
|
||||
- host: alerts.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -28,4 +28,4 @@ spec:
|
||||
tls:
|
||||
- secretName: alertmanager-tls
|
||||
hosts:
|
||||
- alerts.renner.uno
|
||||
- alerts.${SITE_DOMAIN}
|
||||
|
||||
@@ -56,12 +56,18 @@ configMapGenerator:
|
||||
- name: dashboard-heatpump-roi
|
||||
files:
|
||||
- heatpump-roi.json
|
||||
options:
|
||||
annotations:
|
||||
kustomize.toolkit.fluxcd.io/substitute: disabled
|
||||
- name: dashboard-homeassistant-overview
|
||||
files:
|
||||
- homeassistant-overview.json
|
||||
- name: dashboard-home-climate
|
||||
files:
|
||||
- home-climate.json
|
||||
options:
|
||||
annotations:
|
||||
kustomize.toolkit.fluxcd.io/substitute: disabled
|
||||
- name: dashboard-home-energy
|
||||
files:
|
||||
- home-energy.json
|
||||
@@ -98,3 +104,6 @@ configMapGenerator:
|
||||
- name: dashboard-pv-system-roi
|
||||
files:
|
||||
- pv-system-roi.json
|
||||
options:
|
||||
annotations:
|
||||
kustomize.toolkit.fluxcd.io/substitute: disabled
|
||||
|
||||
@@ -9,7 +9,7 @@ spec:
|
||||
name: letsencrypt-cluster
|
||||
kind: ClusterIssuer
|
||||
dnsNames:
|
||||
- syslog.renner.uno
|
||||
- syslog.${SITE_DOMAIN}
|
||||
privateKey:
|
||||
rotationPolicy: Always
|
||||
---
|
||||
@@ -231,7 +231,7 @@ metadata:
|
||||
lb-pool: bgp
|
||||
annotations:
|
||||
io.cilium/lb-ipam-ips: "10.2.99.84,fd99::84"
|
||||
external-dns.kubernetes.io/hostname: syslog.renner.uno
|
||||
external-dns.kubernetes.io/hostname: syslog.${SITE_DOMAIN}
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
externalTrafficPolicy: Local
|
||||
|
||||
@@ -56,9 +56,9 @@ spec:
|
||||
http:
|
||||
fail_if_not_ssl: true
|
||||
tls_config:
|
||||
server_name: home.renner.uno
|
||||
server_name: home.${SITE_DOMAIN}
|
||||
headers:
|
||||
Host: home.renner.uno
|
||||
Host: home.${SITE_DOMAIN}
|
||||
# Plain TCP connect, used by the blackbox-tcp-relays Probe for the
|
||||
# NetBird relay ws listeners on :33080. Address family is spelled
|
||||
# out as ip6-then-fallback (blackbox's own tcp default, pinned so
|
||||
@@ -87,7 +87,7 @@ spec:
|
||||
prober: dns
|
||||
timeout: 5s
|
||||
dns:
|
||||
query_name: ha.renner.uno
|
||||
query_name: ha.${SITE_DOMAIN}
|
||||
query_type: A
|
||||
valid_rcodes: [NOERROR]
|
||||
validate_answer_rrs:
|
||||
@@ -97,7 +97,7 @@ spec:
|
||||
prober: dns
|
||||
timeout: 5s
|
||||
dns:
|
||||
query_name: mqtt.renner.uno
|
||||
query_name: mqtt.${SITE_DOMAIN}
|
||||
query_type: A
|
||||
valid_rcodes: [NOERROR]
|
||||
validate_answer_rrs:
|
||||
@@ -107,7 +107,7 @@ spec:
|
||||
prober: dns
|
||||
timeout: 5s
|
||||
dns:
|
||||
query_name: ldap.renner.uno
|
||||
query_name: ldap.${SITE_DOMAIN}
|
||||
query_type: A
|
||||
valid_rcodes: [NOERROR]
|
||||
validate_answer_rrs:
|
||||
@@ -117,7 +117,7 @@ spec:
|
||||
prober: dns
|
||||
timeout: 5s
|
||||
dns:
|
||||
query_name: syslog.renner.uno
|
||||
query_name: syslog.${SITE_DOMAIN}
|
||||
query_type: A
|
||||
valid_rcodes: [NOERROR]
|
||||
validate_answer_rrs:
|
||||
@@ -127,7 +127,7 @@ spec:
|
||||
prober: dns
|
||||
timeout: 5s
|
||||
dns:
|
||||
query_name: ftp.paperless.renner.uno
|
||||
query_name: ftp.paperless.${SITE_DOMAIN}
|
||||
query_type: A
|
||||
valid_rcodes: [NOERROR]
|
||||
validate_answer_rrs:
|
||||
@@ -137,7 +137,7 @@ spec:
|
||||
prober: dns
|
||||
timeout: 5s
|
||||
dns:
|
||||
query_name: syncthing.paperless.renner.uno
|
||||
query_name: syncthing.paperless.${SITE_DOMAIN}
|
||||
query_type: A
|
||||
valid_rcodes: [NOERROR]
|
||||
validate_answer_rrs:
|
||||
|
||||
@@ -75,7 +75,7 @@ spec:
|
||||
podMonitorSelectorNilUsesHelmValues: false
|
||||
# Rewrites the in-cluster URL embedded in alert generatorURLs and
|
||||
# status pages so click-through from emails works.
|
||||
externalUrl: https://prometheus.renner.uno
|
||||
externalUrl: https://prometheus.${SITE_DOMAIN}
|
||||
# Time retention is the one in force; the size cap is a backstop
|
||||
# (REN-107, owner decision: 30 days on the existing disk). This was
|
||||
# 90d/30GB, and at ~1 GB/day of ingest the 30GiB cap bound first: the
|
||||
@@ -135,11 +135,11 @@ spec:
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-cluster
|
||||
hosts:
|
||||
- dash.renner.uno
|
||||
- dash.${SITE_DOMAIN}
|
||||
tls:
|
||||
- secretName: grafana-tls
|
||||
hosts:
|
||||
- dash.renner.uno
|
||||
- dash.${SITE_DOMAIN}
|
||||
# Postgres backend (gf-pg CNPG cluster). SQLite-on-RBD made schema
|
||||
# migrations on cold rebuilds blow past the liveness probe budget;
|
||||
# Postgres batches WAL fsyncs server-side and migrations finish in
|
||||
@@ -201,7 +201,7 @@ spec:
|
||||
defaultBucket: suedstr28
|
||||
tlsSkipVerify: true
|
||||
secureJsonData:
|
||||
token: ${INFLUXDB_TOKEN}
|
||||
token: $${INFLUXDB_TOKEN}
|
||||
sidecar:
|
||||
resources:
|
||||
requests:
|
||||
@@ -242,15 +242,15 @@ spec:
|
||||
- name: grafana-influxdb-token
|
||||
grafana.ini:
|
||||
server:
|
||||
root_url: https://dash.renner.uno
|
||||
root_url: https://dash.${SITE_DOMAIN}
|
||||
auth.generic_oauth:
|
||||
enabled: true
|
||||
name: Authentik
|
||||
allow_sign_up: true
|
||||
scopes: openid profile email
|
||||
auth_url: https://auth.renner.uno/application/o/authorize/
|
||||
token_url: https://auth.renner.uno/application/o/token/
|
||||
api_url: https://auth.renner.uno/application/o/userinfo/
|
||||
auth_url: https://auth.${SITE_DOMAIN}/application/o/authorize/
|
||||
token_url: https://auth.${SITE_DOMAIN}/application/o/token/
|
||||
api_url: https://auth.${SITE_DOMAIN}/application/o/userinfo/
|
||||
use_pkce: true
|
||||
role_attribute_path: "contains(groups[*], 'Infrastructure Admins') && 'Admin' || 'Viewer'"
|
||||
resources:
|
||||
@@ -276,7 +276,7 @@ spec:
|
||||
# the "Source" link of every grouped alert, so the email link
|
||||
# resolves externally instead of pointing at the in-cluster
|
||||
# service hostname.
|
||||
externalUrl: https://alerts.renner.uno
|
||||
externalUrl: https://alerts.${SITE_DOMAIN}
|
||||
storage:
|
||||
volumeClaimTemplate:
|
||||
spec:
|
||||
|
||||
@@ -16,7 +16,7 @@ metadata:
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: loki.renner.uno
|
||||
- host: loki.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -28,5 +28,5 @@ spec:
|
||||
number: 3100
|
||||
tls:
|
||||
- hosts:
|
||||
- loki.renner.uno
|
||||
- loki.${SITE_DOMAIN}
|
||||
secretName: loki-ingress-tls
|
||||
|
||||
@@ -15,7 +15,7 @@ metadata:
|
||||
spec:
|
||||
ingressClassName: authentik-gate
|
||||
rules:
|
||||
- host: prometheus.renner.uno
|
||||
- host: prometheus.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -28,4 +28,4 @@ spec:
|
||||
tls:
|
||||
- secretName: prometheus-tls
|
||||
hosts:
|
||||
- prometheus.renner.uno
|
||||
- prometheus.${SITE_DOMAIN}
|
||||
|
||||
@@ -12,7 +12,7 @@ metadata:
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: music.renner.uno
|
||||
- host: music.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -25,4 +25,4 @@ spec:
|
||||
tls:
|
||||
- secretName: music-assistant-tls
|
||||
hosts:
|
||||
- music.renner.uno
|
||||
- music.${SITE_DOMAIN}
|
||||
|
||||
@@ -11,7 +11,7 @@ metadata:
|
||||
spec:
|
||||
ingressClassName: authentik-gate
|
||||
rules:
|
||||
- host: navidrome.renner.uno
|
||||
- host: navidrome.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -24,7 +24,7 @@ spec:
|
||||
tls:
|
||||
- secretName: navidrome-tls
|
||||
hosts:
|
||||
- navidrome.renner.uno
|
||||
- navidrome.${SITE_DOMAIN}
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
@@ -50,7 +50,7 @@ metadata:
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: navidrome.renner.uno
|
||||
- host: navidrome.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /rest
|
||||
@@ -81,7 +81,7 @@ spec:
|
||||
- websecure
|
||||
routes:
|
||||
- kind: Rule
|
||||
match: Host(`navidrome.renner.uno`) && (PathPrefix(`/auth`) || PathPrefix(`/api`)) && !HeaderRegexp(`Cookie`, `authentik_proxy_`)
|
||||
match: Host(`navidrome.${SITE_DOMAIN}`) && (PathPrefix(`/auth`) || PathPrefix(`/api`)) && !HeaderRegexp(`Cookie`, `authentik_proxy_`)
|
||||
priority: 1000
|
||||
middlewares:
|
||||
- name: navidrome-strip-identity
|
||||
|
||||
@@ -200,7 +200,7 @@ metadata:
|
||||
spec:
|
||||
ingressClassName: authentik-gate
|
||||
rules:
|
||||
- host: lidarr.renner.uno
|
||||
- host: lidarr.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -213,4 +213,4 @@ spec:
|
||||
tls:
|
||||
- secretName: lidarr-tls
|
||||
hosts:
|
||||
- lidarr.renner.uno
|
||||
- lidarr.${SITE_DOMAIN}
|
||||
|
||||
@@ -14,8 +14,8 @@ metadata:
|
||||
namespace: paperless
|
||||
spec:
|
||||
redirectRegex:
|
||||
regex: ^https://docs\.renner\.uno/(.*)
|
||||
replacement: https://paperless.renner.uno/${1}
|
||||
regex: ^https://docs\.${SITE_DOMAIN_RE}/(.*)
|
||||
replacement: https://paperless.${SITE_DOMAIN}/$${1}
|
||||
permanent: true
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
@@ -33,7 +33,7 @@ metadata:
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: docs.renner.uno
|
||||
- host: docs.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -46,4 +46,4 @@ spec:
|
||||
tls:
|
||||
- secretName: docs-renner-uno-tls
|
||||
hosts:
|
||||
- docs.renner.uno
|
||||
- docs.${SITE_DOMAIN}
|
||||
|
||||
@@ -119,7 +119,7 @@ metadata:
|
||||
# label — FTP must not be exposed via the cargo tunnel (clear-text
|
||||
# protocol, public exposure not appropriate). The brother scanner
|
||||
# uses this hostname instead of the LB IP.
|
||||
external-dns.kubernetes.io/hostname: ftp.paperless.renner.uno
|
||||
external-dns.kubernetes.io/hostname: ftp.paperless.${SITE_DOMAIN}
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
externalTrafficPolicy: Local
|
||||
|
||||
@@ -22,8 +22,8 @@ data:
|
||||
# new users hit 403 on /api/ui_settings/ until an admin grants
|
||||
# perms manually.
|
||||
PAPERLESS_ACCOUNT_DEFAULT_GROUPS: "Familie"
|
||||
PAPERLESS_URL: "https://paperless.renner.uno"
|
||||
PAPERLESS_CSRF_TRUSTED_ORIGINS: "https://paperless.renner.uno,https://docs.renner.uno"
|
||||
PAPERLESS_URL: "https://paperless.${SITE_DOMAIN}"
|
||||
PAPERLESS_CSRF_TRUSTED_ORIGINS: "https://paperless.${SITE_DOMAIN},https://docs.${SITE_DOMAIN}"
|
||||
# anchor: allowed-hosts
|
||||
PAPERLESS_ALLOWED_HOSTS: "*"
|
||||
PAPERLESS_TIME_ZONE: "Europe/Berlin"
|
||||
@@ -365,7 +365,7 @@ metadata:
|
||||
spec:
|
||||
ingressClassName: authentik-gate
|
||||
rules:
|
||||
- host: paperless.renner.uno
|
||||
- host: paperless.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -378,4 +378,4 @@ spec:
|
||||
tls:
|
||||
- secretName: paperless-tls
|
||||
hosts:
|
||||
- paperless.renner.uno
|
||||
- paperless.${SITE_DOMAIN}
|
||||
|
||||
@@ -145,7 +145,7 @@ metadata:
|
||||
lb-pool: bgp
|
||||
annotations:
|
||||
io.cilium/lb-ipam-ips: "10.2.99.85,fd99::85"
|
||||
external-dns.kubernetes.io/hostname: syncthing.paperless.renner.uno
|
||||
external-dns.kubernetes.io/hostname: syncthing.paperless.${SITE_DOMAIN}
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
externalTrafficPolicy: Local
|
||||
|
||||
@@ -27,7 +27,7 @@ data:
|
||||
config.json: |
|
||||
{
|
||||
"platform": "gitea",
|
||||
"endpoint": "https://git.renner.uno/api/v1",
|
||||
"endpoint": "https://git.${SITE_DOMAIN}/api/v1",
|
||||
"gitAuthor": "Renovate Bot <renovate-bot@renner.uno>",
|
||||
"autodiscover": true,
|
||||
"onboarding": false,
|
||||
|
||||
@@ -37,7 +37,7 @@ spec:
|
||||
- name: REPOS
|
||||
value: konairius/homelab,konairius/homeassistant,konairius/authentik-samba-bridge,konairius/shelly-manager
|
||||
- name: GITEA_URL
|
||||
value: https://gitea.renner.uno
|
||||
value: https://gitea.${SITE_DOMAIN}
|
||||
- name: AGENT_NAME
|
||||
value: pr-review
|
||||
- name: CONNECTOR_ID
|
||||
|
||||
@@ -12,10 +12,10 @@ spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- review-bot.renner.uno
|
||||
- review-bot.${SITE_DOMAIN}
|
||||
secretName: review-bot-tls
|
||||
rules:
|
||||
- host: review-bot.renner.uno
|
||||
- host: review-bot.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /status
|
||||
|
||||
@@ -37,10 +37,10 @@ spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- trmnl-device.renner.uno
|
||||
- trmnl-device.${SITE_DOMAIN}
|
||||
secretName: trmnl-device-tls
|
||||
rules:
|
||||
- host: trmnl-device.renner.uno
|
||||
- host: trmnl-device.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
|
||||
@@ -13,10 +13,10 @@ spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- trmnl.renner.uno
|
||||
- trmnl.${SITE_DOMAIN}
|
||||
secretName: trmnl-tls
|
||||
rules:
|
||||
- host: trmnl.renner.uno
|
||||
- host: trmnl.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
|
||||
@@ -17,7 +17,7 @@ data:
|
||||
# earlier value was http on the theory that the firmware would reject an
|
||||
# internal CA; there is no internal CA here — letsencrypt-cluster issues
|
||||
# publicly-trusted certs for LAN-only names via DNS-01.
|
||||
API_URI: "https://trmnl-device.renner.uno"
|
||||
API_URI: "https://trmnl-device.${SITE_DOMAIN}"
|
||||
KEYVALUE_URL: "redis://terminus-valkey:6379"
|
||||
# Rack::Attack rate-limit bypass. Beyond upstream's RFC1918 defaults this
|
||||
# adds fc00::/7, which covers the pod CIDR (fd10:244::/48), the Cilium LB
|
||||
|
||||
@@ -248,13 +248,13 @@
|
||||
apiVersion: endpointmonitor.stakater.com/v1alpha1
|
||||
kind: EndpointMonitor
|
||||
metadata:
|
||||
name: www.renner.uno
|
||||
name: www.${SITE_DOMAIN}
|
||||
namespace: uptime-monitor
|
||||
spec:
|
||||
# `https://www.renner.uno` and not `/wp-admin` or `/wp-login.php`: those two
|
||||
# paths are served by a SECOND Ingress that sits behind the Authentik outpost
|
||||
# (../wordpress/ingress.yaml#gated-paths). `/` is the ungated one.
|
||||
url: https://www.renner.uno/
|
||||
url: https://www.${SITE_DOMAIN}/
|
||||
uptimeRobotConfig:
|
||||
# `http`, not `keyword`. The survey recommends a keyword check here
|
||||
# eventually, and that is right — a WordPress instance can serve 200 with a
|
||||
@@ -280,7 +280,7 @@ spec:
|
||||
apiVersion: endpointmonitor.stakater.com/v1alpha1
|
||||
kind: EndpointMonitor
|
||||
metadata:
|
||||
name: audiobooks.renner.uno
|
||||
name: audiobooks.${SITE_DOMAIN}
|
||||
namespace: uptime-monitor
|
||||
spec:
|
||||
# The path belongs in `spec.url` and NOT in `spec.healthEndpoint`, because
|
||||
@@ -289,7 +289,7 @@ spec:
|
||||
# `/healthcheck` is ungated, so the check reaches application health rather
|
||||
# than mere reachability — it is the same endpoint the Gatus check uses
|
||||
# (Web Service 8 in ../gatus/configmap.yaml#gatus-endpoints).
|
||||
url: https://audiobooks.renner.uno/healthcheck
|
||||
url: https://audiobooks.${SITE_DOMAIN}/healthcheck
|
||||
uptimeRobotConfig:
|
||||
monitorType: http
|
||||
interval: 300
|
||||
|
||||
@@ -65,7 +65,7 @@ spec:
|
||||
- name: Wiki__Index__File__Directory
|
||||
value: /data
|
||||
- name: Wiki__NoteStore__Gitea__BaseUrl
|
||||
value: https://gitea.renner.uno
|
||||
value: https://gitea.${SITE_DOMAIN}
|
||||
- name: Wiki__NoteStore__Gitea__Repository
|
||||
value: konairius/notes
|
||||
- name: Wiki__NoteStore__Gitea__Branch
|
||||
@@ -74,7 +74,7 @@ spec:
|
||||
value: http://wiki-embed.ai.svc.cluster.local:8080
|
||||
# Human login: the `wiki` application in apps/authentik/blueprints-configmap.yaml.
|
||||
- name: Auth__Oidc__Authority
|
||||
value: https://auth.renner.uno/application/o/wiki/
|
||||
value: https://auth.${SITE_DOMAIN}/application/o/wiki/
|
||||
- name: Auth__Oidc__ClientId
|
||||
value: wiki
|
||||
# wiki-index BasicAuth users (htpasswd), checked by the service for the read API.
|
||||
|
||||
@@ -12,10 +12,10 @@ spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- wiki.renner.uno
|
||||
- wiki.${SITE_DOMAIN}
|
||||
secretName: wiki-tls
|
||||
rules:
|
||||
- host: wiki.renner.uno
|
||||
- host: wiki.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -50,10 +50,10 @@ spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- wiki-index.renner.uno
|
||||
- wiki-index.${SITE_DOMAIN}
|
||||
secretName: wiki-api-tls
|
||||
rules:
|
||||
- host: wiki-index.renner.uno
|
||||
- host: wiki-index.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
|
||||
@@ -13,7 +13,7 @@ metadata:
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: ci.renner.uno
|
||||
- host: ci.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -26,4 +26,4 @@ spec:
|
||||
tls:
|
||||
- secretName: woodpecker-tls
|
||||
hosts:
|
||||
- ci.renner.uno
|
||||
- ci.${SITE_DOMAIN}
|
||||
|
||||
@@ -43,7 +43,7 @@ spec:
|
||||
image: woodpeckerci/woodpecker-server:v3.19.0 # {"$imagepolicy": "flux-system:woodpecker-server"}
|
||||
env:
|
||||
- name: WOODPECKER_HOST
|
||||
value: https://ci.renner.uno
|
||||
value: https://ci.${SITE_DOMAIN}
|
||||
- name: WOODPECKER_OPEN
|
||||
value: "true"
|
||||
- name: WOODPECKER_ADMIN
|
||||
@@ -55,7 +55,7 @@ spec:
|
||||
- name: WOODPECKER_GITEA
|
||||
value: "true"
|
||||
- name: WOODPECKER_GITEA_URL
|
||||
value: https://gitea.renner.uno
|
||||
value: https://gitea.${SITE_DOMAIN}
|
||||
# Woodpecker v3 removed default privileged plugins. The image-build
|
||||
# pipelines (e.g. shelly-manager) previously ran a privileged
|
||||
# docker/buildx plugin under the docker backend; on the kubernetes
|
||||
|
||||
@@ -52,8 +52,8 @@ spec:
|
||||
key: password
|
||||
- name: WORDPRESS_CONFIG_EXTRA
|
||||
value: |
|
||||
define('WP_HOME', 'https://www.renner.uno');
|
||||
define('WP_SITEURL', 'https://www.renner.uno');
|
||||
define('WP_HOME', 'https://www.${SITE_DOMAIN}');
|
||||
define('WP_SITEURL', 'https://www.${SITE_DOMAIN}');
|
||||
define('FORCE_SSL_ADMIN', true);
|
||||
/* Traefik terminates TLS; without this WordPress sees plain
|
||||
HTTP and builds http:// URLs into every page and redirect. */
|
||||
|
||||
@@ -21,7 +21,7 @@ metadata:
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: www.renner.uno
|
||||
- host: www.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -34,7 +34,7 @@ spec:
|
||||
tls:
|
||||
- secretName: wordpress-tls
|
||||
hosts:
|
||||
- www.renner.uno
|
||||
- www.${SITE_DOMAIN}
|
||||
---
|
||||
# wp-admin, wp-login.php, xmlrpc.php and the outpost's own OAuth leg
|
||||
# (/outpost.goauthentik.io) are gated by authentik-gate-sync (ingress class
|
||||
@@ -61,9 +61,9 @@ spec:
|
||||
tls:
|
||||
- secretName: wordpress-tls
|
||||
hosts:
|
||||
- www.renner.uno
|
||||
- www.${SITE_DOMAIN}
|
||||
rules:
|
||||
- host: www.renner.uno
|
||||
- host: www.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /outpost.goauthentik.io
|
||||
|
||||
@@ -15,7 +15,7 @@ metadata:
|
||||
spec:
|
||||
ingressClassName: authentik-gate
|
||||
rules:
|
||||
- host: zone-configurator.home.renner.uno
|
||||
- host: zone-configurator.home.${SITE_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -28,4 +28,4 @@ spec:
|
||||
tls:
|
||||
- secretName: zone-configurator-tls
|
||||
hosts:
|
||||
- zone-configurator.home.renner.uno
|
||||
- zone-configurator.home.${SITE_DOMAIN}
|
||||
|
||||
@@ -1905,13 +1905,18 @@ EOF
|
||||
# patch), and CoreDNS forwards external names to the node. Until k8s-gateway
|
||||
# answers, every lookup waits out the node's 2 s read timeout and CoreDNS
|
||||
# gives up first: pods resolve nothing, Flux included. So it is installed
|
||||
# here, from the values Flux will reconcile (the HelmRelease, under the
|
||||
# here, from the values Flux will reconcile (the HelmRelease, substituted
|
||||
# as its Flux Kustomization does with the site's site-settings, under the
|
||||
# site's k8s-gateway-site values, as valuesFrom merges them), and Flux
|
||||
# adopts the release.
|
||||
step 0 "Installing k8s-gateway (the nodes' first resolver)..."
|
||||
command -v flux >/dev/null || fail "flux not found (substitutes the k8s-gateway values)"
|
||||
k8sgw_values=$(mktemp)
|
||||
k8sgw_site_values=$(mktemp)
|
||||
yq 'select(.kind == "HelmRelease") | .spec.values' "$K8SGW_HR" > "$k8sgw_values"
|
||||
yq 'select(.kind == "HelmRelease") | .spec.values' "$K8SGW_HR" \
|
||||
| SITE_PATCH_DIR="$TALOS_DIR/patches" bash "$REPO_ROOT/scripts/ci/site-render.sh" \
|
||||
--subst "$SITE" kubernetes/infrastructure > "$k8sgw_values" \
|
||||
|| fail "k8s-gateway values do not substitute with the site-settings of talos/patches/site-${SITE}.yaml"
|
||||
yq -r 'select(.cluster) | .cluster.inlineManifests[] | select(.name == "k8s-gateway-site") | .contents' \
|
||||
"$TALOS_DIR/patches/site-${SITE}.yaml" \
|
||||
| yq -r '.data["values.yaml"]' > "$k8sgw_site_values"
|
||||
|
||||
@@ -12,6 +12,10 @@ spec:
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
# decryption block dropped 2026-07-13: path is sops-free (external-dns +
|
||||
# woodpecker migrated) — secrets come from OpenBao via ESO
|
||||
|
||||
@@ -16,6 +16,10 @@ spec:
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/audiobookshelf
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
decryption:
|
||||
provider: sops
|
||||
|
||||
@@ -15,6 +15,10 @@ spec:
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/authentik
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
# decryption block dropped 2026-07-14: path is sops-free — secrets come
|
||||
# from OpenBao via ESO (MIGRATION-sops-openbao.md). This was the LAST
|
||||
|
||||
@@ -12,6 +12,10 @@ spec:
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/evcc
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
# decryption block dropped 2026-07-13: path is sops-free — secrets come
|
||||
# from OpenBao via ESO (MIGRATION-sops-openbao.md).
|
||||
|
||||
@@ -3,3 +3,14 @@ kind: Kustomization
|
||||
resources:
|
||||
- gotk-components.yaml
|
||||
- gotk-sync.yaml
|
||||
patches:
|
||||
- target:
|
||||
kind: Kustomization
|
||||
name: flux-system
|
||||
patch: |
|
||||
- op: add
|
||||
path: /spec/postBuild
|
||||
value:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
|
||||
@@ -12,6 +12,10 @@ spec:
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/gitea-mcp
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
dependsOn:
|
||||
- name: infrastructure
|
||||
|
||||
@@ -13,6 +13,10 @@ spec:
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/gitea
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
# The claim's bound volume differs per cluster (apps/gitea/pvc.yaml).
|
||||
ignore:
|
||||
|
||||
@@ -12,6 +12,10 @@ spec:
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/hermes
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
dependsOn:
|
||||
- name: infrastructure
|
||||
|
||||
@@ -15,6 +15,10 @@ spec:
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/home-assistant
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
# decryption block dropped 2026-07-13: path is sops-free — secrets come
|
||||
# from OpenBao via ESO (MIGRATION-sops-openbao.md).
|
||||
@@ -45,6 +49,7 @@ spec:
|
||||
kind: GitRepository
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/matter-server
|
||||
suspend: ${SITE_SUSPEND_IOT_VLAN}
|
||||
prune: true
|
||||
wait: true
|
||||
dependsOn:
|
||||
@@ -67,7 +72,12 @@ spec:
|
||||
kind: GitRepository
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/home-assistant-iot
|
||||
suspend: ${SITE_SUSPEND_IOT_VLAN}
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
dependsOn:
|
||||
- name: restore-home-assistant # shelly-manager's PVC
|
||||
@@ -115,6 +125,10 @@ spec:
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/zone-configurator
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
dependsOn:
|
||||
- name: home-assistant
|
||||
|
||||
@@ -16,6 +16,10 @@ spec:
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/immich
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
# decryption block dropped 2026-07-13: path is sops-free — secrets come
|
||||
# from OpenBao via ESO (MIGRATION-sops-openbao.md).
|
||||
|
||||
@@ -13,6 +13,10 @@ spec:
|
||||
path: ./kubernetes/infrastructure-inhouse
|
||||
prune: true
|
||||
wait: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
dependsOn:
|
||||
- name: infrastructure
|
||||
- name: infrastructure-crds
|
||||
|
||||
@@ -17,6 +17,10 @@ spec:
|
||||
name: flux-system
|
||||
path: ./kubernetes/infrastructure/crowdsec
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
dependsOn:
|
||||
- name: infrastructure
|
||||
@@ -40,7 +44,12 @@ spec:
|
||||
kind: GitRepository
|
||||
name: flux-system
|
||||
path: ./kubernetes/infrastructure/external-dns-unifi
|
||||
suspend: ${SITE_SUSPEND_UNIFI}
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
dependsOn:
|
||||
- name: infrastructure
|
||||
@@ -60,6 +69,10 @@ spec:
|
||||
name: flux-system
|
||||
path: ./kubernetes/infrastructure/netbird-ingress
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
dependsOn:
|
||||
- name: infrastructure
|
||||
|
||||
@@ -12,6 +12,10 @@ spec:
|
||||
name: flux-system
|
||||
path: ./kubernetes/infrastructure
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
# wait: false + explicit healthChecks (NOT wait: true). With wait: true, Flux
|
||||
# ignores healthChecks and blocks this Kustomization's Ready on EVERY resource
|
||||
# it applies (hundreds) — so one crashlooping workload froze it and, via
|
||||
@@ -81,14 +85,14 @@ spec:
|
||||
wait: true
|
||||
# decryption block dropped 2026-07-13: no *.sops.yaml left under this
|
||||
# path — secrets come from OpenBao via ESO (MIGRATION-sops-openbao.md).
|
||||
# Substitute ${ACME_SERVER} (and CLUSTER_IN_DEVELOPMENT for any future
|
||||
# gates) from the cluster-settings ConfigMap into the ClusterIssuer
|
||||
# manifest — this is the single point where the dev/prod LE flag lands
|
||||
# in cert-manager state.
|
||||
# cluster-settings: CLUSTER_IN_DEVELOPMENT. site-settings: SITE_DOMAIN and
|
||||
# SITE_ACME_SERVER for the ClusterIssuers.
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: cluster-settings
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
dependsOn:
|
||||
- name: infrastructure
|
||||
---
|
||||
|
||||
@@ -14,7 +14,12 @@ spec:
|
||||
kind: GitRepository
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/kuma-sync
|
||||
suspend: ${SITE_SUSPEND_UPTIME_MONITORING}
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
healthCheckExprs:
|
||||
- apiVersion: external-secrets.io/v1
|
||||
|
||||
@@ -14,7 +14,12 @@ spec:
|
||||
kind: GitRepository
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/llama-server
|
||||
suspend: ${SITE_SUSPEND_IGPU}
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
dependsOn:
|
||||
- name: infrastructure
|
||||
|
||||
@@ -41,6 +41,10 @@ spec:
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/monitoring
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
# decryption block dropped 2026-07-13: path is sops-free — secrets come
|
||||
# from OpenBao via ESO (MIGRATION-sops-openbao.md).
|
||||
@@ -73,6 +77,10 @@ spec:
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/monitoring-rules
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
dependsOn:
|
||||
- name: monitoring
|
||||
|
||||
@@ -15,7 +15,12 @@ spec:
|
||||
kind: GitRepository
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/music-assistant
|
||||
suspend: ${SITE_SUSPEND_IOT_VLAN}
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
decryption:
|
||||
provider: sops
|
||||
|
||||
@@ -12,6 +12,10 @@ spec:
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/navidrome
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
dependsOn:
|
||||
- name: restore-navidrome
|
||||
|
||||
@@ -16,6 +16,10 @@ spec:
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/paperless
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
# decryption block dropped 2026-07-13: path is sops-free — secrets come
|
||||
# from OpenBao via ESO (MIGRATION-sops-openbao.md).
|
||||
|
||||
@@ -23,6 +23,10 @@ spec:
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/renovate
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
healthCheckExprs:
|
||||
- apiVersion: external-secrets.io/v1
|
||||
|
||||
@@ -12,6 +12,10 @@ spec:
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/review-bot
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
dependsOn:
|
||||
- name: infrastructure
|
||||
|
||||
@@ -15,6 +15,10 @@ spec:
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/trmnl
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
dependsOn:
|
||||
- name: restore-trmnl
|
||||
|
||||
@@ -87,6 +87,10 @@ spec:
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/uptime-monitor
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
# No name/namespace qualifier, so this is evaluated against every
|
||||
# ExternalSecret in this Kustomization's inventory. That is exactly one
|
||||
|
||||
@@ -12,7 +12,12 @@ spec:
|
||||
kind: GitRepository
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/wiki
|
||||
suspend: ${SITE_SUSPEND_IGPU}
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
dependsOn:
|
||||
- name: infrastructure
|
||||
|
||||
@@ -17,6 +17,10 @@ spec:
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/woodpecker
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
# sops-free path: woodpecker-secret + barman creds come from OpenBao via ESO
|
||||
# (MIGRATION-sops-openbao.md).
|
||||
|
||||
@@ -12,6 +12,10 @@ spec:
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/wordpress
|
||||
prune: true
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: ConfigMap
|
||||
name: site-settings
|
||||
wait: true
|
||||
dependsOn:
|
||||
- name: restore-wordpress
|
||||
|
||||
@@ -94,9 +94,13 @@ spec:
|
||||
value: http://openbao.openbao.svc.cluster.local:8200
|
||||
- name: BAO_ROLE
|
||||
value: netbird-service-sync
|
||||
# Must match cluster.network.serviceSubnets in talos/patches/cluster.yaml.
|
||||
# Must match cluster.network.serviceSubnets in talos/patches/site-*.yaml.
|
||||
- name: SERVICE_CIDR
|
||||
value: "10.96.0.0/12"
|
||||
value: "${SITE_SERVICE_CIDR}"
|
||||
- name: NETWORK_NAME
|
||||
value: "${SITE_NETBIRD_NETWORK}"
|
||||
- name: ROUTER_GROUP_NAME
|
||||
value: "${SITE_NETBIRD_ROUTER_GROUP}"
|
||||
- name: TRAEFIK_SERVICE
|
||||
value: ingress/traefik
|
||||
- name: INTERVAL_SECONDS
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user