docs: surface lnbits workspace gotchas as a committed reference
New docs/lnbits-workspace-notes.md collects the day-to-day gotchas that have repeatedly surprised people doing lnbits dev work — content that lives in personal CLAUDE.md / memory notes elsewhere but is genuinely useful as a public reference once stripped of identity. Sections: - pick a non-default port (5000 collides with macOS AirPlay etc.) - LNBITS_SRC + docker-compose build context (commits don't reach the dev image if the build context points at a stale worktree; `docker compose config | grep -A2 lnbits` to verify) - extension folder mounted as install target: clicking "Upgrade" in the LNbits UI extracts the catalog tarball over the mounted fork checkout, wiping .git - Nostr key handling: upstream stores user nsecs plaintext in accounts.prvkey; signer-abstraction shape (LocalSigner with envelope-encrypted blob / RemoteBunkerSigner via NIP-46 / ClientSideOnlySigner) with the "prefer don't-store-the-key" principle - CLINK protocol scope: Shocknet's 21001-21003 event kinds are Lightning.Pub-specific; LNbits has zero CLINK knowledge; how to think about cross-system designs without conflating them - fork versioning: v<upstream>-<tag>.<N> + the PEP 440 `+<tag>.<N>` package-version form for pyproject.toml - codebase reading tips (key paths, --first-parent log reading) Linked from README "Further reading". No personal identity in the new doc — scrubbed of aiolabs/atitlan/bohm/etc references. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
30fbd0cef9
commit
6c9f76bd70
2 changed files with 167 additions and 0 deletions
|
|
@ -202,6 +202,11 @@ lb fix-issue-123 # cd ~/dev/lnbits/fix-issue-123
|
||||||
- [`docs/lnbits-upstream-flow.md`](docs/lnbits-upstream-flow.md) —
|
- [`docs/lnbits-upstream-flow.md`](docs/lnbits-upstream-flow.md) —
|
||||||
reference for how `lnbits/lnbits` itself moves: `dev` / `main`
|
reference for how `lnbits/lnbits` itself moves: `dev` / `main`
|
||||||
branch model, squash-merge convention, release tagging.
|
branch model, squash-merge convention, release tagging.
|
||||||
|
- [`docs/lnbits-workspace-notes.md`](docs/lnbits-workspace-notes.md) —
|
||||||
|
practical gotchas and design constraints for day-to-day lnbits work:
|
||||||
|
port choice, `LNBITS_SRC` build-context traps, the
|
||||||
|
extension-folder-upgrade-wipes-fork issue, Nostr key handling,
|
||||||
|
CLINK protocol scope, fork versioning.
|
||||||
|
|
||||||
## Contributing to this scaffold
|
## Contributing to this scaffold
|
||||||
|
|
||||||
|
|
|
||||||
162
docs/lnbits-workspace-notes.md
Normal file
162
docs/lnbits-workspace-notes.md
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
# lnbits workspace notes
|
||||||
|
|
||||||
|
Practical reference for day-to-day work in an lnbits dev environment.
|
||||||
|
Collected gotchas, conventions, and design constraints that have
|
||||||
|
repeatedly surprised people. Not a tutorial — assumes you're already
|
||||||
|
running lnbits and contributing or extending it.
|
||||||
|
|
||||||
|
## Pick a non-default port for your local dev server
|
||||||
|
|
||||||
|
LNbits defaults to `:5000`. That collides with macOS AirPlay Receiver,
|
||||||
|
common docker-compose stacks, and other dev tools. Pick something
|
||||||
|
unambiguous (many forks settle on `:5001`) and stick to it across:
|
||||||
|
|
||||||
|
- `settings.nix` → `lnbits.port`
|
||||||
|
- any MCP server config you wire against your dev instance
|
||||||
|
- bookmarks, env files, CI configs
|
||||||
|
|
||||||
|
Once you wire it in three places, switching the port retroactively is
|
||||||
|
mostly find-and-replace pain. Decide early.
|
||||||
|
|
||||||
|
## LNBITS_SRC and docker-compose build context
|
||||||
|
|
||||||
|
If you run a docker-compose dev stack (regtest or otherwise) that
|
||||||
|
builds lnbits from a local checkout, the `Dockerfile` typically reads
|
||||||
|
from `${LNBITS_SRC:-/some/default}`.
|
||||||
|
|
||||||
|
The trap: **commits to your day-to-day worktree don't reach the dev
|
||||||
|
image if `LNBITS_SRC` is currently pointed elsewhere** (e.g. at a
|
||||||
|
feature-branch worktree you were testing). Even
|
||||||
|
`docker compose build --no-cache` happily rebuilds from the wrong
|
||||||
|
checkout.
|
||||||
|
|
||||||
|
Sanity-check before assuming a rebuild picked up your change:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose config | grep -A2 lnbits
|
||||||
|
# look for the resolved `context:` path
|
||||||
|
```
|
||||||
|
|
||||||
|
If it's pointing somewhere stale, either cherry-pick your commit onto
|
||||||
|
the active build branch or flip `LNBITS_SRC` and rebuild.
|
||||||
|
|
||||||
|
## Extension folder is the install target — "Upgrade" wipes forks
|
||||||
|
|
||||||
|
If your dev setup mounts an extension checkout directly into the lnbits
|
||||||
|
container (e.g. `~/dev/shared/extensions/` → `/shared`, with
|
||||||
|
`LNBITS_EXTENSIONS_PATH=/shared`), **the extension git checkout IS the
|
||||||
|
installed extension**. There's no separate copy.
|
||||||
|
|
||||||
|
Consequence: clicking **"Upgrade"** in the LNbits UI on an extension
|
||||||
|
that's mounted from a fork checkout will:
|
||||||
|
|
||||||
|
1. Download the catalog tarball.
|
||||||
|
2. Extract it directly over the mounted directory.
|
||||||
|
3. **Wipe `.git`**, replace every file with the catalog version, and
|
||||||
|
silently discard any local changes / fork patches.
|
||||||
|
|
||||||
|
If this happens, recover with `git clone <your-fork-url> <dir>` over
|
||||||
|
the wiped directory. Mitigations:
|
||||||
|
|
||||||
|
- Set an extension version in your fork that always sorts above the
|
||||||
|
catalog's so "Upgrade" never thinks the catalog is newer.
|
||||||
|
- Or don't expose the catalog upgrade UI to a workflow that has
|
||||||
|
mounted-fork extensions.
|
||||||
|
|
||||||
|
## Nostr key handling — don't persist plaintext nsecs
|
||||||
|
|
||||||
|
As of 2026, LNbits's upstream account model server-generates a Nostr
|
||||||
|
private key (`accounts.prvkey`) for every new account and stores it
|
||||||
|
**plaintext in the DB**. The key is returned to clients over HTTPS via
|
||||||
|
auth endpoints. A DB snapshot, backup leak, or curious operator is a
|
||||||
|
wholesale identity compromise for every user.
|
||||||
|
|
||||||
|
If your fork or extensions touch user Nostr keys, design around this.
|
||||||
|
A defensible shape:
|
||||||
|
|
||||||
|
- A `NostrSigner` abstraction with at least three implementations:
|
||||||
|
- `LocalSigner` — server-side key, but envelope-encrypted at rest.
|
||||||
|
User unlocks per session.
|
||||||
|
- `RemoteBunkerSigner` — NIP-46 connection to a bunker the user
|
||||||
|
runs themselves. Server never sees the private key.
|
||||||
|
- `ClientSideOnlySigner` — the server only knows the user's
|
||||||
|
public key. Signing happens entirely in the browser / NIP-07
|
||||||
|
extension / hardware signer.
|
||||||
|
- Optionally a `DelegatedSigner` via NIP-26 for accounts that want
|
||||||
|
a server-side signer for low-value operations while keeping
|
||||||
|
high-value ops client-side.
|
||||||
|
|
||||||
|
Also audit every server-side Nostr key your extensions persist
|
||||||
|
(merchant signing keys, notification keys, transport keys, …) and
|
||||||
|
either encrypt at rest or document why ephemeral is fine.
|
||||||
|
|
||||||
|
The general principle: **prefer "don't store the key" when possible.**
|
||||||
|
Let the user's signer (browser extension, hardware device, NIP-46
|
||||||
|
bunker) do the signing.
|
||||||
|
|
||||||
|
## CLINK is Lightning.Pub-specific, not LNbits
|
||||||
|
|
||||||
|
Shocknet's [CLINK protocol](https://github.com/shocknet/CLINK) — the
|
||||||
|
Nostr-native payment flow with event kinds 21001 (offer/noffer),
|
||||||
|
21002 (debit/ndebit request), 21003 (debit response) — lives inside
|
||||||
|
`Lightning.Pub`, not LNbits. LNbits as of 2026 has zero CLINK
|
||||||
|
knowledge.
|
||||||
|
|
||||||
|
If you're thinking about `ndebit` / `noffer` semantics in an lnbits
|
||||||
|
context:
|
||||||
|
|
||||||
|
- They're Lightning.Pub primitives. LNbits doesn't implement them.
|
||||||
|
- Building "equivalent outcomes" in LNbits typically goes through
|
||||||
|
LNURL + payment-hash subscriptions + opaque `extra` payloads
|
||||||
|
rather than CLINK-style events. Different primitives, similar
|
||||||
|
effective semantics.
|
||||||
|
- If you genuinely need CLINK in lnbits, it's a new transport
|
||||||
|
module, not a tweak to existing code paths. NIP-44 plumbing in
|
||||||
|
the nostr-transport surface is the natural building block.
|
||||||
|
|
||||||
|
Don't conflate the two — keep CLINK semantics on the Lightning.Pub
|
||||||
|
side of any cross-system design.
|
||||||
|
|
||||||
|
## Fork versioning — surface your fork in the version string
|
||||||
|
|
||||||
|
If you maintain a long-lived fork (especially one running in
|
||||||
|
production), the released version string should make it obvious that
|
||||||
|
fork-modified code is what's running. Consumers of the package, log
|
||||||
|
readers, and bug reporters all benefit.
|
||||||
|
|
||||||
|
A common pattern:
|
||||||
|
|
||||||
|
- **Git tag:** `v<upstream-version>-<your-tag>.<N>`
|
||||||
|
e.g. `v1.5.4-aio.1`, `v1.5.4-aio.2`, …
|
||||||
|
- **`pyproject.toml` `version`:** `<upstream-version>+<your-tag>.<N>`
|
||||||
|
e.g. `1.5.4+aio.1`.
|
||||||
|
|
||||||
|
The two spellings differ because PEP 440 doesn't accept
|
||||||
|
hyphen-with-arbitrary-suffix as a valid package version, but the local-
|
||||||
|
version form (`+<tag>.<N>`) is valid. Extensions don't have the PEP 440
|
||||||
|
constraint (their `config.json` is freeform), so the tag form works
|
||||||
|
everywhere except `pyproject.toml`.
|
||||||
|
|
||||||
|
Decide your own tag string and stick to it. The key property is that
|
||||||
|
`importlib.metadata.version("lnbits")` and the LNbits UI footer make
|
||||||
|
it visible to anyone looking.
|
||||||
|
|
||||||
|
For the upstream lnbits branch / release model these tags sit on top
|
||||||
|
of, see [lnbits-upstream-flow.md](lnbits-upstream-flow.md).
|
||||||
|
|
||||||
|
## Reading the codebase fast
|
||||||
|
|
||||||
|
For mass-grep and reference reading, point a tools-friendly mirror of
|
||||||
|
the lnbits source at a known location (see your `~/dev/refs/` setup if
|
||||||
|
you use one). Key paths once you have a checkout:
|
||||||
|
|
||||||
|
- `lnbits/core/services/` — core money flows. Start here for invoice
|
||||||
|
/ payment / wallet logic.
|
||||||
|
- `lnbits/wallets/` — backend implementations. One file per
|
||||||
|
`LNBITS_BACKEND_WALLET_CLASS` value.
|
||||||
|
- `lnbits/extensions/` — first-party extensions. Third-party
|
||||||
|
extensions follow the same shape (`config.json`, `views.py`,
|
||||||
|
`migrations.py`, etc.).
|
||||||
|
- `git log --first-parent dev --oneline` — see the squash-merge
|
||||||
|
sequence into `dev` (see lnbits-upstream-flow.md for why
|
||||||
|
`--first-parent` matters here).
|
||||||
Reference in a new issue