Commit Graph
300 Commits
Author SHA1 Message Date
Story Crater Bot 7f8e947958 feat(agent-pod): add agent-hub sidecar for multi-agent session monitoring
Hub relays pi's own --mode json session protocol (session/agent_start/
turn_start/message_*/turn_end/agent_end -- the same event shape Claude
Code sessions use) to agent-console over WebSocket, so concurrent pi
runs inside the pod are observable as real transcripts instead of log
tails. agent-run.js wraps `pi -p --mode json` and relays its stdout
lines to the hub; each invocation gets its own session id, so N
concurrent agents show up as N sessions. Source mounted via ConfigMap
and run with `go run .` (no registry yet, same as the pi container).
2026-08-16 07:55:33 -07:00
Story Crater Bot 0c52dec155 feat(api): add Kong RED metrics for LLM routes
Cluster-wide prometheus KongClusterPlugin (kong-metrics.yaml) +
chart-native ServiceMonitor (kong-values.yaml) expose
kong_http_requests_total/kong_latency_bucket/kong_bandwidth_bytes for
every route, LLM and otherwise. Dashboard filters to route=~"llm-.*"
for request rate, error rate, p95 upstream latency, and bandwidth.
Token-count metrics still need ai-proxy-advanced (Enterprise-only);
not attempted.
2026-08-16 07:01:15 -07:00
Story Crater Bot 3724cd3cdb feat(monitoring): add LLM Frontend Grafana dashboard
Resources (CPU/mem/restarts) + Loki logs across llm-serving, agent-pod,
and api (Kong) namespaces, mirroring the svc-*.yaml dashboard pattern.
No request-rate/latency/token panels yet -- no ServiceMonitor exists
for Kong or the KServe predictors, so there's no metric to query.
2026-08-16 06:54:56 -07:00
Story Crater Bot 6fb9b7cf1e fix(agent-pod): send Kong apikey via provider-level headers, not compat.customHeaders
pi's openai-completions handler (pi-ai/dist/api/openai-completions.js)
reads model.headers (populated from a provider-level "headers" field
in the config), not compat.customHeaders -- that field only gets read
by the Bedrock handler. Without it, pi used the openai SDK's default
Authorization: Bearer <apiKey>, which Kong's key-auth plugin rejects
(verified via direct fetch: apikey header -> 200, Bearer -> 401,
plugin does not strip the Bearer prefix). Moved the apikey header to
the correct provider-level "headers" field.
2026-08-16 06:47:33 -07:00
Story Crater Bot 23fc334b8a fix(agent-pod): pin api.riotpiao.com via hostAliases
api.riotpiao.com has no in-cluster DNS record (getaddrinfo ENOTFOUND
from inside the pod, confirmed against google.com resolving fine and
kong-proxy.api.svc.cluster.local resolving fine) -- it only resolves
via the home network's own DNS. Node's fetch/undici also silently
drops a manually-set Host header (forbidden header per WHATWG fetch
spec), so overriding Host per-request isn't viable either. hostAliases
pinning the hostname to ingress-nginx-controller's ClusterIP lets
pi's models.json baseUrl work completely unchanged -- TLS SNI/Host
still say api.riotpiao.com so cert validation and Kong's Host-based
routing both still work.
2026-08-16 00:59:14 -07:00
Story Crater Bot 8d60396d14 fix(agent-pod): bump base image to node:22-slim
pi-coding-agent requires node >=22.19.0 (undici's
webidl.util.markAsUncloneable, added in Node 22) -- node:20-slim
installed fine but crashed on every invocation.
2026-08-16 00:54:45 -07:00
Story Crater Bot 0a2757aeac fix(agent-pod): pin pi-coding-agent to a version that actually exists on npm
0.1.23 was pi's own internal version string (from local pi --version),
not the npm package's semver -- npm has no such release, so the
container crash-looped on ETARGET. Latest published is 0.84.2.
2026-08-16 00:34:55 -07:00
Story Crater Bot 19089e0514 fix(agent-pod): register agent-pod-models.enc.yaml with ksops generator
Secret was created but never listed in secret-generator.yaml's files
allowlist, so ksops never decrypted it — pod stuck on FailedMount
waiting for secret "pi-models".
2026-08-16 00:27:10 -07:00
Story Crater Bot 3620b4929c feat(agent-pod): deploy pi coding-agent pod for in-cluster harness
Namespace + Deployment (2-4 CPU, 4-8Gi mem) running node:20-slim with
pi installed at startup, wired to the homelab-ornith/reasoning/qwen
model providers via the existing model-invoke-apikey. Placeholder
node:20-slim image for now, real harness image to follow.
2026-08-16 00:23:30 -07:00
Story Crater Bot 9b75b15aa3 fix(longhorn): clear permanent OutOfSync on longhorn-config
StorageClass.parameters is immutable and mkfsParams was added after creation,
so every sync failed. Replace=true recreates it instead of patching. Existing
volumes keep their format; only new ones get mkfsParams.

