Co-authored-by: Claude (Opus 4.6) <claude@hermes.local> Co-committed-by: Claude (Opus 4.6) <claude@hermes.local>
210 lines
8.8 KiB
Bash
Executable File
210 lines
8.8 KiB
Bash
Executable File
#!/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" <<ENV
|
|
# Timmy Tower API — Production environment
|
|
# Generated: $(date -u)
|
|
|
|
NODE_ENV=production
|
|
PORT=8080
|
|
|
|
# Database
|
|
DATABASE_URL=postgres://${DB_USER}:${DB_PASS}@localhost:5432/${DB_NAME}
|
|
|
|
# LNbits — running on same host via Docker (see docker-compose.yml)
|
|
LNBITS_URL=http://localhost:5000
|
|
LNBITS_API_KEY=<set-after-lnbits-wallet-creation>
|
|
|
|
# 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=<hex-secret-key>
|
|
|
|
# Relay policy shared secret (must match relay-policy container)
|
|
# RELAY_POLICY_SECRET=<shared-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 ""
|