#!/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" < "$BITCOIN_DIR/bitcoin.conf" < "$LND_DIR/lnd.conf" < "$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" <> "$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" < Label com.timmy.bitcoind ProgramArguments /opt/homebrew/bin/bitcoind -conf=$HOME/Library/Application Support/Bitcoin/bitcoin.conf -datadir=$HOME/Library/Application Support/Bitcoin -daemon=0 RunAtLoad KeepAlive StandardOutPath $LOG_DIR/bitcoind.log StandardErrorPath $LOG_DIR/bitcoind.err EnvironmentVariables HOME$HOME EOF # LND — starts after bitcoind (bitcoind takes ~30s to be RPC-ready) cat > "$LAUNCHD_DIR/com.timmy.lnd.plist" < Label com.timmy.lnd ProgramArguments /opt/homebrew/bin/lnd --configfile=$LND_DIR/lnd.conf --lnddir=$LND_DIR RunAtLoad KeepAlive StandardOutPath $LOG_DIR/lnd.log StandardErrorPath $LOG_DIR/lnd.err EnvironmentVariables HOME$HOME EOF # LNbits — depends on LND being up cat > "$LAUNCHD_DIR/com.timmy.lnbits.plist" < Label com.timmy.lnbits ProgramArguments /bin/bash -c cd $LNBITS_DIR && env \$(cat $LNBITS_DATA_DIR/.env | grep -v '^#' | xargs) poetry run lnbits RunAtLoad KeepAlive StandardOutPath $LOG_DIR/lnbits.log StandardErrorPath $LOG_DIR/lnbits.err EnvironmentVariables HOME$HOME PATH/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin 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 ""