56 Commits
Author SHA1 Message Date
adminandGitHub 46400dd6e1 Add Shardok profiling admin backend (#8754) 2026-07-23 07:15:34 -07:00
adminandGitHub ce82c952c5 Disable Postgres leak tracing in production compose (#8714) 2026-07-19 08:35:02 -07:00
adminandGitHub 6274ba4195 Pool Postgres connections behind provider (#8372)
* Pool Postgres connections behind provider

* Use smaller production Postgres pool default
2026-07-09 10:47:54 -07:00
adminandGitHub 7685f4470c Default Eagle history backend to Postgres (#6982) 2026-06-08 16:58:01 -07:00
adminandGitHub 5da3a75e7c Add Postgres game history backend (#6970) 2026-06-08 08:20:47 -07:00
adminandGitHub fe2b516e59 Wire Postgres secrets into Eagle deploy (#6966)
* Wire Postgres secrets into Eagle deploy

* Default Postgres SSL mode in compose
2026-06-08 07:00:30 -07:00
3731a2886e Add "Create PR" button to map editor (#6233)
Allows map editor changes to be submitted as GitHub PRs directly from
the admin UI. Uses the GitHub REST API with a fine-grained PAT (stored
as ADMIN_GITHUB_TOKEN secret) to create a branch, commit the map file,
and open a PR -- no git credentials needed on the admin server.

The button only appears when GITHUB_TOKEN is set. The flow saves the
map first, then creates the PR, showing a clickable link to the result.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 17:21:00 -08:00
8ff56d7d28 Enable map editor in production admin server deployment (#6190)
Add .e0mj map files as a layer in the admin server Docker image
and pass --maps-dir=/app/maps to enable the web-based map editor
added in #6186.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 17:53:20 -08:00
ee8dfba000 Add "Restart Eagle" button to admin console (#6187)
Uses Docker Engine API over mounted Unix socket to restart the active
Eagle container directly from the admin UI. Includes confirmation dialog
and graceful degradation when Docker socket is unavailable.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 13:41:22 -08:00
fde8ccf675 Fix S3 credentials: separate credentials for Eagle and admin (#5854)
Eagle and admin server use different S3 buckets:
- Eagle: eagle0 bucket (game data) - uses DO_SPACES_* secrets
- Admin: eagle0-assets bucket (What's New) - uses ACCESS_KEY_ID/SECRET_KEY secrets

The previous change (#5847) broke Eagle by giving it credentials that only
have access to eagle0-assets. This fix:
- Reverts Eagle to use DO_SPACES_* secrets (eagle0 bucket access)
- Adds ADMIN_S3_* env vars for admin server (eagle0-assets bucket access)
- Admin container receives ADMIN_S3_* values as DO_SPACES_* env vars

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 11:28:30 -08:00
30379e3e81 Add S3 credentials to admin server container (#5834)
The admin server needs DO_SPACES_* environment variables to access
S3/Spaces for What's New data storage. Without these, the What's New
Management page fails with "static credentials are empty".

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 07:07:13 -08:00
45c6b22ef8 Add client update notification system (server-side) (#5820)
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>
2026-02-03 20:57:37 -08:00
89ad8371b5 Add Gemini LLM support with model comparison documentation (#5561)
This adds Google Gemini as a third LLM provider option alongside OpenAI
and Anthropic (Claude). Based on streaming latency tests, Gemini 2.5
Flash-Lite shows the fastest time-to-first-token (~0.6s) at the lowest
cost ($0.10/$0.40 per 1M tokens).

Changes:
- Add GeminiServiceImpl.scala following OpenAI/Claude pattern
- Add GeminiModelName setting (default: gemini-2.5-flash-lite)
- Update LlmResolver to support "gemini" provider
- Update admin console dropdowns with Gemini options
- Reorder dropdown options to show recommended models first:
  - OpenAI: gpt-4.1-mini (was gpt-5-mini)
  - Claude: claude-3-5-haiku (already first)
  - Gemini: gemini-2.5-flash-lite
- Add GEMINI_API_KEY to docker-compose and deployment workflow
- Add docs/LLM_MODEL_COMPARISON.md with latency/pricing comparison

Streaming TTFT results from testing:
- Gemini 2.5 Flash-Lite: ~0.6s (fastest, cheapest)
- gpt-4.1-mini: ~1.7s
- claude-3-5-haiku: ~1.9s
- Gemini 2.5 Flash: ~6.8s (surprisingly slow)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 08:23:38 -08:00
25f92c7832 Add ANTHROPIC_API_KEY to deployment pipeline (#5558)
* Add ANTHROPIC_API_KEY to docker-compose environment

Required for Claude LLM provider to work in production.

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

* Add ANTHROPIC_API_KEY to deployment workflow

Pass the secret to the deploy job and export it for docker-compose.

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 07:25:04 -08:00
c4f887255b Remove --gpt-model-name from docker-compose (#5554)
The --gpt-model-name CLI argument was removed in the LLM model switching
PR (97b313f6), but docker-compose.prod.yml still passed it, causing the
server to fail with "Unknown option --gpt-model-name".

LLM provider and model are now configured via settings (admin console)
instead of CLI arguments.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 21:41:44 -08:00
2188834ddf Fix auth deploy: remove SHARDOK_ADDRESS validation from docker-compose (#5406)
docker-compose validates the entire file even when deploying just auth.
Remove the :? validation since docker_build.yml already validates
SHARDOK_ADDRESS explicitly before deploying Eagle.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 12:45:18 -08:00
f16257591b Add Steam and Twitch OAuth support (#5403)
- Steam: OpenID 2.0 authentication (no API key required)
- Twitch: Standard OAuth 2.0 with user:read:email scope
- Add TWITCH_CLIENT_ID and TWITCH_CLIENT_SECRET to deployment config
- Add Steam/Twitch buttons to invitation landing page

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 12:26:53 -08:00
a485772e66 Consolidate secrets handling: Eagle uses public key only for JWT validation (#5398)
Security improvement: Eagle no longer has access to JWT private keys.

- JwtService.scala: Simplified to validation-only (removed signing methods)
- Loads public key from /etc/eagle0/keys/public.pem (shared volume from auth)
- Falls back to extracting public key from JWT_PRIVATE_KEY for backward compat
- AuthServiceImpl.scala: Local fallbacks throw UNIMPLEMENTED (signing in Go auth)
- docker-compose.prod.yml: Removed JWT_PRIVATE_KEY from Eagle, volume now read-only

Auth service unchanged - still bootstraps PEM files from JWT_PRIVATE_KEY env var.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 09:44:03 -08:00
be59f9775f Add deployment validation for env vars and Shardok connectivity (#5394)
After investigating a bad deployment where Eagle connected to "shardok"
instead of the actual Hetzner address, found the root cause: GitHub
Actions secrets can occasionally fail to load, causing the heredoc to
expand with default values instead of the actual secrets.

Changes:
- Add validation at deploy start to check critical env vars are present
- Add network connectivity check to Shardok before blue-green deployment
  (blocks deployment if Shardok is unreachable)
- Remove the misleading "shardok:40042" default - now SHARDOK_ADDRESS
  must be explicitly set, making configuration errors immediately obvious
- docker-compose.prod.yml now uses :? syntax to require SHARDOK_ADDRESS

The env var validation will abort deployment early with a clear error
message if secrets failed to load, rather than deploying with broken
config.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 09:00:13 -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
e4b7d8e8a8 Fix: Remove shardok from eagle-green depends_on (#5286)
Missed this in #5282 - eagle-green still had depends_on: shardok
which caused the deployment to fail.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 11:02:06 -08:00
8bc22baa7c Remove DigitalOcean Shardok deployment (use Hetzner only) (#5282)
Shardok now runs exclusively on the Hetzner ARM64 server, deployed via
the shardok_arm64_build.yml workflow. This removes:

- shardok service from docker-compose.prod.yml
- Shardok x86 build/push from docker_build.yml
- SHARDOK_IMAGE from env.template and deploy script
- C++ path trigger from docker_build.yml (handled by ARM64 workflow)

The next deployment will stop and remove any existing shardok-server
container on the DigitalOcean droplet.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 10:54:07 -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
5a56f71c4c Require invitation codes for new user registration (#5244)
Add REQUIRE_INVITATION_CODE=true to auth service in production.
Without this, anyone could create an account without an invitation.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 17:45:45 -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
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
ee47f7d6bc Switch from SMTP to Fastmail JMAP API for invitation emails (#5164)
DigitalOcean blocks SMTP ports (587, 465) by default. Switch to
Fastmail's JMAP API which uses HTTPS and is not blocked.

Changes:
- Rewrite sendgrid.go to use JMAP API (Email/set + EmailSubmission/set)
- Update docker-compose.prod.yml with FASTMAIL_* env vars
- Update auth_build.yml workflow with new secrets

Required GitHub secrets:
- FASTMAIL_API_TOKEN: API token with email submission scope
- FASTMAIL_FROM_EMAIL: Sender email (optional, uses identity default)
- FASTMAIL_FROM_NAME: Sender name (optional, defaults to "Eagle0 Game")

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 17:54:10 -08:00
fe3c2d3e79 Add SMTP env vars to auth service in docker-compose (#5163)
The workflow writes SMTP credentials to .env, but docker-compose only
passes explicitly listed environment variables to containers.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 17:38:49 -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
ea46bb4a90 Enable Docker IPv6 for connecting to Hetzner Shardok (#5047)
The Hetzner Shardok server is IPv6-only. Docker containers need
IPv6 support to reach it.

Changes:
- docker-compose.prod.yml: Add IPv6-enabled network
- docker_build.yml: Configure Docker daemon for IPv6 on deploy
  (with ip6tables for NAT)

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 12:09:58 -08:00
adminandClaude Opus 4.5 581ffb735e Make Shardok address configurable via SHARDOK_ADDRESS env var
Allows switching between local Shardok (default: shardok:40042) and
remote Hetzner Shardok (shardok.prod.eagle0.net:40042) via GitHub
Actions secrets.

To use Hetzner Shardok in production:
1. Add secret SHARDOK_ADDRESS=shardok.prod.eagle0.net:40042
2. Add secret SHARDOK_AUTH_TOKEN=<your-token>

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 10:51:52 -08:00
e7c8df8b33 Add admin user management to admin console (#5041)
* Add admin user management to admin console

Adds functionality to view and manage user identity→display name links:

Backend (auth service):
- New admin.proto with AdminService gRPC (ListUsers, SetUserDisplayName, SetUserAdmin)
- AdminHandler validates JWT is_admin claim for authorization
- UserService methods for listing users and admin operations

Frontend (admin console):
- OAuth login flow with Discord/Google (JWT stored in HTTP-only cookie)
- Auth middleware requiring is_admin claim for /users routes
- Users page with search, display name editing, and admin toggle
- HTMX-powered table with infinite scroll pagination

Deployment:
- admin container now connects to auth service for user management

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

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

* Add authcli tool for user management

CLI tool for managing auth service users directly via the users.pb file.
Useful for bootstrapping the first admin user or emergency access.

Commands:
- list: List all users with their admin status
- find <email>: Find user by email (partial match)
- set-admin <id> true|false: Set admin status by user ID
- grant-admin <email>: Grant admin to user by exact email match

Usage:
  authcli --data-dir=/app/data list
  authcli --data-dir=/app/data grant-admin your@email.com

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

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

* Add authcli to auth server Docker image

- Include authcli_linux_amd64 in auth_binary_layer
- Update auth_build.yml paths to trigger on authcli and admin proto changes

Usage on VM:
  docker exec auth-server /app/authcli_linux_amd64 --data-dir=/app/data list
  docker exec auth-server /app/authcli_linux_amd64 --data-dir=/app/data grant-admin user@email.com

🤖 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-04 07:55:23 -08:00
ecd089de59 Add Sentry integration for exception alerting (#5011)
- Add io.sentry:sentry dependency
- Initialize Sentry from SENTRY_DSN environment variable
- Report uncaught exceptions via ExceptionInterceptor
- Add SENTRY_DSN to docker-compose and CI workflow

When SENTRY_DSN is configured, uncaught exceptions in gRPC handlers
will be reported to Sentry for email/Slack alerts.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 22:20:05 -08:00
78533a9ff0 Remove Eagle user management - forward to Go auth service (#5033)
Phase 2 of moving user management to Go auth service:
- Eagle now forwards setDisplayName, getCurrentUser, logout to Go auth
- Removed InternalUserServiceImpl and internal gRPC server (port 40034)
- UserService is now optional (only created when not using external auth)
- Updated docker-compose to remove port 40034 exposure

This makes Eagle stateless for user data when configured with
--auth-service-url, enabling QA to point to prod auth service.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 21:39:24 -08:00
fbabe9cea4 Move user management to Go auth service (Phase 1) (#5030)
* Move user management to Go auth service (Phase 1)

Auth service now manages users locally instead of calling Eagle:
- Add users.go with UserService for protobuf-based user persistence
- Implement SetDisplayName, GetCurrentUser, Logout handlers
- Extract JWT from gRPC metadata (authorization header)
- Add ValidateAccessToken function to jwt.go
- Add user_go_proto target for user.proto
- Add AUTH_DATA_DIR env var and /app/data volume mount

Auth service no longer depends on Eagle's InternalUserService.
Eagle's user management code remains for now (Phase 2 cleanup).

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

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

* Add unit tests and migration path for user service

- Add comprehensive unit tests for UserService (users_test.go)
- Add automatic migration from Eagle's users.pb location
- Add atomic write pattern for crash safety (write .tmp, then rename)
- Add AUTH_LEGACY_DATA_DIR config for migration path
- Mount Eagle's saves volume read-only for migration access

Migration strategy:
1. Auth service checks /app/data/users.pb first
2. If not found, reads from /app/saves/auth/users.pb (Eagle's location)
3. Saves migrated data to new location
4. Subsequent reads/writes use new location only

🤖 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-03 19:21:45 -08:00
e9dd53fdf8 Hetzner setup: docs + Step 7 implementation (#5018)
* Update Hetzner docs with actual deployment details

- Use shardok.prod.eagle0.net as the domain name
- Step 4: Specify GitHub Actions secrets location
- Step 5: Recommend Hillsboro, OR (hil) + IPv6 floating IP
- Update all code examples and cloud-init scripts

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

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

* Wire SHARDOK_AUTH_TOKEN through Eagle's Shardok connection

Implements Step 7 of Hetzner setup guide:
- Main.scala reads SHARDOK_AUTH_TOKEN env var and creates ShardokSecurityConfig
- TLS is auto-enabled when Shardok address contains ".eagle0.net"
- Security config passed to both newGamesManager and newCustomBattleManager
- docker-compose.prod.yml passes SHARDOK_AUTH_TOKEN to Eagle container
- docker_build.yml passes secret during deployment

This is backward compatible: local Docker Shardok (shardok:40042) continues
to work without TLS/auth since the address doesn't contain ".eagle0.net".

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

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

* Update ShardokInstanceManager for DigitalOcean registry and TLS

- Update cloud-init script to use DigitalOcean Container Registry instead of ghcr.io
- Add Let's Encrypt/Certbot setup for TLS certificates
- Configure automatic certificate renewal via cron
- Pass TLS cert paths and auth token file to Shardok container
- Default to shardok.prod.eagle0.net domain

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

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

* Fix cloud-init to use eagle0.conf instead of environment variables

The Shardok C++ server reads configuration from /usr/local/share/eagle0/eagle0.conf,
not environment variables. Updated cloud-init script to:
- Create eagle0.conf with TLS and auth paths
- Mount the config directory into the container
- Remove unused -e environment variable flags

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

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

* Simplify spin-up strategy: trigger on human turns with 10-min idle timeout

Instead of trying to predict battles, we now:
- Spin up Shardok when any human player takes a turn
- Shut down after 10 minutes of no human turns

This is simpler and more reliable. Battles happen regularly during active
play, so Shardok will be ready when needed. The 10-minute timeout is long
enough to cover thinking time but short enough to minimize idle costs.

Updated:
- SHARDOK_ON_DEMAND_COMPUTE.md with new strategy and state machine
- ShardokInstanceManager: renamed onPlayerActivity -> onHumanTurn,
  changed default idle timeout from 60 to 10 minutes

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

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

* Document Hetzner testing progress and ARM64 blocker

Testing status:
- Hetzner CAX41 ARM64 server created in Helsinki
- Floating IPv6 configured with netplan persistence
- DNS and Let's Encrypt certificates working
- NAT64 configured for IPv6→IPv4 registry access

BLOCKER: ARM64 container crashes with runfiles error:
"cannot find runfiles (argv0="/app/shardok-server")"
Needs BUILD.bazel investigation for ARM64 image packaging.

Also documented known issues:
- IPv6-only servers need NAT64 DNS (nat64.net)
- Floating IP requires manual netplan config

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

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

* Fix ARM64 container crash: add missing environment variables

The ARM64 Shardok container was crashing with "cannot find runfiles"
because SHARDOK_RESOURCES_PATH and SHARDOK_MAPS_PATH weren't set.
Without these, the binary tries to use Bazel runfiles which don't
exist in the container.

Added the required environment variables to the docker run command
in the cloud-init script, matching docker-compose.prod.yml.

🤖 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-03 17:07:10 -08:00
58fd3dc112 Fix user database persistence in Docker (#5017)
The user database was being written to ~/eagle0/eagle/save/ but Docker
only mounted ./saves to /app/saves. This caused user data (including
displayName) to be lost on every container restart.

Add EAGLE_SAVE_DIR and EAGLE_ARCHIVE_DIR environment variables to
SaveDirectory, defaulting to the existing paths for local development.
Docker compose now sets these to /app/saves and /app/archived which
are properly mounted to persistent volumes.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 08:26:15 -08:00
06f6ba8340 Fix auth port conflict with nginx (#4993)
Remove direct port 40033 binding from auth service since nginx
now proxies this port (PR #4987). Both can't bind to the same
host port.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 11:57:02 -08:00
bfe5befff2 Auth service bootstraps JWT keys from JWK format (#4992)
- Add bootstrapKeysFromJWK() to convert JWK to PEM files on first run
- Uses only Go stdlib (no third-party JWK libraries)
- Pass JWT_PRIVATE_KEY env var to auth service in docker-compose
- PEM files persist in jwt-keys volume after first bootstrap

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 11:47:56 -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
23d2cb3968 Extract OAuth to Go service (Phase 1) (#4980)
* Extract OAuth to Go service (Phase 1)

Move OAuth authentication handling from Eagle Scala server into a separate Go
service running in its own container. This simplifies Eagle, enables independent
deployment, and sets up for future JWT validation extraction.

Architecture:
- Go auth service handles OAuth flows, JWT creation, state management
- Eagle proxies Auth gRPC calls to Go service when configured
- Go service calls Eagle's InternalUserService for user persistence
- Shared JWT keys via volume mount

New files:
- src/main/go/net/eagle0/authservice/ - Go auth service
- auth_internal.proto - Internal gRPC for Go→Eagle communication
- InternalUserServiceImpl.scala - User service wrapper for internal gRPC
- ExternalAuthClient.scala - Client for forwarding to Go service

Backward compatible: Eagle runs in standalone mode without --auth-service-url

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

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

* Add unit tests for Go auth service

- jwt_test.go: Tests for JWT token creation, validation, and refresh
- oauth_test.go: Tests for OAuth state management and status checking

🤖 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-02 07:32:02 -08:00
31458c2a98 Deploy: Pass Discord OAuth credentials to Eagle container (#4950)
Adds DISCORD_CLIENT_ID and DISCORD_CLIENT_SECRET to deploy config,
enabling Discord OAuth login in production.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 06:52:38 -08:00
dabf309e6d Deploy: Pass JWT_PRIVATE_KEY to Eagle container (#4949)
Enables OAuth JWT authentication in production by:
- Adding JWT_PRIVATE_KEY secret to deploy workflow env
- Passing it through SSH to the droplet .env file
- Configuring docker-compose to pass it to the Eagle container

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 00:21:53 -08:00
7e87ca792e Add memory limit to Shardok container (#4942)
Set 1GB hard limit so Shardok can't OOM the entire droplet.
If exceeded, Docker kills just that container and it restarts
automatically (restart: unless-stopped).

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 16:20:24 -08:00
4157863b3d Share /tmp volume between Eagle and jfr-sidecar for JVM attach (#4919)
JVM attach (used by jcmd) communicates via socket files in /tmp.
With shared PID namespace but separate filesystems, the sidecar
couldn't find Eagle's .java_pid1 socket file.

Add a shared named volume for /tmp between both containers.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 16:13:10 -08:00
78fce1ba74 Add JFR dump download to admin console via sidecar (#4917)
Add a JFR sidecar container that shares PID namespace with Eagle to
enable JFR dumps without giving admin container Docker socket access.

Components:
- jfr_server: Minimal Go HTTP server that runs jcmd and serves JFR files
- Sidecar uses shared PID namespace (pid: "service:eagle") to see Eagle JVM
- Admin console proxies /jfr/download to sidecar
- UI button in nav bar triggers download

Security:
- Sidecar can only see Eagle's processes (no Docker socket)
- No host filesystem access
- Minimal attack surface

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 15:52:55 -08:00
800ac22ff6 Enable JFR profiling for Eagle server in Docker (#4895)
Adds Java Flight Recorder with continuous low-overhead profiling (~1%):
- Keeps 1 hour of data in a 100MB circular buffer
- Auto-dumps on JVM exit
- Stack depth of 256 for detailed traces

To capture a profile:
  docker exec eagle-server jcmd 1 JFR.dump filename=/app/jfr/profile.jfr
  docker cp eagle-server:/app/jfr/profile.jfr .

Analyze with JDK Mission Control (jmc) or IntelliJ's JFR viewer.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 21:07:21 -08:00
e5a71bd3b4 Add Go admin server to Docker deployment (#4843)
* Add Go admin server to Docker deployment

- Add oci_image, oci_load, oci_push targets for admin_server in ci/BUILD.bazel
- Add Alpine Linux 3.21 base image to MODULE.bazel (lightweight for Go binary)
- Add admin service to docker-compose.prod.yml with health check
- Add build-admin job to GitHub Actions workflow
- Update deploy job to pull and deploy admin image alongside eagle/shardok

The admin server connects to Eagle via internal Docker network (eagle:40032)
and exposes HTTP on port 8080 for the admin console.

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

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

* Fix Go cross-compilation by enabling pure Go build

Add `pure = "on"` to admin_server go_binary to disable CGO.
This avoids linker errors when cross-compiling from macOS ARM64
to Linux x86_64 with the LLVM toolchain.

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

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

* Use rules_go platform for Go cross-compilation

Use @io_bazel_rules_go//go/toolchain:linux_amd64 instead of
//:linux_x86_64 for the admin server build. The rules_go platform
uses Go's native cross-compiler and doesn't involve the LLVM C
toolchain, avoiding linker errors.

🤖 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-26 11:46:07 -08:00