From 32c9575952e4d3122255aa76e63e149bb3180cd8 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Sat, 9 May 2026 18:01:09 -0500 Subject: [PATCH] chore(k8s): unify secrets template into one k8s/secrets.example.yaml Replace the single-secret towerops-llm template with one multi-document YAML covering every Secret the Deployment references: towerops-secrets, towerops-db, towerops-aws, towerops-redis, towerops-billing, towerops-llm. Workflow: copy k8s/secrets.example.yaml to k8s/secrets.yaml (which is gitignored), fill in real values, kubectl apply. Real values never land in git. --- .gitignore | 2 +- k8s/README.md | 19 +++--- k8s/secrets.example.yaml | 93 ++++++++++++++++++++++++++++ k8s/towerops-llm-secret.example.yaml | 27 -------- 4 files changed, 106 insertions(+), 35 deletions(-) create mode 100644 k8s/secrets.example.yaml delete mode 100644 k8s/towerops-llm-secret.example.yaml diff --git a/.gitignore b/.gitignore index b59e5016..0484532a 100644 --- a/.gitignore +++ b/.gitignore @@ -99,7 +99,7 @@ profiles.json # Stride API credentials (contains secrets - never commit) .stride_auth.md -# Local k8s secret manifest — copy from any *.example.yaml in k8s/, fill in +# Local k8s secret manifest — copy from k8s/secrets.example.yaml, fill in # real values, then `kubectl apply -f k8s/secrets.yaml`. Never commit. k8s/secrets.yaml diff --git a/k8s/README.md b/k8s/README.md index 38626662..e5bac7de 100644 --- a/k8s/README.md +++ b/k8s/README.md @@ -13,22 +13,27 @@ Required secrets in the `towerops` namespace: Optional secrets: - `towerops-llm` - DeepSeek API credentials for LLM-powered insight enrichment. Optional — when missing, insights still display without an AI summary. +- `towerops-billing` - Stripe credentials. Every key is optional in the deployment. ### Local secrets workflow -The file `k8s/secrets.yaml` is gitignored. Use it to keep one-or-more -`Secret` manifests with real values for hand-application against the -cluster. Bootstrap from any `*.example.yaml` template: +`k8s/secrets.yaml` is gitignored. Use it to keep every `Secret` manifest +the deployment needs in one place for hand-application against the +cluster. Bootstrap from the unified template: ```bash -cp k8s/towerops-llm-secret.example.yaml k8s/secrets.yaml -# edit k8s/secrets.yaml — fill in DEEPSEEK_API_KEY, etc. +cp k8s/secrets.example.yaml k8s/secrets.yaml +# edit k8s/secrets.yaml — fill in DATABASE_URL, AWS keys, RELEASE_COOKIE, +# SECRET_KEY_BASE, CLOAK_KEY, and any optional ones (DEEPSEEK_API_KEY, +# Stripe keys, etc.) kubectl apply -f k8s/secrets.yaml kubectl rollout restart deployment/towerops -n towerops ``` -`k8s/secrets.yaml` may contain multiple `---`-separated documents if you -need more than one secret. It is excluded from git via `.gitignore`. +`k8s/secrets.yaml` is one multi-document YAML covering every Secret the +deployment references (towerops-secrets, towerops-db, towerops-aws, +towerops-redis, towerops-billing, towerops-llm). It is excluded from git +via `.gitignore` so real values never accidentally land in source. For local development, the project root `.envrc` is used by direnv. diff --git a/k8s/secrets.example.yaml b/k8s/secrets.example.yaml new file mode 100644 index 00000000..4f92d330 --- /dev/null +++ b/k8s/secrets.example.yaml @@ -0,0 +1,93 @@ +# Unified template for every Kubernetes Secret the towerops Deployment +# references. Bootstrap your local copy: +# +# cp k8s/secrets.example.yaml k8s/secrets.yaml +# # edit k8s/secrets.yaml — fill in real values +# kubectl apply -f k8s/secrets.yaml +# kubectl rollout restart deployment/towerops -n towerops +# +# k8s/secrets.yaml is gitignored — never commit real secrets. Real +# values live in 1Password. +# +# Secret-by-secret: +# * towerops-secrets — application secrets (REQUIRED) +# * towerops-db — Postgres connection (REQUIRED, envFrom) +# * towerops-aws — AWS credentials (REQUIRED, envFrom) +# * towerops-redis — Redis connection (REQUIRED, envFrom) +# * towerops-billing — Stripe (optional) +# * towerops-llm — DeepSeek API key for insight enrichment (optional) +# * gitlab-registry — Docker registry creds (image pull, separate workflow) +--- +apiVersion: v1 +kind: Secret +metadata: + name: towerops-secrets + namespace: towerops +type: Opaque +stringData: + # Generate with: openssl rand -base64 32 + RELEASE_COOKIE: "" + # Generate with: mix phx.gen.secret (or openssl rand -base64 64) + SECRET_KEY_BASE: "" + # Generate with: openssl rand -base64 32 + # CRITICAL: losing this makes encrypted columns unrecoverable. + CLOAK_KEY: "" +--- +apiVersion: v1 +kind: Secret +metadata: + name: towerops-db + namespace: towerops +type: Opaque +stringData: + # Full ecto:// URL — used envFrom in the deployment. + DATABASE_URL: "ecto://USER:PASSWORD@HOST:5432/DATABASE" + # Optional pool tuning (defaults applied in runtime.exs if unset) + POOL_SIZE: "" +--- +apiVersion: v1 +kind: Secret +metadata: + name: towerops-aws + namespace: towerops +type: Opaque +stringData: + AWS_ACCESS_KEY_ID: "" + AWS_SECRET_ACCESS_KEY: "" + AWS_REGION: "" +--- +apiVersion: v1 +kind: Secret +metadata: + name: towerops-redis + namespace: towerops +type: Opaque +stringData: + # envFrom block injects whatever keys exist here. + REDIS_URL: "redis://HOST:6379/0" +--- +apiVersion: v1 +kind: Secret +metadata: + name: towerops-billing + namespace: towerops +type: Opaque +stringData: + # Stripe — every key is optional; the deployment marks them optional: true. + STRIPE_SECRET_KEY: "" + STRIPE_WEBHOOK_SECRET: "" + STRIPE_PRICE_ID: "" + STRIPE_METER_ID: "" +--- +apiVersion: v1 +kind: Secret +metadata: + name: towerops-llm + namespace: towerops +type: Opaque +stringData: + # DeepSeek API key — get one at https://platform.deepseek.com/ + # Without this, insight LLM enrichment stays no-op (insights still render). + DEEPSEEK_API_KEY: "" + # Optional: override the default model (default: deepseek-v4-pro). + DEEPSEEK_MODEL: "" diff --git a/k8s/towerops-llm-secret.example.yaml b/k8s/towerops-llm-secret.example.yaml deleted file mode 100644 index 2cf2e87d..00000000 --- a/k8s/towerops-llm-secret.example.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# Template for the towerops-llm secret. Apply with values filled in: -# -# kubectl create secret generic towerops-llm \ -# --from-literal=DEEPSEEK_API_KEY="" \ -# --from-literal=DEEPSEEK_MODEL="deepseek-v4-pro" \ -# -n towerops -# -# Or apply this file directly after replacing the placeholder strings with -# your real values, then run: kubectl apply -f k8s/towerops-llm-secret.example.yaml -# -# This file is committed as a placeholder — DO NOT put real keys in here. -# Real values live in 1Password and are created in the cluster manually. -# -# The deployment references this secret with `optional: true`, so the app -# will start fine without it (LLM enrichment just stays no-op). ---- -apiVersion: v1 -kind: Secret -metadata: - name: towerops-llm - namespace: towerops -type: Opaque -stringData: - # DeepSeek API key — get one at https://platform.deepseek.com/ - DEEPSEEK_API_KEY: "" - # Optional: override the default model (default: deepseek-v4-pro) - DEEPSEEK_MODEL: ""