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>
354 lines
14 KiB
Markdown
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).
|