Longhorn names its own disk key and writes storageReserved into it, so git's
default-disk never matched and selfHeal kept trying to add a second disk on the
same path. Dropped spec.disks from git, added ignoreDifferences.
2026-08-15 23:07:49 -07:00
Story Crater Bot a005a07648 fix(llm-serving): ornith 504 on cold start
Two bugs. Kong timeouts were on the Ingress; it reads them from the Service, so
its 60s default applied. Moved to the isvc, which KServe propagates.

Probes ran 'ollama list' — models on disk, not in VRAM — so the pod went Ready
before it could serve. Now 'ollama ps', and both models are warmed at startup.
2026-08-15 22:58:24 -07:00
Story Crater Bot 5b7d22c90d fix(talos): indent cilium inlineManifests block scalars and let longhorn_disks declare an array with optional mountpoint/kind — the scalars rendered their first line at column 0, silently invalidating all 3 control-plane configs so lb-ippool/l2-announcement never reached any node 2026-08-15 22:46:19 -07:00
Story Crater Bot e87780b22f feat(llm-serving): adopt InferenceServices into ArgoCD
Five model servers were applied by hand and tracked nowhere. Exported live,
kubectl diff empty on all five, so the first sync adopts without restarting.

prune: false — KServe copies isvc labels to its child Deployment, so ArgoCD
would prune children it does not own and KServe would recreate them.
2026-08-15 22:45:38 -07:00
Story Crater BotandClaude Opus 5 826a2acda3 fix(api): unbreak >10.6KB LLM requests and actually bind key-auth to the model routes
Two independent bugs, both silent, both found while pointing an agent harness
at api.riotpiao.com.

1. Requests over ~10.6KB failed with HTTP 400
   {"error":{"message":"[] is too short - 'messages'"}}.

   The request-transformer plugin on the llm-chat-* routes rewrites the JSON
   body, which means it reads it via kong.request.get_body(). That returns
   nothing once nginx spills the body past client_body_buffer_size into a temp
   file, so the plugin re-serialized a body with no `messages` and the upstream
   rejected it. Measured on /v1/ornith/chat/completions: 10588 B -> 200,
   11088 B -> 400. Isolated by size-sweeping /v1/embeddings, the one route with
   no request-transformer, which passed an 18057 B body straight through to a
   semantic 413 from TEI.

   Raises the Kong http-block buffer to 16m. Any agent request carrying tool
   schemas clears the old ceiling in a single turn.

2. key-auth was never applied to any model route.

   The model-key-auth KongPlugin sat in namespace `api` while all five routes
   that annotate it live in `llm-serving`. The ingress controller resolves
   konghq.com/plugins against the annotated object's own namespace, so the
   reference dangled and the plugin never bound. Verified before the fix:
   unauthenticated GET /v1/models and POST /v1/ornith/chat/completions both
   returned 200. A dangling plugin reference fails open and logs nothing.

   Re-test both without a key after this syncs; expect 401.

