mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 22:55:41 +00:00
* Add blue-green deployment infrastructure for Eagle server - Add ReloadGames RPC to eagle.proto for reloading game state from disk - Implement reloadAllGames() and flushToDisk() in GamesManager.scala - Add reloadGames() handler to EagleServiceImpl.scala - Update docker-compose.prod.yml with eagle-blue/green services - Update nginx.conf for switchable upstream - Create scripts/deploy-blue-green.sh for zero-downtime deployment - Create Go warmup tool (src/main/go/net/eagle0/warmup) that: - Uses bidirectional streaming to create test games - Posts Improve command and verifies ActionResults - Cleans up test game after warmup - Create scripts/warmup-eagle.sh wrapper that uses Go tool or falls back to grpcurl - Update docker_build.yml to build and deploy warmup binary 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add X-Warmup-User header support with localhost restriction - AuthorizationInterceptor: Accept X-Warmup-User header for warmup authentication - Only allow X-Warmup-User from localhost connections (security) - Go warmup tool: Send x-warmup-user metadata header - Add grpc/metadata dependency to warmup BUILD 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
208 lines
6.4 KiB
Bash
Executable File
208 lines
6.4 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# Blue-Green Deployment Script for Eagle Server
|
|
#
|
|
# This script performs a zero-downtime deployment by:
|
|
# 1. Starting the new version on a staging port (green)
|
|
# 2. Waiting for it to become healthy
|
|
# 3. Running warmup traffic to pre-heat the JIT
|
|
# 4. Stopping the old version (blue) - which flushes state to disk
|
|
# 5. Telling green to reload games from disk
|
|
# 6. Switching nginx to route traffic to green
|
|
# 7. Cleaning up
|
|
#
|
|
# Usage: ./deploy-blue-green.sh [NEW_IMAGE_TAG]
|
|
#
|
|
# Example:
|
|
# ./deploy-blue-green.sh latest
|
|
# ./deploy-blue-green.sh sha-abc123
|
|
#
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
APP_DIR="${APP_DIR:-/opt/eagle0}"
|
|
NGINX_CONF="${APP_DIR}/nginx/nginx.conf"
|
|
COMPOSE_FILE="${APP_DIR}/docker-compose.prod.yml"
|
|
WARMUP_SCRIPT="${SCRIPT_DIR}/warmup-eagle.sh"
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m' # No Color
|
|
|
|
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
|
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
|
|
|
# Determine which instance is currently active
|
|
get_active_instance() {
|
|
if grep -q "eagle-blue:40032" "${NGINX_CONF}"; then
|
|
echo "blue"
|
|
elif grep -q "eagle-green:40032" "${NGINX_CONF}"; then
|
|
echo "green"
|
|
else
|
|
log_error "Cannot determine active instance from nginx config"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Wait for a container to be healthy
|
|
wait_for_healthy() {
|
|
local container=$1
|
|
local max_attempts=${2:-60}
|
|
local attempt=1
|
|
|
|
log_info "Waiting for ${container} to become healthy..."
|
|
while [ $attempt -le $max_attempts ]; do
|
|
health=$(docker inspect --format='{{.State.Health.Status}}' "${container}" 2>/dev/null || echo "unknown")
|
|
if [ "$health" = "healthy" ]; then
|
|
log_info "${container} is healthy"
|
|
return 0
|
|
fi
|
|
echo -n "."
|
|
sleep 2
|
|
attempt=$((attempt + 1))
|
|
done
|
|
echo ""
|
|
log_error "${container} did not become healthy after $((max_attempts * 2)) seconds"
|
|
return 1
|
|
}
|
|
|
|
# Main deployment logic
|
|
main() {
|
|
local new_tag="${1:-latest}"
|
|
local registry="registry.digitalocean.com/eagle0/eagle-server"
|
|
local new_image="${registry}:${new_tag}"
|
|
|
|
log_info "Starting blue-green deployment"
|
|
log_info "New image: ${new_image}"
|
|
|
|
cd "${APP_DIR}"
|
|
|
|
# Determine current active instance
|
|
local active=$(get_active_instance)
|
|
local staging
|
|
if [ "$active" = "blue" ]; then
|
|
staging="green"
|
|
else
|
|
staging="blue"
|
|
fi
|
|
|
|
log_info "Active instance: eagle-${active}"
|
|
log_info "Staging instance: eagle-${staging}"
|
|
|
|
# Pull the new image
|
|
log_info "Pulling new image..."
|
|
docker pull "${new_image}"
|
|
|
|
# Start staging instance with new image
|
|
log_info "Starting eagle-${staging} with new image..."
|
|
if [ "$staging" = "green" ]; then
|
|
EAGLE_IMAGE_NEW="${new_image}" docker compose -f "${COMPOSE_FILE}" --profile blue-green up -d eagle-green
|
|
else
|
|
EAGLE_IMAGE="${new_image}" docker compose -f "${COMPOSE_FILE}" up -d eagle-blue
|
|
fi
|
|
|
|
# Wait for staging to be healthy
|
|
if ! wait_for_healthy "eagle-${staging}" 90; then
|
|
log_error "Staging instance failed health check, aborting deployment"
|
|
docker compose -f "${COMPOSE_FILE}" stop "eagle-${staging}"
|
|
docker compose -f "${COMPOSE_FILE}" rm -f "eagle-${staging}"
|
|
exit 1
|
|
fi
|
|
|
|
# Run warmup/smoke test
|
|
local staging_port
|
|
if [ "$staging" = "green" ]; then
|
|
staging_port=40034
|
|
else
|
|
staging_port=40032
|
|
fi
|
|
|
|
log_info "Running warmup against eagle-${staging}..."
|
|
if [ -x "${WARMUP_SCRIPT}" ]; then
|
|
if ! "${WARMUP_SCRIPT}" "localhost:${staging_port}"; then
|
|
log_error "Warmup/smoke test failed, aborting deployment"
|
|
docker compose -f "${COMPOSE_FILE}" stop "eagle-${staging}"
|
|
docker compose -f "${COMPOSE_FILE}" rm -f "eagle-${staging}"
|
|
exit 1
|
|
fi
|
|
else
|
|
log_warn "Warmup script not found at ${WARMUP_SCRIPT}, skipping warmup"
|
|
log_warn "JIT will be cold on first requests"
|
|
fi
|
|
|
|
# Stop the active instance (this flushes state to disk)
|
|
log_info "Stopping eagle-${active} (flushing state to disk)..."
|
|
docker compose -f "${COMPOSE_FILE}" stop "eagle-${active}"
|
|
|
|
# Tell staging to reload games from disk
|
|
log_info "Telling eagle-${staging} to reload games from disk..."
|
|
if command -v grpcurl &> /dev/null; then
|
|
grpcurl -plaintext -d '{}' "localhost:${staging_port}" net.eagle0.eagle.api.Eagle/ReloadGames || true
|
|
else
|
|
log_warn "grpcurl not installed, skipping game reload"
|
|
log_warn "New instance will use games loaded at startup"
|
|
fi
|
|
|
|
# Switch nginx upstream
|
|
log_info "Switching nginx upstream to eagle-${staging}..."
|
|
if [ "$staging" = "green" ]; then
|
|
sed -i.bak 's/eagle-blue:40032/eagle-green:40032/g' "${NGINX_CONF}"
|
|
else
|
|
sed -i.bak 's/eagle-green:40032/eagle-blue:40032/g' "${NGINX_CONF}"
|
|
fi
|
|
|
|
# Reload nginx
|
|
log_info "Reloading nginx..."
|
|
docker compose -f "${COMPOSE_FILE}" exec nginx nginx -s reload
|
|
|
|
# Clean up old instance
|
|
log_info "Removing old eagle-${active} container..."
|
|
docker compose -f "${COMPOSE_FILE}" rm -f "eagle-${active}"
|
|
|
|
# Update the staging instance's restart policy and image var
|
|
# For blue, we need to update EAGLE_IMAGE; for green, update EAGLE_IMAGE_NEW
|
|
if [ "$staging" = "blue" ]; then
|
|
log_info "Updating EAGLE_IMAGE to ${new_image} for future restarts"
|
|
# User should update their .env file
|
|
else
|
|
log_info "Green is now active. Consider switching to blue on next deployment."
|
|
fi
|
|
|
|
log_info "Deployment complete!"
|
|
log_info "Active instance: eagle-${staging}"
|
|
log_info ""
|
|
log_info "Note: Update your .env file with EAGLE_IMAGE=${new_image}"
|
|
log_info " if you want future 'docker compose up' to use this version."
|
|
}
|
|
|
|
# Check for required tools
|
|
check_requirements() {
|
|
if ! command -v docker &> /dev/null; then
|
|
log_error "docker is required but not installed"
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v sed &> /dev/null; then
|
|
log_error "sed is required but not installed"
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f "${NGINX_CONF}" ]; then
|
|
log_error "nginx config not found at ${NGINX_CONF}"
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f "${COMPOSE_FILE}" ]; then
|
|
log_error "docker-compose file not found at ${COMPOSE_FILE}"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Run
|
|
check_requirements
|
|
main "$@"
|