From 35ef01fd70f0cf36d30421a28d7d29d57e9abde5 Mon Sep 17 00:00:00 2001 From: Padreug Date: Mon, 25 May 2026 20:32:48 +0200 Subject: [PATCH] docs(workspace-notes): add settings precedence (.env vs DB) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LNbits has two sources of truth for settings depending on lifecycle. The trap: on first boot, .env seeds the DB; on every subsequent boot, the DB row overwrites the in-memory Settings. Editing .env after the first boot is a no-op for editable fields — they can only change via the Admin UI or by clearing rows in system_settings. Documented: - the boot-time read-DB → seed-from-env → overwrite-cached-Settings sequence with file:line references for verification - exceptions where env still wins (super_user, lnbits_admin_ui=False, the full ReadOnlySettings field list) - LNBITS_FIRST_INSTALL_TOKEN rotation does NOT reset settings to env (it's an admin-recovery escape hatch, not a config-refresh) - the deploy-side implication: declarative env vars are a *seed* for EditableSettings, authoritative for ReadOnlySettings — plan your deploy story accordingly Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/lnbits-workspace-notes.md | 81 ++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/docs/lnbits-workspace-notes.md b/docs/lnbits-workspace-notes.md index 728af93..104af1b 100644 --- a/docs/lnbits-workspace-notes.md +++ b/docs/lnbits-workspace-notes.md @@ -63,6 +63,87 @@ the wiped directory. Mitigations: - Or don't expose the catalog upgrade UI to a workflow that has mounted-fork extensions. +## Settings precedence: `.env` seeds the DB on first boot, then the DB wins + +This one trips up most people who deploy lnbits declaratively (via NixOS, +docker-compose, ansible, …). Verified 2026-05-24 against upstream +`lnbits/main`. + +LNbits has two sources of truth for settings depending on lifecycle. +**On boot, when `lnbits_admin_ui=True`** (the default): + +1. Read DB row via `get_super_settings()` + (`lnbits/core/services/users.py:236`). +2. If the DB row is empty → seed it from `.env` via + `init_admin_settings()`. First-boot only. +3. `update_cached_settings(settings_db.dict())` overwrites the + in-memory `Settings` with the DB row. **`.env` values loaded by + Pydantic at startup are clobbered.** + +**Practical consequence:** once an instance has booted once, editing +`.env` and restarting **changes nothing** for editable fields. They can +only be changed via: + +- The Admin UI (`PUT /api/v1/settings`, gated by `check_admin`), or +- Clearing the relevant rows in the `system_settings` table in the + core DB. + +**Exceptions where `.env` still wins on every boot:** + +- `super_user` — env overrides DB explicitly in `users.py`. +- `lnbits_admin_ui=False` — the whole DB-load block is skipped; env + stays authoritative (there's no Admin UI to populate the DB anyway). +- All `ReadOnlySettings` fields (defined in `lnbits/settings.py` — + `EnvSettings` / `PersistenceSettings` / `SuperUserSettings` / + `ExtensionsInstallSettings`). Concretely: + + - `host`, `port` + - `lnbits_path`, `lnbits_data_folder`, `lnbits_extensions_path` + - `lnbits_database_url` + - `auth_secret_key`, `first_install_token` + - `lnbits_title` (the API title — NOT `lnbits_site_title`, which is + editable) + - `lnbits_admin_ui` itself + - `lnbits_allowed_funding_sources` (the *list of which sources can be + enabled* — the per-source credentials live in `FundingSourcesSettings` + → `EditableSettings` and ARE DB-frozen) + + `update_cached_settings` skips any key in `readonly_variables`, so + env-loaded values for these fields survive the DB-load overwrite. + +**Editable** settings — site title/tagline, theme, watchdog thresholds, +fee defaults, rate limits, all per-funding-source credentials, the +whole Admin UI form — get DB-frozen after first boot. + +### `LNBITS_FIRST_INSTALL_TOKEN` rotation ≠ "reset settings to env" + +Rotating the first-install token creates a new super_user account with +a fresh random UUID and re-enables the `/first_install` endpoint. It is +an **escape hatch for a locked-out admin** to re-claim the instance +with a new username/password. It does NOT refresh any settings values +from `.env` — by the time it runs, the DB row has already overwritten +cached `settings`, and `init_admin_settings()` then upserts those same +DB values back, so no env values flow through. + +### Deploy-side implication + +If you manage lnbits config declaratively (NixOS module, ansible role, +docker-compose env file), **editable env vars only take effect on a +fresh install** (empty `settings` table). For an existing deployment, +changing them in your declarative source and redeploying won't change +runtime behavior — you have to edit through the Admin UI OR clear the +relevant rows in `system_settings`. + +The "set it in nix, redeploy, done" mental model only works for +`ReadOnlySettings` fields. Anything an admin can edit in the UI is +DB-frozen post-first-boot. Plan your deploy story accordingly: + +- For `ReadOnlySettings` (host, port, paths, secret_key, …): + declarative source is authoritative on every boot. Reproducible. +- For `EditableSettings` (site title, fees, funding-source creds, …): + declarative source is a *seed*, not authoritative. To re-seed an + existing instance, you must explicitly clear the DB row. + ## Nostr key handling — don't persist plaintext nsecs As of 2026, LNbits's upstream account model server-generates a Nostr