339 lines
13 KiB
Bash
Executable File
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 && 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 ""
|