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

14 KiB

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 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 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:

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 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 for topology patterns).

  3. Replace the placeholder hardware config with your real one:

    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:

    nix flake check --no-build
    

    Should print all checks passed!.

  5. Switch when ready:

    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. See 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:

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 and 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, 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:

<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:

<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 decoratorsrequire_admin_key (per-wallet write) ≠ check_admin (instance admin). Easy to misuse.
  • Upstream PR targetlnbits/lnbits accepts PRs against dev, not main. Lowercase conventional-commit titles.
  • CLINK ↔ LNbits scopendebit / 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:

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 — the three remote-topology patterns (upstream-only / github-fork / multi-remote).
  • 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 — reference for how lnbits/lnbits itself moves: dev / main branch model, squash-merge convention, release tagging.
  • 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 — 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 — 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 — 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.