Rebuild the regtest dev fixture: bitcoind + 3 LND nodes + RTL
The setup in docker/ has been unable to start since Feb 2021. The boltz
service added in f817ae39 references BOLTZ_* variables that are not in .env
(so its ports render as ":" and compose rejects the file with "invalid
proto"), a ./boltz build context that has never existed in the repo, and
boltz_data/boltz_shared volumes that are never declared. Because compose
validates the whole project up front, this broke every service: "docker
compose up -d bitcoind", the first command in the README, failed too.
Rather than repair a five-year-old file pinned to bitcoind 0.19.0 and lnd
0.12.0-beta, this replaces it.
What changed:
- bitcoind 30.0 and 3x lnd 0.20.0-beta, using Polar's images. They are
multi-arch, so nothing is built locally and this works on arm64. The old
setup built bitcoind and lnd from local Dockerfiles.
- Three nodes, not one: alice -> bob -> carol. bob forwards, so RTL's
routing and forwarding screens have data. Two nodes leave them empty.
- scripts/seed.sh funds the nodes, opens channels, and makes payments with
fixed amounts. A fresh run reproduces identical state, so screenshots
taken before and after a change differ only by the change. It is
deliberately not idempotent and refuses to run against a seeded network,
since re-running would double-fund it.
- rtl/RTL-Config.regtest.json configures all three nodes. RTL rewrites its
config on startup, so an init container stages a copy into a volume: a
read-only bind mount makes RTL exit with EROFS, and a writable one would
let RTL modify a tracked file. It is not named RTL-Config.json because
.gitignore matches that bare name at any depth.
- bin/ln-cli now takes a node name and passes --lnddir=/home/lnd/.lnd,
because 'docker compose exec' lands as root while lnd's datadir is under
/home/lnd. Both helpers use compose v2.
- README rewritten to match.
Boltz, Core Lightning and Eclair are left out of this pass. Polar publishes
multi-arch clightning and eclair images, so adding them later needs compose
services, config entries and seed adapters, but no image building.
Verified from a clean 'down -v': all nodes sync, channels go active, 5/5
payments route through bob, bob records 5 forwards, and RTL serves the UI
with all three nodes configured. Two independent from-scratch runs produced
identical balances.
The old bitcoind/ and lnd/ build contexts are now unreferenced but left in
place for a follow-up.
2026-07-15 23:09:03 -07:00
|
|
|
|
# RTL regtest dev fixture
|
2019-04-15 00:00:20 +01:00
|
|
|
|
|
Rebuild the regtest dev fixture: bitcoind + 3 LND nodes + RTL
The setup in docker/ has been unable to start since Feb 2021. The boltz
service added in f817ae39 references BOLTZ_* variables that are not in .env
(so its ports render as ":" and compose rejects the file with "invalid
proto"), a ./boltz build context that has never existed in the repo, and
boltz_data/boltz_shared volumes that are never declared. Because compose
validates the whole project up front, this broke every service: "docker
compose up -d bitcoind", the first command in the README, failed too.
Rather than repair a five-year-old file pinned to bitcoind 0.19.0 and lnd
0.12.0-beta, this replaces it.
What changed:
- bitcoind 30.0 and 3x lnd 0.20.0-beta, using Polar's images. They are
multi-arch, so nothing is built locally and this works on arm64. The old
setup built bitcoind and lnd from local Dockerfiles.
- Three nodes, not one: alice -> bob -> carol. bob forwards, so RTL's
routing and forwarding screens have data. Two nodes leave them empty.
- scripts/seed.sh funds the nodes, opens channels, and makes payments with
fixed amounts. A fresh run reproduces identical state, so screenshots
taken before and after a change differ only by the change. It is
deliberately not idempotent and refuses to run against a seeded network,
since re-running would double-fund it.
- rtl/RTL-Config.regtest.json configures all three nodes. RTL rewrites its
config on startup, so an init container stages a copy into a volume: a
read-only bind mount makes RTL exit with EROFS, and a writable one would
let RTL modify a tracked file. It is not named RTL-Config.json because
.gitignore matches that bare name at any depth.
- bin/ln-cli now takes a node name and passes --lnddir=/home/lnd/.lnd,
because 'docker compose exec' lands as root while lnd's datadir is under
/home/lnd. Both helpers use compose v2.
- README rewritten to match.
Boltz, Core Lightning and Eclair are left out of this pass. Polar publishes
multi-arch clightning and eclair images, so adding them later needs compose
services, config entries and seed adapters, but no image building.
Verified from a clean 'down -v': all nodes sync, channels go active, 5/5
payments route through bob, bob records 5 forwards, and RTL serves the UI
with all three nodes configured. Two independent from-scratch runs produced
identical balances.
The old bitcoind/ and lnd/ build contexts are now unreferenced but left in
place for a follow-up.
2026-07-15 23:09:03 -07:00
|
|
|
|
### NOT suitable for production. Development only. Every credential here is throwaway.
|
2019-04-15 00:00:20 +01:00
|
|
|
|
|
Rebuild the regtest dev fixture: bitcoind + 3 LND nodes + RTL
The setup in docker/ has been unable to start since Feb 2021. The boltz
service added in f817ae39 references BOLTZ_* variables that are not in .env
(so its ports render as ":" and compose rejects the file with "invalid
proto"), a ./boltz build context that has never existed in the repo, and
boltz_data/boltz_shared volumes that are never declared. Because compose
validates the whole project up front, this broke every service: "docker
compose up -d bitcoind", the first command in the README, failed too.
Rather than repair a five-year-old file pinned to bitcoind 0.19.0 and lnd
0.12.0-beta, this replaces it.
What changed:
- bitcoind 30.0 and 3x lnd 0.20.0-beta, using Polar's images. They are
multi-arch, so nothing is built locally and this works on arm64. The old
setup built bitcoind and lnd from local Dockerfiles.
- Three nodes, not one: alice -> bob -> carol. bob forwards, so RTL's
routing and forwarding screens have data. Two nodes leave them empty.
- scripts/seed.sh funds the nodes, opens channels, and makes payments with
fixed amounts. A fresh run reproduces identical state, so screenshots
taken before and after a change differ only by the change. It is
deliberately not idempotent and refuses to run against a seeded network,
since re-running would double-fund it.
- rtl/RTL-Config.regtest.json configures all three nodes. RTL rewrites its
config on startup, so an init container stages a copy into a volume: a
read-only bind mount makes RTL exit with EROFS, and a writable one would
let RTL modify a tracked file. It is not named RTL-Config.json because
.gitignore matches that bare name at any depth.
- bin/ln-cli now takes a node name and passes --lnddir=/home/lnd/.lnd,
because 'docker compose exec' lands as root while lnd's datadir is under
/home/lnd. Both helpers use compose v2.
- README rewritten to match.
Boltz, Core Lightning and Eclair are left out of this pass. Polar publishes
multi-arch clightning and eclair images, so adding them later needs compose
services, config entries and seed adapters, but no image building.
Verified from a clean 'down -v': all nodes sync, channels go active, 5/5
payments route through bob, bob records 5 forwards, and RTL serves the UI
with all three nodes configured. Two independent from-scratch runs produced
identical balances.
The old bitcoind/ and lnd/ build contexts are now unreferenced but left in
place for a follow-up.
2026-07-15 23:09:03 -07:00
|
|
|
|
A self-contained regtest network for developing and testing RTL: `bitcoind`, three
|
Add an Eclair node to the regtest docker fixture
Completes backend coverage of RTL's three implementations in the docker/
dev fixture: an eclair node (polarlightning/eclair 0.13.1) joins the three
LND nodes and the CLN node, wired to RTL over its HTTP API with basic auth,
and the seed opens an eclair->bob channel (3.5M sats, 1M pushed), sends two
direct payments and leaves one open invoice.
Non-obvious plumbing this needed:
- polarlightning/eclair instead of acinq/eclair: the official image is
amd64-only (useless on Apple Silicon) and its newest versioned tag is
years stale; Polar builds the same ACINQ source multi-arch.
- Eclair has no on-chain wallet of its own -- it drives a bitcoind wallet.
A new eclair-wallet-init container creates a dedicated "eclair" wallet
before the node starts; without it eclair attaches to "the default
loaded wallet", i.e. the rtldev mining wallet.
- bitcoind now also publishes a zmqpubhashblock endpoint (28336): eclair's
bitcoind.zmqblock consumes the hashblock topic, not the rawblock one LND
uses. Wired to rawblock, eclair never sees new blocks and channels hang
in WAIT_FOR_FUNDING_CONFIRMED.
- Eclair confirms channels at 8 blocks (channel.min-depth-blocks), not 6,
and 'open' returns before the funding tx is broadcast -- the seed waits
for the mempool and mines 8 blocks for this channel.
Adds a bin/e-cli helper (eclair-cli with the API password), updates the
README, and verified end-to-end: seed completes, the channel reaches
NORMAL, both payments settle, and RTL's /rtl/api/ecl endpoints return the
node, channel and invoice data.
2026-07-18 00:23:08 -07:00
|
|
|
|
LND nodes, a Core Lightning node, an Eclair node, and RTL wired to all five.
|
2019-04-15 00:00:20 +01:00
|
|
|
|
|
Rebuild the regtest dev fixture: bitcoind + 3 LND nodes + RTL
The setup in docker/ has been unable to start since Feb 2021. The boltz
service added in f817ae39 references BOLTZ_* variables that are not in .env
(so its ports render as ":" and compose rejects the file with "invalid
proto"), a ./boltz build context that has never existed in the repo, and
boltz_data/boltz_shared volumes that are never declared. Because compose
validates the whole project up front, this broke every service: "docker
compose up -d bitcoind", the first command in the README, failed too.
Rather than repair a five-year-old file pinned to bitcoind 0.19.0 and lnd
0.12.0-beta, this replaces it.
What changed:
- bitcoind 30.0 and 3x lnd 0.20.0-beta, using Polar's images. They are
multi-arch, so nothing is built locally and this works on arm64. The old
setup built bitcoind and lnd from local Dockerfiles.
- Three nodes, not one: alice -> bob -> carol. bob forwards, so RTL's
routing and forwarding screens have data. Two nodes leave them empty.
- scripts/seed.sh funds the nodes, opens channels, and makes payments with
fixed amounts. A fresh run reproduces identical state, so screenshots
taken before and after a change differ only by the change. It is
deliberately not idempotent and refuses to run against a seeded network,
since re-running would double-fund it.
- rtl/RTL-Config.regtest.json configures all three nodes. RTL rewrites its
config on startup, so an init container stages a copy into a volume: a
read-only bind mount makes RTL exit with EROFS, and a writable one would
let RTL modify a tracked file. It is not named RTL-Config.json because
.gitignore matches that bare name at any depth.
- bin/ln-cli now takes a node name and passes --lnddir=/home/lnd/.lnd,
because 'docker compose exec' lands as root while lnd's datadir is under
/home/lnd. Both helpers use compose v2.
- README rewritten to match.
Boltz, Core Lightning and Eclair are left out of this pass. Polar publishes
multi-arch clightning and eclair images, so adding them later needs compose
services, config entries and seed adapters, but no image building.
Verified from a clean 'down -v': all nodes sync, channels go active, 5/5
payments route through bob, bob records 5 forwards, and RTL serves the UI
with all three nodes configured. Two independent from-scratch runs produced
identical balances.
The old bitcoind/ and lnd/ build contexts are now unreferenced but left in
place for a follow-up.
2026-07-15 23:09:03 -07:00
|
|
|
|
```
|
Add an Eclair node to the regtest docker fixture
Completes backend coverage of RTL's three implementations in the docker/
dev fixture: an eclair node (polarlightning/eclair 0.13.1) joins the three
LND nodes and the CLN node, wired to RTL over its HTTP API with basic auth,
and the seed opens an eclair->bob channel (3.5M sats, 1M pushed), sends two
direct payments and leaves one open invoice.
Non-obvious plumbing this needed:
- polarlightning/eclair instead of acinq/eclair: the official image is
amd64-only (useless on Apple Silicon) and its newest versioned tag is
years stale; Polar builds the same ACINQ source multi-arch.
- Eclair has no on-chain wallet of its own -- it drives a bitcoind wallet.
A new eclair-wallet-init container creates a dedicated "eclair" wallet
before the node starts; without it eclair attaches to "the default
loaded wallet", i.e. the rtldev mining wallet.
- bitcoind now also publishes a zmqpubhashblock endpoint (28336): eclair's
bitcoind.zmqblock consumes the hashblock topic, not the rawblock one LND
uses. Wired to rawblock, eclair never sees new blocks and channels hang
in WAIT_FOR_FUNDING_CONFIRMED.
- Eclair confirms channels at 8 blocks (channel.min-depth-blocks), not 6,
and 'open' returns before the funding tx is broadcast -- the seed waits
for the mempool and mines 8 blocks for this channel.
Adds a bin/e-cli helper (eclair-cli with the API password), updates the
README, and verified end-to-end: seed completes, the channel reaches
NORMAL, both payments settle, and RTL's /rtl/api/ecl endpoints return the
node, channel and invoice data.
2026-07-18 00:23:08 -07:00
|
|
|
|
alice --[ 5,000,000 sat ]--> bob --[ 3,000,000 sat ]--> carol
|
|
|
|
|
|
cln --[ 4,000,000 sat ]--> alice
|
|
|
|
|
|
eclair --[ 3,500,000 sat ]--> bob
|
Rebuild the regtest dev fixture: bitcoind + 3 LND nodes + RTL
The setup in docker/ has been unable to start since Feb 2021. The boltz
service added in f817ae39 references BOLTZ_* variables that are not in .env
(so its ports render as ":" and compose rejects the file with "invalid
proto"), a ./boltz build context that has never existed in the repo, and
boltz_data/boltz_shared volumes that are never declared. Because compose
validates the whole project up front, this broke every service: "docker
compose up -d bitcoind", the first command in the README, failed too.
Rather than repair a five-year-old file pinned to bitcoind 0.19.0 and lnd
0.12.0-beta, this replaces it.
What changed:
- bitcoind 30.0 and 3x lnd 0.20.0-beta, using Polar's images. They are
multi-arch, so nothing is built locally and this works on arm64. The old
setup built bitcoind and lnd from local Dockerfiles.
- Three nodes, not one: alice -> bob -> carol. bob forwards, so RTL's
routing and forwarding screens have data. Two nodes leave them empty.
- scripts/seed.sh funds the nodes, opens channels, and makes payments with
fixed amounts. A fresh run reproduces identical state, so screenshots
taken before and after a change differ only by the change. It is
deliberately not idempotent and refuses to run against a seeded network,
since re-running would double-fund it.
- rtl/RTL-Config.regtest.json configures all three nodes. RTL rewrites its
config on startup, so an init container stages a copy into a volume: a
read-only bind mount makes RTL exit with EROFS, and a writable one would
let RTL modify a tracked file. It is not named RTL-Config.json because
.gitignore matches that bare name at any depth.
- bin/ln-cli now takes a node name and passes --lnddir=/home/lnd/.lnd,
because 'docker compose exec' lands as root while lnd's datadir is under
/home/lnd. Both helpers use compose v2.
- README rewritten to match.
Boltz, Core Lightning and Eclair are left out of this pass. Polar publishes
multi-arch clightning and eclair images, so adding them later needs compose
services, config entries and seed adapters, but no image building.
Verified from a clean 'down -v': all nodes sync, channels go active, 5/5
payments route through bob, bob records 5 forwards, and RTL serves the UI
with all three nodes configured. Two independent from-scratch runs produced
identical balances.
The old bitcoind/ and lnd/ build contexts are now unreferenced but left in
place for a follow-up.
2026-07-15 23:09:03 -07:00
|
|
|
|
```
|
2019-04-15 00:00:20 +01:00
|
|
|
|
|
2026-07-18 00:26:15 -07:00
|
|
|
|
## Topology
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
flowchart TB
|
|
|
|
|
|
subgraph chain["Chain backend"]
|
|
|
|
|
|
bitcoind["bitcoind (regtest)<br/>RPC · ZMQ rawblock/rawtx · ZMQ hashblock"]
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
subgraph ln["Lightning nodes"]
|
|
|
|
|
|
alice["alice (LND)"]
|
|
|
|
|
|
bob["bob (LND)<br/>forwards payments"]
|
|
|
|
|
|
carol["carol (LND)"]
|
|
|
|
|
|
cln["cln (Core Lightning)"]
|
|
|
|
|
|
eclair["eclair (Eclair)"]
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
alice =="5M sat"==> bob
|
|
|
|
|
|
bob =="3M sat"==> carol
|
|
|
|
|
|
cln =="4M sat"==> alice
|
|
|
|
|
|
eclair =="3.5M sat"==> bob
|
|
|
|
|
|
|
|
|
|
|
|
alice -.-> bitcoind
|
|
|
|
|
|
bob -.-> bitcoind
|
|
|
|
|
|
carol -.-> bitcoind
|
|
|
|
|
|
cln -.-> bitcoind
|
|
|
|
|
|
eclair -.->|"dedicated 'eclair' wallet<br/>+ hashblock ZMQ"| bitcoind
|
|
|
|
|
|
|
|
|
|
|
|
rtl["RTL<br/>localhost:3000"]
|
|
|
|
|
|
rtl -->|"REST + macaroon"| alice
|
|
|
|
|
|
rtl -->|"REST + macaroon"| bob
|
|
|
|
|
|
rtl -->|"REST + macaroon"| carol
|
|
|
|
|
|
rtl -->|"clnrest + rune"| cln
|
|
|
|
|
|
rtl -->|"HTTP API + basic auth"| eclair
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Thick arrows are channels (opener → peer), dotted arrows the chain backend each node
|
|
|
|
|
|
uses, and solid arrows how RTL reaches each node.
|
|
|
|
|
|
|
Rebuild the regtest dev fixture: bitcoind + 3 LND nodes + RTL
The setup in docker/ has been unable to start since Feb 2021. The boltz
service added in f817ae39 references BOLTZ_* variables that are not in .env
(so its ports render as ":" and compose rejects the file with "invalid
proto"), a ./boltz build context that has never existed in the repo, and
boltz_data/boltz_shared volumes that are never declared. Because compose
validates the whole project up front, this broke every service: "docker
compose up -d bitcoind", the first command in the README, failed too.
Rather than repair a five-year-old file pinned to bitcoind 0.19.0 and lnd
0.12.0-beta, this replaces it.
What changed:
- bitcoind 30.0 and 3x lnd 0.20.0-beta, using Polar's images. They are
multi-arch, so nothing is built locally and this works on arm64. The old
setup built bitcoind and lnd from local Dockerfiles.
- Three nodes, not one: alice -> bob -> carol. bob forwards, so RTL's
routing and forwarding screens have data. Two nodes leave them empty.
- scripts/seed.sh funds the nodes, opens channels, and makes payments with
fixed amounts. A fresh run reproduces identical state, so screenshots
taken before and after a change differ only by the change. It is
deliberately not idempotent and refuses to run against a seeded network,
since re-running would double-fund it.
- rtl/RTL-Config.regtest.json configures all three nodes. RTL rewrites its
config on startup, so an init container stages a copy into a volume: a
read-only bind mount makes RTL exit with EROFS, and a writable one would
let RTL modify a tracked file. It is not named RTL-Config.json because
.gitignore matches that bare name at any depth.
- bin/ln-cli now takes a node name and passes --lnddir=/home/lnd/.lnd,
because 'docker compose exec' lands as root while lnd's datadir is under
/home/lnd. Both helpers use compose v2.
- README rewritten to match.
Boltz, Core Lightning and Eclair are left out of this pass. Polar publishes
multi-arch clightning and eclair images, so adding them later needs compose
services, config entries and seed adapters, but no image building.
Verified from a clean 'down -v': all nodes sync, channels go active, 5/5
payments route through bob, bob records 5 forwards, and RTL serves the UI
with all three nodes configured. Two independent from-scratch runs produced
identical balances.
The old bitcoind/ and lnd/ build contexts are now unreferenced but left in
place for a follow-up.
2026-07-15 23:09:03 -07:00
|
|
|
|
bob sits in the middle so it accrues forwarding history, which is what gives RTL's
|
Add a Core Lightning node to the regtest docker fixture
The fixture had only bitcoind + three LND nodes, so RTL's Core Lightning screens
had no backend to exercise. Add a `cln` node (official elementsproject/lightningd
image, multi-arch) wired to RTL over clnrest with rune auth, and have the seed
open a cln->alice channel so the CLN channel/peer screens have real data.
- docker-compose.yml: cln service (clnrest on 0.0.0.0:3010, https), a healthcheck
gated on the rune file so rtl waits for it, and rtl now mounts the cln volume
read-only and depends on cln being healthy. The rtl image is parameterized via
${RTL_IMAGE:-...} so an unreleased build can be tested against the fixture.
- cln/poststart.d/create-rune.sh: once the RPC is up, create a master rune and
write it as LIGHTNING_RUNE="..." where RTL reads it (runePath). Polls for RPC
readiness because the image entrypoint can invoke poststart before the socket
exists.
- RTL-Config.regtest.json: add node index 4 (CLN, rune auth, https://cln:3010).
- seed.sh: fund cln, connect to alice, open a 4,000,000 sat channel, wait active.
- README + release notes updated.
Used to verify the CLN channel connection-status fix (#1606) end-to-end.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 10:03:34 -07:00
|
|
|
|
routing screens something to show. Two nodes would leave them empty. The `cln`
|
|
|
|
|
|
(Core Lightning) node gives RTL's CLN screens a real backend — it talks to RTL over
|
Add an Eclair node to the regtest docker fixture
Completes backend coverage of RTL's three implementations in the docker/
dev fixture: an eclair node (polarlightning/eclair 0.13.1) joins the three
LND nodes and the CLN node, wired to RTL over its HTTP API with basic auth,
and the seed opens an eclair->bob channel (3.5M sats, 1M pushed), sends two
direct payments and leaves one open invoice.
Non-obvious plumbing this needed:
- polarlightning/eclair instead of acinq/eclair: the official image is
amd64-only (useless on Apple Silicon) and its newest versioned tag is
years stale; Polar builds the same ACINQ source multi-arch.
- Eclair has no on-chain wallet of its own -- it drives a bitcoind wallet.
A new eclair-wallet-init container creates a dedicated "eclair" wallet
before the node starts; without it eclair attaches to "the default
loaded wallet", i.e. the rtldev mining wallet.
- bitcoind now also publishes a zmqpubhashblock endpoint (28336): eclair's
bitcoind.zmqblock consumes the hashblock topic, not the rawblock one LND
uses. Wired to rawblock, eclair never sees new blocks and channels hang
in WAIT_FOR_FUNDING_CONFIRMED.
- Eclair confirms channels at 8 blocks (channel.min-depth-blocks), not 6,
and 'open' returns before the funding tx is broadcast -- the seed waits
for the mempool and mines 8 blocks for this channel.
Adds a bin/e-cli helper (eclair-cli with the API password), updates the
README, and verified end-to-end: seed completes, the channel reaches
NORMAL, both payments settle, and RTL's /rtl/api/ecl endpoints return the
node, channel and invoice data.
2026-07-18 00:23:08 -07:00
|
|
|
|
clnrest with rune auth. The `eclair` node does the same for RTL's Eclair screens —
|
|
|
|
|
|
RTL talks to its HTTP API with basic auth.
|
2019-04-15 00:00:20 +01:00
|
|
|
|
|
Add an Eclair node to the regtest docker fixture
Completes backend coverage of RTL's three implementations in the docker/
dev fixture: an eclair node (polarlightning/eclair 0.13.1) joins the three
LND nodes and the CLN node, wired to RTL over its HTTP API with basic auth,
and the seed opens an eclair->bob channel (3.5M sats, 1M pushed), sends two
direct payments and leaves one open invoice.
Non-obvious plumbing this needed:
- polarlightning/eclair instead of acinq/eclair: the official image is
amd64-only (useless on Apple Silicon) and its newest versioned tag is
years stale; Polar builds the same ACINQ source multi-arch.
- Eclair has no on-chain wallet of its own -- it drives a bitcoind wallet.
A new eclair-wallet-init container creates a dedicated "eclair" wallet
before the node starts; without it eclair attaches to "the default
loaded wallet", i.e. the rtldev mining wallet.
- bitcoind now also publishes a zmqpubhashblock endpoint (28336): eclair's
bitcoind.zmqblock consumes the hashblock topic, not the rawblock one LND
uses. Wired to rawblock, eclair never sees new blocks and channels hang
in WAIT_FOR_FUNDING_CONFIRMED.
- Eclair confirms channels at 8 blocks (channel.min-depth-blocks), not 6,
and 'open' returns before the funding tx is broadcast -- the seed waits
for the mempool and mines 8 blocks for this channel.
Adds a bin/e-cli helper (eclair-cli with the API password), updates the
README, and verified end-to-end: seed completes, the channel reaches
NORMAL, both payments settle, and RTL's /rtl/api/ecl endpoints return the
node, channel and invoice data.
2026-07-18 00:23:08 -07:00
|
|
|
|
LND, bitcoind and Eclair images come from [Polar](https://lightningpolar.com); the Core
|
Add a Core Lightning node to the regtest docker fixture
The fixture had only bitcoind + three LND nodes, so RTL's Core Lightning screens
had no backend to exercise. Add a `cln` node (official elementsproject/lightningd
image, multi-arch) wired to RTL over clnrest with rune auth, and have the seed
open a cln->alice channel so the CLN channel/peer screens have real data.
- docker-compose.yml: cln service (clnrest on 0.0.0.0:3010, https), a healthcheck
gated on the rune file so rtl waits for it, and rtl now mounts the cln volume
read-only and depends on cln being healthy. The rtl image is parameterized via
${RTL_IMAGE:-...} so an unreleased build can be tested against the fixture.
- cln/poststart.d/create-rune.sh: once the RPC is up, create a master rune and
write it as LIGHTNING_RUNE="..." where RTL reads it (runePath). Polls for RPC
readiness because the image entrypoint can invoke poststart before the socket
exists.
- RTL-Config.regtest.json: add node index 4 (CLN, rune auth, https://cln:3010).
- seed.sh: fund cln, connect to alice, open a 4,000,000 sat channel, wait active.
- README + release notes updated.
Used to verify the CLN channel connection-status fix (#1606) end-to-end.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 10:03:34 -07:00
|
|
|
|
Lightning image is the official [`elementsproject/lightningd`](https://hub.docker.com/r/elementsproject/lightningd).
|
|
|
|
|
|
All are multi-arch (amd64 + arm64) and nothing is built locally, so this works on
|
Add an Eclair node to the regtest docker fixture
Completes backend coverage of RTL's three implementations in the docker/
dev fixture: an eclair node (polarlightning/eclair 0.13.1) joins the three
LND nodes and the CLN node, wired to RTL over its HTTP API with basic auth,
and the seed opens an eclair->bob channel (3.5M sats, 1M pushed), sends two
direct payments and leaves one open invoice.
Non-obvious plumbing this needed:
- polarlightning/eclair instead of acinq/eclair: the official image is
amd64-only (useless on Apple Silicon) and its newest versioned tag is
years stale; Polar builds the same ACINQ source multi-arch.
- Eclair has no on-chain wallet of its own -- it drives a bitcoind wallet.
A new eclair-wallet-init container creates a dedicated "eclair" wallet
before the node starts; without it eclair attaches to "the default
loaded wallet", i.e. the rtldev mining wallet.
- bitcoind now also publishes a zmqpubhashblock endpoint (28336): eclair's
bitcoind.zmqblock consumes the hashblock topic, not the rawblock one LND
uses. Wired to rawblock, eclair never sees new blocks and channels hang
in WAIT_FOR_FUNDING_CONFIRMED.
- Eclair confirms channels at 8 blocks (channel.min-depth-blocks), not 6,
and 'open' returns before the funding tx is broadcast -- the seed waits
for the mempool and mines 8 blocks for this channel.
Adds a bin/e-cli helper (eclair-cli with the API password), updates the
README, and verified end-to-end: seed completes, the channel reaches
NORMAL, both payments settle, and RTL's /rtl/api/ecl endpoints return the
node, channel and invoice data.
2026-07-18 00:23:08 -07:00
|
|
|
|
Apple Silicon. (The official `acinq/eclair` image is amd64-only and its versioned tags
|
|
|
|
|
|
are years stale, which is why Polar's build of the same source is used instead.)
|
2021-12-29 18:08:41 -05:00
|
|
|
|
|
Rebuild the regtest dev fixture: bitcoind + 3 LND nodes + RTL
The setup in docker/ has been unable to start since Feb 2021. The boltz
service added in f817ae39 references BOLTZ_* variables that are not in .env
(so its ports render as ":" and compose rejects the file with "invalid
proto"), a ./boltz build context that has never existed in the repo, and
boltz_data/boltz_shared volumes that are never declared. Because compose
validates the whole project up front, this broke every service: "docker
compose up -d bitcoind", the first command in the README, failed too.
Rather than repair a five-year-old file pinned to bitcoind 0.19.0 and lnd
0.12.0-beta, this replaces it.
What changed:
- bitcoind 30.0 and 3x lnd 0.20.0-beta, using Polar's images. They are
multi-arch, so nothing is built locally and this works on arm64. The old
setup built bitcoind and lnd from local Dockerfiles.
- Three nodes, not one: alice -> bob -> carol. bob forwards, so RTL's
routing and forwarding screens have data. Two nodes leave them empty.
- scripts/seed.sh funds the nodes, opens channels, and makes payments with
fixed amounts. A fresh run reproduces identical state, so screenshots
taken before and after a change differ only by the change. It is
deliberately not idempotent and refuses to run against a seeded network,
since re-running would double-fund it.
- rtl/RTL-Config.regtest.json configures all three nodes. RTL rewrites its
config on startup, so an init container stages a copy into a volume: a
read-only bind mount makes RTL exit with EROFS, and a writable one would
let RTL modify a tracked file. It is not named RTL-Config.json because
.gitignore matches that bare name at any depth.
- bin/ln-cli now takes a node name and passes --lnddir=/home/lnd/.lnd,
because 'docker compose exec' lands as root while lnd's datadir is under
/home/lnd. Both helpers use compose v2.
- README rewritten to match.
Boltz, Core Lightning and Eclair are left out of this pass. Polar publishes
multi-arch clightning and eclair images, so adding them later needs compose
services, config entries and seed adapters, but no image building.
Verified from a clean 'down -v': all nodes sync, channels go active, 5/5
payments route through bob, bob records 5 forwards, and RTL serves the UI
with all three nodes configured. Two independent from-scratch runs produced
identical balances.
The old bitcoind/ and lnd/ build contexts are now unreferenced but left in
place for a follow-up.
2026-07-15 23:09:03 -07:00
|
|
|
|
## Requirements
|
2019-04-15 00:00:20 +01:00
|
|
|
|
|
Rebuild the regtest dev fixture: bitcoind + 3 LND nodes + RTL
The setup in docker/ has been unable to start since Feb 2021. The boltz
service added in f817ae39 references BOLTZ_* variables that are not in .env
(so its ports render as ":" and compose rejects the file with "invalid
proto"), a ./boltz build context that has never existed in the repo, and
boltz_data/boltz_shared volumes that are never declared. Because compose
validates the whole project up front, this broke every service: "docker
compose up -d bitcoind", the first command in the README, failed too.
Rather than repair a five-year-old file pinned to bitcoind 0.19.0 and lnd
0.12.0-beta, this replaces it.
What changed:
- bitcoind 30.0 and 3x lnd 0.20.0-beta, using Polar's images. They are
multi-arch, so nothing is built locally and this works on arm64. The old
setup built bitcoind and lnd from local Dockerfiles.
- Three nodes, not one: alice -> bob -> carol. bob forwards, so RTL's
routing and forwarding screens have data. Two nodes leave them empty.
- scripts/seed.sh funds the nodes, opens channels, and makes payments with
fixed amounts. A fresh run reproduces identical state, so screenshots
taken before and after a change differ only by the change. It is
deliberately not idempotent and refuses to run against a seeded network,
since re-running would double-fund it.
- rtl/RTL-Config.regtest.json configures all three nodes. RTL rewrites its
config on startup, so an init container stages a copy into a volume: a
read-only bind mount makes RTL exit with EROFS, and a writable one would
let RTL modify a tracked file. It is not named RTL-Config.json because
.gitignore matches that bare name at any depth.
- bin/ln-cli now takes a node name and passes --lnddir=/home/lnd/.lnd,
because 'docker compose exec' lands as root while lnd's datadir is under
/home/lnd. Both helpers use compose v2.
- README rewritten to match.
Boltz, Core Lightning and Eclair are left out of this pass. Polar publishes
multi-arch clightning and eclair images, so adding them later needs compose
services, config entries and seed adapters, but no image building.
Verified from a clean 'down -v': all nodes sync, channels go active, 5/5
payments route through bob, bob records 5 forwards, and RTL serves the UI
with all three nodes configured. Two independent from-scratch runs produced
identical balances.
The old bitcoind/ and lnd/ build contexts are now unreferenced but left in
place for a follow-up.
2026-07-15 23:09:03 -07:00
|
|
|
|
Docker with Compose v2 (`docker compose`, not `docker-compose`).
|
2019-04-15 00:00:20 +01:00
|
|
|
|
|
Rebuild the regtest dev fixture: bitcoind + 3 LND nodes + RTL
The setup in docker/ has been unable to start since Feb 2021. The boltz
service added in f817ae39 references BOLTZ_* variables that are not in .env
(so its ports render as ":" and compose rejects the file with "invalid
proto"), a ./boltz build context that has never existed in the repo, and
boltz_data/boltz_shared volumes that are never declared. Because compose
validates the whole project up front, this broke every service: "docker
compose up -d bitcoind", the first command in the README, failed too.
Rather than repair a five-year-old file pinned to bitcoind 0.19.0 and lnd
0.12.0-beta, this replaces it.
What changed:
- bitcoind 30.0 and 3x lnd 0.20.0-beta, using Polar's images. They are
multi-arch, so nothing is built locally and this works on arm64. The old
setup built bitcoind and lnd from local Dockerfiles.
- Three nodes, not one: alice -> bob -> carol. bob forwards, so RTL's
routing and forwarding screens have data. Two nodes leave them empty.
- scripts/seed.sh funds the nodes, opens channels, and makes payments with
fixed amounts. A fresh run reproduces identical state, so screenshots
taken before and after a change differ only by the change. It is
deliberately not idempotent and refuses to run against a seeded network,
since re-running would double-fund it.
- rtl/RTL-Config.regtest.json configures all three nodes. RTL rewrites its
config on startup, so an init container stages a copy into a volume: a
read-only bind mount makes RTL exit with EROFS, and a writable one would
let RTL modify a tracked file. It is not named RTL-Config.json because
.gitignore matches that bare name at any depth.
- bin/ln-cli now takes a node name and passes --lnddir=/home/lnd/.lnd,
because 'docker compose exec' lands as root while lnd's datadir is under
/home/lnd. Both helpers use compose v2.
- README rewritten to match.
Boltz, Core Lightning and Eclair are left out of this pass. Polar publishes
multi-arch clightning and eclair images, so adding them later needs compose
services, config entries and seed adapters, but no image building.
Verified from a clean 'down -v': all nodes sync, channels go active, 5/5
payments route through bob, bob records 5 forwards, and RTL serves the UI
with all three nodes configured. Two independent from-scratch runs produced
identical balances.
The old bitcoind/ and lnd/ build contexts are now unreferenced but left in
place for a follow-up.
2026-07-15 23:09:03 -07:00
|
|
|
|
## Quick start
|
2019-04-15 00:00:20 +01:00
|
|
|
|
|
Rebuild the regtest dev fixture: bitcoind + 3 LND nodes + RTL
The setup in docker/ has been unable to start since Feb 2021. The boltz
service added in f817ae39 references BOLTZ_* variables that are not in .env
(so its ports render as ":" and compose rejects the file with "invalid
proto"), a ./boltz build context that has never existed in the repo, and
boltz_data/boltz_shared volumes that are never declared. Because compose
validates the whole project up front, this broke every service: "docker
compose up -d bitcoind", the first command in the README, failed too.
Rather than repair a five-year-old file pinned to bitcoind 0.19.0 and lnd
0.12.0-beta, this replaces it.
What changed:
- bitcoind 30.0 and 3x lnd 0.20.0-beta, using Polar's images. They are
multi-arch, so nothing is built locally and this works on arm64. The old
setup built bitcoind and lnd from local Dockerfiles.
- Three nodes, not one: alice -> bob -> carol. bob forwards, so RTL's
routing and forwarding screens have data. Two nodes leave them empty.
- scripts/seed.sh funds the nodes, opens channels, and makes payments with
fixed amounts. A fresh run reproduces identical state, so screenshots
taken before and after a change differ only by the change. It is
deliberately not idempotent and refuses to run against a seeded network,
since re-running would double-fund it.
- rtl/RTL-Config.regtest.json configures all three nodes. RTL rewrites its
config on startup, so an init container stages a copy into a volume: a
read-only bind mount makes RTL exit with EROFS, and a writable one would
let RTL modify a tracked file. It is not named RTL-Config.json because
.gitignore matches that bare name at any depth.
- bin/ln-cli now takes a node name and passes --lnddir=/home/lnd/.lnd,
because 'docker compose exec' lands as root while lnd's datadir is under
/home/lnd. Both helpers use compose v2.
- README rewritten to match.
Boltz, Core Lightning and Eclair are left out of this pass. Polar publishes
multi-arch clightning and eclair images, so adding them later needs compose
services, config entries and seed adapters, but no image building.
Verified from a clean 'down -v': all nodes sync, channels go active, 5/5
payments route through bob, bob records 5 forwards, and RTL serves the UI
with all three nodes configured. Two independent from-scratch runs produced
identical balances.
The old bitcoind/ and lnd/ build contexts are now unreferenced but left in
place for a follow-up.
2026-07-15 23:09:03 -07:00
|
|
|
|
From this directory:
|
2019-04-15 00:00:20 +01:00
|
|
|
|
|
Rebuild the regtest dev fixture: bitcoind + 3 LND nodes + RTL
The setup in docker/ has been unable to start since Feb 2021. The boltz
service added in f817ae39 references BOLTZ_* variables that are not in .env
(so its ports render as ":" and compose rejects the file with "invalid
proto"), a ./boltz build context that has never existed in the repo, and
boltz_data/boltz_shared volumes that are never declared. Because compose
validates the whole project up front, this broke every service: "docker
compose up -d bitcoind", the first command in the README, failed too.
Rather than repair a five-year-old file pinned to bitcoind 0.19.0 and lnd
0.12.0-beta, this replaces it.
What changed:
- bitcoind 30.0 and 3x lnd 0.20.0-beta, using Polar's images. They are
multi-arch, so nothing is built locally and this works on arm64. The old
setup built bitcoind and lnd from local Dockerfiles.
- Three nodes, not one: alice -> bob -> carol. bob forwards, so RTL's
routing and forwarding screens have data. Two nodes leave them empty.
- scripts/seed.sh funds the nodes, opens channels, and makes payments with
fixed amounts. A fresh run reproduces identical state, so screenshots
taken before and after a change differ only by the change. It is
deliberately not idempotent and refuses to run against a seeded network,
since re-running would double-fund it.
- rtl/RTL-Config.regtest.json configures all three nodes. RTL rewrites its
config on startup, so an init container stages a copy into a volume: a
read-only bind mount makes RTL exit with EROFS, and a writable one would
let RTL modify a tracked file. It is not named RTL-Config.json because
.gitignore matches that bare name at any depth.
- bin/ln-cli now takes a node name and passes --lnddir=/home/lnd/.lnd,
because 'docker compose exec' lands as root while lnd's datadir is under
/home/lnd. Both helpers use compose v2.
- README rewritten to match.
Boltz, Core Lightning and Eclair are left out of this pass. Polar publishes
multi-arch clightning and eclair images, so adding them later needs compose
services, config entries and seed adapters, but no image building.
Verified from a clean 'down -v': all nodes sync, channels go active, 5/5
payments route through bob, bob records 5 forwards, and RTL serves the UI
with all three nodes configured. Two independent from-scratch runs produced
identical balances.
The old bitcoind/ and lnd/ build contexts are now unreferenced but left in
place for a follow-up.
2026-07-15 23:09:03 -07:00
|
|
|
|
```bash
|
Add an Eclair node to the regtest docker fixture
Completes backend coverage of RTL's three implementations in the docker/
dev fixture: an eclair node (polarlightning/eclair 0.13.1) joins the three
LND nodes and the CLN node, wired to RTL over its HTTP API with basic auth,
and the seed opens an eclair->bob channel (3.5M sats, 1M pushed), sends two
direct payments and leaves one open invoice.
Non-obvious plumbing this needed:
- polarlightning/eclair instead of acinq/eclair: the official image is
amd64-only (useless on Apple Silicon) and its newest versioned tag is
years stale; Polar builds the same ACINQ source multi-arch.
- Eclair has no on-chain wallet of its own -- it drives a bitcoind wallet.
A new eclair-wallet-init container creates a dedicated "eclair" wallet
before the node starts; without it eclair attaches to "the default
loaded wallet", i.e. the rtldev mining wallet.
- bitcoind now also publishes a zmqpubhashblock endpoint (28336): eclair's
bitcoind.zmqblock consumes the hashblock topic, not the rawblock one LND
uses. Wired to rawblock, eclair never sees new blocks and channels hang
in WAIT_FOR_FUNDING_CONFIRMED.
- Eclair confirms channels at 8 blocks (channel.min-depth-blocks), not 6,
and 'open' returns before the funding tx is broadcast -- the seed waits
for the mempool and mines 8 blocks for this channel.
Adds a bin/e-cli helper (eclair-cli with the API password), updates the
README, and verified end-to-end: seed completes, the channel reaches
NORMAL, both payments settle, and RTL's /rtl/api/ecl endpoints return the
node, channel and invoice data.
2026-07-18 00:23:08 -07:00
|
|
|
|
docker compose up -d # bitcoind, alice, bob, carol, cln, eclair, rtl
|
Rebuild the regtest dev fixture: bitcoind + 3 LND nodes + RTL
The setup in docker/ has been unable to start since Feb 2021. The boltz
service added in f817ae39 references BOLTZ_* variables that are not in .env
(so its ports render as ":" and compose rejects the file with "invalid
proto"), a ./boltz build context that has never existed in the repo, and
boltz_data/boltz_shared volumes that are never declared. Because compose
validates the whole project up front, this broke every service: "docker
compose up -d bitcoind", the first command in the README, failed too.
Rather than repair a five-year-old file pinned to bitcoind 0.19.0 and lnd
0.12.0-beta, this replaces it.
What changed:
- bitcoind 30.0 and 3x lnd 0.20.0-beta, using Polar's images. They are
multi-arch, so nothing is built locally and this works on arm64. The old
setup built bitcoind and lnd from local Dockerfiles.
- Three nodes, not one: alice -> bob -> carol. bob forwards, so RTL's
routing and forwarding screens have data. Two nodes leave them empty.
- scripts/seed.sh funds the nodes, opens channels, and makes payments with
fixed amounts. A fresh run reproduces identical state, so screenshots
taken before and after a change differ only by the change. It is
deliberately not idempotent and refuses to run against a seeded network,
since re-running would double-fund it.
- rtl/RTL-Config.regtest.json configures all three nodes. RTL rewrites its
config on startup, so an init container stages a copy into a volume: a
read-only bind mount makes RTL exit with EROFS, and a writable one would
let RTL modify a tracked file. It is not named RTL-Config.json because
.gitignore matches that bare name at any depth.
- bin/ln-cli now takes a node name and passes --lnddir=/home/lnd/.lnd,
because 'docker compose exec' lands as root while lnd's datadir is under
/home/lnd. Both helpers use compose v2.
- README rewritten to match.
Boltz, Core Lightning and Eclair are left out of this pass. Polar publishes
multi-arch clightning and eclair images, so adding them later needs compose
services, config entries and seed adapters, but no image building.
Verified from a clean 'down -v': all nodes sync, channels go active, 5/5
payments route through bob, bob records 5 forwards, and RTL serves the UI
with all three nodes configured. Two independent from-scratch runs produced
identical balances.
The old bitcoind/ and lnd/ build contexts are now unreferenced but left in
place for a follow-up.
2026-07-15 23:09:03 -07:00
|
|
|
|
./scripts/seed.sh # fund, connect, open channels, make payments
|
2019-04-15 00:00:20 +01:00
|
|
|
|
```
|
|
|
|
|
|
|
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
|
|
|
|
To also bring up the BTCPay single-sign-on harness, add `--profile sso` — see
|
|
|
|
|
|
[BTCPay SSO harness](#btcpay-sso-harness).
|
|
|
|
|
|
|
Add an Eclair node to the regtest docker fixture
Completes backend coverage of RTL's three implementations in the docker/
dev fixture: an eclair node (polarlightning/eclair 0.13.1) joins the three
LND nodes and the CLN node, wired to RTL over its HTTP API with basic auth,
and the seed opens an eclair->bob channel (3.5M sats, 1M pushed), sends two
direct payments and leaves one open invoice.
Non-obvious plumbing this needed:
- polarlightning/eclair instead of acinq/eclair: the official image is
amd64-only (useless on Apple Silicon) and its newest versioned tag is
years stale; Polar builds the same ACINQ source multi-arch.
- Eclair has no on-chain wallet of its own -- it drives a bitcoind wallet.
A new eclair-wallet-init container creates a dedicated "eclair" wallet
before the node starts; without it eclair attaches to "the default
loaded wallet", i.e. the rtldev mining wallet.
- bitcoind now also publishes a zmqpubhashblock endpoint (28336): eclair's
bitcoind.zmqblock consumes the hashblock topic, not the rawblock one LND
uses. Wired to rawblock, eclair never sees new blocks and channels hang
in WAIT_FOR_FUNDING_CONFIRMED.
- Eclair confirms channels at 8 blocks (channel.min-depth-blocks), not 6,
and 'open' returns before the funding tx is broadcast -- the seed waits
for the mempool and mines 8 blocks for this channel.
Adds a bin/e-cli helper (eclair-cli with the API password), updates the
README, and verified end-to-end: seed completes, the channel reaches
NORMAL, both payments settle, and RTL's /rtl/api/ecl endpoints return the
node, channel and invoice data.
2026-07-18 00:23:08 -07:00
|
|
|
|
Then open <http://localhost:3000> — password `rtldev`. All five nodes (alice, bob,
|
|
|
|
|
|
carol, cln, eclair) appear in the node switcher.
|
2019-04-15 00:00:20 +01:00
|
|
|
|
|
Rebuild the regtest dev fixture: bitcoind + 3 LND nodes + RTL
The setup in docker/ has been unable to start since Feb 2021. The boltz
service added in f817ae39 references BOLTZ_* variables that are not in .env
(so its ports render as ":" and compose rejects the file with "invalid
proto"), a ./boltz build context that has never existed in the repo, and
boltz_data/boltz_shared volumes that are never declared. Because compose
validates the whole project up front, this broke every service: "docker
compose up -d bitcoind", the first command in the README, failed too.
Rather than repair a five-year-old file pinned to bitcoind 0.19.0 and lnd
0.12.0-beta, this replaces it.
What changed:
- bitcoind 30.0 and 3x lnd 0.20.0-beta, using Polar's images. They are
multi-arch, so nothing is built locally and this works on arm64. The old
setup built bitcoind and lnd from local Dockerfiles.
- Three nodes, not one: alice -> bob -> carol. bob forwards, so RTL's
routing and forwarding screens have data. Two nodes leave them empty.
- scripts/seed.sh funds the nodes, opens channels, and makes payments with
fixed amounts. A fresh run reproduces identical state, so screenshots
taken before and after a change differ only by the change. It is
deliberately not idempotent and refuses to run against a seeded network,
since re-running would double-fund it.
- rtl/RTL-Config.regtest.json configures all three nodes. RTL rewrites its
config on startup, so an init container stages a copy into a volume: a
read-only bind mount makes RTL exit with EROFS, and a writable one would
let RTL modify a tracked file. It is not named RTL-Config.json because
.gitignore matches that bare name at any depth.
- bin/ln-cli now takes a node name and passes --lnddir=/home/lnd/.lnd,
because 'docker compose exec' lands as root while lnd's datadir is under
/home/lnd. Both helpers use compose v2.
- README rewritten to match.
Boltz, Core Lightning and Eclair are left out of this pass. Polar publishes
multi-arch clightning and eclair images, so adding them later needs compose
services, config entries and seed adapters, but no image building.
Verified from a clean 'down -v': all nodes sync, channels go active, 5/5
payments route through bob, bob records 5 forwards, and RTL serves the UI
with all three nodes configured. Two independent from-scratch runs produced
identical balances.
The old bitcoind/ and lnd/ build contexts are now unreferenced but left in
place for a follow-up.
2026-07-15 23:09:03 -07:00
|
|
|
|
Tear down, discarding all state:
|
2019-04-15 00:00:20 +01:00
|
|
|
|
|
Rebuild the regtest dev fixture: bitcoind + 3 LND nodes + RTL
The setup in docker/ has been unable to start since Feb 2021. The boltz
service added in f817ae39 references BOLTZ_* variables that are not in .env
(so its ports render as ":" and compose rejects the file with "invalid
proto"), a ./boltz build context that has never existed in the repo, and
boltz_data/boltz_shared volumes that are never declared. Because compose
validates the whole project up front, this broke every service: "docker
compose up -d bitcoind", the first command in the README, failed too.
Rather than repair a five-year-old file pinned to bitcoind 0.19.0 and lnd
0.12.0-beta, this replaces it.
What changed:
- bitcoind 30.0 and 3x lnd 0.20.0-beta, using Polar's images. They are
multi-arch, so nothing is built locally and this works on arm64. The old
setup built bitcoind and lnd from local Dockerfiles.
- Three nodes, not one: alice -> bob -> carol. bob forwards, so RTL's
routing and forwarding screens have data. Two nodes leave them empty.
- scripts/seed.sh funds the nodes, opens channels, and makes payments with
fixed amounts. A fresh run reproduces identical state, so screenshots
taken before and after a change differ only by the change. It is
deliberately not idempotent and refuses to run against a seeded network,
since re-running would double-fund it.
- rtl/RTL-Config.regtest.json configures all three nodes. RTL rewrites its
config on startup, so an init container stages a copy into a volume: a
read-only bind mount makes RTL exit with EROFS, and a writable one would
let RTL modify a tracked file. It is not named RTL-Config.json because
.gitignore matches that bare name at any depth.
- bin/ln-cli now takes a node name and passes --lnddir=/home/lnd/.lnd,
because 'docker compose exec' lands as root while lnd's datadir is under
/home/lnd. Both helpers use compose v2.
- README rewritten to match.
Boltz, Core Lightning and Eclair are left out of this pass. Polar publishes
multi-arch clightning and eclair images, so adding them later needs compose
services, config entries and seed adapters, but no image building.
Verified from a clean 'down -v': all nodes sync, channels go active, 5/5
payments route through bob, bob records 5 forwards, and RTL serves the UI
with all three nodes configured. Two independent from-scratch runs produced
identical balances.
The old bitcoind/ and lnd/ build contexts are now unreferenced but left in
place for a follow-up.
2026-07-15 23:09:03 -07:00
|
|
|
|
```bash
|
|
|
|
|
|
docker compose down -v
|
2019-04-15 00:00:20 +01:00
|
|
|
|
```
|
2021-12-29 18:08:41 -05:00
|
|
|
|
|
Rebuild the regtest dev fixture: bitcoind + 3 LND nodes + RTL
The setup in docker/ has been unable to start since Feb 2021. The boltz
service added in f817ae39 references BOLTZ_* variables that are not in .env
(so its ports render as ":" and compose rejects the file with "invalid
proto"), a ./boltz build context that has never existed in the repo, and
boltz_data/boltz_shared volumes that are never declared. Because compose
validates the whole project up front, this broke every service: "docker
compose up -d bitcoind", the first command in the README, failed too.
Rather than repair a five-year-old file pinned to bitcoind 0.19.0 and lnd
0.12.0-beta, this replaces it.
What changed:
- bitcoind 30.0 and 3x lnd 0.20.0-beta, using Polar's images. They are
multi-arch, so nothing is built locally and this works on arm64. The old
setup built bitcoind and lnd from local Dockerfiles.
- Three nodes, not one: alice -> bob -> carol. bob forwards, so RTL's
routing and forwarding screens have data. Two nodes leave them empty.
- scripts/seed.sh funds the nodes, opens channels, and makes payments with
fixed amounts. A fresh run reproduces identical state, so screenshots
taken before and after a change differ only by the change. It is
deliberately not idempotent and refuses to run against a seeded network,
since re-running would double-fund it.
- rtl/RTL-Config.regtest.json configures all three nodes. RTL rewrites its
config on startup, so an init container stages a copy into a volume: a
read-only bind mount makes RTL exit with EROFS, and a writable one would
let RTL modify a tracked file. It is not named RTL-Config.json because
.gitignore matches that bare name at any depth.
- bin/ln-cli now takes a node name and passes --lnddir=/home/lnd/.lnd,
because 'docker compose exec' lands as root while lnd's datadir is under
/home/lnd. Both helpers use compose v2.
- README rewritten to match.
Boltz, Core Lightning and Eclair are left out of this pass. Polar publishes
multi-arch clightning and eclair images, so adding them later needs compose
services, config entries and seed adapters, but no image building.
Verified from a clean 'down -v': all nodes sync, channels go active, 5/5
payments route through bob, bob records 5 forwards, and RTL serves the UI
with all three nodes configured. Two independent from-scratch runs produced
identical balances.
The old bitcoind/ and lnd/ build contexts are now unreferenced but left in
place for a follow-up.
2026-07-15 23:09:03 -07:00
|
|
|
|
## What the seed creates
|
2021-12-29 18:08:41 -05:00
|
|
|
|
|
Rebuild the regtest dev fixture: bitcoind + 3 LND nodes + RTL
The setup in docker/ has been unable to start since Feb 2021. The boltz
service added in f817ae39 references BOLTZ_* variables that are not in .env
(so its ports render as ":" and compose rejects the file with "invalid
proto"), a ./boltz build context that has never existed in the repo, and
boltz_data/boltz_shared volumes that are never declared. Because compose
validates the whole project up front, this broke every service: "docker
compose up -d bitcoind", the first command in the README, failed too.
Rather than repair a five-year-old file pinned to bitcoind 0.19.0 and lnd
0.12.0-beta, this replaces it.
What changed:
- bitcoind 30.0 and 3x lnd 0.20.0-beta, using Polar's images. They are
multi-arch, so nothing is built locally and this works on arm64. The old
setup built bitcoind and lnd from local Dockerfiles.
- Three nodes, not one: alice -> bob -> carol. bob forwards, so RTL's
routing and forwarding screens have data. Two nodes leave them empty.
- scripts/seed.sh funds the nodes, opens channels, and makes payments with
fixed amounts. A fresh run reproduces identical state, so screenshots
taken before and after a change differ only by the change. It is
deliberately not idempotent and refuses to run against a seeded network,
since re-running would double-fund it.
- rtl/RTL-Config.regtest.json configures all three nodes. RTL rewrites its
config on startup, so an init container stages a copy into a volume: a
read-only bind mount makes RTL exit with EROFS, and a writable one would
let RTL modify a tracked file. It is not named RTL-Config.json because
.gitignore matches that bare name at any depth.
- bin/ln-cli now takes a node name and passes --lnddir=/home/lnd/.lnd,
because 'docker compose exec' lands as root while lnd's datadir is under
/home/lnd. Both helpers use compose v2.
- README rewritten to match.
Boltz, Core Lightning and Eclair are left out of this pass. Polar publishes
multi-arch clightning and eclair images, so adding them later needs compose
services, config entries and seed adapters, but no image building.
Verified from a clean 'down -v': all nodes sync, channels go active, 5/5
payments route through bob, bob records 5 forwards, and RTL serves the UI
with all three nodes configured. Two independent from-scratch runs produced
identical balances.
The old bitcoind/ and lnd/ build contexts are now unreferenced but left in
place for a follow-up.
2026-07-15 23:09:03 -07:00
|
|
|
|
| | |
|
|
|
|
|
|
|---|---|
|
Add an Eclair node to the regtest docker fixture
Completes backend coverage of RTL's three implementations in the docker/
dev fixture: an eclair node (polarlightning/eclair 0.13.1) joins the three
LND nodes and the CLN node, wired to RTL over its HTTP API with basic auth,
and the seed opens an eclair->bob channel (3.5M sats, 1M pushed), sends two
direct payments and leaves one open invoice.
Non-obvious plumbing this needed:
- polarlightning/eclair instead of acinq/eclair: the official image is
amd64-only (useless on Apple Silicon) and its newest versioned tag is
years stale; Polar builds the same ACINQ source multi-arch.
- Eclair has no on-chain wallet of its own -- it drives a bitcoind wallet.
A new eclair-wallet-init container creates a dedicated "eclair" wallet
before the node starts; without it eclair attaches to "the default
loaded wallet", i.e. the rtldev mining wallet.
- bitcoind now also publishes a zmqpubhashblock endpoint (28336): eclair's
bitcoind.zmqblock consumes the hashblock topic, not the rawblock one LND
uses. Wired to rawblock, eclair never sees new blocks and channels hang
in WAIT_FOR_FUNDING_CONFIRMED.
- Eclair confirms channels at 8 blocks (channel.min-depth-blocks), not 6,
and 'open' returns before the funding tx is broadcast -- the seed waits
for the mempool and mines 8 blocks for this channel.
Adds a bin/e-cli helper (eclair-cli with the API password), updates the
README, and verified end-to-end: seed completes, the channel reaches
NORMAL, both payments settle, and RTL's /rtl/api/ecl endpoints return the
node, channel and invoice data.
2026-07-18 00:23:08 -07:00
|
|
|
|
| On-chain | 10,000,000 sats per node (LND) + 10,000,000 sats each on cln and eclair, confirmed |
|
|
|
|
|
|
| Channels | alice→bob 5,000,000 · bob→carol 3,000,000 · eclair→bob 3,500,000 sats (1,000,000 pushed each) · cln→alice 4,000,000 sats (no push) |
|
Rebuild the regtest dev fixture: bitcoind + 3 LND nodes + RTL
The setup in docker/ has been unable to start since Feb 2021. The boltz
service added in f817ae39 references BOLTZ_* variables that are not in .env
(so its ports render as ":" and compose rejects the file with "invalid
proto"), a ./boltz build context that has never existed in the repo, and
boltz_data/boltz_shared volumes that are never declared. Because compose
validates the whole project up front, this broke every service: "docker
compose up -d bitcoind", the first command in the README, failed too.
Rather than repair a five-year-old file pinned to bitcoind 0.19.0 and lnd
0.12.0-beta, this replaces it.
What changed:
- bitcoind 30.0 and 3x lnd 0.20.0-beta, using Polar's images. They are
multi-arch, so nothing is built locally and this works on arm64. The old
setup built bitcoind and lnd from local Dockerfiles.
- Three nodes, not one: alice -> bob -> carol. bob forwards, so RTL's
routing and forwarding screens have data. Two nodes leave them empty.
- scripts/seed.sh funds the nodes, opens channels, and makes payments with
fixed amounts. A fresh run reproduces identical state, so screenshots
taken before and after a change differ only by the change. It is
deliberately not idempotent and refuses to run against a seeded network,
since re-running would double-fund it.
- rtl/RTL-Config.regtest.json configures all three nodes. RTL rewrites its
config on startup, so an init container stages a copy into a volume: a
read-only bind mount makes RTL exit with EROFS, and a writable one would
let RTL modify a tracked file. It is not named RTL-Config.json because
.gitignore matches that bare name at any depth.
- bin/ln-cli now takes a node name and passes --lnddir=/home/lnd/.lnd,
because 'docker compose exec' lands as root while lnd's datadir is under
/home/lnd. Both helpers use compose v2.
- README rewritten to match.
Boltz, Core Lightning and Eclair are left out of this pass. Polar publishes
multi-arch clightning and eclair images, so adding them later needs compose
services, config entries and seed adapters, but no image building.
Verified from a clean 'down -v': all nodes sync, channels go active, 5/5
payments route through bob, bob records 5 forwards, and RTL serves the UI
with all three nodes configured. Two independent from-scratch runs produced
identical balances.
The old bitcoind/ and lnd/ build contexts are now unreferenced but left in
place for a follow-up.
2026-07-15 23:09:03 -07:00
|
|
|
|
| Routed payments | 5 × alice→carol via bob (10k, 25k, 50k, 75k, 100k sats) |
|
Add an Eclair node to the regtest docker fixture
Completes backend coverage of RTL's three implementations in the docker/
dev fixture: an eclair node (polarlightning/eclair 0.13.1) joins the three
LND nodes and the CLN node, wired to RTL over its HTTP API with basic auth,
and the seed opens an eclair->bob channel (3.5M sats, 1M pushed), sends two
direct payments and leaves one open invoice.
Non-obvious plumbing this needed:
- polarlightning/eclair instead of acinq/eclair: the official image is
amd64-only (useless on Apple Silicon) and its newest versioned tag is
years stale; Polar builds the same ACINQ source multi-arch.
- Eclair has no on-chain wallet of its own -- it drives a bitcoind wallet.
A new eclair-wallet-init container creates a dedicated "eclair" wallet
before the node starts; without it eclair attaches to "the default
loaded wallet", i.e. the rtldev mining wallet.
- bitcoind now also publishes a zmqpubhashblock endpoint (28336): eclair's
bitcoind.zmqblock consumes the hashblock topic, not the rawblock one LND
uses. Wired to rawblock, eclair never sees new blocks and channels hang
in WAIT_FOR_FUNDING_CONFIRMED.
- Eclair confirms channels at 8 blocks (channel.min-depth-blocks), not 6,
and 'open' returns before the funding tx is broadcast -- the seed waits
for the mempool and mines 8 blocks for this channel.
Adds a bin/e-cli helper (eclair-cli with the API password), updates the
README, and verified end-to-end: seed completes, the channel reaches
NORMAL, both payments settle, and RTL's /rtl/api/ecl endpoints return the
node, channel and invoice data.
2026-07-18 00:23:08 -07:00
|
|
|
|
| Direct payments | 2 × alice→bob (5k, 15k sats) · 2 × eclair→bob (8k, 18k sats) |
|
|
|
|
|
|
| Open invoices | 2 unpaid on carol (20k, 40k sats) · 1 unpaid on eclair (30k sats) |
|
|
|
|
|
|
| Personas | alice + bob + cln + eclair OPERATOR, carol MERCHANT |
|
Rebuild the regtest dev fixture: bitcoind + 3 LND nodes + RTL
The setup in docker/ has been unable to start since Feb 2021. The boltz
service added in f817ae39 references BOLTZ_* variables that are not in .env
(so its ports render as ":" and compose rejects the file with "invalid
proto"), a ./boltz build context that has never existed in the repo, and
boltz_data/boltz_shared volumes that are never declared. Because compose
validates the whole project up front, this broke every service: "docker
compose up -d bitcoind", the first command in the README, failed too.
Rather than repair a five-year-old file pinned to bitcoind 0.19.0 and lnd
0.12.0-beta, this replaces it.
What changed:
- bitcoind 30.0 and 3x lnd 0.20.0-beta, using Polar's images. They are
multi-arch, so nothing is built locally and this works on arm64. The old
setup built bitcoind and lnd from local Dockerfiles.
- Three nodes, not one: alice -> bob -> carol. bob forwards, so RTL's
routing and forwarding screens have data. Two nodes leave them empty.
- scripts/seed.sh funds the nodes, opens channels, and makes payments with
fixed amounts. A fresh run reproduces identical state, so screenshots
taken before and after a change differ only by the change. It is
deliberately not idempotent and refuses to run against a seeded network,
since re-running would double-fund it.
- rtl/RTL-Config.regtest.json configures all three nodes. RTL rewrites its
config on startup, so an init container stages a copy into a volume: a
read-only bind mount makes RTL exit with EROFS, and a writable one would
let RTL modify a tracked file. It is not named RTL-Config.json because
.gitignore matches that bare name at any depth.
- bin/ln-cli now takes a node name and passes --lnddir=/home/lnd/.lnd,
because 'docker compose exec' lands as root while lnd's datadir is under
/home/lnd. Both helpers use compose v2.
- README rewritten to match.
Boltz, Core Lightning and Eclair are left out of this pass. Polar publishes
multi-arch clightning and eclair images, so adding them later needs compose
services, config entries and seed adapters, but no image building.
Verified from a clean 'down -v': all nodes sync, channels go active, 5/5
payments route through bob, bob records 5 forwards, and RTL serves the UI
with all three nodes configured. Two independent from-scratch runs produced
identical balances.
The old bitcoind/ and lnd/ build contexts are now unreferenced but left in
place for a follow-up.
2026-07-15 23:09:03 -07:00
|
|
|
|
|
|
|
|
|
|
## Determinism
|
|
|
|
|
|
|
|
|
|
|
|
Every amount and payment in `scripts/seed.sh` is fixed. A fresh run always produces
|
|
|
|
|
|
identical state, so screenshots taken before and after a change differ only by the
|
|
|
|
|
|
change. **Do not introduce randomness.**
|
|
|
|
|
|
|
|
|
|
|
|
The seed is deterministic but deliberately *not* idempotent — running it twice would
|
|
|
|
|
|
fund every node again and open a second set of channels. It refuses to run against an
|
|
|
|
|
|
already-seeded network. To start over:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
docker compose down -v && docker compose up -d && ./scripts/seed.sh
|
2021-12-29 18:08:41 -05:00
|
|
|
|
```
|
|
|
|
|
|
|
Rebuild the regtest dev fixture: bitcoind + 3 LND nodes + RTL
The setup in docker/ has been unable to start since Feb 2021. The boltz
service added in f817ae39 references BOLTZ_* variables that are not in .env
(so its ports render as ":" and compose rejects the file with "invalid
proto"), a ./boltz build context that has never existed in the repo, and
boltz_data/boltz_shared volumes that are never declared. Because compose
validates the whole project up front, this broke every service: "docker
compose up -d bitcoind", the first command in the README, failed too.
Rather than repair a five-year-old file pinned to bitcoind 0.19.0 and lnd
0.12.0-beta, this replaces it.
What changed:
- bitcoind 30.0 and 3x lnd 0.20.0-beta, using Polar's images. They are
multi-arch, so nothing is built locally and this works on arm64. The old
setup built bitcoind and lnd from local Dockerfiles.
- Three nodes, not one: alice -> bob -> carol. bob forwards, so RTL's
routing and forwarding screens have data. Two nodes leave them empty.
- scripts/seed.sh funds the nodes, opens channels, and makes payments with
fixed amounts. A fresh run reproduces identical state, so screenshots
taken before and after a change differ only by the change. It is
deliberately not idempotent and refuses to run against a seeded network,
since re-running would double-fund it.
- rtl/RTL-Config.regtest.json configures all three nodes. RTL rewrites its
config on startup, so an init container stages a copy into a volume: a
read-only bind mount makes RTL exit with EROFS, and a writable one would
let RTL modify a tracked file. It is not named RTL-Config.json because
.gitignore matches that bare name at any depth.
- bin/ln-cli now takes a node name and passes --lnddir=/home/lnd/.lnd,
because 'docker compose exec' lands as root while lnd's datadir is under
/home/lnd. Both helpers use compose v2.
- README rewritten to match.
Boltz, Core Lightning and Eclair are left out of this pass. Polar publishes
multi-arch clightning and eclair images, so adding them later needs compose
services, config entries and seed adapters, but no image building.
Verified from a clean 'down -v': all nodes sync, channels go active, 5/5
payments route through bob, bob records 5 forwards, and RTL serves the UI
with all three nodes configured. Two independent from-scratch runs produced
identical balances.
The old bitcoind/ and lnd/ build contexts are now unreferenced but left in
place for a follow-up.
2026-07-15 23:09:03 -07:00
|
|
|
|
## Helpers
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
bin/b-cli getblockcount # bitcoin-cli
|
|
|
|
|
|
bin/b-cli -rpcwallet=rtldev getbalance
|
|
|
|
|
|
bin/ln-cli alice getinfo # lncli, node name required
|
|
|
|
|
|
bin/ln-cli bob listchannels
|
|
|
|
|
|
bin/ln-cli bob fwdinghistory # forwarding history
|
Add an Eclair node to the regtest docker fixture
Completes backend coverage of RTL's three implementations in the docker/
dev fixture: an eclair node (polarlightning/eclair 0.13.1) joins the three
LND nodes and the CLN node, wired to RTL over its HTTP API with basic auth,
and the seed opens an eclair->bob channel (3.5M sats, 1M pushed), sends two
direct payments and leaves one open invoice.
Non-obvious plumbing this needed:
- polarlightning/eclair instead of acinq/eclair: the official image is
amd64-only (useless on Apple Silicon) and its newest versioned tag is
years stale; Polar builds the same ACINQ source multi-arch.
- Eclair has no on-chain wallet of its own -- it drives a bitcoind wallet.
A new eclair-wallet-init container creates a dedicated "eclair" wallet
before the node starts; without it eclair attaches to "the default
loaded wallet", i.e. the rtldev mining wallet.
- bitcoind now also publishes a zmqpubhashblock endpoint (28336): eclair's
bitcoind.zmqblock consumes the hashblock topic, not the rawblock one LND
uses. Wired to rawblock, eclair never sees new blocks and channels hang
in WAIT_FOR_FUNDING_CONFIRMED.
- Eclair confirms channels at 8 blocks (channel.min-depth-blocks), not 6,
and 'open' returns before the funding tx is broadcast -- the seed waits
for the mempool and mines 8 blocks for this channel.
Adds a bin/e-cli helper (eclair-cli with the API password), updates the
README, and verified end-to-end: seed completes, the channel reaches
NORMAL, both payments settle, and RTL's /rtl/api/ecl endpoints return the
node, channel and invoice data.
2026-07-18 00:23:08 -07:00
|
|
|
|
bin/e-cli getinfo # eclair-cli
|
|
|
|
|
|
bin/e-cli channels
|
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
|
|
|
|
bin/sso-url # BTCPay-style SSO link (needs --profile sso)
|
Add a Core Lightning node to the regtest docker fixture
The fixture had only bitcoind + three LND nodes, so RTL's Core Lightning screens
had no backend to exercise. Add a `cln` node (official elementsproject/lightningd
image, multi-arch) wired to RTL over clnrest with rune auth, and have the seed
open a cln->alice channel so the CLN channel/peer screens have real data.
- docker-compose.yml: cln service (clnrest on 0.0.0.0:3010, https), a healthcheck
gated on the rune file so rtl waits for it, and rtl now mounts the cln volume
read-only and depends on cln being healthy. The rtl image is parameterized via
${RTL_IMAGE:-...} so an unreleased build can be tested against the fixture.
- cln/poststart.d/create-rune.sh: once the RPC is up, create a master rune and
write it as LIGHTNING_RUNE="..." where RTL reads it (runePath). Polls for RPC
readiness because the image entrypoint can invoke poststart before the socket
exists.
- RTL-Config.regtest.json: add node index 4 (CLN, rune auth, https://cln:3010).
- seed.sh: fund cln, connect to alice, open a 4,000,000 sat channel, wait active.
- README + release notes updated.
Used to verify the CLN channel connection-status fix (#1606) end-to-end.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 10:03:34 -07:00
|
|
|
|
docker compose exec cln lightning-cli --network=regtest listpeerchannels # Core Lightning
|
2021-12-29 18:08:41 -05:00
|
|
|
|
```
|
|
|
|
|
|
|
Rebuild the regtest dev fixture: bitcoind + 3 LND nodes + RTL
The setup in docker/ has been unable to start since Feb 2021. The boltz
service added in f817ae39 references BOLTZ_* variables that are not in .env
(so its ports render as ":" and compose rejects the file with "invalid
proto"), a ./boltz build context that has never existed in the repo, and
boltz_data/boltz_shared volumes that are never declared. Because compose
validates the whole project up front, this broke every service: "docker
compose up -d bitcoind", the first command in the README, failed too.
Rather than repair a five-year-old file pinned to bitcoind 0.19.0 and lnd
0.12.0-beta, this replaces it.
What changed:
- bitcoind 30.0 and 3x lnd 0.20.0-beta, using Polar's images. They are
multi-arch, so nothing is built locally and this works on arm64. The old
setup built bitcoind and lnd from local Dockerfiles.
- Three nodes, not one: alice -> bob -> carol. bob forwards, so RTL's
routing and forwarding screens have data. Two nodes leave them empty.
- scripts/seed.sh funds the nodes, opens channels, and makes payments with
fixed amounts. A fresh run reproduces identical state, so screenshots
taken before and after a change differ only by the change. It is
deliberately not idempotent and refuses to run against a seeded network,
since re-running would double-fund it.
- rtl/RTL-Config.regtest.json configures all three nodes. RTL rewrites its
config on startup, so an init container stages a copy into a volume: a
read-only bind mount makes RTL exit with EROFS, and a writable one would
let RTL modify a tracked file. It is not named RTL-Config.json because
.gitignore matches that bare name at any depth.
- bin/ln-cli now takes a node name and passes --lnddir=/home/lnd/.lnd,
because 'docker compose exec' lands as root while lnd's datadir is under
/home/lnd. Both helpers use compose v2.
- README rewritten to match.
Boltz, Core Lightning and Eclair are left out of this pass. Polar publishes
multi-arch clightning and eclair images, so adding them later needs compose
services, config entries and seed adapters, but no image building.
Verified from a clean 'down -v': all nodes sync, channels go active, 5/5
payments route through bob, bob records 5 forwards, and RTL serves the UI
with all three nodes configured. Two independent from-scratch runs produced
identical balances.
The old bitcoind/ and lnd/ build contexts are now unreferenced but left in
place for a follow-up.
2026-07-15 23:09:03 -07:00
|
|
|
|
Logs:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
docker compose logs -f rtl
|
|
|
|
|
|
docker compose logs alice
|
2021-12-29 18:08:41 -05:00
|
|
|
|
```
|
Rebuild the regtest dev fixture: bitcoind + 3 LND nodes + RTL
The setup in docker/ has been unable to start since Feb 2021. The boltz
service added in f817ae39 references BOLTZ_* variables that are not in .env
(so its ports render as ":" and compose rejects the file with "invalid
proto"), a ./boltz build context that has never existed in the repo, and
boltz_data/boltz_shared volumes that are never declared. Because compose
validates the whole project up front, this broke every service: "docker
compose up -d bitcoind", the first command in the README, failed too.
Rather than repair a five-year-old file pinned to bitcoind 0.19.0 and lnd
0.12.0-beta, this replaces it.
What changed:
- bitcoind 30.0 and 3x lnd 0.20.0-beta, using Polar's images. They are
multi-arch, so nothing is built locally and this works on arm64. The old
setup built bitcoind and lnd from local Dockerfiles.
- Three nodes, not one: alice -> bob -> carol. bob forwards, so RTL's
routing and forwarding screens have data. Two nodes leave them empty.
- scripts/seed.sh funds the nodes, opens channels, and makes payments with
fixed amounts. A fresh run reproduces identical state, so screenshots
taken before and after a change differ only by the change. It is
deliberately not idempotent and refuses to run against a seeded network,
since re-running would double-fund it.
- rtl/RTL-Config.regtest.json configures all three nodes. RTL rewrites its
config on startup, so an init container stages a copy into a volume: a
read-only bind mount makes RTL exit with EROFS, and a writable one would
let RTL modify a tracked file. It is not named RTL-Config.json because
.gitignore matches that bare name at any depth.
- bin/ln-cli now takes a node name and passes --lnddir=/home/lnd/.lnd,
because 'docker compose exec' lands as root while lnd's datadir is under
/home/lnd. Both helpers use compose v2.
- README rewritten to match.
Boltz, Core Lightning and Eclair are left out of this pass. Polar publishes
multi-arch clightning and eclair images, so adding them later needs compose
services, config entries and seed adapters, but no image building.
Verified from a clean 'down -v': all nodes sync, channels go active, 5/5
payments route through bob, bob records 5 forwards, and RTL serves the UI
with all three nodes configured. Two independent from-scratch runs produced
identical balances.
The old bitcoind/ and lnd/ build contexts are now unreferenced but left in
place for a follow-up.
2026-07-15 23:09:03 -07:00
|
|
|
|
|
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
|
|
|
|
## BTCPay SSO harness
|
|
|
|
|
|
|
|
|
|
|
|
BTCPay Server bundles RTL and runs it in single-sign-on mode, reached through a very
|
|
|
|
|
|
different entry path than the standalone login: no password, a rotating cookie, and a
|
|
|
|
|
|
reverse proxy in front. That path has broken before without the standalone flow
|
|
|
|
|
|
noticing, so the fixture can reproduce it.
|
|
|
|
|
|
|
|
|
|
|
|
It is behind a compose profile, so a plain `docker compose up -d` does not start it:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
docker compose --profile sso up -d
|
|
|
|
|
|
./scripts/verify-sso.sh # 11 assertions over the whole entry path
|
|
|
|
|
|
open "$(bin/sso-url)" # or click through it yourself
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`bin/sso-url` prints the link BTCPay renders on its Services page. Following it lands
|
|
|
|
|
|
you in RTL already authenticated, against the `alice` node.
|
|
|
|
|
|
|
|
|
|
|
|
### How the flow works
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
sequenceDiagram
|
|
|
|
|
|
participant B as Browser
|
|
|
|
|
|
participant P as rtl-sso-proxy<br/>(stands in for traefik)
|
|
|
|
|
|
participant R as rtl-sso<br/>(RTL_SSO=1)
|
|
|
|
|
|
participant C as .cookie<br/>(shared volume)
|
|
|
|
|
|
|
|
|
|
|
|
R->>C: writes 64 random bytes at startup
|
|
|
|
|
|
Note over B: bin/sso-url reads the cookie —<br/>BTCPay reads the same file
|
|
|
|
|
|
B->>P: GET /rtl/api/authenticate/cookie?access-key=<cookie>
|
|
|
|
|
|
P->>R: same URI, prefix passed through
|
|
|
|
|
|
R-->>B: not a registered route → catch-all:<br/>mints XSRF-TOKEN, serves index.html
|
|
|
|
|
|
B->>P: POST /rtl/api/authenticate<br/>{ PASSWORD, sha256(access-key) }
|
|
|
|
|
|
P->>R:
|
|
|
|
|
|
R->>C: matches → rotates the cookie
|
|
|
|
|
|
R-->>B: JWT
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
The three services are `rtl-sso-config-init` (stages `rtl/RTL-Config.sso.json`, same
|
|
|
|
|
|
copy-into-a-volume dance as the standalone RTL), `rtl-sso` (RTL with `RTL_SSO=1`,
|
|
|
|
|
|
`RTL_COOKIE_PATH` and `LOGOUT_REDIRECT_LINK` — the env block is lifted verbatim from
|
|
|
|
|
|
BTCPay's own compose fragment), and `rtl-sso-proxy` (nginx standing in for BTCPay's
|
|
|
|
|
|
traefik). `RTL_IMAGE` overrides both RTL containers at once, so a branch build gets
|
|
|
|
|
|
tested through both entry paths.
|
|
|
|
|
|
|
|
|
|
|
|
It is a second RTL container rather than a flag on the first because RTL picks one
|
|
|
|
|
|
authentication mode at startup — SSO and the password login cannot coexist in one
|
|
|
|
|
|
instance. Both are up at the same time on different ports.
|
|
|
|
|
|
|
|
|
|
|
|
### Things this makes visible
|
|
|
|
|
|
|
|
|
|
|
|
**No prefix stripping anywhere.** RTL is built with `<base href="/rtl/">` and mounts
|
|
|
|
|
|
every route under `baseHref '/rtl'`, so BTCPay's traefik — and the nginx here — pass
|
|
|
|
|
|
`/rtl/…` through unmodified. The proxy deliberately 404s everything outside `/rtl`, so
|
|
|
|
|
|
a request escaping the prefix shows up as a failure instead of being quietly served.
|
|
|
|
|
|
|
|
|
|
|
|
**The entry URL is not a real route.** `/rtl/api/authenticate/cookie` matches nothing in
|
|
|
|
|
|
`server/routes/shared/authenticate.ts`; it falls through to the catch-all in
|
|
|
|
|
|
`server/utils/app.ts`, which is what mints the `XSRF-TOKEN` cookie and serves the SPA.
|
|
|
|
|
|
The access-key is the raw cookie file content — the frontend sha256s it before posting
|
|
|
|
|
|
and the backend compares against `sha256(cookieValue)`.
|
|
|
|
|
|
|
|
|
|
|
|
**`GET /rtl/` mints no CSRF token.** That path is served by `express.static`, which
|
|
|
|
|
|
sits *above* the catch-all, so a client entering there has no `XSRF-TOKEN` and its first
|
|
|
|
|
|
POST gets a 403. Only the catch-all mints one. This is long-standing behaviour, not a
|
|
|
|
|
|
regression — but it is why `verify-sso.sh` always seeds its cookie jar from the entry
|
|
|
|
|
|
URL, and worth remembering before concluding that CSRF is broken.
|
|
|
|
|
|
|
|
|
|
|
|
**The cookie is effectively single-use.** Authenticating rotates it, so a stale
|
|
|
|
|
|
`bin/sso-url` link fails. BTCPay re-reads the file on every page render, which is why
|
|
|
|
|
|
this is invisible in normal use.
|
|
|
|
|
|
|
|
|
|
|
|
### What it does not cover
|
|
|
|
|
|
|
|
|
|
|
|
BTCPay itself is not here — no postgres, nbxplorer or btcpayserver container. So this
|
|
|
|
|
|
does not exercise BTCPay *generating* the link, its Services page, or its own upgrades.
|
|
|
|
|
|
For that, run BTCPay's own regtest stack and point it at a local image:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# in a btcpayserver-docker checkout, after building an RTL image locally
|
|
|
|
|
|
docker build -t shahanafarooqui/rtl:dev /path/to/RTL
|
|
|
|
|
|
# then edit the rtl image tag in the generated docker-compose, or set it in
|
|
|
|
|
|
# docker-compose-generator/docker-fragments/bitcoin-lnd.yml before generating
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
That tests the real composition rather than this reconstruction of it; the harness here
|
|
|
|
|
|
is the fast everyday check.
|
|
|
|
|
|
|
Rebuild the regtest dev fixture: bitcoind + 3 LND nodes + RTL
The setup in docker/ has been unable to start since Feb 2021. The boltz
service added in f817ae39 references BOLTZ_* variables that are not in .env
(so its ports render as ":" and compose rejects the file with "invalid
proto"), a ./boltz build context that has never existed in the repo, and
boltz_data/boltz_shared volumes that are never declared. Because compose
validates the whole project up front, this broke every service: "docker
compose up -d bitcoind", the first command in the README, failed too.
Rather than repair a five-year-old file pinned to bitcoind 0.19.0 and lnd
0.12.0-beta, this replaces it.
What changed:
- bitcoind 30.0 and 3x lnd 0.20.0-beta, using Polar's images. They are
multi-arch, so nothing is built locally and this works on arm64. The old
setup built bitcoind and lnd from local Dockerfiles.
- Three nodes, not one: alice -> bob -> carol. bob forwards, so RTL's
routing and forwarding screens have data. Two nodes leave them empty.
- scripts/seed.sh funds the nodes, opens channels, and makes payments with
fixed amounts. A fresh run reproduces identical state, so screenshots
taken before and after a change differ only by the change. It is
deliberately not idempotent and refuses to run against a seeded network,
since re-running would double-fund it.
- rtl/RTL-Config.regtest.json configures all three nodes. RTL rewrites its
config on startup, so an init container stages a copy into a volume: a
read-only bind mount makes RTL exit with EROFS, and a writable one would
let RTL modify a tracked file. It is not named RTL-Config.json because
.gitignore matches that bare name at any depth.
- bin/ln-cli now takes a node name and passes --lnddir=/home/lnd/.lnd,
because 'docker compose exec' lands as root while lnd's datadir is under
/home/lnd. Both helpers use compose v2.
- README rewritten to match.
Boltz, Core Lightning and Eclair are left out of this pass. Polar publishes
multi-arch clightning and eclair images, so adding them later needs compose
services, config entries and seed adapters, but no image building.
Verified from a clean 'down -v': all nodes sync, channels go active, 5/5
payments route through bob, bob records 5 forwards, and RTL serves the UI
with all three nodes configured. Two independent from-scratch runs produced
identical balances.
The old bitcoind/ and lnd/ build contexts are now unreferenced but left in
place for a follow-up.
2026-07-15 23:09:03 -07:00
|
|
|
|
## Notes and gotchas
|
|
|
|
|
|
|
|
|
|
|
|
**RTL's config.** `rtl/RTL-Config.regtest.json` is the tracked template. RTL rewrites
|
|
|
|
|
|
its config on startup, so an init container copies it into a volume rather than
|
|
|
|
|
|
bind-mounting it — a read-only mount makes RTL exit with `EROFS`, and a writable one
|
|
|
|
|
|
would let RTL modify a version-controlled file. The name is not `RTL-Config.json`
|
|
|
|
|
|
because `.gitignore` matches that bare filename at any depth.
|
|
|
|
|
|
|
|
|
|
|
|
**`lncli` needs `--lnddir=/home/lnd/.lnd`.** `docker compose exec` lands as root,
|
|
|
|
|
|
whose HOME is `/root`, but lnd's datadir is `/home/lnd/.lnd`. `bin/ln-cli` handles this.
|
|
|
|
|
|
|
|
|
|
|
|
**Changing bitcoind credentials.** `docker-compose.yml` carries an `-rpcauth` hash for
|
|
|
|
|
|
the `BITCOIN_RPC_USER` / `BITCOIN_RPC_PASSWORD` in `.env`. Changing them there is not
|
|
|
|
|
|
enough; regenerate the hash:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
python3 - <<'EOF'
|
|
|
|
|
|
import hmac, hashlib
|
|
|
|
|
|
user, password, salt = "rtldev", "rtldev", "8a1f2c3d4e5b6a7c8d9e0f1a2b3c4d5e"
|
|
|
|
|
|
print(f"{user}:{salt}${hmac.new(salt.encode(), password.encode(), hashlib.sha256).hexdigest()}")
|
|
|
|
|
|
EOF
|
2021-12-29 18:08:41 -05:00
|
|
|
|
```
|
|
|
|
|
|
|
Rebuild the regtest dev fixture: bitcoind + 3 LND nodes + RTL
The setup in docker/ has been unable to start since Feb 2021. The boltz
service added in f817ae39 references BOLTZ_* variables that are not in .env
(so its ports render as ":" and compose rejects the file with "invalid
proto"), a ./boltz build context that has never existed in the repo, and
boltz_data/boltz_shared volumes that are never declared. Because compose
validates the whole project up front, this broke every service: "docker
compose up -d bitcoind", the first command in the README, failed too.
Rather than repair a five-year-old file pinned to bitcoind 0.19.0 and lnd
0.12.0-beta, this replaces it.
What changed:
- bitcoind 30.0 and 3x lnd 0.20.0-beta, using Polar's images. They are
multi-arch, so nothing is built locally and this works on arm64. The old
setup built bitcoind and lnd from local Dockerfiles.
- Three nodes, not one: alice -> bob -> carol. bob forwards, so RTL's
routing and forwarding screens have data. Two nodes leave them empty.
- scripts/seed.sh funds the nodes, opens channels, and makes payments with
fixed amounts. A fresh run reproduces identical state, so screenshots
taken before and after a change differ only by the change. It is
deliberately not idempotent and refuses to run against a seeded network,
since re-running would double-fund it.
- rtl/RTL-Config.regtest.json configures all three nodes. RTL rewrites its
config on startup, so an init container stages a copy into a volume: a
read-only bind mount makes RTL exit with EROFS, and a writable one would
let RTL modify a tracked file. It is not named RTL-Config.json because
.gitignore matches that bare name at any depth.
- bin/ln-cli now takes a node name and passes --lnddir=/home/lnd/.lnd,
because 'docker compose exec' lands as root while lnd's datadir is under
/home/lnd. Both helpers use compose v2.
- README rewritten to match.
Boltz, Core Lightning and Eclair are left out of this pass. Polar publishes
multi-arch clightning and eclair images, so adding them later needs compose
services, config entries and seed adapters, but no image building.
Verified from a clean 'down -v': all nodes sync, channels go active, 5/5
payments route through bob, bob records 5 forwards, and RTL serves the UI
with all three nodes configured. Two independent from-scratch runs produced
identical balances.
The old bitcoind/ and lnd/ build contexts are now unreferenced but left in
place for a follow-up.
2026-07-15 23:09:03 -07:00
|
|
|
|
In `docker-compose.yml` the `$` must be written `$$` to escape Compose interpolation.
|
|
|
|
|
|
|
|
|
|
|
|
**Payments right after channel open will fail.** The channel graph has to reach alice
|
|
|
|
|
|
before she can route to carol. The seed waits for this; anything you script yourself
|
|
|
|
|
|
should too.
|
|
|
|
|
|
|
Add a Core Lightning node to the regtest docker fixture
The fixture had only bitcoind + three LND nodes, so RTL's Core Lightning screens
had no backend to exercise. Add a `cln` node (official elementsproject/lightningd
image, multi-arch) wired to RTL over clnrest with rune auth, and have the seed
open a cln->alice channel so the CLN channel/peer screens have real data.
- docker-compose.yml: cln service (clnrest on 0.0.0.0:3010, https), a healthcheck
gated on the rune file so rtl waits for it, and rtl now mounts the cln volume
read-only and depends on cln being healthy. The rtl image is parameterized via
${RTL_IMAGE:-...} so an unreleased build can be tested against the fixture.
- cln/poststart.d/create-rune.sh: once the RPC is up, create a master rune and
write it as LIGHTNING_RUNE="..." where RTL reads it (runePath). Polls for RPC
readiness because the image entrypoint can invoke poststart before the socket
exists.
- RTL-Config.regtest.json: add node index 4 (CLN, rune auth, https://cln:3010).
- seed.sh: fund cln, connect to alice, open a 4,000,000 sat channel, wait active.
- README + release notes updated.
Used to verify the CLN channel connection-status fix (#1606) end-to-end.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 10:03:34 -07:00
|
|
|
|
**Core Lightning auth uses a rune.** RTL talks to `cln` over clnrest and authenticates
|
2026-07-17 11:54:55 -07:00
|
|
|
|
with a rune, not a macaroon. `cln/create-rune.sh` — run from the `cln` healthcheck —
|
|
|
|
|
|
creates a master rune once the RPC is up and writes it as `LIGHTNING_RUNE="…"` to
|
|
|
|
|
|
`rtl.rune` in the shared `cln_data` volume; RTL reads it via the `runePath` in its config.
|
|
|
|
|
|
The healthcheck reports unhealthy until that file exists, so RTL (which waits on
|
|
|
|
|
|
`service_healthy`) starts only once the rune is ready. Because it runs on every
|
|
|
|
|
|
healthcheck tick (idempotent), a transient RPC-startup race just retries and self-heals
|
|
|
|
|
|
rather than wedging the stack. `--clnrest-host=0.0.0.0` is required for RTL (another
|
|
|
|
|
|
container) to reach clnrest; the default `127.0.0.1` would only be reachable from inside
|
|
|
|
|
|
the node.
|
Add a Core Lightning node to the regtest docker fixture
The fixture had only bitcoind + three LND nodes, so RTL's Core Lightning screens
had no backend to exercise. Add a `cln` node (official elementsproject/lightningd
image, multi-arch) wired to RTL over clnrest with rune auth, and have the seed
open a cln->alice channel so the CLN channel/peer screens have real data.
- docker-compose.yml: cln service (clnrest on 0.0.0.0:3010, https), a healthcheck
gated on the rune file so rtl waits for it, and rtl now mounts the cln volume
read-only and depends on cln being healthy. The rtl image is parameterized via
${RTL_IMAGE:-...} so an unreleased build can be tested against the fixture.
- cln/poststart.d/create-rune.sh: once the RPC is up, create a master rune and
write it as LIGHTNING_RUNE="..." where RTL reads it (runePath). Polls for RPC
readiness because the image entrypoint can invoke poststart before the socket
exists.
- RTL-Config.regtest.json: add node index 4 (CLN, rune auth, https://cln:3010).
- seed.sh: fund cln, connect to alice, open a 4,000,000 sat channel, wait active.
- README + release notes updated.
Used to verify the CLN channel connection-status fix (#1606) end-to-end.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 10:03:34 -07:00
|
|
|
|
|
Add an Eclair node to the regtest docker fixture
Completes backend coverage of RTL's three implementations in the docker/
dev fixture: an eclair node (polarlightning/eclair 0.13.1) joins the three
LND nodes and the CLN node, wired to RTL over its HTTP API with basic auth,
and the seed opens an eclair->bob channel (3.5M sats, 1M pushed), sends two
direct payments and leaves one open invoice.
Non-obvious plumbing this needed:
- polarlightning/eclair instead of acinq/eclair: the official image is
amd64-only (useless on Apple Silicon) and its newest versioned tag is
years stale; Polar builds the same ACINQ source multi-arch.
- Eclair has no on-chain wallet of its own -- it drives a bitcoind wallet.
A new eclair-wallet-init container creates a dedicated "eclair" wallet
before the node starts; without it eclair attaches to "the default
loaded wallet", i.e. the rtldev mining wallet.
- bitcoind now also publishes a zmqpubhashblock endpoint (28336): eclair's
bitcoind.zmqblock consumes the hashblock topic, not the rawblock one LND
uses. Wired to rawblock, eclair never sees new blocks and channels hang
in WAIT_FOR_FUNDING_CONFIRMED.
- Eclair confirms channels at 8 blocks (channel.min-depth-blocks), not 6,
and 'open' returns before the funding tx is broadcast -- the seed waits
for the mempool and mines 8 blocks for this channel.
Adds a bin/e-cli helper (eclair-cli with the API password), updates the
README, and verified end-to-end: seed completes, the channel reaches
NORMAL, both payments settle, and RTL's /rtl/api/ecl endpoints return the
node, channel and invoice data.
2026-07-18 00:23:08 -07:00
|
|
|
|
**Eclair has no wallet of its own.** It drives a bitcoind wallet over RPC. The
|
|
|
|
|
|
`eclair-wallet-init` service creates a dedicated `eclair` wallet before the node starts;
|
|
|
|
|
|
without it eclair would attach to "the default loaded wallet" — the `rtldev` mining
|
|
|
|
|
|
wallet — and report the miner's balance as its own. RTL authenticates to eclair with
|
|
|
|
|
|
`lnApiPassword` (HTTP basic auth), no file mount needed. Eclair also confirms channels
|
|
|
|
|
|
at 8 blocks (`channel.min-depth-blocks`), not 6 — the seed mines accordingly. And its
|
|
|
|
|
|
`bitcoind.zmqblock` must point at a `zmqpubhashblock` endpoint — wired to the rawblock
|
|
|
|
|
|
one LND uses, eclair never sees new blocks and channels never confirm.
|
|
|
|
|
|
|
Rebuild the regtest dev fixture: bitcoind + 3 LND nodes + RTL
The setup in docker/ has been unable to start since Feb 2021. The boltz
service added in f817ae39 references BOLTZ_* variables that are not in .env
(so its ports render as ":" and compose rejects the file with "invalid
proto"), a ./boltz build context that has never existed in the repo, and
boltz_data/boltz_shared volumes that are never declared. Because compose
validates the whole project up front, this broke every service: "docker
compose up -d bitcoind", the first command in the README, failed too.
Rather than repair a five-year-old file pinned to bitcoind 0.19.0 and lnd
0.12.0-beta, this replaces it.
What changed:
- bitcoind 30.0 and 3x lnd 0.20.0-beta, using Polar's images. They are
multi-arch, so nothing is built locally and this works on arm64. The old
setup built bitcoind and lnd from local Dockerfiles.
- Three nodes, not one: alice -> bob -> carol. bob forwards, so RTL's
routing and forwarding screens have data. Two nodes leave them empty.
- scripts/seed.sh funds the nodes, opens channels, and makes payments with
fixed amounts. A fresh run reproduces identical state, so screenshots
taken before and after a change differ only by the change. It is
deliberately not idempotent and refuses to run against a seeded network,
since re-running would double-fund it.
- rtl/RTL-Config.regtest.json configures all three nodes. RTL rewrites its
config on startup, so an init container stages a copy into a volume: a
read-only bind mount makes RTL exit with EROFS, and a writable one would
let RTL modify a tracked file. It is not named RTL-Config.json because
.gitignore matches that bare name at any depth.
- bin/ln-cli now takes a node name and passes --lnddir=/home/lnd/.lnd,
because 'docker compose exec' lands as root while lnd's datadir is under
/home/lnd. Both helpers use compose v2.
- README rewritten to match.
Boltz, Core Lightning and Eclair are left out of this pass. Polar publishes
multi-arch clightning and eclair images, so adding them later needs compose
services, config entries and seed adapters, but no image building.
Verified from a clean 'down -v': all nodes sync, channels go active, 5/5
payments route through bob, bob records 5 forwards, and RTL serves the UI
with all three nodes configured. Two independent from-scratch runs produced
identical balances.
The old bitcoind/ and lnd/ build contexts are now unreferenced but left in
place for a follow-up.
2026-07-15 23:09:03 -07:00
|
|
|
|
## Not included
|
2019-04-15 00:00:20 +01:00
|
|
|
|
|
Add an Eclair node to the regtest docker fixture
Completes backend coverage of RTL's three implementations in the docker/
dev fixture: an eclair node (polarlightning/eclair 0.13.1) joins the three
LND nodes and the CLN node, wired to RTL over its HTTP API with basic auth,
and the seed opens an eclair->bob channel (3.5M sats, 1M pushed), sends two
direct payments and leaves one open invoice.
Non-obvious plumbing this needed:
- polarlightning/eclair instead of acinq/eclair: the official image is
amd64-only (useless on Apple Silicon) and its newest versioned tag is
years stale; Polar builds the same ACINQ source multi-arch.
- Eclair has no on-chain wallet of its own -- it drives a bitcoind wallet.
A new eclair-wallet-init container creates a dedicated "eclair" wallet
before the node starts; without it eclair attaches to "the default
loaded wallet", i.e. the rtldev mining wallet.
- bitcoind now also publishes a zmqpubhashblock endpoint (28336): eclair's
bitcoind.zmqblock consumes the hashblock topic, not the rawblock one LND
uses. Wired to rawblock, eclair never sees new blocks and channels hang
in WAIT_FOR_FUNDING_CONFIRMED.
- Eclair confirms channels at 8 blocks (channel.min-depth-blocks), not 6,
and 'open' returns before the funding tx is broadcast -- the seed waits
for the mempool and mines 8 blocks for this channel.
Adds a bin/e-cli helper (eclair-cli with the API password), updates the
README, and verified end-to-end: seed completes, the channel reaches
NORMAL, both payments settle, and RTL's /rtl/api/ecl endpoints return the
node, channel and invoice data.
2026-07-18 00:23:08 -07:00
|
|
|
|
The Boltz swap service.
|
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
|
|
|
|
|
|
|
|
|
|
BTCPay Server itself (postgres + nbxplorer + btcpayserver). The `sso` profile
|
|
|
|
|
|
reproduces the entry path BTCPay uses to reach RTL without running BTCPay — see
|
|
|
|
|
|
[BTCPay SSO harness](#btcpay-sso-harness) for what that covers and what it does not.
|