The exportArchive step was relying on the Xcode-Token in the keychain
for App Store Connect authentication. This token expires periodically,
breaking headless CI builds. Pass the App Store Connect API key (already
used by the upload step) to the export step as well.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The nightly iOS TestFlight build already builds and uploads iOS
addressables as part of its pipeline. The standalone workflow added
unnecessary complexity and forced platform switches on the unity-mac
runner. Manual TestFlight dispatches cover any need for immediate
addressable updates.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Filter nightly iOS TestFlight by relevant paths
Instead of rebuilding whenever any file changes, compare changed files
against paths that actually affect the iOS build (C#, protobuf, resources,
C++ plugins, scripts, CI config). Scala, Go, and test-only changes no
longer trigger unnecessary nightly builds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove src/main/cpp/ from iOS-relevant paths
Shardok C++ code is server-side only. Native Unity plugins come from an
external Bazel dep (@net_eagle0_unity_godice), not the in-repo C++ code.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Adds a check-changes job that compares HEAD against the last successful
scheduled run. If the SHA matches, the build is skipped. Manual
workflow_dispatch triggers always build regardless.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Runs at 9 PM Pacific (5 AM UTC) every night, alongside the existing
manual workflow_dispatch trigger.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Self-hosted runners persist .git/lfs/objects/ between runs, and LFS
objects are now served from a local Gitea mirror on the LAN. The
actions/cache step was downloading ~1.8 GB from GitHub on every run,
adding ~35s and consuming transfer quota unnecessarily.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Decouple non-Mac builds from Xcode via BAZEL_NO_APPLE_CPP_TOOLCHAIN
Set BAZEL_NO_APPLE_CPP_TOOLCHAIN=1 in .bazelrc so apple_support skips
Xcode detection (xcode-locator) entirely for Scala/C++/Go builds.
Only mactools builds (Mac/Sparkle) re-enable the Apple CC toolchain.
This means Xcode version changes no longer affect Eagle, Shardok, test,
auth, docker, or Unity Windows builds — no expunge, no cache invalidation,
no rebuild. The sync script and its expunge/cache-key logic are now scoped
to mactools only and removed from all non-Mac CI workflows.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Revert BAZEL_NO_APPLE_CPP_TOOLCHAIN; keep Apple CC with stable environ
BAZEL_NO_APPLE_CPP_TOOLCHAIN=1 broke builds because apple_support
registers platforms (e.g. macos_x86_64) that require Apple CC toolchains
for resolution — even in non-Mac builds.
Instead, keep Apple CC enabled but ensure its repo rule never
re-evaluates on non-Mac builds by keeping DEVELOPER_DIR pinned in
common:macos. The sync script only runs for mactools builds and
overrides DEVELOPER_DIR there.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add actions/cache for .git/lfs in all workflows that fetch LFS objects
(unity_build, mac_build, ios_testflight, ios_addressables_build).
The cache key is based on .gitattributes so it updates when LFS tracking
rules change. Subsequent runs restore from cache and git lfs pull only
fetches new/changed objects, avoiding re-downloading the full 6+ GB.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The grpc 1.78.0 upgrade eliminated the rules_swift version conflict
that required SparklePlugin to live in a separate Bazel workspace.
Move it back into the main workspace under src/main/objc/.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Fix Bazel Xcode version caching causing CI build failures
When Xcode updates on self-hosted runners, Bazel's cached
local_config_xcode still references the old version, causing
"xcode-locator <old_version> failed" errors.
Add sync_bazel_xcode.sh which detects Xcode version changes
and runs `bazel sync --configure` to refresh the config without
nuking the build cache. Added to all 8 CI workflows that use Bazel.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use rm of cached repo instead of bazel sync --configure
bazel sync --configure requires WORKSPACE mode and fails with
bzlmod. Instead, delete the stale local_config_xcode from the
output base directly — the next bazel build regenerates it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use bazel clean --expunge instead of targeted repo deletion
Deleting just local_config_xcode is insufficient — the stale Xcode
version is also embedded in cached actions and other toolchain repos.
The only reliable fix is a full expunge. Since Xcode updates are
infrequent, the one-time rebuild cost is acceptable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use action_env to bake Xcode version into cache keys
Instead of expunging the cache (which doesn't help with the remote
cache anyway), write the Xcode build version to .bazelrc.xcode via
--action_env. This makes the version part of every action's cache
key, so stale entries in both local and remote caches are naturally
ignored after an Xcode update — no deletion needed anywhere.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add expunge back alongside action_env for belt-and-suspenders fix
action_env alone isn't enough — local_config_xcode is a cached
repository rule that bakes in the old Xcode version before any
actions run. Need expunge to clear the local toolchain config,
plus action_env to prevent stale remote cache hits afterward.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add Xcode sync to mac_build build-and-sign job
The sync step was only in the deploy job, but build-and-sign calls
bazel via build_protos.sh before that. This runs on the unity-mac
runner which also has the stale Xcode cache.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The warmup-binary artifacts from docker_build were accumulating and
exceeding the 500 MB storage threshold. GitHub's retention-days cleanup
wasn't keeping up with deploy frequency. Add explicit artifact deletion
after deploy, matching what mac_build already does.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Change deploy conditions from event_name == 'push' to
event_name != 'pull_request', matching the pattern already used by
the Upload Addressables step. This allows workflow_dispatch to
trigger a full deploy, which is needed for re-deploying without
a code change.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Migrate client update system to SHA-addressed blob storage
Upload game files and installer to blobs/<sha256> with immutable cache
headers, serve manifests with Cache-Control: no-cache so CDN always
revalidates. This eliminates the need for CDN cache purges after builds.
During the transition period, files are also uploaded to their legacy
paths (unity3d/win/<path> and installer/Eagle0.exe) so existing
installers continue to work until users auto-update.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add blob cleanup workflow and fix installer manifest URL
- Add daily scheduled GitHub Action to garbage-collect unreferenced blobs
- Fix installer_build.yml manifest step to use blobs/<sha> URL instead
of the legacy installer/Eagle0.exe path
- Add installer_sha256 to manifest for blob cleanup tool
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Revert installer blob changes — installer staleness is benign
The installer self-updates on next launch, so CDN staleness for
installer/Eagle0.exe is harmless. Keep blob addressing only for
game files where manifest/file mismatch causes download failures.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use manifest comparison instead of HEAD requests for blob dedup
Fetch the previous manifest to determine which SHAs already exist as
blobs, avoiding N HEAD requests per build. Also only upload to legacy
unity3d/win/ paths when the file at that path actually changed, matching
the old behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove legacy unity3d/win/ uploads — installer self-updates first
Old installers fetch the manifest, see a new installer version, and
self-update before downloading game files. The new installer fetches
from blobs/<sha>, so there's no need to keep uploading to the legacy
unity3d/win/<path> paths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Set no-cache on installer and whats-new.json uploads
Both are mutable files at fixed CDN URLs that were relying on default
cache behavior. Setting no-cache ensures the CDN always revalidates,
so users get the latest installer on first download and see current
changelog entries immediately.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
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>
The self-hosted runner doesn't have Node.js in PATH, causing the
node --check step to fail with 'command not found'.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
A git conflict marker was accidentally left in map_editor.js after a
rebase merge, breaking the map editor in production. This adds two
safeguards:
- CI lint job: `node --check` to catch JS syntax errors
- Pre-commit: `check-merge-conflict` hook to catch conflict markers
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The tile PNGs in static/tiles/ are LFS-tracked but the Docker build
workflow checks out with lfs: false, embedding LFS pointer files
instead of actual images. Add a targeted git lfs pull for just the
admin server tile images (~736KB) before the Bazel build.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The Hetzner server uses IPv6-only networking with NAT64 for IPv4. The first
docker pull often fails because the NAT64 gateway hasn't warmed up. Add a
curl pre-warm to the DO registry and retry docker pull up to 3 times with
a 10s backoff.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The Verify workspace step was added to debug the sparse-checkout issue,
which is now fixed by the Clean workspace step. Also removes clean:true
which runs git clean -ffdx and destroys bazel-* symlinks unnecessarily.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The gh CLI isn't installed on all self-hosted runners. Replace with
curl + python3 which are available everywhere.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When multiple commits are merged to main in quick succession, intermediate
builds can be skipped if a newer docker_build run is already queued.
This avoids wasting runner time on builds that will be immediately
superseded, while never cancelling a build or deploy that's in progress.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When multiple commits are merged to main in quick succession, intermediate
builds can be skipped if a newer docker_build run is already queued.
This avoids wasting runner time on builds that will be immediately
superseded, while never cancelling a build or deploy that's in progress.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Self-hosted runners can get stuck in sparse-checkout mode from previous
deploy jobs. The actions/checkout@v4 sparse-checkout disable has a bug:
it writes core.sparseCheckout=false to .git/config.worktree, then
immediately unsets extensions.worktreeConfig, causing git to fall back
to .git/config where core.sparseCheckout is still true.
Fix: explicitly set core.sparseCheckout=false in .git/config and
remove the worktree config files entirely before checkout.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The deploy job in docker_build.yml used sparse-checkout on [self-hosted, bazel]
runners. This left the workspace in sparse-checkout mode, causing subsequent
jobs on the same runner to fail with "not invoked from within a workspace"
because MODULE.bazel was missing.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The Windows build covers shared C#/proto compilation. Mac only needs
to build on PRs when Mac-specific config changes (workflow, build
scripts, Sparkle, code signing, etc.). Mac still builds on every
push to main.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Bee's incremental compilation handles content-only .cs modifications
fine. The stale DAG errors that originally motivated unconditional Bee/
deletion are caused by structural changes (files moved/renamed/deleted),
not content edits. This saves significant rebuild time on asset-only
changes like FBX meta edits or texture updates.
The last-built commit SHA is saved after each successful build and
compared on the next run to detect structural C# changes.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
With the CDN TTL increased to 1 hour, iOS addressables updates need
active cache purging to be visible to clients promptly.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Instead of sleeping 60s for CDN cache to expire, actively purge the
DigitalOcean Spaces CDN cache before notifying clients of new builds.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Both jobs now run on a dedicated [self-hosted, macOS, testflight]
runner (halfdan), so the Xcode project stays on disk between jobs.
This removes the tar/upload/download/extract cycle through GitHub's
artifact storage, saving ~5-13 minutes per TestFlight build.
Requires adding the 'testflight' label to the halfdan runner.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Git sparse checkout cone mode only supports directories. Add
sparse-checkout-cone-mode: false to allow the docker-compose.prod.yml
file pattern.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The cleanup job on ubuntu-latest already deletes these artifacts with
if: always(). The deploy job's copy was redundant.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Move sleep 60 + client notify from self-hosted runners to ubuntu-latest
jobs in mac_build and unity_build, freeing expensive runners sooner
- Skip eagle_build and shardok_build on main pushes (redundant with
docker_build and shardok_arm64_build respectively, kept for PRs)
- Add lfs: false to bazel_cache_cleanup (only runs bazel clean)
- Add sparse checkout + lfs: false to docker_build deploy job (only
needs docker-compose.prod.yml, nginx/, and scripts/)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Build-only workflows (bazel_test, eagle_build, shardok_build) cancel
superseded runs for both PRs and main pushes.
Deploy workflows (mac_build, unity_build, ios_addressables,
shardok_arm64) cancel superseded PR builds but queue main pushes to
avoid interrupting in-progress deployments.
docker_build already had concurrency control.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Same change as #6099 for mac/windows: switch to `clean: false` with
manual `git clean -ffd` to preserve Library/ between runs on
self-hosted runners. Eliminates two rsync operations (~4GB each)
per build.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The previous approach used `clean: true` (which runs `git clean -ffdx`,
deleting gitignored files like Library/), then rsync'd ~4GB from /tmp
to restore it, then rsync'd it back after the build.
Since self-hosted runners have persistent working directories, Library/
naturally survives between runs. Switch to `clean: false` with a manual
`git clean -ffd` (without -x) to remove stale untracked files while
preserving gitignored Library/. Bee/ is still cleaned each build to
avoid stale DAG files.
The restore/persist scripts are kept for the iOS workflows.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The deploy job runs on [self-hosted, macOS, unity-mac] which doesn't
have `gh` CLI installed. Replace `gh api` calls with `curl`/`python3`
which are universally available on macOS.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Both docker_build and shardok_arm64_build workflows were running
'bazel build //ci:eagle_server_push' just to extract crane from
its runfiles. In the ARM64 workflow this was especially costly (~29s)
because it triggered 'discarding analysis cache' due to the platform
flag change from linux_arm64 back to the default.
Install crane directly to ~/.local/bin with a version check, cached
across runs. First run downloads it; subsequent runs reuse the cached
binary.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
od -tx2 reads the 2-byte e_machine field as a 16-bit value in host
byte order (little-endian on macOS ARM64), so the ELF bytes b7 00
are displayed as 00b7, not b700. The check was producing a false
WARNING on every build even though the binary is correct.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Instead of downloading crane fresh every deployment, check if the
expected version already exists and skip the download. Saves ~3-5s
per deploy.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Pull all five images (eagle, admin, jfr-sidecar, nginx, certbot)
concurrently, then load the OCI tarballs sequentially. The network
download is the bottleneck, so parallelizing pulls saves ~15-20s.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The deploy job was rebuilding //ci:warmup_tar (~24s) even though
build-all already built it. Upload the warmup binary as a GitHub
Actions artifact and download it in the deploy job instead.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Push all three images (eagle, admin, jfr-sidecar) concurrently instead
of sequentially. Each push takes ~15-20s, so this should reduce the
push step from ~58s to ~20s.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The global `common --stamp` in .bazelrc caused every action's cache key
to include workspace status data, preventing cache reuse between the
bazel_test, eagle_build, and shardok_build workflows. Only three Go
binary targets actually use stamped x_defs values (admin_server,
authservice, installer), so --stamp is now passed only in the CI
workflow commands that build those targets.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Replace Python map generator with Rust implementation
- Add Rust map generator at src/main/rust/net/eagle0/eagle/map_generator/
- Parallel province equalization using graph coloring
- Generates equalized province sizes with ~7x improvement
- Run with: bazel run //src/main/rust/net/eagle0/eagle/map_generator -- --generate
- Remove Python map generator (src/main/python/)
- Rust version is faster and more maintainable
- Removes rules_python dependency from MODULE.bazel
- Add build test to verify Rust code compiles
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove Python from CI test workflow
Python map generator was replaced with Rust in this branch.
Rust tests are already covered by //src/test/...
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add Bazel Python support and parallelized map generator
- Add rules_python to MODULE.bazel with Python 3.13
- Create map generator at src/main/python/net/eagle0/eagle/
- Add parallel processing using ProcessPoolExecutor
- Provinces are grown in parallel across multiple CPU cores
Usage:
bazel run //src/main/python/net/eagle0/eagle:map_generator -- --analyze
bazel run //src/main/python/net/eagle0/eagle:map_generator -- --generate
bazel run //src/main/python/net/eagle0/eagle:map_generator -- --workers 8
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add smoke test for map_generator
Basic unit tests that verify:
- Constants are correct
- Land mask generation works
- Border detection works
- Border pixel filling works
Run with: bazel test //src/main/python/net/eagle0/eagle:map_generator_test
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Include Python tests in CI test job
Add //src/main/python/... to the bazel test command so Python tests
are run alongside other tests in CI.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Replace deprecated altool (removed in Xcode 14) with xcodebuild
-exportArchive using App Store Connect API authentication.
Changes:
- upload_testflight.sh: Use xcodebuild with destination=upload
and API key authentication instead of altool
- ios_testflight.yml: Pass xcarchive path and use new API key secrets
Required new GitHub secrets:
- APP_STORE_CONNECT_API_KEY_ID
- APP_STORE_CONNECT_API_ISSUER_ID
- APP_STORE_CONNECT_API_KEY (contents of .p8 file)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1. Speed up check_build_deps.sh:
- Cache expensive `bazel query deps(...)` results
- Reuse cached deps for all three checks instead of running
separate bazel query commands
- Use grep filtering on cached results instead of bazel intersect
2. Run lint job in parallel with test job:
- Split bazel_test.yml into separate lint and test jobs
- Jobs run concurrently, reducing total CI time
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Bazel 8 changed module extension repository naming from `~~` and `~` to
`++` and `+`. Update workflows to find crane dynamically instead of
using hardcoded paths that break with Bazel version changes.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>