This repository has been archived on 2026-06-22. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
lnbits-sensei/README.md
Padreug 3d73e02d6b docs(readme): reflect the now-real dev-env tooling
The root README pitched a unified 'dev up' CLI and called the dev-env
stubs — both stale now that the real implementation has landed. Rewrite
the status header, the day-to-day command set, the module file tree, and
drop the (planned)/(stub) markers on bootstrap, prb, and nav helpers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 21:18:56 +02:00

354 lines
14 KiB
Markdown

# lnbits-sensei
> **Status:** working dev-env. Module schema, flake wiring, and the
> dev-env tooling (bootstrap, worktree/nav helpers, regtest wrappers,
> upstream-PR flow, deploy wrapper, declarative tmux) are all real and
> `nix flake check`-green. Fill in `settings.nix` + a project list and
> it's usable end-to-end.
Opinionated, bare-skeleton scaffold for running an LNbits stack
— inspired by the [omnixy](https://github.com/TheArctesian/omnixy)
NixOS module pattern but stripped of any personal identity so the
template is consumable as-is.
The goal: clone, fill in your identity + remote topology + a project
list, rebuild, and get a working multi-project LNbits dev environment —
bare repos + worktrees materialized in one command, with navigation,
sync, regtest, and upstream-PR helpers on PATH:
```
dev-env-bootstrap # materialize bare repos + worktrees on disk
lb dev # cd to the lnbits dev worktree
dev-status # dirty + ahead/behind across every worktree
regtest-start dev # build lnbits from a worktree, bring up regtest
prb lnbits fix-x # spin a throwaway worktree for an upstream PR
```
See [`modules/dev-env/README.md`](modules/dev-env/README.md) for the
full command set and bootstrap walkthrough.
FakeWallet (`LNBITS_BACKEND_WALLET_CLASS=FakeWallet`) is the default
iteration path for extension/frontend work — no docker needed. The
`regtest-*` helpers (gated on `devEnv.regtest.enable`) exist for the
moments you need real channels/payments.
## Remote topology
`settings.nix` exposes three slots so the same scaffold works whether
you push to GitHub only, run a private mirror, or both:
```nix
git.remotes = {
upstream = "https://github.com/lnbits/lnbits"; # always
fork = null; # or "https://github.com/<you>/lnbits"
extras = []; # or [{ name = "private"; url = "ssh://…"; }]
};
```
See [`docs/remotes.md`](docs/remotes.md) for the three canonical
patterns (upstream-only, github-fork-for-PRs, multi-remote-with-private
host on forgejo / gitea / codeberg / sourcehut).
## Repository layout
```
flake.nix # inputs: nixpkgs, home-manager, lnbits-src
flake.lock
settings.nix # consumer fills in: user, host, identity, remotes
configuration.nix # NixOS entry — thin imports list
home.nix # home-manager entry — thin
hardware-configuration.nix # placeholder; overwrite with nixos-generate-config
modules/
├── core.nix # lnbits-sensei.* option schema
├── lib.nix # shared helpers — config.lnbits-sensei.lib
├── secrets.nix # sops-nix wiring (inert until you add a secret)
├── git/
│ └── remotes.nix # remote topology (upstream / fork / extras)
└── dev-env/
├── README.md # command set + bootstrap walkthrough
├── default.nix # module entry; imports options + lib + config
├── options.nix # devEnv schema (projects, deploy, regtest, tmux)
├── lib.nix # mkProject + path/remote helpers
├── config.nix # renders /etc/dev-env/*, installs scripts
├── presets/example.nix # worked, generic project list — copy and edit
├── tests/smoke.nix # nix flake check schema test
└── scripts/
├── bootstrap.sh # materialize bare repos + worktrees
├── nav.sh # lb / g / ext / dep / prs / shared / repos
├── worktree.sh # wt / wts / wtu / wtn + lnbits sync helpers
├── pr-helpers.sh # prb / prc / prl — upstream-PR worktrees
├── rebase.sh # safe fork-onto-upstream rebase
├── status.sh # dev-status — divergence across worktrees
├── deploy.sh # dev-deploy — nixos-rebuild wrapper
├── regtest.sh # regtest-start/-stop/-status (gated)
├── tmux-launch.sh # dev-tm — declarative tmux sessions
└── git-hooks/pre-commit # shared secret-scanner hook
docs/ # human-facing references (also seeded to ~/dev/)
└── remotes.md # the three remote-topology patterns
LICENSE # MIT (placeholders — fill in year + holder)
```
## Development flow
### First-time bootstrap (per machine)
1. **Clone** the repo into a working directory of your choice.
2. **Edit `settings.nix`** — fill in `user`, `hostName`, `timeZone`,
`gitName`, `gitEmail`, and `git.remotes` (see
[docs/remotes.md](docs/remotes.md) for topology patterns).
3. **Replace the placeholder hardware config** with your real one:
```sh
sudo nixos-generate-config --root / --dir $(pwd)
```
(The shipped `hardware-configuration.nix` evaluates but won't boot —
intentional, so a forgotten regeneration fails loudly rather than
silently booting on someone else's disk layout.)
4. **Validate** the schema before activating anything:
```sh
nix flake check --no-build
```
Should print `all checks passed!`.
5. **Switch** when ready:
```sh
sudo nixos-rebuild switch --flake .#<hostName>
```
### Day-to-day
After a rebuild, the dev-env helpers are on PATH (standalone commands)
or sourced into interactive shells (navigation/worktree functions).
Run `dev-env-bootstrap` once to materialize the repos + worktrees you
declared, then:
```
dev-env-bootstrap # bring the ~/dev/ tree up to spec (idempotent)
lb dev / g <cat> <repo> # navigate worktrees
dev-status # divergence + dirty state across everything
wts / wtu <repo> # sync worktrees / fetch upstream + show divergence
rebase status # which forks need rebasing onto upstream
regtest-start dev # build lnbits from a worktree, bring up regtest
prb lnbits fix-x # throwaway worktree for an upstream PR
dev-tm <session> # launch a declared tmux session
dev-deploy <host> # nixos-rebuild against your deploy flake
```
The full command table + bootstrap walkthrough lives in
[`modules/dev-env/README.md`](modules/dev-env/README.md). See
**[Claude orientation](#claude-orientation)** below if you want
AI-assisted work to pick up the project's gotchas for free.
### Workspace layout (the `~/dev/` tree)
The dev-env module's job is to put every project, every worktree, and
every upstream PR in a predictable place — so navigation, grepping
across reference code, and switching between branches stays mechanical.
```
~/dev/
├── repos/ # bare repos, one per project
│ ├── lnbits.git
│ ├── lnbits-extensions.git
│ └── …
├── lnbits/ # one dir per project; worktrees inside
│ ├── main/ # worktree on `main`
│ ├── dev/ # worktree on `dev`
│ └── fix-issue-123/ # ad-hoc feature worktree
├── lnbits-extensions/
│ ├── main/
│ └── …
├── upstream-prs/ # PRs against upstreams (separate tree)
│ ├── lnbits-fix-payment-race/ # each worktree branched from upstream/main
│ └── …
├── shared/ # cross-project utilities / notes
├── scratch/ # ephemeral one-off scratchpads
└── refs/ # curated read-only reference repos
```
Declare which projects materialize via `settings.nix`:
```nix
devEnv.projects = {
lnbits = {
url = "https://github.com/lnbits/lnbits";
worktrees = {
main = { branch = "main"; };
dev = { branch = "dev"; };
};
};
};
```
`dev-env-bootstrap` materializes the bare repo at
`~/dev/repos/lnbits.git`, wires its remotes, and checks out one
worktree per declared entry. It's idempotent — re-running only fills in
what's missing, and never touches a worktree whose branch has drifted
from the spec. See [`docs/remotes.md`](docs/remotes.md) and
[`modules/dev-env/presets/example.nix`](modules/dev-env/presets/example.nix)
for richer project declarations (explicit `url`, `upstream`, `fork`,
`category`, single-clone projects).
**Why bare repos + worktrees** (rather than fresh clones per branch):
- One copy of the git object database on disk — five branches checked
out is five lightweight worktrees, not five clones.
- `git fetch` once and every worktree sees the new refs.
- The worktree directory name *is* the branch — no "wait, what am I on?"
**Why a separate `upstream-prs/` tree:**
PRs against upstream repos (e.g. `lnbits/lnbits`) want to branch from
*upstream's* `main`, not your day-to-day fork. Mixing PR branches into
the per-project tree pollutes the branch list and tempts "I'll just
commit this here" accidents. The `upstream-prs/` tree makes the
contract explicit: this worktree is on `upstream/main`, branch off,
push to your fork, open the PR. The `prb` helper does this:
```
prb lnbits fix-payment-race # create ~/dev/upstream-prs/lnbits-fix-payment-race
# branched from upstream/main, fork as push target
prc lnbits fix-payment-race # remove the worktree after the PR merges
prl # list active PR worktrees
```
**Navigation helpers** — shell functions sourced into interactive
shells, driven by what's on disk under `~/dev/`:
```
lb dev # cd ~/dev/lnbits/dev
lb fix-issue-123 # cd ~/dev/lnbits/fix-issue-123
g extensions myext # cd ~/dev/extensions/myext
ext <name> # cd ~/dev/shared/extensions/<name>
```
## Claude orientation
For workflows that use [Claude Code](https://docs.anthropic.com/en/docs/claude-code),
lnbits-sensei ships curated workspace orientation that gets symlinked
into `~/dev/` so AI-assisted work picks up lnbits-specific gotchas
without anyone copying files around or re-explaining them in every
session.
**Concrete example.** A Claude session iterating in
`~/dev/lnbits/extensions/<my-ext>/templates/` automatically inherits
the per-project `CLAUDE.md` from `~/dev/lnbits/`, which carries the
**Vue/Quasar UMD "no self-closing tags"** rule. Without that seed,
Claude would happily emit:
```html
<q-input v-model="amount" label="Amount" />
```
Correct in any Vue SFC repo, **silently broken** under LNbits's UMD
load model — the browser's HTML parser doesn't honor self-close on
non-void elements, the close tag gets implied at the wrong place, and
nesting corrupts. With the seed loaded, the model writes:
```html
<q-input v-model="amount" label="Amount"></q-input>
```
— the form that actually works. Same story for every other gotcha
the per-project CLAUDE.md surfaces:
- **Settings precedence** — editing `.env` post-first-boot is a no-op
for editable fields. AI knows to check `system_settings` rows or
Admin UI, not chase env-var edits.
- **Auth decorators** — `require_admin_key` (per-wallet write) ≠
`check_admin` (instance admin). Easy to misuse.
- **Upstream PR target** — `lnbits/lnbits` accepts PRs against `dev`,
not `main`. Lowercase conventional-commit titles.
- **CLINK ↔ LNbits scope** — `ndebit` / `noffer` semantics live in
Lightning.Pub, not LNbits. Don't propose CLINK shims for LNbits.
The full set lives in `docs/`; the seeded `CLAUDE.md` files are tight
summaries with pointers back.
### How it works
Files in this repo's `files/` directory are symlinked to your `~/dev/`
paths via home-manager's `mkOutOfStoreSymlink` — edits in your
checkout take effect on the next Claude session, no rebuild.
- `files/lnbits-CLAUDE.md` → `~/dev/lnbits/CLAUDE.md` (per-project)
- `files/dev-CLAUDE.md` → `~/dev/CLAUDE.md` (workspace, opt-in)
### Wiring
In your `settings.nix`:
```nix
devEnv = {
enable = true;
scaffoldPath = "/home/<you>/dev/lnbits-sensei"; # absolute path
claude = {
enable = true; # ~/dev/lnbits/CLAUDE.md
workspaceOrientation = false; # also ~/dev/CLAUDE.md (opt-in;
# clobbers any existing one)
};
};
```
If you don't use Claude Code, leave both off — the same content is in
`docs/` for human reading. The seeding is purely an optional layer on
top of the human-facing docs, not a replacement for them.
## 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.
- [`docs/secrets-management.md`](docs/secrets-management.md) —
beginner-friendly walkthrough for getting secrets out of `.env`
and into sops-encrypted YAML files: generating an age key, adding
recipients, declaring secrets in NixOS, rotating, multi-host
server setups, and common pitfalls. The scaffold ships the sops-nix
wiring already (inert until you create your first encrypted file).
## Contributing to this scaffold
Iteration happens under the sandboxed-claude policy from omnixy's
`scripts/sandbox-claude.sh` — a per-launch `.claude/settings.json`
that allows nix/git/inspection and hard-denies sudo, push/remote,
container engines, etc. Substantive scaffolding work happens in that
sandbox; promotion (`git push`) is done from the main shell after
review.
## License
[MIT](LICENSE).