* 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>
4.1 KiB
Hetzner Setup Guide
This guide walks through setting up Hetzner Cloud infrastructure for running Shardok on-demand compute.
Prerequisites
- All code PRs merged (#4990, #4996, #4998, #5001, #5009)
- Access to DigitalOcean Container Registry (for pulling Shardok ARM64 image)
Step 1: Create Hetzner Cloud Account
- Go to https://console.hetzner.cloud/
- Sign up and add payment method
- Create a new project (e.g., "eagle0")
Step 2: Generate Hetzner API Token
- In Hetzner Console → Security → API Tokens
- Click "Generate API Token"
- Give it Read & Write permissions
- Copy the token (you'll only see it once)
Step 3: Generate Shardok Auth Token
Generate a 256-bit random token for Eagle-Shardok authentication:
openssl rand -hex 32
Save this output - it's the shared secret between Eagle and Shardok.
Step 4: Store Secrets in GitHub Actions
Add these secrets in GitHub → Settings → Secrets and variables → Actions:
| Secret Name | Description |
|---|---|
HETZNER_API_TOKEN |
From Step 2 - for Hetzner API calls |
SHARDOK_AUTH_TOKEN |
From Step 3 - shared secret for gRPC auth |
Note: DO_REGISTRY_TOKEN already exists and will be used for Hetzner to pull container images.
These secrets will be passed to Eagle at runtime via docker_build.yml, similar to how OPENAI_API_KEY and other secrets are handled.
Step 5: DNS Setup (for Let's Encrypt)
You need a domain pointing to the Shardok instance for TLS certificates.
Option A: Floating IP (Recommended)
- In Hetzner Console → Networking → Floating IPs
- Create a Floating IPv6 in Hillsboro, Oregon (hil) region
- IPv6 costs €1/month vs €3/month for IPv4
- Hillsboro has better latency to DigitalOcean SFO than Ashburn
- Server-to-server communication works fine with IPv6-only
- Point
shardok.prod.eagle0.netto this IP via AAAA record - The ShardokInstanceManager will attach this IP to instances on spin-up
Location choice: Hillsboro, OR (hil) is recommended for US West Coast. Same pricing as Ashburn (ash).
Option B: Dynamic DNS
Update DNS programmatically when instance spins up. More complex but avoids floating IP cost.
Step 6: Upload SSH Key to Hetzner
For debugging access to instances:
- In Hetzner Console → Security → SSH Keys
- Click "Add SSH Key"
- Paste your public key (e.g.,
~/.ssh/id_rsa.pub) - Give it a name (e.g., "eagle-deploy")
Step 7: Wire Security Config into Eagle
Update Eagle's startup code to use the security config when connecting to remote Shardok:
val securityConfig = ShardokSecurityConfig(
useTls = true,
authToken = Some(sys.env("SHARDOK_AUTH_TOKEN"))
)
val channel = ServerSetupHelpers.newChannel(
"shardok.prod.eagle0.net",
50051,
securityConfig
)
Testing
Manual Instance Spin-up
Test the Hetzner integration by triggering instance creation:
val manager = new ShardokInstanceManager(
hetznerApiToken = sys.env("HETZNER_API_TOKEN"),
// ... other config
)
manager.ensureInstanceRunning()
Verify TLS and Auth
- Instance spins up and gets Let's Encrypt certificate
- Eagle connects via TLS
- Auth token is validated on each request
Cost Estimate
| Component | Cost |
|---|---|
| CAX41 (16 ARM cores) | ~$0.04/hour |
| Floating IP | ~$4/month |
| Typical usage (20 hrs/week) | ~$3.50/month compute |
Total: ~$7-8/month for typical usage.
Troubleshooting
Instance won't start
- Check Hetzner API token has Read & Write permissions
- Verify you're using the correct region (
hilfor Hillsboro OR, orashfor Ashburn VA)
TLS certificate fails
- Ensure DNS points to the instance IP before certbot runs
- Check port 80 is open for Let's Encrypt HTTP-01 challenge
Auth failures
- Verify
SHARDOK_AUTH_TOKENmatches on both Eagle and Shardok - Check the token file is readable by Shardok container
Can't pull container image
- Ensure
DO_REGISTRY_TOKENis passed to cloud-init - Verify the ARM64 image exists:
registry.digitalocean.com/eagle0/shardok-server:arm64-latest