Configuration
Secrets come from the luna-secrets Secret through secretKeyRef. They are
never literals in the manifest and never logged. The backend runs with no
configuration at all, but most features then report Unavailable or refuse
honestly rather than degrade in silence.
Identity and sessions
| Variable | Default | Purpose |
|---|---|---|
LUNA_OWNER_EMAIL | (empty: reject all) | The allowlist. See Access and sessions. |
LUNA_OWNER_EMAIL_FILE | (unset) | A file holding the allowlist, re-read every 5 s. Wins over LUNA_OWNER_EMAIL. |
LUNA_GOOGLE_CLIENT_ID | (unset: sign-in not configured) | The Google OAuth client id. It is both the OIDC audience and the client id baked into the Flutter build. No client secret is used anywhere, and none should ever be committed. |
LUNA_OIDC_ISSUER | Overrides the OIDC issuer. | |
CORE_ENVELOPE_KEK | required | The platform key-encryption key. LUNA’s appfs key is derived from it. Without it there is no session store and nothing persists. |
LUNA_JWT_SECRET | (none, no fallback) | No longer signs sessions. Still read as the root of the key that signs avatar media URLs, so keep it in the manifest. |
Storage
| Variable | Default | Purpose |
|---|---|---|
OBJECTSTORE_ENDPOINT, OBJECTSTORE_BUCKET, other OBJECTSTORE_* | (unset) | The platform object store, holding the appfs root and avatar media. The full set is documented by the store library. |
LUNA_APPFS_DIR | a temp directory | The local encrypted appfs root. With an object store, it is only the pre-connect fallback for memory and sessions. Without an object store, it is the durable root for everything (local development). |
LUNA_TIMEZONE | UTC | IANA timezone that sets the day boundary for streaks and points. |
Inference
| Variable | Default | Purpose |
|---|---|---|
INFERENCE_REMOTE_URL | (unset: chat fails) | The shared mistral.rs text plane, used for chat only (and model-backed plan and meal work). |
INFERENCE_CONTEXT_SIZE | engine default | The context window the prompt budget is measured against. History is trimmed oldest-first to fit. |
LUNA_VISION_REMOTE_URL | (unset: vision RPCs fail FailedPrecondition) | The dedicated vision tier. A comma-separated list is tried in order. A single in-cluster .svc. name also gets its -direct sibling added as a failover. |
LUNA_VISION_MODEL | default | Model name sent to the vision tier. |
LUNA_EMBED_URL | (unset: keyword-only memory) | The embedding endpoint for semantic recall. When it is unset, the boot log says semantic recall INACTIVE. |
LUNA_EMBED_MODEL | default | Embedding model name on every request. |
LUNA_EMBED_API_KEY | (unset) | Optional key for the embedding endpoint. |
EMBEDDING_FORMAT | qwen3-embedding | The embedding instruction format: qwen3-embedding, nomic-v1.5 or raw. An unknown name stops the server at boot. Changing it opens a new, empty memory index per account, and nothing here re-embeds old memories. |
LUNA_EMBEDDING_DIM | 768 | Width of every memory index. |
LUNA_AGENT_CONFIG | agents/luna.ini | Per-function sampling settings (sections such as chat, plan-draft, meal-suggest). A missing file is non-fatal: engine defaults apply. |
LUNA_JUDGEMENT | on | The judging ladder over model proposals. off, false, 0, no or disabled switches it off. Off is not a pass: proposals are shown unjudged, and health-adjacent ones still say “not verified”. |
LUNA_ADMIT_MAX | (unset: direct dispatch) | Turns on the in-process admission scheduler for vision and photo-plan calls, ordered by level tier. A non-numeric value fails startup. Leave it off while chasing a timeout. |
LUNA_ADMIT_QUEUE | 4 when LUNA_ADMIT_MAX is set | The number of callers that may wait behind the admission slots. Never unbounded. |
LUNA_GATE_MAX_INFLIGHT, LUNA_GATE_FIRST_TOKEN_SECONDS | (unset: inert) | The shared inference library’s pre-submission backpressure gate. It refuses a request whose remaining deadline can’t cover the wait plus the decode. |
Voice
| Variable | Default | Purpose |
|---|---|---|
VOXTRAL_REMOTE_URL | (unset) | The in-cluster Voxtral speech-to-text endpoint. When it is unset, Transcribe answers voice transcription not configured (VOXTRAL_REMOTE_URL unset). |
TTSD_URL | http://ttsd.inference-system.svc.cluster.local:5005/v1/audio/speech | The platform’s ttsd speech service. It works with no configuration. Set it to "" to disable server speech, and clients then use on-device TTS. |
Limits
| Variable | Default | Purpose |
|---|---|---|
LUNA_RATE_RPS / LUNA_RATE_BURST | 5 / 20 | Transport rate limit per session. The two defaults apply independently. |
LUNA_CHAT_TURNS_PER_HOUR | 30 | Model chat turns per hour. Deterministic plan actions and habit logging are free. 0 disables the cap. |
LUNA_VISION_CALLS_PER_HOUR | 12 | Vision calls per hour: meal photos, form review and plan-from-photos. 0 disables the cap. |
LUNA_VOICE_CALLS_PER_HOUR | 120 | Transcription plus speech calls per hour. 0 disables the cap. |
Fixed limits, not configurable: at most 3 images per vision request, 6 MiB per image, 16 MiB of audio per transcription, and a 17 MiB gRPC message cap. Deadlines are 6 minutes for chat, 8 for vision and plans, and 4 for voice.
Serving and observability
| Variable | Default | Purpose |
|---|---|---|
PORT | 0 (auto-assign) | The listen port for gRPC, gRPC-Web, /media/ and the web bundle, all multiplexed on one port. |
LUNA_WEB_DIR | (unset: no static serving) | Directory of the Flutter web bundle, served from the same origin as the API. The image sets /app/web. |
CORS_ALLOWED_ORIGINS | * | Comma-separated gRPC-Web origins. Same-origin serving needs no CORS in production. |
APP_VERSION | (unset) | The version stamped on logs. |
CORE_HEALTH_URL | (unset: no reporting) | Where agent health is reported to the CORE console. It carries a bearer token (CORE_HEALTH_TOKEN). |
Outbound transport check
Before it opens its listener, the server checks every outbound endpoint:
INFERENCE_REMOTE_URL, LUNA_VISION_REMOTE_URL, VOXTRAL_REMOTE_URL,
LUNA_EMBED_URL, TTSD_URL, CORE_HEALTH_URL and OBJECTSTORE_ENDPOINT.
Cluster-internal plaintext is allowed. Anything else that would carry personal
data off the cluster unencrypted stops the process, and the error lists
every bad endpoint at once:
luna: N outbound endpoint(s) would carry personal data in cleartext off this cluster:
- …Flutter build-time defines
| Define | Default | Purpose |
|---|---|---|
LUNA_BACKEND_URL | web: the serving origin (or a runtime config.json); native release: https://luna.runink.org; native debug: http://localhost:50051 | The backend base URL |
LUNA_GOOGLE_CLIENT_ID | the production client id | The Google Identity Services client |
Store builds must go through flutter/tool/build_release.sh appbundle|ipa,
which obfuscates the code and writes split debug symbols. Keep those symbols
for each release, because they are the only way to read a crash.