diff --git a/README.md b/README.md index 8dd7b38..8030ac6 100644 --- a/README.md +++ b/README.md @@ -114,40 +114,6 @@ in follow-up passes. The `dev` CLI is the single entry point. See the top of this README for the verb set. -### Claude orientation seeding (optional) - -If you use [Claude Code](https://docs.anthropic.com/en/docs/claude-code), -opt in to having curated CLAUDE.md files seeded into your workspace. -The files live under `files/` in this repo and are symlinked into -`~/dev/` via home-manager's `mkOutOfStoreSymlink` — edits in your -checkout take effect on the next Claude session, no rebuild. - -Wire it in `settings.nix`: - -```nix -devEnv = { - enable = true; - scaffoldPath = "/home//dev/lnbits-sensei"; # absolute path to your checkout - claude = { - enable = true; # ~/dev/lnbits/CLAUDE.md - workspaceOrientation = false; # also ~/dev/CLAUDE.md (opt-in; - # clobbers any existing one) - }; -}; -``` - -- `claude.enable` seeds `~/dev/lnbits/CLAUDE.md` from - `files/lnbits-CLAUDE.md` — project-level orientation that loads for - any Claude session in the lnbits worktree subtree (settings - precedence, Quasar UMD self-closing-tag rule, auth-decorator - distinctions, …). -- `claude.workspaceOrientation` *additionally* seeds `~/dev/CLAUDE.md` - from `files/dev-CLAUDE.md` — generic workspace orientation. Off by - default because most people already maintain their own. - -If you don't use Claude Code, leave both off — the same content is in -`docs/` for human reading. - ### Workspace layout (the `~/dev/` tree) The dev-env module's job is to put every project, every worktree, and @@ -226,33 +192,7 @@ lb dev # cd ~/dev/lnbits/dev lb fix-issue-123 # cd ~/dev/lnbits/fix-issue-123 ``` -## Further reading - -- [`docs/remotes.md`](docs/remotes.md) — the three remote-topology - patterns (upstream-only / github-fork / multi-remote). -- [`docs/upstream-prs.md`](docs/upstream-prs.md) — how to send a PR - upstream using the `~/dev/upstream-prs/` worktree flow. Includes - a primer for anyone new to forks / pull requests. -- [`docs/lnbits-upstream-flow.md`](docs/lnbits-upstream-flow.md) — - reference for how `lnbits/lnbits` itself moves: `dev` / `main` - 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, settings precedence - (`.env` vs DB), Nostr key handling, CLINK protocol scope, fork - versioning. -- [`docs/lnbits-extension-dev.md`](docs/lnbits-extension-dev.md) — - building and maintaining LNbits extensions: auth-decorator - distinctions, FakeWallet vs regtest testing strategy, the - `migrations_fork.py` pattern for keeping fork-only schema deltas - out of upstream-tracked `migrations.py`. -- [`docs/lnbits-frontend-gotchas.md`](docs/lnbits-frontend-gotchas.md) — - Vue/Quasar UMD traps in lnbits page templates: no self-closing - tags, CSS specificity vs Quasar's `!important` utilities, cache - busting via `?v={server_startup_time}`, dark-mode color discipline. - -## Contributing to this scaffold +## Contributing Iteration happens under the sandboxed-claude policy from omnixy's `scripts/sandbox-claude.sh` — a per-launch `.claude/settings.json` diff --git a/docs/lnbits-extension-dev.md b/docs/lnbits-extension-dev.md deleted file mode 100644 index 3f7e27c..0000000 --- a/docs/lnbits-extension-dev.md +++ /dev/null @@ -1,143 +0,0 @@ -# lnbits extension development - -Reference for building and maintaining LNbits extensions — the parts -that catch first-time extension authors and the patterns worth -adopting once you maintain a fork-modified extension. - -## Auth decorators - -Easy to confuse. Different scopes: - -| Decorator | Auth scope | Returns | Use for | -|---|---|---|---| -| `require_invoice_key` | Wallet invoice key — read access | `Wallet` | Read endpoints (balance, payment history) callable with a user's invoice key | -| `require_admin_key` | Wallet admin key — write to **own wallet only** | `Wallet` | Endpoints that create payments / modify wallet state | -| `check_admin` | **LNbits instance admin** (super_user + `lnbits_admin_users`), Bearer token | `Account` | Cross-user / admin-only operations | -| `check_super_user` | LNbits super user only, Bearer token | `Account` | Operations restricted to the single super_user | - -**`require_admin_key` ≠ `check_admin`.** This is the most common -misuse. `require_admin_key` is a *wallet*-level write key — every -user has one for each of their own wallets. `check_admin` is -*instance* admin access. Endpoints that operate on other users' data -or change global settings need `check_admin`, not `require_admin_key`. - -## Testing - -Use **FakeWallet** -(`LNBITS_BACKEND_WALLET_CLASS=FakeWallet`) for testing extension CRUD, -API endpoints, and UI changes. Spin up the full regtest stack only -when end-to-end Lightning payment behavior is actually under test. - -**Why:** regtest takes time to start (docker build + multiple LND/CLN -containers), uses real resources, and adds no fidelity for non-payment -flows. FakeWallet makes payments succeed instantly with no network — -ideal for testing the surrounding logic. - -If you ship a `dev` CLI (see lnbits-sensei's pattern), keep -`--fakewallet` as the default mode for this reason. - -## Fork-migrations pattern (`migrations_fork.py`) - -Long-lived forks need to add schema columns and tables on top of -upstream's. The naive approach — append fork-only migration functions -to `migrations.py` — guarantees merge conflicts on every upstream -rebase. The pattern below sidesteps that by keeping `migrations.py` -byte-identical to upstream and putting fork deltas in a sibling file. - -> **Note:** this pattern requires a patch to LNbits core's -> `migrate_extension_database()` that loads `migrations_fork.py` under -> a `_fork` key in the `dbversions` table. As of 2026 that -> patch is fork-internal (an upstream PR is the natural follow-up). -> If you maintain a fork that ships this patch, the rest of this -> section is the user-facing pattern. - -### Architecture facts - -- `dbversions` lives in the **core LNbits DB** (`database.sqlite3`), - not per-extension. Schema: `(db TEXT PRIMARY KEY, version INT)`. - `update_migration_version` is an INSERT-OR-UPDATE, so a new - `_fork` row appears on first run with no schema migration - needed core-side. -- Extension data tables live in `ext_.sqlite3` (SQLite) or a - Postgres schema named after ``. Created lazily on first - `Database.connect()` via `ATTACH` (SQLite) or `CREATE SCHEMA` - (Postgres). -- The version-bump connection is routed based on `db.schema`: `None` - means a core migration (same connection), set means an extension - (opens a fresh `core_db.connect()` to write `dbversions`). See - `lnbits/core/helpers.py:run_migration`. -- **No cross-DB atomicity.** Extension migration writes commit to - `ext_.sqlite3`; the `dbversions` upsert commits to - `database.sqlite3`. If the extension write succeeds and the - dbversions write fails, the migration is orphaned — re-runs on - next startup. **Every fork migration MUST be idempotent** (use - an `_alter_add_column_safe` wrapper that swallows - duplicate-column errors, `CREATE TABLE IF NOT EXISTS`, etc.). - Self-healing covers the orphan case. - -### Squash recipe (adopting the pattern on an existing fork) - -If your fork has accumulated fork-only migrations interleaved into -`migrations.py`: - -1. **Restore `migrations.py` to upstream-byte-identical content.** - Drop all fork-only `m{NNN}_*` functions and any helper added only - for them. -2. **Create `migrations_fork.py`** with a SINGLE - `m001___schema` function that idempotently applies - every fork-only schema delta the old migrations used to do. One - readable file forever. -3. The squash uses `_alter_add_column_safe` per ALTER and - `CREATE TABLE IF NOT EXISTS` per table — no-ops cleanly on - installs that already ran the old fork migrations. - -### One-time fix on existing installs adopting the pattern - -Installs that previously ran the old fork migrations have their -`dbversions['']` row ahead of upstream (e.g. `events|11` after -fork-only `m007`–`m011`). After moving to `migrations_fork`, the -next upstream rebase that adds e.g. `m007_add_allow_fiat` would -compare `7 > 11 → false` and **silently skip** the new upstream -migration. - -**Reset the row to match upstream's actual migration count** before -the rebase lands: - -```sql --- Run against the CORE DB (database.sqlite3), not the extension DB. -UPDATE dbversions SET version = WHERE db = ''; -``` - -For containerized dev where the file is root-owned inside the -container: - -```bash -docker compose exec lnbits python3 -c "import sqlite3; \ - c = sqlite3.connect('database.sqlite3'); \ - c.execute(\"UPDATE dbversions SET version = WHERE db = ''\"); \ - c.commit()" -``` - -### Upstream-overlap scenario at rebase time - -If upstream eventually adds a schema change you already carry in -`migrations_fork.py` (e.g. they add the same `ALTER TABLE … ADD COLUMN -bar` you shipped), the next rebase creates a problem: fresh installs -work (your `_alter_add_column_safe` guards swallow the dup), but -**existing installs crash** when upstream's now-redundant migration -runs without an idempotency guard (upstream rarely uses them). - -Mitigation at rebase time: - -1. **Prune the redundant block from `migrations_fork.py`** so future - fresh installs get the column from upstream's migration. -2. **Pre-deploy `dbversions` surgery on every affected install**: - `UPDATE dbversions SET version = WHERE db = ''` - so the loader skips the now-overlapping upstream migration. - -Do both — (1) keeps future fresh installs clean, (2) keeps existing -installs alive through the deploy. - -**Don't** patch upstream's migration with idempotency guards in your -fork. That breaks the "migrations.py == upstream byte-identical" -property and reintroduces every-rebase conflicts. diff --git a/docs/lnbits-frontend-gotchas.md b/docs/lnbits-frontend-gotchas.md deleted file mode 100644 index cbd1b7b..0000000 --- a/docs/lnbits-frontend-gotchas.md +++ /dev/null @@ -1,105 +0,0 @@ -# lnbits frontend gotchas - -LNbits ships its UI with **Vue 3 + Quasar 2 as UMD globals** — no -build step, plain Jinja templates with per-page JS. This applies -across lnbits core (`lnbits/templates/*.html`), every extension -(`/templates/`), and every fork that doesn't restructure the -frontend stack. The UMD load model has several traps that don't -manifest under the build-step model most Vue tutorials assume. - -## No self-closing tags - -Per [Quasar's UMD usage rules](https://quasar.dev/start/umd/#usage), -components must use the explicit-close form: - -```html - - - - - - - -``` - -**Why:** UMD-loaded templates are parsed by the browser's HTML parser, -not Vue's compiler. The HTML parser doesn't honor self-close on -non-void elements (per the HTML spec). The close tag gets implied at -the wrong place, nesting breaks silently, and subsequent siblings end -up nested inside the prior component. - -Self-closing is fine in `.vue` SFCs (the build step rewrites them -before the browser sees anything), so if you copy a snippet from a -Vue SFC repo into an LNbits template, **expand all self-closing tags -before saving**. - -## CSS specificity trap - -LNbits applies its own theme overrides on Quasar's typography -utilities (`.text-caption`, `.text-grey-*`, etc.) with `!important`. -Class-based CSS rules in an extension page — *even with `!important`* — -lose this fight unless your selector is strictly more specific than -the upstream rule. - -**Rule:** for per-element typography/color overrides on LNbits pages, -reach for Vue `:style` bindings (or static `style="..."` attrs), not -` - - - - -``` - -Background/border tweaks at card-level via class are fine — the trap -is specifically the typography utilities (`text-*`) and Quasar's -color utilities. - -## Cache busting - -Static assets are served with `?v={server_startup_time}` appended -(see `static_url_for` in `lnbits/helpers.py`). Consequences: - -- **Bumping JS requires a server restart.** Reloading the browser - doesn't help if `?v=` hasn't changed — the browser keeps serving - the cached file. -- **Jinja templates re-render on every request** (the `?v=` is only - on static assets). No restart needed for template edits — just - refresh. - -If a browser keeps serving stale JS after a restart, hard-refresh -(`Ctrl+Shift+R`) to bypass the HTTP cache. - -## Dark-mode color discipline - -LNbits's dark theme inverts text colors on most surfaces but **not** -on `bg-{color}-1` pale-background utilities. Result: a `bg-red-1` -without an explicit text color renders white-on-cream under dark -theme — basically invisible. - -**Rule:** pair every pale-background utility with an explicit dark -text class: - -```html - -
Warning
- - -
Warning
-``` - -Same for `bg-green-1`, `bg-blue-1`, `bg-amber-1`, etc. The `text-grey-9` -choice is the safe default; pick a darker shade if you want stronger -contrast. - -## When in doubt - -Test under both light and dark themes (Quasar's theme toggle is at -the top of every LNbits page once you're logged in). Most of the -above gotchas are silent under one theme and obvious under the other -— don't ship UI without flipping the toggle at least once. diff --git a/docs/lnbits-upstream-flow.md b/docs/lnbits-upstream-flow.md deleted file mode 100644 index 3284f69..0000000 --- a/docs/lnbits-upstream-flow.md +++ /dev/null @@ -1,128 +0,0 @@ -# `lnbits/lnbits` — Upstream Development Flow - -Reference for [`github.com/lnbits/lnbits`](https://github.com/lnbits/lnbits). -Verified against git history on 2026-05-18; bump the date when you -re-verify, and patch this doc if the model has drifted. - -## Branch model - -Two long-lived branches: - -| Branch | Role | How it moves | -|---|---|---| -| `dev` | Integration / staging | One squash-merge commit per PR. Linear. | -| `main` | Release | A non-fast-forward merge of `dev` at each release. | - -`main` never receives feature PRs directly. **Every change reaches -`main` through `dev`.** - -## PR flow → `dev` - -1. Contributor opens a PR targeting `dev` (not `main`). -2. Maintainer squash-merges via the GitHub UI. -3. The resulting commit on `dev` has: - - exactly one parent (linear history), - - subject ending in `(#NNNN)` — the squash-merge signature, - - lowercase conventional-commit prefix: `feat:`, `fix:`, `chore:`, - `chore(deps):`, `docs:`, `ci:`, `test:`, `refactor:`. -4. CI runs on the PR; nothing else is required between merge and the - commit appearing on `dev`. - -Example chain on `dev`: - -``` -c9c68bd8 Fix: Use default reaction on bootstrap (#3965) -810a1372 fix: tighten agents file (#3966) -36d696b2 Fix: wrong use of `in` operator (#3960) -8b426efa test: add pyinstrument profiler (#3955) -``` - -Each is a single squashed commit. No merge commits inside `dev`. - -## Release flow → `main` - -When `dev` is ready to ship: - -1. A release-candidate version bump lands on `dev` as a normal PR: - `chore: update to version vX.Y.Z-rcN (#NNNN)`. -2. Validation happens against the RC. -3. A final version-bump PR lands on `dev`: - `chore: update to version vX.Y.Z (#NNNN)`. -4. A maintainer runs the release merge locally: - - ```bash - git checkout main - git pull --ff-only origin main - git merge dev # true (non-FF) merge, default message - git push origin main - git tag vX.Y.Z # tag the merge commit or the bump commit - git push origin vX.Y.Z - ``` - -5. The resulting merge commit on `main` has: - - **two parents** (prior `main` tip + `dev` tip), - - subject exactly `Merge branch 'dev'` (git default when on `main`), - - **not** authored via the GitHub PR-merge UI (that would produce - `Merge pull request #N from …`). - -Because `main` typically carries a stray release-bump commit that isn't -on `dev`, the histories have diverged and git is forced into a true -merge. `--no-ff` is not needed for that reason. - -## Reading the history - -```bash -# Release log (one entry per release): -git log main --first-parent --oneline - -# Full changelog leading into the next release: -git log dev --oneline - -# Verify a merge is a true non-FF merge: -git log -1 --format='%P' # two parent hashes = true merge -``` - -## Diagram - -``` - (PRs squash-merged one at a time) - │ - ▼ -dev: ── A ── B ── C ── D ── E ── F (rc1) ── G ── H (v1.5.4) - ╲ - ╲ (true merge) - ╲ -main: ────────── prev release ─────────────── X ─────────── M - ▲ - │ - Merge branch 'dev' - tag: v1.5.4 -``` - -## Implications for contributors - -- **Base PRs on `dev`.** PRs against `main` will not be accepted. -- **Use lowercase conventional-commit titles.** Verified stable across - the last 25+ merged PRs. -- **Don't expect `main` to move between releases.** It only advances - when a maintainer cuts a release merge. -- **Tagging is on `main`.** Consumers pinning to a tag get the - released state, never a `dev` snapshot. - -## Adapting this in your own fork - -If you maintain a long-lived fork of `lnbits/lnbits` for production use, -you'll likely want to: - -1. Mirror upstream's `dev` / `main` split — easier to track upstream - merges back into your fork. -2. Adopt a version-suffix convention that surfaces fork identity in - tags and the packaged `pyproject.toml` version, e.g. - `v-.`. This makes it unambiguous in logs and - deployed-package metadata that you're running fork-modified code. -3. Decide whether you also need pre-release channels (`-rcN`, `-devN`) - on top of upstream's; useful if your fork ships to staging hosts - before promotion. - -Specifics depend entirely on your team's needs — this scaffold -deliberately doesn't prescribe a fork-versioning scheme. diff --git a/docs/lnbits-workspace-notes.md b/docs/lnbits-workspace-notes.md deleted file mode 100644 index 104af1b..0000000 --- a/docs/lnbits-workspace-notes.md +++ /dev/null @@ -1,243 +0,0 @@ -# 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 ` 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. - -## 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 -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-.` - e.g. `v1.5.4-aio.1`, `v1.5.4-aio.2`, … -- **`pyproject.toml` `version`:** `+.` - 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 (`+.`) 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). diff --git a/docs/upstream-prs.md b/docs/upstream-prs.md deleted file mode 100644 index f1816fa..0000000 --- a/docs/upstream-prs.md +++ /dev/null @@ -1,138 +0,0 @@ -# Contributing back to upstream - -How to send fixes or features upstream (to projects like `lnbits/lnbits`) -using the worktree-based PR flow built into this scaffold's dev-env -layer. - -> **New to forks and pull requests?** Skim the [Primer](#primer) first. -> Otherwise jump to [Workflow](#workflow). - -## Primer - -GitHub, Forgejo, Gitea, and similar services host *git repositories*. -To contribute to a project you don't own: - -1. **Fork** the upstream repo via the web UI ("Fork" button). This - creates `//` — your personal copy. -2. **Branch** off your fork's `main` (or whichever branch upstream - accepts PRs against). Make your change. -3. **Push** the branch to your fork. -4. **Open a Pull Request** from your fork's branch into the upstream. -5. **Iterate** — maintainers review, you push follow-up commits to the - same branch and the PR updates automatically. -6. **Maintainer merges** — usually as a single squashed commit on - upstream's target branch. -7. **Delete the branch** on both sides once merged. - -The three roles a remote can play in your local clone: - -| Remote name | What it points at | Read or write? | -|---|---|---| -| `upstream` | The canonical project you're contributing to | Read (you fetch from it) | -| `github-fork` (or `fork`) | Your personal copy you push to | Write (you push to it) | -| `origin` | Optional: a private mirror you control (forgejo / gitea / codeberg / sourcehut) | Read + write | - -This scaffold's `settings.nix` declares all three via `git.remotes`; -the bootstrap script wires them into the local bare repo so every -worktree sees the same set automatically. See -[remotes.md](remotes.md) for topology patterns. - -## Prerequisites (per project) - -1. **Fork** the upstream repo on the relevant host. -2. **Set your fork URL** in `settings.nix`: - - ```nix - git.remotes.fork = "https://github.com//lnbits"; - ``` - - Use the SSH form (`git@github.com:/lnbits.git`) if - you've set up SSH push. - -3. **Bootstrap** (or re-bootstrap). The (planned) bootstrap script - adds the `github-fork` remote to the bare repo at - `~/dev/repos/lnbits.git`. - -Verify the remotes are wired: - -```sh -git -C ~/dev/repos/lnbits.git remote -v -``` - -Expected at minimum: - -``` -upstream https://github.com/lnbits/lnbits (fetch / push) -github-fork https://github.com//lnbits (fetch / push) -``` - -If `origin` is set, it points at your private remote (forgejo etc.). - -## Workflow - -### 1. Create the PR branch - -```sh -prb lnbits fix-invoice-validation -``` - -The (planned) `prb` helper: - -- `git fetch upstream` -- creates branch `fix-invoice-validation` off `upstream/main` -- adds a worktree at `~/dev/upstream-prs/lnbits-fix-invoice-validation` -- sets the worktree's push target to your fork (`github-fork`) - -### 2. Make changes - -```sh -cd ~/dev/upstream-prs/lnbits-fix-invoice-validation -# … edit, test, commit … -git commit -am "Fix invoice validation for zero-amount invoices" -``` - -### 3. Push to your fork + open the PR - -```sh -git push -u github-fork fix-invoice-validation -``` - -git prints a URL to open the PR. Alternatively, with the GitHub CLI: - -```sh -gh pr create --base main --head :fix-invoice-validation -``` - -(Use the project's preferred target branch — for `lnbits/lnbits` it's -`dev`, not `main`. See [lnbits-upstream-flow.md](lnbits-upstream-flow.md).) - -### 4. Iterate on review - -Push more commits to the same branch — the PR updates automatically. -Don't force-push unless you've squashed/rebased deliberately. - -### 5. After merge — clean up - -```sh -prc lnbits fix-invoice-validation -``` - -The (planned) `prc` helper removes the worktree and prunes the local -branch. On the web UI, click "Delete branch" on the merged PR to -remove your fork's copy too. - -## Common pitfalls - -- **PR target branch.** Many projects (including `lnbits/lnbits`) - accept PRs against `dev`, not `main`. Read the project's - CONTRIBUTING file or its branch-model reference before opening. -- **Stale branches on your fork.** They accumulate forever if you - don't clean up. `prc` handles the local side; the web UI handles - the fork side. -- **Commits drifting onto your fork's `main`.** Keep your fork's - `main` strictly in lockstep with `upstream/main`. All feature - work lives in branches. -- **Wrong remote when pushing.** If `git push` defaults to `upstream` - (because that's `origin`), you'll get an access-denied error. The - worktree's tracking remote should be `github-fork`; if not, set it: - `git branch --set-upstream-to=github-fork/`. diff --git a/files/dev-CLAUDE.md b/files/dev-CLAUDE.md deleted file mode 100644 index 6b2f150..0000000 --- a/files/dev-CLAUDE.md +++ /dev/null @@ -1,61 +0,0 @@ -# `~/dev` — lnbits dev workspace - -You're working in a dev workspace bootstrapped by **lnbits-sensei**. -This file is workspace-level orientation; per-project CLAUDE.md files -(e.g. `~/dev/lnbits/CLAUDE.md`) layer project-specific gotchas on top. - -> This file is a symlink into your lnbits-sensei checkout's -> `files/dev-CLAUDE.md` (via home-manager's `mkOutOfStoreSymlink`). -> Edits should happen in the checkout and be committed there. - -## Layout - -``` -~/dev/ -├── repos/ # bare git repos, one per project -├── // # worktrees per branch -├── upstream-prs// # PR branches against upstreams -├── shared/, scratch/ # workspace utilities -└── refs/ # curated reference repos (optional) -``` - -Rationale: bare-repos + per-project-worktree directories means one git -object database per project (no clone-per-branch churn) and an explicit -PR contract in `upstream-prs/`. See the lnbits-sensei README's -"Workspace layout" section for the full reasoning. - -## Quick commands - -- `dev up [--fakewallet|--regtest]` — start the lnbits dev server. - Default `--fakewallet` is instant, no docker, good for - extension/UI/API work. -- `dev down` / `dev logs` / `dev shell` — control / inspect. -- *(planned)* `prb ` — create a PR worktree branched - from `upstream/main` under `~/dev/upstream-prs/`. -- *(planned)* `lb ` — cd shortcut into `~/dev/lnbits/`. - -## Reference docs - -Full reference lives in the lnbits-sensei checkout's `docs/`: - -- `docs/remotes.md` — three remote-topology patterns (upstream-only / - github-fork / multi-remote-with-private). -- `docs/upstream-prs.md` — PR workflow with a primer for anyone new - to fork-based contribution. -- `docs/lnbits-upstream-flow.md` — how `lnbits/lnbits` itself moves - (the `dev` / `main` branch split, squash-merge convention). -- `docs/lnbits-workspace-notes.md` — practical gotchas: port choice, - `LNBITS_SRC` build-context traps, extension-folder-upgrade wiping - forks, Nostr key handling, CLINK scope, fork versioning, **settings - precedence (`.env` vs DB)**. - -## Per-project orientations - -Per-project CLAUDE.md files are placed at the worktree root: - -- `~/dev/lnbits/CLAUDE.md` — LNbits dev gotchas (settings precedence, - frontend rules, auth decorators, …). - -These are also symlinked from your lnbits-sensei checkout (under -`files/`); the seeding is driven by `lnbits-sensei.devEnv.claude.*` -options. diff --git a/files/lnbits-CLAUDE.md b/files/lnbits-CLAUDE.md deleted file mode 100644 index a091104..0000000 --- a/files/lnbits-CLAUDE.md +++ /dev/null @@ -1,94 +0,0 @@ -# `~/dev/lnbits` — LNbits dev orientation - -Per-project CLAUDE.md for the LNbits worktree subtree. Workspace-level -orientation is at `~/dev/CLAUDE.md` (when wired). Full reference -material lives in your lnbits-sensei checkout under `docs/`. - -> This file is a symlink to `files/lnbits-CLAUDE.md` in your -> lnbits-sensei checkout. Edits should happen there. - -## Critical gotchas (read first) - -### Settings: `.env` seeds the DB on first boot, then DB wins - -After first boot, **editing `.env` and restarting changes nothing** -for editable fields. Only `ReadOnlySettings` (`host`, `port`, -`lnbits_path`, `lnbits_data_folder`, `lnbits_extensions_path`, -`lnbits_database_url`, `auth_secret_key`, `first_install_token`, -`lnbits_title`, `lnbits_admin_ui`, `lnbits_allowed_funding_sources`) -re-read env every boot. - -Editable settings (site title, fees, watchdog, funding-source -credentials, the whole Admin UI form) change only via: - -- `PUT /api/v1/settings` (Admin UI), or -- Clearing the relevant rows in the `system_settings` table. - -Deploy-side: declarative env vars are a *seed* for editable fields, -not authoritative. Plan accordingly. - -Full reference: `docs/lnbits-workspace-notes.md` → "Settings precedence". - -### Frontend templates: NO self-closing tags - -LNbits pages load Vue 3 + Quasar 2 as UMD globals. The browser's -HTML parser doesn't honor self-close on non-void elements — the -close tag gets implied at the wrong place and subsequent siblings -end up inside the prior component. - -```html - - -``` - -Applies to `lnbits/templates/*.html`, every extension's `templates/`, -every fork. Fine in `.vue` SFCs (the build step rewrites them) — if -you copy a snippet from a Vue SFC repo into an LNbits template, -**expand all self-closing tags before saving**. - -### Auth decorators - -Easy to confuse. Different scopes: - -| Decorator | Auth scope | -|---|---| -| `require_invoice_key` | Wallet invoice key — read access | -| `require_admin_key` | Wallet admin key — write to **own wallet only** | -| `check_admin` | **LNbits instance admin** (super_user / admin users) | -| `check_super_user` | LNbits super user only | - -`require_admin_key` ≠ `check_admin`. Use `check_admin` for -cross-user / admin-only endpoints. - -### Upstream PRs target `dev`, not `main` - -PRs to `github.com/lnbits/lnbits` go to `dev`. `main` only moves on -release merges. Use lowercase conventional-commit titles -(`feat:`, `fix:`, `chore:`, `chore(deps):`, `docs:`, `ci:`). - -Full reference: `docs/lnbits-upstream-flow.md`. - -## Default dev workflow - -``` -dev up # FakeWallet, instant -dev up --regtest # multi-node regtest (slower boot, real channels) -``` - -Use **FakeWallet** for extension CRUD / UI / API work. Spin up -**regtest** only when actual channel/payment behavior is under test. - -## Key paths - -- `~/dev/lnbits/main/` — worktree on `main` (your fork's day-to-day) -- `~/dev/lnbits/dev/` — worktree on `dev` (integration / staging) -- `~/dev/upstream-prs/lnbits-/` — PR worktrees branched from - `upstream/main` -- `~/dev/repos/lnbits.git` — the bare repo all the above point at - -## When in doubt - -The canonical reference for any of the above lives in your -lnbits-sensei checkout's `docs/`. Trust the docs over this file when -they diverge — this CLAUDE.md is a quick-reference; the docs are the -source of truth. diff --git a/home.nix b/home.nix index 6ae7214..3f02f44 100644 --- a/home.nix +++ b/home.nix @@ -35,25 +35,4 @@ # Future passes: shell prompt, editor, lnbits dev shell aliases, # direnv integration, tmux launcher, etc. Keep this file thin and # delegate behaviour to modules/. - - # CLAUDE.md seeding — symlinks under ~/dev/ that point at files - # tracked in your lnbits-sensei checkout, so Claude sessions in the - # workspace pick up the curated orientation automatically. Gated on - # `lnbits-sensei.devEnv.claude.*` options in the NixOS config. - # - # `mkOutOfStoreSymlink` keeps the link pointed at your live checkout - # (not the Nix store), so editing the source file takes effect on - # the next Claude session without a rebuild. - home.file = lib.mkMerge [ - (lib.optionalAttrs osConfig.lnbits-sensei.devEnv.claude.enable { - "dev/lnbits/CLAUDE.md".source = - config.lib.file.mkOutOfStoreSymlink - "${osConfig.lnbits-sensei.devEnv.scaffoldPath}/files/lnbits-CLAUDE.md"; - }) - (lib.optionalAttrs osConfig.lnbits-sensei.devEnv.claude.workspaceOrientation { - "dev/CLAUDE.md".source = - config.lib.file.mkOutOfStoreSymlink - "${osConfig.lnbits-sensei.devEnv.scaffoldPath}/files/dev-CLAUDE.md"; - }) - ]; } diff --git a/modules/dev-env/options.nix b/modules/dev-env/options.nix index 03425cb..6f623bb 100644 --- a/modules/dev-env/options.nix +++ b/modules/dev-env/options.nix @@ -97,21 +97,6 @@ in options.lnbits-sensei.devEnv = { enable = mkEnableOption "lnbits-sensei dev-env tooling"; - scaffoldPath = mkOption { - type = types.str; - description = '' - Absolute path to your lnbits-sensei checkout on this machine. - Used to source the seedable CLAUDE.md files (and, later, the - dev-env scripts) via `mkOutOfStoreSymlink` so edits in your - checkout take effect without a rebuild. - - Required when any `claude.*` integration is enabled. Type is - `str` (not `path`) intentionally — `path` would copy the file - into the Nix store and break the out-of-store-symlink semantics. - ''; - example = "/home/alice/dev/lnbits-sensei"; - }; - root = mkOption { type = types.str; default = "/home/${config.lnbits-sensei.user or "user"}/dev"; @@ -123,23 +108,6 @@ in ''; }; - claude = { - enable = mkEnableOption '' - Seed `~/dev/lnbits/CLAUDE.md` from - `''${scaffoldPath}/files/lnbits-CLAUDE.md` so Claude sessions - anywhere in the lnbits worktree subtree pick up project-specific - orientation (settings precedence, frontend rules, auth - decorators, …) - ''; - - workspaceOrientation = mkEnableOption '' - Also seed `~/dev/CLAUDE.md` from - `''${scaffoldPath}/files/dev-CLAUDE.md`. Skip if you already - maintain a workspace-level CLAUDE.md — this option clobbers - whatever is there - ''; - }; - projects = mkOption { type = types.attrsOf projectType; default = { };