diff --git a/.woodpecker/build-ci-images.yml b/.woodpecker/build-ci-images.yml index ed2cde89..3ac27167 100644 --- a/.woodpecker/build-ci-images.yml +++ b/.woodpecker/build-ci-images.yml @@ -130,3 +130,43 @@ 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. + # + # 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" 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