Commit Graph
167 Commits
Author SHA1 Message Date
renovate-bot 335c423ca6 chore(deps): update node.js to v26
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
2026-11-04 03:05:56 +00:00
k.renner e5d0ffc3e5 Merge pull request 'chore(deps): update hermes image' (#1544) from renovate/hermes-image into main
ci/woodpecker/push/build-ci-images Pipeline was successful
ci/woodpecker/push/hermes-image Pipeline was canceled
Reviewed-on: #1544
2026-11-02 10:52:51 +00:00
renovate-bot 48a28d1c8f chore(deps): update hermes image
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
2026-11-02 03:02:06 +00:00
renovate-bot 50307fe748 chore(deps): update ci non-major
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/review-bot-test Pipeline was successful
2026-11-02 03:01:57 +00:00
hermes-bot 8c85a49ae5 Revert "Merge pull request 'feat(authentik-gate-sync): OIDC clients from OIDCClient resources' (#1535) from feat/gate-sync-oidc into main"
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
This reverts commit 09a03dfd76, reversing
changes made to e518b46045.
2026-11-01 10:44:22 +00:00
hermes-bot 8fa68bc9cd Revert "Merge pull request 'fix(authentik-gate-sync): stop sweeping every gate as an OIDC client' (#1536) from fix/gate-sync-proxy-sweep into main"
This reverts commit 60dfcf5382, reversing
changes made to 1a99c02996.
2026-11-01 10:44:22 +00:00
hermes-bot 5e814c525a fix(authentik-gate-sync): stop sweeping every gate as an OIDC client
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
Outage since 10:11Z: the first pass of image 2026-11-01-0959 (#1535)
removed all 14 gates ("removed OIDC client from Authentik", "14 objects
removed"), and every pass since recreates and removes them again.

ProxyProvider subclasses OAuth2Provider (authentik 2026.8.4
providers/proxy/models.py: class ProxyProvider(OutpostModel,
OAuth2Provider)), so GET providers/oauth2/ also lists every proxy
provider. owned_applications() yielded each gate a second time as an
OAuth2 application; it was not in the OIDC keep set, so the sweep
deleted its application and provider.

Both the sweep and ensure_oidc's provider lookup now take from
providers/oauth2/ only providers whose meta_model_name is
authentik_providers_oauth2.oauth2provider. The test fake now lists proxy
providers under providers/oauth2/ like the real API; the new tests
(gate-only and gate + OIDC client stable over two passes) fail against
the previous controller.
2026-11-01 10:12:58 +00:00
hermes-bot f76defb1e1 fix(authentik-gate-sync): review of the OIDC clients
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
- Desired redirect URIs carry redirect_uri_type: authorization, which the
  API returns on every entry (RedirectURISerializer default in 2026.8.4);
  without it every pass would re-PATCH the provider.
- OIDC slugs and gate names are kept apart: a slug that is a gate name is
  refused, an existing application without this controller's OAuth2
  provider is refused, and the sweep keeps proxy and OAuth2 applications
  by their own sets, so an OIDCClient can neither take over nor delete a
  gate.
- Status is written only when it changed.
- Adoption of an unassigned oidc-<slug> provider is documented; removal
  logs say OIDC client.
- reconcile() takes k8s/ak for tests; new loop tests: create then quiet,
  duplicate slug, transient error keeps the client, sweep after the
  resource is gone, slug equal to a gate name.
2026-11-01 09:49:33 +00:00
hermes-bot 3e0557cfe8 feat(authentik-gate-sync): OIDC clients as OIDCClient resources, not Ingress annotations
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
OIDC is not part of the Ingress. The client is now declared by an
OIDCClient (auth.renner.uno/v1alpha1, CRD in
infrastructure/authentik-gate-sync-crds, like kuma-sync-crds) next to the
app: launchURL, redirectURIs, optional slug, group, authorizationFlow.
The controller writes a Ready condition and the issuer to its status.

The auth.renner.uno/oidc-* Ingress annotations are gone; RBAC gains
get/list/watch on oidcclients and patch on their status.
2026-11-01 09:31:43 +00:00
hermes-bot 8739efe026 feat(authentik-gate-sync): OIDC clients from auth.renner.uno/oidc-client
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
An Ingress annotated auth.renner.uno/oidc-client: <slug> gets a
confidential OAuth2/OpenID provider (client ID = slug, secret generated by
Authentik), an application with that slug and, with auth.renner.uno/group,
the same group expression policy as gates. auth.renner.uno/oidc-redirect-uri
pins strict redirect URIs on the Ingress host; default is any path on it.

Same fail-closed and ownership rules as gates: owned clients are found via
the OAuth2 provider list and meta_publisher and swept when their Ingress
goes. The service-account role gains the OAuth2 provider permissions plus
view on scope mappings and certificate key pairs.

So apps that log in through Authentik via OIDC (first: YouTrack, #1534)
need no blueprint entries; the groups stay managed in Authentik.
2026-11-01 09:22:25 +00:00
claude-botandClaude Opus 5.5 811178e321 feat(kuma-sync): one mail per outage via a root group
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
Monitors without a parentRef are placed under the root group set by
KUMA_ROOT_GROUP (kuma-sync/renner-uno). Only the root references the
email notification, so a fleet-wide outage sends one DOWN and one UP mail
listing the affected groups. resendInterval (consecutive DOWN heartbeats)
is now a managed KumaMonitor field; the root resends hourly while down.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 16:33:16 +01:00
claude-botandClaude Opus 5.5 63b2c3cc50 feat(kuma-sync): declarative notifications via KumaNotification
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
Every Kuma monitor, group or child, carried the default "email"
notification, so one outage sent a down and an up mail per monitor.

- New cluster-scoped CRD KumaNotification (type smtp): from/to in the
  spec, host/port/username/password from a Secret read uncached.
  kuma-sync adopts the Kuma notification of the same name, keeps it
  isDefault=false and reverts drift.
- KumaMonitor gains spec.notificationRefs. A monitor attaches only the
  notifications it references; none means none. The default-notification
  attachment is removed.
- KumaNotification "email" (uptime@renner.uno -> admin@renner.uno) with
  ExternalSecret kuma-sync-smtp from homelab/infrastructure/uptime-kuma/smtp
  and a Role granting get on that one Secret. No monitor references it.
- kuma-setup.js no longer creates the notification; the uptime-kuma role
  and the uptime-kuma-vps policy drop the SMTP credentials
  (policy must be re-applied by hand).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:35:59 +01:00
k.renner 576611faae Merge pull request 'chore(deps): update ci non-major' (#1444) from renovate/ci-minor-patch into main
ci/woodpecker/push/build-ci-images Pipeline is pending
ci/woodpecker/push/ci-claude-refresh Pipeline is pending
ci/woodpecker/push/deploy-infrastructure Pipeline is pending
Reviewed-on: #1444
Reviewed-by: Review Bot <review-bot@renner.uno>
2026-10-26 06:41:44 +00:00
renovate-bot 9258947fef chore(deps): update hermes image
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
2026-10-26 03:01:35 +00:00
renovate-bot 1955a11b81 chore(deps): update ci non-major
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/review-bot-test Pipeline was successful
2026-10-26 03:01:34 +00:00
claude-botandClaude Opus 5.5 6998845854 fix(authentik-gate-sync): embedded outpost redirects browsers to the public URL
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
The embedded outpost builds the sign-in redirect from authentik_host, not authentik_host_browser. Setting it to the in-cluster Service sent every gate's login to http://authentik-server.authentik.svc.cluster.local/. authentik_host is now https://auth.renner.uno/, Authentik's default for the embedded outpost.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 09:55:33 +02:00
claude-botandClaude Opus 5.5 6178063299 fix(authentik-gate-sync): verify the embedded outpost match, refuse shared hosts
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
The embedded outpost is matched on the client too, so a filter the server ignored cannot select the LDAP outpost. A host named by two gated Ingresses is a GateError: the outpost finds providers by host, so it would serve one gate for both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 09:44:34 +02:00
claude-botandClaude Opus 5.5 0b7a2b6c46 feat(authentik-gate): gate config lives on the Ingress itself
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was canceled
The gate is now declared on the app's own traefik Ingress: naming the
static Middleware authentik/gate (ForwardAuth against the embedded
outpost) in router.middlewares opts in, and its auth.renner.uno/*
annotations configure it. No generated route Ingress, ExternalName
Service or per-gate Middleware; the IngressClass authentik-gate is gone.

authentik-gate-sync keeps building the Authentik side (forward_single
provider on the embedded outpost, application, group policy) and the
backend CNP admitting Traefik, and sweeps the objects it generated
before. Traefik publishes the Ingress status itself.

The outpost's own paths (/outpost.goauthentik.io/) are one static
IngressRoute above the app routes. The embedded outpost answers 404 for
a host without a provider (checked live: auth/traefik with
X-Forwarded-Host no-such-gate.renner.uno), so a gate without its
provider stays closed.

docs.renner.uno points at paperless:80 behind redirect + gate (Paperless
Users) instead of the removed paperless-gate Service. Drops __pycache__
committed by mistake.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 09:40:27 +02:00
claude-botandClaude Opus 5.5 772311c574 feat(authentik-gate): Traefik ForwardAuth on the embedded outpost
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
Each gate was a single-replica proxy outpost in series with Traefik, so
any outpost restart, drain or node loss took its app down. Gates now
run as Traefik ForwardAuth against Authentik's embedded outpost inside
authentik-server: the request path is Traefik -> app, and the only auth
dependency is authentik-server, which every login already needs.

Per gated Ingress the controller now creates a forward_single provider
attached to the embedded outpost, a ForwardAuth Middleware first in the
route's chain, a /outpost.goauthentik.io route to authentik-server, and
a CNP admitting Traefik to the backend pods. It removes the per-gate
outposts and their egress CNPs.

Identity headers: authResponseHeadersRegex makes Traefik delete every
client-sent X-authentik-* header before copying the outpost's answer
(traefik pkg/middlewares/auth/forward.go), so a gated backend only ever
sees identity Authentik issued. Traefik answers 500 if the outpost is
unreachable and drops a router whose Middleware is missing: fail closed.

The blueprint no longer declares the embedded outpost: an entry must
carry providers, and re-applying [] would detach every gate. The
controller owns its providers and authentik_host/_browser.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 09:16:30 +02:00
claude-botandClaude Opus 5.5 5bfa957f29 feat(dns): one consumer per label; public DNS as CNAME to proxy.renner.uno
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
Step 2 of 2 of the label split (owner decision 2026-10-22). Requires
#1425 (step 1) to be applied first.

- external-dns-unifi: labelFilter dns.renner.uno/internal=true (opt-in).
  Every object it publishes today carries the label since step 1, so the
  UDM record set is unchanged.
- external-dns-cloudflare: labelFilter dns.renner.uno/public=true, default
  target proxy.renner.uno (the cloudflare-lb-sync load balancer) instead of
  the retired tunnel, no --cloudflare-proxied, policy sync. Each public
  host gets a DNS-only CNAME to proxy.renner.uno plus its TXT registry
  record; this is the prerequisite for removing the *.renner.uno wildcard.
- netbird-service-sync: reads netbird.renner.uno/public only. Every
  dns-public Ingress carries it since step 1, so the 21 proxy services are
  unchanged.
- git/gitea/gitea-mcp: add dns.renner.uno/public (public today via the
  wildcard; keep their LAN record via dns.renner.uno/internal).
- Drop comments describing the old public!=true filter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 21:45:20 +02:00
claude-botandClaude Opus 5.5 444b20d70e feat(cloudflare-lb-sync): manage proxy.renner.uno load balancer from the cluster
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
New in-house controller (kuma-sync house pattern) that reconciles a
CloudflareLoadBalancer resource into a DNS-only Cloudflare load balancer,
its pool and its health monitor, using cloudflare-go v7.

proxy.renner.uno had round-robin A/AAAA records without health checks, so
every proxy reboot blackholed part of the public traffic. The owner set up
a DNS-only load balancer by hand (hostname origins proxy-1/proxy-2, HTTPS
monitor with Host: status.renner.uno); this moves it into git.

- Identity is the load balancer hostname; pool and monitor are found via
  its references. Hand-made objects are adopted (the account allows only
  2 origins, so a parallel pool would be rejected) and tagged in their
  description; objects with another owner's tag are never written.
- Status shows per-origin health; resync every 2 minutes.
- Token from OpenBao homelab/infrastructure/k8s-cloudflare-lb via
  ExternalSecret; egress limited to api.cloudflare.com:443.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 20:46:01 +02:00
k.renner 919e7b9208 Merge pull request 'chore(talos): move to v1.13.12 (CVE-2026-99999)' (#1420) from chore/talos-1.13.12 into main
ci/woodpecker/push/build-ci-images Pipeline was successful
Reviewed-on: #1420
2026-10-22 08:08:30 +00:00
claude-botandClaude Opus 5.5 704e3d44b7 chore(talos): move to v1.13.12
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
Fixes CVE-2026-99999 (ipv4: validate checksum_start before completing
checksum, CVSS 9.3). The nodes run 6.18.48-talos; the fix is in 6.18.57,
which v1.13.12 ships (siderolabs/pkgs@19e92dc).

Not applied to any node yet. Rollout is one node at a time: drain,
talosctl upgrade --image, wait for Ceph HEALTH_OK, starting with k8s-1.

Patch release within 1.13; Kubernetes stays v1.36.3. Schematic is
unchanged; factory installer manifest and ISO verified to exist for the
tag. talosctl checksum taken from the release's sha256sum.txt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 09:54:26 +02:00
hermes-bot de080442d3 feat(hermes): alert issues end to end: labelled at filing, routed, fixed by the expert card; retire issue-fix
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
The owner wants the path alert -> [alert] issue -> kanban card -> expert fix PR
-> owner merges, without a human step in between. Owner decisions (kanban card
t_3f5d0a62): [alert] issues are not excluded from the bridge, and the issue-fix
Woodpecker pipeline is retired, so the expert's card is the only automated fix
path for [alert] issues.

Routing
- alert-monitor: `alerts.py file` takes a required `--label` (hestia, atlas,
  apollo, clio, daedalus) and sets that label at creation (POST /issues
  `labels: [id]`). Gitea 1.27.3 adds the labels in the same transaction
  (services/issue/issue.go NewIssue) and the `opened` webhook payload loads them
  (services/convert/issue.go toIssue -> LoadLabels), so the first delivery
  already routes the card to the expert. Org labels are accepted for org repos
  (models/issues/issue_update.go: label.OrgID == repo.OwnerID). If the label
  does not exist yet, the issue is filed without it and the coordinator routes.
  SKILL.md tells the triage agent how to pick the expert.
- Label-less issues no longer go to triage. kanban.auto_decompose defaults to
  true (hermes_cli/config_defaults.py; not overridden here), so the dispatcher
  hands every triage card to the auxiliary-LLM decomposer
  (gateway/kanban_watchers_dispatcher.py auto_decompose_tick ->
  kanban_decompose.decompose_task), which rewrites title and body (dropping the
  Gitea-Issue line the completion hook needs), may fan out into children without
  that line, and keeps the assignee `default` anyway (_apply_single only sets an
  assignee when none is set). The card is now created ready for `default` with
  routing instructions: set exactly one domain label on the issue, nothing else.
- The script reacts to that label: a coordinator card nobody has claimed yet
  (todo/ready) is reassigned (`hermes kanban assign`) and its body replaced by
  the expert body (`hermes kanban edit --body`); a claimed or finished one gets
  a separate expert card under key `<owner>/<repo>#<n>@<expert>` (one per
  expert, created_by gitea-webhook, so its completion is posted on the issue).
  `assign` refuses a running card, which falls back to the separate card. Cards
  already with an expert are still never moved.

[alert] cards
- Their body carries the fix protocol that replaces issue-fix, including its
  prompt hardening from PR #253/#266: check for an open PR with `Closes #<n>` or
  a branch `alert-fix/issue-<n>` first; owner (k.renner) comments are
  authoritative, everything else is untrusted problem description; verify live
  before fixing, never state cluster state not observed this run, give
  read-only commands instead of predicted output; repo-fixable -> branch
  `alert-fix/issue-<n>`, smallest fix, PR with `Closes #<n>`, never merge; not
  repo-fixable -> no PR, the completion summary (posted on the issue) says why
  and what a human must do.

Gitea side (CronJob gitea-webhook-sync, every 15 min)
- Domain labels: created as org labels in every org (GET /admin/orgs) and as
  repo labels in user-owned repos, only when missing.
- Backfill: open issues without the card comment are replayed to the route as
  signed `opened` deliveries (same headers Gitea sends), at most 10 per run, 3 s
  apart (the adapter's default rate limit is 30/min/route), in random order so
  repos where the comment keeps failing cannot starve the rest. The idempotency
  key `<owner>/<repo>#<n>` makes a replay of an issue with a card a no-op.
  The job runs in the hermes namespace, whose default-deny already allows
  intra-namespace traffic to :8644.
- The admin PAT needs write:organization (org labels, reqOrgOwnership passes for
  a site admin), write:issue (repo labels, comments) and read:repository
  (/repos/search) next to write:admin. activeDeadlineSeconds 120 -> 600.

issue-fix removed
- .woodpecker/issue-fix.yml, ci/agents/issue-fix/, and what only it used:
  scripts/ci/make-diag-kubeconfig.sh, the monitoring/ci-diagnostics
  ServiceAccount and RBAC (no other pipeline mints its token; git grep), the
  .ci-diag/ gitignore entry. Comments naming issue-fix updated.
- Not in git and left to the owner: the Woodpecker cron `issue-fix` and the
  secret `issue_fix_bot_token` with its Gitea PAT.

Tested: unittest 73 OK; gitea_issues.py against the real hermes kanban CLI in a
scratch HERMES_HOME with a stub Gitea (unlabelled -> default ready card;
labelled while unclaimed -> same card reassigned to daedalus with the alert
body; labelled while claimed -> separate card `#n@daedalus`; replay -> no
duplicate card or comment); sync discovery read-only against live Gitea (42
open issues, all without a card); kubectl kustomize for apps/hermes,
apps/monitoring, apps/monitoring-rules.
2026-10-21 05:48:01 +00:00
k.renner 8332bfb166 Merge pull request 'feat(kuma-sync): declare the public status page in git' (#1403) from feat/kuma-status-page into main
ci/woodpecker/push/deploy-uptime-kuma Pipeline was successful
ci/woodpecker/push/build-ci-images Pipeline was successful
Reviewed-on: #1403
2026-10-20 19:49:51 +00:00
claude-botandClaude Opus 5.5 e3e3b987fa feat(kuma-sync): ship the KumaStatusPage CRD, RBAC and the renner page
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
The CRD and the ClusterRole rules land with the controller change, so
Flux applies them at merge, before the new kuma-sync image is built and
rolled out by the ImagePolicy; the new controller watches
KumaStatusPage and would not start without them.

- infrastructure/kuma-sync-crds: crd-kumastatuspages.yaml, a copy of
  config/crd with prune disabled, next to crd-kumamonitors.yaml.
- apps/kuma-sync/rbac.yaml: kumastatuspages, /status, /finalizers, as in
  config/rbac/role.yaml.
- apps/kuma-sync/statuspage-renner.yaml: the public page (slug renner,
  domain uptime.renner.uno) with the sections Services and Third-party,
  selected by the uptime.renner.uno/status-page-section label.
- The label on the 17 top-level service groups and the four third-party
  HTTP checks.

An envtest test validates the deployed page object against the CRD.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 21:15:57 +02:00
claude-botandClaude Opus 5.5 c24e02d048 fix(kuma-sync): clear stale status page view, test unsent config keys
A Conflict loser no longer keeps the page ID, section counts and unsynced
count of an earlier sync; an invalid selector keeps only the page ID.
The round-trip integration test now asserts that autoRefreshInterval,
showOnlyLastHeartbeat and rssTitle survive a managed save. Doc comments
state that per-monitor custom URLs are cleared on save.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 21:11:16 +02:00
claude-botandClaude Opus 5.5 37c83ed6c2 feat(kuma-sync): declare the public status page as a KumaStatusPage
New namespaced CRD KumaStatusPage (uptime.renner.uno/v1alpha1) and a
reconciler that owns the Kuma status page completely: config, domain
names and the full group/monitor list. Sections select KumaMonitors in
any namespace by label; monitors are ordered by displayName, then
namespace/name, a monitor goes to the first matching section only, and
monitors without a Kuma ID are left out and counted in status.

The reconciler adopts an existing page with the slug, writes only when
the managed content differs (no write per resync), reuses Kuma group IDs
by position so saves do not churn them, reverts UI edits on resync,
deletes the page on resource deletion, and reports Ready=False/Conflict
for a younger resource claiming the same slug. Every KumaMonitor change
to labels, displayName, monitor ID or deletion enqueues all pages.

kumaclient gains a StatusPages interface (Breml + Fake). The socket API
returns no group list, so groups are read from Kuma's public
GET /api/status-page/<slug>, as the bootstrap did. Fields kuma-sync does
not manage (logo, custom CSS, analytics, RSS title, auto refresh
interval) are written back unchanged. autoRefreshInterval is not in the
spec: go-uptime-kuma-client v0.6.2 (latest) never sends it on save.

The Ansible bootstrap no longer creates or edits status pages; the
existing page is left in place for kuma-sync to adopt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 21:03:50 +02:00
claude-bot 4f547dfd27 feat(hermes): alert-monitor moves from Woodpecker to a Hermes cron job
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
Owner decision: Hermes files the [alert] issues; the Woodpecker
alert-monitor pipeline is retired. issue-fix keeps working unchanged: it
picks issues by the "[alert] " title prefix, and the dedup contract is the
same (title "[alert] <alertname> (<namespace|job|cluster>)", noise-log lines
"- <fp> <ALERTNAME> — <date>: <reason>" with a 30-day TTL, blip
fingerprint sha256(alertname|scope)[:16], fingerprint footer on issues).

- skill alert-monitor: scripts/alerts.py (stdlib) is the cron pre-run gate:
  Alertmanager active alerts minus heartbeats and null-routed ones, plus
  ALERTS blips of the last 75 min, minus open [alert] issues (paginated, the
  pipeline read only the first 100) and noise fingerprints. Nothing new ->
  {"wakeAgent": false}, no LLM run, no message. `file` and `noise` write the
  verdicts in the fixed formats. SKILL.md ports the triage prompt: wiki
  first, verify live (kubectl, Prometheus, Loki, talosctl), problem/noise,
  root cause vs symptoms; Matrix report per run that found something.
- Cron job alert-monitor, hourly at :20, deliver matrix. The init container
  (renamed cron-jobs) now ensures both jobs by name and copies the gate
  script to /opt/data/scripts (Hermes only runs scripts from there).
- HermesDown: blackbox probe of https://hermes.renner.uno/api/status plus an
  alert, since a Hermes outage now stops alert -> issue (Alertmanager email
  is unaffected).
- Removed .woodpecker/alert-monitor.yml and ci/agents/alert-monitor/;
  comments that named them updated.

Tested: the gate run inside the hermes pod against live Alertmanager,
Prometheus and Gitea: 2 active, 36 open issues, 50 noise suppressions, 0 new
-> wakeAgent false. With a 3-day lookback and no noise TTL it produced 4
blips with the expected titles. file/noise against a fake Gitea (61 issues,
pagination, duplicate title skipped, footer, noise log created then
appended, parsed back by the gate; bad title/body/fingerprint refused).
Init script run twice on a scratch HERMES_HOME holding an existing
ha-updates job: ha-updates edited in place, alert-monitor created once,
script copied. Blackbox probe of the new target: 200. kustomize, citations
and server dry-run pass.

After merge:
- Delete the Woodpecker cron alert-monitor (id 4); without its pipeline file
  it has nothing to run.
- The alert_monitor_bot_token secret and the alert-monitor-bot account are
  unused afterwards.

Trade-off: the pipeline posted with alert-monitor-bot, a PAT limited to
write:issue. Hermes posts as hermes-bot, which also has repo write; the
agent reads untrusted alert text with a broader credential than before.
2026-10-20 20:52:46 +02:00
hermes-bot 58b59fbe43 feat(kuma): monitor every public service and the proxies, defined with their apps
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
Every host the NetBird proxies serve (exactly the Ingresses labelled
dns.renner.uno/public or netbird.renner.uno/public; verified by SNI probe of
proxy-1 and proxy-2) gets a group with three kinds of check:

- DNS: cluster services have no own record, so the check asserts the
  *.renner.uno wildcard answers (CNAME == proxy.renner.uno, Cloudflare
  resolver, IPv6 first). External names (apex, proxies, third-party) assert
  their own AAAA/A record instead.
- HTTP reachability: a URL routed to the backend, redirects not followed,
  any status below 500 counts.
- Response validation: health/API JSON or a page keyword; services fully
  behind the Authentik gate assert the login page.

The proxies get their own group: proxy.renner.uno, and per instance its
DNS plus reachability and a Gatus health check pinned to that proxy (URL
host is the proxy, Host header status.renner.uno; Node derives SNI from the
Host header, so the proxy routes it like a client request).

Layout: monitors live in the kustomization of the app they monitor;
external ones (proxies, apex, third-party) in apps/monitoring. The CRD moves
to infrastructure/ so every app Kustomization can consume it; it carries
prune: disabled, so leaving kuma-sync's inventory does not delete it. The
old kuma-sync/monitors objects go; their Kuma monitors are recreated under
the new owner keys (history since this morning is lost).

App Kustomizations wait on their resources, and KumaMonitor Ready reflects
whether Uptime Kuma on the VPS is reachable. Each consuming Kustomization
therefore treats KumaMonitor as always current, so a Kuma outage cannot
stall Flux.

kuma-sync: dns monitors gain spec.dns.conditions (Kuma's "record"
variable with its string operators, ANDed), mapped both ways so drift is
detected and an emptied list clears Kuma's conditions. Until the new image
rolls out the controller drops the field and the DNS checks only test
resolvability.
2026-10-20 09:24:09 +00:00
claude-botandClaude Opus 5.5 d9fb1d6244 docs(review-bot): every repo's default branch is main
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/review-bot-test Pipeline was successful
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 11:08:50 +02:00
claude-botandClaude Opus 5.5 5528ccbd0b chore: default branch main
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
Pipeline branch filters and PR bases, Flux GitRepository ref, image automation
checkout and push, restore-test, vmtest and bootstrap.sh. Merge right before
the master -> main rename.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 10:57:20 +02:00
claude-botandClaude Opus 5.5 7f29ebb878 chore: follow homelab to konairius/homelab
review-bot reviewed: comment
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/review-bot-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
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>
2026-10-19 10:43:23 +02:00
hermes-bot 3c5c04d4a5 fix(hermes): let Renovate keep the Hermes image current
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
review-bot reviewed: comment
ci/woodpecker/pr/ci-manifests-test Pipeline was canceled
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).
2026-10-19 08:06:09 +00:00
hermes-bot f73cc54705 feat(hermes): build a Hermes image with the agent's CLI tools
review-bot reviewed: comment
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
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.
2026-10-19 07:58:27 +00:00
claude-botandClaude Opus 5.5 bb992833c2 feat(kuma-sync): attach Kuma's default notifications to managed monitors
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
review-bot reviewed: comment
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>
2026-10-19 08:23:44 +02:00
claude-botandClaude Opus 5.5 286564a0d7 fix(kuma-sync): create when the previous monitor ID belongs to another owner
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
review-bot reviewed: comment
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>
2026-10-19 08:12:45 +02:00
k.renner 9e62fadcb1 Merge pull request 'chore(deps): update ci non-major' (#1345) from renovate/ci-minor-patch into master
ci/woodpecker/push/ci-claude-refresh Pipeline was successful
ci/woodpecker/push/build-ci-images Pipeline was successful
Reviewed-on: k.renner/homelab#1345
2026-10-19 05:17:04 +00:00
renovate-bot f8b98907e8 chore(deps): update ci non-major
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
review-bot reviewed: comment
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
2026-10-19 02:01:57 +00:00
claude-botandClaude Opus 5.5 e1c0e73f61 fix(kuma-sync): orphan grace, refuse create beside an untagged previous monitor, drop push
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
review-bot reviewed: comment
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 00:47:16 +02:00
claude-botandClaude Opus 5.5 59ccd74a89 test(kuma-sync): race-free drift test, restart proves adoption
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 00:30:13 +02:00
claude-botandClaude Opus 5.5 15a4e2a0dc test(kuma-sync): end-to-end integration suite
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 00:23:14 +02:00
claude-botandClaude Opus 5.5 2af1d880e4 fix(kuma-sync): confirm orphan deletes against the API server
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 00:16:21 +02:00
claude-botandClaude Opus 5.5 18ef262d93 feat(kuma-sync): orphan GC, manager and image build
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 00:12:24 +02:00
claude-botandClaude Opus 5.5 185738bd2c fix(kuma-sync): never create on an incomplete list; typed owner conflicts; duplicate owners
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 00:02:04 +02:00
claude-botandClaude Opus 5.5 0c487656e8 feat(kuma-sync): KumaMonitor reconciler
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 23:54:48 +02:00
claude-botandClaude Opus 5.5 b3f68753d1 fix(kuma-sync): flag unknown-outcome creates as possible orphans
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 23:50:53 +02:00
claude-botandClaude Opus 5.5 1968e91516 fix(kuma-sync): never create after a failed owner lookup
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 23:47:54 +02:00
claude-botandClaude Opus 5.5 4f0c7c414e fix(kuma-sync): reconnect, no orphan on create timeout, safe timeout, owner-tag integrity
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 23:41:20 +02:00
claude-botandClaude Opus 5.5 734f546d8b feat(kuma-sync): Kuma client on go-uptime-kuma-client
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 23:29:36 +02:00