docs(workspace-notes): add settings precedence (.env vs DB)
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) <noreply@anthropic.com>
This commit is contained in:
parent
6c9f76bd70
commit
35ef01fd70
1 changed files with 81 additions and 0 deletions
|
|
@ -63,6 +63,87 @@ the wiped directory. Mitigations:
|
||||||
- Or don't expose the catalog upgrade UI to a workflow that has
|
- Or don't expose the catalog upgrade UI to a workflow that has
|
||||||
mounted-fork extensions.
|
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
|
## Nostr key handling — don't persist plaintext nsecs
|
||||||
|
|
||||||
As of 2026, LNbits's upstream account model server-generates a Nostr
|
As of 2026, LNbits's upstream account model server-generates a Nostr
|
||||||
|
|
|
||||||
Reference in a new issue