Note for follow-up: llm-embeddings and llm-score carry no plugins annotation at
all, so they stay unauthenticated even after this change.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-13 21:34:50 -07:00
Story Crater Bot a118195b6f feat(api): add Kong key-auth for the model API — static API key (ksops-managed model-invoke-apikey) via KongConsumer model-invoker + key-auth plugin on all 5 model routes, OpenAI-SDK compatible (Authorization: Bearer <key>) 2026-08-13 20:28:32 -07:00
Story Crater Bot c52b4acc74 fix(ci): make the hardcoded-secret scan blocking and close the .gitignore/.sops.yaml gaps that let a plaintext deploy key through — also untracks tfplan binaries and skills-lock.json 2026-08-13 18:02:59 -07:00
Story Crater Bot a816045d3b fix(argocd): clone the public GitHub seed anonymously over HTTPS and delete the SSH deploy-key Secret — its private half had been committed in plaintext to a public remote, and a public repo needs no credential at all 2026-08-13 18:02:52 -07:00
Story Crater Bot ff77df5933 fix(forgejo): strategy Recreate for RWO data PVC — RollingUpdate deadlocked (new pod Multi-Attach error on the RWO gitea PVC held by the old pod, stuck Init forever) 2026-08-13 10:47:57 -07:00
Story Crater Bot 4645e320d5 fix(authentik): label argocd oidc-secret part-of=argocd — argocd's $secret substitution only reads labelled Secrets; without it OIDC login failed with oauth2 invalid_client (empty client_secret to IdP) 2026-08-13 08:56:37 -07:00
Story Crater Bot 27ae187526 feat(argocd): wire Authentik OIDC + local rock/cicd accounts + RBAC — adds oidc.config (homelab-admins->admin SSO), url, accounts.rock (login+apiKey) and accounts.cicd (apiKey for CD pipeline token), all role:admin 2026-08-13 08:45:20 -07:00
Story Crater Bot 0a29d781de fix(homarr): raise CPU limit 500m->2 + disable analytics cron — Next.js aborted with exit 134 (SIGABRT) under CPU throttle during icon-updater/analytics, self-restarting in a loop and 502ing at the ingress 2026-08-13 08:03:22 -07:00
Story Crater Bot a2e97e8cd3 fix(cilium): restrict L2 announcement to control-plane nodes — GPU worker lacks eno1 (Mellanox enp28s0f*), so when it won the .160 lease it couldn't ARP the VIP, black-holing all ingress (flapped on reboots) 2026-08-13 07:58:12 -07:00
Story Crater Bot b695cee987 fix(api): label Kong pods llm-client=true so llm-serving NetworkPolicy admits them — chat/embeddings/rerank/score routes silently hung until the client timeout because Cilium dropped Kong's packets
llm-serving-default-deny admits port 8080 only from pods carrying
llm-client=true. Kong lacked it, so every route that actually contacts an
upstream timed out. /v1/models masked the problem: request-termination answers
inside Kong and never touches an upstream, so it returned 200 throughout.

Opting in via podLabels rather than relaxing the policy — it is a compensating
control, not hygiene, since vLLM v0.11.0 is frozen on Volta and will not receive
patches for several remote/unauthenticated advisories.

podLabels land only in the pod template, not spec.selector.matchLabels, so this
is not an immutable-field change.
2026-08-13 07:55:21 -07:00
Story Crater Bot 245a03e951 feat(api): add DeepSeek-shaped LLM API on Kong — /v1/models, per-model chat completions, embeddings, rerank, score; disable Kong response buffering so stream:true actually streams
Kong matches routes on host/path/method/header, never on the request body, so a
single /v1/chat/completions dispatching on body.model is not expressible in Kong
OSS (ai-proxy-advanced, which does multi-target model routing, is Enterprise).
Model therefore goes in the path:

  GET  /v1/models                        static list (request-termination)
  POST /v1/reasoning/chat/completions     reasoning-predictor  (vLLM)
  POST /v1/ornith/chat/completions        ornith-predictor     (Ollama)
  POST /v1/qwen/chat/completions          ornith-predictor     (Ollama, same pod)
  POST /v1/embeddings                     embeddings-predictor (TEI)
  POST /v1/rerank                         reranker-predictor   (TEI)
  POST /v1/score                          verifier-predictor   (vLLM pooling)

- each chat route force-overwrites body.model via request-transformer add+replace:
  ornith:35b and qwen2.5:3b-instruct share one Ollama pod, so without this a
  client hitting /v1/qwen with "model":"ornith:35b" would silently get the 35B
- routes live in ns llm-serving, not api: an Ingress can only reference a Service
  in its own namespace, and KIC watches all namespaces
- embeddings and score need no rewrite (TEI/vLLM already serve the canonical
  paths); rerank does, since /v1/rerank 404s and only /rerank exists
- read/write timeouts 1h: Kong defaults to 60s, which a 32B model on Volta
  exceeds mid-generation and returns 504
- nginx_proxy_proxy_buffering=off: buffered responses lump or stall SSE, and both
  hops (nginx Ingress and Kong) must be unbuffered or the buffered one wins
- no auth for now, per decision; api.riotpiao.com is reachable through nginx, so
  GPU time is currently unauthenticated
