diff --git a/docs/conventions.md b/docs/conventions.md new file mode 100644 index 0000000..bcb0024 --- /dev/null +++ b/docs/conventions.md @@ -0,0 +1,185 @@ +# House Rules, Darling 🏠 + +These are the conventions for working in The Super Boujee Business Box ✨ infrastructure. Follow them and everything stays clean. Ignore them and things get messy. We don't do messy. + +--- + +## The Don'ts ❌ + +| Rule | Why | +|------|-----| +| No Helm charts for simple resources | Plain YAML when a Deployment + Service + ConfigMap is all you need | +| No Ingress resources | Everything routes through Pingora — add a route in the proxy config | +| No Terraform / Pulumi | Kustomize + sunbeam CLI. That's the stack. | +| No RBAC / NetworkPolicy / PodSecurityPolicy | Linkerd handles mTLS. Keep it simple. | +| No new namespaces without discussion | The namespace layout is intentional | +| No hardcoded domains | Use `DOMAIN_SUFFIX` placeholder — gets replaced at deploy time | +| No TLS keys in Git | Secrets live in OpenBao, synced by Vault Secrets Operator | +| No configMapGenerator / secretGenerator | Plain YAML ConfigMaps and Secrets | +| No commonLabels / commonAnnotations | They cause merge conflicts and confusion | +| No abstract base layers or nested overlays | One level: base → overlay. That's it. | + +--- + +## The Do's ✅ + +### Kustomize Patterns + +**Base holds canonical config.** Everything goes in `base/{namespace}/`: + +``` +base/{namespace}/ +├── namespace.yaml +├── kustomization.yaml +├── {app}-deployment.yaml +├── {app}-service.yaml +├── {app}-config.yaml +├── {chart}-values.yaml # Helm chart values +├── vault-secrets.yaml # VaultStaticSecret + VaultDynamicSecret +├── patch-{what}.yaml # Strategic merge patches +├── {component}-alertrules.yaml # PrometheusRule resources +└── {component}-servicemonitor.yaml +``` + +**Overlays hold only patches.** Environment-specific overrides in `overlays/{env}/`: + +``` +overlays/{env}/ +├── kustomization.yaml +├── patch-*.yaml # Resource limits, env-specific config +└── values-*.yaml # Helm value overrides +``` + +### ConfigMaps & Secrets + +- **ConfigMaps:** Use `envFrom` to inject all keys as env vars +- **Secrets:** Use individual `env` entries with `secretKeyRef` (explicit about what's secret) +- **Shared ConfigMaps** in lasuite namespace: `lasuite-postgres`, `lasuite-valkey`, `lasuite-s3`, `lasuite-oidc-provider`, `lasuite-resource-server` + +### Secret Management + +Every secret follows the Vault pattern: + +```yaml +# Static secret (OIDC keys, Django secrets, DKIM keys) +VaultStaticSecret: + mount: secret + type: kv-v2 + refreshAfter: 30s + +# Dynamic secret (database credentials) +VaultDynamicSecret: + mount: database + path: static-creds/{app} + refreshAfter: 5m + rolloutRestartTargets: + - kind: Deployment + name: {app} +``` + +Dynamic secrets rotate every 5 minutes. The Vault Secrets Operator handles the K8s Secret lifecycle. + +### Deployments + +- **Init containers** run migrations: `python manage.py migrate` +- **Short image names** — the registry is set via `images:` in the overlay kustomization +- **Liveness probes:** `/__heartbeat__` +- **Readiness probes:** `/__lbheartbeat__` +- **Resource limits** on every pod — no unbounded memory +- **Linkerd injection:** `linkerd.io/inject: enabled` annotation on all application namespaces + +### OIDC Clients + +Declare as `HydraOAuth2Client` CRD. Hydra Maester creates the K8s Secret: + +```yaml +apiVersion: hydra.ory.sh/v1alpha1 +kind: OAuth2Client +metadata: + name: oidc-{app} + namespace: lasuite +spec: + grantTypes: [authorization_code, refresh_token] + responseTypes: [code] + scope: openid email profile + redirectUris: + - https://{app}.DOMAIN_SUFFIX/api/v1.0/callback/ + secretName: oidc-{app} +``` + +### Helm Charts + +Used for complex components where plain YAML isn't practical: + +| Chart | Component | +|-------|-----------| +| CloudNativePG | PostgreSQL operator | +| Ory Kratos | Identity management | +| Ory Hydra | OAuth2/OIDC | +| Gitea | Git hosting | +| kube-prometheus-stack | Monitoring | +| Loki | Log aggregation | +| Tempo | Tracing | +| Alloy | Collection agent | +| LiveKit | Video conferencing | +| OpenBao | Secrets management | +| cert-manager | TLS certificates | +| Longhorn | Volume management | +| Drive | File management | +| Docs | Document editing | + +Values go in `{chart}-values.yaml` in the base directory. Overlay-specific overrides in `values-{chart}.yaml`. + +--- + +## Naming Conventions + +| Thing | Pattern | Example | +|-------|---------|---------| +| Deployments | `{app}-deployment.yaml` | `sol-deployment.yaml` | +| Services | `{app}-service.yaml` | `sol-service.yaml` | +| ConfigMaps | `{app}-config.yaml` | `sol-config.yaml` | +| Secrets | From Vault: `{app}-{purpose}` | `messages-django-secret` | +| OIDC clients | `oidc-{app}` | `oidc-drive` | +| Alert rules | `{component}-alertrules.yaml` | `postgres-alertrules.yaml` | +| ServiceMonitors | `{component}-servicemonitor.yaml` | `gitea-servicemonitor.yaml` | +| Dashboards | `dashboards-{domain}.yaml` | `dashboards-ingress.yaml` | +| Helm values | `{chart}-values.yaml` | `drive-values.yaml` | +| Patches | `patch-{what}.yaml` | `patch-drive-wopi-init.yaml` | +| S3 buckets | `sunbeam-{purpose}` | `sunbeam-game-assets` | + +--- + +## AI Configuration + +Three env vars, consistent across all components: + +``` +AI_BASE_URL=https://api.scaleway.ai/v1/ +AI_API_KEY= +AI_MODEL=mistral-small-3.2-24b-instruct-2506 +``` + +Scaleway Generative APIs — hosted in Paris, GDPR-compliant, OpenAI-compatible. ~€1-5/month for a three-person studio. + +--- + +## Domain Pattern + +All apps follow `{prefix}.DOMAIN_SUFFIX`: + +``` +docs, meet, drive, mail, messages, people, find, src, auth, +integration, cal, projects, s3, livekit, metrics, systemmetrics, +systemlogs, systemtracing, vault, search, id, hydra +``` + +`DOMAIN_SUFFIX` is replaced at deploy time: +- Production: `sunbeam.pt` +- Local: `{LIMA_IP}.sslip.io` + +--- + +## The Golden Rule + +If you can derive it from the code, don't store it separately. If it needs to be secret, put it in OpenBao. If it's environment-specific, put it in the overlay. Everything else goes in base. Keep it flat, keep it obvious, keep it boujee. 💅