#!/usr/bin/env bash # ============================================================ # Timmy Tower API — One-shot production setup for Ubuntu 22.04+ # # Run as root on the VPS (same hermes droplet as the Lightning node): # bash setup-api.sh # # Prerequisites: # - setup.sh (Bitcoin node bootstrap) has already run # - Tailscale is authenticated # ============================================================ 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-api]${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-api.sh" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DEPLOY_DIR="/opt/timmy-tower" DB_NAME="timmy_tower" DB_USER="timmy" info "Starting Timmy Tower API setup..." echo "" # ── 1. Node.js 24 via NodeSource ───────────────────────────── info "Installing Node.js 24..." if ! command -v node &>/dev/null || [[ "$(node -v | cut -d. -f1 | tr -d v)" -lt 24 ]]; then curl -fsSL https://deb.nodesource.com/setup_24.x | bash - apt-get install -y -qq nodejs fi node -v ok "Node.js $(node -v) ready" # ── 2. PostgreSQL ──────────────────────────────────────────── info "Installing PostgreSQL..." if ! command -v psql &>/dev/null; then apt-get install -y -qq postgresql postgresql-contrib systemctl enable postgresql systemctl start postgresql fi ok "PostgreSQL ready" # Create database and user (idempotent) info "Provisioning database..." DB_PASS=$(openssl rand -hex 16) sudo -u postgres psql -tc "SELECT 1 FROM pg_roles WHERE rolname='$DB_USER'" | grep -q 1 || \ sudo -u postgres psql -c "CREATE USER $DB_USER WITH PASSWORD '$DB_PASS';" sudo -u postgres psql -tc "SELECT 1 FROM pg_database WHERE datname='$DB_NAME'" | grep -q 1 || \ sudo -u postgres psql -c "CREATE DATABASE $DB_NAME OWNER $DB_USER;" sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE $DB_NAME TO $DB_USER;" 2>/dev/null || true ok "Database '$DB_NAME' provisioned" # ── 3. Caddy (reverse proxy with automatic HTTPS) ─────────── info "Installing Caddy..." if ! command -v caddy &>/dev/null; then apt-get install -y -qq debian-keyring debian-archive-keyring apt-transport-https curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list apt-get update -qq apt-get install -y -qq caddy fi ok "Caddy ready" # ── 4. System user ─────────────────────────────────────────── info "Creating system user..." if ! id "$DB_USER" &>/dev/null; then useradd --system --create-home --shell /usr/sbin/nologin "$DB_USER" fi ok "User '$DB_USER' ready" # ── 5. Deploy directory ────────────────────────────────────── info "Setting up deploy directory..." mkdir -p "$DEPLOY_DIR" chown "$DB_USER:$DB_USER" "$DEPLOY_DIR" # Install externalized npm packages (not bundled by esbuild) if [[ ! -d "$DEPLOY_DIR/node_modules" ]]; then info "Installing external npm packages..." cd "$DEPLOY_DIR" cat > package.json <<'PKG' { "private": true, "dependencies": { "nostr-tools": "^2.23.3", "cookie-parser": "^1.4.7" } } PKG npm install --production chown -R "$DB_USER:$DB_USER" "$DEPLOY_DIR" fi ok "Deploy directory ready" # ── 6. Environment file ───────────────────────────────────── info "Writing environment file..." if [[ ! -f "$DEPLOY_DIR/.env" ]]; then cat > "$DEPLOY_DIR/.env" < # AI backend — set one of these # Option A: Direct Anthropic # ANTHROPIC_API_KEY=sk-ant-... # Option B: OpenRouter (Anthropic SDK compat layer) # AI_INTEGRATIONS_ANTHROPIC_BASE_URL=https://openrouter.ai/api/v1 # AI_INTEGRATIONS_ANTHROPIC_API_KEY=sk-or-... # Nostr identity (generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))") # TIMMY_NOSTR_NSEC= # Relay policy shared secret (must match relay-policy container) # RELAY_POLICY_SECRET= ENV chmod 600 "$DEPLOY_DIR/.env" chown "$DB_USER:$DB_USER" "$DEPLOY_DIR/.env" warn "Edit $DEPLOY_DIR/.env to set API keys before starting the service" else # Update DATABASE_URL if DB was just created if ! grep -q "DATABASE_URL" "$DEPLOY_DIR/.env"; then echo "DATABASE_URL=postgres://${DB_USER}:${DB_PASS}@localhost:5432/${DB_NAME}" >> "$DEPLOY_DIR/.env" fi warn ".env already exists — not overwriting. DB password: $DB_PASS" fi # ── 7. Systemd service ────────────────────────────────────── info "Installing systemd service..." cp "$SCRIPT_DIR/timmy-tower.service" /etc/systemd/system/timmy-tower.service systemctl daemon-reload systemctl enable timmy-tower ok "Systemd service installed (timmy-tower)" # ── 8. Caddy config ───────────────────────────────────────── info "Installing Caddy config..." mkdir -p /var/log/caddy cp "$SCRIPT_DIR/Caddyfile" /etc/caddy/Caddyfile ok "Caddyfile installed" echo "" echo -e "${YELLOW}NOTE: Set TIMMY_DOMAIN in /etc/caddy/Caddyfile or as env var before starting Caddy.${NC}" echo -e "${YELLOW} For HTTPS, point your DNS A record to this server's IP first.${NC}" echo "" # ── 9. Logrotate ───────────────────────────────────────────── info "Installing logrotate config..." mkdir -p /var/log/timmy-tower chown "$DB_USER:$DB_USER" /var/log/timmy-tower cp "$SCRIPT_DIR/logrotate.conf" /etc/logrotate.d/timmy-tower ok "Logrotate configured" # ── 10. Firewall — open HTTP/HTTPS ─────────────────────────── info "Opening firewall ports for HTTP/HTTPS..." ufw allow 80/tcp comment "HTTP (Caddy)" 2>/dev/null || true ufw allow 443/tcp comment "HTTPS (Caddy)" 2>/dev/null || true ok "Firewall updated" # ── 11. Health check cron ──────────────────────────────────── info "Installing health check cron..." HEALTHCHECK_SCRIPT="$DEPLOY_DIR/healthcheck.sh" cp "$SCRIPT_DIR/healthcheck.sh" "$HEALTHCHECK_SCRIPT" chmod +x "$HEALTHCHECK_SCRIPT" chown "$DB_USER:$DB_USER" "$HEALTHCHECK_SCRIPT" # Add cron job (every 5 minutes) crontab -l 2>/dev/null | grep -v "timmy-tower.*healthcheck" | crontab - || true (crontab -l 2>/dev/null; echo "*/5 * * * * bash $HEALTHCHECK_SCRIPT >> /var/log/timmy-tower/healthcheck.log 2>&1 # timmy-tower healthcheck") | crontab - ok "Health check cron installed (every 5 min)" # ── Done ───────────────────────────────────────────────────── echo "" echo -e "${GREEN}════════════════════════════════════════${NC}" echo -e "${GREEN} API setup complete — next steps:${NC}" echo -e "${GREEN}════════════════════════════════════════${NC}" echo "" echo -e " 1. ${CYAN}Edit environment variables:${NC}" echo -e " nano $DEPLOY_DIR/.env" echo -e " (set LNBITS_API_KEY, AI keys, Nostr identity)" echo "" echo -e " 2. ${CYAN}Deploy the API bundle:${NC}" echo -e " bash $SCRIPT_DIR/deploy.sh" echo -e " (or from dev machine: bash infrastructure/api-server/deploy.sh)" echo "" echo -e " 3. ${CYAN}Set your domain and start Caddy:${NC}" echo -e " export TIMMY_DOMAIN=yourdomain.com" echo -e " systemctl restart caddy" echo "" echo -e " 4. ${CYAN}Start the API:${NC}" echo -e " systemctl start timmy-tower" echo -e " journalctl -u timmy-tower -f" echo "" echo -e " 5. ${CYAN}Verify health:${NC}" echo -e " curl -s http://localhost:8080/api/health | jq ." echo "" echo -e " DB credentials: ${YELLOW}postgres://${DB_USER}:${DB_PASS}@localhost:5432/${DB_NAME}${NC}" echo ""