Add crash-safe persistence for cassette inventory, cashbox state, and transaction history using better-sqlite3 in the Electron main process. The state machine now loads inventory from the database at runtime instead of using hardcoded values, and transactions are automatically persisted on completion. Remove the unnecessary npub linking code — Lightning.Pub auto-creates and associates Nostr users when appId is included in RPC requests, making the HTTP-based user creation and token linking redundant. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1087 lines
38 KiB
Bash
Executable file
1087 lines
38 KiB
Bash
Executable file
#!/bin/bash
|
|
#
|
|
# Lamassu Next Development Environment
|
|
#
|
|
# Single command to manage the complete development stack:
|
|
# - Regtest Bitcoin/Lightning network (from ~/dev/local/docker/regtest)
|
|
# - Lightning.Pub with configurable image/worktree
|
|
# - Auto-configured ATM application
|
|
#
|
|
# Usage:
|
|
# ./dev.sh up # Start (uses shared regtest)
|
|
# ./dev.sh up --standalone # Start self-contained (own bitcoind/lnd)
|
|
# ./dev.sh up --fund # Start and auto-fund ATM with 100k sats
|
|
# ./dev.sh up --fund=50000 # Start and auto-fund with specific amount
|
|
# ./dev.sh up --machine # Start and launch ATM app
|
|
# ./dev.sh up --fund --machine # Start, fund, and launch ATM
|
|
# ./dev.sh up --worktree ~/path/to/lp # Build Lightning.Pub from worktree
|
|
# ./dev.sh up --image myimage:tag # Use specific Docker image
|
|
# ./dev.sh down # Stop everything
|
|
# ./dev.sh atm # Launch ATM application
|
|
# ./dev.sh status # Show connection info
|
|
# ./dev.sh logs [service] # Follow logs
|
|
# ./dev.sh fund [amount] # Fund ATM (default: 100000 sats)
|
|
# ./dev.sh mine [blocks] # Mine blocks manually
|
|
# ./dev.sh reset # Reset all state (fresh start)
|
|
#
|
|
|
|
set -e
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
REGTEST_DIR="${REGTEST_DIR:-$HOME/dev/local/docker/regtest}"
|
|
DEFAULT_IMAGE="lightning-pub-withdraw:latest"
|
|
DEFAULT_FUNDING_SATS=100000
|
|
|
|
# State files
|
|
STATE_DIR="$SCRIPT_DIR/.state"
|
|
PUBKEY_FILE="$STATE_DIR/lightning-pub-pubkey"
|
|
NPROFILE_FILE="$STATE_DIR/lightning-pub-nprofile"
|
|
APP_ID_FILE="$STATE_DIR/atm-app-id"
|
|
MODE_FILE="$STATE_DIR/compose-mode" # "standalone" or "unified"
|
|
|
|
# Get current compose file based on last used mode
|
|
get_compose_file() {
|
|
local mode=$(cat "$MODE_FILE" 2>/dev/null || echo "unified")
|
|
if [[ "$mode" == "standalone" ]]; then
|
|
echo "docker-compose.dev.yml"
|
|
else
|
|
echo "docker-compose.regtest.yml"
|
|
fi
|
|
}
|
|
|
|
# Colors
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
RED='\033[0;31m'
|
|
CYAN='\033[0;36m'
|
|
BOLD='\033[1m'
|
|
DIM='\033[2m'
|
|
NC='\033[0m'
|
|
|
|
log() { echo -e "${GREEN}►${NC} $1"; }
|
|
warn() { echo -e "${YELLOW}⚠${NC} $1"; }
|
|
error() { echo -e "${RED}✗${NC} $1"; }
|
|
success() { echo -e "${GREEN}✓${NC} $1"; }
|
|
|
|
# Ensure state directory exists
|
|
mkdir -p "$STATE_DIR"
|
|
|
|
# Get ATM pubkey from its private key
|
|
get_atm_pubkey() {
|
|
local env_file="$PROJECT_DIR/apps/machine/.env"
|
|
if [[ ! -f "$env_file" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
local privkey=$(grep "VITE_ATM_PRIVATE_KEY=" "$env_file" | cut -d= -f2)
|
|
if [[ -z "$privkey" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
# Use node with @noble/curves to derive pubkey from privkey
|
|
cd "$PROJECT_DIR" && node -e "
|
|
const { secp256k1 } = require('./node_modules/.pnpm/@noble+curves@2.0.1/node_modules/@noble/curves/secp256k1.js');
|
|
const privkeyHex = '$privkey';
|
|
const privkey = Uint8Array.from(Buffer.from(privkeyHex, 'hex'));
|
|
const pubkeyFull = secp256k1.getPublicKey(privkey, true);
|
|
const pubkey = Buffer.from(pubkeyFull.slice(1)).toString('hex');
|
|
console.log(pubkey);
|
|
" 2>/dev/null
|
|
}
|
|
|
|
#############################################################################
|
|
# Helper Functions
|
|
#############################################################################
|
|
|
|
get_local_ip() {
|
|
# Allow explicit override via env var
|
|
if [[ -n "${LAMASSU_HOST_IP:-}" ]]; then
|
|
echo "$LAMASSU_HOST_IP"
|
|
return
|
|
fi
|
|
|
|
# Linux: use ip route
|
|
if command -v ip &>/dev/null; then
|
|
local ip=$(ip route get 1 2>/dev/null | awk '{print $7; exit}')
|
|
if [[ -n "$ip" ]]; then
|
|
echo "$ip"
|
|
return
|
|
fi
|
|
fi
|
|
|
|
# macOS: use route + ifconfig
|
|
if [[ "$(uname)" == "Darwin" ]]; then
|
|
local iface=$(route get default 2>/dev/null | awk '/interface:/ {print $2}')
|
|
if [[ -n "$iface" ]]; then
|
|
local ip=$(ifconfig "$iface" 2>/dev/null | awk '/inet / {print $2}')
|
|
if [[ -n "$ip" ]]; then
|
|
echo "$ip"
|
|
return
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Fallback: hostname -I (Linux) or hostname (macOS)
|
|
hostname -I 2>/dev/null | awk '{print $1}' || hostname 2>/dev/null
|
|
}
|
|
|
|
is_regtest_running() {
|
|
# Check if lnd-4 container is actually running (not just network exists)
|
|
docker ps --filter "name=lnbits-lnd-4-1" --format "{{.Names}}" 2>/dev/null | grep -q lnbits-lnd-4-1
|
|
}
|
|
|
|
is_lnd4_ready() {
|
|
# Use lnd-4 hostname (not localhost) since lnd binds to container IP
|
|
docker exec lnbits-lnd-4-1 lncli --network=regtest --rpcserver=lnd-4:10009 getinfo &>/dev/null 2>&1
|
|
}
|
|
|
|
lnd4_has_channels() {
|
|
local channel_count=$(docker exec lnbits-lnd-4-1 lncli --network=regtest --rpcserver=lnd-4:10009 listchannels 2>/dev/null | grep -c '"chan_id"' || echo "0")
|
|
[[ "$channel_count" -gt 0 ]]
|
|
}
|
|
|
|
lnd4_has_active_channels() {
|
|
docker exec lnbits-lnd-4-1 lncli --network=regtest --rpcserver=lnd-4:10009 listchannels 2>/dev/null | grep -q '"active": true'
|
|
}
|
|
|
|
is_lightning_pub_ready() {
|
|
docker logs lamassu-lightning-pub 2>&1 | grep -q "LightningPub listening"
|
|
}
|
|
|
|
get_lnd4_balance() {
|
|
docker exec lnbits-lnd-4-1 lncli --network=regtest --rpcserver=lnd-4:10009 walletbalance 2>/dev/null | grep -oP '"total_balance":\s*"\K[0-9]+' || echo "0"
|
|
}
|
|
|
|
get_lnd4_channel_balance() {
|
|
docker exec lnbits-lnd-4-1 lncli --network=regtest --rpcserver=lnd-4:10009 channelbalance 2>/dev/null | grep -oP '"local_balance"[^}]*"sat":\s*"\K[0-9]+' || echo "0"
|
|
}
|
|
|
|
#############################################################################
|
|
# Regtest Management
|
|
#############################################################################
|
|
|
|
is_lnd1_ready() {
|
|
# Check if lnd-1 is fully ready (can make RPC calls)
|
|
docker exec lnbits-lnd-1-1 lncli --network=regtest --rpcserver=lnd-1:10009 getinfo &>/dev/null 2>&1
|
|
}
|
|
|
|
is_lnd1_wallet_ready() {
|
|
# Check if lnd-1 wallet is ready (can query balance - needed for channel operations)
|
|
docker exec lnbits-lnd-1-1 lncli --network=regtest --rpcserver=lnd-1:10009 walletbalance &>/dev/null 2>&1
|
|
}
|
|
|
|
wait_for_lnd1_wallet() {
|
|
local attempts=0
|
|
while ! is_lnd1_wallet_ready && [[ $attempts -lt 30 ]]; do
|
|
sleep 2
|
|
attempts=$((attempts + 1))
|
|
done
|
|
is_lnd1_wallet_ready
|
|
}
|
|
|
|
wait_for_lnd1() {
|
|
log "Waiting for lnd-1 to be ready..."
|
|
local attempts=0
|
|
while ! is_lnd1_ready && [[ $attempts -lt 30 ]]; do
|
|
sleep 2
|
|
attempts=$((attempts + 1))
|
|
done
|
|
is_lnd1_ready
|
|
}
|
|
|
|
ensure_lnd4_channel() {
|
|
# Ensure lnd-4 has a channel so it can create invoices
|
|
# Also ensure lnd-3 has a channel so it can pay invoices (for funding)
|
|
# Opens channels from lnd-1 (hub) to both lnd-3 and lnd-4
|
|
|
|
if lnd4_has_active_channels; then
|
|
log "lnd-4 has active channels"
|
|
return 0
|
|
fi
|
|
|
|
log "Setting up Lightning channels for ATM..."
|
|
|
|
# Wait for lnd-1 to be ready (it's the funding hub)
|
|
if ! wait_for_lnd1; then
|
|
error "lnd-1 failed to start"
|
|
return 1
|
|
fi
|
|
success "lnd-1 is ready"
|
|
|
|
# Wait for wallet to be fully initialized (needed for channel operations)
|
|
log "Waiting for lnd-1 wallet..."
|
|
if ! wait_for_lnd1_wallet; then
|
|
error "lnd-1 wallet failed to initialize"
|
|
return 1
|
|
fi
|
|
success "lnd-1 wallet is ready"
|
|
|
|
# From a clean slate, bitcoind may have 0 blocks. Mine some for LND to sync.
|
|
local block_count=$(docker exec lnbits-bitcoind-1 bitcoin-cli -regtest getblockcount 2>/dev/null || echo "0")
|
|
if [[ "$block_count" -lt 101 ]]; then
|
|
log "Mining initial blocks for chain sync..."
|
|
local init_addr=$(docker exec lnbits-lnd-1-1 lncli --network=regtest --rpcserver=lnd-1:10009 newaddress p2wkh 2>/dev/null | grep -oP '"address":\s*"\K[^"]+')
|
|
if [[ -n "$init_addr" ]]; then
|
|
docker exec lnbits-bitcoind-1 bitcoin-cli -regtest generatetoaddress 110 "$init_addr" &>/dev/null
|
|
sleep 3
|
|
success "Initial blocks mined"
|
|
fi
|
|
fi
|
|
|
|
# Wait for LND to sync to chain
|
|
log "Waiting for lnd-1 to sync..."
|
|
local sync_attempts=0
|
|
while [[ $sync_attempts -lt 30 ]]; do
|
|
local synced=$(docker exec lnbits-lnd-1-1 lncli --network=regtest --rpcserver=lnd-1:10009 getinfo 2>/dev/null | grep -oP '"synced_to_chain":\s*\K(true|false)')
|
|
if [[ "$synced" == "true" ]]; then
|
|
break
|
|
fi
|
|
sleep 2
|
|
sync_attempts=$((sync_attempts + 1))
|
|
done
|
|
success "lnd-1 synced to chain"
|
|
|
|
# Check if lnd-1 has funds, if not fund it
|
|
local lnd1_balance=$(docker exec lnbits-lnd-1-1 lncli --network=regtest --rpcserver=lnd-1:10009 walletbalance 2>/dev/null | grep -oP '"confirmed_balance":\s*"\K[0-9]+' | head -1 || echo "0")
|
|
lnd1_balance="${lnd1_balance:-0}"
|
|
if [[ "$lnd1_balance" -lt 100000000 ]]; then
|
|
log "Funding lnd-1 with mining rewards..."
|
|
local lnd1_addr=$(docker exec lnbits-lnd-1-1 lncli --network=regtest --rpcserver=lnd-1:10009 newaddress p2wkh 2>/dev/null | grep -oP '"address":\s*"\K[^"]+')
|
|
docker exec lnbits-bitcoind-1 bitcoin-cli -regtest generatetoaddress 110 "$lnd1_addr" &>/dev/null
|
|
sleep 3
|
|
success "lnd-1 funded"
|
|
fi
|
|
|
|
# Get pubkeys
|
|
local lnd4_pubkey=$(docker exec lnbits-lnd-4-1 lncli --network=regtest --rpcserver=lnd-4:10009 getinfo 2>/dev/null | grep -oP '"identity_pubkey":\s*"\K[^"]+')
|
|
local lnd3_pubkey=$(docker exec lnbits-lnd-3-1 lncli --network=regtest --rpcserver=lnd-3:10009 getinfo 2>/dev/null | grep -oP '"identity_pubkey":\s*"\K[^"]+')
|
|
|
|
if [[ -z "$lnd4_pubkey" ]] || [[ -z "$lnd3_pubkey" ]]; then
|
|
error "Could not get LND pubkeys"
|
|
return 1
|
|
fi
|
|
|
|
# Connect lnd-1 to lnd-3 and lnd-4
|
|
log "Connecting nodes..."
|
|
docker exec lnbits-lnd-1-1 lncli --network=regtest --rpcserver=lnd-1:10009 \
|
|
connect "${lnd3_pubkey}@lnd-3:9735" &>/dev/null || true
|
|
docker exec lnbits-lnd-1-1 lncli --network=regtest --rpcserver=lnd-1:10009 \
|
|
connect "${lnd4_pubkey}@lnd-4:9735" &>/dev/null || true
|
|
|
|
# Retry loop for opening channels (server may still be initializing)
|
|
# From a clean slate, LND needs ~30-60s after getinfo works to fully initialize
|
|
local result3="" result4="" attempts=0
|
|
while [[ $attempts -lt 10 ]]; do
|
|
# Open channel to lnd-3: 10M sats, push 5M so lnd-3 has sending capacity
|
|
if [[ -z "$result3" ]] || echo "$result3" | grep -q "still in the process of starting"; then
|
|
result3=$(docker exec lnbits-lnd-1-1 lncli --network=regtest --rpcserver=lnd-1:10009 \
|
|
openchannel --node_key="$lnd3_pubkey" --local_amt=10000000 --push_amt=5000000 2>&1)
|
|
fi
|
|
|
|
# Open channel to lnd-4: 10M sats, push 5M so lnd-4 has receiving capacity
|
|
if [[ -z "$result4" ]] || echo "$result4" | grep -q "still in the process of starting"; then
|
|
result4=$(docker exec lnbits-lnd-1-1 lncli --network=regtest --rpcserver=lnd-1:10009 \
|
|
openchannel --node_key="$lnd4_pubkey" --local_amt=10000000 --push_amt=5000000 2>&1)
|
|
fi
|
|
|
|
# Check if both succeeded
|
|
if echo "$result3" | grep -q "funding_txid" && echo "$result4" | grep -q "funding_txid"; then
|
|
break
|
|
fi
|
|
|
|
attempts=$((attempts + 1))
|
|
if [[ $attempts -lt 10 ]]; then
|
|
log "Waiting for LND to be fully ready... (attempt $attempts/10)"
|
|
sleep 5
|
|
fi
|
|
done
|
|
|
|
log "Opening channel lnd-1 -> lnd-3..."
|
|
log "Opening channel lnd-1 -> lnd-4..."
|
|
|
|
local channels_opened=false
|
|
if echo "$result3" | grep -q "funding_txid" || echo "$result4" | grep -q "funding_txid"; then
|
|
channels_opened=true
|
|
fi
|
|
|
|
if [[ "$channels_opened" == "true" ]]; then
|
|
log "Mining blocks to confirm channels..."
|
|
|
|
# From clean slate, bitcoind may need wallet. Ensure it has one.
|
|
docker exec lnbits-bitcoind-1 bitcoin-cli -regtest createwallet "regtest" &>/dev/null 2>&1 || true
|
|
|
|
# Mine 10 blocks to confirm channels
|
|
local addr=$(docker exec lnbits-bitcoind-1 bitcoin-cli -regtest getnewaddress 2>/dev/null)
|
|
docker exec lnbits-bitcoind-1 bitcoin-cli -regtest generatetoaddress 10 "$addr" &>/dev/null
|
|
|
|
# Wait for channels to become active (may take up to 60s)
|
|
local attempts=0
|
|
while ! lnd4_has_active_channels && [[ $attempts -lt 30 ]]; do
|
|
sleep 2
|
|
attempts=$((attempts + 1))
|
|
done
|
|
|
|
if lnd4_has_active_channels; then
|
|
# Wait for graph propagation (critical for routing payments)
|
|
log "Waiting for network graph to sync..."
|
|
# Mine more blocks to trigger channel announcements
|
|
docker exec lnbits-bitcoind-1 bitcoin-cli -regtest generatetoaddress 6 "$addr" &>/dev/null
|
|
sleep 5
|
|
|
|
# Verify graph has propagated by checking if lnd-3 can route to lnd-4
|
|
local route_attempts=0
|
|
while [[ $route_attempts -lt 15 ]]; do
|
|
local route=$(docker exec lnbits-lnd-3-1 lncli --network=regtest --rpcserver=lnd-3:10009 \
|
|
queryroutes --dest "$lnd4_pubkey" --amt 10000 2>&1)
|
|
if echo "$route" | grep -q '"total_amt"'; then
|
|
# Route found - wait a bit more for full sync before payments
|
|
sleep 10
|
|
break
|
|
fi
|
|
sleep 5
|
|
route_attempts=$((route_attempts + 1))
|
|
done
|
|
|
|
success "Channels active - lnd-3 and lnd-4 ready"
|
|
return 0
|
|
else
|
|
warn "Channels opened but not yet active, may need more time"
|
|
return 0
|
|
fi
|
|
else
|
|
error "Failed to open channels"
|
|
echo "lnd-3 result: $result3"
|
|
echo "lnd-4 result: $result4"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
start_regtest() {
|
|
if is_regtest_running; then
|
|
log "Regtest network already running"
|
|
return 0
|
|
fi
|
|
|
|
if [[ ! -d "$REGTEST_DIR" ]]; then
|
|
error "Regtest directory not found: $REGTEST_DIR"
|
|
echo " Clone it from: https://github.com/your-org/regtest-env"
|
|
exit 1
|
|
fi
|
|
|
|
log "Starting regtest environment..."
|
|
# Use project name "lnbits" to match the expected network name (lnbits_default)
|
|
(cd "$REGTEST_DIR" && docker compose -p lnbits up -d)
|
|
|
|
# Wait for lnd-4 to be ready
|
|
log "Waiting for lnd-4 to be ready..."
|
|
local attempts=0
|
|
while ! is_lnd4_ready && [[ $attempts -lt 30 ]]; do
|
|
sleep 2
|
|
attempts=$((attempts + 1))
|
|
done
|
|
|
|
if is_lnd4_ready; then
|
|
success "lnd-4 is ready"
|
|
else
|
|
error "lnd-4 failed to start"
|
|
return 1
|
|
fi
|
|
|
|
# Ensure lnd-4 has a channel for invoices
|
|
ensure_lnd4_channel || warn "Could not setup lnd-4 channel (funding may fail)"
|
|
}
|
|
|
|
stop_regtest() {
|
|
if [[ -d "$REGTEST_DIR" ]]; then
|
|
log "Stopping regtest environment..."
|
|
(cd "$REGTEST_DIR" && docker compose down)
|
|
fi
|
|
}
|
|
|
|
#############################################################################
|
|
# Lightning.Pub Management
|
|
#############################################################################
|
|
|
|
build_from_worktree() {
|
|
local worktree="$1"
|
|
local image_name="lightning-pub-dev:latest"
|
|
|
|
if [[ ! -d "$worktree" ]]; then
|
|
error "Worktree not found: $worktree"
|
|
exit 1
|
|
fi
|
|
|
|
log "Building Lightning.Pub from $worktree..."
|
|
docker build -t "$image_name" "$worktree"
|
|
echo "$image_name"
|
|
}
|
|
|
|
wait_for_lightning_pub() {
|
|
log "Waiting for Lightning.Pub..."
|
|
local attempts=0
|
|
while ! is_lightning_pub_ready && [[ $attempts -lt 45 ]]; do
|
|
# Check for errors
|
|
if docker logs lamassu-lightning-pub 2>&1 | grep -q "Error:"; then
|
|
local err=$(docker logs lamassu-lightning-pub 2>&1 | grep "Error:" | tail -1)
|
|
error "Lightning.Pub error: $err"
|
|
return 1
|
|
fi
|
|
sleep 2
|
|
attempts=$((attempts + 1))
|
|
done
|
|
|
|
if is_lightning_pub_ready; then
|
|
sleep 2 # Extra time for Nostr middleware
|
|
success "Lightning.Pub is ready"
|
|
return 0
|
|
else
|
|
error "Lightning.Pub failed to start (timeout)"
|
|
docker logs lamassu-lightning-pub 2>&1 | tail -10
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
extract_lightning_pub_info() {
|
|
local pubkey=$(docker logs lamassu-lightning-pub 2>&1 | grep -oP 'pubkey:\s*\K[a-f0-9]+' | tail -1)
|
|
local nprofile=$(docker logs lamassu-lightning-pub 2>&1 | grep -oP 'nprofile:\s*\K\S+' | tail -1)
|
|
|
|
echo "$pubkey" > "$PUBKEY_FILE"
|
|
echo "$nprofile" > "$NPROFILE_FILE"
|
|
|
|
echo "$pubkey"
|
|
}
|
|
|
|
#############################################################################
|
|
# ATM Configuration
|
|
#############################################################################
|
|
|
|
update_atm_env() {
|
|
local pubkey="$1"
|
|
local env_file="$PROJECT_DIR/apps/machine/.env"
|
|
|
|
if [[ ! -f "$env_file" ]]; then
|
|
warn "ATM .env not found, creating..."
|
|
cat > "$env_file" << EOF
|
|
# Lightning.Pub connection
|
|
VITE_RELAY_URL=ws://localhost:7777
|
|
VITE_LIGHTNING_PUB_PUBKEY=$pubkey
|
|
VITE_LIGHTNING_PUB_API_URL=http://localhost:1776
|
|
VITE_ADMIN_TOKEN=lamassu-dev-admin-token
|
|
VITE_ATM_PRIVATE_KEY=f391a2c3fc734f443b0f685688a0441b5fb9805853c0023f570c5a3c6412b136
|
|
|
|
# Extension API (for LNURL-withdraw)
|
|
VITE_EXTENSION_API_URL=http://localhost:1777
|
|
EOF
|
|
else
|
|
# Update existing pubkey
|
|
if grep -q "VITE_LIGHTNING_PUB_PUBKEY" "$env_file"; then
|
|
sed -i "s/VITE_LIGHTNING_PUB_PUBKEY=.*/VITE_LIGHTNING_PUB_PUBKEY=$pubkey/" "$env_file"
|
|
else
|
|
echo "VITE_LIGHTNING_PUB_PUBKEY=$pubkey" >> "$env_file"
|
|
fi
|
|
fi
|
|
success "Updated ATM .env with pubkey"
|
|
}
|
|
|
|
setup_atm_app() {
|
|
# Use a fixed app name so we reuse the same app across restarts
|
|
local app_name="lamassu-atm-dev"
|
|
|
|
# Check if we already have valid app state
|
|
if [[ -f "$APP_ID_FILE" ]]; then
|
|
local existing_app_id=$(cat "$APP_ID_FILE")
|
|
if [[ -n "$existing_app_id" ]]; then
|
|
log "Using existing ATM app: ${existing_app_id:0:16}..."
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
log "Creating ATM app..."
|
|
|
|
local response=$(curl -s -X POST http://localhost:1776/api/admin/app/add \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: Bearer lamassu-dev-admin-token" \
|
|
-d "{\"name\":\"$app_name\",\"allow_user_creation\":true}" 2>/dev/null)
|
|
|
|
if echo "$response" | grep -q '"status":"OK"'; then
|
|
local app_id=$(echo "$response" | grep -oP '"id":"\K[^"]+')
|
|
|
|
echo "$app_id" > "$APP_ID_FILE"
|
|
|
|
# Update ATM .env with app ID
|
|
local env_file="$PROJECT_DIR/apps/machine/.env"
|
|
if [[ -f "$env_file" ]]; then
|
|
if grep -q "VITE_APP_ID" "$env_file"; then
|
|
sed -i "s/VITE_APP_ID=.*/VITE_APP_ID=$app_id/" "$env_file"
|
|
else
|
|
echo "" >> "$env_file"
|
|
echo "# ATM App ID" >> "$env_file"
|
|
echo "VITE_APP_ID=$app_id" >> "$env_file"
|
|
fi
|
|
fi
|
|
|
|
success "Created ATM app: ${app_id:0:16}..."
|
|
return 0
|
|
else
|
|
warn "Failed to create ATM app (may already exist)"
|
|
if [[ -f "$APP_ID_FILE" ]]; then
|
|
log "Using existing app ID from state"
|
|
return 0
|
|
fi
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
#############################################################################
|
|
# Zeus Connection
|
|
#############################################################################
|
|
|
|
generate_lndconnect() {
|
|
local local_ip=$(get_local_ip)
|
|
local rest_port=8081 # lnd-3 REST API port
|
|
|
|
# Read certs directly from lnd-3 container (host files are root-owned)
|
|
local cert_b64=$(docker exec lnbits-lnd-3-1 base64 -w0 /root/.lnd/tls.cert 2>/dev/null | tr '+/' '-_' | tr -d '=')
|
|
local mac_b64=$(docker exec lnbits-lnd-3-1 base64 -w0 /root/.lnd/data/chain/bitcoin/regtest/admin.macaroon 2>/dev/null | tr '+/' '-_' | tr -d '=')
|
|
|
|
if [[ -n "$cert_b64" ]] && [[ -n "$mac_b64" ]]; then
|
|
echo "lndconnect://${local_ip}:${rest_port}?cert=${cert_b64}&macaroon=${mac_b64}"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
#############################################################################
|
|
# Display Functions
|
|
#############################################################################
|
|
|
|
get_atm_balance() {
|
|
local app_id=$(cat "$APP_ID_FILE" 2>/dev/null)
|
|
if [[ -z "$app_id" ]]; then
|
|
echo "not configured"
|
|
return
|
|
fi
|
|
echo "configured (use './dev.sh fund' to add sats)"
|
|
}
|
|
|
|
show_status() {
|
|
local pubkey=$(cat "$PUBKEY_FILE" 2>/dev/null || docker logs lamassu-lightning-pub 2>&1 | grep -oP 'pubkey:\s*\K[a-f0-9]+' | tail -1)
|
|
local nprofile=$(cat "$NPROFILE_FILE" 2>/dev/null || docker logs lamassu-lightning-pub 2>&1 | grep -oP 'nprofile:\s*\K\S+' | tail -1)
|
|
local local_ip=$(get_local_ip)
|
|
local lndconnect=$(generate_lndconnect 2>/dev/null || echo "")
|
|
local lnd4_balance=$(get_lnd4_balance)
|
|
local app_id=$(cat "$APP_ID_FILE" 2>/dev/null || echo "not set")
|
|
|
|
echo ""
|
|
echo -e "${BOLD}╔═══════════════════════════════════════════════════════════════════╗${NC}"
|
|
echo -e "${BOLD}║ LAMASSU NEXT - DEVELOPMENT ENVIRONMENT ║${NC}"
|
|
echo -e "${BOLD}╚═══════════════════════════════════════════════════════════════════╝${NC}"
|
|
echo ""
|
|
|
|
echo -e "${CYAN}Lightning.Pub${NC}"
|
|
echo -e " Pubkey: ${GREEN}$pubkey${NC}"
|
|
echo -e " nprofile: ${GREEN}$nprofile${NC}"
|
|
echo ""
|
|
|
|
echo -e "${CYAN}Service URLs (local)${NC}"
|
|
echo " Nostr Relay: ws://localhost:7777"
|
|
echo " Lightning.Pub: http://localhost:1776"
|
|
echo " Withdraw API: http://localhost:1777"
|
|
echo ""
|
|
|
|
echo -e "${CYAN}External Access (LAN: $local_ip)${NC}"
|
|
echo " Nostr Relay: ws://${local_ip}:7777"
|
|
echo " Withdraw API: http://${local_ip}:1777"
|
|
echo ""
|
|
|
|
local lnd4_channel_balance=$(get_lnd4_channel_balance)
|
|
echo -e "${CYAN}lnd-4 (Lightning.Pub backend)${NC}"
|
|
echo " Channel Balance: ${lnd4_channel_balance} sats"
|
|
echo " Wallet Balance: ${lnd4_balance} sats"
|
|
echo ""
|
|
|
|
echo -e "${CYAN}ATM App${NC}"
|
|
if [[ "$app_id" != "not set" ]]; then
|
|
echo " App ID: ${app_id:0:16}..."
|
|
echo " Status: $(get_atm_balance)"
|
|
else
|
|
echo " Status: not configured"
|
|
fi
|
|
echo ""
|
|
|
|
if [[ -n "$lndconnect" ]]; then
|
|
echo -e "${CYAN}Zeus Wallet (connect to lnd-3 for testing)${NC}"
|
|
echo -e " ${DIM}${lndconnect:0:60}...${NC}"
|
|
echo -e " ${DIM}To display QR: ./dev.sh zeus${NC}"
|
|
echo ""
|
|
fi
|
|
|
|
echo -e "${CYAN}Quick Commands${NC}"
|
|
echo " ./dev.sh logs lightning-pub # View Lightning.Pub logs"
|
|
echo " ./dev.sh fund 100000 # Fund ATM with 100k sats"
|
|
echo " ./dev.sh status # Show this info"
|
|
echo ""
|
|
echo -e "${BOLD}═══════════════════════════════════════════════════════════════════${NC}"
|
|
}
|
|
|
|
#############################################################################
|
|
# Main Commands
|
|
#############################################################################
|
|
|
|
cmd_up() {
|
|
local image="$DEFAULT_IMAGE"
|
|
local worktree=""
|
|
local skip_regtest=false
|
|
local auto_fund=false
|
|
local fund_amount="$DEFAULT_FUNDING_SATS"
|
|
local launch_machine=false
|
|
local standalone=false
|
|
local compose_file="docker-compose.regtest.yml"
|
|
|
|
# Parse arguments
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--image) image="$2"; shift 2 ;;
|
|
--worktree) worktree="$2"; shift 2 ;;
|
|
--skip-regtest) skip_regtest=true; shift ;;
|
|
--standalone) standalone=true; shift ;;
|
|
--fund) auto_fund=true; shift ;;
|
|
--fund=*) auto_fund=true; fund_amount="${1#*=}"; shift ;;
|
|
--machine|--atm) launch_machine=true; shift ;;
|
|
*) shift ;;
|
|
esac
|
|
done
|
|
|
|
# Determine compose file
|
|
if [[ "$standalone" == "true" ]]; then
|
|
compose_file="docker-compose.dev.yml"
|
|
skip_regtest=true # Standalone mode has its own bitcoind/lnd
|
|
log "Starting in STANDALONE mode (self-contained bitcoind/lnd)"
|
|
else
|
|
log "Starting in UNIFIED mode (shared regtest)"
|
|
fi
|
|
|
|
echo ""
|
|
log "Starting Lamassu development environment..."
|
|
echo ""
|
|
|
|
# 1. Start shared regtest if needed (not in standalone mode)
|
|
if [[ "$standalone" != "true" ]] && [[ "$skip_regtest" != "true" ]]; then
|
|
start_regtest || exit 1
|
|
elif [[ "$standalone" != "true" ]] && ! is_regtest_running; then
|
|
error "Shared regtest not running."
|
|
echo " Start with: regtest-start"
|
|
echo " Or use: ./dev.sh up --standalone"
|
|
exit 1
|
|
elif [[ "$standalone" != "true" ]]; then
|
|
# Regtest already running, but ensure lnd-4 has channels
|
|
ensure_lnd4_channel || warn "Could not setup lnd-4 channel (funding may fail)"
|
|
fi
|
|
|
|
# 2. Build from worktree if specified
|
|
if [[ -n "$worktree" ]]; then
|
|
image=$(build_from_worktree "$worktree")
|
|
fi
|
|
|
|
# 3. Check image exists
|
|
if ! docker image inspect "$image" &>/dev/null; then
|
|
error "Docker image not found: $image"
|
|
echo ""
|
|
echo "Options:"
|
|
echo " 1. Build from worktree: ./dev.sh up --worktree ~/path/to/lightning-pub"
|
|
echo " 2. Build manually: docker build -t $image ~/path/to/lightning-pub"
|
|
exit 1
|
|
fi
|
|
|
|
log "Using Lightning.Pub image: $image"
|
|
|
|
# 4. Start lamassu services
|
|
export REGTEST_DATA_DIR="$REGTEST_DIR/data"
|
|
export LIGHTNING_PUB_IMAGE="$image"
|
|
export HOST_IP=$(get_local_ip)
|
|
|
|
# Save mode for other commands
|
|
if [[ "$standalone" == "true" ]]; then
|
|
echo "standalone" > "$MODE_FILE"
|
|
else
|
|
echo "unified" > "$MODE_FILE"
|
|
fi
|
|
|
|
log "Starting lamassu services..."
|
|
docker compose -f "$SCRIPT_DIR/$compose_file" up -d
|
|
|
|
# 5. Wait for Lightning.Pub
|
|
if ! wait_for_lightning_pub; then
|
|
error "Failed to start. Check: ./dev.sh logs lightning-pub"
|
|
exit 1
|
|
fi
|
|
|
|
# 6. Extract and save Lightning.Pub info
|
|
local pubkey=$(extract_lightning_pub_info)
|
|
|
|
# 7. Update ATM .env
|
|
update_atm_env "$pubkey"
|
|
|
|
# 8. Setup ATM app
|
|
setup_atm_app || true
|
|
|
|
# 9. Auto-fund if requested
|
|
if [[ "$auto_fund" == "true" ]]; then
|
|
echo ""
|
|
log "Auto-funding ATM with $fund_amount sats..."
|
|
sleep 2 # Give Lightning.Pub a moment to settle
|
|
cmd_fund "$fund_amount" || warn "Auto-funding failed. Run './dev.sh fund' manually."
|
|
fi
|
|
|
|
# 10. Show status
|
|
show_status
|
|
|
|
# 11. Launch ATM if requested (in background so status stays visible)
|
|
if [[ "$launch_machine" == "true" ]]; then
|
|
echo ""
|
|
cmd_atm --background
|
|
fi
|
|
}
|
|
|
|
cmd_down() {
|
|
local compose_file=$(get_compose_file)
|
|
log "Stopping lamassu services..."
|
|
docker compose -f "$SCRIPT_DIR/$compose_file" down 2>/dev/null || true
|
|
|
|
if [[ "$1" == "--all" ]]; then
|
|
stop_regtest
|
|
fi
|
|
|
|
success "Services stopped"
|
|
}
|
|
|
|
cmd_reset() {
|
|
warn "This will delete all lamassu state (Lightning.Pub identity, ATM app, etc.)"
|
|
read -p "Continue? [y/N] " -n 1 -r
|
|
echo
|
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
echo "Aborted"
|
|
exit 0
|
|
fi
|
|
|
|
local compose_file=$(get_compose_file)
|
|
log "Stopping services..."
|
|
docker compose -f "$SCRIPT_DIR/$compose_file" down -v 2>/dev/null || true
|
|
|
|
log "Removing state files..."
|
|
rm -rf "$STATE_DIR"
|
|
mkdir -p "$STATE_DIR"
|
|
|
|
success "Reset complete. Run './dev.sh up' for fresh start."
|
|
}
|
|
|
|
cmd_logs() {
|
|
local compose_file=$(get_compose_file)
|
|
docker compose -f "$SCRIPT_DIR/$compose_file" logs -f "$@"
|
|
}
|
|
|
|
cmd_status() {
|
|
if ! docker ps --format '{{.Names}}' | grep -q lamassu-lightning-pub; then
|
|
error "Services not running. Start with: ./dev.sh up"
|
|
exit 1
|
|
fi
|
|
show_status
|
|
}
|
|
|
|
cmd_fund() {
|
|
local amount="${1:-$DEFAULT_FUNDING_SATS}"
|
|
|
|
if ! is_regtest_running; then
|
|
error "Regtest not running"
|
|
exit 1
|
|
fi
|
|
|
|
# Check for Lightning.Pub pubkey
|
|
if [[ ! -f "$PUBKEY_FILE" ]]; then
|
|
error "Lightning.Pub not configured. Run './dev.sh up' first."
|
|
exit 1
|
|
fi
|
|
|
|
local lp_pubkey=$(cat "$PUBKEY_FILE")
|
|
local env_file="$PROJECT_DIR/apps/machine/.env"
|
|
|
|
# Get ATM private key from .env
|
|
if [[ ! -f "$env_file" ]]; then
|
|
error "ATM not configured. Run './dev.sh up' first."
|
|
exit 1
|
|
fi
|
|
|
|
local atm_privkey=$(grep "VITE_ATM_PRIVATE_KEY=" "$env_file" | cut -d= -f2)
|
|
if [[ -z "$atm_privkey" ]]; then
|
|
error "VITE_ATM_PRIVATE_KEY not found in .env"
|
|
exit 1
|
|
fi
|
|
|
|
# Get app ID - required to ensure funds go to the same user as LNURL-withdraw
|
|
local app_id=""
|
|
if [[ -f "$APP_ID_FILE" ]]; then
|
|
app_id=$(cat "$APP_ID_FILE")
|
|
else
|
|
app_id=$(grep "VITE_APP_ID=" "$env_file" | cut -d= -f2)
|
|
fi
|
|
|
|
if [[ -z "$app_id" ]]; then
|
|
error "No app ID found. Run './dev.sh up' first to create the ATM app."
|
|
exit 1
|
|
fi
|
|
|
|
log "Creating invoice via Nostr RPC for $amount sats..."
|
|
log "Using app ID: ${app_id:0:16}..."
|
|
|
|
# Use the Nostr-based funding script (the Lightning.Pub way)
|
|
# This creates an invoice for the ATM's Nostr user under the correct app,
|
|
# ensuring balance is shared with LNURL-withdraw (Extension API)
|
|
local invoice=$(cd "$PROJECT_DIR/packages/nostr-client" && \
|
|
VITE_ATM_PRIVATE_KEY="$atm_privkey" \
|
|
VITE_LIGHTNING_PUB_PUBKEY="$lp_pubkey" \
|
|
VITE_RELAY_URL="ws://localhost:7777" \
|
|
VITE_APP_ID="$app_id" \
|
|
node dev/fund-dev.mjs "$amount" 2>&1)
|
|
|
|
# Extract just the invoice (last line, starts with lnbc)
|
|
local bolt11=$(echo "$invoice" | grep -E "^lnbc")
|
|
|
|
if [[ -z "$bolt11" ]]; then
|
|
error "Failed to create invoice via Nostr"
|
|
echo "Output: $invoice"
|
|
exit 1
|
|
fi
|
|
|
|
log "Got invoice: ${bolt11:0:30}..."
|
|
|
|
log "Paying invoice from lnd-3..."
|
|
|
|
# Source regtest helpers and pay
|
|
if [[ -f "$REGTEST_DIR/docker-scripts.sh" ]]; then
|
|
(
|
|
cd "$REGTEST_DIR"
|
|
source docker-scripts.sh 2>/dev/null
|
|
lncli-sim 3 payinvoice --force "$bolt11"
|
|
)
|
|
if [[ $? -eq 0 ]]; then
|
|
success "Funded ATM with $amount sats"
|
|
else
|
|
error "Payment failed"
|
|
exit 1
|
|
fi
|
|
else
|
|
# Fallback: try direct docker exec
|
|
docker exec lnbits-lnd-3-1 lncli --network=regtest --rpcserver=lnd-3:10009 payinvoice --force "$bolt11"
|
|
if [[ $? -eq 0 ]]; then
|
|
success "Funded ATM with $amount sats"
|
|
else
|
|
error "Payment failed. Make sure lnd-3 has funds and channels."
|
|
exit 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
cmd_mine() {
|
|
local blocks="${1:-1}"
|
|
if ! is_regtest_running; then
|
|
error "Regtest not running"
|
|
exit 1
|
|
fi
|
|
log "Mining $blocks block(s)..."
|
|
docker exec lnbits-bitcoind-1 bitcoin-cli -regtest -generate "$blocks" > /dev/null
|
|
local height=$(docker exec lnbits-bitcoind-1 bitcoin-cli -regtest getblockcount)
|
|
success "Mined $blocks block(s) (height: $height)"
|
|
}
|
|
|
|
cmd_zeus() {
|
|
if ! is_regtest_running; then
|
|
error "Regtest not running. Start with: ./dev.sh up"
|
|
exit 1
|
|
fi
|
|
|
|
local lndconnect=$(generate_lndconnect 2>/dev/null)
|
|
if [[ -z "$lndconnect" ]]; then
|
|
error "Could not generate lndconnect string"
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${CYAN}Zeus Wallet Connection (lnd-3)${NC}"
|
|
echo ""
|
|
|
|
if command -v qrencode &> /dev/null; then
|
|
local qr_file="$STATE_DIR/zeus-lndconnect.png"
|
|
qrencode -o "$qr_file" -s 6 -l L "$lndconnect"
|
|
echo -e "QR code saved to: ${GREEN}$qr_file${NC}"
|
|
echo ""
|
|
|
|
# Try to open the image
|
|
if command -v xdg-open &> /dev/null; then
|
|
xdg-open "$qr_file" 2>/dev/null &
|
|
echo -e "${DIM}Opening QR code image...${NC}"
|
|
elif command -v open &> /dev/null; then
|
|
open "$qr_file" 2>/dev/null &
|
|
echo -e "${DIM}Opening QR code image...${NC}"
|
|
else
|
|
echo -e "${DIM}Open the PNG file to scan with Zeus${NC}"
|
|
fi
|
|
else
|
|
echo -e "${DIM}$lndconnect${NC}"
|
|
echo ""
|
|
echo -e "${YELLOW}Install qrencode for QR display: nix-shell -p qrencode${NC}"
|
|
fi
|
|
echo ""
|
|
}
|
|
|
|
cmd_atm() {
|
|
local atm_dir="$PROJECT_DIR/apps/machine"
|
|
local background=false
|
|
|
|
# Parse arguments
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--background|-b) background=true; shift ;;
|
|
*) shift ;;
|
|
esac
|
|
done
|
|
|
|
if [[ ! -d "$atm_dir" ]]; then
|
|
error "ATM app not found at $atm_dir"
|
|
exit 1
|
|
fi
|
|
|
|
# Check if services are running
|
|
if ! docker ps --format '{{.Names}}' | grep -q lamassu-lightning-pub; then
|
|
warn "Services not running. Start with: ./dev.sh up"
|
|
read -p "Start services first? [Y/n] " -n 1 -r
|
|
echo
|
|
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
|
|
cmd_up
|
|
fi
|
|
fi
|
|
|
|
# Auto-generate lndconnect URI for Zeus QR in mock UI
|
|
local lndconnect=$(generate_lndconnect 2>/dev/null || echo "")
|
|
if [[ -n "$lndconnect" ]]; then
|
|
export VITE_LNDCONNECT_URL="$lndconnect"
|
|
log "Zeus lndconnect injected into ATM UI"
|
|
fi
|
|
|
|
if [[ "$background" == "true" ]]; then
|
|
local log_file="$STATE_DIR/atm.log"
|
|
log "Starting ATM application in background..."
|
|
log "Logs: $log_file"
|
|
(cd "$atm_dir" && nohup pnpm dev > "$log_file" 2>&1 &)
|
|
sleep 2
|
|
if pgrep -f "electron.*machine" > /dev/null; then
|
|
success "ATM app started (PID: $(pgrep -f 'electron.*machine' | head -1))"
|
|
else
|
|
warn "ATM may still be starting. Check logs: tail -f $log_file"
|
|
fi
|
|
else
|
|
log "Starting ATM application..."
|
|
echo ""
|
|
echo -e "${CYAN}ATM Mock Mode:${NC}"
|
|
echo " - Press 'b' to insert a bill (simulates cash insertion)"
|
|
echo " - Use the UI to complete transactions"
|
|
if [[ -n "$lndconnect" ]]; then
|
|
echo -e " - ${GREEN}Zeus QR available on idle screen${NC}"
|
|
fi
|
|
echo ""
|
|
cd "$atm_dir" && pnpm dev
|
|
fi
|
|
}
|
|
|
|
#############################################################################
|
|
# Entry Point
|
|
#############################################################################
|
|
|
|
case "${1:-help}" in
|
|
up|start)
|
|
shift
|
|
cmd_up "$@"
|
|
;;
|
|
down|stop)
|
|
shift
|
|
cmd_down "$@"
|
|
;;
|
|
reset)
|
|
cmd_reset
|
|
;;
|
|
logs)
|
|
shift
|
|
cmd_logs "$@"
|
|
;;
|
|
# Internal command for external orchestration (called by regtest-start --with-lamassu)
|
|
_setup_after_start)
|
|
if ! is_lightning_pub_ready; then
|
|
wait_for_lightning_pub || exit 1
|
|
fi
|
|
local pubkey=$(extract_lightning_pub_info)
|
|
update_atm_env "$pubkey"
|
|
setup_atm_app || true
|
|
echo "ATM setup complete"
|
|
;;
|
|
status|info)
|
|
cmd_status
|
|
;;
|
|
fund)
|
|
shift
|
|
cmd_fund "$@"
|
|
;;
|
|
mine)
|
|
shift
|
|
cmd_mine "$@"
|
|
;;
|
|
atm)
|
|
cmd_atm
|
|
;;
|
|
zeus)
|
|
cmd_zeus
|
|
;;
|
|
*)
|
|
echo "Lamassu Next Development Environment"
|
|
echo ""
|
|
echo "Usage: $0 <command> [options]"
|
|
echo ""
|
|
echo "Commands:"
|
|
echo " up [options] Start development environment"
|
|
echo " down [--all] Stop services (--all includes regtest)"
|
|
echo " atm Launch ATM application (Electron)"
|
|
echo " zeus Show Zeus wallet connection QR code"
|
|
echo " status Show connection info"
|
|
echo " logs [service] Follow service logs"
|
|
echo " fund [sats] Fund ATM account"
|
|
echo " mine [blocks] Mine blocks manually (default: 1)"
|
|
echo " reset Delete all state and start fresh"
|
|
echo ""
|
|
echo "Note: Auto-miner runs in background (1 block/2min). Check with:"
|
|
echo " ./dev.sh logs miner"
|
|
echo ""
|
|
echo "Options for 'up':"
|
|
echo " --standalone Use self-contained mode (own bitcoind/lnd)"
|
|
echo " --worktree <path> Build Lightning.Pub from git worktree"
|
|
echo " --image <name> Use specific Docker image"
|
|
echo " --skip-regtest Don't auto-start shared regtest"
|
|
echo " --fund Auto-fund ATM with 100k sats after startup"
|
|
echo " --fund=<sats> Auto-fund ATM with specific amount"
|
|
echo " --machine Launch ATM app after startup"
|
|
echo ""
|
|
echo "Environment variables:"
|
|
echo " LAMASSU_HOST_IP Override auto-detected LAN IP (for VPN/multi-NIC)"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " $0 up # Start (uses shared regtest)"
|
|
echo " $0 up --standalone # Start self-contained"
|
|
echo " $0 up --fund # Start and fund ATM"
|
|
echo " $0 up --fund --machine # Start, fund, and launch ATM"
|
|
echo " $0 up --fund=50000 # Start and fund with 50k sats"
|
|
echo " $0 up --worktree ~/dev/lightning-pub/withdraw"
|
|
echo " $0 atm # Launch ATM app"
|
|
echo " $0 fund 200000 # Add 200k more sats"
|
|
echo " $0 logs lightning-pub"
|
|
echo " $0 reset && $0 up --fund # Fresh start with funding"
|
|
echo " LAMASSU_HOST_IP=192.168.1.50 $0 up # Override LAN IP"
|
|
;;
|
|
esac
|