Compare commits

...
Author SHA1 Message Date
jakman85andClaude Opus 5.5 60e3a24652 feat(devcontainer): bake velero CLI into the toolbox, pinned to prod server v1.18.2
gate / gate (pull_request) Successful in 3m1s
gate / conformance (pull_request) Skipped
images / images (pull_request) Successful in 44s
The velero CLI and server are version-coupled, so VELERO_VERSION follows the
server running on prod (docker.io/velero/velero:v1.18.2, chart 12.2.3,
verified 2026-11-07). It has no renovate marker on purpose: bump it together
with the chart's appVersion. The verify stage asserts that
`velero version --client-only` reports the pin. The build arg reaches CI
through build-image.sh --print-build-args, so no .gitea/ or ci/ change is
needed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 23:18:21 -06:00
jakman85 2d32b9bd7d Merge pull request 'chore(deps): Update actions/upload-artifact action to v7' (#113) from renovate/major-github-artifact-actions into main
gate / gate (push) Successful in 3m14s
gate / conformance (push) Skipped
Reviewed-on: #113
Reviewed-by: Keith Jackson <[email protected]>
2026-11-08 04:52:04 +00:00
jakman85 9f82b2accf Merge pull request 'chore(deps): Update actions/checkout action to v7' (#112) from renovate/actions-checkout-7.x into main
gate / gate (push) Canceled after 3s
gate / conformance (push) Canceled after 0s
images / images (push) Successful in 4s
Reviewed-on: #112
Reviewed-by: Keith Jackson <[email protected]>
2026-11-08 04:48:40 +00:00
jakman85 990b309cbb Merge pull request 'chore(deps): Update docker Docker tag to v29' (#114) from renovate/docker-29.x into main
gate / gate (push) Canceled after 9m30s
gate / conformance (push) Canceled after 0s
Reviewed-on: #114
2026-11-08 04:39:04 +00:00
jakman85 cedcb01975 Merge pull request 'chore(deps): Update ghcr.io/cloudnative-pg/postgresql Docker tag to v16.15' (#124) from renovate/ghcr.io-cloudnative-pg-postgresql-16.x into main
gate / gate (push) Successful in 3m18s
gate / conformance (push) Skipped
Reviewed-on: #124
2026-11-08 04:28:19 +00:00
jakman85 2b279d4f21 Merge pull request 'chore(deps): Update Helm release headlamp to <0.47.0' (#130) from renovate/headlamp-0.x into main
gate / gate (push) Canceled after 15s
gate / conformance (push) Canceled after 0s
Reviewed-on: #130
2026-11-08 04:28:01 +00:00
jakman85 12c28adf4d Merge pull request 'feat(conformance): L0 guard — external-dns annotationPrefix must match the tree (stacked on #140)' (#141) from feat/l0-externaldns-annotation-prefix into main
gate / gate (push) Successful in 2m55s
gate / conformance (push) Skipped
Reviewed-on: #141
2026-11-08 00:08:29 +00:00
jakman85 b0230ba736 Merge pull request 'fix(dns): pin external-dns annotationPrefix — v0.23 ignored every alpha annotation' (#140) from fix/public-dns-wan-a-record into main
gate / gate (push) Successful in 2m53s
gate / conformance (push) Skipped
Reviewed-on: #140
2026-11-08 00:01:06 +00:00
jakman85andClaude Opus 5.5 9c51f60a35 feat(conformance): L0 guard that the external-dns annotationPrefix matches the tree
gate / gate (pull_request) Successful in 2m58s
gate / conformance (pull_request) Skipped
Add assertion (8) to internal-services-publish-no-public-dns. The external-dns
HelmRelease must declare annotationPrefix explicitly, and every
external-dns annotation in apps/, platform/, runbooks/ and catalog/render.py
must use that prefix. The v0.23.0 default-prefix change on 2026-11-07
ignored the controller: none, target and hostname annotations while
assertions (1)-(3) stayed green.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 17:56:12 -06:00
jakman85andClaude Opus 5.5 267446c68f fix(dns): pin external-dns annotationPrefix to the alpha prefix
gate / gate (pull_request) Successful in 2m58s
gate / conformance (pull_request) Skipped
external-dns v0.22 changed its default annotation prefix to
external-dns.kubernetes.io/ with no fallback. Renovate #129 moved the chart
to 1.23.0 (app v0.23.0), and at 12:13Z it ignored every alpha-prefixed
annotation in this repo: the Gateway target (public names went to A
192.168.2.20), controller: none (jackett, pihole and semaphore got
published), and the L4 Service hostnames (unifi and crafty-mc were deleted).

Set annotationPrefix: external-dns.alpha.kubernetes.io/ to restore the v0.21
reading. Floor the chart at 1.23.0, because the flag needs app >= 0.22.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 17:50:13 -06:00
jakman85 d1994b16e8 Merge pull request 'chore(deps): Update Helm release external-dns to <1.24.0' (#129) from renovate/external-dns-1.x into main
gate / gate (push) Successful in 2m57s
gate / conformance (push) Skipped
Reviewed-on: #129
2026-11-07 12:12:00 +00:00
jakman85 d7317606e7 Merge pull request 'feat: PM-Agent also need to look at PR comments' (#117) from feat/pm-agent-pr-mentions into main
gate / gate (push) Successful in 2m55s
gate / conformance (push) Skipped
images / images (push) Successful in 1m25s
Reviewed-on: #117
2026-11-07 12:07:34 +00:00
jakman85 970d65d193 Merge pull request 'chore(deps): Update python Docker tag to v3.15.2' (#135) from renovate/python-3.x into main
gate / gate (push) Canceled after 1m25s
gate / conformance (push) Canceled after 0s
Reviewed-on: #135
2026-11-07 12:06:07 +00:00
jakman85 6719082e5c Merge pull request 'chore(deps): Update gitea/act_runner Docker tag to v0.6.1' (#127) from renovate/gitea-act_runner-0.x into main
gate / gate (push) Canceled after 1m13s
gate / conformance (push) Canceled after 0s
Reviewed-on: #127
2026-11-07 12:04:54 +00:00
jakman85 428b77f08c Merge pull request 'chore(deps): Update gitea/gitea Docker tag to v1.27.3' (#128) from renovate/gitea-gitea-1.x into main
gate / gate (push) Successful in 2m59s
gate / conformance (push) Skipped
Reviewed-on: #128
2026-11-07 11:44:36 +00:00
renovate-bot 2ee7d5036a Merge pull request 'chore(deps): Update registry.k8s.io/sig-storage/snapshot-controller Docker tag to v8.7.0' (#138) from renovate/registry.k8s.io-sig-storage-snapshot-controller-8.x into main
gate / gate (push) Successful in 2m56s
gate / conformance (push) Has been skipped
2026-11-07 09:22:38 +00:00
renovate-bot 5736f42e22 Merge pull request 'chore(deps): Update registry.gitlab.com/crafty-controller/crafty-4 Docker tag to v4.11.0' (#137) from renovate/registry.gitlab.com-crafty-controller-crafty-4-4.x into main
gate / gate (push) Has been cancelled
gate / conformance (push) Has been cancelled
2026-11-07 09:19:37 +00:00
renovate-bot 773254e3a1 Merge pull request 'chore(deps): Update nginxinc/nginx-unprivileged Docker tag to v1.31.7' (#134) from renovate/nginxinc-nginx-unprivileged-1.x into main
gate / gate (push) Has been cancelled
gate / conformance (push) Has been cancelled
2026-11-07 09:10:44 +00:00
renovate-bot 264cd7a46d Merge pull request 'chore(deps): Update lscr.io/linuxserver/radarr Docker tag to v5.28.0' (#133) from renovate/lscr.io-linuxserver-radarr-5.x into main
gate / gate (push) Has been cancelled
gate / conformance (push) Has been cancelled
2026-11-07 09:07:45 +00:00
renovate-bot 979cc4b21d Merge pull request 'chore(deps): Update lscr.io/linuxserver/qbittorrent Docker tag to v5.3.0' (#132) from renovate/lscr.io-linuxserver-qbittorrent-5.x into main
gate / gate (push) Has been cancelled
gate / conformance (push) Has been cancelled
2026-11-07 09:04:44 +00:00
renovate-bot be6742c22b Merge pull request 'chore(deps): Update ghcr.io/seerr-team/seerr Docker tag to v3.5.1' (#126) from renovate/ghcr.io-seerr-team-seerr-3.x into main
gate / gate (push) Has been cancelled
gate / conformance (push) Has been cancelled
2026-11-07 08:46:56 +00:00
renovate-bot c8b915d8f2 Merge pull request 'chore(deps): Update ghcr.io/gethomepage/homepage Docker tag to v2.7.0' (#125) from renovate/ghcr.io-gethomepage-homepage-2.x into main
gate / gate (push) Has been cancelled
gate / conformance (push) Has been cancelled
2026-11-07 08:43:58 +00:00
renovate-bot 6cd3bd29a9 Merge pull request 'chore(deps): Update favonia/cloudflare-ddns Docker tag to v1.18.0' (#123) from renovate/favonia-cloudflare-ddns-1.x into main
gate / gate (push) Has been cancelled
gate / conformance (push) Has been cancelled
2026-11-07 08:37:58 +00:00
renovate-bot 2040d35222 Merge pull request 'chore(deps): Update docker.io/library/alpine Docker tag to v3.24' (#122) from renovate/docker.io-library-alpine-3.x into main
gate / gate (push) Has been cancelled
gate / conformance (push) Has been cancelled
2026-11-07 08:35:00 +00:00
renovate-bot 47fc27ff08 Merge pull request 'chore(deps): Update velero/velero-plugin-for-aws Docker tag to v1.14.4' (#120) from renovate/velero-velero-plugin-for-aws-1.x into main
gate / gate (push) Has been cancelled
gate / conformance (push) Has been cancelled
2026-11-07 08:29:02 +00:00
renovate-bot 5e8b080298 Merge pull request 'chore(deps): Update semaphoreui/semaphore Docker tag to v2.19.17' (#119) from renovate/semaphoreui-semaphore-2.x into main
gate / gate (push) Has been cancelled
gate / conformance (push) Has been cancelled
2026-11-07 08:26:05 +00:00
renovate-bot 24a0e1e4c5 Merge pull request 'chore(deps): Update lscr.io/linuxserver/jackett Docker tag to v0.24.3025-ls29' (#118) from renovate/lscr.io-linuxserver-jackett-0.x into main
gate / gate (push) Has been cancelled
gate / conformance (push) Has been cancelled
2026-11-07 08:23:06 +00:00
renovate-bot 3aec6886d8 chore(deps): Update docker Docker tag to v29
gate / gate (pull_request) Successful in 2m58s
gate / conformance (pull_request) Has been skipped
2026-11-07 08:19:08 +00:00
renovate-bot 1b2ec82a9d chore(deps): Update actions/upload-artifact action to v7
gate / gate (pull_request) Successful in 3m24s
gate / conformance (pull_request) Skipped
2026-11-07 08:19:06 +00:00
renovate-bot 3724d3068b chore(deps): Update actions/checkout action to v7
images / images (pull_request) Successful in 48s
gate / gate (pull_request) Successful in 3m38s
gate / conformance (pull_request) Skipped
2026-11-07 08:19:03 +00:00
renovate-bot c6ad6b559c chore(deps): Update registry.k8s.io/sig-storage/snapshot-controller Docker tag to v8.7.0
gate / gate (pull_request) Successful in 2m59s
gate / conformance (pull_request) Has been skipped
2026-11-07 08:19:01 +00:00
renovate-bot 6efc49d415 chore(deps): Update registry.gitlab.com/crafty-controller/crafty-4 Docker tag to v4.11.0
gate / gate (pull_request) Successful in 2m59s
gate / conformance (pull_request) Has been skipped
2026-11-07 08:18:58 +00:00
renovate-bot a6dc2d1adb chore(deps): Update python Docker tag to v3.15.2
gate / gate (pull_request) Successful in 2m56s
gate / conformance (pull_request) Has been skipped
2026-11-07 08:18:46 +00:00
renovate-bot 75a472cf67 chore(deps): Update nginxinc/nginx-unprivileged Docker tag to v1.31.7
gate / gate (pull_request) Successful in 2m57s
gate / conformance (pull_request) Has been skipped
2026-11-07 08:18:43 +00:00
renovate-bot 24b02f37df chore(deps): Update lscr.io/linuxserver/radarr Docker tag to v5.28.0
gate / gate (pull_request) Successful in 3m0s
gate / conformance (pull_request) Has been skipped
2026-11-07 08:18:02 +00:00
renovate-bot be3ce4645b chore(deps): Update lscr.io/linuxserver/qbittorrent Docker tag to v5.3.0
gate / gate (pull_request) Successful in 3m1s
gate / conformance (pull_request) Has been skipped
2026-11-07 08:17:42 +00:00
renovate-bot b7a77a7d1e chore(deps): Update Helm release headlamp to <0.47.0
gate / gate (pull_request) Successful in 2m58s
gate / conformance (pull_request) Has been skipped
2026-11-07 08:17:34 +00:00
renovate-bot 1a94b0535c chore(deps): Update Helm release external-dns to <1.24.0
gate / gate (pull_request) Successful in 2m56s
gate / conformance (pull_request) Has been skipped
2026-11-07 08:17:30 +00:00
renovate-bot 0e7d0204b3 chore(deps): Update gitea/gitea Docker tag to v1.27.3
gate / gate (pull_request) Successful in 3m1s
gate / conformance (pull_request) Has been skipped
2026-11-07 08:17:22 +00:00
renovate-bot b77572f843 chore(deps): Update gitea/act_runner Docker tag to v0.6.1
gate / gate (pull_request) Successful in 2m55s
gate / conformance (pull_request) Has been skipped
2026-11-07 08:17:20 +00:00
renovate-bot 76b5d038fb chore(deps): Update ghcr.io/seerr-team/seerr Docker tag to v3.5.1
gate / gate (pull_request) Successful in 2m57s
gate / conformance (pull_request) Has been skipped
2026-11-07 08:17:16 +00:00
renovate-bot 306f6f4fb0 chore(deps): Update ghcr.io/gethomepage/homepage Docker tag to v2.7.0
gate / gate (pull_request) Successful in 3m0s
gate / conformance (pull_request) Has been skipped
2026-11-07 08:17:12 +00:00
renovate-bot 3ceaad15e0 chore(deps): Update ghcr.io/cloudnative-pg/postgresql Docker tag to v16.15
gate / gate (pull_request) Successful in 2m58s
gate / conformance (pull_request) Has been skipped
2026-11-07 08:17:11 +00:00
renovate-bot ae772e050d chore(deps): Update favonia/cloudflare-ddns Docker tag to v1.18.0
gate / gate (pull_request) Successful in 2m57s
gate / conformance (pull_request) Has been skipped
2026-11-07 08:17:08 +00:00
renovate-bot 517dfc9030 chore(deps): Update docker.io/library/alpine Docker tag to v3.24
gate / gate (pull_request) Successful in 3m0s
gate / conformance (pull_request) Has been skipped
2026-11-07 08:17:05 +00:00
renovate-bot 16de787557 chore(deps): Update velero/velero-plugin-for-aws Docker tag to v1.14.4
gate / gate (pull_request) Successful in 2m57s
gate / conformance (pull_request) Has been skipped
2026-11-07 08:16:58 +00:00
renovate-bot b7cb70955c chore(deps): Update semaphoreui/semaphore Docker tag to v2.19.17
gate / gate (pull_request) Successful in 2m58s
gate / conformance (pull_request) Has been skipped
2026-11-07 08:16:49 +00:00
renovate-bot 9bcea04b1b chore(deps): Update lscr.io/linuxserver/jackett Docker tag to v0.24.3025-ls29
gate / gate (pull_request) Successful in 2m59s
gate / conformance (pull_request) Has been skipped
2026-11-07 08:15:48 +00:00
40 changed files with 388 additions and 51 deletions

No files matched your search

+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+10 -10
View File
@@ -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 }}
+1 -1
View File
@@ -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:
+1 -1
View File
@@ -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:
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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:
+1 -1
View File
@@ -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:
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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:
+1 -1
View File
@@ -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:
+1 -1
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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:
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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.
+12 -3
View File
@@ -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__)"
+1
View File
@@ -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) |
+1
View File
@@ -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
+15
View File
@@ -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.
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+27 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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:
+1 -1
View File
@@ -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
+10
View File
@@ -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):
``` ```
+1 -1
View File
@@ -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