mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 21:55:42 +00:00
Notify connected clients when a new version is available after CI deploy. Flow: 1. CI deploys new build and waits 60s for CDN cache 2. CI calls admin server HTTP endpoint with shared secret 3. Admin server calls Eagle via gRPC 4. Eagle broadcasts to all connected lobby users Components: - Proto: ClientUpdateAvailable message, NotifyClientUpdate RPC - Eagle: notifyClientUpdate() broadcasts to lobby users - Admin server: /notify-update HTTP endpoint with secret auth - CI: Notify steps in mac_build.yml and unity_build.yml - Docker: NOTIFY_SECRET env var passed to admin container Client-side handling will be added in a follow-up PR. Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
300 lines
11 KiB
YAML
300 lines
11 KiB
YAML
# Docker Compose for production deployment
|
|
#
|
|
# Local testing:
|
|
# Build images: bazel run //ci:eagle_server_load && bazel run //ci:auth_server_load
|
|
# Run: docker compose -f docker-compose.prod.yml up
|
|
#
|
|
# Production deployment:
|
|
# Run: docker compose -f docker-compose.prod.yml up -d
|
|
#
|
|
# Note: Shardok runs on Hetzner ARM64 server, deployed via shardok_arm64_build.yml workflow.
|
|
|
|
services:
|
|
# Blue-green deployment: eagle-blue is the primary (production) instance
|
|
# eagle-green is the staging instance for zero-downtime deployments
|
|
# See scripts/deploy-blue-green.sh for deployment workflow
|
|
|
|
eagle-blue:
|
|
image: ${EAGLE_IMAGE:-registry.digitalocean.com/eagle0/eagle-server:latest}
|
|
container_name: eagle-blue
|
|
command:
|
|
- "--shardok-interface-remote-address"
|
|
- "${SHARDOK_ADDRESS}"
|
|
- "--auth-service-url"
|
|
- "auth:40033"
|
|
ports:
|
|
- "40032:40032"
|
|
environment:
|
|
OPENAI_API_KEY: "${OPENAI_API_KEY:-}"
|
|
ANTHROPIC_API_KEY: "${ANTHROPIC_API_KEY:-}"
|
|
GEMINI_API_KEY: "${GEMINI_API_KEY:-}"
|
|
EAGLE_ENABLE_S3: "${EAGLE_ENABLE_S3:-false}"
|
|
DO_SPACES_ENDPOINT: "${DO_SPACES_ENDPOINT:-https://sfo3.digitaloceanspaces.com}"
|
|
DO_SPACES_ACCESS_KEY: "${DO_SPACES_ACCESS_KEY:-}"
|
|
DO_SPACES_SECRET_KEY: "${DO_SPACES_SECRET_KEY:-}"
|
|
# JWT public key for token validation (auth service handles signing)
|
|
# Reads from /etc/eagle0/keys/public.pem via shared volume
|
|
# Auth token for Shardok on Hetzner (required)
|
|
SHARDOK_AUTH_TOKEN: "${SHARDOK_AUTH_TOKEN:-}"
|
|
# Use persistent volume for save data (users, games, etc.)
|
|
EAGLE_SAVE_DIR: "/app/saves"
|
|
EAGLE_ARCHIVE_DIR: "/app/archived"
|
|
SENTRY_DSN: "${SENTRY_DSN:-}"
|
|
SENTRY_ENVIRONMENT: "production"
|
|
volumes:
|
|
- ./saves:/app/saves # Game saves and user database
|
|
- ./archived:/app/archived # Archived completed games
|
|
- ./jfr:/app/jfr # JFR recordings - dump with: docker exec eagle-blue jcmd 1 JFR.dump filename=/app/jfr/profile.jfr
|
|
- jvm-tmp:/tmp # Shared with jfr-sidecar for JVM attach socket files
|
|
- jwt-keys:/etc/eagle0/keys:ro # JWT public key from auth service (read-only)
|
|
depends_on:
|
|
- auth
|
|
restart: unless-stopped
|
|
logging:
|
|
driver: "json-file"
|
|
options:
|
|
max-size: "100m"
|
|
max-file: "5"
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "nc -z localhost 40032 || exit 1"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 30s
|
|
|
|
eagle-green:
|
|
image: ${EAGLE_IMAGE_NEW:-registry.digitalocean.com/eagle0/eagle-server:latest}
|
|
container_name: eagle-green
|
|
profiles: ["blue-green"] # Only started during blue-green deployment
|
|
command:
|
|
- "--shardok-interface-remote-address"
|
|
- "${SHARDOK_ADDRESS}"
|
|
- "--auth-service-url"
|
|
- "auth:40033"
|
|
ports:
|
|
- "40034:40032" # Different host port for staging
|
|
environment:
|
|
OPENAI_API_KEY: "${OPENAI_API_KEY:-}"
|
|
ANTHROPIC_API_KEY: "${ANTHROPIC_API_KEY:-}"
|
|
GEMINI_API_KEY: "${GEMINI_API_KEY:-}"
|
|
EAGLE_ENABLE_S3: "${EAGLE_ENABLE_S3:-false}"
|
|
DO_SPACES_ENDPOINT: "${DO_SPACES_ENDPOINT:-https://sfo3.digitaloceanspaces.com}"
|
|
DO_SPACES_ACCESS_KEY: "${DO_SPACES_ACCESS_KEY:-}"
|
|
DO_SPACES_SECRET_KEY: "${DO_SPACES_SECRET_KEY:-}"
|
|
# JWT public key for token validation (auth service handles signing)
|
|
# Reads from /etc/eagle0/keys/public.pem via shared volume
|
|
SHARDOK_AUTH_TOKEN: "${SHARDOK_AUTH_TOKEN:-}"
|
|
EAGLE_SAVE_DIR: "/app/saves"
|
|
EAGLE_ARCHIVE_DIR: "/app/archived"
|
|
SENTRY_DSN: "${SENTRY_DSN:-}"
|
|
SENTRY_ENVIRONMENT: "production"
|
|
volumes:
|
|
- ./saves:/app/saves # Same save directory as blue
|
|
- ./archived:/app/archived # Same archive directory as blue
|
|
- ./jfr:/app/jfr # JFR recordings (same as blue)
|
|
- jvm-tmp:/tmp # Shared with jfr-sidecar-green for JVM attach socket files
|
|
- jwt-keys:/etc/eagle0/keys:ro # JWT public key from auth service (read-only)
|
|
depends_on:
|
|
- auth
|
|
restart: "no" # Don't auto-restart during deployment
|
|
logging:
|
|
driver: "json-file"
|
|
options:
|
|
max-size: "100m"
|
|
max-file: "5"
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "nc -z localhost 40032 || exit 1"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 6
|
|
start_period: 60s
|
|
|
|
# Backward compatibility alias - for scripts that reference 'eagle' service
|
|
eagle:
|
|
extends:
|
|
service: eagle-blue
|
|
|
|
auth:
|
|
image: ${AUTH_IMAGE:-registry.digitalocean.com/eagle0/auth-server:latest}
|
|
container_name: auth-server
|
|
environment:
|
|
# gRPC port for Auth service
|
|
AUTH_GRPC_PORT: "40033"
|
|
# HTTP port for OAuth callbacks
|
|
AUTH_HTTP_PORT: "8080"
|
|
# User data persistence directory
|
|
AUTH_DATA_DIR: "/app/data"
|
|
# Legacy path for migrating users from Eagle (Phase 1 migration)
|
|
AUTH_LEGACY_DATA_DIR: "/app/saves/auth"
|
|
# OAuth provider credentials
|
|
DISCORD_CLIENT_ID: "${DISCORD_CLIENT_ID:-}"
|
|
DISCORD_CLIENT_SECRET: "${DISCORD_CLIENT_SECRET:-}"
|
|
GOOGLE_CLIENT_ID: "${GOOGLE_CLIENT_ID:-}"
|
|
GOOGLE_CLIENT_SECRET: "${GOOGLE_CLIENT_SECRET:-}"
|
|
GH_OAUTH_CLIENT_ID: "${GH_OAUTH_CLIENT_ID:-}"
|
|
GH_OAUTH_CLIENT_SECRET: "${GH_OAUTH_CLIENT_SECRET:-}"
|
|
# Apple Sign-In credentials
|
|
APPLE_SIGNIN_CLIENT_ID: "${APPLE_SIGNIN_CLIENT_ID:-}"
|
|
APPLE_TEAM_ID: "${APPLE_TEAM_ID:-}"
|
|
APPLE_SIGNIN_KEY_ID: "${APPLE_SIGNIN_KEY_ID:-}"
|
|
APPLE_SIGNIN_PRIVATE_KEY: "${APPLE_SIGNIN_PRIVATE_KEY:-}"
|
|
# Twitch OAuth credentials
|
|
TWITCH_CLIENT_ID: "${TWITCH_CLIENT_ID:-}"
|
|
TWITCH_CLIENT_SECRET: "${TWITCH_CLIENT_SECRET:-}"
|
|
# Server base URL for OAuth callbacks
|
|
SERVER_BASE_URL: "${SERVER_BASE_URL:-https://prod.eagle0.net}"
|
|
# JWT keys - PEM files in volume, bootstrapped from JWK on first run
|
|
JWT_KEYS_PATH: "/etc/eagle0/keys"
|
|
JWT_PRIVATE_KEY: "${JWT_PRIVATE_KEY:-}"
|
|
# Fastmail JMAP API for sending invitation emails
|
|
FASTMAIL_API_TOKEN: "${FASTMAIL_API_TOKEN:-}"
|
|
FASTMAIL_FROM_EMAIL: "${FASTMAIL_FROM_EMAIL:-}"
|
|
FASTMAIL_FROM_NAME: "${FASTMAIL_FROM_NAME:-}"
|
|
# Require invitation codes for new user registration
|
|
REQUIRE_INVITATION_CODE: "true"
|
|
# Note: port 40033 is exposed via nginx, not directly
|
|
volumes:
|
|
- jwt-keys:/etc/eagle0/keys # Shared JWT keys with Eagle
|
|
- ./auth-data:/app/data # User database persistence
|
|
- ./saves:/app/saves:ro # Read-only access to Eagle's saves for migration
|
|
restart: unless-stopped
|
|
logging:
|
|
driver: "json-file"
|
|
options:
|
|
max-size: "50m"
|
|
max-file: "3"
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "nc -z localhost 40033 || exit 1"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 10s
|
|
|
|
# Note: Shardok runs on Hetzner ARM64 server, not in this docker-compose.
|
|
# Configure SHARDOK_ADDRESS to point to the Hetzner instance.
|
|
|
|
nginx:
|
|
image: nginx:alpine
|
|
container_name: nginx
|
|
ports:
|
|
- "443:443"
|
|
- "80:80"
|
|
- "40033:40033" # Go Auth service gRPC (Phase 2 direct client connections)
|
|
volumes:
|
|
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
|
- ./certbot/conf:/etc/letsencrypt:ro
|
|
- ./certbot/www:/var/www/certbot:ro
|
|
- ./auth:/etc/nginx/auth:ro
|
|
depends_on:
|
|
- admin
|
|
# Note: nginx connects to eagle via EAGLE_ADDR (default: eagle-blue:40032)
|
|
# For blue-green deployments, update EAGLE_ADDR in .env before switching
|
|
restart: unless-stopped
|
|
logging:
|
|
driver: "json-file"
|
|
options:
|
|
max-size: "50m"
|
|
max-file: "3"
|
|
|
|
admin:
|
|
image: ${ADMIN_IMAGE:-registry.digitalocean.com/eagle0/admin-server:latest}
|
|
container_name: admin-server
|
|
command:
|
|
- "--eagle-addr"
|
|
- "${EAGLE_ADDR:-eagle-blue:40032}" # Can be switched for blue-green
|
|
- "--auth-addr"
|
|
- "auth:40033"
|
|
- "--jfr-sidecar-addr"
|
|
- "${JFR_SIDECAR_ADDR:-jfr-sidecar:8081}" # Can be switched for blue-green
|
|
- "--http-port"
|
|
- "8080"
|
|
environment:
|
|
# Secret for CI to authenticate client update notifications
|
|
NOTIFY_SECRET: "${NOTIFY_SECRET:-}"
|
|
# No external port - accessed via nginx at admin.eagle0.net
|
|
depends_on:
|
|
- auth
|
|
# Note: admin connects to eagle via EAGLE_ADDR and jfr-sidecar via JFR_SIDECAR_ADDR
|
|
# For blue-green deployments, set both in .env before switching
|
|
restart: unless-stopped
|
|
logging:
|
|
driver: "json-file"
|
|
options:
|
|
max-size: "50m"
|
|
max-file: "3"
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "wget -q --spider http://localhost:8080/health || exit 1"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 10s
|
|
|
|
jfr-sidecar:
|
|
image: ${JFR_SIDECAR_IMAGE:-registry.digitalocean.com/eagle0/jfr-sidecar:latest}
|
|
container_name: jfr-sidecar
|
|
# Share PID namespace with Eagle to access its JVM via jcmd
|
|
# For blue-green: use JFR_SIDECAR_ADDR=jfr-sidecar-green:8081 when green is active
|
|
pid: "service:eagle-blue"
|
|
volumes:
|
|
- jvm-tmp:/tmp # Shared with Eagle for JVM attach socket files
|
|
depends_on:
|
|
- eagle-blue
|
|
restart: unless-stopped
|
|
logging:
|
|
driver: "json-file"
|
|
options:
|
|
max-size: "10m"
|
|
max-file: "2"
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "wget -q --spider http://localhost:8081/health || exit 1"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 10s
|
|
|
|
jfr-sidecar-green:
|
|
image: ${JFR_SIDECAR_IMAGE:-registry.digitalocean.com/eagle0/jfr-sidecar:latest}
|
|
container_name: jfr-sidecar-green
|
|
profiles: ["blue-green"] # Only started during blue-green deployment
|
|
# Share PID namespace with Eagle green instance
|
|
pid: "service:eagle-green"
|
|
volumes:
|
|
- jvm-tmp:/tmp # Shared with Eagle for JVM attach socket files
|
|
depends_on:
|
|
- eagle-green
|
|
restart: "no" # Don't auto-restart during deployment
|
|
logging:
|
|
driver: "json-file"
|
|
options:
|
|
max-size: "10m"
|
|
max-file: "2"
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "wget -q --spider http://localhost:8081/health || exit 1"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 10s
|
|
|
|
certbot:
|
|
image: certbot/certbot
|
|
container_name: certbot
|
|
volumes:
|
|
- ./certbot/conf:/etc/letsencrypt
|
|
- ./certbot/www:/var/www/certbot
|
|
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
|
|
|
|
volumes:
|
|
jvm-tmp:
|
|
# Shared /tmp for JVM attach socket files between Eagle and jfr-sidecar
|
|
jwt-keys:
|
|
# Shared JWT RSA keys between Eagle and auth service
|
|
|
|
networks:
|
|
default:
|
|
driver: bridge
|
|
enable_ipv6: true
|
|
ipam:
|
|
config:
|
|
- subnet: 172.28.0.0/16
|
|
- subnet: fd00:dead:beef::/48
|