Files
timmy-tower/infrastructure/lnd-init.sh
alexpaynex 5dd80ee81a Add ability to sweep funds using xpub or a list of addresses
Implement multiple sweep destination modes (static, address list, xpub) with state management and update configuration scripts.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 8df121fd-c189-4c73-a76b-d9a3e07de783
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9f85e954-647c-46a5-90a7-396e495a805a/418bf6f8-212b-4bb0-a7a5-8231a061da4e/sPDHkg8
Replit-Helium-Checkpoint-Created: true
2026-03-18 18:38:16 +00:00

194 lines
8.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# ============================================================
# LND wallet initialization + LNbits startup
# Run AFTER Bitcoin sync is complete (verificationprogress ~1.0)
# Run as root on the droplet: bash /opt/timmy-node/lnd-init.sh
# ============================================================
set -euo pipefail
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
info() { echo -e "${CYAN}[lnd-init]${NC} $*"; }
ok() { echo -e "${GREEN}[ok]${NC} $*"; }
warn() { echo -e "${YELLOW}[warn]${NC} $*"; }
die() { echo -e "${RED}[error]${NC} $*"; exit 1; }
INFRA_DIR="/opt/timmy-node"
CREDS_FILE="/root/node-credentials.txt"
# ── Check Bitcoin sync ───────────────────────────────────────
info "Checking Bitcoin sync status..."
PROGRESS=$(docker exec bitcoin bitcoin-cli -datadir=/home/bitcoin/.bitcoin getblockchaininfo 2>/dev/null | jq -r .verificationprogress || echo "0")
info "Chain sync progress: $PROGRESS"
if (( $(echo "$PROGRESS < 0.999" | bc -l) )); then
warn "Bitcoin is not fully synced yet (progress: $PROGRESS)"
warn "LND needs a synced chain to function correctly."
read -rp "Continue anyway? (y/N) " CONFIRM
[[ "$CONFIRM" != "y" ]] && die "Aborting. Run again when sync is complete."
fi
# ── Start LND ────────────────────────────────────────────────
info "Starting LND..."
cd "$INFRA_DIR"
docker compose up -d lnd
sleep 5
# ── Wallet init ──────────────────────────────────────────────
echo ""
echo -e "${CYAN}══════════════════════════════════════════════${NC}"
echo -e "${CYAN} LND Wallet Setup${NC}"
echo -e "${CYAN}══════════════════════════════════════════════${NC}"
echo ""
echo -e " Choose one:"
echo -e " ${GREEN}c${NC} — Create a NEW wallet (generates a fresh seed phrase)"
echo -e " ${YELLOW}r${NC} — Restore from existing 24-word seed"
echo ""
read -rp " Your choice [c/r]: " WALLET_CHOICE
if [[ "$WALLET_CHOICE" == "r" ]]; then
info "Restoring wallet from seed..."
docker exec -it lnd lncli --network=mainnet create --recovery-window=2500
else
info "Creating new wallet..."
docker exec -it lnd lncli --network=mainnet create
fi
echo ""
echo -e "${RED}══════════════════════════════════════════════${NC}"
echo -e "${RED} CRITICAL: Write down your 24-word seed phrase${NC}"
echo -e "${RED} It was shown above. Store it offline, safely.${NC}"
echo -e "${RED} This is the ONLY way to recover your funds.${NC}"
echo -e "${RED}══════════════════════════════════════════════${NC}"
echo ""
read -rp " I have written down my seed phrase. Press enter to continue..."
# ── Wait for LND to be ready ─────────────────────────────────
info "Waiting for LND to be ready..."
for i in {1..30}; do
STATE=$(docker exec lnd lncli --network=mainnet state 2>/dev/null | jq -r .state || echo "")
if [[ "$STATE" == "SERVER_ACTIVE" ]]; then
ok "LND is active"
break
fi
echo -n "."
sleep 5
done
echo ""
# ── Get LND pubkey ───────────────────────────────────────────
PUBKEY=$(docker exec lnd lncli --network=mainnet getinfo 2>/dev/null | jq -r .identity_pubkey || echo "")
if [[ -n "$PUBKEY" ]]; then
ok "LND node pubkey: $PUBKEY"
echo "LND_PUBKEY=$PUBKEY" >> "$CREDS_FILE"
fi
# ── Start LNbits ─────────────────────────────────────────────
info "Starting LNbits..."
docker compose up -d lnbits
sleep 8
# ── Wait for LNbits ──────────────────────────────────────────
for i in {1..20}; do
if curl -sf http://127.0.0.1:5000/api/v1/health &>/dev/null; then
ok "LNbits is up"
break
fi
echo -n "."
sleep 3
done
echo ""
# ── Enable Tailscale Funnel for LNbits ───────────────────────
info "Exposing LNbits via Tailscale Funnel (public HTTPS)..."
tailscale serve --bg http://127.0.0.1:5000
tailscale funnel --bg 443
TAILSCALE_HOSTNAME=$(tailscale status --json | jq -r '.Self.DNSName' | sed 's/\.$//')
LNBITS_URL="https://$TAILSCALE_HOSTNAME"
ok "LNbits is available at: $LNBITS_URL"
echo "LNBITS_URL=$LNBITS_URL" >> "$CREDS_FILE"
# ── Create Timmy wallet in LNbits ────────────────────────────
info "Creating Timmy wallet in LNbits..."
sleep 3
# Create a user + wallet via LNbits API
LNBITS_ADMIN_KEY=$(curl -sf http://127.0.0.1:5000/api/v1/wallets \
-H "Content-Type: application/json" \
| jq -r '.[0].adminkey' 2>/dev/null || echo "")
if [[ -z "$LNBITS_ADMIN_KEY" ]]; then
warn "Could not auto-create wallet. Open $LNBITS_URL in browser,"
warn "create a wallet called 'Timmy', and copy the Invoice Key."
else
# Create a dedicated Timmy wallet
TIMMY_WALLET=$(curl -sf http://127.0.0.1:5000/api/v1/account \
-X POST \
-H "Content-Type: application/json" \
-H "X-Api-Key: $LNBITS_ADMIN_KEY" \
-d '{"name":"Timmy"}' 2>/dev/null || echo "{}")
TIMMY_INVOICE_KEY=$(echo "$TIMMY_WALLET" | jq -r '.wallets[0].inkey' 2>/dev/null || echo "")
if [[ -n "$TIMMY_INVOICE_KEY" ]]; then
ok "Timmy wallet created"
echo "LNBITS_API_KEY=$TIMMY_INVOICE_KEY" >> "$CREDS_FILE"
fi
fi
# ── Configure cold storage sweep ────────────────────────────
echo ""
echo -e "${CYAN}══════════════════════════════════════════════${NC}"
echo -e "${CYAN} Cold Storage Auto-Sweep Setup${NC}"
echo -e "${CYAN}══════════════════════════════════════════════${NC}"
echo ""
echo -e " Excess on-chain sats above your threshold are automatically"
echo -e " sent to a cold address daily. You never touch the hot wallet."
echo ""
echo -e " Enter your cold Bitcoin address (hardware wallet, Sparrow, etc.)"
echo -e " Press Enter to skip — you can configure this later by editing"
echo -e " $INFRA_DIR/sweep.conf"
echo ""
read -rp " Cold address (bc1q... / 1... / 3...): " COLD_ADDRESS
SWEEP_CONF="$INFRA_DIR/sweep.conf"
if [[ -n "$COLD_ADDRESS" ]]; then
cat > "$SWEEP_CONF" <<CONF
# Timmy Node — Auto-sweep configuration
# Change any value then run: bash ops.sh configure-sweep
SWEEP_MODE="static"
COLD_ADDRESS="$COLD_ADDRESS"
XPUB=""
KEEP_SATS=300000
MIN_SWEEP=50000
SWEEP_CRON="0 3 * * *"
SWEEP_FREQ_LABEL="daily at 3am UTC"
CONF
chmod 600 "$SWEEP_CONF"
ok "Cold address saved to $SWEEP_CONF"
info "Change thresholds or frequency anytime: bash $INFRA_DIR/ops.sh configure-sweep"
else
warn "No cold address provided — sweep is disabled."
warn "Configure later: echo 'COLD_ADDRESS=bc1q...' >> $INFRA_DIR/sweep.conf"
fi
# ── Final output ─────────────────────────────────────────────
echo ""
echo -e "${GREEN}════════════════════════════════════════════════${NC}"
echo -e "${GREEN} LND + LNbits ready — set these in Replit:${NC}"
echo -e "${GREEN}════════════════════════════════════════════════${NC}"
echo ""
grep "LNBITS_URL\|LNBITS_API_KEY" "$CREDS_FILE" 2>/dev/null || true
echo ""
echo -e " If LNBITS_API_KEY is blank above, open $LNBITS_URL,"
echo -e " create a wallet called 'Timmy', and copy its Invoice Key."
echo ""
echo -e " ${CYAN}Next: fund your Lightning node${NC}"
echo -e " Get your on-chain address:"
echo -e " docker exec lnd lncli --network=mainnet newaddress p2wkh"
echo -e " Send at least 0.001 BTC to open your first channel."
echo ""
echo -e " Full credentials: ${YELLOW}cat $CREDS_FILE${NC}"
echo ""