Files

339 lines
13 KiB
Bash
Executable File

#!/usr/bin/env bash
# =============================================================================
# Timmy — Bitcoin Core + LND + LNbits automated setup
# Target: macOS, Apple Silicon (M3 Max)
# Mode: mainnet, pruned (~20 GB block storage)
# =============================================================================
set -euo pipefail
BREW="/opt/homebrew/bin/brew"
BITCOIN_DIR="$HOME/Library/Application Support/Bitcoin"
LND_DIR="$HOME/.lnd"
LNBITS_DIR="$HOME/.lnbits"
LAUNCHD_DIR="$HOME/Library/LaunchAgents"
LOG_DIR="$HOME/Library/Logs/timmy-node"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
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} $*" >&2; exit 1; }
# ─── Guards ──────────────────────────────────────────────────────────────────
[[ "$(uname -s)" == "Darwin" ]] || die "This script is for macOS only."
[[ "$(uname -m)" == "arm64" ]] || warn "Not Apple Silicon — paths may differ."
command -v "$BREW" &>/dev/null || die "Homebrew not found at $BREW. Install from https://brew.sh"
# ─── Step 1: Packages ────────────────────────────────────────────────────────
info "Installing Bitcoin Core and LND via Homebrew…"
"$BREW" update --quiet
"$BREW" install bitcoin lnd || true # 'true' so re-runs don't abort on "already installed"
command -v bitcoind &>/dev/null || die "bitcoind not found after install."
command -v lnd &>/dev/null || die "lnd not found after install."
ok "Bitcoin Core $("$BREW" info --json bitcoin | python3 -c "import sys,json; d=json.load(sys.stdin); print(d[0]['installed'][0]['version'])" 2>/dev/null || bitcoind --version | head -1) installed."
ok "LND $("$BREW" info --json lnd | python3 -c "import sys,json; d=json.load(sys.stdin); print(d[0]['installed'][0]['version'])" 2>/dev/null || lnd --version | head -1) installed."
# ─── Step 2: LNbits (Python) ─────────────────────────────────────────────────
info "Setting up LNbits…"
if ! command -v python3 &>/dev/null; then
"$BREW" install python3
fi
PYTHON="$(command -v python3)"
PIP="$PYTHON -m pip"
if [[ ! -d "$LNBITS_DIR" ]]; then
info "Cloning LNbits…"
git clone --depth 1 https://github.com/lnbits/lnbits.git "$LNBITS_DIR"
else
info "LNbits already cloned at $LNBITS_DIR"
fi
info "Installing LNbits Python deps (this may take a minute)…"
cd "$LNBITS_DIR"
if command -v uv &>/dev/null; then
uv pip install --system poetry 2>/dev/null || $PIP install --quiet poetry
else
$PIP install --quiet poetry 2>/dev/null || true
fi
if command -v poetry &>/dev/null || [[ -f "$HOME/.local/bin/poetry" ]]; then
POETRY="$(command -v poetry 2>/dev/null || echo "$HOME/.local/bin/poetry")"
"$POETRY" install --no-root --quiet 2>/dev/null || warn "poetry install had warnings (often OK)"
else
warn "poetry not found — trying pip install from pyproject.toml…"
$PIP install --quiet lnbits 2>/dev/null || warn "direct pip install of lnbits failed — check $LNBITS_DIR manually"
fi
cd "$SCRIPT_DIR"
ok "LNbits ready at $LNBITS_DIR"
# ─── Step 3: Directories & Logs ──────────────────────────────────────────────
info "Creating data and log directories…"
mkdir -p "$LOG_DIR" "$LAUNCHD_DIR"
# ─── Step 4: Generate secrets ────────────────────────────────────────────────
SECRETS_FILE="$SCRIPT_DIR/.node-secrets"
if [[ -f "$SECRETS_FILE" ]]; then
info "Loading existing secrets from $SECRETS_FILE"
source "$SECRETS_FILE"
else
info "Generating fresh secrets…"
RPC_USER="timmy"
RPC_PASS="$(openssl rand -hex 32)"
WALLET_PASS="$(openssl rand -hex 24)"
LND_MACAROON_PASS="$(openssl rand -hex 24)"
cat > "$SECRETS_FILE" <<EOF
RPC_USER="$RPC_USER"
RPC_PASS="$RPC_PASS"
WALLET_PASS="$WALLET_PASS"
LND_MACAROON_PASS="$LND_MACAROON_PASS"
EOF
chmod 600 "$SECRETS_FILE"
ok "Secrets written to $SECRETS_FILE (keep this safe!)"
fi
# ─── Step 5: Bitcoin Core config ─────────────────────────────────────────────
info "Writing Bitcoin Core config…"
mkdir -p "$BITCOIN_DIR"
# Only write if doesn't exist (don't clobber a running node's config)
if [[ ! -f "$BITCOIN_DIR/bitcoin.conf" ]]; then
cat > "$BITCOIN_DIR/bitcoin.conf" <<EOF
# Timmy node — mainnet, pruned
mainnet=1
server=1
prune=20000
# RPC
rpcuser=$RPC_USER
rpcpassword=$RPC_PASS
rpcallowip=127.0.0.1
rpcbind=127.0.0.1
rpcport=8332
# ZMQ (required by LND)
zmqpubrawblock=tcp://127.0.0.1:28332
zmqpubrawtx=tcp://127.0.0.1:28333
# Performance
dbcache=4096
maxmempool=512
maxconnections=24
listen=1
# Logging (keep it manageable)
debug=0
shrinkdebugfile=1
EOF
ok "bitcoin.conf written."
else
ok "bitcoin.conf already exists — leaving untouched."
fi
# ─── Step 6: LND config ──────────────────────────────────────────────────────
info "Writing LND config…"
mkdir -p "$LND_DIR"
if [[ ! -f "$LND_DIR/lnd.conf" ]]; then
cat > "$LND_DIR/lnd.conf" <<EOF
[Application Options]
debuglevel=info
maxpendingchannels=10
# Expose REST API for LNbits
restlisten=0.0.0.0:8080
rpclisten=0.0.0.0:10009
# Wallet password auto-unlock file (written by start.sh)
wallet-unlock-password-file=$LND_DIR/.wallet-password
[Bitcoin]
bitcoin.active=1
bitcoin.mainnet=1
bitcoin.node=bitcoind
[Bitcoind]
bitcoind.rpchost=127.0.0.1:8332
bitcoind.rpcuser=$RPC_USER
bitcoind.rpcpass=$RPC_PASS
bitcoind.zmqpubrawblock=tcp://127.0.0.1:28332
bitcoind.zmqpubrawtx=tcp://127.0.0.1:28333
bitcoind.estimatemode=ECONOMICAL
[tor]
tor.active=0
EOF
ok "lnd.conf written."
else
ok "lnd.conf already exists — leaving untouched."
fi
# Write wallet unlock password file (used by LND auto-unlock)
echo -n "$WALLET_PASS" > "$LND_DIR/.wallet-password"
chmod 600 "$LND_DIR/.wallet-password"
# ─── Step 7: LNbits .env ─────────────────────────────────────────────────────
info "Writing LNbits environment…"
LNBITS_DATA_DIR="$HOME/.lnbits-data"
mkdir -p "$LNBITS_DATA_DIR"
if [[ ! -f "$LNBITS_DATA_DIR/.env" ]]; then
# Generate a superuser ID for LNbits
LNBITS_SUPER_USER="$(openssl rand -hex 16)"
cat > "$LNBITS_DATA_DIR/.env" <<EOF
# LNbits config — Timmy node
LNBITS_DATA_FOLDER=$LNBITS_DATA_DIR
LNBITS_BACKEND_WALLET_CLASS=LndRestWallet
LND_REST_ENDPOINT=https://127.0.0.1:8080
LND_REST_CERT=$LND_DIR/tls.cert
LND_REST_MACAROON=$LND_DIR/data/chain/bitcoin/mainnet/admin.macaroon
LNBITS_SITE_TITLE=Timmy Node
LNBITS_ALLOWED_IPS=127.0.0.1
HOST=127.0.0.1
PORT=5000
FORWARDED_ALLOW_IPS=*
# Uncomment to set a fixed super user (otherwise auto-generated)
# LNBITS_SUPER_USER=$LNBITS_SUPER_USER
EOF
# Append super user to secrets
echo "LNBITS_SUPER_USER=\"$LNBITS_SUPER_USER\"" >> "$SECRETS_FILE"
ok "LNbits .env written to $LNBITS_DATA_DIR/.env"
else
ok "LNbits .env already exists — leaving untouched."
fi
# ─── Step 8: LaunchAgent plists ──────────────────────────────────────────────
info "Writing LaunchAgent plists (auto-start on login)…"
# Bitcoin Core
cat > "$LAUNCHD_DIR/com.timmy.bitcoind.plist" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key> <string>com.timmy.bitcoind</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/bitcoind</string>
<string>-conf=$HOME/Library/Application Support/Bitcoin/bitcoin.conf</string>
<string>-datadir=$HOME/Library/Application Support/Bitcoin</string>
<string>-daemon=0</string>
</array>
<key>RunAtLoad</key> <true/>
<key>KeepAlive</key> <true/>
<key>StandardOutPath</key> <string>$LOG_DIR/bitcoind.log</string>
<key>StandardErrorPath</key> <string>$LOG_DIR/bitcoind.err</string>
<key>EnvironmentVariables</key>
<dict>
<key>HOME</key><string>$HOME</string>
</dict>
</dict>
</plist>
EOF
# LND — starts after bitcoind (bitcoind takes ~30s to be RPC-ready)
cat > "$LAUNCHD_DIR/com.timmy.lnd.plist" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key> <string>com.timmy.lnd</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/lnd</string>
<string>--configfile=$LND_DIR/lnd.conf</string>
<string>--lnddir=$LND_DIR</string>
</array>
<key>RunAtLoad</key> <true/>
<key>KeepAlive</key> <true/>
<key>StandardOutPath</key> <string>$LOG_DIR/lnd.log</string>
<key>StandardErrorPath</key> <string>$LOG_DIR/lnd.err</string>
<key>EnvironmentVariables</key>
<dict>
<key>HOME</key><string>$HOME</string>
</dict>
</dict>
</plist>
EOF
# LNbits — depends on LND being up
cat > "$LAUNCHD_DIR/com.timmy.lnbits.plist" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key> <string>com.timmy.lnbits</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>cd $LNBITS_DIR &amp;&amp; env \$(cat $LNBITS_DATA_DIR/.env | grep -v '^#' | xargs) poetry run lnbits</string>
</array>
<key>RunAtLoad</key> <true/>
<key>KeepAlive</key> <true/>
<key>StandardOutPath</key> <string>$LOG_DIR/lnbits.log</string>
<key>StandardErrorPath</key> <string>$LOG_DIR/lnbits.err</string>
<key>EnvironmentVariables</key>
<dict>
<key>HOME</key><string>$HOME</string>
<key>PATH</key><string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
</dict>
</dict>
</plist>
EOF
ok "LaunchAgent plists written to $LAUNCHD_DIR"
# ─── Step 9: Load plists ─────────────────────────────────────────────────────
info "Loading LaunchAgents…"
for plist in com.timmy.bitcoind com.timmy.lnd com.timmy.lnbits; do
launchctl unload "$LAUNCHD_DIR/$plist.plist" 2>/dev/null || true
launchctl load -w "$LAUNCHD_DIR/$plist.plist"
ok "$plist loaded."
done
# ─── Step 10: Create LND wallet if needed ─────────────────────────────────────
info "Waiting 15s for LND to start before checking wallet…"
sleep 15
LNCLI="/opt/homebrew/bin/lncli"
if ! "$LNCLI" --lnddir="$LND_DIR" getinfo &>/dev/null; then
info "LND wallet not yet initialized — creating one now…"
echo "Wallet password will be: $WALLET_PASS"
echo "---"
echo "Run this command in a new terminal:"
echo ""
echo " lncli --lnddir=$LND_DIR create"
echo ""
echo "Use password: $WALLET_PASS"
echo "Choose 'n' for existing seed (new wallet)"
echo "Save the 24-word seed in a safe place!"
echo "---"
warn "After creating the wallet, run: bash $SCRIPT_DIR/get-lnbits-key.sh"
else
ok "LND wallet already exists and is accessible."
fi
# ─── Done ────────────────────────────────────────────────────────────────────
echo ""
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN} Timmy node setup complete!${NC}"
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo " Bitcoin Core is syncing — this takes 1-3 days (pruned)."
echo " LND will connect once bitcoind is at chain tip."
echo " LNbits will be live at http://127.0.0.1:5000 once LND is ready."
echo ""
echo " Next steps:"
echo " 1. Create your LND wallet: lncli --lnddir=$LND_DIR create"
echo " 2. Check sync status: bash $SCRIPT_DIR/status.sh"
echo " 3. Once synced, get key: bash $SCRIPT_DIR/get-lnbits-key.sh"
echo ""
echo " LNbits key retrieval (get-lnbits-key.sh):"
echo " • LNbits < 0.12 — auto-creates a wallet via the superuser API"
echo " • LNbits >= 0.12 — superuser API removed; script walks you through"
echo " the Admin UI at http://localhost:5000/admin"
echo ""
echo " Secrets are in: $SECRETS_FILE"
echo " Logs are in: $LOG_DIR/"
echo ""