Files
sbbb/docs/conventions.md

186 lines
5.9 KiB
Markdown
Raw Normal View History

# 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=<SCW_SECRET_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. 💅