Fix mistakes identified via LLM analysis #11
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/llm-analysis-experiment"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
This was my first attempt at using a LLM as an analysis tool to identify mistakes, then fixing them myself manually.
Thoughts
It did give some results, as you can see from the commits in this PR.
The whole pass expended 1.58€ of the free credit included as part of my JetBrains All Products pack.
I feel would be very much not worth it if it wasn't included for free or if I had to spend more to get an IDE with it included.
This is reinforcing my convinction that the only things LLMs are good at are highlighting design issues in existing tooling.
LLM info
Prompt
The prompt was:
External calls
No requests to external calls or websites were made.
Full response
Claude Agent output
I'll explore the repository structure first to understand what's here.
Let me read through the manifests.
Now the workloads themselves.
Let me verify a few specific things I noticed.
The setter-marker mismatch is confirmed. A couple more quick checks:
I've read through the whole repo. Overall this is a well-organized Flux setup — the
ArtifactGeneratormonorepo split is a clever use of Flux 2.8, the naming convention is consistent, and the inline comments explaining why (rate limits, OIDC linking, dead services) are better than most infra repos. Here's what I'd change, roughly in priority order.Actual bugs
1. Three image-automation setters point at ImagePolicies that don't exist. The marker name must match the ImagePolicy's
metadata.name, and these don't:starshard-synapse:livekitapplications/starshard-synapse/synapse-livekit-jwt.Deployment.yaml:22synapse-livekit-jwtsteffo-porobot:discordapplications/steffo-porobot/porobot-discord.Deployment.yaml:21porobot-discordsteffo-porobot:telegramapplications/steffo-porobot/porobot-telegram.Deployment.yaml:21porobot-telegramThose three images are silently frozen. The lk-jwt-service one is the worst: it's pinned to
:latest, so it's both un-updated by Flux and un-reproducible —:latestdefaults toimagePullPolicy: Always, so the running version changes on every pod restart with nothing in git recording it. (Commit8ce8699"Fixporobot-discordImagePolicy" looks like an attempt at this that fixed the policy side but not the marker.)2. Namespaces in metadata that contradict the directory:
applications/steffo-porobot/imgproxy.Service.yaml:5→starshard-porobotapplications/starshard-soju/sojuauth.ImagePolicy.yaml:5andsojuauth-redis.ImagePolicy.yaml:5→starshard-sojuauthThese are currently harmless because
kustomization.yaml'snamespace:field overrides them, but that means the repo silently disagrees with itself and you can't trustmetadata.namespacewhen reading a file. I'd either fix them or dropmetadata.namespacefrom resources entirely and let the kustomization be the single source.3.
letsencrypt-production-tlsserverreuses the shortlived issuer's ACME account key (resources/letsencrypt-production-tlsserver.ClusterIssuer.yaml:15, same in the staging variant). Reusing one ACME account across two issuers mostly works, but it's clearly copy-paste rather than intent, and the two issuers can thrash the account registration if their email/config ever diverge. Give each its ownprivateKeySecretRef.4.
livekit.HelmRelease.yaml:24pinsversion: "*". Any chart release ships straight to prod unreviewed. Everything else in the repo uses a bounded range (^1.20.2,^84.5.0) — this should too.Biggest real risks
5. No backups anywhere. Six CNPG
Clusters with no.spec.backup— no WAL archiving, no PITR, noScheduledBackup. Every PVC islocal-path, i.e. a directory on the single node's disk. Synapse's media, GoToSocial's uploads, Forgejo's 10Gi of repos, Tranquil's blobs, and all six databases live on one disk with no copy. One disk failure or one badforce: truereconcile and it's gone. This is the single highest-value thing to add: CNPG barman-cloud object storage backups pointed at any off-node S3-compatible target, plus something like VolSync or a restic CronJob for the PVCs.6. Secrets are entirely outside GitOps.
.gitignoreexcludes*.Secret.yaml, and no kustomization references one — so they're applied by hand and exist nowhere but the live cluster and your laptop. You can't rebuild this cluster from the repo. SOPS with age is the natural fit (Flux decrypts natively viaspec.decryption) and would let the Secret files live in the kustomizations alongside everything else.7. Flux's git remote is inside the cluster Flux manages.
flux.FluxInstance.yamlsyncs fromg.starshard.space, which is the Forgejo running instarshard-forgejo. If the cluster is down or Forgejo is broken, Flux can't reconcile its way out. A push-mirror to an external forge, used as a fallback source, removes the circularity.8. No resource requests or limits on anything — not one Deployment, not one CNPG Cluster. On a single node this matters more than on a big cluster: every pod is BestEffort, so when the node hits memory pressure the kubelet's eviction ordering is essentially arbitrary. Synapse or Postgres can be killed to make room for a Discord bot. Even rough requests would establish a sane eviction order and give Grafana something to show.
9. No liveness or readiness probes on anything. This also quietly undermines your Kustomizations: they all set
wait: true, but without a readinessProbe a container counts as Ready the moment it starts, so Flux reports a successful reconcile for an app that's crash-looping on a bad config. Readiness probes would makewait: truemean what you want it to mean, and would stop Traefik routing to pods that haven't finished starting.10. No alerting on Flux failures.
notification-controlleris deployed but there are zeroProvider/Alertresources, so a failed reconcile is only visible if you go looking. You already run Synapse and a Discord bot — aProviderpointing at either, with anAlerton allKustomization/HelmReleasefailures, is maybe 20 lines.Reliability and operability
11. ConfigMap changes don't restart pods. Editing
synapse.ConfigMap.yaml'shomeserver.ymlupdates the ConfigMap and nothing else — the pod keeps running the old config until you manually restart it. Same for all theenvFromConfigMaps. Either switch to kustomizeconfigMapGenerator(the hash suffix forces a rollout, and it's the GitOps-native answer) or deploy Stakater Reloader.12. Cert renewal doesn't reach
soju.soju.Deployment.yamlmounts thei-certsecret directly — a cert owned by thesojuauthIngress. When cert-manager rotates it, soju keeps serving the old cert until someone restarts it. The same applies to LiveKit's TURN listener usinglivekit-cert. I suspect this is exactly why sojuauth uses thetlsserverprofile while everything else usesshortlived— a 6-day cert would break weekly. Reloader annotations would let you move soju toshortlivedtoo. Also worth noting the ownership coupling: deleting or renaming thesojuauthIngress silently breaks soju's TLS.13.
force: trueon all eleven app Kustomizations. This tells kustomize-controller to delete-and-recreate any resource it can't patch due to an immutable field. On a Service that's a brief outage; on a PVC backed bylocal-path, it's data loss. I'd remove it and handle immutable-field conflicts deliberately when they come up.14. Prometheus has no persistent storage. You set
grafana.persistenceinkube-prometheus-stack.HelmRelease.yamlbut notprometheus.prometheusSpec.storageSpec, so Prometheus history lives in an emptyDir and vanishes on every restart. Alertmanager is also deployed with the chart's default null receiver, so nothing it fires goes anywhere.15. Nothing app-level is actually monitored. No
ServiceMonitororPodMonitorexists. imgproxy goes out of its way to expose a Prometheus port (IMGPROXY_PROMETHEUS_BIND, service port 8081) that nothing scrapes, and CNPG'smonitoring.enablePodMonitor: trueis unset on all six clusters — so you have no Postgres metrics for the databases you have no backups of.16.
steffo-www's Postgres gets 256M. That's shared with WAL; Postgres will fill it. It's also the only storage value not inGi.17. No CI validation. Every bug in the first section — the marker mismatches, the namespace typos — is something a
kustomize build+kubeconformcheck on push would have caught. You're already running Forgejo, so Forgejo Actions is right there.flux build/flux diffon PRs would go further.Security hardening
18. No
securityContextanywhere — norunAsNonRoot,readOnlyRootFilesystem,allowPrivilegeEscalation: false, or capability drops, and no Pod Security Standards labels on the namespaces. Addingpod-security.kubernetes.io/enforce: baseline(orrestrictedwhere it fits) to the Namespace manifests is a cheap first step.19. No NetworkPolicies. k3s enforces them out of the box, but there are none, so any compromised pod can reach every Postgres in every namespace. A default-deny per namespace plus explicit allows would be a meaningful improvement given the per-app namespace layout you already have. Note
FluxInstance.cluster.networkPolicy: trueonly protectsflux-system.The client-IP FIXME
gotosocial.ConfigMap.yamldisables rate limiting entirely withGTS_ADVANCED_RATE_LIMIT_REQUESTS: "0"and a FIXME about real IPs — andcontrollers/traefik/traefik.HelmChartConfig.yamlis an emptyvaluesContent: ""left over from the fight in commits b495748/7f5649b. The missing piece is that k3s's ServiceLB SNATs the source address, so Traefik never sees the real client. Two changes:then tell GoToSocial to trust Traefik's
X-Forwarded-For— the same thing Forgejo already does withREVERSE_PROXY_TRUSTED_PROXIES: 10.42.0.0/16. GoToSocial's equivalent isGTS_TRUSTED_PROXIESset to the pod CIDR; worth confirming the exact key against the GoToSocial docs for your version before applying. With real IPs arriving you can restore rate limiting and dropGTS_LOG_LEVEL: debug(commit3a5ffbb) back toinfo.Housekeeping
applications/starshard-prosodyis a namespace and nothing else, yet carries a full Kustomization and ArtifactGenerator entry.steffo-nortromandsteffo-threadkillerare annotated "it's dead" in their own ImageRepositories but still deployed. Either finish or delete.ImageUpdateAutomations clone the repo on 5m intervals and all push tomain; that's a few thousand clones a day against Forgejo and occasional non-fast-forward retries. Consolidation isn't clean (IUA only reads ImagePolicies in its own namespace), but the 5m interval on apps whose ImageRepository only scans every 24h is pure waste — match those to 24h.controllersreconciles at 24h andresourcesat 12h, so a ClusterIssuer edit can sit unapplied for half a day. AReceiver+ Forgejo webhook would make all of it push-driven and let you keep the long intervals as a safety net..idea/is committed, butworkspace.xmlis correctly excluded by.idea/.gitignore, so that's fine as-is.If you want, I can start on any of these — I'd suggest the four bugs in the first section first, since they're small and self-contained, then the CNPG backups.
$imagepolicynames 77d28bee5aprivateKeySecretRefkey name 9f0eb317d21.*454d30598eWIP: Fix mistakes identified via LLM analysisto Fix mistakes identified via LLM analysis