CRITICAL: Fix save() to merge with existing games.e0es (#5233)

* Fix critical bug: save() now merges with existing games.e0es

CRITICAL BUG FIX: With lazy loading, gameControllerInfos only contains
games that have been loaded into memory. The old save() would overwrite
games.e0es with only the loaded games, losing all unloaded games.

This caused complete data loss of user-to-game mappings when:
1. New server started (empty gameControllerInfos)
2. Any operation triggered save()
3. games.e0es was overwritten with empty data

Fix: save() now reads existing games.e0es first and merges:
- Unloaded games are preserved from disk
- Loaded games use fresh in-memory state

Also adds logging to diagnose games.e0es read failures.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add S3 backup of games.e0es during blue-green deployment

After stopping the old container (which flushes state to storage),
create a timestamped backup in S3 before switching nginx traffic.
This provides a recovery point if something goes wrong during deployment.

Backups are stored at: s3://eagle0/eagle/save/backups/games.e0es.<timestamp>

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix blue-green deployment dependencies and auth routing

1. docker-compose.prod.yml:
   - Remove eagle-blue from nginx depends_on (use EAGLE_ADDR variable instead)
   - Remove eagle-blue from admin depends_on (use EAGLE_ADDR variable instead)
   - Add TODO note about jfr-sidecar limitation during green deployments

2. nginx/nginx.conf:
   - Fix auth.Auth location on port 443 to route to auth:40033 instead of
     eagle_backend. This was causing auth failures when clients connected
     via the main HTTPS port.

These changes allow blue-green deployments to work without hard
dependencies that cause docker-compose to recreate stopped containers.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add jfr-sidecar-green for blue-green JFR profiling support

- Add jfr-sidecar-green service that shares PID namespace with eagle-green
- Make admin's jfr-sidecar address configurable via JFR_SIDECAR_ADDR env var
- Update deploy script to:
  - Start appropriate jfr-sidecar with each eagle instance
  - Update .env with EAGLE_ADDR and JFR_SIDECAR_ADDR after switching
  - Restart admin service to pick up new addresses
  - Clean up old jfr-sidecar when removing old eagle instance

This ensures the JFR button in admin console works regardless of
whether blue or green is the active instance.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Sync config files from GitHub at start of deployment

Downloads docker-compose.prod.yml and nginx.conf from the main branch
before starting deployment. This ensures new services (like jfr-sidecar-green)
are available when the deploy script runs.

- Preserves the current active instance in nginx.conf
- Creates .bak backups before overwriting
- Continues with existing files if GitHub fetch fails

🤖 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>
This commit is contained in:
2026-01-12 16:28:56 -08:00
committed by GitHub
co-authored by Claude Opus 4.5
parent dd9d6d046d
commit 65a9cf1f97
5 changed files with 163 additions and 38 deletions
+75 -14
View File
@@ -36,6 +36,38 @@ log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# GitHub raw URL base for fetching config files
GITHUB_RAW_BASE="https://raw.githubusercontent.com/nolen777/eagle0/main"
# Sync config files from GitHub to ensure we have the latest versions
sync_config_files() {
log_info "Syncing config files from GitHub..."
# Backup and update docker-compose.prod.yml
if curl -fsSL "${GITHUB_RAW_BASE}/docker-compose.prod.yml" -o "${COMPOSE_FILE}.new"; then
cp "${COMPOSE_FILE}" "${COMPOSE_FILE}.bak"
mv "${COMPOSE_FILE}.new" "${COMPOSE_FILE}"
log_info "Updated docker-compose.prod.yml"
else
log_warn "Failed to fetch docker-compose.prod.yml, using existing"
fi
# Backup and update nginx.conf, preserving current active instance
if curl -fsSL "${GITHUB_RAW_BASE}/nginx/nginx.conf" -o "${NGINX_CONF}.new"; then
# Preserve the current eagle_backend setting (which instance is active)
local current_backend
current_backend=$(grep -o 'default "eagle-[a-z]*:40032"' "${NGINX_CONF}" | head -1 || echo 'default "eagle-blue:40032"')
cp "${NGINX_CONF}" "${NGINX_CONF}.bak"
# Update the new config with the current active instance
sed -i "s/default \"eagle-blue:40032\"/${current_backend}/g" "${NGINX_CONF}.new"
mv "${NGINX_CONF}.new" "${NGINX_CONF}"
log_info "Updated nginx.conf (preserved active: ${current_backend})"
else
log_warn "Failed to fetch nginx.conf, using existing"
fi
}
# Determine which instance is currently active
get_active_instance() {
if grep -q "eagle-blue:40032" "${NGINX_CONF}"; then
@@ -119,6 +151,9 @@ main() {
cd "${APP_DIR}"
# Sync config files from GitHub before deployment
sync_config_files
# Determine current active instance
local active=$(get_active_instance)
local staging
@@ -137,12 +172,12 @@ main() {
exit 1
fi
# Start staging instance with new image
# Start staging instance with new image (and its jfr-sidecar)
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
EAGLE_IMAGE_NEW="${new_image}" docker compose -f "${COMPOSE_FILE}" --profile blue-green up -d eagle-green jfr-sidecar-green
else
EAGLE_IMAGE="${new_image}" docker compose -f "${COMPOSE_FILE}" up -d eagle-blue
EAGLE_IMAGE="${new_image}" docker compose -f "${COMPOSE_FILE}" up -d eagle-blue jfr-sidecar
fi
# Wait for staging to be healthy
@@ -178,6 +213,21 @@ main() {
log_info "Stopping eagle-${active} (flushing state to storage)..."
docker compose -f "${COMPOSE_FILE}" stop "eagle-${active}"
# Backup games.e0es to S3 before switching traffic
# This creates a point-in-time backup we can recover from if something goes wrong
local backup_timestamp
backup_timestamp=$(date +%Y%m%d_%H%M%S)
log_info "Creating S3 backup of games.e0es (backup_${backup_timestamp})..."
if command -v s3cmd &> /dev/null; then
if s3cmd put "${APP_DIR}/saves/games.e0es" "s3://eagle0/eagle/save/backups/games.e0es.${backup_timestamp}" 2>/dev/null; then
log_info "Backup created: s3://eagle0/eagle/save/backups/games.e0es.${backup_timestamp}"
else
log_warn "S3 backup failed (s3cmd put), continuing deployment"
fi
else
log_warn "s3cmd not found, skipping S3 backup"
fi
# Note: No need to explicitly reload games - Eagle uses lazy loading.
# Games are loaded on-demand when users reconnect after nginx switches traffic.
# This ensures games are always loaded from the freshest storage state.
@@ -194,24 +244,35 @@ main() {
log_info "Reloading nginx..."
docker compose -f "${COMPOSE_FILE}" exec nginx nginx -s reload
# Clean up old instance
# Clean up old instance and its jfr-sidecar
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
if [ "$active" = "green" ]; then
docker compose -f "${COMPOSE_FILE}" rm -f "jfr-sidecar-green" 2>/dev/null || true
else
log_info "Green is now active. Consider switching to blue on next deployment."
docker compose -f "${COMPOSE_FILE}" rm -f "jfr-sidecar" 2>/dev/null || true
fi
# Update .env with new EAGLE_ADDR and JFR_SIDECAR_ADDR for admin service
local env_file="${APP_DIR}/.env"
if [ "$staging" = "green" ]; then
log_info "Updating .env for green instance..."
sed -i.bak '/^EAGLE_ADDR=/d; /^JFR_SIDECAR_ADDR=/d' "${env_file}" 2>/dev/null || true
echo "EAGLE_ADDR=eagle-green:40032" >> "${env_file}"
echo "JFR_SIDECAR_ADDR=jfr-sidecar-green:8081" >> "${env_file}"
else
log_info "Updating .env for blue instance..."
sed -i.bak '/^EAGLE_ADDR=/d; /^JFR_SIDECAR_ADDR=/d' "${env_file}" 2>/dev/null || true
echo "EAGLE_ADDR=eagle-blue:40032" >> "${env_file}"
echo "JFR_SIDECAR_ADDR=jfr-sidecar:8081" >> "${env_file}"
fi
# Restart admin to pick up new EAGLE_ADDR and JFR_SIDECAR_ADDR
log_info "Restarting admin service..."
docker compose -f "${COMPOSE_FILE}" up -d admin
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