#!/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/ && mount /dev/ /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 — bitcoinknots/bitcoin image uses /home/bitcoin/.bitcoin inside container # which maps to $DATA_DIR/bitcoin on the host via the Docker volume bind 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" </dev/null | grep -v "timmy-node" | crontab - || true (crontab -l 2>/dev/null; cat <<'CRON' # Timmy Node — auto-sweep hot wallet to cold storage (3am UTC daily) # sweep.sh manages its own logging to /var/log/timmy-sweep.log via tee 0 3 * * * bash /opt/timmy-node/sweep.sh > /dev/null 2>&1 # Timmy Node — LND channel state backup (4am UTC daily) 0 4 * * * bash /opt/timmy-node/ops.sh backup >> /var/log/timmy-backup.log 2>&1 CRON ) | crontab - touch /var/log/timmy-sweep.log /var/log/timmy-backup.log ok "Cron jobs installed (sweep 3am, backup 4am UTC)" # ── 8. Save credentials ────────────────────────────────────── CREDS_FILE="/root/node-credentials.txt" cat > "$CREDS_FILE" < (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 ""