2026-08-13 07:47:45 -07:00
Story Crater Bot af7c5e845a fix(ingress): remove stale ingress-nginx-controller-alias Service — its selfHeal kept clobbering the helm LoadBalancer Service (same name, dead ingress-nginx-bootstrap selector, 0 endpoints), unannouncing LB IP .160 and taking down all ingress 2026-08-13 07:40:23 -07:00
Story Crater Bot 063f9bcd23 fix(homarr): add AUTH_OIDC_URI + email account linking — homarr hides the Authentik sign-in button unless AUTH_OIDC_URI (authorize endpoint) is set alongside AUTH_OIDC_ISSUER (per authentik/homarr SSO docs); was the missing var 2026-08-13 07:26:59 -07:00
Story Crater Bot df9a68d0ba refactor(ingress): drop redundant ArgoCD ingress-nginx app — chart 4.15.1 was double-managed by both the helm-bootstrap release and this ArgoCD app (same chart), fighting over the controller/LB service (ingress-config drift). ingress-nginx is bootstrap-critical (ArgoCD's own reachability path), so helm-bootstrap is the single owner 2026-08-13 07:20:06 -07:00
Story Crater Bot a07af6bf07 feat(sms): add BlueBubbles iMessage delivery (Docker-OSX macOS VM pinned to worker-2) + ArgoCD app + dedicated longhorn-imessage-local SC — default longhorn SC can't schedule a 3-replica 200Gi volume (only worker-1 has 200Gi free at 100% over-provisioning) and Immediate binding would pin the qcow2 to the wrong node
- namespace: PodSecurity privileged, needed for /dev/kvm + privileged QEMU
- storageclass: 1 replica, strict-local, WaitForFirstConsumer
- deployment: nodeSelector workload=imessage + matching NoSchedule toleration,
  Recreate strategy (two QEMU procs on one qcow2 corrupts it), no readiness
  probe (guest install is interactive and takes many minutes)
- services: ClusterIP only; VNC is an unauthenticated console, reach it with
  port-forward, never an Ingress
- networkpolicy: default-deny, opt-in via sms-client=true on port 1234
2026-08-13 07:15:02 -07:00
Story Crater Bot 3a91c19b5c feat(monitoring): enable Alertmanager (null receiver, longhorn PVC, az-a) + fix forgejo-rules ns forgejo->cicd — alerting delivery was disabled; forgejo PrometheusRule targeted a nonexistent namespace 2026-08-13 07:10:03 -07:00
Story Crater Bot 2d7127b37e fix(prometheus): use longhorn StorageClass, drop nonexistent longhorn-wffc — Prometheus CR requested storageClass longhorn-wffc which doesn't exist (deprecated), so operator never created the StatefulSet (Reconciled=False, no metrics server) 2026-08-13 06:37:53 -07:00
Story Crater Bot 2ba89f2ec0 fix(homarr): tune probes via chart values, drop fragile fix-probes-job — first-boot icon updater blocks health endpoint ~50s; default 10s×3 liveness SIGTERMs the pod (247 restarts, 503); chart exposes probes so the PostSync patch-job was unnecessary and reverted on every rollout 2026-08-13 06:33:34 -07:00
Story Crater Bot 0b282ba1f8 fix(authentik): add minio policy scope mapping (homelab-admins->consoleAdmin else readonly) + set rock email — MinIO CLAIM_NAME=policy got no claim (no MinIO access); empty rock email broke Grafana OIDC (GitHub-style /emails 404) 2026-08-12 20:31:13 -07:00
Story Crater Bot f10f0a8a26 fix(grafana): add email/login/name_attribute_path for Authentik OIDC — Grafana was falling back to GitHub-style <api_url>/emails (404 'Error getting email address'), breaking OAuth login; read identity from userinfo claims instead 2026-08-12 16:46:57 -07:00
Story Crater Bot 34288b0b95 fix(forgejo-runner): cicd ns PSS privileged (dind needs it) + mount homelab-ca as ConfigMap not Secret — runner RS created 0 pods under baseline PSS, then FailedMount because homelab-ca is a ConfigMap trust bundle, not a Secret 2026-08-12 16:25:18 -07:00
Story Crater Bot 1fb0b62d7d feat(forgejo): add runner-token Secret via ksops — forgejo-runner register initContainer needs the registration token (from gitea actions generate-runner-token); was missing so runner deploy stuck 0/1 2026-08-12 16:19:38 -07:00
Story Crater Bot 09873aa275 fix(coredns): own Corefile+hostname rewrites via Talos inlineManifest (single-source terraform/files/coredns/Corefile), drop ArgoCD coredns-config app — in-cluster *.riotpiao.com now resolves to nginx ingress so MinIO/OIDC discovery works; update cp-2 IP .213->.214 2026-08-12 16:16:44 -07:00
Story Crater Bot 63f2eaddd6 feat(reloader): enable autoReloadAll + reloadOnCreate — watch all workloads without per-Deployment annotations (charts like homarr don't expose them); auto-restart pods when ksops secrets are created/rotated 2026-08-12 14:15:42 -07:00
Story Crater Bot 555b4b4050 fix(homarr): add auth-oidc-secret + db-encryption Secrets via ksops — homarr chart's envSecrets expect these exact names (oidc-client-id/secret, db-encryption-key); were never created so homarr CreateContainerConfigError 2026-08-12 14:08:18 -07:00
Story Crater Bot 8cf342b27c chore(duckdns): remove duckdns updater entirely — superseded by cloudflared tunnel; drop app-def, manifests, kube-system Deployment 2026-08-12 14:01:01 -07:00
Story Crater Bot 44f9bc25c4 fix(cert-manager): regenerate homelab-ca cert with basicConstraints CA:TRUE — old self-signed cert lacked CA:TRUE so the homelab-ca ClusterIssuer rejected it ('certificate is not a CA'); regen keypair Secret + trust-bundle ConfigMaps (4 ns) with matching CA cert 2026-08-12 13:56:37 -07:00
Story Crater Bot 5e97c5cf64 feat(vault): add vault-unseal-keys Secret via ksops after operator init — vault was never initialized (empty S3 bucket), unseal keys captured from init; pod postStart auto-unseals on restart 2026-08-12 13:53:09 -07:00
Story Crater Bot b7b1f15084 fix(logging): deploy loki-s3-creds as kind:Secret via ksops — was a helm-values fragment wired to nothing, loki extraEnvFrom secretRef loki-s3-creds never resolved (CreateContainerConfigError); provides access_key_id/secret_access_key for MinIO S3 backend 2026-08-12 13:46:07 -07:00
Story Crater Bot d51713ad6a fix(iam): deploy authentik-secrets as kind:Secret via ksops — was a helm-values fragment wired to nothing, so envFrom secretRef authentik-secrets never resolved (CreateContainerConfigError); provides AUTHENTIK_SECRET_KEY/BOOTSTRAP_PASSWORD/BOOTSTRAP_TOKEN 2026-08-12 13:37:39 -07:00
Story Crater Bot fa239972a7 fix(cert-manager): render issuers via kustomization resources list, restore automated sync — directory.include with bare filenames rendered empty (never matched), so ArgoCD tracked 0 resources and prune wiped the CA ConfigMaps + ClusterIssuers 2026-08-12 13:30:46 -07:00
Story Crater Bot f53d54cba9 fix(argocd): disable automated sync on cert-manager-issuers — directory.include renders empty, automated prune was wiping ClusterIssuers + homelab-ca ConfigMaps; manual sync until render root-caused 2026-08-12 13:28:24 -07:00
Story Crater Bot e4bbec95fb fix(cert-manager): drop empty kustomization.yaml shadowing cert-manager-issuers directory.include — stub rendered 0 resources, tripping ArgoCD 'auto-sync will wipe all resources' halt, blocking the homelab-ca.crt ConfigMap fix that authentik CA-init needs 2026-08-12 13:19:17 -07:00
Story Crater Bot beb3cb21a0 refactor(argocd): replace SOPS CMP with ksops kustomize generator, rotate age key — CMP discover glob silently shadowed kustomize rendering of any app whose path held a .enc.yaml (MinIO Tenant/cloudflared/authentik jobs never applied); centralize 8 Secret manifests under k8s/argocd/secrets, defer 4 helm-values fragments 2026-08-12 13:16:15 -07:00
Story Crater Bot 9e84fb3386 fix(cert-manager): add homelab-ca.crt key to homelab-ca ConfigMaps — authentik init merge-ca-certs cats /homelab-ca/homelab-ca.crt which was missing, causing Init:Error and 503 2026-08-12 09:13:15 -07:00
Story Crater Bot 2333310c38 fix(argocd): resolve 502 on argocd.riotpiao.com, dedupe Ingress and TLS mode mismatch
argocd-server ran --insecure (plain HTTP :8080) while its Helm-managed
Ingress set ssl-passthrough: true, which sends nginx's raw TLS handshake
straight to the pod - HTTP server can't complete a TLS handshake, nginx
logged 502 (peer closed connection in SSL handshake). Compounded by a
second, conflicting Ingress for the same host in
k8s/bootstrap/ingress/ingress.yaml - two Ingress objects on one host is
undefined nginx routing behavior. Disabled the Helm-managed Ingress
(enabled: false) so ingress.yaml's passthrough Ingress is the sole
source of truth, and set server.insecure: false so argocd-server
actually terminates TLS itself, matching passthrough's requirement.
2026-08-11 21:03:59 -07:00
Story Crater Bot be16020878 fix(argocd): use comma-separated include list, not brace expansion
ArgoCD directory.include uses Go filepath.Match glob syntax, not shell
brace expansion - {a,b,c} silently matched nothing, only the original 2
files stayed tracked.
2026-08-11 20:51:05 -07:00