Commit Graph
112 Commits
Author SHA1 Message Date
173dfbafd9 Use run-specific keychain names to fix parallel signing conflicts (#5665)
When multiple runners on the same machine try to sign simultaneously,
they were conflicting on the shared keychain names (build.keychain,
ios-build.keychain). This caused errSecInternalComponent errors.

Changes:
- mac_build.yml: Use build-${run_id}.keychain
- ios_testflight.yml: Use ios-build-${run_id}.keychain
- codesign_mac_app.sh: Support KEYCHAIN_NAME env var with fallback

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 08:25:09 -08:00
c0eff139bf Add eagle-logs helper for tailing active instance logs (#5571)
Usage:
  eagle-logs           # Tail logs (follow mode)
  eagle-logs -n 100    # Show last 100 lines and follow
  eagle-logs --no-follow -n 50   # Last 50 lines without following

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 11:00:16 -08:00
1ac7477597 Fix eagle-exec deployment and simplify implementation (#5569)
- Remove sudo symlink creation (deploy user lacks passwordless sudo)
- Simplify eagle-exec to read from /opt/eagle0/.active-instance file
- Update deploy-blue-green.sh to write active instance to file
- Keep fallback to checking running containers if file doesn't exist

Users can add their own alias:
  alias eagle-exec='/opt/eagle0/scripts/eagle-exec.sh'

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 10:47:16 -08:00
84c1bf61b0 Add eagle-exec helper for blue-green deployments (#5567)
Adds a helper script that automatically runs docker exec against the
active Eagle instance (blue or green), determined by checking nginx
config.

Usage:
  eagle-exec printenv GEMINI_API_KEY
  eagle-exec jcmd 1 VM.flags
  eagle-exec sh

The script is deployed to /opt/eagle0/scripts/ and symlinked to
/usr/local/bin/eagle-exec for easy access.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 10:31:53 -08:00
473f83e5f6 Complete deproto migration: delete plan and update linter (#5529)
The library/ code is now fully protoless (zero Scala proto dependencies).
Transitive deps through C++/Go build tools (map generation) are expected.

Changes:
- Delete docs/DEPROTO_PLAN.md - migration complete
- Delete scripts/build_deps_baseline.txt - no longer needed
- Update check_build_deps.sh to enforce zero Scala proto deps in library/
  (C++/Go proto deps are allowed as they're build-time tools)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 16:26:57 -08:00
c41306912d Improve Mac notarization stapling reliability (#5479)
- Verify code signature before attempting to staple (catches transfer corruption)
- Increase retry attempts from 5 to 10
- Increase wait between retries from 10s to 30s (total wait up to 5 minutes)
- Capture and display stapler error output for better debugging
- Show signature details if verification fails

This addresses intermittent stapling failures due to Apple CloudKit
propagation delays after notarization completes.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 22:06:03 -08:00
c6e1fe10ea Remove stale proto exports from province:province (#5478)
* Remove stale proto exports from province:province

The province target was exporting province_event_scala_proto,
army_scala_proto, and unaffiliated_hero_scala_proto but no downstream
targets actually needed these through that path. Removing these stale
exports reduces library/ proto deps from 41 to 26.

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

* Fix unused import and stale test dependency

Remove unused LlmRequestT import and stale unaffiliated_hero_scala_proto
dependency that were exposed by removing the province proto exports.

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

* Fix test build failures from stale proto dependencies

- Remove unused ProtoMatchers from tests that don't use it
- Add missing unaffiliated_hero_quest_scala_proto dep to quest_fulfillment_utils_test
- Remove stale unaffiliated_hero_quest_scala_proto dep from check_for_fulfilled_quests_action_test
- Keep ProtoMatchers in ResolveBattleActionTest (uses equalProto matcher)

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 22:05:24 -08:00
6b92d14d1d Remove stale eagle_unit dependencies from library/ (#5477)
Remove unused eagle_unit deps from resolved_eagle_unit and
province_conquered_action. These files use ResolvedEagleUnit (which
uses Scala types), not EagleUnit (which uses proto types).

This breaks the transitive proto dependency chain, reducing library/
proto deps from 74 to 41.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 21:25:18 -08:00
5599d07247 Replace EndGameCondition proto with Scala sealed trait (#5473)
Migrate ResolvedShardokPlayer to use native Scala EndGameCondition
instead of the proto type. This removes 1 transitive proto dependency
from library/ (75 → 74).

Changes:
- Add Scala EndGameCondition sealed trait with Victory, AllyVictory,
  Draw, and Loss cases
- Add DrawType enum for draw conditions
- Add EndGameConditionConverter in ShardokBattleConverter
- Update ResolvedShardokPlayer to use Scala EndGameCondition
- Move proto conversion to ShardokInterfaceGrpcClient boundary
- Update ResolveBattleAction to use Scala EndGameCondition API
- Update ResolveBattleActionTest to construct Scala EndGameCondition

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 17:06:12 -08:00
6e7611d547 Move ShardokBattle boundary conversion to ShardokInterfaceGrpcClient (#5471)
* Remove stale proto_converter deps from library/ BUILD files

Library code should not depend on proto_converters - those belong at
the service layer boundary. Removed 9 stale proto_converter deps that
were no longer used by any Scala code.

The only remaining proto_converter dep is shardok_battle_converter,
which is actually used by ResolveBattleAction.scala (to be fixed in
a follow-up PR).

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

* Move ShardokBattle boundary conversion to ShardokInterfaceGrpcClient

Previously, BattleResolution contained a proto ShardokBattle, requiring
ResolveBattleAction in library/ to depend on ShardokBattleConverter.
This violated the boundary principle where proto conversions should
happen at the service layer, not in library code.

This change:
- Updates BattleResolution.battle to use Scala ShardokBattle
- Moves the proto-to-Scala conversion into ShardokInterfaceGrpcClient
- Removes ShardokBattleConverter dependency from library/ code
- Updates tests to use the scalaBattle() helper for conversion

This completely removes proto_converters dependencies from library/
(the last one was shardok_battle_converter).

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

* Add linter check for library/ proto_converters boundary

Adds Rule 3 to check_build_deps.sh that verifies library/ code does not
depend on proto_converters. Proto conversions should happen at service
boundaries (ShardokInterfaceGrpcClient, EagleServiceImpl, etc.), not in
library code.

Also updates baseline from 167 to 75 proto deps reflecting recent cleanup.

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 06:27:57 -08:00
fb5ca109b7 Update bundle identifier to net.eagle0.eagle0 (#5455)
Change from com.Shardok-Games.eagle0 to net.eagle0.eagle0 to match
the project's domain (net.eagle0).

Updated in:
- Unity ProjectSettings (applicationIdentifier for Standalone)
- inject_sparkle.sh (CFBundleURLName for URL scheme)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 16:00:31 -08:00
916501cbf8 Replace Hostility proto with Scala Hostility enum (#5438)
* Replace Hostility proto with Scala Hostility enum

This removes direct proto imports from library/ for the Hostility type:

- FactionUtils.hostilityStatus now returns Scala Hostility
- ArmyStats.hostility now uses Scala Hostility type
- AvailableAttackDecisionCommandFactory no longer imports proto
- AvailableFreeForAllDecisionCommandFactory no longer imports proto
- AttackDecisionCommandChooser uses Hostility.Enemy instead of proto
- BattleFilter no longer needs protoToScalaHostility converter
- AvailableCommandConverter uses HostilityConverter.toProto

This completes Phase 8b of the deproto migration, reducing direct
proto imports in library/ from 8 files to 4 (LLM prompt generators).

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

* Fix AvailableCommandConverterTest to use Scala Hostility enum

Update test to use net.eagle0.eagle.model.state.Hostility.Self
instead of proto net.eagle0.common.hostility.Hostility.SELF_HOSTILITY.

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 07:35:38 -08:00
7ab8fc796b Add BUILD.bazel dependency linting (#5437)
* Add BUILD.bazel dependency linting

Adds tooling to enforce architectural boundaries in the codebase:

1. Shell script `scripts/check_build_deps.sh` with multiple modes:
   - Default: runs all checks and shows dependency counts
   - --ci: fails only on hard violations (src/main depends on src/test)
   - --strict: also fails if proto deps in library/ increase from baseline
   - --count: just show current dependency counts
   - --update-baseline: update the baseline file for tracking

2. Bazel test `//:build_deps_test` for integration with test suite

3. Baseline file `scripts/build_deps_baseline.txt` tracking proto dep count

Current enforced rules:
- src/main must not depend on src/test (enforced now)
- library/ proto dependencies tracked (167 currently, deproto in progress)

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

* Update DEPROTO_PLAN.md with accurate proto import inventory

The previous status was inaccurate. This update:

- Corrects summary table: Availability, Command Choice Helpers,
  and other utilities still have Hostility proto imports
- Adds Phase 8b documenting Hostility proto migration (4 files)
- Updates detailed inventory showing actual 13 files with direct
  proto imports (vs 167 transitive deps from bazel query)
- Corrects success criteria checkboxes to reflect actual status
- Documents the grep command used to verify imports

The 167 proto deps from bazel query are transitive dependencies.
Only 13 files have direct proto imports:
- 5 boundary files (expected to keep proto)
- 4 files using Hostility proto (Phase 8b)
- 4 LLM prompt generator files (Phase 8)

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 07:28:58 -08:00
0e9a06cc88 Fix deploy script to not cascade to auth container (#5426)
Add --no-deps to nginx and admin docker compose commands to prevent
them from cascading to auth. The auth container has secrets (like
TWITCH_CLIENT_ID) that are only available in auth_build.yml, not in
docker_build.yml. Without --no-deps, docker compose would recreate
auth with blank env vars when it detected config changes.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 21:44:38 -08:00
6e76ff0fbc Use commit-count based versioning for Mac builds (#5392)
Change version scheme from git describe (which picked up unrelated tags
like busybox-1.35.0) to commit-count based versions like 1.0.9548.

This gives automatic, always-incrementing version numbers that Sparkle
can properly compare for updates.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 09:17:59 -08:00
d6704413d3 Fix Sparkle codesigning for XPC services (#5393)
- Use ditto instead of cp -R to copy Sparkle.framework (preserves bundle structure)
- Skip individual signing of Sparkle's internal XPC services, apps, and executables
- Use --deep flag when signing Sparkle.framework to handle its internal components
- Verify cached Sparkle.framework has proper symlink structure, re-download if corrupted

Fixes "bundle format unrecognized" error during codesigning.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 09:04:39 -08:00
7ad5ebdb56 Re-enable Sparkle auto-update integration for Mac builds (#5318)
* Add native Sparkle plugin to enable Mac auto-updates

The Sparkle framework was being injected into the app bundle, but
nothing was initializing it. This adds:

- Native Objective-C plugin (SparklePlugin.m) that initializes
  SPUStandardUpdaterController at runtime
- C# wrapper (SparkleUpdater.cs) for Unity to call the native plugin
- SparkleInitializer.cs uses RuntimeInitializeOnLoadMethod to
  automatically initialize Sparkle at app startup
- Build script to compile the plugin as a universal binary

The plugin is weak-linked against Sparkle.framework, which is injected
separately by inject_sparkle.sh during the build process.

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

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

* Convert Sparkle plugin build from clang to Bazel

- Add Sparkle framework as http_archive dependency in MODULE.bazel
- Add BUILD.sparkle to import the framework
- Add BUILD.bazel for SparklePlugin using macos_bundle rule
- Update build_sparkle_plugin.sh to use Bazel instead of direct clang
- Register Apple CC toolchain extension

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

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

* Fix symbol exports for SparklePlugin native library

The C functions need to be exported with visibility("default") and
explicit linker flags for Unity P/Invoke to find them. Without this,
the bundle binary had no exported symbols.

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

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

* Re-enable Sparkle auto-update integration for Mac builds

Restores Sparkle integration that was temporarily removed in #5317:
- Restore inject_sparkle.sh script
- Add Sparkle injection step to mac_build.yml
- Re-enable Sparkle signing and appcast updates in deploy step

Combined with native SparklePlugin that initializes Sparkle at runtime.

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

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

* Build SparklePlugin.bundle before Unity build

The SparklePlugin.bundle.meta file tells Unity to include the plugin,
but the actual bundle needs to be built by Bazel first.

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

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

* Convert SparklePlugin Info.plist to XML format

Unity's build system requires Info.plist files in XML format,
but Bazel outputs them in binary plist format.

🤖 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>
2026-01-14 05:28:58 -08:00
92efdbae8b Remove Sparkle auto-update integration from Mac builds (#5317)
* Remove Sparkle auto-update integration from Mac builds

Temporarily removing Sparkle integration to get Mac builds working:
- Remove inject_sparkle.sh script and workflow step
- Make mac_build_handler's Sparkle private key optional
- Skip Sparkle signing and appcast updates when no key provided

This allows Mac builds to complete without Sparkle. Auto-updates can
be re-enabled later once the basic build pipeline is stable.

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

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

* Use clean checkout to remove stale SparklePlugin.bundle

The runner had a leftover SparklePlugin.bundle from previous builds
which was causing Unity to fail when trying to process it.

🤖 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>
2026-01-13 22:28:17 -08:00
c6c31cd28e Remove redundant S3 backup from deploy script (#5296)
Eagle server already persists to S3 on every game save via CompoundPersister
when S3Credentials.isEnabled. The deploy script's s3cmd backup was redundant
and caused warnings when s3cmd wasn't installed.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 15:36:47 -08:00
e96c0704ed Split Unity builds across dedicated runners with async notarization (#5291)
Add dedicated self-hosted runners for parallel Unity builds:
- unity-mac: Mac Unity builds and deployment
- unity-windows: Windows Unity builds (cross-compiled on Mac)
- notarize: Notarization waiting (lightweight, doesn't block builds)

Split mac_build.yml into 3 jobs:
1. build-and-sign (unity-mac): Build, sign, submit to Apple
2. wait-notarization (notarize): Wait for Apple, staple ticket
3. deploy (unity-mac): Deploy notarized app

This allows:
- Mac and Windows Unity builds to run in parallel
- Notarization waiting doesn't block other builds
- All runners share the same Mac Mini hardware

New scripts:
- notarize_submit.sh: Submit without waiting, output submission ID
- notarize_wait.sh: Wait for submission ID, staple ticket

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 13:29:27 -08:00
f874231e04 Fix notarize script cleanup to be idempotent (#5288)
Use rm -f instead of rm when cleaning up the zip file after notarization.
The zip may already be deleted if a previous step failed and was retried,
causing the script to fail even when notarization actually succeeded.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 11:46:18 -08:00
040b0cf580 Fix user wait window metric to measure actual user impact (#5275)
* Auto-invalidate game cache when flush marker is updated

During warmup, the staging server may cache stale game data that was
loaded before the active server flushed. Instead of exposing an RPC
for cache invalidation (which leaks internal state), the server now
automatically detects when the flush marker is updated and invalidates
any cached games.

Changes:
- GamesManager: Added `invalidateCacheIfFlushMarkerUpdated()` that
  checks the flush marker's modification time and clears the cache
  if it's been updated since the last check
- Called before checking if a game is in cache, so stale data is
  cleared before any attempt to use it
- Removed the InvalidateGameCache RPC (no longer needed)

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

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

* Fix user wait window metric to measure actual user impact

The max user wait window was measuring from when nginx switching
started (before recreation) instead of when it completed. This
inflated the metric by ~12s because nginx recreation time was included.

Users can only experience a wait AFTER nginx starts routing to the
staging server, so we now measure from nginx_switch_end (after
recreation completes) to flush_end.

🤖 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>
2026-01-13 08:17:24 -08:00
3ae3f6ba16 Auto-invalidate game cache when flush marker is updated (#5273)
During warmup, the staging server may cache stale game data that was
loaded before the active server flushed. Instead of exposing an RPC
for cache invalidation (which leaks internal state), the server now
automatically detects when the flush marker is updated and invalidates
any cached games.

Changes:
- GamesManager: Added `invalidateCacheIfFlushMarkerUpdated()` that
  checks the flush marker's modification time and clears the cache
  if it's been updated since the last check
- Called before checking if a game is in cache, so stale data is
  cleared before any attempt to use it
- Removed the InvalidateGameCache RPC (no longer needed)

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 08:04:56 -08:00
e7745cc9ec Fix deployment script permission denied on marker files (#5272)
The saves directory is owned by root (created by Docker) but the deploy
script runs as the deploy user. Use docker exec to create marker files
from inside a running container that has the saves directory mounted.

- create_deployment_marker: uses active container (running before staging starts)
- create_flush_marker: uses staging container (running after active stops)
- cleanup_markers_on_failure: uses active container (still running on failure)
- remove_stale_deployment_marker: finds any running eagle container

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 07:02:05 -08:00
8f3bee853f Add flush marker coordination for zero-downtime blue-green deploys (#5270)
* Fix nginx not picking up config changes during blue-green deploy

Root cause: `docker compose restart nginx` doesn't refresh bind-mounted
volume files. The nginx container keeps using its cached copy of
nginx.conf even after we update the host file with sed.

This caused nginx to keep trying to connect to the old (now deleted)
eagle instance, resulting in 502 errors after deployment.

Fix:
- Use `docker compose up -d --force-recreate nginx` instead of `restart`
  This recreates the container, forcing it to read the updated config
- Add verification that nginx picked up the correct backend
- Remove the useless pre-validation (it validated old config in old container)

The progression of failed fixes:
1. `nginx -s reload` - doesn't re-resolve Docker DNS
2. `docker compose restart` - doesn't refresh bind-mounted files
3. `docker compose up -d --force-recreate` - THIS WORKS

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

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

* Reorder deploy: switch nginx BEFORE stopping blue to avoid 502s

Previous order (caused 502 errors):
1. Stop blue → nginx still points to blue → 502!
2. Update nginx config
3. Recreate nginx → traffic finally works

New order (eliminates 502 window):
1. Update nginx config
2. Recreate nginx → traffic goes to green (blue still running)
3. Stop blue → flushes state to disk
4. 3-second pause for flush to complete

The stale data race condition is minimized by stopping blue immediately
after the nginx switch. Users reconnecting to green will lazy-load
fresh game data from disk (after blue has flushed).

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

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

* Add flush marker coordination for zero-downtime blue-green deploys

This ensures green never serves stale game data during deployments:

1. Deploy script creates .deployment_in_progress marker at start
2. Green's lazy-load waits for flush marker if deployment in progress
3. nginx switches to green BEFORE stopping blue (zero 502 downtime)
4. Blue stops, flushes state to disk
5. Deploy script creates .flush_complete marker
6. Green's waiting lazy-loads proceed with fresh disk data

Key changes:
- GamesManager.scala: Add waitForFlushMarker() that blocks lazy-load
  during deployment until flush marker appears (30s timeout)
- GamesManager.scala: Auto-clean stale markers >5 minutes old
- GamesManager.scala: Add deployment ID correlation in logs [DEPLOY:xxx]
- GamesManager.scala: Report flush marker timeouts to Sentry
- deploy-blue-green.sh: Reorder to switch nginx BEFORE stopping blue
- deploy-blue-green.sh: Add marker file coordination with deployment ID
- deploy-blue-green.sh: Add timing metrics (flush duration, user wait window)
- nginx.conf: Keep variable-based routing (Docker DNS only resolves
  running containers, so upstream+backup doesn't work)

Monitoring and observability:
- All deployment-related logs tagged with [DEPLOY:timestamp] for correlation
- Wait duration logged for each lazy-load during deployment
- Flush marker timeouts reported to Sentry for alerting
- Deploy script logs total duration, flush duration, max user wait window

🤖 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>
2026-01-13 06:42:59 -08:00
d0d84e98e0 Use nginx restart instead of reload for blue-green switch (#5260)
nginx reload doesn't always force DNS re-resolution in Docker. During
blue-green deployment, after updating nginx.conf to point to the new
instance (e.g., eagle-green:40032), nginx -s reload would sometimes
keep trying to connect to the old (now removed) container, causing
502 errors.

A full restart ensures nginx picks up the new upstream correctly.
The ~1-2 second restart time is acceptable for deployment reliability.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 19:52:56 -08:00
0e3f1bcb87 Increase warmup timeouts for cold JVM startup (#5259)
The warmup tool was timing out during CreateGame because the per-step
timeout was only 30 seconds. On a cold JVM (freshly started Eagle
instance during blue-green deployment), CreateGame can take longer
than 30 seconds due to:
- JIT compilation not yet warmed up
- First-time class loading
- Game initialization including Shardok communication

Changes:
- Increase per-operation timeout from 30s to 90s
- Increase overall warmup timeout from 60s to 300s (5 minutes)

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 19:51:03 -08:00
0ff7d51f2c Fix deployment issues: missing volumes, redundant operations, validation (#5254)
1. Add missing jfr and jvm-tmp volumes to eagle-green
   - Without these, JFR sidecar can't attach to the JVM when green is active
   - JFR recordings wouldn't work either

2. Remove redundant sync_config_files from deploy script
   - CI already copies config files via scp before running deploy
   - Fetching from GitHub main could cause version mismatches
   - Eliminates unnecessary network calls during deployment

3. Skip image pull if already present locally
   - CI already pulls images before running deploy script
   - Saves time during CI deployments
   - Manual deployments still pull if needed

4. Add nginx config validation before reload
   - Run nginx -t before nginx -s reload
   - Rollback to backup config if validation fails
   - Prevents broken config from taking down nginx

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 19:42:02 -08:00
6fb14ef9af Fix deployment downtime and config sync issues (#5253)
Problems fixed:
1. CI was recreating admin BEFORE blue-green, causing stale .env
2. CI was restarting nginx AFTER blue-green (double restart)
3. Deploy script used slow nginx restart instead of reload
4. Cleanup was blocking the critical path

Changes:
- CI: Only restart shardok before blue-green, let script handle rest
- CI: Remove duplicate nginx restart and fallback path
- Deploy: Use nginx reload (faster) with restart fallback
- Deploy: Update .env before restarting services
- Deploy: Run container cleanup in background

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 19:33:53 -08:00
55ad8a8278 Admin console shows all games, auto-install s3cmd, restart nginx properly (#5238)
* Fix: Move S3 backup to BEFORE stopping old server

Critical fix: The old server may wipe games.e0es on shutdown if it
doesn't have the save() merge fix. The backup must happen BEFORE
stopping to capture valid data.

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

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

* Admin console shows all games, auto-install s3cmd, restart nginx properly

1. Admin console now shows all games from games.e0es, not just loaded ones:
   - Added getAllRunningGamesSummary() to GamesManager
   - Updated getRunningGames() to include unloaded games with "[Not loaded]" status
   - Clicking into a game triggers lazy loading via getGameHistory()

2. Deploy script improvements:
   - Auto-install s3cmd if not present (via pip or apt)
   - Auto-configure s3cmd for DigitalOcean Spaces from .env
   - Use nginx restart instead of reload to ensure all workers pick up new config

🤖 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>
2026-01-12 17:04:33 -08:00
c24509e0b2 Fix: Move S3 backup to BEFORE stopping old server (#5237)
* 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>

* Fix: Move S3 backup to BEFORE stopping old server

Critical fix: The old server may wipe games.e0es on shutdown if it
doesn't have the save() merge fix. The backup must happen BEFORE
stopping to capture valid data.

🤖 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>
2026-01-12 16:40:31 -08:00
65a9cf1f97 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>
2026-01-12 16:28:56 -08:00
1d98df6cbc Remove obsolete ReloadGames RPC call from deploy script (#5232)
With lazy game loading (merged in #5223), games are loaded on-demand
when users reconnect after nginx switches traffic. No explicit reload
call is needed - the new server reads fresh state from storage.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 13:58:22 -08:00
353eb3907c Sign manifest with Ed25519 at build time (#5226)
Add optional Ed25519 signing to the manifest_manager. When a signing key
is provided, the manifest is signed and the signature is prepended as
a header comment that clients can verify.

Changes:
- manifest_manager: Accept optional private key file as 3rd argument
- manifest_manager: Sign manifest content and prepend signature line
- installer_build.yml: Pass MANIFEST_SIGNING_KEY to manifest_manager
- unity_build.yml: Pass MANIFEST_SIGNING_KEY to manifest_manager
- Add generate_manifest_keys.go script to create key pairs

The signature line format is: # signature=<base64-encoded-ed25519-signature>

To enable signing:
1. Run: go run scripts/generate_manifest_keys.go
2. Add the private key as GitHub secret MANIFEST_SIGNING_KEY
3. Embed the public key in the installer for verification (PR 4)

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 12:08:54 -08:00
821e3f1a4a Add retry loop for stapler after notarization (#5222)
Apple's CloudKit can have a brief delay after notarization completes
before the ticket is available for stapling. This adds a retry loop
with 10-second delays, up to 5 attempts.

Error was:
  CloudKit query for eagle0.app failed due to "Record not found".
  The staple and validate action failed! Error 65.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 10:13:43 -08:00
adminandGitHub 2224e78a36 Improve Sparkle sign_update error reporting (#5209)
Improves Sparkle sign_update error reporting by capturing stderr, and allows full signing/notarization/deploy pipeline on feature branches via workflow_dispatch.
2026-01-12 08:36:41 -08:00
64e1e46e0a Fix crane installation and use crane in blue-green deploys (#5202)
1. Fix crane installation - don't try to mv crane to itself
   (tar extracts to cwd which is already /opt/eagle0)
2. Keep crane binary after deploy for blue-green script to use
3. Update blue-green deploy to use crane instead of docker pull
   (fixes OCI/Docker digest mismatch issue)

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 23:09:04 -08:00
9cc9a65e4a Fix codesign script to sign all Sparkle framework components (#5195)
* Fix codesign script to sign all Sparkle framework components

Sign XPC services, nested apps (Updater.app), and standalone
executables (Autoupdate) before signing the framework itself.
Apple notarization requires all nested binaries to be signed
with Developer ID certificate and secure timestamp.

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

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

* Add missing path triggers for pull_request in Mac build workflow

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

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

* Re-add rules_swift for Mac GoDice plugin build

The DarwinGodiceBundle requires rules_swift to build.
This was inadvertently removed in #5194.

🤖 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>
2026-01-11 22:24:55 -08:00
025563b607 Add retry logic to docker pull in blue-green deploy (#5197)
Handles intermittent Docker registry digest mismatch errors like:
"failed commit on ref: unexpected commit digest"

This is a known Docker/containerd issue that can occur due to:
- Registry caching
- Network/proxy issues
- Race conditions during push

Now retries up to 3 times with 5 second delays.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 22:23:18 -08:00
649e80c4ac Add Mac build pipeline with Sparkle auto-updates (#5193)
- Unity Mac build scripts (build_mac.sh, build_unity_mac.sh)
- Code signing with Developer ID certificate
- Apple notarization for Gatekeeper compliance
- Sparkle framework injection for delta auto-updates
- Go build handler for S3 upload and appcast.xml generation
- Update InvitationCodeManager.cs for Mac platform paths
- GitHub Actions workflow triggered on main branch pushes

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 14:22:52 -08:00
9cca922b70 Add blue-green deployment infrastructure for Eagle server (#5162)
* 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>
2026-01-10 22:13:15 -08:00
4f39e16b80 Add missing action result types to proto and remove generator hook (#5082)
- Add HERO_STAT_GAINED (151) and PROFESSION_GAINED (152) to
  action_result_type.proto to maintain parity with the Scala enum
- Remove update-action-result-types pre-commit hook and script
  (no longer needed with simplified Scala enum approach)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 19:14:16 -08:00
adminandGitHub fbb04270a5 Add production deployment pipeline (#4790)
* Next steps for productionization

* Run deploy job on self-hosted runner for secure SSH

* Temporarily disable production environment to debug runner

* Use ubuntu-latest for deploy job

* Add remote_tags to oci_push for latest tag
2025-12-24 06:42:26 -08:00
97df984189 Add BLUF section to changelog emails (#4777)
- Add a "Bottom Line Up Front" section after the title with a short prose
  paragraph highlighting the most important changes and what to look for
  when testing
- Wrap HTML output in proper document with UTF-8 charset declaration to
  fix Unicode character rendering (em-dashes, etc.)

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-23 07:30:20 -08:00
6c771df84f Add PR links section to changelog emails (#4776)
Update the Claude prompt to generate a "PR Details" section after the synopsis,
with the same thematic groupings but listing actual PR numbers, titles, and
clickable GitHub links.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-23 07:23:32 -08:00
7e40420fe1 Update changelog script to use Fastmail JMAP API for HTML emails (#4774)
- Switch from Mac Mail AppleScript to Fastmail JMAP API
- Generate HTML synopsis instead of plain text for better formatting
- Auto-fetch account ID, identity ID, and drafts mailbox from API
- Support config files in ~/.config/eagle0/:
  - fastmail_token: API token (required)
  - changelog_recipient: Email recipients, one per line (optional)
- Support multiple recipients (one email address per line, # for comments)
- Fall back to FASTMAIL_API_TOKEN environment variable for token
- Only require token when not in --dry-run mode

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-23 07:16:22 -08:00
7c49746c37 Add weekly changelog generator script (#4771)
Creates scripts/generate_changelog.sh that:
- Fetches merged PRs since last run (tracked via git tag) or previous Friday 4pm
- Uses Claude CLI to generate a themed synopsis of changes
- Opens an email draft in Mac Mail with the synopsis
- Updates the changelog-last-run tag for next run

Usage: ./scripts/generate_changelog.sh [--dry-run]

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-22 22:24:52 -08:00
1a751cea6d Improve gazelle pre-commit hook to fail if BUILD files are modified (#4640)
The previous hook ran gazelle but didn't check if it modified any files.
This meant commits could go through with non-canonical BUILD files, causing
gazelle_test to fail in CI.

The new wrapper script:
1. Runs gazelle
2. Checks if any BUILD files were modified
3. Fails with a helpful message if they were, instructing the user to stage changes

Also adds a Pre-Commit Checklist section to CLAUDE.md documenting this behavior.

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

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-06 08:58:07 -08:00
209fab050b Strip CRLF line endings from Google Sheets TSV exports (#4577)
Google Sheets exports TSV files with Windows-style CRLF line endings.
This causes spurious git diffs when the download scripts are run.
Pipe curl output through `tr -d '\r'` to strip carriage returns.

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

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-28 11:38:51 -08:00
adminandGitHub 41caa802df update name words and settings (#4572)
* update name words and settings

* add a warning

* gazelle

* run gazelle

* fix test
2025-11-27 09:57:55 -08:00