Thesis

Configuring Wisp

4 min read
Guides

Wisp configuration lives in a few clearly separated layers. This guide maps every knob to where you set it.

The layers

  1. Compile-timeconfig/config.exs (all envs), config/dev.exs, config/prod.exs.
  2. Runtimeconfig/runtime.exs (the :prod block reads env vars; required for secrets).
  3. Database — the Settings singleton (id=1), user-editable via the admin UI and audited.
  4. Environmentfly.toml [env] (static), merged with Fly secrets (fly secrets set, runtime-injected, overriding [env]).

Deployment knobs: env vars + Fly secrets

Required in production:

  • DATABASE_PATH — SQLite file on the persistent volume (boot raises if missing)
  • SECRET_KEY_BASE — cookie/session signing key (mix phx.gen.secret)
  • PHX_HOST — public hostname (also derives WebAuthn rp_id/origin and the mailer sender)
  • RESEND_API_KEY — transactional email key (optional; falls back to Mailer.Local)

Other deployment knobs: PORT (default 4000; 8080 on Fly, must match http_service.internal_port), POOL_SIZE (default 5), PHX_SERVER, DNS_CLUSTER_QUERY.

Set only as Fly secrets: LITESTREAM_REPLICA_URL, LITESTREAM_ACCESS_KEY_ID, LITESTREAM_SECRET_ACCESS_KEY, and (optionally) LITESTREAM_ENDPOINT.

Compile-time adapters (in config/config.exs): billing_adapter (the stub), media_adapter (Local), theme_adapter (HEEx), mailer_adapter, plus the Ghost content_api_token and the WebAuthn Wisp.Staff settings.

The admin Settings page (/admin/settings)

All install-wide content config is the Settings singleton, editable by any staff member (manager-only forms noted):

  • Basic identity: site title (required, 1–120 chars), author, contact email, sections, active theme
  • SEO defaults: default meta description, default og:image URL, robots.txt body
  • Reading: posts per page (1–100), date format (strftime), time zone (IANA; unknown zones safely fall back to UTC at render)
  • Discussion: default new-post visibility (public/members/paid), comments-on-by-default, comment moderation (auto_approve/hold), comment blocklist (one term per line, case-insensitive substring), and email-on-new-comment
  • Maintenance (manager-only, lockout-safe): toggle + message. The toggle uses a dedicated mutator that bypasses title validation, so an admin can always turn it back off.

The Appearance page (/admin/appearance, manager-only)

Separate from Settings purely for authorization (it writes the same Settings fields):

  • Accent colour — strict hex (#RGB or #RRGGBB), default #6366F1
  • Heading font — a server-side allowlist (Wisp.Appearance.heading_fonts()), default Inter
  • Custom CSS — free text, stored verbatim and sanitized at render time before injection

The first-run setup wizard (/admin/setup)

Reachable without a staff session only while setup_complete=false; once finished it redirects to the editor.

  1. Self-check — validates DATABASE_PATH and that the request host matches the WebAuthn rp_id.
  2. Name your site — title, author, contact email.
  3. Owner email — creates the owner staff user (idempotent), issues an enrollment token.
  4. Create a passkey — the WebAuthn enrollment ceremony.
  5. Don't lock yourself out (HARD GATE) — satisfy ONE recovery path: connect email (a real verified test-send) or add a second passkey. Finish stays disabled until then.
  6. Finish — flips setup_complete=true, seeds a welcome draft, opens the editor.

Knob → location table

Knob Where to configure Type
Site title / author / contact email Settings page (or wizard step 1) DB Settings
Sections, active theme Settings page DB Settings
SEO description / image / robots.txt Settings page DB Settings
Posts per page, date format, time zone Settings page DB Settings
Default visibility, comments, moderation, blocklist Settings page DB Settings
Maintenance mode + message Settings page (manager-only) DB Settings
Accent color, heading font, custom CSS Appearance page (manager-only) DB Settings
HTTP port, database path, pool size fly.toml [env] / env var Config (runtime.exs)
WebAuthn rp_id / origin runtime.exs (prod) / config.exs (dev) Config
SECRET_KEY_BASE, PHX_HOST, RESEND_API_KEY Fly secrets / env var Config (runtime.exs)
Litestream URL + credentials Fly secrets Config (runtime.exs)
Billing / media / theme adapter config/config.exs Config (compile-time)

Things worth knowing

  • Settings is a singleton (id=1). Wisp.Settings.get() lazily creates it with defaults.
  • Audit logging never stores values — only action metadata and changed keys (never secrets, custom_css, robots_txt, or field contents).
  • Maintenance mode is lockout-safe: anonymous visitors get a 503 + message, but staff, the /admin tree, /auth routes, and static assets are always exempt.