Flux source URL, flux-git rotator verify repo, review-bot REPOS, pipeline API,
clone and push URLs, the heating-can source label and the review-bot and
kuma-sync Go module paths. Merge right after the transfer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drops the daily cron and the upstream-polling decide step. Every version
in ci/images/hermes/Containerfile is now an image reference (FROM for
nousresearch/hermes-agent, COPY --from for talosctl, kubectl, flux, bao
and tea), which Renovate's enabled dockerfile manager updates and
digest-pins. A merged Renovate PR pushes to ci/images/hermes/**, the
pipeline builds build.<unix-ts>, and Flux image automation rolls it out.
No checksums to maintain: the binaries come from digest-pinned images.
renovate.json: the hermes Containerfile gets a PR of its own instead of
the "CI non-major" group (that group is for changes that never reach the
cluster), and nousresearch/hermes-agent is held to v0.x because the
retired date-versioned v2026.x tags sort above it.
Verified: each COPY --from path exists in the pinned image and the
binary runs on the hermes-agent v0.22.2 base. renovate-config-validator
37.440.7 reports only the two pre-existing errors on master
(flux/kubernetes.managerFilePatterns).
nousresearch/hermes-agent ships no talosctl, kubectl, flux, bao, tea or
jq. The kubectl init container dropped its binary in /opt/data/.local/bin,
which the agent's terminal never has on PATH: Hermes snapshots the env
from a bash login shell, and Debian's /etc/profile resets PATH for
non-root users. /usr/local/bin survives that reset.
Upstream's documented route for always-present tools is a derived image
(docs/user-guide/docker, "Adding tools"). ci/images/hermes builds FROM
the upstream release and adds the binaries, versions matched to the
cluster (Talos v1.13.10, Kubernetes v1.36.3, Flux v2.9.5, OpenBao 2.7.1)
and checksummed against upstream release checksums.
.woodpecker/hermes-image.yml rebuilds daily when upstream publishes a
new release (compared against the uno.renner.hermes.version label on
:latest), and on any push to the Containerfile. Tags are
build.<unix-ts>, the scheme Flux image automation already tracks for
wiki and shelly-manager.
Owner step after merge: register the cron
woodpecker repo cron add --repo k.renner/homelab \
--name hermes-image --branch master --schedule '@daily'
The Deployment switch follows in a separate PR once the first build
exists, so the Recreate rollout never points at a missing tag.
Kuma 2.5.6 applies "default enabled" notifications only in the web UI
(src/pages/EditMonitor.vue:4128-4132); the server's add handler stores
exactly the notificationIDList the client sends (server/server.js:748,791).
Monitors created by kuma-sync therefore had no notifications.
The notification set is now a managed field: exactly the IDs of all
notifications with isDefault set, sorted, applied on create and update
(group monitors included). Monitor.NotificationIDs is part of
Normalized(), so a changed default set or a hand-made change triggers an
update on the next resync and an unchanged one does not.
Client.DefaultNotifications reads the library's cached notificationList
(go-uptime-kuma-client v0.6.2 notification.go:17). Kuma has no command to
request the list; it pushes it at login and to every session of the user
after each change (server/client.js:31, server/server.js:1573,1594,1836).
A failed read is KumaUnavailable with no create or update.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a Kuma database loss or restore, IDs restart and a resource's
status.monitorID can point at a monitor that now carries another
resource's ownership tag. That proves our monitor is gone, so the
controller creates a new one instead of parking at NotOwned forever.
Only an untagged monitor at the previous ID still blocks the create
(tag removed by hand), as before.
Residual Important finding from the kuma-sync final review.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The uptime-kuma play reads its credentials from OpenBao via community.hashi_vault, which needs the python hvac library.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Removes the Paperclip deployment (namespace paperclip, CNPG cluster
pclp-pg, ingresses, image automation, server image build) and the
infrastructure that existed only for its agents:
- agent sandbox: paperclip-renner namespace and CNPs, the
kubernetes-sigs/agent-sandbox controller with its namespace, CNP and
Renovate rules
- agent-runtime-{go,ops} images and the build-agent-runtime pipeline
- matrix bridge (Element <-> Paperclip agents) and synapse's appservice
registration mount
- monitoring loki-read-gateway (only called by the paperclip Role)
- MQTT account agents-read and the controller's RBAC in ns paperclip
- the OpenBao secret-exists-check policy file
- agent admits in the wiki, lidarr, shelly-manager, mosquitto and
hubble-relay CNPs; the paperclip-api Gitea webhook host; the wiki API
key; the ClusterSecretStore namespace entry; the pclp-pg RECOVERY row
- the Paperclip design spec and plan
Authentik group "Paperclip Users" is switched to state: absent so the
blueprint deletes it. package-retention's EXCLUDE_PACKAGES becomes
optional, since its only value was the agent-runtime prefix.
The shelly-manager api-proxy sidecar stays: mqtt-account-controller
calls shelly-manager-api too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A drifted proxy provider was PATCHed with only the drifted fields.
ProxyProviderSerializer.validate (providers/proxy/api.py, 2026.8.3)
checks internal_host against mode from the request attrs alone,
defaulting mode to proxy, so a flow-only PATCH fails with 400
"Internal host cannot be empty when forward auth is disabled." Seen
live after #1285: 8 gates could not move to the new flows and stayed on
their old ones.
The fake Authentik in the tests now applies the same validation, and a
test covers a flow change on an existing gate.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stage 2 of unifying the auth flows, after the llama and paperless trial
of stage 1 was confirmed:
- Brand authentik.srv.renner.uno: flow_authentication =
sso-authentication (was NULL, i.e. the slug-first
default-authentication-flow with password + MFA). Every provider
without its own authentication_flow logs in through it, PVE and PBS
included.
- The 7 blueprint OIDC providers (Home Assistant, NetBird,
Audiobookshelf, Immich, Gitea, Grafana, Matrix MAS): authentication
flow unset (brand), authorization implicit consent.
- authentik-gate-sync: default authorization flow implicit consent, via
DEFAULT_AUTHORIZATION_FLOW on the Deployment and in the code; the
per-gate login/authorization annotations are dropped. Paperless keeps
paperless-authorization.
Every flow referenced here already exists, so the gate controller does
not hit the missing-flow removal seen in stage 1. Stage 3 removes the
passkey-only flows once nothing references them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
All wiki interaction now goes through the wiki service API; CI agents that
distill and consolidate by writing the notes repo directly are retired.
Removes .woodpecker/wiki-maintenance.yml and ci/agents/wiki-maintenance/.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Like any ingress controller, authentik-gate-sync now owns the load-balancer
status of the Ingresses of its class: each pass it copies the address Traefik
reports for the generated route onto the source Ingress, and clears it while
the gate has no route. Previously the migrated Ingresses kept the address
Traefik wrote before their class changed, which Traefik never clears.
Status publishing is non-fatal (logged), so it never holds or removes a gate.
RBAC gains ingresses/status; the change is additive, so the order in which
Flux applies RBAC and the new image does not matter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A named targetPort was resolved to a number through the Service's
EndpointSlices, so with no ready endpoints (app restarting, crashlooping)
the gate counted as invalid: its route was removed and its Authentik
objects torn down, to be rebuilt once the pods came back. Cilium accepts
named ports in toPorts and resolves them against the selected pods'
container ports, so the name now goes into both CNPs as-is and the gate no
longer depends on the backend being up. The EndpointSlice read (and its
RBAC) is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
build-authentik-gate-sync depended on a separate test step, so it only
started after the other steps had run, and Woodpecker skips not-yet-started
steps once the workflow has a failure: on the #1272 merge build-alpine-jq and
build-cni-plugins failed and the image was never built.
The unit tests now run as a stage of the image build (the final stage copies
the controller out of it), and the step starts with depends_on: [] like the
others.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Proxy providers are filtered by name__iexact (plain name= is ignored and
returned the first provider alphabetically).
- Applications are retrieved by slug, never listed: the list endpoint is
policy-filtered for the token's user and cached per user regardless of
filters. Cleanup finds owned apps through the proxy provider list and
confirms meta_publisher + provider one by one.
- Group gates bind an expression policy (ak_is_group_member by name, the name
as a repr() literal) instead of the group: deleting a group cascades its
bindings away and would open the app, the policy keeps denying. New bindings
are created before stale ones are removed.
- Per-gate errors no longer abort the pass; a gate whose Ingress is invalid
loses its route immediately. Network errors become ApiError.
- Gate names carry a hash of namespace/name: unambiguous and bounded to fit
label values and ak-outpost-<name>.
- Teardown removes only an outpost serving exactly the gate's provider; a
provider orphaned by an interrupted teardown is adopted on recreation.
- Role gains the expression-policy permissions; tests cover the Authentik
reconcile against an in-memory API.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds authentik-gate-sync, an in-house controller (ci/images/authentik-gate-sync,
stdlib Python like netbird-service-sync) that implements an `authentik-gate`
IngressClass. An Ingress of that class names its real backend; the controller
turns it into an Authentik proxy gate and wires the upstream ingress provider:
- Authentik: proxy provider, application (meta_publisher marks ownership),
outpost on the local Kubernetes connection, optional group binding.
- App namespace: ExternalName Service <ingress>-gate -> outpost, Traefik
Ingress <ingress>-gate (source hosts/paths/TLS/labels/annotations, backend
swapped, cert-manager annotations dropped so only the source issues), and a
CNP admitting only this outpost to the backend pods.
- authentik namespace: CNP letting the outpost reach only those pods.
Annotations: auth.renner.uno/group (members managed in Authentik; none = any
user), /login-flow, /authorization-flow (default passkey-stepup-authorization),
/skip-path. Fail closed: the Traefik route is applied last and removed when the
gate's config is invalid (missing group/flow/backend); transient API errors
leave the gate as the last good pass left it.
The API token is zero-touch: an ESO Password generator (CreatedOnce) feeds
both the controller and AUTHENTIK_GATE_SYNC_TOKEN on server/worker, which the
new gate-sync blueprint sets as the key of a non-expiring token for a
service account whose role covers only gate objects.
No existing gate moves yet; llama is migrated in a follow-up once this runs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
run.sh asks a Mistral agent (mistral-large-4-0) through the Conversations API. The agent reads the repo and the PR through the read-only Gitea connector, so the review step no longer holds a Claude token or a local checkout. The Claude CLI invocation, the model picker and the context clones stay in run.sh as comments. The answer is cut at the first heading and empty or None-only sections are dropped before the review event is derived. A tool call that is not read-only fails the run and nothing is posted. A failed request now fails the pipeline.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
dns.renner.uno/public publishes a host on the NetBird proxy AND drops its
UDM record (external-dns-unifi labelFilter). For gitea that sends LAN SSH
to the proxy host's own sshd and node image pulls over the WAN, since the
hairpin covers only 80/443 and not the node VLAN.
netbird-service-sync now also selects netbird.renner.uno/public, which
external-dns-unifi does not filter on, so the host is published on the
proxy while the LAN record on the Traefik VIP stays. The gitea Ingress
switches to the new label; it takes effect once the master build of this
image is pinned in the deployment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Alpine with bash, jq, kubectl, openssh and the hcloud CLI (v1.69.0,
copied from the official image). A verify user (uid 65532) exists
because the OpenSSH client refuses to run as a UID without a passwd
entry. Built by build-ci-images.yml with a dated tag; Flux image
automation tracks the newest date.
First step of docs/superpowers/plans/2026-10-10-backup-verify-cloud-vm.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The event is derived by the pipeline from the report's severity sections:
Blocking -> REQUEST_CHANGES; none of Blocking/Suggestions/Unverified on a
non-empty diff -> APPROVE; otherwise COMMENT.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Drop the complexity triage (it downgraded most PRs to a small model) and pick
the model via scripts/ci/pick-model.sh from the live /v1/models catalog,
steered to the strongest and newest. Falls back to the CLI default.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Move the review logic out of .woodpecker/pr-review.yml into
ci/agents/pr-review/run.sh; the per-repo file becomes a logic-free stub that
clones homelab@master and runs it, so the four copies cannot drift.
Prompt: unverified suspicions must be checked (checkout + docs) and are never
blocking. Review agent gets WebFetch/WebSearch.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Sandbox pods got 403 Route not allowed on /api/agents/me/secrets*.
Adds the five /api/agents/me secret routes to the callback bridge
allowlist; company-wide secret routes stay denied.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stock agent-runtime-claude base has no /usr/share/zoneinfo:
TZ=Europe/Berlin date prints UTC and Python ZoneInfo raises
ZoneInfoNotFoundError (REN-1195). k8s-base and k8s-go run these images.
Install tzdata and fail the build if Europe/Berlin does not resolve.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The kubernetes sandbox plugin's release and destroy hooks returned void,
so no lease got a termination receipt and every cancelled pod run left
its card waiting on remote_cleanup. Return state destroyed once the
Sandbox CR / Job (and pod) reads 404, within 20 s; otherwise no receipt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refresh the plugins pin alongside multus v4.3.1 (#1131), as the
cni-plugins-installer.yaml comment asks. SHA256 cross-checked against
upstream's cni-plugins-linux-amd64-v1.9.1.tgz.sha256 asset. The tarball
still ships macvlan, static, host-local and dhcp.
Image only: CI builds cni-plugins:<date>. The DaemonSet tag bump follows
in a separate PR once the image exists.
Refs: REN-722
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stable session fingerprint for the per-run managed AI home paths, plus a
persistent CLAUDE_CONFIG_DIR/projects so the resumed session finds its
transcript. Local runs only. Fix-forward of #1111 (reverted in #1113).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>