23 Commits
Author SHA1 Message Date
ecf7f78a06 Add accounts.eagle0.net user self-service portal (#5760)
Implements a user-facing account management portal at accounts.eagle0.net
that allows non-admin users to view their account information and delete
their account.

The same admin server backend handles both admin.eagle0.net (admin-only)
and accounts.eagle0.net (user self-service), distinguished by Host header.

Changes:
- nginx: Add server blocks for accounts.eagle0.net
- admin_server: Add host detection (isAccountsHost/isAdminHost)
- admin_server: Add requireUser auth wrapper (auth without admin check)
- admin_server: Add /my-account, /my-account/delete, /goodbye routes
- admin_server: Modify login flow to not require admin for accounts portal
- templates: Add my_account_layout.html, my_account.html, goodbye.html
- templates: Update login.html to show different text per portal

Post-deploy manual steps required:
1. DNS: Add A record for accounts.eagle0.net -> droplet IP
2. SSL: Run certbot on droplet for accounts.eagle0.net certificate

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 14:46:42 -08:00
3361dbf99e Add eagle0.net as server name alias (#5751)
Allows accessing the site via eagle0.net in addition to prod.eagle0.net.

Note: After deploying, run certbot to add eagle0.net to the SSL certificate:
  certbot --nginx -d prod.eagle0.net -d eagle0.net

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:23:01 -08:00
107d230d35 Add /credits route to nginx config (#5734)
Proxy the credits/attributions page from the auth service
so it's accessible at prod.eagle0.net/credits.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:17 -08:00
01aa3dbace Add /oauth/steam/callback to nginx config (#5410)
Steam OpenID 2.0 redirects back to /oauth/steam/callback after
authentication, but nginx was not configured to proxy this path to the
auth service, resulting in 404 errors.

Added the location block alongside the existing /oauth/callback and
/oauth/apple/callback blocks.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 13:49:34 -08:00
7cf2072dad Simplify client invitation flow and fix OAuth issues (#5353)
* Remove invitation code handling from Unity client

Account creation now happens on the web landing page, so the client
no longer needs to handle invitation codes.

Removed:
- InvitationCodeManager.cs (entire file)
- Invitation code parameter from AuthClient.GetOAuthUrlAsync()
- OAuthStatus.InvitationRequired handling in AuthClient
- OnInvitationRequired event and handlers in OAuthManager
- Invitation code panel UI fields in ConnectionHandler
- OnInvitationRequired and OnSubmitInvitationCodeClicked methods

The display name panel is retained for edge cases where a user
somehow doesn't have a display name set.

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

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

* Fix OAuth issues on landing page

- Fix Apple OAuth redirect_uri mismatch in token exchange
  The token exchange was using /oauth/callback but the auth request
  uses /oauth/apple/callback, causing redirect_uri mismatch error

- Add nginx route for /oauth/apple/callback
  Apple OAuth uses form_post response mode which posts to a separate
  callback path that wasn't proxied through nginx

- Add credential validation in GetAuthURL
  Only show OAuth buttons if provider credentials are configured,
  preventing broken auth URLs when client ID/secret are missing

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

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

* Pass GitHub and Apple OAuth credentials to auth container

The GH_OAUTH_CLIENT_ID/SECRET and Apple Sign-In credentials were
set in GitHub secrets but not passed to the auth service container
in docker-compose.prod.yml, causing the OAuth providers to appear
unconfigured.

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

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

* Update OAuth tests to expect error on empty credentials

The credential validation now returns an error for empty client ID
or client secret, so update the test to expect this behavior.

🤖 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 20:10:22 -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
f2743fe9cc Increase nginx client_max_body_size for game uploads (#5250)
Default is 1MB which is too small for game save uploads.
Set to 50MB to allow large game files.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 19:05:37 -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
3e868d0305 Fix nginx failing to reload during blue-green deployment (#5219)
The previous configuration used an upstream block with a static hostname:
  upstream eagle_grpc { server eagle-blue:40032; }

This caused nginx -s reload to fail when eagle-blue was stopped because
nginx tries to resolve all upstream hostnames at config load time.

Changed to use a map directive with a variable:
  map $host $eagle_backend { default "eagle-blue:40032"; }
  grpc_pass grpc://$eagle_backend;

This pattern (already used for auth backend) resolves the hostname at
request time, allowing nginx to reload even when the backend is down.
Requests to a stopped backend will get 502 errors instead of failing
to reload nginx entirely.

Tradeoff: Loses keepalive 100; setting, but deployment reliability
is more important than connection pooling optimization.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 09:53:38 -08:00
6940312d3f Add /invite/ route to nginx config (#5188)
The invitation landing page is served by the auth service on port 8080,
but nginx wasn't configured to proxy requests to it, resulting in 404.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 07:18:26 -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
b7ecc07468 Enable HTTPS for admin subdomain (#5079)
- Uncomment HTTPS server block for admin.eagle0.net
- HTTP now redirects to HTTPS

Requires SSL certs to be in place before merge.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 16:56:23 -08:00
da0e059b01 Add admin subdomain support with IPv6 (#5077)
- Add nginx server blocks for admin.eagle0.net and admin.prod.eagle0.net
- Enable IPv6 listeners on all nginx server blocks
- Remove direct port exposure for admin (now accessed via nginx)
- Admin console will be available at https://admin.eagle0.net

Requires SSL certificate setup after DNS propagation:
  certbot certonly --webroot -w /var/www/certbot \
    -d admin.eagle0.net -d admin.prod.eagle0.net

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 16:52:12 -08:00
fbede62140 Add admin subdomain support with IPv6 (#5076)
- Add nginx server blocks for admin.eagle0.net and admin.prod.eagle0.net
- Enable IPv6 listeners on all nginx server blocks
- Remove direct port exposure for admin (now accessed via nginx)
- Admin console will be available at https://admin.eagle0.net

Requires SSL certificate setup after DNS propagation:
  certbot certonly --webroot -w /var/www/certbot \
    -d admin.eagle0.net -d admin.prod.eagle0.net

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 16:36:29 -08:00
db5446ea37 Add Admin service route to nginx port 40033 (#5063)
The QA admin server connecting to prod auth was getting 404 because
nginx only routed the Auth service, not the Admin service which
handles ListUsers RPC for admin privilege checks.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 21:27:33 -08:00
f92f400f28 Route OAuth callback to Go auth service (#5000)
The /oauth/callback endpoint should proxy to auth:8080 (Go auth service)
not eagle:8080. This fixes 502 errors during OAuth flow.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 16:18:38 -08:00
d9893edcb1 Expose port 40033 for Go auth service in nginx (v2) (#4987)
Uses dynamic DNS resolution to avoid startup dependency on auth container.
Previous version failed because static upstreams resolve at nginx startup.

Changes:
- nginx.conf: Add server block on port 40033 with variable-based grpc_pass
- nginx.conf: Add /health endpoint on port 40033
- docker-compose: Expose 40033 on nginx container

Key fix: Using `set $auth_backend "auth:40033"; grpc_pass grpc://$auth_backend;`
instead of static upstream, so DNS resolution happens at request time
(cached by resolver for 10s) rather than at nginx startup.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 10:25:22 -08:00
adminandGitHub 5d4719ea19 Revert "Expose port 40033 for Go auth service in nginx (#4983)" (#4986)
This reverts commit e63a8489b6.
2026-01-02 08:43:41 -08:00
e63a8489b6 Expose port 40033 for Go auth service in nginx (#4983)
Adds nginx server block to listen on port 40033 and route gRPC
traffic to the Go auth service. This enables Phase 2 clients
to connect directly to the auth service.

Changes:
- nginx.conf: Add auth_grpc upstream and server block on port 40033
- docker-compose: Expose 40033 on nginx, add auth dependency

This is safe to deploy before Phase 2 clients - old clients still
use the Eagle proxy through port 443.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 08:24:59 -08:00
d80a151303 Nginx: Add routes for Auth gRPC service and OAuth callback (#4954)
- Add location block for /net.eagle0.eagle.api.auth.Auth gRPC service
- Add location block for /oauth/callback HTTP endpoint

These routes are required for OAuth login to work.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 12:24:17 -08:00
0ff8a3a8e1 Fix nginx DNS caching for container IP changes (#4817)
* Fix crane path discovery for cross-compiled Shardok image

Save the cross-compiled image path before building the push target,
since building push target without --platforms would overwrite it.
Also use find to locate crane binary dynamically.

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

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

* Add diagnostic steps to debug cross-compilation

- Build binary separately first with platform flag
- Verify binary is ELF format (Linux) not Mach-O (macOS)
- Verify binary in pkg_tar layer is also ELF
- This will help identify where cross-compilation breaks

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

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

* Add --extra_toolchains flag to force Linux cross-compilation

The --platforms flag wasn't working because the Linux toolchain is
registered with dev_dependency=True in MODULE.bazel. Adding
--extra_toolchains=@llvm_toolchain_linux//:all forces Bazel to
use the cross-compilation toolchain.

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

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

* Check binary directly from bazel-bin instead of searching bazel-out

The bazel-bin symlink points to the correct output directory, so we
should check that directly instead of searching through bazel-out
which may contain stale build artifacts.

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

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

* Add platform flags to push target build to preserve cross-compilation

The push target build was running without --platforms and --extra_toolchains,
which caused Bazel to rebuild the image without cross-compilation. This
overwrote the cross-compiled binary with a macOS binary before pushing.

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

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

* Use Darwin crane from Eagle push target for Shardok push

When using --platforms for cross-compilation, Bazel downloads the
target-platform crane (Linux) which can't run on the macOS host.
Instead, use the Eagle push target to get a Darwin crane binary,
since Eagle doesn't need cross-compilation.

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

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

* Fix crane detection to handle symlinks

Crane in Bazel runfiles is a symlink, not a regular file.
Changed find to not filter by type, and use -e instead of -f
for existence check.

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

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

* Fix nginx DNS caching for container IP changes

- Add Docker DNS resolver (127.0.0.11) with 10s TTL to nginx.conf
- Restart nginx after eagle/shardok in deploy workflow
- This ensures nginx picks up new container IPs after redeploy

🤖 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>
2025-12-25 11:30:12 -08:00
ded08187b2 Fix Shardok cross-compilation in Docker workflow (#4808)
* Fix Shardok cross-compilation in Docker workflow

The push step was rebuilding without --platforms flag, overwriting
the cross-compiled Linux binary with a macOS ARM64 binary.

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

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

* Update nginx config to use prod.eagle0.net

🤖 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>
2025-12-24 22:27:23 -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