Skip to content

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

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

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

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

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

encrypt_at_rest = true

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

log_requests = true

Access-log middleware toggle. Chat content never lives in the log; path-with-id metadata does (redacted to <id>).

require_auth

require_auth = true

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

loopback_auth_bypass = true

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

open_browser = true

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

tsnet_enabled = false

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

tsnet_hostname = "pluma"

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

tsnet_serve_port = 8443

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

setup_completed = false

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

builtin_pluma_installed = false

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

max_model_download_bytes = 107374182400  # 100 GiB

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

max_model_download_duration_seconds = 21600  # 6 hours

Wall-clock cap on a single download job. 0 = default; -1 = no cap.

Text-to-speech

tts_base_url

tts_base_url = "http://127.0.0.1:8880/v1"

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

tts_model = "kokoro"

Model id sent in /v1/audio/speech requests. "kokoro" matches Kokoro-FastAPI; "tts-1" or "tts-1-hd" for OpenAI hosted.

tts_voice

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.