diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b03c513 --- /dev/null +++ b/.env.example @@ -0,0 +1,51 @@ +# AI Code Battle Environment Configuration +# Copy this file to .env and fill in the values + +# =========================================== +# Worker API Configuration +# =========================================== +# The Cloudflare Worker API endpoint +ACB_API_ENDPOINT=https://api.aicodebattle.com + +# Worker API key for authentication (required for workers and indexer) +ACB_API_KEY=your-worker-api-key-here + +# =========================================== +# R2 Storage Configuration +# =========================================== +# R2 endpoint URL (Cloudflare R2 S3-compatible endpoint) +ACB_R2_ENDPOINT=https://your-account-id.r2.cloudflarestorage.com + +# R2 bucket name for replays and match data +ACB_R2_BUCKET=acb-data + +# R2 access credentials (from Cloudflare dashboard) +ACB_R2_ACCESS_KEY=your-r2-access-key-id +ACB_R2_SECRET_KEY=your-r2-secret-access-key + +# =========================================== +# Bot Secrets (for local development) +# =========================================== +# Each bot needs a secret for HMAC authentication +BOT_SECRET_RANDOM=dev-secret-random +BOT_SECRET_GATHERER=dev-secret-gatherer +BOT_SECRET_RUSHER=dev-secret-rusher +BOT_SECRET_GUARDIAN=dev-secret-guardian +BOT_SECRET_SWARM=dev-secret-swarm +BOT_SECRET_HUNTER=dev-secret-hunter + +# =========================================== +# Worker Configuration +# =========================================== +# Optional: unique worker identifier (auto-generated if not set) +ACB_WORKER_ID= + +# Enable verbose logging (true/false) +ACB_VERBOSE=false + +# =========================================== +# Index Builder Configuration +# =========================================== +# Optional: command to deploy generated files to Cloudflare Pages +# Example: wrangler pages deploy /app/data --project-name=aicodebattle +DEPLOY_COMMAND= diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..fc05c91 --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,156 @@ +# AI Code Battle - Deployment Guide + +This document describes how to deploy AI Code Battle to production. + +## Architecture Overview + +The platform is split across two tiers: + +1. **Cloudflare (free tier)** - Web-facing infrastructure + - Pages: SPA shell + pre-computed JSON index files + - Worker: API endpoints for registration, job coordination + - D1: SQLite database for bots, matches, ratings + - R2: Replays, match metadata, maps, thumbnails + +2. **Rackspace Spot** - Compute tier + - Match workers: Execute matches, upload replays to R2 + - Bot containers: Run strategy bot HTTP servers + - Index builder: Generates JSON indexes, deploys to Pages + +## Prerequisites + +- Cloudflare account with: + - Pages project created + - Worker deployed + - D1 database created + - R2 bucket with custom domain configured +- Rackspace Spot account or equivalent container hosting +- Docker and docker-compose installed + +## Environment Setup + +1. Copy the example environment file: + ```bash + cp .env.example .env + ``` + +2. Edit `.env` and fill in your values: + - `ACB_API_ENDPOINT`: Your Cloudflare Worker URL + - `ACB_API_KEY`: Worker API key + - `ACB_R2_*`: R2 credentials from Cloudflare dashboard + - `BOT_SECRET_*`: Generate unique secrets for each bot + +## Deploying Strategy Bots + +The strategy bots run as HTTP servers that the match workers call during games. + +```bash +# Build and start all 6 strategy bots +docker-compose -f docker-compose.bots.yml up -d + +# Check status +docker-compose -f docker-compose.bots.yml ps + +# View logs +docker-compose -f docker-compose.bots.yml logs -f +``` + +Bot endpoints will be available at: +- RandomBot: http://localhost:8081/turn +- GathererBot: http://localhost:8082/turn +- RusherBot: http://localhost:8083/turn +- GuardianBot: http://localhost:8084/turn +- SwarmBot: http://localhost:8085/turn +- HunterBot: http://localhost:8086/turn + +## Deploying Match Workers + +Match workers poll the Worker API for pending jobs and execute matches. + +```bash +# Build and start match workers +docker-compose -f docker-compose.workers.yml up -d + +# Scale workers based on load +docker-compose -f docker-compose.workers.yml up -d --scale worker=3 +``` + +## Running the Index Builder + +The index builder generates static JSON files for the web platform. + +```bash +# Run once to generate index files +docker-compose -f docker-compose.workers.yml run indexer + +# For automatic deployment, set DEPLOY_COMMAND in .env: +# DEPLOY_COMMAND=wrangler pages deploy /app/data --project-name=aicodebattle +``` + +## Cloudflare Configuration + +### Worker API + +Deploy the worker: +```bash +cd worker-api +npm install +wrangler deploy +``` + +### D1 Database + +Create the database: +```bash +wrangler d1 create acb-db +``` + +Apply migrations (if any): +```bash +wrangler d1 execute acb-db --file=./schema.sql +``` + +### R2 Bucket + +Create the bucket: +```bash +wrangler r2 bucket create acb-data +``` + +Configure custom domain in Cloudflare dashboard: +- Domain: `data.aicodebattle.com` +- Bucket: `acb-data` + +### Pages + +Deploy the web SPA: +```bash +cd web +npm install +npm run build +wrangler pages deploy dist --project-name=aicodebattle +``` + +## Monitoring + +- Cloudflare Analytics: Available in Cloudflare dashboard +- Worker Logs: `wrangler tail` +- Container Logs: `docker-compose logs -f` + +## Troubleshooting + +### Worker can't connect to API + +Check that `ACB_API_ENDPOINT` is correct and accessible from the worker container. + +### Bot authentication failures + +Verify `BOT_SECRET_*` values match what's registered in the Worker API. + +### R2 upload failures + +Check R2 credentials and bucket permissions. + +### Index builder not deploying + +Ensure `DEPLOY_COMMAND` is set correctly and Cloudflare API token has Pages deploy permissions. diff --git a/PROGRESS.md b/PROGRESS.md index 949f0c1..44bb324 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -2,7 +2,38 @@ ## Current Phase: Phase 6 - Deployment & Production -**Status: 🔄 Ready to Start** +**Status: 🔄 In Progress** + +### Phase 6 Progress + +- [x] Match worker container (`cmd/acb-worker/Dockerfile`) + - Multi-stage Go build + - Non-root user for security + - Environment variable configuration +- [x] Bot-host deployment (`docker-compose.bots.yml`) + - Orchestrates all 6 strategy bots + - Health checks for each bot + - Environment-based secret configuration +- [x] Worker deployment (`docker-compose.workers.yml`) + - Match worker with scaling support + - Index builder for periodic runs + - R2 and API configuration +- [x] Environment configuration (`.env.example`) + - Documented all required environment variables +- [x] Deployment documentation (`DEPLOYMENT.md`) + - Architecture overview + - Cloudflare setup instructions + - Container deployment commands + - Troubleshooting guide + +### Remaining Phase 6 Work + +- [ ] Cloudflare Pages project creation and deployment +- [ ] D1 database schema and migrations +- [ ] R2 bucket creation and custom domain +- [ ] Worker API deployment via Wrangler +- [ ] DNS configuration +- [ ] Monitoring setup ### Phase 5 Completed ✅ @@ -64,6 +95,11 @@ ``` ai-code-battle/ ├── go.mod +├── go.sum +├── .env.example # Environment configuration template +├── DEPLOYMENT.md # Deployment guide +├── docker-compose.bots.yml # Bot-host orchestration +├── docker-compose.workers.yml # Worker orchestration ├── engine/ │ ├── types.go # Core data types │ ├── grid.go # Toroidal grid implementation @@ -82,7 +118,8 @@ ai-code-battle/ │ │ ├── main.go # Worker entry point │ │ ├── api.go # Worker API client │ │ ├── api_test.go # API client tests -│ │ └── r2.go # R2 upload client +│ │ ├── r2.go # R2 upload client +│ │ └── Dockerfile # Worker container │ └── acb-indexer/ # Index builder │ ├── package.json │ ├── Dockerfile diff --git a/cmd/acb-worker/Dockerfile b/cmd/acb-worker/Dockerfile new file mode 100644 index 0000000..349d1de --- /dev/null +++ b/cmd/acb-worker/Dockerfile @@ -0,0 +1,51 @@ +# AI Code Battle Match Worker Container +# Polls for match jobs from the Worker API, executes matches, uploads replays to R2 + +# Build stage +FROM golang:1.24-alpine AS builder + +WORKDIR /build + +# Copy go.mod and go.sum first for caching +COPY go.mod go.sum ./ +RUN go mod download + +# Copy engine package +COPY engine/ ./engine/ + +# Copy worker source +COPY cmd/acb-worker/ ./cmd/acb-worker/ + +# Build the binary +RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /acb-worker ./cmd/acb-worker + +# Runtime stage +FROM alpine:3.19 + +WORKDIR /app + +# Install ca-certificates for HTTPS +RUN apk --no-cache add ca-certificates tzdata + +# Copy binary from builder +COPY --from=builder /acb-worker /app/acb-worker + +# Create non-root user +RUN adduser -D -u 1000 acb +USER acb + +# Environment variables (set at runtime) +# ACB_API_ENDPOINT - Worker API URL (e.g., https://api.aicodebattle.com) +# ACB_API_KEY - Worker API key for authentication +# ACB_R2_ENDPOINT - R2 endpoint URL +# ACB_R2_BUCKET - R2 bucket name (default: acb-data) +# ACB_R2_ACCESS_KEY - R2 access key ID +# ACB_R2_SECRET_KEY - R2 secret access key +# ACB_WORKER_ID - Unique worker identifier (auto-generated if not set) +# ACB_VERBOSE - Enable verbose logging (true/false) + +# Default values +ENV ACB_R2_BUCKET=acb-data + +ENTRYPOINT ["/app/acb-worker"] +CMD ["-poll=5s", "-heartbeat=30s", "-timeout=3s"] diff --git a/docker-compose.bots.yml b/docker-compose.bots.yml new file mode 100644 index 0000000..f4f2ec6 --- /dev/null +++ b/docker-compose.bots.yml @@ -0,0 +1,110 @@ +# AI Code Battle - Bot Host Deployment +# Runs all 6 strategy bots as a single deployable unit +# +# Usage: +# docker-compose -f docker-compose.bots.yml up -d +# +# Environment variables (set in .env or pass directly): +# BOT_SECRET_RANDOM - Secret for RandomBot +# BOT_SECRET_GATHERER - Secret for GathererBot +# BOT_SECRET_RUSHER - Secret for RusherBot +# BOT_SECRET_GUARDIAN - Secret for GuardianBot +# BOT_SECRET_SWARM - Secret for SwarmBot +# BOT_SECRET_HUNTER - Secret for HunterBot + +services: + random: + build: + context: ./bots/random + dockerfile: Dockerfile + ports: + - "8081:8080" + environment: + - BOT_PORT=8080 + - BOT_SECRET=${BOT_SECRET_RANDOM:-dev-secret-random} + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/health"] + interval: 30s + timeout: 5s + retries: 3 + restart: unless-stopped + + gatherer: + build: + context: ./bots/gatherer + dockerfile: Dockerfile + ports: + - "8082:8080" + environment: + - BOT_PORT=8080 + - BOT_SECRET=${BOT_SECRET_GATHERER:-dev-secret-gatherer} + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"] + interval: 30s + timeout: 5s + retries: 3 + restart: unless-stopped + + rusher: + build: + context: ./bots/rusher + dockerfile: Dockerfile + ports: + - "8083:8082" + environment: + - BOT_PORT=8082 + - BOT_SECRET=${BOT_SECRET_RUSHER:-dev-secret-rusher} + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost:8082/health"] + interval: 30s + timeout: 5s + retries: 3 + restart: unless-stopped + + guardian: + build: + context: ./bots/guardian + dockerfile: Dockerfile + ports: + - "8084:8083" + environment: + - BOT_PORT=8083 + - BOT_SECRET=${BOT_SECRET_GUARDIAN:-dev-secret-guardian} + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost:8083/health"] + interval: 30s + timeout: 5s + retries: 3 + restart: unless-stopped + + swarm: + build: + context: ./bots/swarm + dockerfile: Dockerfile + ports: + - "8085:8084" + environment: + - BOT_PORT=8084 + - BOT_SECRET=${BOT_SECRET_SWARM:-dev-secret-swarm} + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost:8084/health"] + interval: 30s + timeout: 5s + retries: 3 + restart: unless-stopped + + hunter: + build: + context: ./bots/hunter + dockerfile: Dockerfile + ports: + - "8086:8085" + environment: + - BOT_PORT=8085 + - BOT_SECRET=${BOT_SECRET_HUNTER:-dev-secret-hunter} + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost:8085/health"] + interval: 30s + timeout: 5s + retries: 3 + restart: unless-stopped diff --git a/docker-compose.workers.yml b/docker-compose.workers.yml new file mode 100644 index 0000000..2acb9bb --- /dev/null +++ b/docker-compose.workers.yml @@ -0,0 +1,61 @@ +# AI Code Battle - Rackspace Worker Deployment +# Runs match workers and index builder on Rackspace Spot +# +# Usage: +# docker-compose -f docker-compose.workers.yml up -d +# +# Environment variables (set in .env or pass directly): +# ACB_API_ENDPOINT - Worker API URL (e.g., https://api.aicodebattle.com) +# ACB_API_KEY - Worker API key for authentication +# ACB_R2_ENDPOINT - R2 endpoint URL +# ACB_R2_BUCKET - R2 bucket name +# ACB_R2_ACCESS_KEY - R2 access key ID +# ACB_R2_SECRET_KEY - R2 secret access key + +services: + # Match execution worker + worker: + build: + context: . + dockerfile: cmd/acb-worker/Dockerfile + environment: + - ACB_API_ENDPOINT=${ACB_API_ENDPOINT:-http://host.docker.internal:8787} + - ACB_API_KEY=${ACB_API_KEY:?ACB_API_KEY is required} + - ACB_R2_ENDPOINT=${ACB_R2_ENDPOINT} + - ACB_R2_BUCKET=${ACB_R2_BUCKET:-acb-data} + - ACB_R2_ACCESS_KEY=${ACB_R2_ACCESS_KEY} + - ACB_R2_SECRET_KEY=${ACB_R2_SECRET_KEY} + - ACB_WORKER_ID=${ACB_WORKER_ID:-} + - ACB_VERBOSE=${ACB_VERBOSE:-false} + command: ["-poll=5s", "-heartbeat=30s", "-timeout=3s"] + extra_hosts: + - "host.docker.internal:host-gateway" + restart: unless-stopped + deploy: + replicas: 1 + resources: + limits: + memory: 512M + reservations: + memory: 256M + + # Index builder - runs periodically to rebuild static JSON files + # Run manually: docker-compose -f docker-compose.workers.yml run indexer + indexer: + build: + context: ./cmd/acb-indexer + dockerfile: Dockerfile + environment: + - API_URL=${ACB_API_ENDPOINT:-http://host.docker.internal:8787} + - API_KEY=${ACB_API_KEY:?ACB_API_KEY is required} + - OUTPUT_DIR=/app/data + - DEPLOY_COMMAND=${DEPLOY_COMMAND:-} + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - indexer-data:/app/data + profiles: + - manual + +volumes: + indexer-data: