Compare commits

..
Author SHA1 Message Date
claude-botandClaude Opus 5 fe6de18048 fix(mosquitto): propagate the alpine-jq base tag instead of hardcoding it
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/push/build-ci-images Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
Review point on #449, and a real flaw rather than a nit.

The Containerfile pinned `FROM alpine-jq:2026-07-30`. The pipeline step
already retriggers on changes to ci/images/alpine-jq/**, so a base rebuild
— a CVE patch, say — WOULD rebuild this image, but against the old pinned
tag. That is worse than not rebuilding: the step goes green while shipping
a stale base, so the fix looks applied and is not.

Now ARG BASE_TAG, passed by the pipeline as the same date value used for
this image own tags, with depends_on: [build-alpine-jq] guaranteeing the
base for that date is pushed first. The two images therefore always move
together. The `latest` default applies only to a manual docker build
outside CI.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 14:55:23 +02:00
claude-botandClaude Opus 5 3f5387979c fix(mosquitto): drop runtime apk add from the 7 bootstrap Jobs
Last of the runtime `apk add` cleanups. Each of the seven credential
bootstrap Jobs ran `apk add --no-cache curl jq` on every execution, making
them depend on package-index availability at run time.

Adds ci/images/alpine-curl-jq, LAYERED `FROM alpine-jq` rather than
repeating that image's build. jq is therefore installed in exactly one
Containerfile, the shared layers are pulled once, and the relationship
between the two images is explicit instead of implied by duplicated
instructions. The pipeline step uses depends_on: [build-alpine-jq] and also
triggers on changes to the base's path, so the base is built before
anything is layered onto it.

Kept curl rather than switching to busybox wget, deliberately. wget has
--header and --post-data but NO --ca-certificate, so reaching the
Kubernetes API with the ServiceAccount CA needs the CA appended to the
system trust bundle first. That does work — verified in-cluster: TLS
validated and the request reached RBAC, returning 403 rather than a
certificate error — but it changes error semantics versus `curl -f` and
busybox sets its own Content-Type for --post-data. These Jobs mint
credentials, and the existence check they branch on decides whether a
Secret gets created at all, so their logic was left untouched: the diff is
one deleted line and one image change per Job.

Rationale recorded once at the top of the file rather than repeated seven
times.

Verified all seven Jobs render with the new image and no apk add:

  mosquitto-admin-credentials-bootstrap      -> alpine-curl-jq:2026-07-30
  mosquitto-credentials-bootstrap            -> alpine-curl-jq:2026-07-30
  mosquitto-health-credentials-bootstrap     -> alpine-curl-jq:2026-07-30
  mosquitto-open3e-credentials-bootstrap     -> alpine-curl-jq:2026-07-30
  mosquitto-shellies-credentials-bootstrap   -> alpine-curl-jq:2026-07-30
  mosquitto-sml2mqtt-credentials-bootstrap   -> alpine-curl-jq:2026-07-30
  mosquitto-zigbee2mqtt-credentials-bootstrap-> alpine-curl-jq:2026-07-30

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 14:55:23 +02:00
k.renner 57d333a2f1 Merge pull request 'fix(paperless): drop runtime apk add from fetch-bridge-cert' (#448) from paperless-drop-apk into master
ci/woodpecker/push/build-ci-images Pipeline was successful
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#448
2026-07-30 12:52:47 +00:00
claude-botandClaude Opus 5 a6effdd4cf fix(paperless): drop runtime apk add from fetch-bridge-cert
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/push/build-ci-images Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
Owner directive: tools belong in the image, not installed at container
start. This init container ran `apk add --no-cache openssl` on EVERY
paperless pod start.

Unlike the multus CNI installer, this one genuinely needs an image: the
openssl CLI is absent from the alpine base (verified — the base carries the
libraries but not the command), and busybox has no `s_client` equivalent,
so the STARTTLS certificate capture cannot be done with what is already
there.

Adds ci/images/alpine-openssl, named for what it is rather than for this
consumer, so anything else needing s_client/x509 from a shell script can
reuse it.

Script logic is unchanged — only the apk add line is removed and the image
swapped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 14:49:49 +02:00
k.renner 9c9ac712ca Merge pull request 'fix(multus): bake CNI plugins into an image, drop the node-side fetch' (#447) from cni-plugins-image into master
ci/woodpecker/push/build-ci-images Pipeline was successful
ci/woodpecker/cron/issue-fix Pipeline was successful
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#447
2026-07-30 12:33:49 +00:00
5 changed files with 149 additions and 16 deletions
+69
View File
@@ -188,3 +188,72 @@ steps:
--dockerfile Containerfile \
--destination gitea.renner.uno/gitea_admin/cni-plugins:$TAG \
--destination gitea.renner.uno/gitea_admin/cni-plugins:latest
# Runtime image, not CI tooling: alpine + the openssl CLI, for shell scripts
# needing s_client/x509. Named for what it is; first consumer is the paperless
# fetch-bridge-cert init container. Replaces an `apk add openssl` that ran on
# every paperless pod start. The openssl CLI is genuinely absent from the alpine
# base, so this one needs an image rather than a busybox substitute.
- name: build-alpine-openssl
image: gcr.io/kaniko-project/executor:v1.23.2-debug
directory: ci/images/alpine-openssl
depends_on: []
when:
- event: manual
- event: push
path: ["ci/images/alpine-openssl/**", ".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/alpine-openssl:$TAG \
--destination gitea.renner.uno/gitea_admin/alpine-openssl:latest
# Runtime image, not CI tooling: alpine-jq PLUS curl, LAYERED on that image
# rather than repeating its build, so jq lives in exactly one Containerfile and
# the shared layers are pulled once. Consumed by the mosquitto bootstrap Jobs,
# which talk to the Kubernetes API and build JSON bodies. Replaces an
# `apk add curl jq` that ran on every Job execution.
#
# depends_on build-alpine-jq (not []) because this FROMs it: the base must be
# built and pushed before it is layered on. Combined with --build-arg
# BASE_TAG=$TAG below, that means this image is ALWAYS layered on the base
# built in the SAME pipeline run, so a base change (e.g. a CVE patch)
# propagates automatically instead of silently rebuilding against a stale
# hardcoded tag.
- name: build-alpine-curl-jq
image: gcr.io/kaniko-project/executor:v1.23.2-debug
directory: ci/images/alpine-curl-jq
depends_on: [build-alpine-jq]
when:
- event: manual
- event: push
path:
- "ci/images/alpine-curl-jq/**"
- "ci/images/alpine-jq/**"
- ".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 \
--build-arg BASE_TAG=$TAG \
--destination gitea.renner.uno/gitea_admin/alpine-curl-jq:$TAG \
--destination gitea.renner.uno/gitea_admin/alpine-curl-jq:latest
+29
View File
@@ -0,0 +1,29 @@
# LAYERED on alpine-jq rather than repeating its build. jq is installed in
# exactly one place (ci/images/alpine-jq/Containerfile); this image is that plus
# curl, and shares every layer below it — so consumers that pull both get the
# common layers once.
#
# BASE_TAG is supplied by the pipeline as the SAME `date +%Y-%m-%d` value used for
# this image's own tags, and build-alpine-curl-jq depends_on build-alpine-jq, so
# the base for today's date is always pushed before this is layered onto it.
#
# Deliberately NOT a hardcoded dated tag. With one, a rebuild of alpine-jq — a CVE
# patch, say — would never reach this image: the pipeline's when.path already
# retriggers this step when the base's directory changes, but it would rebuild
# against the OLD pinned tag. That is worse than not rebuilding at all, because
# the step goes green while shipping a stale base. Passing the tag through makes
# the two images move together.
#
# The `latest` default applies only to a manual `docker build` outside the
# pipeline; CI always passes BASE_TAG explicitly.
ARG BASE_TAG=latest
FROM gitea.renner.uno/gitea_admin/alpine-jq:${BASE_TAG}
# curl, not busybox wget, on purpose. busybox wget has --header and --post-data
# but NO --ca-certificate, so talking to the Kubernetes API with the
# ServiceAccount CA needs the CA appended to the system trust bundle first.
# That works (verified: TLS validated, request reached RBAC and returned 403),
# but it changes error semantics versus `curl -f` and busybox sets its own
# Content-Type for --post-data. The consumers here mint Secrets, so keeping curl
# means their scripts are untouched apart from deleting the apk add line.
RUN apk add --no-cache curl
+21
View File
@@ -0,0 +1,21 @@
FROM alpine:3.21@sha256:48b0309ca019d89d40f670aa1bc06e426dc0931948452e8491e3d65087abc07d
# Alpine plus the openssl CLI. Nothing application-specific — named for what it
# is, so anything needing `openssl s_client` / `x509` from a shell script can use
# it.
#
# It exists because installing tools at container start is not acceptable here:
# `apk add` makes every start depend on network and package-index availability,
# and the resolved version can drift between restarts of an otherwise pinned
# image.
#
# Note the openssl CLI is genuinely absent from the alpine base — verified, the
# base carries the libraries but not the command — so unlike the multus CNI
# installer (where busybox wget/tar already covered the need) this one really
# does require an image rather than just dropping the apk add.
#
# First consumer: the paperless fetch-bridge-cert init container, which captures
# the Proton Mail Bridge's self-signed IMAP certificate via STARTTLS and pins it
# for paperless to trust. busybox has no s_client equivalent, so openssl is not
# substitutable there.
RUN apk add --no-cache openssl
@@ -1,4 +1,21 @@
---
# ALL SEVEN bootstrap Jobs in this file run
# gitea.renner.uno/gitea_admin/alpine-curl-jq (built by
# .woodpecker/build-ci-images.yml from ci/images/alpine-curl-jq/, itself layered
# FROM alpine-jq so jq is installed in exactly one place). They previously used
# alpine:3.21 and ran `apk add --no-cache curl jq` on every execution, which made
# each Job depend on package-index availability at run time. Tools belong in the
# image; the scripts below are otherwise unchanged.
#
# curl rather than busybox wget on purpose: busybox wget has --header and
# --post-data but no --ca-certificate, so reaching the Kubernetes API with the
# ServiceAccount CA would need the CA appended to the system trust bundle, and
# its error semantics and Content-Type handling differ from `curl -f`. These Jobs
# mint credentials, so their logic was left alone.
#
# Dated tag, matching the ci-ansible / alpine-jq convention: a change to what
# these Jobs run is a reviewable diff here rather than a silent rebuild.
---
# Generates mosquitto-credentials on first deploy. The password is created
# in-cluster, lives only in the Secret object, and is never written to git
# or seen by a human. The Mosquitto broker hashes it via mosquitto_passwd
@@ -55,13 +72,12 @@ spec:
restartPolicy: OnFailure
containers:
- name: bootstrap
image: alpine:3.21
image: gitea.renner.uno/gitea_admin/alpine-curl-jq:2026-07-30
command:
- sh
- -c
- |
set -eu
apk add --no-cache curl jq >/dev/null
NS=home-assistant
NAME=mosquitto-credentials
@@ -123,13 +139,12 @@ spec:
restartPolicy: OnFailure
containers:
- name: bootstrap
image: alpine:3.21
image: gitea.renner.uno/gitea_admin/alpine-curl-jq:2026-07-30
command:
- sh
- -c
- |
set -eu
apk add --no-cache curl jq >/dev/null
NS=home-assistant
NAME=mosquitto-admin-credentials
@@ -190,13 +205,12 @@ spec:
restartPolicy: OnFailure
containers:
- name: bootstrap
image: alpine:3.21
image: gitea.renner.uno/gitea_admin/alpine-curl-jq:2026-07-30
command:
- sh
- -c
- |
set -eu
apk add --no-cache curl jq >/dev/null
NS=home-assistant
NAME=mosquitto-shellies-credentials
@@ -256,13 +270,12 @@ spec:
restartPolicy: OnFailure
containers:
- name: bootstrap
image: alpine:3.21
image: gitea.renner.uno/gitea_admin/alpine-curl-jq:2026-07-30
command:
- sh
- -c
- |
set -eu
apk add --no-cache curl jq >/dev/null
NS=home-assistant
NAME=mosquitto-sml2mqtt-credentials
@@ -324,13 +337,12 @@ spec:
restartPolicy: OnFailure
containers:
- name: bootstrap
image: alpine:3.21
image: gitea.renner.uno/gitea_admin/alpine-curl-jq:2026-07-30
command:
- sh
- -c
- |
set -eu
apk add --no-cache curl jq >/dev/null
NS=home-assistant
NAME=mosquitto-open3e-credentials
@@ -392,13 +404,12 @@ spec:
restartPolicy: OnFailure
containers:
- name: bootstrap
image: alpine:3.21
image: gitea.renner.uno/gitea_admin/alpine-curl-jq:2026-07-30
command:
- sh
- -c
- |
set -eu
apk add --no-cache curl jq >/dev/null
NS=home-assistant
NAME=mosquitto-health-credentials
@@ -461,13 +472,12 @@ spec:
restartPolicy: OnFailure
containers:
- name: bootstrap
image: alpine:3.21
image: gitea.renner.uno/gitea_admin/alpine-curl-jq:2026-07-30
command:
- sh
- -c
- |
set -eu
apk add --no-cache curl jq >/dev/null
NS=home-assistant
NAME=mosquitto-zigbee2mqtt-credentials
+6 -2
View File
@@ -88,13 +88,17 @@ spec:
# trust without disabling verification.
initContainers:
- name: fetch-bridge-cert
image: alpine:3.21
# alpine + the openssl CLI, built by .woodpecker/build-ci-images.yml
# from ci/images/alpine-openssl/. Replaces `apk add --no-cache openssl`
# on every paperless pod start: tools belong in the image, not
# installed at runtime. Dated tag so a change is a reviewable diff
# here, matching the ci-ansible / alpine-jq convention.
image: gitea.renner.uno/gitea_admin/alpine-openssl:2026-07-30
command:
- sh
- -c
- |
set -e
apk add --no-cache openssl
# Wait until the bridge presents a cert via STARTTLS.
# Re-fetched on every paperless restart so a bridge cert
# rotation only requires a paperless rollout to pick up.