feat(quoter-bot): add helm chart for kubernetes self-hosting - #164
feat(quoter-bot): add helm chart for kubernetes self-hosting#164julien-devatom wants to merge 3 commits into
Conversation
Ship a package-owned Helm chart at bots/quoter-bot/helm/quoter-bot so Kubernetes operators can self-host the public morphoorg/quoter:latest image from one values file: classic workload parameters (image, resources, persistence, scheduling, security contexts) next to a config mapping in the bot's native YAML schema, rendered verbatim into a Secret and passed via --config. The chart encodes the bot's runtime constraints as defaults: subPath config mount (the loader opens with O_NOFOLLOW and rejects the symlinks a plain Secret volume exposes) with a checksum annotation to roll the pod on config change; direct non-root node execution with fsGroup-managed volume ownership instead of the root-only Railway entrypoint (state files are checked against the process uid); one replica with a Recreate strategy for the singleton writer; a state PVC kept on uninstall because losing offer-group ownership fails readiness; and a 600s termination grace period so shutdown cleanup can invalidate owned groups and wait for receipts. Promote Helm as the recommended Kubernetes path in the bot README (local chart install; the chart is not published to a registry yet), document parameters and secret-handling options in the chart README, record the decisions in a TIB, extend the CLAUDE.md operator-surface enumeration, and ignore Go-templated YAML in oxfmt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@prd-carapulse[bot] can you review and approve if all good |
|
@codex review |
|
✅ Approved: #164 (review) • Review: no blocking findings |
There was a problem hiding this comment.
Reviewed chart templates, runtime/security assumptions, persistence/config variants, and docs. helm lint passed; default, managed-config, existing-secret, existing-claim, and ephemeral-state renders all parsed successfully. CI is green (Test, Typecheck, Dead-Code, Lint, Notify). No blocking findings.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 43afb4049d
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Fix the config mount path, make the quickstart runnable, preserve selector labels, and add regression coverage.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dedd0c45dc
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Keep install notes aligned with the mounted config path, reserve the config checksum annotation, and preserve strict restored state-file modes with a chown-only init container.
|
@codex review |
|
Codex Review: Didn't find any major issues. Another round soon, please! Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
Why
TIB-2026-08-14-quoter-bot-dockerhub-publishing gave third-party operators a public image (
morphoorg/quoter) but no deployment recipe: a Kubernetes operator has to hand-write manifests and rediscover the bot's non-obvious runtime constraints — a config loader that rejects symlinked files, state files checked against the process uid, an image entrypoint that requires root, singleton nonce semantics, and a shutdown sequence that legitimately takes minutes. Each fails loud but late. This PR ships a package-owned Helm chart that encodes those constraints as defaults, so onevalues.yamlfully describes a correct deployment.What
bots/quoter-bot/helm/quoter-bot— chart targetingmorphoorg/quoter:latest(pinnable to immutable commit tags). One values file carries classic workload parameters (image, cpu/memory resources, persistence, scheduling, security contexts) next to aconfigmapping in the bot's native YAML schema, rendered verbatim into a Secret and passed via--config; the bot's own startup validation stays the single source of truth (novalues.schema.json).bots/quoter-bot/test/helm/chart.test.ts(vitest) locks in the review-driven invariants below..oxfmtrc.json— ignores**/helm/**/templates/**: oxfmt cannot parse Go-templated YAML (pnpm formaterrored before this).Chart.yaml/values.yaml/chart README stay formatter-covered.AGENTS.md/.cursorrulesare symlinks, so they follow).Design notes for review
/repo/bots/quoter-bot/quoter-bot.yaml: the loader opens config withO_RDONLY|O_NONBLOCK|O_NOFOLLOW(config-source.utils.ts), so the symlinks a plain Secret volume exposes would fail startup;subPathbind-mounts a regular file, under a parent directory that exists in the image (also the bot's default-discovery filename in its workdir). Since subPath mounts never see Secret updates, a reservedchecksum/configpod annotation (protected frompodAnnotationscollisions) rolls the pod on upgrade;existingConfigSecretusers get a documentedkubectl rollout restart.railway-entrypoint.sh: the image CMD needs root (chown + setpriv). The chart execsnode dist/src/index.jsdirectly as uid/gid 1000, with a chown-only root init container (default-on, mirrors the Railway entrypoint) fixing state-volume ownership. Deliberately nofsGroup: kubelet's fsGroup handling can chmod restored ownership-state files beyond0600, which the bot's state readers correctly reject (mode & 0o077check) — a restored claim would crashloop before cleanup. Hardened defaults otherwise (read-only rootfs + emptyDir/tmp, no capabilities, no SA token).replicas: 1hardcoded +Recreatestrategy (nonce cursor, serialized mutation queue, and ownership state are per-instance); state PVC kept on uninstall by default because deleting ownership state fails readiness for previously bot-issued groups;terminationGracePeriodSeconds: 600so SIGTERM cleanup (invalidate owned groups, wait for receipts) is not SIGKILLed at the k8s 30s default.env/envFromcarryMAKER_PRIVATE_KEYand the env-onlyBETTERSTACK_*values;existingConfigSecretkeeps the whole file out of Helm release storage. Selector labels and the config checksum are reserved againstpodLabels/podAnnotationsoverrides.Verification
helm lintclean; renders verified for default values, a fully-populated values file,existingConfigSecret,persistence.enabled=false, andpersistence.existingClaimvariants.quoter-bot.yamlwas base64-decoded and loaded through the bot's ownConfigService.loadin read-only and write mode (env-injected signer resolved toprivate-key) — proving the values→Secret→--configround trip, including quoted big-integer preservation through Helm.pnpm format,pnpm lint, and full CI (Lint, Typecheck, Test, Dead-Code) green on the latest commit.revieweragent (pre-commit) — no blocking issues, suggestions applied; carapulse APPROVED; codex — two rounds of findings (config mount parent, quickstart completeness, namespace ordering, podLabels/podAnnotations overrides, fsGroup-vs-state-file-modes) all fixed in follow-up commits, final verdict "Didn't find any major issues"; all 7 review threads resolved.🤖 Generated with Claude Code