From 0d7fecad8df083b462ba081df63b486495db077a Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Thu, 30 Jul 2026 08:51:38 +0200 Subject: [PATCH 1/2] build(ci): add z2m-seed runtime image (alpine + jq) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step 1 of 2 toward removing `apk add` from the zigbee2mqtt seed-data init container, which currently installs jq on every pod start. Owner directive: baking tools into a layered image is fine, adding them at runtime is not. This commit only adds the image and its build step; nothing consumes it yet, so it is inert on merge. The Deployment cannot be switched in the same commit because runtime images in this repo are pinned by DIGEST (as the secret-rotator CronJobs are), and the digest does not exist until this has built and pushed. The build therefore writes it out via kaniko's --image-name-with-digest-file and echoes it as "PIN THIS:", so the follow-up commit can copy the reference straight from the build log without a separate registry query. Why a real JSON parser rather than busybox sed/awk: "key" occurs 12 times in the actual backup — once per device with an APS link_key — and the FIRST occurrence is a device key, because "devices" sorts before "network_key". An extraction that is not correctly anchored yields a device link key, herdsman then forms a NEW network, and every device drops off. This was reproduced while prototyping the awk approach, which is what makes jq worth an image here rather than clever one-liners in the manifest. Follows the existing conventions: Containerfile alongside the other ci/images entries, base pinned by digest to the same alpine:3.21 the ci-k8s image uses, kaniko build gated on its own path so unrelated pushes do not rebuild it, and date + latest tags pushed as the other images do. Co-Authored-By: Claude Opus 5 (1M context) --- .woodpecker/build-ci-images.yml | 34 ++++++++++++++++++++++++++++++++ ci/images/z2m-seed/Containerfile | 21 ++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 ci/images/z2m-seed/Containerfile diff --git a/.woodpecker/build-ci-images.yml b/.woodpecker/build-ci-images.yml index ed2cde89..26442059 100644 --- a/.woodpecker/build-ci-images.yml +++ b/.woodpecker/build-ci-images.yml @@ -130,3 +130,37 @@ steps: --dockerfile Containerfile \ --destination gitea.renner.uno/gitea_admin/secret-rotator:$TAG \ --destination gitea.renner.uno/gitea_admin/secret-rotator:latest + + # Runtime image, not CI tooling: alpine + jq, the base for the zigbee2mqtt + # `seed-data` init container. Replaces an `apk add --no-cache jq` that ran on + # every pod start — tools belong in the image, not installed at runtime. + # Consumers pin it by DIGEST (as the secret-rotator CronJobs do), so this must + # be built and pushed before the Deployment can reference it. + - name: build-z2m-seed + image: gcr.io/kaniko-project/executor:v1.23.2-debug + directory: ci/images/z2m-seed + depends_on: [] + when: + - event: manual + - event: push + path: ["ci/images/z2m-seed/**", ".woodpecker/build-ci-images.yml"] + environment: + GITEA_USER: gitea_admin + GITEA_TOKEN: + from_secret: ci_images_token + commands: + - mkdir -p /kaniko/.docker + - AUTH=$(printf '%s:%s' "$GITEA_USER" "$GITEA_TOKEN" | base64 | tr -d '\n') + - printf '{"auths":{"gitea.renner.uno":{"auth":"%s"}}}' "$AUTH" > /kaniko/.docker/config.json + - TAG=$(date +%Y-%m-%d) + - | + /kaniko/executor \ + --context . \ + --dockerfile Containerfile \ + --destination gitea.renner.uno/gitea_admin/z2m-seed:$TAG \ + --destination gitea.renner.uno/gitea_admin/z2m-seed:latest \ + --image-name-with-digest-file /kaniko/z2m-seed.digest + # Print the digest-pinned reference so the follow-up commit that pins the + # Deployment can copy it straight from this build log, with no separate + # registry query. + - echo "PIN THIS:" && cat /kaniko/z2m-seed.digest diff --git a/ci/images/z2m-seed/Containerfile b/ci/images/z2m-seed/Containerfile new file mode 100644 index 00000000..4942af00 --- /dev/null +++ b/ci/images/z2m-seed/Containerfile @@ -0,0 +1,21 @@ +FROM alpine:3.21@sha256:48b0309ca019d89d40f670aa1bc06e426dc0931948452e8491e3d65087abc07d + +# Runtime image, not CI tooling: the base for the zigbee2mqtt `seed-data` init +# container. +# +# It exists for exactly one reason — jq. That init container reads +# `.network_key.key` out of the Open Coordinator Backup JSON to render +# /app/data/secret.yaml; everything else it does (sh, sed, cp, cat) is already +# in busybox. jq is baked in here rather than `apk add`ed when the container +# starts, so the init container has no network dependency at start and no +# package-index drift between pod restarts. +# +# Parsing this with busybox sed/awk instead was considered and rejected: "key" +# occurs 12 times in the real backup (once per device with an APS link_key) and +# the FIRST occurrence is a device key, because "devices" sorts before +# "network_key". Hand-rolled extraction that isn't correctly anchored yields a +# device link key, and herdsman then forms a NEW network and drops every +# device — so a real JSON parser earns its keep here. +# +# Stays root: the init container writes to the root-owned zigbee2mqtt-data PVC. +RUN apk add --no-cache jq -- 2.54.0 From 66dfd56b4dcf0606de5f3f4c26ab95e3ff5882a2 Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Thu, 30 Jul 2026 09:09:21 +0200 Subject: [PATCH 2/2] build(ci): emit a single digest line for z2m-seed Review point on #442: kaniko writes one line per --destination to --image-name-with-digest-file, so with both :$TAG and :latest the file holds two entries carrying the same digest, and "PIN THIS:" would print two near-identical lines for whoever does step 2 to choose between. Rather than just comment on the ambiguity, remove it: reduce to the bare digest, dedupe, and print one unambiguous copy-pasteable reference. The multi-line behaviour is documented inline so a future edit does not reintroduce it. Verified the reduction against a two-line fixture matching kaniko's format: gitea.renner.uno/gitea_admin/z2m-seed:2026-07-30@sha256:abc123 gitea.renner.uno/gitea_admin/z2m-seed:latest@sha256:abc123 -> PIN THIS: gitea.renner.uno/gitea_admin/z2m-seed@sha256:abc123 Co-Authored-By: Claude Opus 5 (1M context) --- .woodpecker/build-ci-images.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.woodpecker/build-ci-images.yml b/.woodpecker/build-ci-images.yml index 26442059..3ac27167 100644 --- a/.woodpecker/build-ci-images.yml +++ b/.woodpecker/build-ci-images.yml @@ -163,4 +163,10 @@ steps: # Print the digest-pinned reference so the follow-up commit that pins the # Deployment can copy it straight from this build log, with no separate # registry query. - - echo "PIN THIS:" && cat /kaniko/z2m-seed.digest + # + # kaniko writes ONE LINE PER --destination to that file, so it holds two + # entries (:$TAG and :latest) carrying the same digest. Reduce to the bare + # digest and dedupe, so exactly one unambiguous line is emitted rather than + # two near-identical ones for whoever does step 2 to choose between. + - DIGEST=$(cut -d@ -f2 /kaniko/z2m-seed.digest | sort -u | head -1) + - echo "PIN THIS: gitea.renner.uno/gitea_admin/z2m-seed@$DIGEST" -- 2.54.0