Configuration

Environment variables / CLI flags

| Env var | Flag | Default | Meaning |

|---|---|---|---|

| `MYCELIUM2_DATA_DIR` | `--data-dir` | `/opt/mycelium2/data` | State directory (SQLite, encrypted files, config, certs, assets) |

| `MYCELIUM2_HTTPS_ADDR` | `--https-addr` | `0.0.0.0:443` | HTTPS listen address |

| `MYCELIUM2_HTTP_ADDR` | `--http-addr` | `0.0.0.0:80` | HTTP → HTTPS redirect listener |

| `MYCELIUM2_TLS_CERT` | `--tls-cert` | *(auto self-signed)* | TLS certificate PEM path |

| `MYCELIUM2_TLS_KEY` | `--tls-key` | *(auto self-signed)* | TLS private key PEM path |

| `MYCELIUM2_WEBAUTHN_ORIGIN` | — | `https://localhost` | WebAuthn relying-party origin — the full HTTPS origin (e.g. `https://mycelium.example.com`) passkey ceremonies bind to. Domain deployments must set it to their HTTPS origin or the browser refuses passkey ceremonies; invalid values fail boot |

| `RUST_LOG` | — | `info` | Log filter (e.g. `mycelium_web=debug,info`) |

Service key

The service key encrypts all global-scope data (bookshelves, library

stacks, global skills). It is created automatically on first boot and

stored `0600` in `<data_dir>/config/service-key`. Back it up with the

data directory — without it, global data is unrecoverable.

Runtime config (admin portal)

Stored in SQLite, edited via the admin portal — no environment

variables for runtime settings:

- **LLM backend** — OpenAI-compatible base URL + model, plus an

optional API key (sent as a Bearer auth header when set, omitted

otherwise — keyless endpoints like plain Ollama keep working). The

key is sealed at rest with the service key and never rendered back

(the form shows only whether one is stored); a blank save keeps the

stored key. Default `http://localhost:11434/v1` (Ollama). Used by

the librarian for chapter/section extraction and the /chat agent;

unreachable backends fall back to a heuristic catalog (ingest never

fails on LLM errors).

- **Upload limits** — maximum book upload size (MiB, 1–255; default

32, the original Mycelium's default). Takes effect immediately.

- **Security settings** — tunable within guardrails (out-of-range

values are clamped, on save and on read):

- Session TTL: 15–10080 minutes (default 720 = 12h; applies to new

logins)

- Login failures before lockout: 3–10 (default 3)

- Lockout backoff cap: 30–3600 seconds (default 30)

- Minimum password length: 12–128 (default 20)

- Passage cap: 16384–1048576 chars (default 131072 = 128k)

- **OIDC SSO** — issuer URL, client ID/secret (encrypted at rest with

the service key), redirect URI. Disabled until configured.

LLM concurrency (Admin → LLM)

How many LLM runs may execute at once, and how long each surface will

wait for a slot. These are **not** environment variables: they live in

the sealed `llm` config row and are edited on `/admin/llm`, beside the

backend URL and model.

One process-global admission gate owns the slots. Chat turns, MCP

queries, the stateless chat endpoint and dream are *wait class* — they

join a FIFO queue and wait up to their surface's bound. The

mutation-queue drain and the chat resume worker are *try class*: they

take a slot only when nothing is queued and one is free, so

background work never runs ahead of an interactive waiter and never

blocks.

| Field | Default | Range | Meaning | Used by |

|---|---|---|---|---|

| `max_parallel` | 2 | 1–64 | Concurrent admitted LLM runs (the gate's slot count) | every surface |

| `per_user_active` | 2 | 1–16 | Admitted wait-class runs one user may hold at once; further turns keep their queue place rather than being rejected | every wait-class surface |

| `query_wait_secs` | 30 | 1–600 | Wait bound before giving up on a slot | MCP `memory_query`, stateless `POST /api/v1/chat` |

| `chat_wait_secs` | 600 | 1–7200 | Wait bound before giving up on a slot | chat-session turns, `POST /api/v1/dream` |

| `chat_queue_depth` | 64 | 1–1024 | Cap on total queued waiters; a chat submit beyond it is refused `429` before any row is written | chat-session submits |

Behavior:

- **Clamped, never rejected.** Out-of-range values are clamped into

the range on save *and* on load, so a hand-edited or older row can

never open an unbounded gate.

- **Blank keeps the stored value.** A field left empty — or carrying

something that is not a number — keeps what is stored, so an old

cached form still saves the URL/model without resetting the gate.

- **Applies immediately.** A successful save pushes the new limits

onto the running gate: raising `max_parallel` admits queued waiters

on the spot, lowering it only holds future admissions (a running

agent is never preempted). No restart.

- **Synced at boot**, before the boot sweeps and the background

workers start, so the stored row governs the first runs of the

process rather than the built-in defaults.

What each bound does when it elapses: a chat turn finalizes `failed`

with a capacity message; MCP `memory_query` falls back to the

deterministic keyword search (it never errors on capacity); the

stateless chat and dream endpoints answer `429` with `Retry-After: 5`.

Exact bodies are in API. Gate state is visible on `/health`,

`/metrics` and the admin System page (see

Deployment).

Data directory layout

<data_dir>/
├── config/          # service key, initial admin secrets, JWT keys
├── db/              # SQLite (WAL) — metadata, sessions, index tokens
├── users/<uuid>/    # per-user encrypted concept files
├── library/         # shared encrypted stacks (book texts, catalogs)
├── skills/           # global skills shelf (service-key encrypted)
└── assets/           # served static assets (CSS/JS)

Security-relevant defaults

- Sessions: server-side, cookie `HttpOnly; Secure; SameSite=Strict`;

TTL admin-configurable (default 12h, floor 15min).

- Login throttling with exponential backoff (threshold and cap

admin-configurable within guardrails); TOTP/WebAuthn optional

second factors.

- Forced password change for any admin-reset account (the first admin

is created via `/setup` with an operator-chosen password, so no

forced change applies).

- Password policy: minimum 20 characters.

- Uploads: books capped at the admin-configured limit (default 32 MiB); multipart CSRF enforced.

- MCP: per-user API keys (SHA-256 hashed at rest) as bearer tokens;

the MCP endpoint enforces its own auth and is exempt from the

session/CSRF gates.