- Fix EXTENSION_SERVICE_URL default to localhost (was hardcoded LAN IP) - Fix strfry healthcheck (BusyBox nc lacks -z, use /proc/net/tcp) - Add LP healthcheck + depends_on strfry - Add regtest-bootstrap.sh: funds LND, opens channels, creates LP app - Add regtest.sh: single management script (up/down/reset/status/logs/lncli/bitcoin) - Minimal start uses only 3 containers (bitcoind, lnd-1, lnd-4) instead of 17 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
273 lines
9 KiB
Bash
Executable file
273 lines
9 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# regtest-bootstrap.sh — Bootstrap a working LP+LND regtest environment
|
|
#
|
|
# Prerequisites:
|
|
# 1. Regtest running (minimal — only needs bitcoind + lnd-1 + lnd-4):
|
|
# cd ~/dev/local/docker/regtest && docker compose up -d bitcoind lnd-1 lnd-4
|
|
# 2. Lamassu services running:
|
|
# cd docker && docker compose -f docker-compose.regtest.yml up -d
|
|
#
|
|
# What this script does:
|
|
# 1. Waits for bitcoind and LND nodes to be synced
|
|
# 2. Funds LND-1 (1 BTC from bitcoind)
|
|
# 3. Opens a 5M sat channel from LND-1 → LND-4 (LP's backend)
|
|
# 4. Mines blocks to confirm and announce the channel
|
|
# 5. Waits for LP to be healthy
|
|
# 6. Creates an LP app and funds it (10k sats via invoice from LND-1)
|
|
# 7. Prints summary with pubkeys, app token, and channel status
|
|
|
|
set -euo pipefail
|
|
|
|
# -- Colors --
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m' # No Color
|
|
|
|
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
|
|
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
|
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
|
err() { echo -e "${RED}[ERROR]${NC} $*"; }
|
|
|
|
# -- Auto-detect container name prefix --
|
|
# The regtest compose uses project name "lnbits" (via start-regtest) or "regtest" (direct).
|
|
# Detect which prefix the RUNNING bitcoind container has (filter=running avoids stale containers).
|
|
if docker ps -f name=lnbits-bitcoind-1 -f status=running --format '{{.Names}}' | grep -q .; then
|
|
PREFIX="lnbits"
|
|
elif docker ps -f name=regtest-bitcoind-1 -f status=running --format '{{.Names}}' | grep -q .; then
|
|
PREFIX="regtest"
|
|
else
|
|
err "No running bitcoind container found (tried lnbits-bitcoind-1 and regtest-bitcoind-1)"
|
|
err "Start regtest first: cd ~/dev/local/docker/regtest && docker compose up -d bitcoind lnd-1 lnd-4"
|
|
exit 1
|
|
fi
|
|
info "Using container prefix: ${PREFIX}"
|
|
|
|
# -- Container helpers --
|
|
bitcoin_cli() {
|
|
docker exec "${PREFIX}-bitcoind-1" bitcoin-cli -regtest "$@"
|
|
}
|
|
|
|
lncli_1() {
|
|
docker exec "${PREFIX}-lnd-1-1" lncli --network regtest --rpcserver=lnd-1:10009 "$@"
|
|
}
|
|
|
|
lncli_4() {
|
|
docker exec "${PREFIX}-lnd-4-1" lncli --network regtest --rpcserver=lnd-4:10009 "$@"
|
|
}
|
|
|
|
LP_URL="http://localhost:1776"
|
|
LP_EXT_URL="http://localhost:1777"
|
|
ADMIN_TOKEN="lamassu-dev-admin-token"
|
|
CHANNEL_SIZE=5000000 # 5M sats
|
|
FUND_AMOUNT=10000 # 10k sats for LP app
|
|
|
|
# -- Step 1: Wait for bitcoind --
|
|
info "Waiting for bitcoind..."
|
|
for i in $(seq 1 60); do
|
|
if bitcoin_cli getblockchaininfo > /dev/null 2>&1; then
|
|
ok "bitcoind ready (block $(bitcoin_cli getblockcount))"
|
|
break
|
|
fi
|
|
if [ "$i" -eq 60 ]; then
|
|
err "bitcoind not ready after 60s — is regtest running?"
|
|
exit 1
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
# Ensure a wallet exists
|
|
bitcoin_cli createwallet default 2>/dev/null \
|
|
|| bitcoin_cli loadwallet default 2>/dev/null \
|
|
|| true
|
|
|
|
# Mine initial blocks if needed (coinbase needs 100 confirmations to be spendable)
|
|
BLOCKS=$(bitcoin_cli getblockcount)
|
|
if [ "$BLOCKS" -lt 101 ]; then
|
|
NEEDED=$((101 - BLOCKS))
|
|
info "Mining ${NEEDED} initial blocks (coinbase needs 100 confirmations)..."
|
|
bitcoin_cli -generate "$NEEDED" > /dev/null
|
|
ok "Mined to block $(bitcoin_cli getblockcount)"
|
|
fi
|
|
|
|
# -- Step 2: Wait for LND-1 and LND-4 to sync --
|
|
wait_lnd_sync() {
|
|
local name=$1
|
|
local cli_fn=$2
|
|
info "Waiting for ${name} to sync..."
|
|
for i in $(seq 1 120); do
|
|
if [ "$($cli_fn getinfo 2>/dev/null | jq -r '.synced_to_chain' 2>/dev/null)" = "true" ]; then
|
|
ok "${name} synced"
|
|
return 0
|
|
fi
|
|
if [ "$i" -eq 120 ]; then
|
|
err "${name} not synced after 120s"
|
|
exit 1
|
|
fi
|
|
sleep 1
|
|
done
|
|
}
|
|
|
|
wait_lnd_sync "LND-1" lncli_1
|
|
wait_lnd_sync "LND-4" lncli_4
|
|
|
|
# -- Step 3: Fund LND-1 --
|
|
info "Funding LND-1..."
|
|
LND1_ADDR=$(lncli_1 newaddress p2wkh | jq -r '.address')
|
|
bitcoin_cli -named sendtoaddress address="$LND1_ADDR" amount=1 fee_rate=100 > /dev/null
|
|
info "Mining 6 blocks to confirm funding..."
|
|
bitcoin_cli -generate 6 > /dev/null
|
|
|
|
# Wait for LND-1 to see the confirmed balance
|
|
for i in $(seq 1 30); do
|
|
BALANCE=$(lncli_1 walletbalance | jq -r '.confirmed_balance')
|
|
if [ "$BALANCE" != "0" ] && [ -n "$BALANCE" ]; then
|
|
ok "LND-1 funded: ${BALANCE} sats"
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
# -- Step 4: Connect LND-1 → LND-4 and open channel --
|
|
LND4_PUBKEY=$(lncli_4 getinfo | jq -r '.identity_pubkey')
|
|
info "Connecting LND-1 → LND-4 (${LND4_PUBKEY:0:16}...)..."
|
|
lncli_1 connect "${LND4_PUBKEY}@${PREFIX}-lnd-4-1:9735" > /dev/null 2>&1 || true
|
|
|
|
# Check if channel already exists
|
|
EXISTING=$(lncli_1 listchannels --peer "$LND4_PUBKEY" 2>/dev/null | jq '.channels | length')
|
|
if [ "${EXISTING:-0}" -gt 0 ]; then
|
|
warn "Channel to LND-4 already exists, skipping open"
|
|
else
|
|
info "Opening ${CHANNEL_SIZE} sat channel LND-1 → LND-4..."
|
|
lncli_1 openchannel "$LND4_PUBKEY" "$CHANNEL_SIZE" > /dev/null
|
|
|
|
info "Mining 6 blocks to confirm channel..."
|
|
bitcoin_cli -generate 6 > /dev/null
|
|
|
|
# Wait for channel to leave pending
|
|
for i in $(seq 1 60); do
|
|
PENDING=$(lncli_1 pendingchannels | jq '.pending_open_channels | length')
|
|
if [ "$PENDING" = "0" ]; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
fi
|
|
|
|
# -- Step 5: Verify channel is active and in graph --
|
|
info "Waiting for channel to become active..."
|
|
for i in $(seq 1 60); do
|
|
ACTIVE=$(lncli_1 listchannels --peer "$LND4_PUBKEY" 2>/dev/null | jq '[.channels[] | select(.active == true)] | length')
|
|
if [ "${ACTIVE:-0}" -gt 0 ]; then
|
|
ok "Channel active"
|
|
break
|
|
fi
|
|
if [ "$i" -eq 60 ]; then
|
|
warn "Channel not active after 60s — may need more blocks"
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
# Mine a few more blocks to ensure graph announcement propagates
|
|
bitcoin_cli -generate 3 > /dev/null
|
|
|
|
info "Checking graph..."
|
|
for i in $(seq 1 30); do
|
|
GRAPH_NODES=$(lncli_1 describegraph 2>/dev/null | jq '.nodes | length')
|
|
GRAPH_EDGES=$(lncli_1 describegraph 2>/dev/null | jq '.edges | length')
|
|
if [ "${GRAPH_EDGES:-0}" -gt 0 ]; then
|
|
ok "Graph: ${GRAPH_NODES} nodes, ${GRAPH_EDGES} edges"
|
|
break
|
|
fi
|
|
if [ "$i" -eq 30 ]; then
|
|
warn "Graph has no edges yet — channel may not be announced"
|
|
fi
|
|
sleep 2
|
|
done
|
|
|
|
# -- Step 6: Wait for Lightning.Pub to be healthy --
|
|
info "Waiting for Lightning.Pub..."
|
|
for i in $(seq 1 120); do
|
|
if curl -so /dev/null "${LP_URL}" 2>/dev/null; then
|
|
ok "Lightning.Pub ready"
|
|
break
|
|
fi
|
|
if [ "$i" -eq 120 ]; then
|
|
err "Lightning.Pub not ready after 120s"
|
|
err "Check: docker logs lamassu-lightning-pub"
|
|
exit 1
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
# Small delay to ensure LP has fully initialized its LND connection
|
|
sleep 5
|
|
|
|
# -- Step 7: Create LP app --
|
|
info "Creating LP app 'regtest-atm'..."
|
|
APP_RESPONSE=$(curl -sf "${LP_URL}/api/admin/app/add" \
|
|
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"name": "regtest-atm", "allow_user_creation": true}')
|
|
|
|
if [ -z "$APP_RESPONSE" ]; then
|
|
err "Failed to create LP app — check LP logs"
|
|
exit 1
|
|
fi
|
|
|
|
APP_ID=$(echo "$APP_RESPONSE" | jq -r '.app.id')
|
|
APP_NPUB=$(echo "$APP_RESPONSE" | jq -r '.app.npub')
|
|
APP_TOKEN=$(echo "$APP_RESPONSE" | jq -r '.auth_token')
|
|
|
|
if [ "$APP_ID" = "null" ] || [ -z "$APP_ID" ]; then
|
|
err "App creation returned unexpected response:"
|
|
echo "$APP_RESPONSE" | jq .
|
|
exit 1
|
|
fi
|
|
|
|
ok "App created: id=${APP_ID}"
|
|
|
|
# -- Step 8: Fund the app (create invoice via LP, pay from LND-1) --
|
|
info "Funding app with ${FUND_AMOUNT} sats..."
|
|
INVOICE_RESPONSE=$(curl -sf "${LP_URL}/api/app/add/invoice" \
|
|
-H "Authorization: Bearer ${APP_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"payer_identifier\": \"bootstrap\", \"http_callback_url\": \"\", \"invoice_req\": {\"amountSats\": ${FUND_AMOUNT}, \"memo\": \"regtest bootstrap\"}}")
|
|
|
|
INVOICE=$(echo "$INVOICE_RESPONSE" | jq -r '.invoice')
|
|
if [ "$INVOICE" = "null" ] || [ -z "$INVOICE" ]; then
|
|
warn "Failed to create invoice — app has 0 balance"
|
|
warn "Response: $INVOICE_RESPONSE"
|
|
else
|
|
# Pay from LND-1
|
|
PAY_RESULT=$(lncli_1 payinvoice --force "$INVOICE" 2>&1) || true
|
|
PAY_STATUS=$(echo "$PAY_RESULT" | jq -r '.status' 2>/dev/null || echo "")
|
|
if [ "$PAY_STATUS" = "SUCCEEDED" ] || echo "$PAY_RESULT" | grep -q "SUCCEEDED"; then
|
|
ok "App funded with ${FUND_AMOUNT} sats"
|
|
else
|
|
warn "Payment may have failed — check manually"
|
|
warn "Result: $(echo "$PAY_RESULT" | tail -3)"
|
|
fi
|
|
fi
|
|
|
|
# -- Summary --
|
|
# LP uses LND-4 as its backend, so LP pubkey = LND-4 pubkey
|
|
echo ""
|
|
echo -e "${GREEN}========================================${NC}"
|
|
echo -e "${GREEN} Regtest Bootstrap Complete${NC}"
|
|
echo -e "${GREEN}========================================${NC}"
|
|
echo ""
|
|
echo -e " ${CYAN}LND-1 pubkey:${NC} $(lncli_1 getinfo | jq -r '.identity_pubkey')"
|
|
echo -e " ${CYAN}LP (LND-4):${NC} ${LND4_PUBKEY}"
|
|
echo ""
|
|
echo -e " ${CYAN}LP app ID:${NC} ${APP_ID}"
|
|
echo -e " ${CYAN}LP app token:${NC} ${APP_TOKEN}"
|
|
echo ""
|
|
echo -e " ${CYAN}LP URL:${NC} ${LP_URL}"
|
|
echo -e " ${CYAN}Extension URL:${NC} ${LP_EXT_URL}"
|
|
echo -e " ${CYAN}Relay URL:${NC} ws://localhost:7777"
|
|
echo ""
|
|
CHAN_INFO=$(lncli_1 listchannels --peer "$LND4_PUBKEY" 2>/dev/null | jq -r '.channels[0] | "\(.local_balance) / \(.capacity) sats (active: \(.active))"' 2>/dev/null || echo "unknown")
|
|
echo -e " ${CYAN}Channel:${NC} LND-1 → LND-4: ${CHAN_INFO}"
|
|
echo ""
|