This repository has been archived on 2026-03-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
token-gated-economy/infrastructure/api-server/setup-api.sh
Alexander Whitestone f7dd5f5212
Some checks failed
CI / Typecheck & Lint (pull_request) Failing after 0s
feat: add production deployment infrastructure for API server
Adds systemd service, Caddy reverse proxy, PostgreSQL provisioning,
health check monitoring, log rotation, and deploy script for the
Timmy Tower API on the hermes VPS (Option A: same droplet as
Lightning node).

Files added:
- infrastructure/api-server/setup-api.sh — one-shot VPS bootstrap
- infrastructure/api-server/deploy.sh — build + deploy from dev
- infrastructure/api-server/timmy-tower.service — systemd unit
- infrastructure/api-server/Caddyfile — reverse proxy with auto-HTTPS
- infrastructure/api-server/healthcheck.sh — cron-based monitoring
- infrastructure/api-server/logrotate.conf — log rotation config
- infrastructure/ops.sh — api:status/logs/restart/health commands

Refs #10

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 22:01:08 -04:00

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 ""