Set up Bitcoin node and Lightning infrastructure with Docker
Create Docker Compose configuration, Bitcoin and LND configuration files, and bootstrap/init scripts for setting up a Bitcoin full node, LND, and LNbits on a Digital Ocean droplet. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 0b0f1422-94e9-40dc-9b10-29c5f33a1bac 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
This commit is contained in:
30
infrastructure/configs/bitcoin.conf
Normal file
30
infrastructure/configs/bitcoin.conf
Normal file
@@ -0,0 +1,30 @@
|
||||
# Bitcoin Core — mainnet
|
||||
# Placed at /data/bitcoin/bitcoin.conf on the droplet
|
||||
|
||||
# Network
|
||||
server=1
|
||||
listen=1
|
||||
bind=0.0.0.0
|
||||
|
||||
# RPC (internal Docker network only — never exposed publicly)
|
||||
rpcbind=0.0.0.0
|
||||
rpcallowip=172.0.0.0/8
|
||||
rpcuser=satoshi
|
||||
# rpcpassword is set by setup.sh — do not hardcode here
|
||||
|
||||
# ZMQ — LND uses these to get notified of new blocks/txs
|
||||
zmqpubrawblock=tcp://0.0.0.0:28332
|
||||
zmqpubrawrx=tcp://0.0.0.0:28333
|
||||
|
||||
# Performance
|
||||
dbcache=512
|
||||
maxmempool=300
|
||||
maxconnections=40
|
||||
|
||||
# Prune options — choose one:
|
||||
# Full node (recommended, ~600GB): comment out both prune lines
|
||||
# Pruned (~10GB, lower cost but loses historical tx data):
|
||||
# prune=10240
|
||||
|
||||
# Logging
|
||||
debug=0
|
||||
32
infrastructure/configs/lnd.conf
Normal file
32
infrastructure/configs/lnd.conf
Normal file
@@ -0,0 +1,32 @@
|
||||
[Application Options]
|
||||
# Human-readable node alias visible on the Lightning Network
|
||||
alias=timmy-node
|
||||
color=#F7931A
|
||||
|
||||
# REST API (LNbits connects here — Docker internal only)
|
||||
restlisten=0.0.0.0:8080
|
||||
rpclisten=0.0.0.0:10009
|
||||
|
||||
# Allow connections from Docker network
|
||||
tlsextraip=lnd
|
||||
tlsextradomain=lnd
|
||||
|
||||
# Max pending channels
|
||||
maxpendingchannels=5
|
||||
|
||||
[Bitcoin]
|
||||
bitcoin.active=1
|
||||
bitcoin.mainnet=1
|
||||
bitcoin.node=bitcoind
|
||||
|
||||
[Bitcoind]
|
||||
bitcoind.rpchost=bitcoin:8332
|
||||
bitcoind.rpcuser=satoshi
|
||||
# bitcoind.rpcpass is written by setup.sh at runtime
|
||||
bitcoind.zmqpubrawblock=tcp://bitcoin:28332
|
||||
bitcoind.zmqpubrawrx=tcp://bitcoin:28333
|
||||
|
||||
[tor]
|
||||
# Optional: enable Tor for node privacy
|
||||
# tor.active=1
|
||||
# tor.v3=1
|
||||
87
infrastructure/docker-compose.yml
Normal file
87
infrastructure/docker-compose.yml
Normal file
@@ -0,0 +1,87 @@
|
||||
services:
|
||||
|
||||
bitcoin:
|
||||
image: lncm/bitcoind:v27.0
|
||||
container_name: bitcoin
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- bitcoin_data:/data/.bitcoin
|
||||
ports:
|
||||
- "8333:8333"
|
||||
networks:
|
||||
- node-net
|
||||
healthcheck:
|
||||
test: ["CMD", "bitcoin-cli", "-conf=/data/.bitcoin/bitcoin.conf", "getblockchaininfo"]
|
||||
interval: 60s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
|
||||
lnd:
|
||||
image: lightningnetwork/lnd:v0.18.3-beta
|
||||
container_name: lnd
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
bitcoin:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- lnd_data:/root/.lnd
|
||||
ports:
|
||||
- "9735:9735"
|
||||
networks:
|
||||
- node-net
|
||||
healthcheck:
|
||||
test: ["CMD", "lncli", "--network=mainnet", "state"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 10
|
||||
start_period: 60s
|
||||
|
||||
lnbits:
|
||||
image: lnbits/lnbits:latest
|
||||
container_name: lnbits
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
lnd:
|
||||
condition: service_started
|
||||
ports:
|
||||
- "127.0.0.1:5000:5000"
|
||||
volumes:
|
||||
- lnbits_data:/app/data
|
||||
- lnd_data:/lnd:ro
|
||||
environment:
|
||||
- LNBITS_DATA_FOLDER=/app/data
|
||||
- LNBITS_BACKEND_WALLET_CLASS=LndRestWallet
|
||||
- LND_REST_ENDPOINT=https://lnd:8080
|
||||
- LND_REST_CERT=/lnd/tls.cert
|
||||
- LND_REST_MACAROON=/lnd/data/chain/bitcoin/mainnet/invoice.macaroon
|
||||
- LNBITS_SITE_TITLE=Timmy Node
|
||||
- LNBITS_SITE_TAGLINE=Lightning AI Agent Infrastructure
|
||||
- UVICORN_HOST=0.0.0.0
|
||||
- UVICORN_PORT=5000
|
||||
networks:
|
||||
- node-net
|
||||
|
||||
networks:
|
||||
node-net:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
bitcoin_data:
|
||||
driver: local
|
||||
driver_opts:
|
||||
type: none
|
||||
o: bind
|
||||
device: /data/bitcoin
|
||||
lnd_data:
|
||||
driver: local
|
||||
driver_opts:
|
||||
type: none
|
||||
o: bind
|
||||
device: /data/lnd
|
||||
lnbits_data:
|
||||
driver: local
|
||||
driver_opts:
|
||||
type: none
|
||||
o: bind
|
||||
device: /data/lnbits
|
||||
156
infrastructure/lnd-init.sh
Executable file
156
infrastructure/lnd-init.sh
Executable file
@@ -0,0 +1,156 @@
|
||||
#!/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 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
|
||||
|
||||
# ── 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 ""
|
||||
128
infrastructure/ops.sh
Executable file
128
infrastructure/ops.sh
Executable file
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# Timmy Node — Day-to-day operations helper
|
||||
# Usage: bash ops.sh <command>
|
||||
# ============================================================
|
||||
|
||||
INFRA_DIR="/opt/timmy-node"
|
||||
cd "$INFRA_DIR" 2>/dev/null || { echo "Run on the droplet, not locally"; exit 1; }
|
||||
|
||||
CYAN='\033[0;36m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
|
||||
|
||||
case "${1:-help}" in
|
||||
|
||||
status)
|
||||
echo -e "\n${CYAN}── Docker services ──────────────────────────${NC}"
|
||||
docker compose ps
|
||||
|
||||
echo -e "\n${CYAN}── Bitcoin sync ──────────────────────────────${NC}"
|
||||
docker exec bitcoin bitcoin-cli getblockchaininfo 2>/dev/null \
|
||||
| jq '{blocks, headers, verificationprogress, size_on_disk}' \
|
||||
|| echo "Bitcoin not ready"
|
||||
|
||||
echo -e "\n${CYAN}── LND state ─────────────────────────────────${NC}"
|
||||
docker exec lnd lncli --network=mainnet getinfo 2>/dev/null \
|
||||
| jq '{identity_pubkey, alias, num_peers, num_active_channels, synced_to_chain}' \
|
||||
|| echo "LND not ready"
|
||||
|
||||
echo -e "\n${CYAN}── LND wallet balance ────────────────────────${NC}"
|
||||
docker exec lnd lncli --network=mainnet walletbalance 2>/dev/null \
|
||||
| jq '{confirmed_balance, unconfirmed_balance}' \
|
||||
|| true
|
||||
|
||||
echo -e "\n${CYAN}── LND channel balance ───────────────────────${NC}"
|
||||
docker exec lnd lncli --network=mainnet channelbalance 2>/dev/null \
|
||||
| jq '{balance, pending_open_balance}' \
|
||||
|| true
|
||||
;;
|
||||
|
||||
sync)
|
||||
watch -n 10 'docker exec bitcoin bitcoin-cli getblockchaininfo \
|
||||
| jq "{blocks, verificationprogress, size_on_disk}"'
|
||||
;;
|
||||
|
||||
logs)
|
||||
SERVICE="${2:-}"
|
||||
if [[ -z "$SERVICE" ]]; then
|
||||
docker compose logs -f --tail=50
|
||||
else
|
||||
docker compose logs -f --tail=100 "$SERVICE"
|
||||
fi
|
||||
;;
|
||||
|
||||
restart)
|
||||
SERVICE="${2:-}"
|
||||
if [[ -z "$SERVICE" ]]; then
|
||||
docker compose restart
|
||||
else
|
||||
docker compose restart "$SERVICE"
|
||||
fi
|
||||
;;
|
||||
|
||||
fund)
|
||||
echo -e "${CYAN}Your on-chain deposit address (send BTC here to fund channels):${NC}"
|
||||
docker exec lnd lncli --network=mainnet newaddress p2wkh | jq -r .address
|
||||
;;
|
||||
|
||||
channels)
|
||||
echo -e "\n${CYAN}── Active channels ───────────────────────────${NC}"
|
||||
docker exec lnd lncli --network=mainnet listchannels 2>/dev/null \
|
||||
| jq '.channels[] | {remote_pubkey, capacity, local_balance, remote_balance, active}'
|
||||
echo -e "\n${CYAN}── Pending channels ──────────────────────────${NC}"
|
||||
docker exec lnd lncli --network=mainnet pendingchannels 2>/dev/null \
|
||||
| jq '{pending_open_channels: .pending_open_channels | length, pending_closing_channels: .pending_closing_channels | length}'
|
||||
;;
|
||||
|
||||
open-channel)
|
||||
echo -e "${CYAN}Open a channel — usage:${NC}"
|
||||
echo " bash ops.sh open-channel <peer_pubkey>@<host>:<port> <amount_sats>"
|
||||
if [[ -n "${2:-}" && -n "${3:-}" ]]; then
|
||||
docker exec lnd lncli --network=mainnet connect "$2" 2>/dev/null || true
|
||||
PUBKEY=$(echo "$2" | cut -d@ -f1)
|
||||
docker exec lnd lncli --network=mainnet openchannel \
|
||||
--node_key "$PUBKEY" \
|
||||
--local_amt "$3" \
|
||||
--push_amt 0
|
||||
fi
|
||||
;;
|
||||
|
||||
lnbits-key)
|
||||
echo -e "${CYAN}LNbits API keys for Timmy wallet:${NC}"
|
||||
grep "LNBITS" /root/node-credentials.txt 2>/dev/null || \
|
||||
echo "Check /root/node-credentials.txt — or open LNbits dashboard"
|
||||
TSHOST=$(tailscale status --json 2>/dev/null | jq -r '.Self.DNSName' | sed 's/\.$//')
|
||||
echo -e "\nLNbits dashboard: ${GREEN}https://$TSHOST${NC}"
|
||||
;;
|
||||
|
||||
update)
|
||||
echo -e "${YELLOW}Pulling latest Docker images...${NC}"
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
ok "Done"
|
||||
;;
|
||||
|
||||
backup)
|
||||
BACKUP_FILE="/root/lnd-backup-$(date +%Y%m%d-%H%M%S).tar.gz"
|
||||
echo -e "${CYAN}Backing up LND channel state to $BACKUP_FILE${NC}"
|
||||
tar -czf "$BACKUP_FILE" -C /data/lnd .
|
||||
echo -e "${GREEN}Backup saved. Copy this file off the server:${NC}"
|
||||
echo " scp root@<node>:$BACKUP_FILE ./lnd-backup.tar.gz"
|
||||
;;
|
||||
|
||||
help|*)
|
||||
echo -e "\n${CYAN}Timmy Node operations:${NC}"
|
||||
echo ""
|
||||
echo " bash ops.sh status — overview of all services + balances"
|
||||
echo " bash ops.sh sync — watch Bitcoin chain sync progress"
|
||||
echo " bash ops.sh logs [service] — tail logs (bitcoin | lnd | lnbits)"
|
||||
echo " bash ops.sh restart [svc] — restart a service or all services"
|
||||
echo " bash ops.sh fund — get on-chain deposit address"
|
||||
echo " bash ops.sh channels — list open and pending channels"
|
||||
echo " bash ops.sh open-channel — open a Lightning channel"
|
||||
echo " bash ops.sh lnbits-key — show LNBITS_URL and API key for Replit"
|
||||
echo " bash ops.sh update — pull latest Docker images"
|
||||
echo " bash ops.sh backup — backup LND channel state"
|
||||
echo ""
|
||||
;;
|
||||
|
||||
esac
|
||||
173
infrastructure/setup.sh
Executable file
173
infrastructure/setup.sh
Executable file
@@ -0,0 +1,173 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# Timmy Bitcoin Node — One-shot bootstrap for Ubuntu 22.04 LTS
|
||||
# Run as root on a fresh Digital Ocean droplet:
|
||||
# bash setup.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}[setup]${NC} $*"; }
|
||||
ok() { echo -e "${GREEN}[ok]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[warn]${NC} $*"; }
|
||||
die() { echo -e "${RED}[error]${NC} $*"; exit 1; }
|
||||
|
||||
[[ $EUID -ne 0 ]] && die "Run as root: sudo bash setup.sh"
|
||||
|
||||
# ── 0. Config ────────────────────────────────────────────────
|
||||
DATA_DIR="/data"
|
||||
INFRA_DIR="/opt/timmy-node"
|
||||
RPC_PASS=$(openssl rand -hex 24)
|
||||
|
||||
info "Starting Timmy node setup..."
|
||||
echo ""
|
||||
|
||||
# ── 1. System packages ───────────────────────────────────────
|
||||
info "Updating system packages..."
|
||||
apt-get update -qq
|
||||
apt-get upgrade -y -qq
|
||||
apt-get install -y -qq curl wget git ufw jq openssl
|
||||
|
||||
# ── 2. Docker ────────────────────────────────────────────────
|
||||
info "Installing Docker..."
|
||||
if ! command -v docker &>/dev/null; then
|
||||
curl -fsSL https://get.docker.com | sh
|
||||
systemctl enable docker
|
||||
systemctl start docker
|
||||
fi
|
||||
docker --version
|
||||
ok "Docker ready"
|
||||
|
||||
# ── 3. Tailscale ─────────────────────────────────────────────
|
||||
info "Installing Tailscale..."
|
||||
if ! command -v tailscale &>/dev/null; then
|
||||
curl -fsSL https://tailscale.com/install.sh | sh
|
||||
fi
|
||||
ok "Tailscale installed — you will authenticate it after this script finishes"
|
||||
|
||||
# ── 4. Firewall ──────────────────────────────────────────────
|
||||
info "Configuring UFW firewall..."
|
||||
ufw --force reset
|
||||
# Allow Tailscale
|
||||
ufw allow in on tailscale0
|
||||
# Bitcoin P2P
|
||||
ufw allow 8333/tcp comment "Bitcoin P2P"
|
||||
# Lightning P2P
|
||||
ufw allow 9735/tcp comment "Lightning P2P"
|
||||
# SSH via Tailscale only — but keep public SSH open until Tailscale is confirmed
|
||||
# (after Tailscale auth, you can run: ufw delete allow 22)
|
||||
ufw allow 22/tcp comment "SSH (disable after Tailscale is confirmed)"
|
||||
ufw default deny incoming
|
||||
ufw default allow outgoing
|
||||
ufw --force enable
|
||||
ok "Firewall configured"
|
||||
|
||||
# ── 5. Mount the DO block volume ─────────────────────────────
|
||||
info "Setting up data volume..."
|
||||
# Digital Ocean attaches block volumes at /dev/sda or /dev/disk/by-id/scsi-...
|
||||
# Find the attached volume (should be the largest unmounted disk)
|
||||
VOLUME_DEV=$(lsblk -rno NAME,SIZE,MOUNTPOINT | awk '$3=="" && $2~/G/ {print $1}' | grep -v "^sda$" | head -1)
|
||||
|
||||
if [[ -z "$VOLUME_DEV" ]]; then
|
||||
warn "Could not auto-detect a block volume. If you attached a DO Volume,"
|
||||
warn "run: lsblk to find it, then: mkfs.ext4 /dev/<name> && mount /dev/<name> /data"
|
||||
warn "Then re-run this script or continue manually."
|
||||
mkdir -p "$DATA_DIR"
|
||||
else
|
||||
VOLUME_PATH="/dev/$VOLUME_DEV"
|
||||
info "Found volume at $VOLUME_PATH"
|
||||
# Format only if not already formatted
|
||||
if ! blkid "$VOLUME_PATH" &>/dev/null; then
|
||||
info "Formatting $VOLUME_PATH with ext4..."
|
||||
mkfs.ext4 -F "$VOLUME_PATH"
|
||||
fi
|
||||
mkdir -p "$DATA_DIR"
|
||||
mount "$VOLUME_PATH" "$DATA_DIR"
|
||||
# Persist mount across reboots
|
||||
BLKID=$(blkid -s UUID -o value "$VOLUME_PATH")
|
||||
if ! grep -q "$BLKID" /etc/fstab; then
|
||||
echo "UUID=$BLKID $DATA_DIR ext4 defaults,nofail 0 2" >> /etc/fstab
|
||||
fi
|
||||
ok "Volume mounted at $DATA_DIR"
|
||||
fi
|
||||
|
||||
# ── 6. Directory structure ───────────────────────────────────
|
||||
info "Creating directory structure..."
|
||||
mkdir -p \
|
||||
"$DATA_DIR/bitcoin" \
|
||||
"$DATA_DIR/lnd" \
|
||||
"$DATA_DIR/lnbits" \
|
||||
"$INFRA_DIR/configs"
|
||||
ok "Directories ready"
|
||||
|
||||
# ── 7. Copy configs & inject RPC password ────────────────────
|
||||
info "Installing configs..."
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Bitcoin config
|
||||
cp "$SCRIPT_DIR/configs/bitcoin.conf" "$DATA_DIR/bitcoin/bitcoin.conf"
|
||||
echo "rpcpassword=$RPC_PASS" >> "$DATA_DIR/bitcoin/bitcoin.conf"
|
||||
|
||||
# LND config — inject rpcpass
|
||||
cp "$SCRIPT_DIR/configs/lnd.conf" "$INFRA_DIR/configs/lnd.conf"
|
||||
sed -i "s/# bitcoind.rpcpass is written by setup.sh at runtime/bitcoind.rpcpass=$RPC_PASS/" \
|
||||
"$INFRA_DIR/configs/lnd.conf"
|
||||
|
||||
# Mount lnd.conf into container via docker-compose override
|
||||
cat > "$INFRA_DIR/docker-compose.override.yml" <<OVERRIDE
|
||||
services:
|
||||
lnd:
|
||||
volumes:
|
||||
- lnd_data:/root/.lnd
|
||||
- $INFRA_DIR/configs/lnd.conf:/root/.lnd/lnd.conf:ro
|
||||
OVERRIDE
|
||||
|
||||
# Copy main docker-compose
|
||||
cp "$SCRIPT_DIR/docker-compose.yml" "$INFRA_DIR/docker-compose.yml"
|
||||
cp "$SCRIPT_DIR/lnd-init.sh" "$INFRA_DIR/lnd-init.sh"
|
||||
chmod +x "$INFRA_DIR/lnd-init.sh"
|
||||
|
||||
ok "Configs installed"
|
||||
|
||||
# ── 8. Save credentials ──────────────────────────────────────
|
||||
CREDS_FILE="/root/node-credentials.txt"
|
||||
cat > "$CREDS_FILE" <<CREDS
|
||||
# Timmy Node Credentials — keep this safe
|
||||
# Generated: $(date -u)
|
||||
|
||||
BITCOIN_RPC_USER=satoshi
|
||||
BITCOIN_RPC_PASS=$RPC_PASS
|
||||
CREDS
|
||||
chmod 600 "$CREDS_FILE"
|
||||
ok "Credentials saved to $CREDS_FILE"
|
||||
|
||||
# ── 9. Start Bitcoin Core ────────────────────────────────────
|
||||
info "Starting Bitcoin Core (this begins chain sync — will take days)..."
|
||||
cd "$INFRA_DIR"
|
||||
docker compose up -d bitcoin
|
||||
ok "Bitcoin Core started"
|
||||
|
||||
# ── 10. Done ─────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${GREEN}════════════════════════════════════════${NC}"
|
||||
echo -e "${GREEN} Setup complete — next steps:${NC}"
|
||||
echo -e "${GREEN}════════════════════════════════════════${NC}"
|
||||
echo ""
|
||||
echo -e " 1. ${CYAN}Authenticate Tailscale:${NC}"
|
||||
echo -e " tailscale up --ssh"
|
||||
echo -e " (copy the auth URL, open in browser, log in)"
|
||||
echo ""
|
||||
echo -e " 2. ${CYAN}Verify Tailscale SSH works${NC} from another device:"
|
||||
echo -e " ssh root@<node-name> (via Tailscale MagicDNS)"
|
||||
echo -e " Then lock down public SSH:"
|
||||
echo -e " ufw delete allow 22"
|
||||
echo ""
|
||||
echo -e " 3. ${CYAN}Check Bitcoin sync progress:${NC}"
|
||||
echo -e " watch -n 10 'docker exec bitcoin bitcoin-cli getblockchaininfo | jq .verificationprogress'"
|
||||
echo -e " (will show 0.0 → 1.0 over several days)"
|
||||
echo ""
|
||||
echo -e " 4. ${CYAN}Once sync reaches ~1.0, initialize LND:${NC}"
|
||||
echo -e " bash $INFRA_DIR/lnd-init.sh"
|
||||
echo ""
|
||||
echo -e " Credentials: ${YELLOW}cat $CREDS_FILE${NC}"
|
||||
echo ""
|
||||
Reference in New Issue
Block a user