mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
feat(deploy): add production Helm chart for Buzz (#990)
Signed-off-by: Tyler Longwell <tlongwell@squareup.com> Co-authored-by: npub1jmc9dt2lyvzu3h0kxlwxt5zg4fxp9476awyxw6gwxn72g6cw7exqs64whm <96f056ad5f2305c8ddf637dc65d048aa4c12d7daeb8867690e34fca46b0ef64c@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
co-authored by
npub1jmc9dt2lyvzu3h0kxlwxt5zg4fxp9476awyxw6gwxn72g6cw7exqs64whm
Eva
parent
aadbe67354
commit
629fb57bf0
@@ -0,0 +1,97 @@
|
||||
name: helm chart
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "deploy/charts/buzz/**"
|
||||
- ".github/workflows/helm-chart.yml"
|
||||
- "ct.yaml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "deploy/charts/buzz/**"
|
||||
- ".github/workflows/helm-chart.yml"
|
||||
- "ct.yaml"
|
||||
|
||||
jobs:
|
||||
lint-and-unittest:
|
||||
name: lint + unittest + render matrix
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1
|
||||
with:
|
||||
version: v3.16.4
|
||||
|
||||
- name: Install helm-unittest plugin
|
||||
run: helm plugin install --version 0.8.2 https://github.com/helm-unittest/helm-unittest
|
||||
|
||||
- name: Set up Python (for chart-testing)
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Set up chart-testing
|
||||
uses: helm/chart-testing-action@0d28d3144d3a25ea2cc349d6e59901c4ff469b3b # v2.7.0
|
||||
|
||||
- name: Build chart dependencies
|
||||
run: helm dependency build deploy/charts/buzz
|
||||
|
||||
- name: ct lint
|
||||
run: ct lint --config ct.yaml --all
|
||||
|
||||
- name: helm-unittest
|
||||
run: helm unittest deploy/charts/buzz
|
||||
|
||||
- name: helm template (render every fixture)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for f in deploy/charts/buzz/ci/*-values.yaml deploy/charts/buzz/tests/fixtures/*-values.yaml; do
|
||||
echo "::group::render $f"
|
||||
helm template buzz deploy/charts/buzz -f "$f"
|
||||
echo "::endgroup::"
|
||||
done
|
||||
|
||||
install-on-kind:
|
||||
# Full end-to-end install requires the public ghcr.io/block/buzz image to
|
||||
# exist and to embed Max's startup migrations. Runs only after Sami's
|
||||
# image PR merges (`workflow_dispatch`) or on a schedule once main carries
|
||||
# both prerequisites. Render/lint above is the per-PR signal.
|
||||
name: install on kind (gated)
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
needs: lint-and-unittest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1
|
||||
with:
|
||||
version: v3.16.4
|
||||
|
||||
- name: Set up Python (for chart-testing)
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Set up chart-testing
|
||||
uses: helm/chart-testing-action@0d28d3144d3a25ea2cc349d6e59901c4ff469b3b # v2.7.0
|
||||
|
||||
- name: Create kind cluster
|
||||
uses: helm/kind-action@0025e74a8c7512023d06dc019c617aa3cf561fde # v1.10.0
|
||||
with:
|
||||
version: v0.24.0
|
||||
node_image: kindest/node:v1.31.0
|
||||
|
||||
- name: Build chart dependencies
|
||||
run: helm dependency build deploy/charts/buzz
|
||||
|
||||
- name: ct install (quickstart profile)
|
||||
run: ct install --config ct.yaml --charts deploy/charts/buzz --helm-extra-args "--timeout 600s"
|
||||
@@ -64,3 +64,6 @@ identity.key
|
||||
|
||||
# mesh-llm build cache
|
||||
.cache/
|
||||
|
||||
# Helm dependency tarballs — regenerable from Chart.lock via `helm dependency build`
|
||||
deploy/charts/*/charts/*.tgz
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
chart-dirs:
|
||||
- deploy/charts
|
||||
chart-repos: []
|
||||
helm-extra-args: --timeout 600s
|
||||
target-branch: main
|
||||
# Skip OCI subchart fetches that need docker.io anonymous access — ct
|
||||
# resolves these locally via `helm dependency build` in the workflow.
|
||||
remote: origin
|
||||
validate-maintainers: false
|
||||
@@ -0,0 +1,9 @@
|
||||
dependencies:
|
||||
- name: postgres
|
||||
repository: oci://registry-1.docker.io/cloudpirates
|
||||
version: 0.19.5
|
||||
- name: redis
|
||||
repository: oci://registry-1.docker.io/cloudpirates
|
||||
version: 0.30.3
|
||||
digest: sha256:9c0df32008f782064104ecec8552f348841f120852d49ace66966a56ba904348
|
||||
generated: "2026-06-11T15:45:41.764379-04:00"
|
||||
@@ -0,0 +1,42 @@
|
||||
apiVersion: v2
|
||||
name: buzz
|
||||
description: |
|
||||
Buzz — a Nostr-based messaging platform for human–agent collaboration.
|
||||
|
||||
A single relay binary serving WebSocket + REST + web UI, backed by
|
||||
PostgreSQL, Redis, and Typesense. Configurable for single-node evaluation
|
||||
(subcharts on) and HA production (external services, existingSecret).
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: "0.1.0"
|
||||
home: https://github.com/block/buzz
|
||||
sources:
|
||||
- https://github.com/block/buzz
|
||||
keywords:
|
||||
- nostr
|
||||
- relay
|
||||
- messaging
|
||||
- websocket
|
||||
- chat
|
||||
maintainers:
|
||||
- name: Block
|
||||
url: https://github.com/block
|
||||
annotations:
|
||||
artifacthub.io/changes: |
|
||||
- kind: added
|
||||
description: Initial chart for Buzz relay.
|
||||
artifacthub.io/license: Apache-2.0
|
||||
|
||||
# Optional eval-only subcharts. Production deploys disable both and point
|
||||
# externalPostgresql / externalRedis (or secrets.existingSecret) at managed
|
||||
# services.
|
||||
dependencies:
|
||||
- name: postgres
|
||||
version: "0.19.x"
|
||||
repository: oci://registry-1.docker.io/cloudpirates
|
||||
condition: postgresql.enabled
|
||||
alias: postgresql
|
||||
- name: redis
|
||||
version: "0.30.x"
|
||||
repository: oci://registry-1.docker.io/cloudpirates
|
||||
condition: redis.enabled
|
||||
@@ -0,0 +1,112 @@
|
||||
# Buzz Helm Chart
|
||||
|
||||
[Buzz](https://github.com/block/buzz) is a Nostr-based messaging platform for human–agent collaboration: a single relay binary serving WebSocket + REST + web UI, backed by PostgreSQL, Redis, Typesense, and S3-compatible object storage.
|
||||
|
||||
This chart has two operating profiles selected by values:
|
||||
|
||||
| Profile | When | What you get |
|
||||
|---|---|---|
|
||||
| **Production** (default) | Self-hosted multi-tenant, regulated, or GitOps-managed | External managed Postgres/Redis/Typesense/S3, `secrets.existingSecret:`, no chart-side autogen, HA-capable (`replicaCount ≥ 2`) |
|
||||
| **Quickstart** (eval) | Eval, single-node, one-off demo | In-cluster Postgres + Redis + MinIO + Typesense subcharts/Deployments, chart auto-generates relay + service secrets, single replica |
|
||||
|
||||
## Quickstart (eval only)
|
||||
|
||||
```sh
|
||||
helm install buzz oci://ghcr.io/block/buzz/charts/buzz --version 0.1.0 \
|
||||
--create-namespace --namespace buzz \
|
||||
--set quickstart=true \
|
||||
--set postgresql.enabled=true \
|
||||
--set redis.enabled=true \
|
||||
--set minio.enabled=true \
|
||||
--set typesense.enabled=true \
|
||||
--set relayUrl=wss://buzz.example.com \
|
||||
--set ownerPubkey=<64-char-hex-pubkey>
|
||||
```
|
||||
|
||||
This brings up **everything in-cluster** — Postgres, Redis, MinIO (with its
|
||||
bucket created by a post-install Job), and Typesense — and composes the relay's
|
||||
`BUZZ_S3_ENDPOINT` / `TYPESENSE_URL` plus autogenerated credentials
|
||||
automatically. No external services required. The `quickstart=true` flag is an
|
||||
intent marker surfaced in NOTES.txt; the bundled services are opted in via the
|
||||
four `*.enabled` flags above (see `ci/quickstart-values.yaml` for the exact set
|
||||
CI installs). Eval-only: every bundled service is a single replica with no HA.
|
||||
|
||||
## Production (GitOps)
|
||||
|
||||
The chart is designed for ArgoCD and Flux. Both render charts with `helm template`, in which mode Helm's `lookup` function returns empty — any chart-side `randAlphaNum` call would regenerate secrets on every sync. The chart-managed Secret path is **only** safe for `helm install` / `helm upgrade`.
|
||||
|
||||
Production deploys MUST use `secrets.existingSecret:`. The Secret is consumed for any keys present and ignored for keys missing — extras are harmless.
|
||||
|
||||
See:
|
||||
|
||||
- [`examples/argocd-app.yaml`](examples/argocd-app.yaml) — ArgoCD Application
|
||||
- [`examples/flux-helmrelease.yaml`](examples/flux-helmrelease.yaml) — Flux HelmRelease v2
|
||||
- [`examples/secret-sample.yaml`](examples/secret-sample.yaml) — Secret schema
|
||||
|
||||
## Required inputs
|
||||
|
||||
| Key | What | When required |
|
||||
|---|---|---|
|
||||
| `relayUrl` | Public `wss://` URL clients connect to | Always |
|
||||
| `ownerPubkey` | 64-char lowercase hex Nostr pubkey of the relay operator | When `relay.requireRelayMembership=true` (default) |
|
||||
| `secrets.existingSecret` | Name of pre-created Secret | Production / GitOps |
|
||||
| `externalPostgresql.url` / `externalRedis.url` / `typesense.url` / `s3.endpoint` | External service URLs | Production — when the matching bundled service is disabled (the default) |
|
||||
|
||||
The chart fails at `helm install` / `helm template` time with a clear message if any of these are missing or malformed (see `templates/_validate.tpl`).
|
||||
|
||||
## HA (production)
|
||||
|
||||
`replicaCount > 1` hard-requires both:
|
||||
|
||||
- Redis (`redis.enabled=true`, `externalRedis.url`, or `REDIS_URL` in `existingSecret`) — for `buzz-pubsub` fan-out
|
||||
- ReadWriteMany git PVC — `persistence.git.accessMode: ReadWriteMany` with a RWX storage class (e.g. `efs-sc` on AWS, `azurefile-csi` on Azure)
|
||||
|
||||
The chart **template-fails** if either invariant is broken. No silent degradation.
|
||||
|
||||
## Upgrades
|
||||
|
||||
Schema migrations are embedded in the relay binary via `sqlx::migrate!` and run at startup, gated by `BUZZ_AUTO_MIGRATE` (default `true`). Multiple replicas race-safely behind a Postgres advisory lock. `helm upgrade` is the entire upgrade procedure.
|
||||
|
||||
If you prefer decoupling migrations from serving, set `migrate.autoMigrate=false`. **In that mode the chart does not run migrations for you** — you own running `buzz-admin migrate` (separate Pod / one-shot Job) against the database before every `helm install` / `helm upgrade`. Readiness probes only verify DB connectivity, not schema freshness, so a pod will appear healthy against an unmigrated schema and fail under load. A pre-upgrade Helm Job for this is on the chart roadmap; the values knob `migrate.preUpgradeJob.enabled` is reserved.
|
||||
|
||||
## Backups
|
||||
|
||||
Save these. Losing any of them is data loss. See NOTES.txt printed by `helm install` for the live list:
|
||||
|
||||
1. `BUZZ_RELAY_PRIVATE_KEY` — relay identity. Rotating it = new identity (federation peers will not recognize the relay).
|
||||
2. PostgreSQL database — the canonical event store.
|
||||
3. S3 bucket — media blobs (chart default bucket: `buzz-media`).
|
||||
4. Git PVC — repo on-disk state served by the relay's git endpoint.
|
||||
5. Owner private key — held by the operator, not by this chart. Restore by re-installing with the same `ownerPubkey`.
|
||||
|
||||
## Honest limitations (v1)
|
||||
|
||||
- **Bundled MinIO + Typesense are eval-only.** The quickstart profile runs an
|
||||
in-cluster MinIO and Typesense (single replica, no HA, `lookup`-autogenerated
|
||||
credentials) so the relay starts with zero external services. Production
|
||||
leaves `minio.enabled` / `typesense.enabled` off and points `s3.endpoint` +
|
||||
`typesense.url` (or `BUZZ_S3_*` / `TYPESENSE_URL` in `existingSecret`) at
|
||||
managed S3-compatible storage and Typesense. The bundled Deployments are not
|
||||
GitOps-safe and are not intended for production traffic.
|
||||
- **Minimal-mode is not yet supported.** The relay's `BUZZ_PUBSUB=local` /
|
||||
`BUZZ_SEARCH=pg` / filesystem media paths are upstream work in progress —
|
||||
even quickstart currently stands up real Redis, Typesense, and S3 rather than
|
||||
the relay's single-node fallbacks.
|
||||
- **OCI publish to GHCR + cosign signing** is a follow-up PR. For now, install the chart from source: `helm install buzz ./deploy/charts/buzz` after cloning the repo.
|
||||
|
||||
## Development
|
||||
|
||||
```sh
|
||||
# Render every fixture
|
||||
for f in ci/*-values.yaml tests/fixtures/*-values.yaml; do
|
||||
helm template buzz . -f "$f" >/dev/null && echo "ok: $f"
|
||||
done
|
||||
|
||||
# Unit tests
|
||||
helm plugin install https://github.com/helm-unittest/helm-unittest
|
||||
helm unittest .
|
||||
|
||||
# Lint
|
||||
helm dependency build .
|
||||
ct lint --config ../../../ct.yaml --charts .
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
# Quickstart / eval: subcharts on, autogen secrets, single replica.
|
||||
# This is the scenario `ct install` exercises against a kind cluster — it
|
||||
# spins up postgres + redis + minio + typesense in-cluster so the relay can
|
||||
# actually start and pass its S3 conformance probe.
|
||||
quickstart: true
|
||||
postgresql:
|
||||
enabled: true
|
||||
redis:
|
||||
enabled: true
|
||||
minio:
|
||||
enabled: true
|
||||
typesense:
|
||||
enabled: true
|
||||
relayUrl: wss://buzz.test.local
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000001"
|
||||
relay:
|
||||
# Don't enforce membership in CI — we're testing the chart renders and the
|
||||
# Pod starts, not relay business logic.
|
||||
requireRelayMembership: false
|
||||
podDisruptionBudget:
|
||||
enabled: false
|
||||
@@ -0,0 +1,76 @@
|
||||
# ArgoCD Application — GitOps-safe Buzz install.
|
||||
#
|
||||
# Prerequisite: a Secret named `buzz-secrets` in namespace `buzz` containing
|
||||
# (any subset of) the keys consumed by `secrets.existingSecret`. See
|
||||
# `secret-sample.yaml` for the schema.
|
||||
#
|
||||
# Why `existingSecret` and not chart autogen: ArgoCD renders manifests with
|
||||
# `helm template`, in which mode Helm's `lookup` function returns empty and
|
||||
# any chart-side `randAlphaNum` call regenerates on every sync. The
|
||||
# chart-managed Secret path is for `helm install` / `helm upgrade` only.
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: buzz
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: oci://ghcr.io/block/buzz/charts
|
||||
chart: buzz
|
||||
targetRevision: 0.1.0
|
||||
helm:
|
||||
releaseName: buzz
|
||||
values: |
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000" # replace
|
||||
replicaCount: 3
|
||||
|
||||
secrets:
|
||||
existingSecret: buzz-secrets
|
||||
|
||||
externalPostgresql:
|
||||
# DATABASE_URL also lives in buzz-secrets; this is here only if you
|
||||
# prefer the URL stored in values vs. the Secret. Pick one.
|
||||
url: ""
|
||||
|
||||
externalRedis:
|
||||
url: ""
|
||||
|
||||
typesense:
|
||||
url: "http://typesense.buzz.svc.cluster.local:8108"
|
||||
# apiKey lives in buzz-secrets
|
||||
|
||||
s3:
|
||||
endpoint: "https://s3.us-east-1.amazonaws.com"
|
||||
bucket: "buzz-media"
|
||||
# accessKey / secretKey live in buzz-secrets
|
||||
|
||||
persistence:
|
||||
git:
|
||||
enabled: true
|
||||
accessMode: ReadWriteMany # required: replicaCount > 1
|
||||
storageClass: efs-sc # provider-specific RWX class
|
||||
size: 50Gi
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
# WebSocket: long-lived; raise proxy timeouts.
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
||||
tls:
|
||||
- hosts: [buzz.example.com]
|
||||
secretName: buzz-tls
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: buzz
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,64 @@
|
||||
# Flux HelmRelease — GitOps-safe Buzz install.
|
||||
#
|
||||
# Prerequisite: a Secret named `buzz-secrets` in namespace `buzz`. See
|
||||
# `secret-sample.yaml`. Flux renders the chart server-side via the
|
||||
# helm-controller, which (like ArgoCD) treats chart-side `randAlphaNum`
|
||||
# autogen as non-idempotent. `existingSecret` is the only safe path.
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: buzz
|
||||
namespace: buzz
|
||||
spec:
|
||||
type: oci
|
||||
url: oci://ghcr.io/block/buzz/charts
|
||||
interval: 10m
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: buzz
|
||||
namespace: buzz
|
||||
spec:
|
||||
interval: 10m
|
||||
chart:
|
||||
spec:
|
||||
chart: buzz
|
||||
version: "0.1.0"
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: buzz
|
||||
values:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000" # replace
|
||||
replicaCount: 3
|
||||
|
||||
secrets:
|
||||
existingSecret: buzz-secrets
|
||||
|
||||
typesense:
|
||||
url: "http://typesense.buzz.svc.cluster.local:8108"
|
||||
# apiKey lives in buzz-secrets
|
||||
|
||||
s3:
|
||||
endpoint: "https://s3.us-east-1.amazonaws.com"
|
||||
bucket: "buzz-media"
|
||||
|
||||
persistence:
|
||||
git:
|
||||
enabled: true
|
||||
accessMode: ReadWriteMany
|
||||
storageClass: efs-sc
|
||||
size: 50Gi
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
||||
tls:
|
||||
- hosts: [buzz.example.com]
|
||||
secretName: buzz-tls
|
||||
@@ -0,0 +1,69 @@
|
||||
# Ingress + automatic TLS via cert-manager (Let's Encrypt HTTP-01).
|
||||
#
|
||||
# This file is BOTH:
|
||||
# 1. A values fragment for the chart (the `ingress:` block) — pass with `-f`.
|
||||
# 2. A ClusterIssuer manifest at the bottom — apply ONCE per cluster with
|
||||
# `kubectl apply -f`. The chart does not manage it.
|
||||
#
|
||||
# Helm's value-file parser reads ONLY the first YAML document; the second
|
||||
# document (the ClusterIssuer) is ignored by Helm. That is intentional — the
|
||||
# ClusterIssuer is cluster-scoped and outlives any single release.
|
||||
#
|
||||
# Prerequisite: cert-manager installed in the cluster. The chart does not
|
||||
# depend on it — that is a cluster operator decision.
|
||||
#
|
||||
# helm install cert-manager cert-manager \
|
||||
# --repo https://charts.jetstack.io \
|
||||
# --namespace cert-manager --create-namespace \
|
||||
# --set crds.enabled=true
|
||||
#
|
||||
# Apply this file in two passes:
|
||||
#
|
||||
# # 1. Install the cluster-scoped ClusterIssuer (idempotent):
|
||||
# kubectl apply -f deploy/charts/buzz/examples/ingress-cert-manager.yaml
|
||||
#
|
||||
# # 2. Install/upgrade the chart with the values fragment:
|
||||
# helm upgrade --install buzz ./deploy/charts/buzz \
|
||||
# -f values-production.yaml \
|
||||
# -f deploy/charts/buzz/examples/ingress-cert-manager.yaml \
|
||||
# --set relayUrl=wss://buzz.example.com
|
||||
#
|
||||
# Why HTTP-01: works for any public-DNS host without DNS-API credentials.
|
||||
# Switch to DNS-01 if your relay is on a private/split-DNS host or you want
|
||||
# wildcard certs.
|
||||
|
||||
# ── Values fragment (Helm reads this document) ───────────────────────────────
|
||||
ingress:
|
||||
enabled: true
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
# Long-lived WebSocket connections — generous timeouts.
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
||||
hosts:
|
||||
- host: buzz.example.com # replace; must match relayUrl host
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- hosts:
|
||||
- buzz.example.com # replace
|
||||
secretName: buzz-tls # cert-manager creates this Secret
|
||||
|
||||
---
|
||||
# ── ClusterIssuer (kubectl apply, NOT consumed by Helm) ──────────────────────
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: letsencrypt-prod
|
||||
spec:
|
||||
acme:
|
||||
server: https://acme-v02.api.letsencrypt.org/directory
|
||||
email: ops@example.com # replace
|
||||
privateKeySecretRef:
|
||||
name: letsencrypt-prod-account-key
|
||||
solvers:
|
||||
- http01:
|
||||
ingress:
|
||||
class: nginx # match your ingress controller's ingressClass
|
||||
@@ -0,0 +1,30 @@
|
||||
# Sample Secret matching `secrets.existingSecret: buzz-secrets`.
|
||||
#
|
||||
# Manage this via SealedSecrets / SOPS / External Secrets / Vault — anything
|
||||
# that keeps the unencrypted form out of git. The chart reads keys it finds
|
||||
# and leaves the rest as Pod env vars marked `optional: true`.
|
||||
#
|
||||
# Keys consumed by the relay (all optional unless required by relay config):
|
||||
# BUZZ_RELAY_PRIVATE_KEY — 64-char hex; relay identity (do NOT rotate)
|
||||
# BUZZ_GIT_HOOK_HMAC_SECRET — 32+ chars; required when replicaCount > 1
|
||||
# DATABASE_URL — postgres://...
|
||||
# REDIS_URL — redis://... (required when replicaCount > 1)
|
||||
# TYPESENSE_URL
|
||||
# TYPESENSE_API_KEY
|
||||
# BUZZ_S3_ACCESS_KEY
|
||||
# BUZZ_S3_SECRET_KEY
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: buzz-secrets
|
||||
namespace: buzz
|
||||
type: Opaque
|
||||
stringData:
|
||||
BUZZ_RELAY_PRIVATE_KEY: "REPLACE_WITH_64_HEX"
|
||||
BUZZ_GIT_HOOK_HMAC_SECRET: "REPLACE_WITH_RANDOM_64_CHARS"
|
||||
DATABASE_URL: "postgres://buzz:REPLACE@postgres.buzz.svc.cluster.local:5432/buzz?sslmode=require"
|
||||
REDIS_URL: "redis://:REPLACE@redis.buzz.svc.cluster.local:6379"
|
||||
TYPESENSE_URL: "http://typesense.buzz.svc.cluster.local:8108"
|
||||
TYPESENSE_API_KEY: "REPLACE"
|
||||
BUZZ_S3_ACCESS_KEY: "REPLACE"
|
||||
BUZZ_S3_SECRET_KEY: "REPLACE"
|
||||
@@ -0,0 +1,98 @@
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
Buzz {{ .Chart.AppVersion }} — release "{{ .Release.Name }}" (namespace {{ .Release.Namespace }})
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
▶ Relay URL
|
||||
{{ .Values.relayUrl }}
|
||||
|
||||
▶ Owner pubkey
|
||||
{{ .Values.ownerPubkey }}
|
||||
{{- if not .Values.ownerPubkey }}
|
||||
⚠ ownerPubkey is empty — this is only valid when relay.requireRelayMembership=false.
|
||||
{{- end }}
|
||||
|
||||
▶ Health
|
||||
kubectl -n {{ .Release.Namespace }} port-forward svc/{{ include "buzz.fullname" . }} 8080:{{ .Values.service.healthPort }}
|
||||
curl http://localhost:8080/_readiness
|
||||
|
||||
{{ if not .Values.ingress.enabled }}{{ if not .Values.httproute.enabled }}
|
||||
▶ Networking
|
||||
Neither ingress nor Gateway API HTTPRoute is enabled. Expose the relay
|
||||
through your own gateway, then ensure clients reach .Values.relayUrl
|
||||
({{ .Values.relayUrl }}) over wss://. Long-lived WebSocket connections
|
||||
require generous proxy read/send timeouts (≥ 1h).
|
||||
{{ end }}{{ end }}
|
||||
|
||||
──────────────────────────────────────────────────────────────────────────────
|
||||
Profile
|
||||
──────────────────────────────────────────────────────────────────────────────
|
||||
{{ if or .Values.postgresql.enabled .Values.redis.enabled .Values.minio.enabled .Values.typesense.enabled }}
|
||||
⚠ QUICKSTART / EVALUATION PROFILE
|
||||
{{ if .Values.postgresql.enabled }}- In-cluster Postgres subchart (CloudPirates){{ end }}
|
||||
{{ if .Values.redis.enabled }}- In-cluster Redis subchart (CloudPirates){{ end }}
|
||||
{{ if .Values.minio.enabled }}- In-cluster MinIO (eval-only, single replica; bucket "{{ .Values.s3.bucket }}" created by post-install Job){{ end }}
|
||||
{{ if .Values.typesense.enabled }}- In-cluster Typesense (eval-only, single replica){{ end }}
|
||||
- Chart auto-generates secrets via the `lookup` pattern. This is NOT
|
||||
GitOps-safe — secrets will silently rotate under ArgoCD/Flux. For
|
||||
production, see examples/argocd-app.yaml or examples/flux-helmrelease.yaml.
|
||||
|
||||
{{ else }}
|
||||
✓ PRODUCTION PROFILE
|
||||
External Postgres, Redis (if enabled), Typesense, S3.
|
||||
{{ if .Values.secrets.existingSecret }}- Secrets sourced from: {{ .Values.secrets.existingSecret }}{{ end }}
|
||||
{{ end }}
|
||||
|
||||
──────────────────────────────────────────────────────────────────────────────
|
||||
Backups — save these
|
||||
──────────────────────────────────────────────────────────────────────────────
|
||||
1. BUZZ_RELAY_PRIVATE_KEY — relay identity. Rotating it = identity change;
|
||||
federation peers will treat the relay as a new identity.
|
||||
2. PostgreSQL database{{ if .Values.postgresql.enabled }} ({{ .Release.Name }}-postgresql PVC){{ end }}
|
||||
3. S3 bucket "{{ .Values.s3.bucket }}" — media blobs
|
||||
4. Git PVC ({{ include "buzz.fullname" . }}-git) — repo on-disk state
|
||||
5. Owner private key (held by the operator, NOT the chart) — restore by
|
||||
re-installing with the same ownerPubkey.
|
||||
|
||||
──────────────────────────────────────────────────────────────────────────────
|
||||
Degradation warnings
|
||||
──────────────────────────────────────────────────────────────────────────────
|
||||
{{- if not .Values.relay.requireAuthToken }}
|
||||
⚠ relay.requireAuthToken=false — REST API bypasses token auth. Production
|
||||
should set this to true.
|
||||
{{- end }}
|
||||
{{- if not .Values.relay.requireRelayMembership }}
|
||||
⚠ relay.requireRelayMembership=false — relay is OPEN. Anyone can publish.
|
||||
{{- end }}
|
||||
{{- if not .Values.migrate.autoMigrate }}
|
||||
⚠ migrate.autoMigrate=false — relay startup will NOT run sqlx migrations.
|
||||
You must run `buzz-admin migrate` against the database before every
|
||||
`helm install` / `helm upgrade`, or pods will start against an unmigrated
|
||||
schema. Readiness probes only verify DB connectivity, not schema freshness.
|
||||
{{- end }}
|
||||
{{- if or .Values.secrets.relayPrivateKey .Values.secrets.gitHookHmacSecret }}
|
||||
⚠ Inline secret values are set in values.yaml
|
||||
({{ if .Values.secrets.relayPrivateKey }}secrets.relayPrivateKey{{ end }}{{ if and .Values.secrets.relayPrivateKey .Values.secrets.gitHookHmacSecret }}, {{ end }}{{ if .Values.secrets.gitHookHmacSecret }}secrets.gitHookHmacSecret{{ end }}).
|
||||
Inline overrides leak secrets into git history and CI logs. Move them to a
|
||||
Kubernetes Secret and reference it via secrets.existingSecret — see
|
||||
examples/secret-sample.yaml.
|
||||
{{- end }}
|
||||
{{- if and (gt (.Values.replicaCount | int) 1) (eq .Values.persistence.git.accessMode "ReadWriteOnce") }}
|
||||
⚠ replicaCount > 1 with ReadWriteOnce git PVC will fail at template time
|
||||
(this message should never appear — file a bug).
|
||||
{{- end }}
|
||||
{{- if not .Values.secrets.existingSecret }}
|
||||
{{- if not (or .Values.postgresql.enabled .Values.redis.enabled) }}
|
||||
⚠ Chart-managed Secret is in use (no secrets.existingSecret). This is fine
|
||||
for `helm install` / `helm upgrade` but NOT safe under GitOps tools that
|
||||
`helm template` to render manifests — the `lookup` function returns empty
|
||||
in that mode and secrets will silently rotate. Use existingSecret for
|
||||
ArgoCD / Flux.
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
──────────────────────────────────────────────────────────────────────────────
|
||||
Useful commands
|
||||
──────────────────────────────────────────────────────────────────────────────
|
||||
kubectl -n {{ .Release.Namespace }} get pods -l app.kubernetes.io/instance={{ .Release.Name }}
|
||||
kubectl -n {{ .Release.Namespace }} logs -l app.kubernetes.io/instance={{ .Release.Name }} --tail=200
|
||||
kubectl -n {{ .Release.Namespace }} rollout status deployment/{{ include "buzz.fullname" . }}
|
||||
@@ -0,0 +1,126 @@
|
||||
{{/* Standard naming/labels helpers. */}}
|
||||
|
||||
{{- define "buzz.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "buzz.fullname" -}}
|
||||
{{- if .Values.fullnameOverride -}}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||
{{- if contains $name .Release.Name -}}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "buzz.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "buzz.labels" -}}
|
||||
helm.sh/chart: {{ include "buzz.chart" . }}
|
||||
{{ include "buzz.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
app.kubernetes.io/part-of: buzz
|
||||
{{- end -}}
|
||||
|
||||
{{- define "buzz.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "buzz.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Relay-specific selector: scopes the relay Deployment + Service so they do
|
||||
not also match the quickstart MinIO/Typesense pods, which share the base
|
||||
selectorLabels but carry their own component label. */}}
|
||||
{{- define "buzz.relaySelectorLabels" -}}
|
||||
{{ include "buzz.selectorLabels" . }}
|
||||
app.kubernetes.io/component: relay
|
||||
{{- end -}}
|
||||
|
||||
{{- define "buzz.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
{{- default (include "buzz.fullname" .) .Values.serviceAccount.name -}}
|
||||
{{- else -}}
|
||||
{{- default "default" .Values.serviceAccount.name -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "buzz.image" -}}
|
||||
{{- $tag := default .Chart.AppVersion .Values.image.tag -}}
|
||||
{{- printf "%s:%s" .Values.image.repository $tag -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Name of the chart-managed Secret holding relay-identity material and any
|
||||
chart-composed connection strings.
|
||||
*/}}
|
||||
{{- define "buzz.chartSecretName" -}}
|
||||
{{- printf "%s-relay" (include "buzz.fullname" .) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
The Secret name the relay should pull env from. If the operator supplied
|
||||
secrets.existingSecret, use that. Otherwise use the chart-managed one.
|
||||
*/}}
|
||||
{{- define "buzz.envSecretName" -}}
|
||||
{{- if .Values.secrets.existingSecret -}}
|
||||
{{- .Values.secrets.existingSecret -}}
|
||||
{{- else -}}
|
||||
{{- include "buzz.chartSecretName" . -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Host derived from relayUrl, used as ingress default + media domain. */}}
|
||||
{{- define "buzz.relayHost" -}}
|
||||
{{- $url := required "relayUrl is required: set --set relayUrl=wss://your.domain" .Values.relayUrl -}}
|
||||
{{- $stripped := $url | replace "wss://" "" | replace "ws://" "" | replace "https://" "" | replace "http://" "" -}}
|
||||
{{- first (splitList "/" $stripped) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Default media base URL: https://<host>/media derived from relayUrl. */}}
|
||||
{{- define "buzz.mediaBaseUrl" -}}
|
||||
{{- if .Values.mediaBaseUrl -}}
|
||||
{{- .Values.mediaBaseUrl -}}
|
||||
{{- else -}}
|
||||
{{- printf "https://%s/media" (include "buzz.relayHost" .) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Quickstart-only in-cluster service hostnames (eval profile). */}}
|
||||
{{- define "buzz.minioFullname" -}}
|
||||
{{- printf "%s-minio" (include "buzz.fullname" .) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "buzz.typesenseFullname" -}}
|
||||
{{- printf "%s-typesense" (include "buzz.fullname" .) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* In-cluster MinIO endpoint, used when minio.enabled and s3.endpoint unset. */}}
|
||||
{{- define "buzz.minioEndpoint" -}}
|
||||
{{- printf "http://%s.%s.svc.cluster.local:9000" (include "buzz.minioFullname" .) .Release.Namespace -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Effective S3 endpoint: explicit s3.endpoint wins, else bundled MinIO. */}}
|
||||
{{- define "buzz.s3Endpoint" -}}
|
||||
{{- if .Values.s3.endpoint -}}
|
||||
{{- .Values.s3.endpoint -}}
|
||||
{{- else if .Values.minio.enabled -}}
|
||||
{{- include "buzz.minioEndpoint" . -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* In-cluster Typesense URL, used when typesense.enabled and url unset. */}}
|
||||
{{- define "buzz.typesenseUrl" -}}
|
||||
{{- if .Values.typesense.url -}}
|
||||
{{- .Values.typesense.url -}}
|
||||
{{- else if .Values.typesense.enabled -}}
|
||||
{{- printf "http://%s.%s.svc.cluster.local:8108" (include "buzz.typesenseFullname" .) .Release.Namespace -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,64 @@
|
||||
{{/*
|
||||
Hard fail guards. Included from every rendered template so misconfigs
|
||||
surface at template time regardless of which manifest helm renders first.
|
||||
*/}}
|
||||
|
||||
{{- define "buzz.validate" -}}
|
||||
|
||||
{{/* relayUrl is required */}}
|
||||
{{- if not .Values.relayUrl -}}
|
||||
{{- fail "relayUrl is required: set --set relayUrl=wss://your.domain" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* replicaCount > 1 requires Redis */}}
|
||||
{{- if gt (.Values.replicaCount | int) 1 -}}
|
||||
{{- if and (not .Values.redis.enabled) (not .Values.externalRedis.url) (not .Values.secrets.existingSecret) -}}
|
||||
{{- fail (printf "replicaCount=%d requires Redis for buzz-pubsub. Enable redis.enabled=true, set externalRedis.url, or provide secrets.existingSecret with key REDIS_URL." (.Values.replicaCount | int)) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* replicaCount > 1 requires ReadWriteMany git storage */}}
|
||||
{{- if gt (.Values.replicaCount | int) 1 -}}
|
||||
{{- if and .Values.persistence.git.enabled (not .Values.persistence.git.existingClaim) -}}
|
||||
{{- if ne .Values.persistence.git.accessMode "ReadWriteMany" -}}
|
||||
{{- fail (printf "replicaCount=%d requires persistence.git.accessMode=ReadWriteMany (got %q). The relay's git on-disk state must be shared across replicas." (.Values.replicaCount | int) .Values.persistence.git.accessMode) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Owner pubkey required when requireRelayMembership */}}
|
||||
{{- if .Values.relay.requireRelayMembership -}}
|
||||
{{- if not .Values.ownerPubkey -}}
|
||||
{{- fail "ownerPubkey is required when relay.requireRelayMembership=true. Set ownerPubkey to the 64-char lowercase hex Nostr pubkey of the relay operator, or set relay.requireRelayMembership=false for an open relay." -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* ownerPubkey format check */}}
|
||||
{{- if .Values.ownerPubkey -}}
|
||||
{{- if not (regexMatch "^[0-9a-f]{64}$" .Values.ownerPubkey) -}}
|
||||
{{- fail (printf "ownerPubkey must be 64 lowercase hex characters (got %d chars; must match ^[0-9a-f]{64}$)." (len .Values.ownerPubkey)) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* ingress + httproute mutually exclusive */}}
|
||||
{{- if and .Values.ingress.enabled .Values.httproute.enabled -}}
|
||||
{{- fail "ingress.enabled and httproute.enabled cannot both be true — choose one." -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Postgres source must exist somewhere */}}
|
||||
{{- if not (or .Values.postgresql.enabled .Values.externalPostgresql.url .Values.secrets.existingSecret) -}}
|
||||
{{- fail "Postgres source missing: enable postgresql.enabled=true, set externalPostgresql.url, or provide secrets.existingSecret with key DATABASE_URL." -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Typesense source must exist somewhere */}}
|
||||
{{- if not (or .Values.typesense.enabled .Values.typesense.url .Values.secrets.existingSecret) -}}
|
||||
{{- fail "Typesense source missing: enable typesense.enabled=true (quickstart in-cluster), set typesense.url + typesense.apiKey, or provide secrets.existingSecret with keys TYPESENSE_URL + TYPESENSE_API_KEY." -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* S3 / object-storage source must exist somewhere (relay hard-fails its
|
||||
startup conformance probe without a reachable bucket). */}}
|
||||
{{- if not (or .Values.minio.enabled .Values.s3.endpoint .Values.secrets.existingSecret) -}}
|
||||
{{- fail "S3/object-storage source missing: enable minio.enabled=true (quickstart in-cluster), set s3.endpoint + s3.bucket + credentials, or provide secrets.existingSecret with keys BUZZ_S3_ACCESS_KEY + BUZZ_S3_SECRET_KEY. The relay runs a startup S3 conformance probe and exits if storage is unreachable." -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,184 @@
|
||||
{{- include "buzz.validate" . -}}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "buzz.fullname" . }}
|
||||
labels:
|
||||
{{- include "buzz.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "buzz.relaySelectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "buzz.relaySelectorLabels" . | nindent 8 }}
|
||||
{{- with .Values.relay.podLabels }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
annotations:
|
||||
# Roll pods when the chart-managed Secret changes.
|
||||
checksum/secret: {{ include (print $.Template.BasePath "/secret-chart.yaml") . | sha256sum }}
|
||||
{{- with .Values.relay.podAnnotations }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
serviceAccountName: {{ include "buzz.serviceAccountName" . }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.relay.securityContext | nindent 8 }}
|
||||
terminationGracePeriodSeconds: {{ .Values.relay.terminationGracePeriodSeconds }}
|
||||
{{- with .Values.image.pullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.relay.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.relay.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.relay.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.relay.topologySpreadConstraints }}
|
||||
topologySpreadConstraints:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: relay
|
||||
image: {{ include "buzz.image" . }}
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.relay.containerSecurityContext | nindent 12 }}
|
||||
ports:
|
||||
- { name: app, containerPort: 3000, protocol: TCP }
|
||||
- { name: health, containerPort: {{ .Values.service.healthPort }}, protocol: TCP }
|
||||
- { name: metrics, containerPort: {{ .Values.service.metricsPort }}, protocol: TCP }
|
||||
env:
|
||||
# ── Networking ───────────────────────────────────────────
|
||||
- { name: BUZZ_BIND_ADDR, value: {{ .Values.relay.bindAddr | quote }} }
|
||||
- { name: BUZZ_HEALTH_PORT, value: {{ .Values.service.healthPort | quote }} }
|
||||
- { name: BUZZ_METRICS_PORT, value: {{ .Values.service.metricsPort | quote }} }
|
||||
- { name: RELAY_URL, value: {{ .Values.relayUrl | quote }} }
|
||||
- { name: BUZZ_MEDIA_BASE_URL, value: {{ include "buzz.mediaBaseUrl" . | quote }} }
|
||||
- { name: BUZZ_MEDIA_SERVER_DOMAIN, value: {{ include "buzz.relayHost" . | quote }} }
|
||||
|
||||
# ── Behavior ─────────────────────────────────────────────
|
||||
- { name: BUZZ_MAX_CONNECTIONS, value: {{ .Values.relay.maxConnections | quote }} }
|
||||
- { name: BUZZ_MAX_CONCURRENT_HANDLERS, value: {{ .Values.relay.maxConcurrentHandlers | quote }} }
|
||||
- { name: BUZZ_SEND_BUFFER, value: {{ .Values.relay.sendBuffer | quote }} }
|
||||
- { name: BUZZ_REQUIRE_AUTH_TOKEN, value: {{ .Values.relay.requireAuthToken | quote }} }
|
||||
- { name: BUZZ_REQUIRE_RELAY_MEMBERSHIP, value: {{ .Values.relay.requireRelayMembership | quote }} }
|
||||
- { name: BUZZ_ALLOW_NIP_OA_AUTH, value: {{ .Values.relay.allowNipOaAuth | quote }} }
|
||||
- { name: BUZZ_PUBKEY_ALLOWLIST, value: {{ .Values.relay.pubkeyAllowlist | quote }} }
|
||||
{{- if .Values.relay.corsOrigins }}
|
||||
- { name: BUZZ_CORS_ORIGINS, value: {{ join "," .Values.relay.corsOrigins | quote }} }
|
||||
{{- end }}
|
||||
{{- if gt (.Values.relay.ephemeralTtlOverride | int) 0 }}
|
||||
- { name: BUZZ_EPHEMERAL_TTL_OVERRIDE, value: {{ .Values.relay.ephemeralTtlOverride | quote }} }
|
||||
{{- end }}
|
||||
|
||||
# ── Owner ────────────────────────────────────────────────
|
||||
- { name: RELAY_OWNER_PUBKEY, value: {{ .Values.ownerPubkey | quote }} }
|
||||
|
||||
# ── Migrations ───────────────────────────────────────────
|
||||
- { name: BUZZ_AUTO_MIGRATE, value: {{ .Values.migrate.autoMigrate | quote }} }
|
||||
|
||||
# ── Git ──────────────────────────────────────────────────
|
||||
- { name: BUZZ_GIT_REPO_PATH, value: {{ .Values.persistence.git.mountPath | quote }} }
|
||||
- { name: BUZZ_GIT_MAX_PACK_BYTES, value: {{ .Values.git.maxPackBytes | quote }} }
|
||||
- { name: BUZZ_GIT_MAX_REPOS_PER_PUBKEY, value: {{ .Values.git.maxReposPerPubkey | quote }} }
|
||||
- { name: BUZZ_GIT_MAX_CONCURRENT_OPS, value: {{ .Values.git.maxConcurrentOps | quote }} }
|
||||
|
||||
# ── S3 (non-secret) ──────────────────────────────────────
|
||||
{{- $s3Endpoint := include "buzz.s3Endpoint" . }}
|
||||
{{- if $s3Endpoint }}
|
||||
- { name: BUZZ_S3_ENDPOINT, value: {{ $s3Endpoint | quote }} }
|
||||
{{- end }}
|
||||
- { name: BUZZ_S3_BUCKET, value: {{ .Values.s3.bucket | quote }} }
|
||||
|
||||
# ── Secrets (from chart-managed or existing) ─────────────
|
||||
- name: BUZZ_RELAY_PRIVATE_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "buzz.envSecretName" . }}
|
||||
key: BUZZ_RELAY_PRIVATE_KEY
|
||||
optional: true
|
||||
- name: BUZZ_GIT_HOOK_HMAC_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "buzz.envSecretName" . }}
|
||||
key: BUZZ_GIT_HOOK_HMAC_SECRET
|
||||
optional: true
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "buzz.envSecretName" . }}
|
||||
key: DATABASE_URL
|
||||
- name: REDIS_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "buzz.envSecretName" . }}
|
||||
key: REDIS_URL
|
||||
optional: {{ and (eq (.Values.replicaCount | int) 1) (not .Values.redis.enabled) (not .Values.externalRedis.url) }}
|
||||
- name: TYPESENSE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "buzz.envSecretName" . }}
|
||||
key: TYPESENSE_URL
|
||||
- name: TYPESENSE_API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "buzz.envSecretName" . }}
|
||||
key: TYPESENSE_API_KEY
|
||||
- name: BUZZ_S3_ACCESS_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "buzz.envSecretName" . }}
|
||||
key: BUZZ_S3_ACCESS_KEY
|
||||
optional: true
|
||||
- name: BUZZ_S3_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "buzz.envSecretName" . }}
|
||||
key: BUZZ_S3_SECRET_KEY
|
||||
optional: true
|
||||
|
||||
{{- with .Values.relay.extraEnv }}
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.relay.extraEnvFrom }}
|
||||
envFrom:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.relay.livenessProbe | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.relay.readinessProbe | nindent 12 }}
|
||||
startupProbe:
|
||||
{{- toYaml .Values.relay.startupProbe | nindent 12 }}
|
||||
|
||||
resources:
|
||||
{{- toYaml .Values.relay.resources | nindent 12 }}
|
||||
|
||||
volumeMounts:
|
||||
{{- if .Values.persistence.git.enabled }}
|
||||
- { name: git-repos, mountPath: {{ .Values.persistence.git.mountPath | quote }} }
|
||||
{{- end }}
|
||||
|
||||
volumes:
|
||||
{{- if .Values.persistence.git.enabled }}
|
||||
- name: git-repos
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ default (printf "%s-git" (include "buzz.fullname" .)) .Values.persistence.git.existingClaim }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,28 @@
|
||||
{{- include "buzz.validate" . -}}
|
||||
{{- if .Values.httproute.enabled -}}
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: {{ include "buzz.fullname" . }}
|
||||
labels:
|
||||
{{- include "buzz.labels" . | nindent 4 }}
|
||||
spec:
|
||||
parentRefs:
|
||||
{{- toYaml .Values.httproute.parentRefs | nindent 4 }}
|
||||
{{- with .Values.httproute.hostnames }}
|
||||
hostnames:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- if .Values.httproute.rules }}
|
||||
{{- toYaml .Values.httproute.rules | nindent 4 }}
|
||||
{{- else }}
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: {{ include "buzz.fullname" . }}
|
||||
port: {{ .Values.service.port }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,43 @@
|
||||
{{- include "buzz.validate" . -}}
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
{{- $fullName := include "buzz.fullname" . -}}
|
||||
{{- $svcPort := .Values.service.port -}}
|
||||
{{- $defaultHost := include "buzz.relayHost" . -}}
|
||||
{{- $hosts := .Values.ingress.hosts -}}
|
||||
{{- if not $hosts -}}
|
||||
{{- $hosts = list (dict "host" $defaultHost "paths" (list (dict "path" "/" "pathType" "Prefix"))) -}}
|
||||
{{- end -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
labels:
|
||||
{{- include "buzz.labels" . | nindent 4 }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .Values.ingress.className }}
|
||||
ingressClassName: {{ .Values.ingress.className }}
|
||||
{{- end }}
|
||||
{{- with .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range $hosts }}
|
||||
- host: {{ .host | default $defaultHost | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ .path | default "/" }}
|
||||
pathType: {{ .pathType | default "Prefix" }}
|
||||
backend:
|
||||
service:
|
||||
name: {{ $fullName }}
|
||||
port:
|
||||
number: {{ $svcPort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,18 @@
|
||||
{{- include "buzz.validate" . -}}
|
||||
{{- if and .Values.podDisruptionBudget.enabled (gt (.Values.replicaCount | int) 1) -}}
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: {{ include "buzz.fullname" . }}
|
||||
labels:
|
||||
{{- include "buzz.labels" . | nindent 4 }}
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "buzz.relaySelectorLabels" . | nindent 6 }}
|
||||
{{- if .Values.podDisruptionBudget.minAvailable }}
|
||||
minAvailable: {{ .Values.podDisruptionBudget.minAvailable }}
|
||||
{{- else if .Values.podDisruptionBudget.maxUnavailable }}
|
||||
maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,22 @@
|
||||
{{- include "buzz.validate" . -}}
|
||||
{{- if and .Values.persistence.git.enabled (not .Values.persistence.git.existingClaim) -}}
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ include "buzz.fullname" . }}-git
|
||||
labels:
|
||||
{{- include "buzz.labels" . | nindent 4 }}
|
||||
{{- with .Values.persistence.git.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
accessModes:
|
||||
- {{ .Values.persistence.git.accessMode }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.git.size }}
|
||||
{{- if .Values.persistence.git.storageClass }}
|
||||
storageClassName: {{ .Values.persistence.git.storageClass | quote }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,51 @@
|
||||
{{- /*
|
||||
Creates the media bucket in the bundled MinIO after install/upgrade. Mirrors
|
||||
the docker-compose `minio-init` step. Hook-managed so it re-runs on upgrade
|
||||
and is garbage-collected. Quickstart-only.
|
||||
*/ -}}
|
||||
{{- if .Values.minio.enabled -}}
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: {{ include "buzz.minioFullname" . }}-init
|
||||
labels:
|
||||
{{- include "buzz.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: minio-init
|
||||
annotations:
|
||||
helm.sh/hook: post-install,post-upgrade
|
||||
helm.sh/hook-weight: "0"
|
||||
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
|
||||
spec:
|
||||
backoffLimit: 10
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "buzz.selectorLabels" . | nindent 8 }}
|
||||
app.kubernetes.io/component: minio-init
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: mc
|
||||
image: {{ .Values.minio.mcImage | quote }}
|
||||
env:
|
||||
- name: S3_ACCESS_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "buzz.chartSecretName" . }}
|
||||
key: BUZZ_S3_ACCESS_KEY
|
||||
- name: S3_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "buzz.chartSecretName" . }}
|
||||
key: BUZZ_S3_SECRET_KEY
|
||||
command: ["/bin/sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
set -e
|
||||
until mc alias set local {{ include "buzz.minioEndpoint" . }} "$S3_ACCESS_KEY" "$S3_SECRET_KEY"; do
|
||||
echo "waiting for MinIO..."; sleep 3
|
||||
done
|
||||
mc mb --ignore-existing local/{{ .Values.s3.bucket }}
|
||||
mc anonymous set none local/{{ .Values.s3.bucket }}
|
||||
echo "bucket {{ .Values.s3.bucket }} ready"
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,99 @@
|
||||
{{- /*
|
||||
Eval-only in-cluster MinIO for the quickstart profile. NOT for production —
|
||||
single replica, no TLS, credentials from the chart-managed Secret. Production
|
||||
deploys leave minio.enabled=false and point s3.* at managed object storage.
|
||||
*/ -}}
|
||||
{{- if .Values.minio.enabled -}}
|
||||
{{- if .Values.secrets.existingSecret -}}
|
||||
{{- fail "minio.enabled=true (quickstart) is incompatible with secrets.existingSecret. Quickstart autogenerates MinIO credentials in the chart-managed Secret; for external S3 set minio.enabled=false and provide BUZZ_S3_ACCESS_KEY/BUZZ_S3_SECRET_KEY." -}}
|
||||
{{- end -}}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "buzz.minioFullname" . }}
|
||||
labels:
|
||||
{{- include "buzz.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: minio
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "buzz.selectorLabels" . | nindent 6 }}
|
||||
app.kubernetes.io/component: minio
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "buzz.selectorLabels" . | nindent 8 }}
|
||||
app.kubernetes.io/component: minio
|
||||
spec:
|
||||
containers:
|
||||
- name: minio
|
||||
image: {{ .Values.minio.image | quote }}
|
||||
args: ["server", "/data", "--console-address", ":9001"]
|
||||
env:
|
||||
- name: MINIO_ROOT_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "buzz.chartSecretName" . }}
|
||||
key: BUZZ_S3_ACCESS_KEY
|
||||
- name: MINIO_ROOT_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "buzz.chartSecretName" . }}
|
||||
key: BUZZ_S3_SECRET_KEY
|
||||
ports:
|
||||
- { name: api, containerPort: 9000 }
|
||||
- { name: console, containerPort: 9001 }
|
||||
readinessProbe:
|
||||
httpGet: { path: /minio/health/ready, port: api }
|
||||
initialDelaySeconds: 3
|
||||
periodSeconds: 5
|
||||
livenessProbe:
|
||||
httpGet: { path: /minio/health/live, port: api }
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
volumeMounts:
|
||||
- { name: data, mountPath: /data }
|
||||
volumes:
|
||||
- name: data
|
||||
{{- if .Values.minio.persistence.enabled }}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "buzz.minioFullname" . }}
|
||||
{{- else }}
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "buzz.minioFullname" . }}
|
||||
labels:
|
||||
{{- include "buzz.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: minio
|
||||
spec:
|
||||
selector:
|
||||
{{- include "buzz.selectorLabels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: minio
|
||||
ports:
|
||||
- { name: api, port: 9000, targetPort: api }
|
||||
- { name: console, port: 9001, targetPort: console }
|
||||
{{- if .Values.minio.persistence.enabled }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ include "buzz.minioFullname" . }}
|
||||
labels:
|
||||
{{- include "buzz.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: minio
|
||||
annotations:
|
||||
helm.sh/resource-policy: keep
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.minio.persistence.size | quote }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,96 @@
|
||||
{{- /*
|
||||
Eval-only in-cluster Typesense for the quickstart profile. NOT for production —
|
||||
single replica, no TLS, API key from the chart-managed Secret. Production
|
||||
deploys leave typesense.enabled=false and point typesense.url/apiKey (or
|
||||
secrets.existingSecret) at a managed Typesense service.
|
||||
*/ -}}
|
||||
{{- if .Values.typesense.enabled -}}
|
||||
{{- if .Values.secrets.existingSecret -}}
|
||||
{{- fail "typesense.enabled=true (quickstart) is incompatible with secrets.existingSecret. Quickstart autogenerates the Typesense key in the chart-managed Secret; for external Typesense set typesense.enabled=false and provide TYPESENSE_URL/TYPESENSE_API_KEY." -}}
|
||||
{{- end -}}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "buzz.typesenseFullname" . }}
|
||||
labels:
|
||||
{{- include "buzz.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: typesense
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "buzz.selectorLabels" . | nindent 6 }}
|
||||
app.kubernetes.io/component: typesense
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "buzz.selectorLabels" . | nindent 8 }}
|
||||
app.kubernetes.io/component: typesense
|
||||
spec:
|
||||
containers:
|
||||
- name: typesense
|
||||
image: {{ .Values.typesense.image | quote }}
|
||||
args:
|
||||
- "--data-dir=/data"
|
||||
- "--api-key=$(TYPESENSE_API_KEY)"
|
||||
- "--enable-cors"
|
||||
env:
|
||||
- name: TYPESENSE_API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "buzz.chartSecretName" . }}
|
||||
key: TYPESENSE_API_KEY
|
||||
ports:
|
||||
- { name: http, containerPort: 8108 }
|
||||
readinessProbe:
|
||||
httpGet: { path: /health, port: http }
|
||||
initialDelaySeconds: 3
|
||||
periodSeconds: 5
|
||||
livenessProbe:
|
||||
httpGet: { path: /health, port: http }
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
volumeMounts:
|
||||
- { name: data, mountPath: /data }
|
||||
volumes:
|
||||
- name: data
|
||||
{{- if .Values.typesense.persistence.enabled }}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "buzz.typesenseFullname" . }}
|
||||
{{- else }}
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "buzz.typesenseFullname" . }}
|
||||
labels:
|
||||
{{- include "buzz.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: typesense
|
||||
spec:
|
||||
selector:
|
||||
{{- include "buzz.selectorLabels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: typesense
|
||||
ports:
|
||||
- { name: http, port: 8108, targetPort: http }
|
||||
{{- if .Values.typesense.persistence.enabled }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ include "buzz.typesenseFullname" . }}
|
||||
labels:
|
||||
{{- include "buzz.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: typesense
|
||||
annotations:
|
||||
helm.sh/resource-policy: keep
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.typesense.persistence.size | quote }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,127 @@
|
||||
{{- include "buzz.validate" . -}}
|
||||
{{- /*
|
||||
Chart-managed Secret.
|
||||
|
||||
Renders only when at least one chart-managed value is needed (no
|
||||
secrets.existingSecret provided OR in-cluster Postgres composes DATABASE_URL
|
||||
here). Persists across upgrades via the `lookup` pattern. Not GitOps-safe —
|
||||
ArgoCD/Flux users should provide secrets.existingSecret instead.
|
||||
*/ -}}
|
||||
|
||||
{{- if not .Values.secrets.existingSecret -}}
|
||||
{{- $existing := (lookup "v1" "Secret" .Release.Namespace (include "buzz.chartSecretName" .)) | default dict -}}
|
||||
{{- $existingData := (get $existing "data") | default dict -}}
|
||||
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ include "buzz.chartSecretName" . }}
|
||||
labels:
|
||||
{{- include "buzz.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
helm.sh/resource-policy: keep
|
||||
type: Opaque
|
||||
data:
|
||||
|
||||
{{- /* Relay private key (relay identity; rotation = identity change) */}}
|
||||
{{- if .Values.secrets.relayPrivateKey }}
|
||||
BUZZ_RELAY_PRIVATE_KEY: {{ .Values.secrets.relayPrivateKey | b64enc | quote }}
|
||||
{{- else if (index $existingData "BUZZ_RELAY_PRIVATE_KEY") }}
|
||||
BUZZ_RELAY_PRIVATE_KEY: {{ index $existingData "BUZZ_RELAY_PRIVATE_KEY" | quote }}
|
||||
{{- else }}
|
||||
BUZZ_RELAY_PRIVATE_KEY: {{ randAlphaNum 64 | sha256sum | b64enc | quote }}
|
||||
{{- end }}
|
||||
|
||||
{{- /* Git hook HMAC (required when replicaCount > 1) */}}
|
||||
{{- if .Values.secrets.gitHookHmacSecret }}
|
||||
BUZZ_GIT_HOOK_HMAC_SECRET: {{ .Values.secrets.gitHookHmacSecret | b64enc | quote }}
|
||||
{{- else if (index $existingData "BUZZ_GIT_HOOK_HMAC_SECRET") }}
|
||||
BUZZ_GIT_HOOK_HMAC_SECRET: {{ index $existingData "BUZZ_GIT_HOOK_HMAC_SECRET" | quote }}
|
||||
{{- else }}
|
||||
BUZZ_GIT_HOOK_HMAC_SECRET: {{ randAlphaNum 64 | b64enc | quote }}
|
||||
{{- end }}
|
||||
|
||||
{{- /* In-cluster Postgres: compose DATABASE_URL + postgres-password */}}
|
||||
{{- if .Values.postgresql.enabled }}
|
||||
{{- $pgHost := printf "%s-postgresql" .Release.Name }}
|
||||
{{- $pgDb := .Values.postgresql.auth.database }}
|
||||
{{- $pgUser := .Values.postgresql.auth.username }}
|
||||
{{- $pgPass := "" }}
|
||||
{{- if (index $existingData "postgres-password") }}
|
||||
{{- $pgPass = index $existingData "postgres-password" | b64dec }}
|
||||
{{- else }}
|
||||
{{- $pgPass = randAlphaNum 24 }}
|
||||
{{- end }}
|
||||
postgres-password: {{ $pgPass | b64enc | quote }}
|
||||
DATABASE_URL: {{ printf "postgres://%s:%s@%s:5432/%s" $pgUser $pgPass $pgHost $pgDb | b64enc | quote }}
|
||||
{{- else if .Values.externalPostgresql.url }}
|
||||
DATABASE_URL: {{ .Values.externalPostgresql.url | b64enc | quote }}
|
||||
{{- end }}
|
||||
|
||||
{{- /* In-cluster Redis: compose REDIS_URL */}}
|
||||
{{- if .Values.redis.enabled }}
|
||||
{{- $redisHost := printf "%s-redis" .Release.Name }}
|
||||
{{- $redisPass := "" }}
|
||||
{{- if (index $existingData "redis-password") }}
|
||||
{{- $redisPass = index $existingData "redis-password" | b64dec }}
|
||||
{{- else }}
|
||||
{{- $redisPass = randAlphaNum 24 }}
|
||||
{{- end }}
|
||||
redis-password: {{ $redisPass | b64enc | quote }}
|
||||
REDIS_URL: {{ printf "redis://:%s@%s:6379" $redisPass $redisHost | b64enc | quote }}
|
||||
{{- else if .Values.externalRedis.url }}
|
||||
REDIS_URL: {{ .Values.externalRedis.url | b64enc | quote }}
|
||||
{{- end }}
|
||||
|
||||
{{- /* Typesense — bundled (quickstart) composes URL + autogen key; else
|
||||
pass through external values. */}}
|
||||
{{- if .Values.typesense.enabled }}
|
||||
{{- $tsKey := "" }}
|
||||
{{- if (index $existingData "TYPESENSE_API_KEY") }}
|
||||
{{- $tsKey = index $existingData "TYPESENSE_API_KEY" | b64dec }}
|
||||
{{- else if .Values.typesense.apiKey }}
|
||||
{{- $tsKey = .Values.typesense.apiKey }}
|
||||
{{- else }}
|
||||
{{- $tsKey = randAlphaNum 32 }}
|
||||
{{- end }}
|
||||
TYPESENSE_URL: {{ include "buzz.typesenseUrl" . | b64enc | quote }}
|
||||
TYPESENSE_API_KEY: {{ $tsKey | b64enc | quote }}
|
||||
{{- else }}
|
||||
{{- if .Values.typesense.url }}
|
||||
TYPESENSE_URL: {{ .Values.typesense.url | b64enc | quote }}
|
||||
{{- end }}
|
||||
{{- if .Values.typesense.apiKey }}
|
||||
TYPESENSE_API_KEY: {{ .Values.typesense.apiKey | b64enc | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- /* S3 creds — bundled MinIO (quickstart) autogenerates; else pass
|
||||
through external values. */}}
|
||||
{{- if .Values.minio.enabled }}
|
||||
{{- $s3Access := "" }}
|
||||
{{- if (index $existingData "BUZZ_S3_ACCESS_KEY") }}
|
||||
{{- $s3Access = index $existingData "BUZZ_S3_ACCESS_KEY" | b64dec }}
|
||||
{{- else if .Values.s3.accessKey }}
|
||||
{{- $s3Access = .Values.s3.accessKey }}
|
||||
{{- else }}
|
||||
{{- $s3Access = printf "buzz-%s" (randAlphaNum 12 | lower) }}
|
||||
{{- end }}
|
||||
{{- $s3Secret := "" }}
|
||||
{{- if (index $existingData "BUZZ_S3_SECRET_KEY") }}
|
||||
{{- $s3Secret = index $existingData "BUZZ_S3_SECRET_KEY" | b64dec }}
|
||||
{{- else if .Values.s3.secretKey }}
|
||||
{{- $s3Secret = .Values.s3.secretKey }}
|
||||
{{- else }}
|
||||
{{- $s3Secret = randAlphaNum 32 }}
|
||||
{{- end }}
|
||||
BUZZ_S3_ACCESS_KEY: {{ $s3Access | b64enc | quote }}
|
||||
BUZZ_S3_SECRET_KEY: {{ $s3Secret | b64enc | quote }}
|
||||
{{- else }}
|
||||
{{- if .Values.s3.accessKey }}
|
||||
BUZZ_S3_ACCESS_KEY: {{ .Values.s3.accessKey | b64enc | quote }}
|
||||
{{- end }}
|
||||
{{- if .Values.s3.secretKey }}
|
||||
BUZZ_S3_SECRET_KEY: {{ .Values.s3.secretKey | b64enc | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,19 @@
|
||||
{{- include "buzz.validate" . -}}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "buzz.fullname" . }}
|
||||
labels:
|
||||
{{- include "buzz.labels" . | nindent 4 }}
|
||||
{{- with .Values.service.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
selector:
|
||||
{{- include "buzz.relaySelectorLabels" . | nindent 4 }}
|
||||
ports:
|
||||
- { name: app, port: {{ .Values.service.port }}, targetPort: app, protocol: TCP }
|
||||
- { name: health, port: {{ .Values.service.healthPort }}, targetPort: health, protocol: TCP }
|
||||
- { name: metrics, port: {{ .Values.service.metricsPort }}, targetPort: metrics, protocol: TCP }
|
||||
@@ -0,0 +1,13 @@
|
||||
{{- include "buzz.validate" . -}}
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "buzz.serviceAccountName" . }}
|
||||
labels:
|
||||
{{- include "buzz.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,22 @@
|
||||
# HA shape: replicas=3 + Redis + RWX. Render-only check.
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
|
||||
replicaCount: 3
|
||||
secrets:
|
||||
existingSecret: buzz-secrets
|
||||
externalPostgresql:
|
||||
url: "postgres://buzz:pw@postgres.example.com:5432/buzz"
|
||||
externalRedis:
|
||||
url: "redis://:pw@redis.example.com:6379"
|
||||
typesense:
|
||||
url: "https://typesense.example.com"
|
||||
s3:
|
||||
bucket: "buzz-media"
|
||||
persistence:
|
||||
git:
|
||||
enabled: true
|
||||
accessMode: ReadWriteMany
|
||||
size: 50Gi
|
||||
podDisruptionBudget:
|
||||
enabled: true
|
||||
minAvailable: 2
|
||||
@@ -0,0 +1,27 @@
|
||||
# Production / GitOps shape: external services, existingSecret. Renders only;
|
||||
# `ct install` is not asked to satisfy the external services.
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
|
||||
secrets:
|
||||
existingSecret: buzz-secrets
|
||||
externalPostgresql:
|
||||
url: "postgres://buzz:pw@postgres.example.com:5432/buzz"
|
||||
externalRedis:
|
||||
url: "redis://:pw@redis.example.com:6379"
|
||||
typesense:
|
||||
url: "https://typesense.example.com"
|
||||
s3:
|
||||
endpoint: "https://s3.us-east-1.amazonaws.com"
|
||||
bucket: "buzz-media"
|
||||
persistence:
|
||||
git:
|
||||
enabled: true
|
||||
accessMode: ReadWriteMany
|
||||
size: 50Gi
|
||||
ingress:
|
||||
enabled: true
|
||||
className: nginx
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
||||
podDisruptionBudget:
|
||||
enabled: false
|
||||
@@ -0,0 +1,87 @@
|
||||
suite: networking
|
||||
templates:
|
||||
- templates/ingress.yaml
|
||||
- templates/httproute.yaml
|
||||
- templates/service.yaml
|
||||
tests:
|
||||
- it: Service exposes app/health/metrics ports
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
externalPostgresql.url: postgres://u:p@h:5432/d
|
||||
typesense.url: http://ts:8108
|
||||
typesense.apiKey: k
|
||||
s3.endpoint: http://minio:9000
|
||||
s3.accessKey: a
|
||||
s3.secretKey: s
|
||||
asserts:
|
||||
- equal:
|
||||
path: spec.ports[0].name
|
||||
value: app
|
||||
template: templates/service.yaml
|
||||
- equal:
|
||||
path: spec.ports[0].port
|
||||
value: 3000
|
||||
template: templates/service.yaml
|
||||
|
||||
- it: Ingress disabled by default
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
externalPostgresql.url: postgres://u:p@h:5432/d
|
||||
typesense.url: http://ts:8108
|
||||
typesense.apiKey: k
|
||||
s3.endpoint: http://minio:9000
|
||||
s3.accessKey: a
|
||||
s3.secretKey: s
|
||||
asserts:
|
||||
- hasDocuments:
|
||||
count: 0
|
||||
template: templates/ingress.yaml
|
||||
- hasDocuments:
|
||||
count: 0
|
||||
template: templates/httproute.yaml
|
||||
|
||||
- it: Ingress renders with derived host when relayUrl provided
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
externalPostgresql.url: postgres://u:p@h:5432/d
|
||||
typesense.url: http://ts:8108
|
||||
typesense.apiKey: k
|
||||
s3.endpoint: http://minio:9000
|
||||
s3.accessKey: a
|
||||
s3.secretKey: s
|
||||
ingress.enabled: true
|
||||
ingress.className: nginx
|
||||
asserts:
|
||||
- hasDocuments:
|
||||
count: 1
|
||||
template: templates/ingress.yaml
|
||||
- equal:
|
||||
path: spec.ingressClassName
|
||||
value: nginx
|
||||
template: templates/ingress.yaml
|
||||
|
||||
- it: HTTPRoute renders when enabled
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
externalPostgresql.url: postgres://u:p@h:5432/d
|
||||
typesense.url: http://ts:8108
|
||||
typesense.apiKey: k
|
||||
s3.endpoint: http://minio:9000
|
||||
s3.accessKey: a
|
||||
s3.secretKey: s
|
||||
httproute.enabled: true
|
||||
httproute.parentRefs:
|
||||
- name: my-gateway
|
||||
namespace: gateway-system
|
||||
asserts:
|
||||
- hasDocuments:
|
||||
count: 1
|
||||
template: templates/httproute.yaml
|
||||
- equal:
|
||||
path: kind
|
||||
value: HTTPRoute
|
||||
template: templates/httproute.yaml
|
||||
@@ -0,0 +1,112 @@
|
||||
suite: quickstart bundled services
|
||||
# The dev quickstart must stand up MinIO + Typesense in-cluster so the relay's
|
||||
# startup S3 conformance probe passes with zero external dependencies.
|
||||
templates:
|
||||
- templates/quickstart-minio.yaml
|
||||
- templates/quickstart-minio-init.yaml
|
||||
- templates/quickstart-typesense.yaml
|
||||
- templates/deployment.yaml
|
||||
- templates/secret-chart.yaml
|
||||
- templates/service.yaml
|
||||
tests:
|
||||
- it: renders the in-cluster MinIO Deployment when minio.enabled
|
||||
release:
|
||||
name: rel
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
postgresql.enabled: true
|
||||
redis.enabled: true
|
||||
typesense.enabled: true
|
||||
minio.enabled: true
|
||||
asserts:
|
||||
- containsDocument:
|
||||
kind: Deployment
|
||||
apiVersion: apps/v1
|
||||
name: rel-buzz-minio
|
||||
template: templates/quickstart-minio.yaml
|
||||
documentIndex: 0
|
||||
- containsDocument:
|
||||
kind: Job
|
||||
apiVersion: batch/v1
|
||||
name: rel-buzz-minio-init
|
||||
template: templates/quickstart-minio-init.yaml
|
||||
|
||||
- it: renders the in-cluster Typesense Deployment when typesense.enabled
|
||||
release:
|
||||
name: rel
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
postgresql.enabled: true
|
||||
redis.enabled: true
|
||||
typesense.enabled: true
|
||||
minio.enabled: true
|
||||
asserts:
|
||||
- containsDocument:
|
||||
kind: Deployment
|
||||
apiVersion: apps/v1
|
||||
name: rel-buzz-typesense
|
||||
template: templates/quickstart-typesense.yaml
|
||||
documentIndex: 0
|
||||
|
||||
- it: relay S3 endpoint resolves to the bundled MinIO Service
|
||||
release:
|
||||
name: rel
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
postgresql.enabled: true
|
||||
redis.enabled: true
|
||||
typesense.enabled: true
|
||||
minio.enabled: true
|
||||
asserts:
|
||||
- contains:
|
||||
path: spec.template.spec.containers[0].env
|
||||
content:
|
||||
name: BUZZ_S3_ENDPOINT
|
||||
value: "http://rel-buzz-minio.NAMESPACE.svc.cluster.local:9000"
|
||||
template: templates/deployment.yaml
|
||||
|
||||
- it: chart Secret composes TYPESENSE_URL at the bundled Typesense Service
|
||||
release:
|
||||
name: rel
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
postgresql.enabled: true
|
||||
redis.enabled: true
|
||||
typesense.enabled: true
|
||||
minio.enabled: true
|
||||
asserts:
|
||||
- matchRegex:
|
||||
path: data.TYPESENSE_URL
|
||||
decodeBase64: true
|
||||
pattern: "^http://rel-buzz-typesense\\..+:8108$"
|
||||
template: templates/secret-chart.yaml
|
||||
documentIndex: 0
|
||||
# MinIO creds the relay reads must exist for the conformance probe.
|
||||
- isNotNullOrEmpty:
|
||||
path: data.BUZZ_S3_ACCESS_KEY
|
||||
template: templates/secret-chart.yaml
|
||||
documentIndex: 0
|
||||
- isNotNullOrEmpty:
|
||||
path: data.BUZZ_S3_SECRET_KEY
|
||||
template: templates/secret-chart.yaml
|
||||
documentIndex: 0
|
||||
|
||||
- it: relay Service selector is scoped to component=relay (must NOT match bundled MinIO/Typesense pods)
|
||||
release:
|
||||
name: rel
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
postgresql.enabled: true
|
||||
redis.enabled: true
|
||||
typesense.enabled: true
|
||||
minio.enabled: true
|
||||
asserts:
|
||||
- equal:
|
||||
path: spec.selector["app.kubernetes.io/component"]
|
||||
value: relay
|
||||
template: templates/service.yaml
|
||||
@@ -0,0 +1,35 @@
|
||||
suite: quickstart guards
|
||||
# Quickstart autogenerates MinIO/Typesense creds in the chart-managed Secret,
|
||||
# so it is mutually exclusive with secrets.existingSecret. Isolated single-
|
||||
# template suites so the fail-guard is the only document under assertion.
|
||||
tests:
|
||||
- it: minio.enabled is incompatible with existingSecret
|
||||
templates:
|
||||
- templates/quickstart-minio.yaml
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
postgresql.enabled: true
|
||||
typesense.url: http://ts:8108
|
||||
typesense.apiKey: k
|
||||
minio.enabled: true
|
||||
secrets.existingSecret: "buzz-secrets"
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorPattern: "incompatible with secrets.existingSecret"
|
||||
|
||||
- it: typesense.enabled is incompatible with existingSecret
|
||||
templates:
|
||||
- templates/quickstart-typesense.yaml
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
postgresql.enabled: true
|
||||
s3.endpoint: http://minio:9000
|
||||
s3.accessKey: a
|
||||
s3.secretKey: s
|
||||
typesense.enabled: true
|
||||
secrets.existingSecret: "buzz-secrets"
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorPattern: "incompatible with secrets.existingSecret"
|
||||
@@ -0,0 +1,57 @@
|
||||
suite: production render
|
||||
# Multi-template scope: needed so $.Template.BasePath lookups in deployment.yaml
|
||||
# (e.g. checksum/secret include of secret-chart.yaml) resolve at render time.
|
||||
templates:
|
||||
- templates/deployment.yaml
|
||||
- templates/secret-chart.yaml
|
||||
- templates/serviceaccount.yaml
|
||||
- templates/service.yaml
|
||||
- templates/pvc-git.yaml
|
||||
tests:
|
||||
- it: renders cleanly in production profile (external pg/redis/typesense)
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
externalPostgresql.url: postgres://u:p@h:5432/d
|
||||
externalRedis.url: redis://h:6379
|
||||
typesense.url: http://ts:8108
|
||||
typesense.apiKey: k
|
||||
s3.endpoint: http://minio:9000
|
||||
s3.accessKey: a
|
||||
s3.secretKey: s
|
||||
asserts:
|
||||
- equal:
|
||||
path: kind
|
||||
value: Deployment
|
||||
template: templates/deployment.yaml
|
||||
- equal:
|
||||
path: kind
|
||||
value: ServiceAccount
|
||||
template: templates/serviceaccount.yaml
|
||||
- equal:
|
||||
path: kind
|
||||
value: Service
|
||||
template: templates/service.yaml
|
||||
|
||||
- it: renders HA cleanly with replicaCount=3 + RWX + Redis
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
externalPostgresql.url: postgres://u:p@h:5432/d
|
||||
externalRedis.url: redis://h:6379
|
||||
typesense.url: http://ts:8108
|
||||
typesense.apiKey: k
|
||||
s3.endpoint: http://minio:9000
|
||||
s3.accessKey: a
|
||||
s3.secretKey: s
|
||||
replicaCount: 3
|
||||
persistence.git.accessMode: ReadWriteMany
|
||||
asserts:
|
||||
- equal:
|
||||
path: spec.replicas
|
||||
value: 3
|
||||
template: templates/deployment.yaml
|
||||
- equal:
|
||||
path: spec.accessModes[0]
|
||||
value: ReadWriteMany
|
||||
template: templates/pvc-git.yaml
|
||||
@@ -0,0 +1,151 @@
|
||||
suite: secrets wiring
|
||||
templates:
|
||||
- templates/secret-chart.yaml
|
||||
- templates/deployment.yaml
|
||||
tests:
|
||||
- it: chart-managed Secret is rendered when existingSecret is empty
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
externalPostgresql.url: postgres://u:p@h:5432/d
|
||||
typesense.url: http://ts:8108
|
||||
typesense.apiKey: k
|
||||
s3.endpoint: http://minio:9000
|
||||
s3.accessKey: a
|
||||
s3.secretKey: s
|
||||
asserts:
|
||||
- hasDocuments:
|
||||
count: 1
|
||||
template: templates/secret-chart.yaml
|
||||
- equal:
|
||||
path: kind
|
||||
value: Secret
|
||||
template: templates/secret-chart.yaml
|
||||
- equal:
|
||||
path: metadata.annotations["helm.sh/resource-policy"]
|
||||
value: keep
|
||||
template: templates/secret-chart.yaml
|
||||
|
||||
- it: chart-managed Secret is NOT rendered when existingSecret is set
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
externalPostgresql.url: postgres://u:p@h:5432/d
|
||||
typesense.url: http://ts:8108
|
||||
typesense.apiKey: k
|
||||
s3.endpoint: http://minio:9000
|
||||
s3.accessKey: a
|
||||
s3.secretKey: s
|
||||
secrets.existingSecret: "buzz-secrets"
|
||||
asserts:
|
||||
- hasDocuments:
|
||||
count: 0
|
||||
template: templates/secret-chart.yaml
|
||||
|
||||
- it: Deployment env points BUZZ_RELAY_PRIVATE_KEY at existingSecret when set
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
externalPostgresql.url: postgres://u:p@h:5432/d
|
||||
typesense.url: http://ts:8108
|
||||
typesense.apiKey: k
|
||||
s3.endpoint: http://minio:9000
|
||||
s3.accessKey: a
|
||||
s3.secretKey: s
|
||||
secrets.existingSecret: "buzz-secrets"
|
||||
asserts:
|
||||
- contains:
|
||||
path: spec.template.spec.containers[0].env
|
||||
content:
|
||||
name: BUZZ_RELAY_PRIVATE_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: buzz-secrets
|
||||
key: BUZZ_RELAY_PRIVATE_KEY
|
||||
optional: true
|
||||
template: templates/deployment.yaml
|
||||
|
||||
- it: RELAY_OWNER_PUBKEY env is set (not BUZZ_RELAY_OWNER_PUBKEY)
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
|
||||
externalPostgresql.url: postgres://u:p@h:5432/d
|
||||
typesense.url: http://ts:8108
|
||||
typesense.apiKey: k
|
||||
s3.endpoint: http://minio:9000
|
||||
s3.accessKey: a
|
||||
s3.secretKey: s
|
||||
asserts:
|
||||
- contains:
|
||||
path: spec.template.spec.containers[0].env
|
||||
content:
|
||||
name: RELAY_OWNER_PUBKEY
|
||||
value: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
|
||||
template: templates/deployment.yaml
|
||||
- notContains:
|
||||
path: spec.template.spec.containers[0].env
|
||||
content:
|
||||
name: BUZZ_RELAY_OWNER_PUBKEY
|
||||
template: templates/deployment.yaml
|
||||
|
||||
- it: BUZZ_AUTO_MIGRATE defaults to "true"
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
externalPostgresql.url: postgres://u:p@h:5432/d
|
||||
typesense.url: http://ts:8108
|
||||
typesense.apiKey: k
|
||||
s3.endpoint: http://minio:9000
|
||||
s3.accessKey: a
|
||||
s3.secretKey: s
|
||||
asserts:
|
||||
- contains:
|
||||
path: spec.template.spec.containers[0].env
|
||||
content:
|
||||
name: BUZZ_AUTO_MIGRATE
|
||||
value: "true"
|
||||
template: templates/deployment.yaml
|
||||
|
||||
- it: quickstart composes DATABASE_URL/REDIS_URL at the actual subchart Service hosts
|
||||
release:
|
||||
name: rel
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
postgresql.enabled: true
|
||||
redis.enabled: true
|
||||
typesense.url: http://ts:8108
|
||||
typesense.apiKey: k
|
||||
s3.endpoint: http://minio:9000
|
||||
s3.accessKey: a
|
||||
s3.secretKey: s
|
||||
asserts:
|
||||
# Postgres Service is "<release>-postgresql"; Redis (standalone) is
|
||||
# "<release>-redis" — NOT "-redis-master". A host mismatch here is the
|
||||
# connection-refused class of bug this guards against.
|
||||
- matchRegex:
|
||||
path: data.DATABASE_URL
|
||||
decodeBase64: true
|
||||
pattern: "@rel-postgresql:5432/buzz$"
|
||||
template: templates/secret-chart.yaml
|
||||
- matchRegex:
|
||||
path: data.REDIS_URL
|
||||
decodeBase64: true
|
||||
pattern: "@rel-redis:6379$"
|
||||
template: templates/secret-chart.yaml
|
||||
documentIndex: 0
|
||||
- notMatchRegex:
|
||||
path: data.REDIS_URL
|
||||
decodeBase64: true
|
||||
pattern: "redis-master"
|
||||
template: templates/secret-chart.yaml
|
||||
documentIndex: 0
|
||||
# Autogenerated relay key MUST be a valid Nostr secret key: 64 lowercase
|
||||
# hex chars. randAlphaNum produces letters g-z that fail nostr::Keys::parse
|
||||
# and crash relay startup with "invalid BUZZ_RELAY_PRIVATE_KEY".
|
||||
- matchRegex:
|
||||
path: data.BUZZ_RELAY_PRIVATE_KEY
|
||||
decodeBase64: true
|
||||
pattern: "^[0-9a-f]{64}$"
|
||||
template: templates/secret-chart.yaml
|
||||
documentIndex: 0
|
||||
@@ -0,0 +1,105 @@
|
||||
suite: validation
|
||||
templates:
|
||||
- templates/deployment.yaml
|
||||
tests:
|
||||
- it: fails when relayUrl is missing
|
||||
set:
|
||||
relayUrl: ""
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
externalPostgresql.url: postgres://u:p@h:5432/d
|
||||
typesense.url: http://ts:8108
|
||||
typesense.apiKey: k
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorMessage: "relayUrl is required: set --set relayUrl=wss://your.domain"
|
||||
|
||||
- it: fails when ownerPubkey is missing and requireRelayMembership is true
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: ""
|
||||
externalPostgresql.url: postgres://u:p@h:5432/d
|
||||
typesense.url: http://ts:8108
|
||||
typesense.apiKey: k
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorPattern: "ownerPubkey is required when relay.requireRelayMembership=true"
|
||||
|
||||
- it: fails when ownerPubkey is not 64 lowercase hex (schema-level)
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "NOTAHEX"
|
||||
externalPostgresql.url: postgres://u:p@h:5432/d
|
||||
typesense.url: http://ts:8108
|
||||
typesense.apiKey: k
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorPattern: "ownerPubkey: Does not match pattern"
|
||||
|
||||
- it: fails when replicaCount>1 without Redis
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
externalPostgresql.url: postgres://u:p@h:5432/d
|
||||
typesense.url: http://ts:8108
|
||||
typesense.apiKey: k
|
||||
replicaCount: 3
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorPattern: "replicaCount=3 requires Redis"
|
||||
|
||||
- it: fails when replicaCount>1 with RWO git PVC
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
externalPostgresql.url: postgres://u:p@h:5432/d
|
||||
externalRedis.url: redis://h:6379
|
||||
typesense.url: http://ts:8108
|
||||
typesense.apiKey: k
|
||||
replicaCount: 3
|
||||
persistence.git.accessMode: ReadWriteOnce
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorPattern: "requires persistence.git.accessMode=ReadWriteMany"
|
||||
|
||||
- it: fails when ingress and httproute both enabled
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
externalPostgresql.url: postgres://u:p@h:5432/d
|
||||
typesense.url: http://ts:8108
|
||||
typesense.apiKey: k
|
||||
ingress.enabled: true
|
||||
httproute.enabled: true
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorPattern: "cannot both be true"
|
||||
|
||||
- it: fails when Postgres source is missing
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
typesense.url: http://ts:8108
|
||||
typesense.apiKey: k
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorPattern: "Postgres source missing"
|
||||
|
||||
- it: fails when Typesense source is missing
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
externalPostgresql.url: postgres://u:p@h:5432/d
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorPattern: "Typesense source missing"
|
||||
|
||||
- it: fails when S3/object-storage source is missing
|
||||
set:
|
||||
relayUrl: wss://buzz.example.com
|
||||
ownerPubkey: "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
externalPostgresql.url: postgres://u:p@h:5432/d
|
||||
typesense.url: http://ts:8108
|
||||
typesense.apiKey: k
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorPattern: "S3/object-storage source missing"
|
||||
@@ -0,0 +1,251 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "Buzz Helm chart values",
|
||||
"description": "Schema for values.yaml. Catches misconfiguration at `helm install` time before _validate.tpl runtime fails. Both layers are intentional: schema rejects malformed inputs; templates reject inconsistent combinations.",
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"quickstart": {
|
||||
"type": "boolean",
|
||||
"description": "Master toggle for evaluation profile (enables postgresql + redis subcharts and chart-side autogen). Not GitOps-safe."
|
||||
},
|
||||
"image": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"repository": { "type": "string", "minLength": 1 },
|
||||
"tag": { "type": "string" },
|
||||
"pullPolicy": { "type": "string", "enum": ["Always", "IfNotPresent", "Never"] },
|
||||
"pullSecrets": {
|
||||
"type": "array",
|
||||
"items": { "type": "object", "required": ["name"], "properties": { "name": { "type": "string" } } }
|
||||
}
|
||||
},
|
||||
"required": ["repository", "pullPolicy"]
|
||||
},
|
||||
"replicaCount": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"description": "Replica count for the relay Deployment. replicaCount > 1 requires Redis (for buzz-pubsub) and ReadWriteMany git storage — enforced by _validate.tpl."
|
||||
},
|
||||
"relayUrl": {
|
||||
"type": "string",
|
||||
"pattern": "^(wss?://.+)?$",
|
||||
"description": "Public wss:// URL clients connect to. Required (validated by _validate.tpl). Drives RELAY_URL, default mediaBaseUrl, default ingress host."
|
||||
},
|
||||
"mediaBaseUrl": {
|
||||
"type": "string",
|
||||
"pattern": "^(https?://.+)?$"
|
||||
},
|
||||
"ownerPubkey": {
|
||||
"type": "string",
|
||||
"pattern": "^([0-9a-f]{64})?$",
|
||||
"description": "64-char lowercase hex Nostr pubkey of the relay operator. Required when relay.requireRelayMembership=true."
|
||||
},
|
||||
"secrets": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"existingSecret": { "type": "string", "description": "Name of an externally managed Secret. Production / GitOps path." },
|
||||
"relayPrivateKey": { "type": "string" },
|
||||
"gitHookHmacSecret": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"relay": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"bindAddr": { "type": "string", "minLength": 1 },
|
||||
"maxConnections": { "type": "integer", "minimum": 1 },
|
||||
"maxConcurrentHandlers": { "type": "integer", "minimum": 1 },
|
||||
"sendBuffer": { "type": "integer", "minimum": 1 },
|
||||
"requireAuthToken": { "type": "boolean" },
|
||||
"requireRelayMembership": { "type": "boolean" },
|
||||
"allowNipOaAuth": { "type": "boolean" },
|
||||
"pubkeyAllowlist": { "type": "boolean" },
|
||||
"corsOrigins": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"ephemeralTtlOverride": { "type": "integer", "minimum": 0 }
|
||||
}
|
||||
},
|
||||
"service": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"type": { "type": "string", "enum": ["ClusterIP", "NodePort", "LoadBalancer"] },
|
||||
"port": { "type": "integer", "minimum": 1, "maximum": 65535 },
|
||||
"healthPort": { "type": "integer", "minimum": 1, "maximum": 65535 },
|
||||
"metricsPort": { "type": "integer", "minimum": 1, "maximum": 65535 }
|
||||
}
|
||||
},
|
||||
"serviceAccount": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"create": { "type": "boolean" },
|
||||
"name": { "type": "string" },
|
||||
"annotations": { "type": "object" }
|
||||
}
|
||||
},
|
||||
"podDisruptionBudget": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"minAvailable": { "oneOf": [{ "type": "integer" }, { "type": "string" }] },
|
||||
"maxUnavailable": { "oneOf": [{ "type": "integer" }, { "type": "string" }] }
|
||||
}
|
||||
},
|
||||
"ingress": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"className": { "type": "string" },
|
||||
"annotations": { "type": "object" },
|
||||
"hosts": { "type": "array" },
|
||||
"tls": { "type": "array" }
|
||||
}
|
||||
},
|
||||
"httproute": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"parentRefs": { "type": "array" },
|
||||
"hostnames": { "type": "array", "items": { "type": "string" } },
|
||||
"rules": { "type": "array" }
|
||||
}
|
||||
},
|
||||
"persistence": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"git": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"mountPath": { "type": "string", "minLength": 1 },
|
||||
"storageClass": { "type": "string" },
|
||||
"accessMode": { "type": "string", "enum": ["ReadWriteOnce", "ReadWriteMany", "ReadOnlyMany", "ReadWriteOncePod"] },
|
||||
"size": { "type": "string", "pattern": "^[0-9]+(\\.[0-9]+)?(E|P|T|G|M|K|Ei|Pi|Ti|Gi|Mi|Ki)?$" },
|
||||
"annotations": { "type": "object" },
|
||||
"existingClaim": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"postgresql": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"properties": { "enabled": { "type": "boolean" } }
|
||||
},
|
||||
"externalPostgresql": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"url": { "type": "string", "pattern": "^(postgres(ql)?://.+)?$" }
|
||||
}
|
||||
},
|
||||
"redis": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"properties": { "enabled": { "type": "boolean" } }
|
||||
},
|
||||
"externalRedis": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"url": { "type": "string", "pattern": "^(rediss?://.+)?$" }
|
||||
}
|
||||
},
|
||||
"typesense": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"url": { "type": "string", "pattern": "^(https?://.+)?$" },
|
||||
"apiKey": { "type": "string" },
|
||||
"image": { "type": "string", "minLength": 1 },
|
||||
"persistence": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"size": { "type": "string", "pattern": "^[0-9]+(\\.[0-9]+)?(E|P|T|G|M|K|Ei|Pi|Ti|Gi|Mi|Ki)?$" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"s3": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"endpoint": { "type": "string", "pattern": "^(https?://.+)?$" },
|
||||
"bucket": { "type": "string", "minLength": 1 },
|
||||
"accessKey": { "type": "string" },
|
||||
"secretKey": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"minio": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"image": { "type": "string", "minLength": 1 },
|
||||
"mcImage": { "type": "string", "minLength": 1 },
|
||||
"persistence": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"size": { "type": "string", "pattern": "^[0-9]+(\\.[0-9]+)?(E|P|T|G|M|K|Ei|Pi|Ti|Gi|Mi|Ki)?$" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"git": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"maxPackBytes": { "type": "integer", "minimum": 1 },
|
||||
"maxReposPerPubkey": { "type": "integer", "minimum": 1 },
|
||||
"maxConcurrentOps": { "type": "integer", "minimum": 1 }
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"autoMigrate": { "type": "boolean" },
|
||||
"preUpgradeJob": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"resources": { "type": "object" },
|
||||
"backoffLimit": { "type": "integer", "minimum": 0 },
|
||||
"activeDeadlineSeconds": { "type": "integer", "minimum": 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"serviceMonitor": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"namespace": { "type": "string" },
|
||||
"interval": { "type": "string" },
|
||||
"scrapeTimeout": { "type": "string" },
|
||||
"labels": { "type": "object" }
|
||||
}
|
||||
},
|
||||
"extraManifests": {
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
# Default values for buzz.
|
||||
#
|
||||
# Two supported tiers:
|
||||
#
|
||||
# PRODUCTION (default) — external Postgres/Redis/Typesense/S3, existingSecret
|
||||
# refs everywhere, no chart-side autogeneration, GitOps-safe (ArgoCD/Flux).
|
||||
# HA-ready: replicaCount >= 2 (requires Redis and RWX storage for git).
|
||||
#
|
||||
# QUICKSTART — bundles in-cluster Postgres + Redis + MinIO + Typesense and
|
||||
# auto-generates relay secrets via the `lookup` pattern (NOT GitOps-safe —
|
||||
# see README), single replica, evaluation only. Opt in by enabling each
|
||||
# bundled service: postgresql.enabled, redis.enabled, minio.enabled,
|
||||
# typesense.enabled. See ci/quickstart-values.yaml and the README.
|
||||
#
|
||||
# See examples/argocd-app.yaml and examples/flux-helmrelease.yaml for the
|
||||
# canonical GitOps configurations.
|
||||
|
||||
# Intent marker for the evaluation profile, surfaced in NOTES.txt. It does NOT
|
||||
# by itself enable any bundled service — set the per-service .enabled flags
|
||||
# (postgresql / redis / minio / typesense) to bring them up in-cluster.
|
||||
quickstart: false
|
||||
|
||||
# ── Image ────────────────────────────────────────────────────────────────────
|
||||
image:
|
||||
repository: ghcr.io/block/buzz
|
||||
tag: "" # empty → .Chart.AppVersion
|
||||
pullPolicy: IfNotPresent
|
||||
pullSecrets: []
|
||||
|
||||
# ── Topology ────────────────────────────────────────────────────────────────
|
||||
# replicaCount > 1 hard-requires:
|
||||
# - Redis for buzz-pubsub (in-cluster or external)
|
||||
# - ReadWriteMany storage for the git PVC (or shared FS via existingClaim)
|
||||
replicaCount: 1
|
||||
|
||||
# ── Public URL ───────────────────────────────────────────────────────────────
|
||||
# Required. The wss:// URL clients use to connect. Drives:
|
||||
# - RELAY_URL env (relay-side)
|
||||
# - Default mediaBaseUrl (https://<host>/media)
|
||||
# - Default ingress host
|
||||
relayUrl: ""
|
||||
mediaBaseUrl: ""
|
||||
|
||||
# ── Owner ────────────────────────────────────────────────────────────────────
|
||||
# 64-char lowercase hex Nostr pubkey of the relay operator. Required when
|
||||
# relay.requireRelayMembership=true (the production default).
|
||||
ownerPubkey: ""
|
||||
|
||||
# ── Chart-managed secrets ────────────────────────────────────────────────────
|
||||
# Production / GitOps path: create a Secret out-of-band with these keys and
|
||||
# point `secrets.existingSecret` at it. Any key omitted from the existing
|
||||
# Secret falls back to chart-side autogen (only effective at first install).
|
||||
#
|
||||
# Expected keys (all optional unless required by relay config):
|
||||
# BUZZ_RELAY_PRIVATE_KEY — 64-char hex; relay identity (rotation = identity change)
|
||||
# BUZZ_GIT_HOOK_HMAC_SECRET — 32+ chars; required when replicaCount > 1
|
||||
# DATABASE_URL — full Postgres URL (preferred over externalPostgresql.url)
|
||||
# REDIS_URL — full Redis URL with auth
|
||||
# TYPESENSE_URL — Typesense base URL
|
||||
# TYPESENSE_API_KEY — Typesense API key
|
||||
# BUZZ_S3_ACCESS_KEY — S3 access key
|
||||
# BUZZ_S3_SECRET_KEY — S3 secret key
|
||||
secrets:
|
||||
existingSecret: ""
|
||||
# Inline overrides (NOT recommended for production; they land in values).
|
||||
relayPrivateKey: ""
|
||||
gitHookHmacSecret: ""
|
||||
|
||||
# ── Relay behavior ───────────────────────────────────────────────────────────
|
||||
relay:
|
||||
bindAddr: "0.0.0.0:3000"
|
||||
maxConnections: 10000
|
||||
maxConcurrentHandlers: 1024
|
||||
sendBuffer: 1000
|
||||
requireAuthToken: true
|
||||
requireRelayMembership: true
|
||||
allowNipOaAuth: true
|
||||
pubkeyAllowlist: false
|
||||
corsOrigins: []
|
||||
ephemeralTtlOverride: 0
|
||||
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /_liveness
|
||||
port: health
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /_readiness
|
||||
port: health
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /_liveness
|
||||
port: health
|
||||
failureThreshold: 60
|
||||
periodSeconds: 2
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: "500m"
|
||||
memory: "512Mi"
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: "2Gi"
|
||||
|
||||
podAnnotations: {}
|
||||
podLabels: {}
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
topologySpreadConstraints: []
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65532
|
||||
runAsGroup: 65532
|
||||
fsGroup: 65532
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containerSecurityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: false # git writes need a writable repo path
|
||||
terminationGracePeriodSeconds: 60
|
||||
|
||||
extraEnv: []
|
||||
extraEnvFrom: []
|
||||
|
||||
# ── Service ──────────────────────────────────────────────────────────────────
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 3000
|
||||
healthPort: 8080
|
||||
metricsPort: 9102
|
||||
annotations: {}
|
||||
|
||||
serviceAccount:
|
||||
create: true
|
||||
name: ""
|
||||
annotations: {}
|
||||
|
||||
podDisruptionBudget:
|
||||
enabled: true
|
||||
minAvailable: 1
|
||||
maxUnavailable: ""
|
||||
|
||||
# ── Ingress (classic) ────────────────────────────────────────────────────────
|
||||
# Mutually exclusive with httproute.enabled.
|
||||
ingress:
|
||||
enabled: false
|
||||
className: ""
|
||||
annotations: {}
|
||||
hosts: [] # empty → derived from relayUrl
|
||||
tls: [] # [{hosts: [...], secretName: "..."}]
|
||||
|
||||
# ── Gateway API (HTTPRoute) ──────────────────────────────────────────────────
|
||||
httproute:
|
||||
enabled: false
|
||||
parentRefs: []
|
||||
hostnames: []
|
||||
rules: [] # empty → default match-all → service
|
||||
|
||||
# ── Git on-disk state ────────────────────────────────────────────────────────
|
||||
persistence:
|
||||
git:
|
||||
enabled: true
|
||||
mountPath: /var/lib/buzz/git
|
||||
storageClass: ""
|
||||
accessMode: ReadWriteOnce # MUST be ReadWriteMany if replicaCount > 1
|
||||
size: 10Gi
|
||||
annotations: {}
|
||||
existingClaim: ""
|
||||
|
||||
# ── Postgres ─────────────────────────────────────────────────────────────────
|
||||
# Eval-only CloudPirates subchart. The relay's DATABASE_URL is composed in the
|
||||
# chart-managed Secret with a chart-generated password; auth.existingSecret
|
||||
# points this subchart at that same Secret/key so server and client agree.
|
||||
postgresql:
|
||||
enabled: false
|
||||
auth:
|
||||
database: buzz
|
||||
username: buzz
|
||||
existingSecret: '{{ if contains "buzz" .Release.Name }}{{ .Release.Name }}-relay{{ else }}{{ .Release.Name }}-buzz-relay{{ end }}'
|
||||
secretKeys:
|
||||
adminPasswordKey: postgres-password
|
||||
persistence:
|
||||
enabled: true
|
||||
size: 10Gi
|
||||
externalPostgresql:
|
||||
url: "" # postgres://user:pass@host:5432/db
|
||||
|
||||
# ── Redis ────────────────────────────────────────────────────────────────────
|
||||
# Eval-only CloudPirates subchart (standalone). REDIS_URL is composed in the
|
||||
# chart-managed Secret; auth.existingSecret points the subchart at that Secret
|
||||
# so the server password matches the URL the relay dials.
|
||||
redis:
|
||||
enabled: false
|
||||
auth:
|
||||
existingSecret: '{{ if contains "buzz" .Release.Name }}{{ .Release.Name }}-relay{{ else }}{{ .Release.Name }}-buzz-relay{{ end }}'
|
||||
existingSecretPasswordKey: redis-password
|
||||
persistence:
|
||||
enabled: true
|
||||
size: 4Gi
|
||||
externalRedis:
|
||||
url: "" # redis://:pass@host:6379
|
||||
|
||||
# ── Typesense ────────────────────────────────────────────────────────────────
|
||||
# Production: point url/apiKey at an external Typesense service (or supply
|
||||
# TYPESENSE_URL/TYPESENSE_API_KEY via secrets.existingSecret).
|
||||
# Quickstart (`enabled: true`): the chart runs an in-cluster, eval-only
|
||||
# Typesense Deployment and composes TYPESENSE_URL with an autogenerated key.
|
||||
typesense:
|
||||
enabled: false # quickstart: set true for bundled in-cluster Typesense
|
||||
url: ""
|
||||
apiKey: ""
|
||||
image: typesense/typesense:30.2
|
||||
persistence:
|
||||
enabled: true
|
||||
size: 4Gi
|
||||
|
||||
# ── S3 / object storage (media) ──────────────────────────────────────────────
|
||||
# Production: point endpoint/bucket at an external S3-compatible service and
|
||||
# supply credentials (inline below or via secrets.existingSecret).
|
||||
# Quickstart (`minio.enabled: true`): the chart runs an in-cluster, eval-only
|
||||
# MinIO Deployment, creates the bucket via a post-install Job, and composes
|
||||
# the endpoint + autogenerated credentials automatically.
|
||||
s3:
|
||||
endpoint: ""
|
||||
bucket: "buzz-media"
|
||||
accessKey: ""
|
||||
secretKey: ""
|
||||
|
||||
# In-cluster MinIO for the quickstart profile only. Production deploys leave
|
||||
# this disabled and use s3.* (or secrets.existingSecret) against managed S3.
|
||||
minio:
|
||||
enabled: false # quickstart: set true for bundled in-cluster MinIO
|
||||
image: minio/minio:RELEASE.2025-09-07T16-13-09Z
|
||||
mcImage: minio/mc:RELEASE.2025-08-13T08-35-41Z
|
||||
persistence:
|
||||
enabled: true
|
||||
size: 10Gi
|
||||
|
||||
# ── Git server config ────────────────────────────────────────────────────────
|
||||
git:
|
||||
maxPackBytes: 524288000 # 500 MiB
|
||||
maxReposPerPubkey: 100
|
||||
maxConcurrentOps: 20
|
||||
|
||||
# ── Migrations ───────────────────────────────────────────────────────────────
|
||||
# Relay runs sqlx migrations at startup via BUZZ_AUTO_MIGRATE=true.
|
||||
migrate:
|
||||
autoMigrate: true
|
||||
preUpgradeJob:
|
||||
enabled: false
|
||||
resources: {}
|
||||
backoffLimit: 3
|
||||
activeDeadlineSeconds: 600
|
||||
|
||||
# ── Monitoring ───────────────────────────────────────────────────────────────
|
||||
serviceMonitor:
|
||||
enabled: false
|
||||
namespace: ""
|
||||
interval: 30s
|
||||
scrapeTimeout: 10s
|
||||
labels: {}
|
||||
|
||||
# ── Free-form extra manifests ────────────────────────────────────────────────
|
||||
extraManifests: []
|
||||
Reference in New Issue
Block a user