Compare commits
48
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60e3a24652 | ||
|
|
2d32b9bd7d | ||
|
|
9f82b2accf | ||
|
|
990b309cbb | ||
|
|
cedcb01975 | ||
|
|
2b279d4f21 | ||
|
|
12c28adf4d | ||
|
|
b0230ba736 | ||
|
|
9c51f60a35 | ||
|
|
267446c68f | ||
|
|
d1994b16e8 | ||
|
|
d7317606e7 | ||
|
|
970d65d193 | ||
|
|
6719082e5c | ||
|
|
428b77f08c | ||
|
|
2ee7d5036a | ||
|
|
5736f42e22 | ||
|
|
773254e3a1 | ||
|
|
264cd7a46d | ||
|
|
979cc4b21d | ||
|
|
be6742c22b | ||
|
|
c8b915d8f2 | ||
|
|
6cd3bd29a9 | ||
|
|
2040d35222 | ||
|
|
47fc27ff08 | ||
|
|
5e8b080298 | ||
|
|
24a0e1e4c5 | ||
|
|
3aec6886d8 | ||
|
|
1b2ec82a9d | ||
|
|
3724d3068b | ||
|
|
c6ad6b559c | ||
|
|
6efc49d415 | ||
|
|
a6dc2d1adb | ||
|
|
75a472cf67 | ||
|
|
24b02f37df | ||
|
|
be3ce4645b | ||
|
|
b7a77a7d1e | ||
|
|
1a94b0535c | ||
|
|
0e7d0204b3 | ||
|
|
b77572f843 | ||
|
|
76b5d038fb | ||
|
|
306f6f4fb0 | ||
|
|
3ceaad15e0 | ||
|
|
ae772e050d | ||
|
|
517dfc9030 | ||
|
|
16de787557 | ||
|
|
b7cb70955c | ||
|
|
9bcea04b1b |
No files matched your search
@@ -80,7 +80,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # full history so ci/touched-requirements.sh can diff vs main
|
fetch-depth: 0 # full history so ci/touched-requirements.sh can diff vs main
|
||||||
|
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ jobs:
|
|||||||
timeout-minutes: 75
|
timeout-minutes: 75
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # the per-image change detection diffs against the base commit
|
fetch-depth: 0 # the per-image change detection diffs against the base commit
|
||||||
|
|
||||||
|
|||||||
@@ -60,9 +60,9 @@ jobs:
|
|||||||
spec-immutability:
|
spec-immutability:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 # the implementation repo
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 # the implementation repo
|
||||||
- name: Check out the read-only spec (sibling path)
|
- name: Check out the read-only spec (sibling path)
|
||||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with:
|
with:
|
||||||
repository: ${{ vars.SPEC_REPO_SLUG }} # e.g. Home-Network/home-network-k3s (set in Gitea)
|
repository: ${{ vars.SPEC_REPO_SLUG }} # e.g. Home-Network/home-network-k3s (set in Gitea)
|
||||||
path: ../home-network-k3s
|
path: ../home-network-k3s
|
||||||
@@ -74,7 +74,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [spec-immutability]
|
needs: [spec-immutability]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with: { fetch-depth: 0 } # full history so touched-requirement diff works
|
with: { fetch-depth: 0 } # full history so touched-requirement diff works
|
||||||
- name: Install pinned tools
|
- name: Install pinned tools
|
||||||
run: ./.gitea/workflows/scripts/install-tools.sh lint
|
run: ./.gitea/workflows/scripts/install-tools.sh lint
|
||||||
@@ -85,7 +85,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [lint]
|
needs: [lint]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with: { fetch-depth: 0 }
|
with: { fetch-depth: 0 }
|
||||||
- name: Install pinned tools
|
- name: Install pinned tools
|
||||||
run: ./.gitea/workflows/scripts/install-tools.sh l0
|
run: ./.gitea/workflows/scripts/install-tools.sh l0
|
||||||
@@ -96,7 +96,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [l0]
|
needs: [l0]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with: { fetch-depth: 0 }
|
with: { fetch-depth: 0 }
|
||||||
- name: Install pinned tools
|
- name: Install pinned tools
|
||||||
run: ./.gitea/workflows/scripts/install-tools.sh l1
|
run: ./.gitea/workflows/scripts/install-tools.sh l1
|
||||||
@@ -107,7 +107,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [render-golden]
|
needs: [render-golden]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with: { fetch-depth: 0 }
|
with: { fetch-depth: 0 }
|
||||||
- name: Install pinned tools
|
- name: Install pinned tools
|
||||||
run: ./.gitea/workflows/scripts/install-tools.sh l1
|
run: ./.gitea/workflows/scripts/install-tools.sh l1
|
||||||
@@ -115,7 +115,7 @@ jobs:
|
|||||||
run: make -C ci l1
|
run: make -C ci l1
|
||||||
- name: Upload L1 artifacts
|
- name: Upload L1 artifacts
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
|
||||||
with:
|
with:
|
||||||
name: l1-junit-coverage
|
name: l1-junit-coverage
|
||||||
path: |
|
path: |
|
||||||
@@ -131,7 +131,7 @@ jobs:
|
|||||||
needs: [l1]
|
needs: [l1]
|
||||||
if: ${{ vars.CLUSTER_AVAILABLE == 'true' }}
|
if: ${{ vars.CLUSTER_AVAILABLE == 'true' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with: { fetch-depth: 0 }
|
with: { fetch-depth: 0 }
|
||||||
- name: Install pinned tools
|
- name: Install pinned tools
|
||||||
run: ./.gitea/workflows/scripts/install-tools.sh conformance
|
run: ./.gitea/workflows/scripts/install-tools.sh conformance
|
||||||
@@ -142,7 +142,7 @@ jobs:
|
|||||||
run: make -C ci conformance
|
run: make -C ci conformance
|
||||||
- name: Upload L2/L3 artifacts
|
- name: Upload L2/L3 artifacts
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
|
||||||
with:
|
with:
|
||||||
name: conformance-junit-coverage
|
name: conformance-junit-coverage
|
||||||
path: |
|
path: |
|
||||||
@@ -160,7 +160,7 @@ jobs:
|
|||||||
needs: [lint, l0, render-golden, l1, conformance]
|
needs: [lint, l0, render-golden, l1, conformance]
|
||||||
if: always()
|
if: always()
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
- name: Decide
|
- name: Decide
|
||||||
env:
|
env:
|
||||||
R_LINT: ${{ needs.lint.result }}
|
R_LINT: ${{ needs.lint.result }}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: gitea
|
- name: gitea
|
||||||
image: gitea/gitea:1.24.3
|
image: gitea/gitea:1.27.3
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 3000
|
- containerPort: 3000
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: jackett
|
- name: jackett
|
||||||
image: lscr.io/linuxserver/jackett:v0.24.3009-ls26
|
image: lscr.io/linuxserver/jackett:v0.24.3025-ls29
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 9117
|
- containerPort: 9117
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ spec:
|
|||||||
# Scaling out is non-disruptive: CNPG creates -2 then -3 sequentially, each seeded by
|
# Scaling out is non-disruptive: CNPG creates -2 then -3 sequentially, each seeded by
|
||||||
# pg_basebackup from the running primary, which is neither restarted nor disconnected.
|
# pg_basebackup from the running primary, which is neither restarted nor disconnected.
|
||||||
instances: 3
|
instances: 3
|
||||||
imageName: ghcr.io/cloudnative-pg/postgresql:16.4
|
imageName: ghcr.io/cloudnative-pg/postgresql:16.15
|
||||||
# Stamp app.kubernetes.io/name=nextcloud-db onto every generated pod/service so the rendered
|
# Stamp app.kubernetes.io/name=nextcloud-db onto every generated pod/service so the rendered
|
||||||
# nextcloud egress CiliumNetworkPolicy's toEndpoints selector matches the primary pod (R-NET-02).
|
# nextcloud egress CiliumNetworkPolicy's toEndpoints selector matches the primary pod (R-NET-02).
|
||||||
inheritedMetadata:
|
inheritedMetadata:
|
||||||
@@ -300,7 +300,7 @@ spec:
|
|||||||
# is already in it and it is already present on every node, so this
|
# is already in it and it is already present on every node, so this
|
||||||
# adds no new image and no new supply-chain surface. Keep the tag in
|
# adds no new image and no new supply-chain surface. Keep the tag in
|
||||||
# step with spec.imageName above when Renovate bumps it.
|
# step with spec.imageName above when Renovate bumps it.
|
||||||
image: ghcr.io/cloudnative-pg/postgresql:16.4
|
image: ghcr.io/cloudnative-pg/postgresql:16.15
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
command:
|
command:
|
||||||
- psql
|
- psql
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: qbittorrent
|
- name: qbittorrent
|
||||||
image: lscr.io/linuxserver/qbittorrent:5.1.2
|
image: lscr.io/linuxserver/qbittorrent:5.3.0
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8080
|
- containerPort: 8080
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ spec:
|
|||||||
# Scaling out is non-disruptive: CNPG creates -2 then -3 sequentially, each seeded by
|
# Scaling out is non-disruptive: CNPG creates -2 then -3 sequentially, each seeded by
|
||||||
# pg_basebackup from the running primary, which is neither restarted nor disconnected.
|
# pg_basebackup from the running primary, which is neither restarted nor disconnected.
|
||||||
instances: 3
|
instances: 3
|
||||||
imageName: ghcr.io/cloudnative-pg/postgresql:16.4
|
imageName: ghcr.io/cloudnative-pg/postgresql:16.15
|
||||||
# Stamp app.kubernetes.io/name=semaphore-db onto every generated pod/service so the rendered
|
# Stamp app.kubernetes.io/name=semaphore-db onto every generated pod/service so the rendered
|
||||||
# semaphore egress CiliumNetworkPolicy's toEndpoints selector matches the primary pod (R-NET-02).
|
# semaphore egress CiliumNetworkPolicy's toEndpoints selector matches the primary pod (R-NET-02).
|
||||||
inheritedMetadata:
|
inheritedMetadata:
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ spec:
|
|||||||
initContainers:
|
initContainers:
|
||||||
- name: dind
|
- name: dind
|
||||||
restartPolicy: Always # <- this field is what makes it a SIDECAR
|
restartPolicy: Always # <- this field is what makes it a SIDECAR
|
||||||
image: docker:27-dind
|
image: docker:29-dind
|
||||||
securityContext:
|
securityContext:
|
||||||
privileged: true
|
privileged: true
|
||||||
env:
|
env:
|
||||||
@@ -167,7 +167,7 @@ spec:
|
|||||||
containers:
|
containers:
|
||||||
# ---- act_runner: registers with Gitea, dispatches jobs to the DinD daemon ----
|
# ---- act_runner: registers with Gitea, dispatches jobs to the DinD daemon ----
|
||||||
- name: runner
|
- name: runner
|
||||||
image: gitea/act_runner:0.2.11
|
image: gitea/act_runner:0.6.1
|
||||||
# No `command:` override. The image's own entrypoint (/sbin/tini -- /opt/act/run.sh)
|
# No `command:` override. The image's own entrypoint (/sbin/tini -- /opt/act/run.sh)
|
||||||
# registers from env on first boot and runs `act_runner daemon`. The two wait-loops
|
# registers from env on first boot and runs `act_runner daemon`. The two wait-loops
|
||||||
# that used to live here are gone: kubelet now holds this container until the dind
|
# that used to live here are gone: kubelet now holds this container until the dind
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: radarr
|
- name: radarr
|
||||||
image: lscr.io/linuxserver/radarr:5.21.1
|
image: lscr.io/linuxserver/radarr:5.28.0
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 7878
|
- containerPort: 7878
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: semaphore
|
- name: semaphore
|
||||||
image: semaphoreui/semaphore:v2.19.10
|
image: semaphoreui/semaphore:v2.19.17
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 3000
|
- containerPort: 3000
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: seerr
|
- name: seerr
|
||||||
image: ghcr.io/seerr-team/seerr:v3.4.1
|
image: ghcr.io/seerr-team/seerr:v3.5.1
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 5055
|
- containerPort: 5055
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
name: gitea
|
name: gitea
|
||||||
kind: container
|
kind: container
|
||||||
source:
|
source:
|
||||||
image: gitea/gitea:1.24.3 # pinned recent stable tag (R-UPD-01); update.range governs advancement
|
image: gitea/gitea:1.27.3 # pinned recent stable tag (R-UPD-01); update.range governs advancement
|
||||||
# Headless-install bootstrap (formerly apps/gitea-bootstrap-patch.yaml; now first-class via the
|
# Headless-install bootstrap (formerly apps/gitea-bootstrap-patch.yaml; now first-class via the
|
||||||
# ServiceDefinition env/envFrom/lifecycle fields, schema >=1.2.0). Gitea must come up INSTALLED
|
# ServiceDefinition env/envFrom/lifecycle fields, schema >=1.2.0). Gitea must come up INSTALLED
|
||||||
# without the interactive web wizard and with a FIXED, persistent SECRET_KEY, so all of this is
|
# without the interactive web wizard and with a FIXED, persistent SECRET_KEY, so all of this is
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
name: jackett
|
name: jackett
|
||||||
kind: container
|
kind: container
|
||||||
source:
|
source:
|
||||||
image: lscr.io/linuxserver/jackett:v0.24.3009-ls26
|
image: lscr.io/linuxserver/jackett:v0.24.3025-ls29
|
||||||
exposure: internal # LAN-only web UI at jackett.${BASE_DOMAIN} (docs/13 row 4; R-DNS/R-NET)
|
exposure: internal # LAN-only web UI at jackett.${BASE_DOMAIN} (docs/13 row 4; R-DNS/R-NET)
|
||||||
egress: vpn # indexer scraping via node PIA tunnel + kill-switch (docs/03; ADR-0015; R-NET-05/06)
|
egress: vpn # indexer scraping via node PIA tunnel + kill-switch (docs/03; ADR-0015; R-NET-05/06)
|
||||||
storage:
|
storage:
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
name: qbittorrent
|
name: qbittorrent
|
||||||
kind: container
|
kind: container
|
||||||
source:
|
source:
|
||||||
image: lscr.io/linuxserver/qbittorrent:5.1.2 # pinned stable tag (R-UPD-01); update.range governs advancement
|
image: lscr.io/linuxserver/qbittorrent:5.3.0 # pinned stable tag (R-UPD-01); update.range governs advancement
|
||||||
# LinuxServer images run their service as the user `abc`, whose uid/gid these two set. They are
|
# LinuxServer images run their service as the user `abc`, whose uid/gid these two set. They are
|
||||||
# LOAD-BEARING FOR THE BULK MOUNT, not cosmetic — exactly as on sonarr/radarr. Measured on prod
|
# LOAD-BEARING FOR THE BULK MOUNT, not cosmetic — exactly as on sonarr/radarr. Measured on prod
|
||||||
# 2026-09-12 BEFORE this change: `id` inside the container reported the qbittorrent-nox process as
|
# 2026-09-12 BEFORE this change: `id` inside the container reported the qbittorrent-nox process as
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
name: radarr
|
name: radarr
|
||||||
kind: container
|
kind: container
|
||||||
source:
|
source:
|
||||||
image: lscr.io/linuxserver/radarr:5.21.1 # pinned stable v5 tag (R-UPD-01); update.range governs advancement
|
image: lscr.io/linuxserver/radarr:5.28.0 # pinned stable v5 tag (R-UPD-01); update.range governs advancement
|
||||||
# LinuxServer images run their service as the user `abc`, whose uid/gid these two set. They are
|
# LinuxServer images run their service as the user `abc`, whose uid/gid these two set. They are
|
||||||
# LOAD-BEARING for the bulk mount, not cosmetic: see the storage note below. The default is 911,
|
# LOAD-BEARING for the bulk mount, not cosmetic: see the storage note below. The default is 911,
|
||||||
# which cannot write a 1000:3000-owned 0755 tree.
|
# which cannot write a 1000:3000-owned 0755 tree.
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@
|
|||||||
name: seerr
|
name: seerr
|
||||||
kind: container
|
kind: container
|
||||||
source:
|
source:
|
||||||
image: ghcr.io/seerr-team/seerr:v3.4.1 # Seerr: maintained Overseerr/Jellyseerr successor, Plex-native request UI (docs/13 row 5); served on 5055
|
image: ghcr.io/seerr-team/seerr:v3.5.1 # Seerr: maintained Overseerr/Jellyseerr successor, Plex-native request UI (docs/13 row 5); served on 5055
|
||||||
# The ghcr.io/seerr-team/seerr image runs as the non-root user `node:node` (UID 1000 / GID 1000 —
|
# The ghcr.io/seerr-team/seerr image runs as the non-root user `node:node` (UID 1000 / GID 1000 —
|
||||||
# the standard node base-image user; confirmed from the image config's User field). On boot the app
|
# the standard node base-image user; confirmed from the image config's User field). On boot the app
|
||||||
# creates /app/config/logs and writes its settings/DB under the mounted /app/config PVC. A fresh
|
# creates /app/config/logs and writes its settings/DB under the mounted /app/config PVC. A fresh
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
name: semaphore
|
name: semaphore
|
||||||
kind: container
|
kind: container
|
||||||
source:
|
source:
|
||||||
image: semaphoreui/semaphore:v2.19.10 # pinned recent stable tag (R-UPD-01); update.range governs advancement
|
image: semaphoreui/semaphore:v2.19.17 # pinned recent stable tag (R-UPD-01); update.range governs advancement
|
||||||
env:
|
env:
|
||||||
# --- PostgreSQL connection (the CNPG-DB pattern). DIALECT/HOST/PORT/DB/USER are literals; PASS is
|
# --- PostgreSQL connection (the CNPG-DB pattern). DIALECT/HOST/PORT/DB/USER are literals; PASS is
|
||||||
# sourced from the in-namespace SOPS secret (the CNPG -app secret lives in ns semaphore-db,
|
# sourced from the in-namespace SOPS secret (the CNPG -app secret lives in ns semaphore-db,
|
||||||
|
|||||||
@@ -26,7 +26,10 @@
|
|||||||
# KNOWN_NON_CATALOG_PUBLISHERS — shrink-only, stale entries FAIL;
|
# KNOWN_NON_CATALOG_PUBLISHERS — shrink-only, stale entries FAIL;
|
||||||
# (7) the check is LOAD-BEARING: whole-repo scope declared in chainsaw-test.yaml, honoured by
|
# (7) the check is LOAD-BEARING: whole-repo scope declared in chainsaw-test.yaml, honoured by
|
||||||
# ci/l0.sh's --include-scope union, and ci/gate.sh blocks on an l0 failure. NO hand-written
|
# ci/l0.sh's --include-scope union, and ci/gate.sh blocks on an l0 failure. NO hand-written
|
||||||
# ci/l0.sh stage (whole-repo-invariants-wired fails the gate if one appears).
|
# ci/l0.sh stage (whole-repo-invariants-wired fails the gate if one appears);
|
||||||
|
# (8) the external-dns HelmRelease declares `annotationPrefix` explicitly and it matches the
|
||||||
|
# prefix of EVERY external-dns annotation in the tree and in catalog/render.py — added after
|
||||||
|
# the 2026-11-07 v0.23.0 default-prefix change silently voided (1)-(3) at runtime.
|
||||||
#
|
#
|
||||||
# WHAT THIS DOES *NOT* PROVE (honest gaps — it is STATIC, offline, cluster-free):
|
# WHAT THIS DOES *NOT* PROVE (honest gaps — it is STATIC, offline, cluster-free):
|
||||||
# - That the record is gone from Cloudflare. Live read; belongs beside
|
# - That the record is gone from Cloudflare. Live read; belongs beside
|
||||||
@@ -90,9 +93,18 @@ CHECKS = 0
|
|||||||
# ControllerValue = "dns-controller" -> the value that CLAIMS an object
|
# ControllerValue = "dns-controller" -> the value that CLAIMS an object
|
||||||
# source/annotations/processors.go IsControllerMismatch skips a source object when the key is
|
# source/annotations/processors.go IsControllerMismatch skips a source object when the key is
|
||||||
# PRESENT and the value is NOT ControllerValue. So "disowned" == key present, value != claimed.
|
# PRESENT and the value is NOT ControllerValue. So "disowned" == key present, value != claimed.
|
||||||
CONTROLLER_KEY = "external-dns.alpha.kubernetes.io/controller"
|
#
|
||||||
|
# THE PREFIX IS NOT A CONSTANT OF NATURE. Every key external-dns reads is `<annotationPrefix><name>`,
|
||||||
|
# and the prefix is a runtime flag whose DEFAULT moved in v0.22.0 (#6424: alpha -> GA, "with no
|
||||||
|
# fallback"). On 2026-11-07 the chart bump to v0.23.0 made every key below invisible to the running
|
||||||
|
# controller while this check — which only asserted the keys were PRESENT — stayed green. (8) now
|
||||||
|
# ties ANNOTATION_PREFIX to the HelmRelease's explicit `annotationPrefix`.
|
||||||
|
ANNOTATION_PREFIX = "external-dns.alpha.kubernetes.io/"
|
||||||
|
CONTROLLER_KEY = ANNOTATION_PREFIX + "controller"
|
||||||
CONTROLLER_CLAIMED = "dns-controller"
|
CONTROLLER_CLAIMED = "dns-controller"
|
||||||
HOSTNAME_KEY = "external-dns.alpha.kubernetes.io/hostname"
|
HOSTNAME_KEY = ANNOTATION_PREFIX + "hostname"
|
||||||
|
# Any key that looks like an external-dns annotation, under ANY prefix variant (alpha, GA, typos).
|
||||||
|
EXTERNAL_DNS_KEY_RE = re.compile(r"^external-dns(\.[a-z0-9-]+)*\.kubernetes\.io/")
|
||||||
|
|
||||||
|
|
||||||
def check(cond, msg):
|
def check(cond, msg):
|
||||||
@@ -231,9 +243,7 @@ for sub in SCAN_DIRS:
|
|||||||
ann = md.get("annotations") or {}
|
ann = md.get("annotations") or {}
|
||||||
if kind == "HTTPRoute":
|
if kind == "HTTPRoute":
|
||||||
routes.append((rel, d))
|
routes.append((rel, d))
|
||||||
elif kind == "Service" and any(
|
elif kind == "Service" and any(EXTERNAL_DNS_KEY_RE.match(a) for a in ann):
|
||||||
a.startswith("external-dns.alpha.kubernetes.io/") for a in ann
|
|
||||||
):
|
|
||||||
dns_services.append((rel, d))
|
dns_services.append((rel, d))
|
||||||
|
|
||||||
check(len(routes) > 0, "inventory: no HTTPRoute found under apps/ platform/ runbooks/ — parse "
|
check(len(routes) > 0, "inventory: no HTTPRoute found under apps/ platform/ runbooks/ — parse "
|
||||||
@@ -444,6 +454,67 @@ check(
|
|||||||
"and the whole-repo-invariants-wired meta-test fails the gate on a hand-written stage.",
|
"and the whole-repo-invariants-wired meta-test fails the gate on a hand-written stage.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# =============================================================================================
|
||||||
|
# (8) THE RUNNING CONTROLLER READS THE PREFIX THIS TREE WRITES.
|
||||||
|
# (a) the external-dns HelmRelease sets `values.annotationPrefix` EXPLICITLY (an upstream
|
||||||
|
# default is exactly what moved under us) and it equals ANNOTATION_PREFIX;
|
||||||
|
# (b) it does not ALSO enable `--enable-legacy-annotation-prefix` (dual-read hides a
|
||||||
|
# half-migration; upstream says the flag will be removed);
|
||||||
|
# (c) every external-dns-looking annotation key on any object under apps/ platform/ runbooks/
|
||||||
|
# uses ANNOTATION_PREFIX — a key under another prefix is silently ignored at runtime;
|
||||||
|
# (d) catalog/render.py emits no external-dns key under another prefix.
|
||||||
|
# Migrating to the GA prefix = change ANNOTATION_PREFIX, the HelmRelease value, the renderer
|
||||||
|
# and every manifest in ONE commit; this assertion fails on any partial state.
|
||||||
|
# =============================================================================================
|
||||||
|
HR_PATH = ROOT / "platform/externaldns/helmrelease.yaml"
|
||||||
|
hr = next((d for d in docs_of(HR_PATH) if d.get("kind") == "HelmRelease"
|
||||||
|
and (d.get("metadata") or {}).get("name") == "external-dns"), None)
|
||||||
|
check(hr is not None, f"(8) {HR_PATH.relative_to(ROOT)} holds no HelmRelease `external-dns` — "
|
||||||
|
f"cannot prove which annotation prefix the controller reads")
|
||||||
|
values = ((hr or {}).get("spec") or {}).get("values") or {}
|
||||||
|
hr_prefix = values.get("annotationPrefix")
|
||||||
|
check(
|
||||||
|
hr_prefix == ANNOTATION_PREFIX,
|
||||||
|
f"(8a) external-dns HelmRelease `values.annotationPrefix` is {hr_prefix!r}, but every "
|
||||||
|
f"external-dns annotation in this tree uses {ANNOTATION_PREFIX!r}. A mismatch (or relying on "
|
||||||
|
f"the upstream default, which changed in v0.22.0) makes the controller IGNORE `controller: "
|
||||||
|
f"none`, the Gateway `target` and Service `hostname` — internal names go public, public names "
|
||||||
|
f"answer RFC1918 (incident 2026-11-07).",
|
||||||
|
)
|
||||||
|
extra = values.get("extraArgs") or []
|
||||||
|
extra_s = " ".join(f"{k}={v}" for k, v in extra.items()) if isinstance(extra, dict) \
|
||||||
|
else " ".join(map(str, extra))
|
||||||
|
check("enable-legacy-annotation-prefix" not in extra_s and "annotation-prefix" not in extra_s,
|
||||||
|
"(8b) external-dns extraArgs must not set --annotation-prefix or "
|
||||||
|
"--enable-legacy-annotation-prefix; the prefix is declared ONLY via values.annotationPrefix "
|
||||||
|
"so this check can see it")
|
||||||
|
|
||||||
|
foreign: list[str] = []
|
||||||
|
seen_keys = 0
|
||||||
|
for sub in SCAN_DIRS:
|
||||||
|
base = ROOT / sub
|
||||||
|
if not base.is_dir():
|
||||||
|
continue
|
||||||
|
for p in sorted(base.rglob("*.yaml")):
|
||||||
|
rel = p.relative_to(ROOT).as_posix()
|
||||||
|
for d in docs_of(p):
|
||||||
|
for k in annotations_of(d):
|
||||||
|
if isinstance(k, str) and EXTERNAL_DNS_KEY_RE.match(k):
|
||||||
|
seen_keys += 1
|
||||||
|
if not k.startswith(ANNOTATION_PREFIX):
|
||||||
|
foreign.append(f"{rel}:{'/'.join(ident(d))} {k}")
|
||||||
|
check(seen_keys > 0, "(8c) no external-dns annotation found anywhere — parse contract broken "
|
||||||
|
"(the Gateway target alone guarantees at least one)")
|
||||||
|
check(not foreign,
|
||||||
|
f"(8c) external-dns annotations under a prefix the controller does NOT read "
|
||||||
|
f"({ANNOTATION_PREFIX!r} is configured): {foreign}")
|
||||||
|
render_src = read(CATALOG / "render.py")
|
||||||
|
bad_render = sorted({m for m in re.findall(r"external-dns(?:\.[a-z0-9-]+)*\.kubernetes\.io/",
|
||||||
|
render_src) if m != ANNOTATION_PREFIX})
|
||||||
|
check(not bad_render,
|
||||||
|
f"(8d) catalog/render.py emits external-dns keys under {bad_render}, but the controller "
|
||||||
|
f"reads {ANNOTATION_PREFIX!r}")
|
||||||
|
|
||||||
# =============================================================================================
|
# =============================================================================================
|
||||||
if FAILURES:
|
if FAILURES:
|
||||||
for f in FAILURES:
|
for f in FAILURES:
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
date: 2026-11-07
|
||||||
|
branch: feat/devcontainer-velero-cli
|
||||||
|
title: Bake the velero CLI into the dev toolbox, pinned to the prod server version
|
||||||
|
---
|
||||||
|
|
||||||
|
## What changed
|
||||||
|
|
||||||
|
- `infra/devcontainer/versions.env`: new `VELERO_VERSION=1.18.2`, with a comment explaining that
|
||||||
|
the CLI and server are version-coupled, so this pin follows the server and is not bumped on
|
||||||
|
its own. It has **no** `# renovate:` marker on purpose. When a `velero` chart bump in
|
||||||
|
`platform/velero/helmrelease.yaml` changes the appVersion, bump this pin in the same PR.
|
||||||
|
- `infra/devcontainer/Containerfile`: `ARG VELERO_VERSION`; the binary comes from the
|
||||||
|
vmware-tanzu GitHub release tarball and is installed in the same RUN as flux/helm. There is
|
||||||
|
no checksum check, which matches the other tools in that file (none of them verify one).
|
||||||
|
`velero=` is recorded in `/etc/dev-toolchain-versions`. The verify stage now checks that
|
||||||
|
`velero` is on PATH, and `check velero v${VELERO_VERSION}` asserts the output of
|
||||||
|
`velero version --client-only`.
|
||||||
|
- `infra/devcontainer/build-image.sh`: `VELERO_VERSION` added to `BUILD_ARGS`. CI reads that
|
||||||
|
list generically through `--print-build-args` (`.gitea/workflows/scripts/images.sh`), so
|
||||||
|
**no sensitive path (`.gitea/**`, `ci/**`) was touched**.
|
||||||
|
- `infra/devcontainer/README.md`: added a Backups row to the tool table.
|
||||||
|
- `runbooks/backup-failure.md`: under "First commands", a read-only
|
||||||
|
`velero backup describe --details` / `backup logs` example run through dev.sh.
|
||||||
|
|
||||||
|
## What I verified
|
||||||
|
|
||||||
|
- Live server version, read-only: `kubectl --context k3s-prod -n velero get deploy velero`
|
||||||
|
shows image `docker.io/velero/velero:v1.18.2`, and HelmRelease `velero` has
|
||||||
|
lastAttemptedRevision `12.2.3`.
|
||||||
|
- Local build: `IMAGE=localhost/home-k3s-dev:velero-wt ./build-image.sh` returned rc 0. The
|
||||||
|
verify stage printed `ok velero` (on PATH), `ok velero v1.18.2` (pin check) and
|
||||||
|
`TOOLBOX VERIFIED`. The image was built under a separate local tag and **not pushed**.
|
||||||
|
- Smoke test with that image against prod (read-only): `velero version` reports Client v1.18.2
|
||||||
|
and Server v1.18.2. `velero backup get` lists the daily backups, all `Completed` with 0 errors.
|
||||||
|
- Recurring warning (reported only, not fixed): every daily backup since
|
||||||
|
`velero-daily-20261031030050` has 1 warning. Backups up to 20261030 have 0. The warning text is:
|
||||||
|
`gitea-actions: resource: /pods name: /act-runner-5944dfdfb8-69bvq message: /Skip pod volume
|
||||||
|
data error: /pod is not in the expected status, ... phase=Failed: pod is not running`.
|
||||||
|
It is the same pod in 20261031, 20261104 and 20261108. `gitea-actions` holds 11 `Failed`
|
||||||
|
pods with reason `Evicted` from ReplicaSet `act-runner-5944dfdfb8`, ten of them started
|
||||||
|
2026-10-30T08:26Z. The running runner is `act-runner-fc597f6b5-vsvkl`. So the warning comes from a
|
||||||
|
leftover evicted (`Failed`) pod that Velero still tries to back up volumes for. Cleaning up the evicted pods is an
|
||||||
|
operator action.
|
||||||
|
- Gate: see the PR description for the toolbox `ci/gate.sh` rc and the skipped stages.
|
||||||
|
|
||||||
|
## What I did NOT verify
|
||||||
|
|
||||||
|
- The CI image build (`.gitea/workflows/images.yaml`) on this branch. It runs on push/PR and I
|
||||||
|
have not seen its result while writing this.
|
||||||
|
- An arm64 build. Only amd64 was built (`TARGETARCH` defaults to amd64).
|
||||||
|
- Any mutating velero command (`backup create`, `restore`, `delete`). None were run.
|
||||||
|
- Why only `-69bvq` of the 11 evicted act-runner pods shows up in the warning. A likely guess is
|
||||||
|
that only that pod's volumes match the backup's pod-volume selection, but I did not check.
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
date: 2026-11-07
|
||||||
|
branch: feat/l0-externaldns-annotation-prefix
|
||||||
|
title: L0 guard — external-dns annotationPrefix must match every external-dns annotation in the tree
|
||||||
|
---
|
||||||
|
|
||||||
|
## What changed
|
||||||
|
|
||||||
|
`conformance/tests/l0-static/internal-services-publish-no-public-dns/check.sh` gains assertion
|
||||||
|
(8). The check is already whole-repo-scoped, so it runs on every gate.
|
||||||
|
|
||||||
|
- **(8a)** `platform/externaldns/helmrelease.yaml` must set `values.annotationPrefix` explicitly,
|
||||||
|
and it must equal the check's `ANNOTATION_PREFIX` (`external-dns.alpha.kubernetes.io/`). The
|
||||||
|
check does not trust the upstream default, because that default is what changed.
|
||||||
|
- **(8b)** `extraArgs` must not carry `--annotation-prefix` or `--enable-legacy-annotation-prefix`,
|
||||||
|
so the prefix has one declared place.
|
||||||
|
- **(8c)** Every external-dns-looking annotation key under apps/, platform/ and runbooks/ must use
|
||||||
|
that prefix. The match is any `external-dns(.x)*.kubernetes.io/` key.
|
||||||
|
- **(8d)** catalog/render.py must emit no external-dns key under any other prefix.
|
||||||
|
|
||||||
|
`CONTROLLER_KEY` and `HOSTNAME_KEY` are now derived from `ANNOTATION_PREFIX`. The Service
|
||||||
|
inventory matches any external-dns prefix variant, not only the alpha one.
|
||||||
|
|
||||||
|
Why: on 2026-11-07, external-dns v0.23.0 (whose default prefix is GA) ignored every alpha
|
||||||
|
annotation. Assertions (1) to (3) stayed green, because they prove the annotations are present,
|
||||||
|
not that the controller reads them. See `decisions/2026-11-07-public-dns-wan-a-record.md`.
|
||||||
|
|
||||||
|
This branch is stacked on `fix/public-dns-wan-a-record` (PR #140). Without that fix, (8a) fails,
|
||||||
|
which is correct. Merge #140 first.
|
||||||
|
|
||||||
|
## What I verified
|
||||||
|
|
||||||
|
Run on the host (PyYAML):
|
||||||
|
|
||||||
|
- the check passes on this tree: `check OK: 62 assertions passed`.
|
||||||
|
- it **fails (8a)** with origin/main's HelmRelease, which has no `annotationPrefix`.
|
||||||
|
- it **fails (8c)** when `platform/gateway/gateway.yaml`'s `target` key is changed to the GA
|
||||||
|
prefix.
|
||||||
|
|
||||||
|
Toolbox gate: see the PR description.
|
||||||
|
|
||||||
|
## What I did NOT verify
|
||||||
|
|
||||||
|
- Whether a future chart could read the prefix from somewhere other than `values.annotationPrefix`,
|
||||||
|
for example a renamed chart value. (8a) would then pass while the flag silently vanished from the
|
||||||
|
rendered args. Rendering the chart in the gate would close that gap. Not done: it needs network
|
||||||
|
access or a vendored chart in L0.
|
||||||
|
- `conformance/**` is a **sensitive path**. This PR needs the operator's approving review or
|
||||||
|
`/approve-override`. The agent did not post one.
|
||||||
|
- The header of the sibling chainsaw-test.yaml still says "external-dns v0.21.0". That is cosmetic
|
||||||
|
and I left it alone.
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
---
|
||||||
|
date: 2026-11-07
|
||||||
|
branch: fix/public-dns-wan-a-record
|
||||||
|
title: external-dns v0.23 ignored every alpha-prefixed annotation — pin annotationPrefix back
|
||||||
|
---
|
||||||
|
|
||||||
|
## What changed
|
||||||
|
|
||||||
|
`platform/externaldns/helmrelease.yaml`:
|
||||||
|
|
||||||
|
- adds `values.annotationPrefix: "external-dns.alpha.kubernetes.io/"`. The chart renders it as
|
||||||
|
`--annotation-prefix=`.
|
||||||
|
- tightens the chart range from `<1.24.0` to `>=1.23.0 <1.24.0`, because that flag needs app
|
||||||
|
>= 0.22.
|
||||||
|
- adds a comment block explaining why.
|
||||||
|
|
||||||
|
**Root cause.** Renovate PR #129 (merged 12:12:00Z) moved the chart from `>=1.21.0 <1.22.0` to
|
||||||
|
`<1.24.0`. Helm deployed chart 1.23.0 at 12:13:08Z, which moved the app from v0.21.0 to v0.23.0.
|
||||||
|
The upstream v0.22.0 release notes (#6424) say: "The default annotation prefix is now
|
||||||
|
`external-dns.kubernetes.io/` with no fallback". Every external-dns annotation in this repo uses
|
||||||
|
`external-dns.alpha.kubernetes.io/`. At 12:13:12Z v0.23.0 therefore:
|
||||||
|
|
||||||
|
- **ignored the Gateway's `target`.** Every public name flipped from `CNAME
|
||||||
|
wan.home.keithjackson.dev` to `A 192.168.2.20`, an RFC1918 address served in public DNS.
|
||||||
|
- **ignored `controller: none`.** The internal names jackett, pihole and semaphore were published.
|
||||||
|
That breaks R-DNS-03.
|
||||||
|
- **ignored the L4 Services' `hostname`.** The unifi and crafty-mc A records were deleted.
|
||||||
|
|
||||||
|
cloudflare-ddns (#123, v1.18.0) is not involved. It logs only `IPv4-enabled domains:
|
||||||
|
wan.home.keithjackson.dev` and has made no changes.
|
||||||
|
|
||||||
|
**Why pin the prefix instead of migrating the annotations.** The pin is one value in one file, and
|
||||||
|
that file is not on the sensitive-path list. It restores the exact v0.21 reading of `target`,
|
||||||
|
`controller` and `hostname` together. A migration would touch `catalog/render.py`, every
|
||||||
|
`apps/wave-*` file, the hand-written platform manifests, and the conformance goldens and checks.
|
||||||
|
`conformance/**` needs an operator override. And a migration half-applied in either direction
|
||||||
|
reproduces this incident.
|
||||||
|
|
||||||
|
I rejected `--enable-legacy-annotation-prefix`. Upstream calls it a migration aid that will be
|
||||||
|
removed.
|
||||||
|
|
||||||
|
**Follow-up.** The L0 guard that the tree's annotation prefix matches this value is a separate PR
|
||||||
|
(`feat/l0-externaldns-annotation-prefix`), because it lives in `conformance/**`. A full migration
|
||||||
|
to the GA prefix remains open. It must change the annotations and this value in one commit.
|
||||||
|
|
||||||
|
## What I verified
|
||||||
|
|
||||||
|
- **Live HelmRelease history.** `1.23.0 0.23.0 lastDeployed 2026-11-07T12:13:08Z deployed`, and
|
||||||
|
before that `1.21.1 0.21.0 ... superseded`. The Deployment image is `external-dns:v0.23.0`. The
|
||||||
|
startup config logs `AnnotationPrefix:external-dns.kubernetes.io/
|
||||||
|
EnableLegacyAnnotationPrefix:false`.
|
||||||
|
- **external-dns log, 12:13:12Z:**
|
||||||
|
- `action=CREATE ... type=A` and `a-` TXT for gitea, crafty, the apex, headlamp, auth, jackett,
|
||||||
|
nextcloud, keycloak, grafana, seerr, plex, pihole and semaphore.
|
||||||
|
- `action=DELETE ... type=CNAME` and `cname-` TXT for all of those except jackett, pihole and
|
||||||
|
semaphore.
|
||||||
|
- `action=DELETE ... type=A` for unifi and crafty-mc.
|
||||||
|
- **Before the upgrade.** Loki covers about 11-05 13:00Z to 12:13Z. The old pod logged no `Changing
|
||||||
|
record` line in that window. Its only error was one Cloudflare 502 on 11-05.
|
||||||
|
- **Gateway.** `gateway/shared` still carries `external-dns.alpha.kubernetes.io/target:
|
||||||
|
wan.home.keithjackson.dev`.
|
||||||
|
- **Public DNS.** At the authority (betty.ns.cloudflare.com), 1.1.1.1 and 8.8.8.8:
|
||||||
|
- plex, gitea, auth, the apex, jackett, pihole and semaphore answer A `192.168.2.20`.
|
||||||
|
- unifi and crafty-mc have no record.
|
||||||
|
- `wan.` answers A `136.51.115.252`.
|
||||||
|
- There is no AAAA for any of these names.
|
||||||
|
- The `a-plex` and `a-jackett` TXT records read `external-dns/owner=prod`.
|
||||||
|
- **Upstream v0.23.0 source.**
|
||||||
|
- `controller/execute.go` calls `annotations.SetAnnotationPrefix(cfg.AnnotationPrefix)`.
|
||||||
|
- `SetAnnotationPrefix` rebuilds `TargetKey`, `ControllerKey` and `HostnameKey`.
|
||||||
|
- `source/gateway.go` still reads `TargetsFromTargetAnnotation(gw.Annotations)`.
|
||||||
|
- Chart 1.23.0 `templates/deployment.yaml` renders `--annotation-prefix={{
|
||||||
|
.Values.annotationPrefix }}`.
|
||||||
|
- **Phone.** Pi-hole 192.168.2.23 answers correctly: A `192.168.2.20` with TTL 60, and AAAA
|
||||||
|
NOERROR with no data. The router 192.168.2.1 answers `gitea.` with NOERROR and 0 answers, because
|
||||||
|
it strips RFC1918 results. The workstation uses only `nameserver 192.168.2.23` and has
|
||||||
|
`disable_ipv6 = 1`.
|
||||||
|
- **Toolbox gate.** See the PR description.
|
||||||
|
|
||||||
|
## What I did NOT verify
|
||||||
|
|
||||||
|
- **The fix live.** This depends on Flux applying it. Expected on the first reconcile, with
|
||||||
|
`policy: sync` and owner `prod`, all of these records being prod-owned:
|
||||||
|
- DELETE the A record and create CNAME `wan.` for the ten public names.
|
||||||
|
- DELETE the A and TXT records for jackett, pihole and semaphore.
|
||||||
|
- CREATE A for unifi (.21) and crafty-mc (.22).
|
||||||
|
This is UNVERIFIED until someone reads the external-dns log and checks with `dig
|
||||||
|
@betty.ns.cloudflare.com`. The A-to-CNAME type swap went through cleanly on 2026-10-03, and the
|
||||||
|
reverse went through today, so I expect it to converge. If it does not, see the manual list in
|
||||||
|
the PR.
|
||||||
|
- **That the chart's schema accepts `annotationPrefix` under Flux.** I did not render the
|
||||||
|
HelmRelease with helm here. The gate checks YAML and policy, not the chart render.
|
||||||
|
- **The phone.** Not investigated, by decision. Likely cause: the phone bypasses Pi-hole on the
|
||||||
|
client side. Candidates are IPv6 RA/RDNSS DNS from the router, Android Private DNS and iCloud
|
||||||
|
Private Relay. Answers it gets that way are rebind-stripped, or point to the WAN IP, which needs
|
||||||
|
hairpin NAT. Hairpin looked absent on 2026-10-03 (a self-signed cert on WAN:443). This is
|
||||||
|
router- or client-side, not in-repo.
|
||||||
|
- The AAAA records the operator saw on 8.8.8.8. I did not reproduce them.
|
||||||
|
- The cilium (#104) and pihole (#109) bumps were not examined beyond Pi-hole answering correctly.
|
||||||
@@ -30,6 +30,9 @@ ARG CHECK_JSONSCHEMA_VERSION
|
|||||||
# Pinned to the Prometheus version prod actually runs — see versions.env for why that lockstep is
|
# Pinned to the Prometheus version prod actually runs — see versions.env for why that lockstep is
|
||||||
# the point, and for the live-cluster command that establishes it.
|
# the point, and for the live-cluster command that establishes it.
|
||||||
ARG PROMTOOL_VERSION
|
ARG PROMTOOL_VERSION
|
||||||
|
# velero: operator CLI for the backup server. Pinned to the server version prod runs — the CLI
|
||||||
|
# and server are version-coupled; see versions.env.
|
||||||
|
ARG VELERO_VERSION
|
||||||
ARG TARGETARCH=amd64
|
ARG TARGETARCH=amd64
|
||||||
|
|
||||||
# Distro packages: libvirt client libs (harmless disk-image utilities), ansible, ssh,
|
# Distro packages: libvirt client libs (harmless disk-image utilities), ansible, ssh,
|
||||||
@@ -66,6 +69,10 @@ RUN set -eux; \
|
|||||||
curl -fsSL "https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/${TARGETARCH}/kubectl" -o /usr/local/bin/kubectl; chmod +x /usr/local/bin/kubectl; \
|
curl -fsSL "https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/${TARGETARCH}/kubectl" -o /usr/local/bin/kubectl; chmod +x /usr/local/bin/kubectl; \
|
||||||
curl -fsSL "https://get.helm.sh/helm-v${HELM_VERSION}-linux-${TARGETARCH}.tar.gz" | tar -xz -C /tmp; \
|
curl -fsSL "https://get.helm.sh/helm-v${HELM_VERSION}-linux-${TARGETARCH}.tar.gz" | tar -xz -C /tmp; \
|
||||||
install -m0755 "/tmp/linux-${TARGETARCH}/helm" /usr/local/bin/helm; \
|
install -m0755 "/tmp/linux-${TARGETARCH}/helm" /usr/local/bin/helm; \
|
||||||
|
VELERO_TARBALL="velero-v${VELERO_VERSION}-linux-${TARGETARCH}"; \
|
||||||
|
curl -fsSL "https://github.com/vmware-tanzu/velero/releases/download/v${VELERO_VERSION}/${VELERO_TARBALL}.tar.gz" \
|
||||||
|
| tar -xz -C /usr/local/bin --strip-components=1 "${VELERO_TARBALL}/velero"; \
|
||||||
|
chmod +x /usr/local/bin/velero; \
|
||||||
rm -rf /tmp/*
|
rm -rf /tmp/*
|
||||||
|
|
||||||
# Pinned conformance test toolchain (L0/L1 static gate + the L2/L3 Chainsaw runner).
|
# Pinned conformance test toolchain (L0/L1 static gate + the L2/L3 Chainsaw runner).
|
||||||
@@ -93,7 +100,7 @@ RUN { echo "opentofu=${OPENTOFU_VERSION}"; echo "age=${AGE_VERSION}"; echo "sops
|
|||||||
echo "kyverno=${KYVERNO_VERSION}"; echo "kyverno-json=${KYVERNO_JSON_VERSION}"; \
|
echo "kyverno=${KYVERNO_VERSION}"; echo "kyverno-json=${KYVERNO_JSON_VERSION}"; \
|
||||||
echo "chainsaw=${CHAINSAW_VERSION}"; echo "yamllint=${YAMLLINT_VERSION}"; \
|
echo "chainsaw=${CHAINSAW_VERSION}"; echo "yamllint=${YAMLLINT_VERSION}"; \
|
||||||
echo "yq=${YQ_VERSION}"; echo "check-jsonschema=${CHECK_JSONSCHEMA_VERSION}"; \
|
echo "yq=${YQ_VERSION}"; echo "check-jsonschema=${CHECK_JSONSCHEMA_VERSION}"; \
|
||||||
echo "promtool=${PROMTOOL_VERSION}"; \
|
echo "promtool=${PROMTOOL_VERSION}"; echo "velero=${VELERO_VERSION}"; \
|
||||||
} > /etc/dev-toolchain-versions
|
} > /etc/dev-toolchain-versions
|
||||||
|
|
||||||
WORKDIR /work
|
WORKDIR /work
|
||||||
@@ -112,6 +119,7 @@ ARG KUBECTL_VERSION
|
|||||||
ARG CHAINSAW_VERSION
|
ARG CHAINSAW_VERSION
|
||||||
ARG KYVERNO_VERSION
|
ARG KYVERNO_VERSION
|
||||||
ARG SOPS_VERSION
|
ARG SOPS_VERSION
|
||||||
|
ARG VELERO_VERSION
|
||||||
RUN --network=none /bin/bash -euo pipefail <<'VERIFY'
|
RUN --network=none /bin/bash -euo pipefail <<'VERIFY'
|
||||||
echo "--- no network (the build-time stand-in for run --network none) ---"
|
echo "--- no network (the build-time stand-in for run --network none) ---"
|
||||||
if getent hosts registry.fedoraproject.org >/dev/null 2>&1 || curl -sS --max-time 3 -o /dev/null https://1.1.1.1 2>/dev/null; then
|
if getent hosts registry.fedoraproject.org >/dev/null 2>&1 || curl -sS --max-time 3 -o /dev/null https://1.1.1.1 2>/dev/null; then
|
||||||
@@ -122,13 +130,13 @@ echo "--- baked versions ---"; cat /etc/dev-toolchain-versions
|
|||||||
echo "--- every tool is on PATH ---"
|
echo "--- every tool is on PATH ---"
|
||||||
for t in tofu age age-keygen sops flux kubectl helm kyverno kyverno-json chainsaw yq \
|
for t in tofu age age-keygen sops flux kubectl helm kyverno kyverno-json chainsaw yq \
|
||||||
yamllint check-jsonschema promtool ansible-playbook git make jq diff python3 \
|
yamllint check-jsonschema promtool ansible-playbook git make jq diff python3 \
|
||||||
wg qrencode; do
|
wg qrencode velero; do
|
||||||
if command -v "$t" >/dev/null 2>&1; then printf " ok %s\n" "$t";
|
if command -v "$t" >/dev/null 2>&1; then printf " ok %s\n" "$t";
|
||||||
else printf " MISSING %s\n" "$t"; missing=1; fi
|
else printf " MISSING %s\n" "$t"; missing=1; fi
|
||||||
done
|
done
|
||||||
[ -z "${missing:-}" ] || { echo "toolbox is missing tools"; exit 1; }
|
[ -z "${missing:-}" ] || { echo "toolbox is missing tools"; exit 1; }
|
||||||
echo "--- version-sensitive tools report their pins ---"
|
echo "--- version-sensitive tools report their pins ---"
|
||||||
: "${PROMTOOL_VERSION:?}" "${KUBECTL_VERSION:?}" "${CHAINSAW_VERSION:?}" "${KYVERNO_VERSION:?}" "${SOPS_VERSION:?}"
|
: "${PROMTOOL_VERSION:?}" "${KUBECTL_VERSION:?}" "${CHAINSAW_VERSION:?}" "${KYVERNO_VERSION:?}" "${SOPS_VERSION:?}" "${VELERO_VERSION:?}"
|
||||||
check() { # name pin actual-output
|
check() { # name pin actual-output
|
||||||
if printf "%s" "$3" | grep -qF "$2"; then printf " ok %-10s %s\n" "$1" "$2";
|
if printf "%s" "$3" | grep -qF "$2"; then printf " ok %-10s %s\n" "$1" "$2";
|
||||||
else printf " DRIFT %-10s want %s, got: %s\n" "$1" "$2" "$(printf "%s" "$3" | head -1)"; drift=1; fi
|
else printf " DRIFT %-10s want %s, got: %s\n" "$1" "$2" "$(printf "%s" "$3" | head -1)"; drift=1; fi
|
||||||
@@ -138,6 +146,7 @@ check kubectl "${KUBECTL_VERSION}" "$(kubectl version --client 2>&1)"
|
|||||||
check chainsaw "${CHAINSAW_VERSION}" "$(chainsaw version 2>&1)"
|
check chainsaw "${CHAINSAW_VERSION}" "$(chainsaw version 2>&1)"
|
||||||
check kyverno "${KYVERNO_VERSION}" "$(kyverno version 2>&1)"
|
check kyverno "${KYVERNO_VERSION}" "$(kyverno version 2>&1)"
|
||||||
check sops "${SOPS_VERSION}" "$(sops --version --disable-version-check 2>&1 || sops --version 2>&1)"
|
check sops "${SOPS_VERSION}" "$(sops --version --disable-version-check 2>&1 || sops --version 2>&1)"
|
||||||
|
check velero "v${VELERO_VERSION}" "$(velero version --client-only 2>&1)"
|
||||||
[ -z "${drift:-}" ] || { echo "a tool does not report its pinned version"; exit 1; }
|
[ -z "${drift:-}" ] || { echo "a tool does not report its pinned version"; exit 1; }
|
||||||
echo "--- python can import what the renderer and the gate need ---"
|
echo "--- python can import what the renderer and the gate need ---"
|
||||||
python3 -c "import yaml; print(' PyYAML', yaml.__version__)"
|
python3 -c "import yaml; print(' PyYAML', yaml.__version__)"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ baseline, and the *same* binaries CI uses.
|
|||||||
| Provisioning | OpenTofu (`bpg/proxmox` provider), `ansible-core`, `openssh-clients` |
|
| Provisioning | OpenTofu (`bpg/proxmox` provider), `ansible-core`, `openssh-clients` |
|
||||||
| GitOps / cluster | `flux`, `kubectl`, `helm` |
|
| GitOps / cluster | `flux`, `kubectl`, `helm` |
|
||||||
| Secrets | `sops`, `age` + `age-keygen` |
|
| Secrets | `sops`, `age` + `age-keygen` |
|
||||||
|
| Backups (`platform/velero`) | `velero` CLI — pinned to the **server** version prod runs (version-coupled; see `versions.env`) |
|
||||||
| VPN (`platform/vpn-ingress`) | `wg` (wireguard-tools) — mint the server key, derive client public keys; `qrencode` — render a client profile as a phone-scannable QR |
|
| VPN (`platform/vpn-ingress`) | `wg` (wireguard-tools) — mint the server key, derive client public keys; `qrencode` — render a client profile as a phone-scannable QR |
|
||||||
| **CI gate** | `yamllint`, `check-jsonschema`, `kyverno` CLI, `kyverno-json`, `chainsaw`, `yq`, `diffutils`, `make` |
|
| **CI gate** | `yamllint`, `check-jsonschema`, `kyverno` CLI, `kyverno-json`, `chainsaw`, `yq`, `diffutils`, `make` |
|
||||||
| Misc | `qemu-img`, `cloud-utils`/`genisoimage`, `libvirt-client` (disk-image utilities only — leftovers from the libvirt era, harmless) |
|
| Misc | `qemu-img`, `cloud-utils`/`genisoimage`, `libvirt-client` (disk-image utilities only — leftovers from the libvirt era, harmless) |
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ BUILD_ARGS=(
|
|||||||
"KYVERNO_JSON_VERSION=$KYVERNO_JSON_VERSION" "CHAINSAW_VERSION=$CHAINSAW_VERSION"
|
"KYVERNO_JSON_VERSION=$KYVERNO_JSON_VERSION" "CHAINSAW_VERSION=$CHAINSAW_VERSION"
|
||||||
"YAMLLINT_VERSION=$YAMLLINT_VERSION" "YQ_VERSION=$YQ_VERSION"
|
"YAMLLINT_VERSION=$YAMLLINT_VERSION" "YQ_VERSION=$YQ_VERSION"
|
||||||
"CHECK_JSONSCHEMA_VERSION=$CHECK_JSONSCHEMA_VERSION" "PROMTOOL_VERSION=$PROMTOOL_VERSION"
|
"CHECK_JSONSCHEMA_VERSION=$CHECK_JSONSCHEMA_VERSION" "PROMTOOL_VERSION=$PROMTOOL_VERSION"
|
||||||
|
"VELERO_VERSION=$VELERO_VERSION"
|
||||||
)
|
)
|
||||||
if [ "${PRINT_ARGS}" -eq 1 ]; then printf '%s\n' "${BUILD_ARGS[@]}"; exit 0; fi
|
if [ "${PRINT_ARGS}" -eq 1 ]; then printf '%s\n' "${BUILD_ARGS[@]}"; exit 0; fi
|
||||||
|
|
||||||
|
|||||||
@@ -44,4 +44,19 @@ CHECK_JSONSCHEMA_VERSION=0.29.4
|
|||||||
# promtool ships INSIDE the prometheus release tarball (prometheus-<v>.linux-<arch>.tar.gz), so
|
# promtool ships INSIDE the prometheus release tarball (prometheus-<v>.linux-<arch>.tar.gz), so
|
||||||
# the version string here is a Prometheus release, not a separate promtool release.
|
# the version string here is a Prometheus release, not a separate promtool release.
|
||||||
PROMTOOL_VERSION=3.14.0
|
PROMTOOL_VERSION=3.14.0
|
||||||
|
# velero — the operator CLI for the Velero backup server (platform/velero): `velero backup
|
||||||
|
# describe <name> --details`, `velero backup logs`, `velero backup create --from-schedule`.
|
||||||
|
#
|
||||||
|
# THIS PIN IS NOT FREE-FLOATING: the CLI and server are VERSION-COUPLED (the CLI talks to the
|
||||||
|
# server's CRDs and its download-request protocol; upstream supports only a matching minor), so
|
||||||
|
# it MUST equal the Velero server version actually running on prod.
|
||||||
|
# Verified against the live cluster on 2026-11-07:
|
||||||
|
# kubectl --context k3s-prod -n velero get deploy velero \
|
||||||
|
# -o jsonpath='{.spec.template.spec.containers[0].image}'
|
||||||
|
# -> docker.io/velero/velero:v1.18.2 (HelmRelease velero, chart velero-12.2.3)
|
||||||
|
# Deliberately carries NO `# renovate:` marker: it must move WITH the server, i.e. when a
|
||||||
|
# vmware-tanzu `velero` chart bump (platform/velero/helmrelease.yaml, range >=12 <13) changes the
|
||||||
|
# chart's appVersion, bump this in the same PR. Never bump it on its own. The verify stage
|
||||||
|
# asserts `velero version --client-only` reports exactly this version.
|
||||||
|
VELERO_VERSION=1.18.2
|
||||||
# Proxmox provider (bpg/proxmox) is pinned in the tofu module (infra/tofu/versions.tf), not here.
|
# Proxmox provider (bpg/proxmox) is pinned in the tofu module (infra/tofu/versions.tf), not here.
|
||||||
@@ -61,7 +61,7 @@ spec:
|
|||||||
- name: crafty
|
- name: crafty
|
||||||
# registry.gitlab.com/crafty-controller/crafty-4 — official image. Pinned to the current
|
# registry.gitlab.com/crafty-controller/crafty-4 — official image. Pinned to the current
|
||||||
# stable release (4.10.8, 2026-07-29; tags are bare semver, NO `v` prefix). Renovate maintains.
|
# stable release (4.10.8, 2026-07-29; tags are bare semver, NO `v` prefix). Renovate maintains.
|
||||||
image: registry.gitlab.com/crafty-controller/crafty-4:4.10.8
|
image: registry.gitlab.com/crafty-controller/crafty-4:4.11.0
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
env:
|
env:
|
||||||
- name: TZ
|
- name: TZ
|
||||||
|
|||||||
@@ -51,4 +51,4 @@ images:
|
|||||||
# Controller image == CRD version. A version bump = re-vendor crds.yaml + snapshot-controller.yaml
|
# Controller image == CRD version. A version bump = re-vendor crds.yaml + snapshot-controller.yaml
|
||||||
# at the new tag (see header) AND bump this newTag.
|
# at the new tag (see header) AND bump this newTag.
|
||||||
- name: registry.k8s.io/sig-storage/snapshot-controller
|
- name: registry.k8s.io/sig-storage/snapshot-controller
|
||||||
newTag: v8.2.0
|
newTag: v8.7.0
|
||||||
@@ -194,7 +194,7 @@ spec:
|
|||||||
- args:
|
- args:
|
||||||
- --v=5
|
- --v=5
|
||||||
- --leader-election=true
|
- --leader-election=true
|
||||||
image: registry.k8s.io/sig-storage/snapshot-controller:v8.0.1
|
image: registry.k8s.io/sig-storage/snapshot-controller:v8.7.0
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
name: snapshot-controller
|
name: snapshot-controller
|
||||||
serviceAccountName: snapshot-controller
|
serviceAccountName: snapshot-controller
|
||||||
@@ -51,7 +51,7 @@ spec:
|
|||||||
type: RuntimeDefault
|
type: RuntimeDefault
|
||||||
containers:
|
containers:
|
||||||
- name: cloudflare-ddns
|
- name: cloudflare-ddns
|
||||||
image: favonia/cloudflare-ddns:1.17.1
|
image: favonia/cloudflare-ddns:1.18.0
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
env:
|
env:
|
||||||
- name: CLOUDFLARE_API_TOKEN
|
- name: CLOUDFLARE_API_TOKEN
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ spec:
|
|||||||
chart:
|
chart:
|
||||||
spec:
|
spec:
|
||||||
chart: external-dns
|
chart: external-dns
|
||||||
version: ">=1.21.0 <1.22.0" # k8s-1.36 catch-up (app v0.21; policy already set). Renovate maintains
|
# chart 1.23.x = app v0.23.0. Floor >=1.23.0 because `annotationPrefix` below needs app >=0.22.
|
||||||
|
# Renovate maintains — but READ THE APP RELEASE NOTES on every 0.x minor: 0.22 changed the
|
||||||
|
# annotation prefix with no fallback and silently re-published every internal name (2026-11-07).
|
||||||
|
version: ">=1.23.0 <1.24.0"
|
||||||
sourceRef:
|
sourceRef:
|
||||||
kind: HelmRepository
|
kind: HelmRepository
|
||||||
name: external-dns
|
name: external-dns
|
||||||
@@ -25,6 +28,29 @@ spec:
|
|||||||
values:
|
values:
|
||||||
provider:
|
provider:
|
||||||
name: cloudflare
|
name: cloudflare
|
||||||
|
# ---------------------------------------------------------------------------------------------
|
||||||
|
# ANNOTATION PREFIX — PINNED TO THE LEGACY `alpha` PREFIX (2026-11-07). Do not remove.
|
||||||
|
#
|
||||||
|
# external-dns v0.22.0 (#6424) changed the default annotation prefix from
|
||||||
|
# `external-dns.alpha.kubernetes.io/` to `external-dns.kubernetes.io/` "with no fallback".
|
||||||
|
# EVERY external-dns annotation in this repo uses the alpha prefix: the Gateway's `target`
|
||||||
|
# (platform/gateway/gateway.yaml), the renderer's `controller: none` on `internal` routes
|
||||||
|
# (catalog/render.py -> apps/wave-*), and the L4 Services' `hostname` (crafty-mc, unifi).
|
||||||
|
# Renovate PR #129 moved the chart range to `<1.24.0` (app v0.21.0 -> v0.23.0) and on the first
|
||||||
|
# reconcile (2026-11-07T12:13Z) v0.23.0, reading only the GA prefix, saw none of them:
|
||||||
|
# * `target` ignored -> every public name flipped CNAME wan.<base> -> A 192.168.2.20 (RFC1918)
|
||||||
|
# * `controller: none` ignored -> internal jackett, pihole, semaphore PUBLISHED (R-DNS-03 broken)
|
||||||
|
# * `hostname` ignored -> unifi and crafty-mc A records DELETED
|
||||||
|
# Logged as `action=CREATE ... type=A` / `action=DELETE ... type=CNAME` for every name at once.
|
||||||
|
#
|
||||||
|
# The chart's `annotationPrefix` renders `--annotation-prefix=`, which v0.23.0 applies to every
|
||||||
|
# key it reads (source/annotations/annotations.go SetAnnotationPrefix: target, controller,
|
||||||
|
# hostname, ttl, ...), restoring exactly the v0.21 behaviour. Chosen over
|
||||||
|
# `--enable-legacy-annotation-prefix` (a dual-read migration aid upstream says will be removed)
|
||||||
|
# and over migrating the annotations (renderer + goldens + conformance = sensitive paths, and a
|
||||||
|
# flag-day across ~20 objects). Migrating to the GA prefix is the follow-up: change every
|
||||||
|
# annotation AND this value in ONE commit, never one without the other.
|
||||||
|
annotationPrefix: "external-dns.alpha.kubernetes.io/"
|
||||||
# Watch Gateway API routes (Cilium Gateway) + Services. docs/09: records rendered from intent.
|
# Watch Gateway API routes (Cilium Gateway) + Services. docs/09: records rendered from intent.
|
||||||
#
|
#
|
||||||
# BOTH SOURCES ARE UNFILTERED HERE, AND THAT IS DELIBERATE — see the WHICH RESOURCES block
|
# BOTH SOURCES ARE UNFILTERED HERE, AND THAT IS DELIBERATE — see the WHICH RESOURCES block
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ spec:
|
|||||||
# values (-session-ttl + the -oidc-* args from the externalSecret; useCookie stays default-false
|
# values (-session-ttl + the -oidc-* args from the externalSecret; useCookie stays default-false
|
||||||
# so `-oidc-use-cookie` is NOT emitted) is supported by the v0.41.0 backend. Renovate maintains
|
# so `-oidc-use-cookie` is NOT emitted) is supported by the v0.41.0 backend. Renovate maintains
|
||||||
# within 0.41.x; the appVersion continues to pin the Headlamp image.
|
# within 0.41.x; the appVersion continues to pin the Headlamp image.
|
||||||
version: ">=0.41.0 <0.42.0"
|
version: "<0.47.0"
|
||||||
sourceRef:
|
sourceRef:
|
||||||
kind: HelmRepository
|
kind: HelmRepository
|
||||||
name: headlamp
|
name: headlamp
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ spec:
|
|||||||
containers:
|
containers:
|
||||||
- name: homepage
|
- name: homepage
|
||||||
# Renovate maintains this pin (ghcr.io/gethomepage/homepage). Config lives in /app/config.
|
# Renovate maintains this pin (ghcr.io/gethomepage/homepage). Config lives in /app/config.
|
||||||
image: ghcr.io/gethomepage/homepage:v2.1.2
|
image: ghcr.io/gethomepage/homepage:v2.7.0
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
|
|||||||
@@ -1224,7 +1224,7 @@ spec:
|
|||||||
# curlimages elsewhere in the repo. Renovate does not manage plain
|
# curlimages elsewhere in the repo. Renovate does not manage plain
|
||||||
# `image:` tags (renovate.json disables image-tag updates so it never
|
# `image:` tags (renovate.json disables image-tag updates so it never
|
||||||
# fights Flux image-automation), so this is bumped by hand.
|
# fights Flux image-automation), so this is bumped by hand.
|
||||||
image: python:3.13.15-alpine3.23
|
image: python:3.15.2-alpine3.23
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
command:
|
command:
|
||||||
- python3
|
- python3
|
||||||
|
|||||||
@@ -3205,7 +3205,7 @@ spec:
|
|||||||
# sibling's pod on prod runs a different digest under this same tag.
|
# sibling's pod on prod runs a different digest under this same tag.
|
||||||
# Renovate's `kubernetes` manager covers platform/** (renovate.json5), so
|
# Renovate's `kubernetes` manager covers platform/** (renovate.json5), so
|
||||||
# it proposes tag and digest bumps for this line like any other image.
|
# it proposes tag and digest bumps for this line like any other image.
|
||||||
image: python:3.13.15-alpine3.23@sha256:6438599575cca0d1df94aeee0d2ae088d4d8846eab554b2ee7784a3a6df0d516
|
image: python:3.15.2-alpine3.23@sha256:8d77d813a53e534dec9e1e5182cbd5b651b70d63b27e4ea7c1f616b37cf9aad2
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
command:
|
command:
|
||||||
- python3
|
- python3
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ spec:
|
|||||||
# not disconnected. (The anti-affinity change is a separate commit precisely because that one DOES
|
# not disconnected. (The anti-affinity change is a separate commit precisely because that one DOES
|
||||||
# cause a switchover — see it for the blast radius.)
|
# cause a switchover — see it for the blast radius.)
|
||||||
instances: 3
|
instances: 3
|
||||||
imageName: ghcr.io/cloudnative-pg/postgresql:16.4
|
imageName: ghcr.io/cloudnative-pg/postgresql:16.15
|
||||||
# HARD anti-affinity — the three instances MUST land on three DIFFERENT nodes (2026-09-07).
|
# HARD anti-affinity — the three instances MUST land on three DIFFERENT nodes (2026-09-07).
|
||||||
#
|
#
|
||||||
# CNPG's default is `preferred`, which is only a scheduler HINT: the scheduler may put two
|
# CNPG's default is `preferred`, which is only a scheduler HINT: the scheduler may put two
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ spec:
|
|||||||
# OIDC bootstrap hooks. (ci: flux-postbuild-substitution enforces it.)
|
# OIDC bootstrap hooks. (ci: flux-postbuild-substitution enforces it.)
|
||||||
extraInitContainers:
|
extraInitContainers:
|
||||||
- name: wait-for-idp
|
- name: wait-for-idp
|
||||||
image: docker.io/library/alpine:3.22
|
image: docker.io/library/alpine:3.24
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
command: ["/bin/sh", "-c"]
|
command: ["/bin/sh", "-c"]
|
||||||
args:
|
args:
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ spec:
|
|||||||
# rather than becoming a fifth per-environment knob. ---
|
# rather than becoming a fifth per-environment knob. ---
|
||||||
initContainers:
|
initContainers:
|
||||||
- name: velero-plugin-for-aws
|
- name: velero-plugin-for-aws
|
||||||
image: velero/velero-plugin-for-aws:v1.14.3-rc.2
|
image: velero/velero-plugin-for-aws:v1.14.4
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- mountPath: /target
|
- mountPath: /target
|
||||||
|
|||||||
@@ -99,6 +99,16 @@ infra/devcontainer/dev.sh run bash -c \
|
|||||||
-o custom-columns=NAME:.metadata.name,BACKUP:.metadata.labels.velero\\.io/backup-name,PHASE:.status.phase,BYTES:.status.progress.bytesDone'
|
-o custom-columns=NAME:.metadata.name,BACKUP:.metadata.labels.velero\\.io/backup-name,PHASE:.status.phase,BYTES:.status.progress.bytesDone'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `velero` CLI is in the toolbox (pinned to the server version) and gives the per-item errors and
|
||||||
|
warnings the CR only counts. Read-only; `velero backup create/delete` and `velero restore` are
|
||||||
|
operator actions:
|
||||||
|
|
||||||
|
```
|
||||||
|
infra/devcontainer/dev.sh run bash -c \
|
||||||
|
'velero --kubecontext k3s-prod -n velero backup describe <name> --details; \
|
||||||
|
velero --kubecontext k3s-prod -n velero backup logs <name> | grep -iE "level=(error|warn)" | tail -25'
|
||||||
|
```
|
||||||
|
|
||||||
The plugin pin (cause 1 depends entirely on this):
|
The plugin pin (cause 1 depends entirely on this):
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ spec:
|
|||||||
# Renovate maintains this pin. nginx-unprivileged (not nginx) so the container can honour
|
# Renovate maintains this pin. nginx-unprivileged (not nginx) so the container can honour
|
||||||
# runAsNonRoot: the stock nginx image's entrypoint needs root to drop privileges and to
|
# runAsNonRoot: the stock nginx image's entrypoint needs root to drop privileges and to
|
||||||
# bind :80. `stable-alpine` branch, pinned exactly.
|
# bind :80. `stable-alpine` branch, pinned exactly.
|
||||||
image: nginxinc/nginx-unprivileged:1.30.4-alpine
|
image: nginxinc/nginx-unprivileged:1.31.7-alpine
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
|
|||||||
Reference in new issue
Block a user