diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 4ac9081..b6f379b 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -14,6 +14,9 @@ jobs: - name: Set up Helm uses: azure/setup-helm@v4 + - name: Build dependencies + run: helm dependency build . + - name: Helm lint run: helm lint . --set citadel.secretKey=test diff --git a/CHANGELOG.md b/CHANGELOG.md index c90d8cf..0166f5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ All notable changes to the Citadel Helm chart will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.2.1] - 2026-03-08 + +### Changed + +- **Go runtime migration**: Removed Python/uvicorn command override and `PYTHONPATH` env var from deployment — the Go image's `CMD ["/app/citadel"]` is now used as the entrypoint +- **Init container**: Replaced Python/asyncpg database wait script with a lightweight `busybox` + `nc` TCP check (no longer pulls the full app image for the init container) +- **`passthrough.enabled`**: Default changed from `false` to `true` to match the Go app default +- **`guardrails.openaiModeration`**: Default changed from `false` to `true` to match the Go app default +- Bumped `appVersion` to `0.2.0` + +### Added + +- **`citadel.uiSessionSecret`** value: Exposes `UI_SESSION_SECRET` in the Helm secret so production deployments don't silently CrashLoop when the Go app rejects the default session secret +- **NOTES.txt**: Added post-install guidance for creating an API key and PVC cleanup reminder + +### Fixed + +- Docs: Removed unnecessary "Add Bitnami repo" step (chart uses OCI dependencies) +- Docs: Fixed health response format from `{"status": "healthy"}` to `{"status":"ok"}` +- Docs: Replaced Python `asyncpg` troubleshooting command with `curl` health check +- Docs: Updated image tag references from `0.1.0` to `0.2.0` +- Docs: Added `uiSessionSecret` to production hardening checklist + ## [0.1.0] - 2025-03-03 ### Added diff --git a/Chart.yaml b/Chart.yaml index c30551e..5cdc525 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -4,10 +4,10 @@ description: Helm chart for Citadel AI Gateway. Big Bang ready. type: application # Chart version — increment on each release -version: 0.1.0 +version: 0.2.1 # Application version deployed by this chart -appVersion: "0.1.0" +appVersion: "0.2.1" dependencies: - name: postgresql diff --git a/README.md b/README.md index ba54d6c..28a9045 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ Required keys in your secret: `DATABASE_URL`, `SECRET_KEY`. Optional: `OPENROUTE | `citadel.logLevel` | Log level | `INFO` | | `citadel.autoProvisionUsers` | Auto-create users from headers | `true` | | `citadel.guardrails.enabled` | Enable guardrails | `true` | -| `citadel.passthrough.enabled` | Enable API key passthrough | `false` | +| `citadel.passthrough.enabled` | Enable API key passthrough | `true` | | `citadel.plugins.enabled` | Enable plugin system | `true` | | `citadel.okta.enabled` | Enable Okta OIDC | `false` | | `providers.openrouter.apiKey` | OpenRouter API key | `""` | diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 62ebd7d..f15c504 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -10,12 +10,6 @@ This guide walks you through deploying Citadel AI Gateway on Kubernetes and conf ## Step 1: Install the Chart -### Add the Bitnami dependency repo - -```bash -helm repo add bitnami https://charts.bitnami.com/bitnami -``` - ### Install in evaluation mode Evaluation mode enables dev login so you can explore the UI, create users, and generate API keys without configuring OIDC. @@ -55,7 +49,7 @@ kubectl port-forward svc/citadel 8000:8000 & # Liveness check curl -s http://localhost:8000/health | jq . -# Expected: {"status": "healthy", "version": "0.1.0"} +# Expected: {"status":"ok","version":"0.2.0"} # Readiness check (verifies database connectivity) curl -s http://localhost:8000/health/ready | jq . @@ -252,6 +246,7 @@ Before deploying to production, review this checklist: ### Security - [ ] **Set a strong `secretKey`**: `openssl rand -hex 32` +- [ ] **Set `uiSessionSecret`** for production: `openssl rand -hex 32` - [ ] **Disable dev login**: `citadel.devLoginEnabled: false` (default) - [ ] **Set environment to production**: `citadel.environment: production` (default) - [ ] **Configure OIDC** (Okta) for user authentication @@ -346,7 +341,7 @@ The container image may not be accessible: kubectl describe pod -l app.kubernetes.io/name=citadel ``` -- Verify image exists: `docker pull ghcr.io/radiusmethod/citadel:0.1.0` +- Verify image exists: `docker pull ghcr.io/radiusmethod/citadel:0.2.0` - For private registries, set `imagePullSecrets` ### Database connection failures @@ -380,5 +375,5 @@ This means the database is not connected. Check: ```bash # PostgreSQL connectivity -kubectl exec -it citadel-XXXXX -- python -c "import asyncio, asyncpg, os; asyncio.run(asyncpg.connect(os.environ['DATABASE_URL']))" +kubectl exec -it citadel-XXXXX -- curl -s http://localhost:8000/health/ready ``` diff --git a/templates/NOTES.txt b/templates/NOTES.txt index 75ea5c6..1c28d2a 100644 --- a/templates/NOTES.txt +++ b/templates/NOTES.txt @@ -29,9 +29,21 @@ Dev login is ENABLED. Click "Dev Login" on the UI to get started. Disable for production: --set citadel.devLoginEnabled=false {{- end }} +Next steps — create your first API key: + 1. Open the UI and navigate to API Keys + 2. Click "Create Key" and copy the generated key (starts with sk-citadel-) + 3. Configure your client: + Configure Claude Code to use Citadel: claude config set --global apiBaseUrl http://localhost:8000/v1 Configure OpenAI-compatible clients: export OPENAI_API_BASE=http://localhost:8000/v1 export OPENAI_API_KEY= + +{{- if .Values.postgresql.enabled }} + +NOTE: The bundled PostgreSQL uses a PersistentVolumeClaim that is NOT deleted +on helm uninstall. To fully clean up after uninstalling: + kubectl delete pvc data-{{ .Release.Name }}-postgresql-0 +{{- end }} diff --git a/templates/deployment.yaml b/templates/deployment.yaml index 713852b..ea3a44d 100644 --- a/templates/deployment.yaml +++ b/templates/deployment.yaml @@ -42,33 +42,22 @@ spec: {{- if .Values.postgresql.enabled }} initContainers: - name: wait-for-db - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} + image: busybox:1.37 command: - - python + - sh - -c - | - import asyncio, asyncpg, os, sys, time - async def wait(): - url = os.environ["DATABASE_URL"] - for i in range(30): - try: - conn = await asyncpg.connect(url) - await conn.close() - print("Database is ready") - return - except Exception as e: - print(f"Waiting for database... ({e})") - time.sleep(2) - print("Database not ready after 60s") - sys.exit(1) - asyncio.run(wait()) - env: - - name: DATABASE_URL - valueFrom: - secretKeyRef: - name: {{ include "citadel.secretName" . }} - key: DATABASE_URL + echo "Waiting for {{ .Release.Name }}-postgresql:5432..." + for i in $(seq 1 30); do + if nc -z {{ .Release.Name }}-postgresql 5432; then + echo "Database is ready" + exit 0 + fi + echo "Attempt $i/30 — waiting 2s..." + sleep 2 + done + echo "Database not ready after 60s" + exit 1 securityContext: {{- toYaml .Values.securityContext | nindent 12 }} {{- end }} @@ -78,10 +67,7 @@ spec: {{- toYaml .Values.securityContext | nindent 12 }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} - command: ["uvicorn", "citadel.main:app", "--host", "0.0.0.0", "--port", "8000"] env: - - name: PYTHONPATH - value: /app/src {{- with .Values.extraEnv }} {{- toYaml . | nindent 12 }} {{- end }} diff --git a/templates/secrets.yaml b/templates/secrets.yaml index e485b94..c3c54c7 100644 --- a/templates/secrets.yaml +++ b/templates/secrets.yaml @@ -13,6 +13,9 @@ stringData: DATABASE_URL: {{ .Values.externalDatabase.url | quote }} {{- end }} SECRET_KEY: {{ .Values.citadel.secretKey | quote }} + {{- if .Values.citadel.uiSessionSecret }} + UI_SESSION_SECRET: {{ .Values.citadel.uiSessionSecret | quote }} + {{- end }} {{- if .Values.citadel.okta.enabled }} OKTA_CLIENT_SECRET: {{ .Values.citadel.okta.clientSecret | quote }} UI_SESSION_SECRET: {{ .Values.citadel.okta.sessionSecret | quote }} diff --git a/values.yaml b/values.yaml index 40412ed..0bfa0bc 100644 --- a/values.yaml +++ b/values.yaml @@ -19,15 +19,16 @@ citadel: environment: production logLevel: INFO secretKey: "" + uiSessionSecret: "" apiKeyPrefix: "sk-citadel" autoProvisionUsers: true devLoginEnabled: false guardrails: enabled: true - openaiModeration: false + openaiModeration: true piiFilter: false passthrough: - enabled: false + enabled: true plugins: enabled: true # OIDC / Okta (optional)