config.toml¶
Lives at <datadir>/config.toml. First boot writes a hand-formatted seed with a comment block above every option. Every later write (Settings UI, wizard, Tailscale toggle, TTS save) preserves the comments by rebuilding the file from the same seed template with the current values substituted.
Want to regenerate the commented version after editing? rm config.toml, restart. Pluma re-seeds from defaults.
Listen + paths¶
listen¶
HTTP bind address. CLI -addr and PLUMA_ADDR env both override. Empty falls back to :8787.
:8787— all interfaces (default). Reachable from LAN / Tailscale / anything that can route to the host.127.0.0.1:8787— loopback only. Phone on the same Wi-Fi can't reach it.0.0.0.0:8787— same as:8787; explicit.
card_dirs¶
Extra directories scanned for read-only Tavern cards. Show with an "ext" badge in the UI; can't be edited through the app. CLI -card-dirs / PLUMA_CARD_DIRS env append to this list.
trusted_proxies¶
CIDRs / IPs whose X-Forwarded-For Pluma honours when computing the client IP. Set this when Pluma sits behind a reverse proxy (Caddy, Cloudflare Tunnel, Tailscale Serve, nginx, traefik). Empty means RemoteAddr is the source of truth — correct when Pluma is the edge.
Pluma walks XFF right-to-left, skipping any hop in a trusted CIDR; the first untrusted IP is the real client. Honoured only when the immediate peer is in a trusted CIDR — XFF from anyone else is ignored.
allowed_hosts¶
Hosts allowed to reach /api/*. Empty list = allow all. Patterns:
| Pattern | Matches |
|---|---|
10.0.0.42 |
Exact IP (skips rDNS) |
10.0.0.0/24 |
CIDR range (IPv4 or IPv6) |
host.example.com |
Exact hostname (via rDNS) |
*.example.com |
Any subdomain (rDNS; does NOT include the bare apex) |
* |
Anything (same as empty, but explicit) |
Loopback (127.0.0.1, ::1) is always allowed regardless of patterns. The Tailscale (tsnet) listener bypasses the allowlist by design — tailnet membership is itself an auth gate.
Security & auth¶
encrypt_at_rest¶
AES-256-GCM encryption for stored conversation content. The key lives in the OS keyring (Keychain on macOS, libsecret on Linux, Credential Manager on Windows). Setting false makes new writes plaintext; existing ciphertext keeps being decrypted on read. Strongly recommended to leave on.
log_requests¶
Access-log middleware toggle. Chat content never lives in the log; path-with-id metadata does (redacted to <id>).
require_auth¶
WebAuthn passkey gate on /api/*. With false, the previous wide-open behaviour returns — anyone reaching the listen port gets full access. Useful in throwaway dev environments; never on machines you care about.
loopback_auth_bypass¶
Exempts 127.0.0.1 / ::1 from require_auth. The host user is already keyring-authenticated against the OS; making them pair their laptop to itself is silly. Flip false for lockdown mode (kiosk installs, shared workstations).
open_browser¶
Pop the system browser at the listen URL on startup. CLI -open and PLUMA_OPEN env override. Useful to flip false when running as a service or under a process supervisor.
Tailscale (tsnet)¶
tsnet_enabled¶
Opt-in embedded Tailscale node. When true, Pluma also serves HTTPS on :443 over your tailnet via Tailscale's magic-cert. State persists under <datadir>/tsnet/. First boot prints a login URL (also at /api/tailscale/status) the user opens once to authenticate.
tsnet_hostname¶
Device name on the tailnet. Becomes the leftmost label in the magic-cert SAN (e.g. pluma.<tailnet>.ts.net). Set before first enable; can't be changed without a tsnet_enabled = false cycle.
tsnet_serve_port¶
Vestigial — an earlier build proxied Pluma onto the host tailscaled at a chosen HTTPS port before the tsnet path became the only mode. Safe to leave at default.
Setup state¶
setup_completed¶
Tracked by the first-run wizard. false shows the wizard; true hides it forever. POST /api/setup/complete flips it; you can flip it back manually to re-see the wizard.
builtin_pluma_installed¶
Whether the built-in Pluma character has been seeded into <datadir>/characters/. Stops every boot from silently re-creating a card the user has deliberately deleted. POST /api/characters/restore-pluma bypasses the flag.
Model downloads¶
max_model_download_bytes¶
Per-file size cap on HuggingFace downloads. Generous enough for any real model, tight enough that a hostile or runaway response can't fill the disk. 0 = use default; -1 = no cap.
max_model_download_duration_seconds¶
Wall-clock cap on a single download job. 0 = default; -1 = no cap.
Text-to-speech¶
tts_base_url¶
OpenAI-compatible TTS endpoint speaking /v1/audio/speech. Default expects Kokoro-FastAPI on its standard port. OpenAI hosted and ElevenLabs-compat shims work as drop-ins.
tts_model¶
Model id sent in /v1/audio/speech requests. "kokoro" matches Kokoro-FastAPI; "tts-1" or "tts-1-hd" for OpenAI hosted.
tts_voice¶
Default voice id. Kokoro ships dozens of named voices (af_bella, am_michael, ef_dora, …); empty falls through and the server picks its own default. Per-character voice (when wired) will override this.