RTL/CLAUDE.md

123 lines
6.5 KiB
Markdown
Raw Permalink Normal View History

# RTL — notes for AI coding agents
RTL (Ride The Lightning) is a device-agnostic web UI for Lightning node operations:
an Angular single-page frontend plus a Node/Express backend, both TypeScript.
`CONTRIBUTING.md` is the process document — how to install, run the dev servers, package a
build, open a PR, add a library, and handle Dependabot. **Read it first.** This file covers
only the things that are easy to get wrong and aren't obvious from the tree.
## Source vs. generated — read this before editing anything
| Directory | What it is | Edit it? |
|-------------|-----------------------------------|----------|
| `src/` | Angular frontend source | yes |
| `server/` | Express backend source | yes |
| `frontend/` | Built AOT bundle, **committed** | never by hand |
| `backend/` | Compiled `server/` output, **committed** | never by hand |
`frontend/` and `backend/` look like ignorable build artifacts, but they are tracked in git
and are expected to stay in sync with the sources. So a code change is a two-step edit:
change `src/`/`server/`, then rebuild and commit the regenerated output in the same PR.
```bash
npm run buildbackend # tsc: server/ -> backend/
npm run buildfrontend # ng build --configuration production: src/ -> frontend/
```
`backend/` is a plain `tsc` transpile of `server/`, so it changes only when `server/` does —
a dependency bump alone won't move it. `frontend/` is a bundle, so it also carries the app
version and any bundled dependency.
## The three-implementation pattern
RTL supports three Lightning implementations, and the layout mirrors that everywhere:
```
src/app/{lnd,cln,eclair,shared}/
server/controllers/{lnd,cln,eclair,shared}/
server/routes/{lnd,cln,eclair,shared}/
```
A feature or fix usually touches the matching folder in **each layer** for the
implementations it affects; genuinely cross-cutting logic belongs in `shared/`. When fixing a
bug in one implementation, check whether the same shape exists in the other two — they
frequently do, since the controllers were written in parallel.
## Commands, and where they bite
- **Install with `npm ci --legacy-peer-deps`**, not `npm install`. Plain `npm ci` fails on an
`ERESOLVE` conflict from `@fortawesome/angular-fontawesome`.
- **`npm run server` only works on Windows** — it sets `NODE_ENV` with `set X=Y&&` syntax. On
macOS/Linux use `npm run serverUbuntu`.
Add a BTCPay Server SSO harness to the docker fixture (#1669) * Add a BTCPay Server SSO harness to the docker fixture BTCPay bundles RTL and runs it in single-sign-on mode, reached over an entry path the standalone login never exercises: no password, a rotating cookie file, an unregistered /rtl/api/authenticate/cookie URL that falls through to the catch-all in server/utils/app.ts, and a reverse proxy in front. Regressions on that path have previously gone unnoticed until they reached BTCPay users. Adds an "sso" compose profile, so a plain `docker compose up -d` is unchanged: - rtl-sso, a second RTL running with RTL_SSO=1, RTL_COOKIE_PATH and LOGOUT_REDIRECT_LINK -- the environment block lifted verbatim from BTCPay's own compose fragment, so this exercises the env-driven SSO path BTCPay actually uses. A second container is required because RTL selects one authentication mode at startup, so SSO and password login cannot coexist in one instance. - rtl-sso-config-init, staging rtl/RTL-Config.sso.json into a volume -- the same copy-into-a-volume dance the standalone RTL already needs, because RTL rewrites its config on startup. - rtl-sso-proxy, nginx standing in for BTCPay's traefik, routing only /rtl and /rtl/* exactly as BTCPay's router rule does. There is no prefix stripping anywhere: RTL is built with <base href="/rtl/"> and mounts every route under baseHref '/rtl', so the prefix is passed through unmodified. Everything outside /rtl 404s, so a request escaping the prefix surfaces as a failure rather than being quietly served. scripts/verify-sso.sh asserts the whole flow in 11 checks -- prefix routing, CSRF token minting on the catch-all, the sha256 access-key handshake, an authenticated node call, cookie rotation on login, and rejection of a wrong key -- and exits non-zero so it can gate a change. bin/sso-url prints the link BTCPay renders on its Services page. RTL_IMAGE overrides both RTL containers at once, so a branch build gets tested through both entry paths. BTCPay itself (postgres, nbxplorer, btcpayserver) is deliberately not included; the README documents what that leaves untested and how to run against BTCPay's own regtest stack when the question is BTCPay's behaviour rather than RTL's. Also bumps the fixture's default RTL image from v0.15.8 to v0.15.10. * Document the SSO harness in the rtl-docker-fixture skill * Point CLAUDE.md at the BTCPay SSO harness * Note that no CI runs on an open PR
2026-08-04 18:17:19 -07:00
- **`npm run lint` and `npm run test` must both be green before a PR.** Nothing will check
this for you: no build or test CI runs on an open PR. `checks.yml` fires on
`pull_request: closed` (i.e. on merge) and on tags/releases, and `rtlreviewbot` only on a
requested review or a comment — so running both locally is the only gate before merge.
- If lint reports hundreds of template "Parsing error" failures, look for a stale
**`coverage/`** directory (git-ignored Karma output). The template linter walks its HTML
report. Delete it and re-run.
- The repo README is at **`.github/README.md`** — there is none at the root.
## Branches and releases
- **PRs target the current `Release-x.y.z` branch, not `master`.** Because release branches
merge into `master` by *rebase*, a `Fixes #N` reference never auto-closes its issue (GitHub
only does that for the default branch) — close it manually after the merge.
- **Every PR adds its own release-note entry**, in the same PR as the change:
`release-notes/Release-notes-<x.y.z>.md`, under `## Bug Fixes`, `## Enhancements`,
`## Code Health` or `## Developer Tooling`. Create the file if it doesn't exist yet.
Link the PR and any issue, and state the root cause briefly. Because the PR number isn't
known until the PR exists, commit the entry with `#TBD` and follow up with a
`Fill in PR number in release note (#N)` commit.
### If a release ships while your PR is open
The rebase-merge rewrites every commit of the release branch to a new hash, and the next
release branch is cut from `master` — so a branch based on the old release branch shares no
recent ancestor with the new one. Retargeting it makes the merge base collapse to before the
release cycle, and GitHub replays the entire cycle into your PR: hundreds of files, and a
`CONFLICTING` state. Replay just your own commits instead:
```bash
git rebase --onto Release-<new> Release-<old> <your-branch>
```
Then confirm `git diff Release-<old>..<old-head>` is identical to
`git diff Release-<new>..<new-head>` before force-pushing. Retargeting *before* the release
branch is merged avoids the problem entirely.
## Testing against real nodes
`docker/` is a self-contained regtest fixture — bitcoind, three LND nodes, Core Lightning and
Eclair, wired to RTL — for end-to-end testing across all three implementations. See
`docker/README.md`, or the `rtl-docker-fixture` skill in `.claude/skills/`. It is dev-only;
every credential in it is throwaway.
Add a BTCPay Server SSO harness to the docker fixture (#1669) * Add a BTCPay Server SSO harness to the docker fixture BTCPay bundles RTL and runs it in single-sign-on mode, reached over an entry path the standalone login never exercises: no password, a rotating cookie file, an unregistered /rtl/api/authenticate/cookie URL that falls through to the catch-all in server/utils/app.ts, and a reverse proxy in front. Regressions on that path have previously gone unnoticed until they reached BTCPay users. Adds an "sso" compose profile, so a plain `docker compose up -d` is unchanged: - rtl-sso, a second RTL running with RTL_SSO=1, RTL_COOKIE_PATH and LOGOUT_REDIRECT_LINK -- the environment block lifted verbatim from BTCPay's own compose fragment, so this exercises the env-driven SSO path BTCPay actually uses. A second container is required because RTL selects one authentication mode at startup, so SSO and password login cannot coexist in one instance. - rtl-sso-config-init, staging rtl/RTL-Config.sso.json into a volume -- the same copy-into-a-volume dance the standalone RTL already needs, because RTL rewrites its config on startup. - rtl-sso-proxy, nginx standing in for BTCPay's traefik, routing only /rtl and /rtl/* exactly as BTCPay's router rule does. There is no prefix stripping anywhere: RTL is built with <base href="/rtl/"> and mounts every route under baseHref '/rtl', so the prefix is passed through unmodified. Everything outside /rtl 404s, so a request escaping the prefix surfaces as a failure rather than being quietly served. scripts/verify-sso.sh asserts the whole flow in 11 checks -- prefix routing, CSRF token minting on the catch-all, the sha256 access-key handshake, an authenticated node call, cookie rotation on login, and rejection of a wrong key -- and exits non-zero so it can gate a change. bin/sso-url prints the link BTCPay renders on its Services page. RTL_IMAGE overrides both RTL containers at once, so a branch build gets tested through both entry paths. BTCPay itself (postgres, nbxplorer, btcpayserver) is deliberately not included; the README documents what that leaves untested and how to run against BTCPay's own regtest stack when the question is BTCPay's behaviour rather than RTL's. Also bumps the fixture's default RTL image from v0.15.8 to v0.15.10. * Document the SSO harness in the rtl-docker-fixture skill * Point CLAUDE.md at the BTCPay SSO harness * Note that no CI runs on an open PR
2026-08-04 18:17:19 -07:00
The fixture also carries a **BTCPay Server SSO harness** behind a compose profile
(`docker compose --profile sso up -d`). BTCPay bundles RTL and reaches it over a path the
standalone login never exercises: a rotating cookie file, an unregistered
`/rtl/api/authenticate/cookie` URL that is not a route at all and falls through to the
catch-all in `server/utils/app.ts`, and a reverse proxy serving it under `/rtl`. Run
`docker/scripts/verify-sso.sh` (11 assertions, exits non-zero) after touching
authentication, CSRF or static serving — none of that path is covered by logging into the
fixture's own RTL. One trap it encodes: `GET /rtl/` is served by `express.static`, which
sits above the catch-all and mints no `XSRF-TOKEN`, so a client entering there gets a 403
on its first POST. That is long-standing behaviour, not a regression.
Release 0.15.10 (#1665) * Update version 0.15.10 * Update project dependencies to resolve Dependabot security alerts Applies the fixes from the open Dependabot PRs (#1648, #1649, #1650) in a single pass on the release branch, regenerating the lockfile from scratch. axios 1.16.0 -> 1.18.1 was the only production exposure (10 advisories). Transitive deps moved to their fixed in-range versions (fast-uri 3.1.4, form-data, qs, tough-cookie, tar, del, globby); dev toolchain took safe bumps (nodemon 3.1.14, eslint 9.39.5, @typescript-eslint 8.65.0). Drops the unused protractor devDependency: no e2e directory, no config and no e2e target in angular.json, but 100 packages and the deprecated request stack behind it. That clears both critical advisories. npm audit: 50 (2 critical) -> 29 (0 critical); production deps 1 -> 0. Remaining findings are dev-only tooling needing an Angular 21 migration rather than a version bump. Verified: lint, 204 frontend specs, backend + frontend production builds, and 19 API checks against the docker regtest fixture covering LND, Core Lightning and Eclair (getinfo, channels, peers, invoices, payments and forwarding history). * Fill in PR number in release note (#1653) * Harden login request validation (#1654) Tightens server-side validation of authentication requests, guards the password-reset route behind an authenticated session, and wires the backend regression suite (test/backend/) into npm run test. Users with two-factor authentication enabled are encouraged to update promptly. Verified: backend specs 12/12, lint green, frontend specs 204/204, and the full authentication matrix end-to-end on the docker regtest fixture. * Reduce exposure of authentication secrets in logs and config responses (#1659) * Reduce exposure of authentication secrets in logs and config responses * Fill in PR number in release note (#1659) * Harden redaction helpers and secret restore paths * Pin deployment auth switches server-side and harden settings persistence * Contain backup file reads and harden config persistence * Pin backup containment root and preserve config file mode on save * Update Angular framework packages to 20.3.27 (#1661) * Update Angular framework packages to 20.3.27 Batches the three Dependabot PRs open against master for the Angular framework (@angular/core #1658, @angular/compiler #1657, @angular/common #1655) into one update on the release branch. The framework packages are pinned to exact versions and their peer ranges require them to move together, so all nine 20.3.26 packages go to 20.3.27: animations, common, compiler, compiler-cli, core, forms, platform-browser, platform-browser-dynamic and router. Patch-level upstream fixes only, no advisories. The update stays inside Angular 20 - @angular/build and @angular/cli (20.3.32) and @angular/cdk/@angular/material (20.2.14) are already at the top of their v20 lines - so it does not pull in the Angular 21 migration tracked by #1650. Rebuilt frontend/ for the new framework code. backend/ is unchanged, as no server/ source moved. * Fill in PR number in release note (#1661) * Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts Fixes #1630 (#1651) * Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts Fixes #1630 * Address review feedback: fix options race, error handling, release notes * Improve release notes entry to cover full PR scope * Address review feedback: per-task options copy, exclude qs from alias requests * Stop logging the eclair auth header at DEBUG level (#1664) * Stop logging the eclair auth header at DEBUG level getChannels in the eclair channels controller logged its whole request options object. Eclair authenticates with HTTP basic auth, so those options carry the configured lnApiPassword in an authorization header - raising an eclair node's logLevel to DEBUG wrote "authorization":"Basic <base64>" into the node log file, which is a recoverable form of the credential and is routinely shared when debugging. The log now carries only the request url and form, matching every other DEBUG log in the controllers. This was the only site in server/ passing a whole options object to the logger; the rest log options.form, .url, .body or .qs, none of which hold credentials. Present since 0.12.0 and only reachable by opting in to DEBUG (the default log level is ERROR), but it contradicted the logging guarantee stated for #1659. Found by scanning node logs at DEBUG while verifying the 0.15.10 branch against the regtest fixture. Regression test added in test/backend/eclair-channels.test.mjs; it fails on the previous code with "auth header key must not reach the node log". * Fill in PR number in release note (#1664) --------- Co-authored-by: Osuji <weezdomosuji@gmail.com>
2026-08-03 22:49:14 -07:00
Backend regression tests live in `test/backend/` (plain `node:test`, run against the
compiled `backend/`). `npm run test` compiles the backend, then runs them
(`npm run testbackend`) before the frontend Karma/Jasmine specs, so they never test stale
code. For backend changes, also verify against the fixture and say so in the PR.
## Conventions
- **Be conservative about dependencies.** This is security-sensitive software. Prefer what's
already there, and raise an issue before adding anything. Never run `npm audit fix` — it
reaches for breaking major bumps. Dependabot PRs are batched, not merged individually; see
`CONTRIBUTING.md`.
- Match the surrounding code's style, naming and structure. Only fix style in code you're
already changing.
- `RTL-Config.json` is local runtime config and git-ignored; `Sample-RTL-Config.json` is the
template, and `RTL.conf` is an alternate config format.