mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-29 04:42:18 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99e331b40b | ||
|
|
0d832ea8f2 | ||
|
|
e296a31033 |
@@ -1,37 +0,0 @@
|
||||
name: Artifact Storage Check
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run every 6 hours
|
||||
- cron: '0 */6 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
check-storage:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check artifact storage size
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
# Calculate total artifact storage
|
||||
total_bytes=$(gh api "repos/${{ github.repository }}/actions/artifacts" \
|
||||
--paginate -q '.artifacts[].size_in_bytes' | awk '{sum+=$1} END {print sum}')
|
||||
|
||||
total_mb=$((total_bytes / 1024 / 1024))
|
||||
echo "Total artifact storage: ${total_mb} MB"
|
||||
|
||||
# Fail if over 500MB
|
||||
if [ "$total_mb" -gt 500 ]; then
|
||||
echo "::error::Artifact storage is ${total_mb} MB, which exceeds the 500 MB threshold!"
|
||||
echo ""
|
||||
echo "Largest artifacts:"
|
||||
gh api "repos/${{ github.repository }}/actions/artifacts" \
|
||||
--paginate -q '.artifacts[] | "\(.size_in_bytes)\t\(.name)\t\(.created_at)"' | \
|
||||
sort -rn | head -20 | \
|
||||
awk -F'\t' '{printf "%d MB\t%s\t%s\n", $1/1024/1024, $2, $3}'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Storage is within acceptable limits."
|
||||
@@ -104,14 +104,6 @@ jobs:
|
||||
DISCORD_CLIENT_SECRET: ${{ secrets.DISCORD_CLIENT_SECRET }}
|
||||
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
|
||||
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
|
||||
GH_OAUTH_CLIENT_ID: ${{ secrets.GH_OAUTH_CLIENT_ID }}
|
||||
GH_OAUTH_CLIENT_SECRET: ${{ secrets.GH_OAUTH_CLIENT_SECRET }}
|
||||
APPLE_SIGNIN_CLIENT_ID: ${{ secrets.APPLE_SIGNIN_CLIENT_ID }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_SIGNIN_KEY_ID: ${{ secrets.APPLE_SIGNIN_KEY_ID }}
|
||||
APPLE_SIGNIN_PRIVATE_KEY: ${{ secrets.APPLE_SIGNIN_PRIVATE_KEY }}
|
||||
TWITCH_CLIENT_ID: ${{ secrets.TWITCH_CLIENT_ID }}
|
||||
TWITCH_CLIENT_SECRET: ${{ secrets.TWITCH_CLIENT_SECRET }}
|
||||
JWT_PRIVATE_KEY: ${{ secrets.JWT_PRIVATE_KEY }}
|
||||
FASTMAIL_API_TOKEN: ${{ secrets.FASTMAIL_API_TOKEN }}
|
||||
FASTMAIL_FROM_EMAIL: ${{ secrets.FASTMAIL_FROM_EMAIL }}
|
||||
@@ -120,6 +112,16 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Copy update-env script to server
|
||||
uses: appleboy/scp-action@v0.1.7
|
||||
with:
|
||||
host: ${{ secrets.DO_DROPLET_IP }}
|
||||
username: deploy
|
||||
key: ${{ secrets.DO_SSH_KEY }}
|
||||
source: "deploy/update-env.sh,deploy/env.template"
|
||||
target: /opt/eagle0/
|
||||
strip_components: 1
|
||||
|
||||
- name: Deploy auth service to production
|
||||
uses: appleboy/ssh-action@v1.0.3
|
||||
with:
|
||||
@@ -127,75 +129,51 @@ jobs:
|
||||
username: deploy
|
||||
key: ${{ secrets.DO_SSH_KEY }}
|
||||
script_stop: true
|
||||
envs: AUTH_IMAGE,DISCORD_CLIENT_ID,DISCORD_CLIENT_SECRET,GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET,GH_OAUTH_CLIENT_ID,GH_OAUTH_CLIENT_SECRET,APPLE_SIGNIN_CLIENT_ID,APPLE_TEAM_ID,APPLE_SIGNIN_KEY_ID,APPLE_SIGNIN_PRIVATE_KEY,TWITCH_CLIENT_ID,TWITCH_CLIENT_SECRET,JWT_PRIVATE_KEY,FASTMAIL_API_TOKEN,FASTMAIL_FROM_EMAIL,FASTMAIL_FROM_NAME
|
||||
envs: AUTH_IMAGE,DISCORD_CLIENT_ID,DISCORD_CLIENT_SECRET,GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET,JWT_PRIVATE_KEY,FASTMAIL_API_TOKEN,FASTMAIL_FROM_EMAIL,FASTMAIL_FROM_NAME
|
||||
script: |
|
||||
set -x
|
||||
cd /opt/eagle0
|
||||
|
||||
# Export env vars for docker compose (appleboy/ssh-action sets them but doesn't export)
|
||||
export AUTH_IMAGE="${AUTH_IMAGE}"
|
||||
export DISCORD_CLIENT_ID="${DISCORD_CLIENT_ID}"
|
||||
export DISCORD_CLIENT_SECRET="${DISCORD_CLIENT_SECRET}"
|
||||
export GOOGLE_CLIENT_ID="${GOOGLE_CLIENT_ID}"
|
||||
export GOOGLE_CLIENT_SECRET="${GOOGLE_CLIENT_SECRET}"
|
||||
export GH_OAUTH_CLIENT_ID="${GH_OAUTH_CLIENT_ID}"
|
||||
export GH_OAUTH_CLIENT_SECRET="${GH_OAUTH_CLIENT_SECRET}"
|
||||
export APPLE_SIGNIN_CLIENT_ID="${APPLE_SIGNIN_CLIENT_ID}"
|
||||
export APPLE_TEAM_ID="${APPLE_TEAM_ID}"
|
||||
export APPLE_SIGNIN_KEY_ID="${APPLE_SIGNIN_KEY_ID}"
|
||||
export APPLE_SIGNIN_PRIVATE_KEY="${APPLE_SIGNIN_PRIVATE_KEY}"
|
||||
export TWITCH_CLIENT_ID="${TWITCH_CLIENT_ID}"
|
||||
export TWITCH_CLIENT_SECRET="${TWITCH_CLIENT_SECRET}"
|
||||
export JWT_PRIVATE_KEY="${JWT_PRIVATE_KEY}"
|
||||
export FASTMAIL_API_TOKEN="${FASTMAIL_API_TOKEN}"
|
||||
export FASTMAIL_FROM_EMAIL="${FASTMAIL_FROM_EMAIL}"
|
||||
export FASTMAIL_FROM_NAME="${FASTMAIL_FROM_NAME}"
|
||||
# Update env vars using shared script (preserves vars set by other workflows)
|
||||
chmod +x update-env.sh
|
||||
./update-env.sh \
|
||||
"AUTH_IMAGE=${AUTH_IMAGE}" \
|
||||
"DISCORD_CLIENT_ID=${DISCORD_CLIENT_ID}" \
|
||||
"DISCORD_CLIENT_SECRET=${DISCORD_CLIENT_SECRET}" \
|
||||
"GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}" \
|
||||
"GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET}" \
|
||||
"JWT_PRIVATE_KEY=${JWT_PRIVATE_KEY}" \
|
||||
"FASTMAIL_API_TOKEN=${FASTMAIL_API_TOKEN}" \
|
||||
"FASTMAIL_FROM_EMAIL=${FASTMAIL_FROM_EMAIL}" \
|
||||
"FASTMAIL_FROM_NAME=${FASTMAIL_FROM_NAME}"
|
||||
|
||||
# Login to registry
|
||||
echo "${{ secrets.DO_REGISTRY_TOKEN }}" | docker login registry.digitalocean.com -u "${{ secrets.DO_REGISTRY_TOKEN }}" --password-stdin
|
||||
|
||||
echo "Deploying auth service: $AUTH_IMAGE"
|
||||
|
||||
# Pull the image directly (docker is already logged in)
|
||||
echo "Pulling Auth image..."
|
||||
docker pull "${AUTH_IMAGE}" || { echo "ERROR: Failed to pull auth image"; exit 1; }
|
||||
# Use crane to pull image
|
||||
echo "Installing crane..."
|
||||
curl -sL https://github.com/google/go-containerregistry/releases/download/v0.20.2/go-containerregistry_Linux_x86_64.tar.gz | tar xzf - crane
|
||||
chmod +x crane
|
||||
|
||||
# Tag as :latest locally so any fallback uses correct image
|
||||
docker tag "${AUTH_IMAGE}" registry.digitalocean.com/eagle0/auth-server:latest
|
||||
echo "Pulling Auth image with crane..."
|
||||
./crane pull "${AUTH_IMAGE}" auth.tar || { echo "ERROR: Failed to pull auth image"; exit 1; }
|
||||
echo "Loading Auth image into Docker..."
|
||||
docker load -i auth.tar
|
||||
rm auth.tar
|
||||
rm ./crane
|
||||
|
||||
# Debug: check environment and .env file
|
||||
echo "DEBUG: AUTH_IMAGE=$AUTH_IMAGE"
|
||||
env | grep AUTH || echo "AUTH_IMAGE not in env output"
|
||||
if [ -f .env ]; then
|
||||
echo "DEBUG: .env file contents related to AUTH:"
|
||||
grep AUTH .env || echo "No AUTH in .env"
|
||||
fi
|
||||
|
||||
# Recreate auth container - pass AUTH_IMAGE explicitly on command line
|
||||
AUTH_IMAGE="${AUTH_IMAGE}" docker compose -f docker-compose.prod.yml up -d --no-deps --force-recreate auth
|
||||
# Only recreate the auth container (not eagle, shardok, etc.)
|
||||
docker compose -f docker-compose.prod.yml up -d --no-deps --force-recreate auth
|
||||
|
||||
# Wait for health check
|
||||
sleep 5
|
||||
|
||||
# Verify container is using the correct image
|
||||
# Note: docker-compose may use :latest tag (which we tagged to the correct image)
|
||||
echo "=== Verifying auth container image ==="
|
||||
RUNNING_IMAGE=$(docker inspect auth-server --format '{{.Config.Image}}')
|
||||
RUNNING_DIGEST=$(docker inspect auth-server --format '{{.Image}}')
|
||||
EXPECTED_DIGEST=$(docker inspect "${AUTH_IMAGE}" --format '{{.Id}}')
|
||||
echo "Expected image: ${AUTH_IMAGE}"
|
||||
echo "Running image: ${RUNNING_IMAGE}"
|
||||
echo "Expected digest: ${EXPECTED_DIGEST}"
|
||||
echo "Running digest: ${RUNNING_DIGEST}"
|
||||
|
||||
if [ "$RUNNING_DIGEST" != "$EXPECTED_DIGEST" ]; then
|
||||
echo "ERROR: Container is running wrong image!"
|
||||
exit 1
|
||||
fi
|
||||
echo "Image digests match - correct image is running"
|
||||
|
||||
# Show container status
|
||||
docker compose -f docker-compose.prod.yml ps auth
|
||||
|
||||
# Verify container is using correct image
|
||||
echo "=== Verifying auth container image ==="
|
||||
docker compose -f docker-compose.prod.yml images auth
|
||||
|
||||
# Cleanup old images
|
||||
docker image prune -f
|
||||
|
||||
@@ -33,9 +33,9 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: false
|
||||
- name: Check BUILD.bazel dependencies
|
||||
run: ./scripts/check_build_deps.sh --strict
|
||||
- name: Run tests
|
||||
id: test
|
||||
continue-on-error: true
|
||||
run: bazel test --build_event_json_file=test.json //src/test/... //src/main/go/...
|
||||
- name: Collect failed test logs
|
||||
if: always()
|
||||
@@ -75,7 +75,6 @@ jobs:
|
||||
with:
|
||||
name: test.json
|
||||
path: test.json
|
||||
retention-days: 5
|
||||
- name: Archive failed test logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -83,4 +82,6 @@ jobs:
|
||||
name: failed-test-logs
|
||||
path: failed_test_logs/
|
||||
if-no-files-found: ignore
|
||||
retention-days: 5
|
||||
- name: Fail if tests failed
|
||||
if: steps.test.outcome == 'failure'
|
||||
run: exit 1
|
||||
|
||||
@@ -37,7 +37,6 @@ jobs:
|
||||
with:
|
||||
name: ubuntu-noble-sysroot-amd64
|
||||
path: tools/sysroot/output/
|
||||
retention-days: 1
|
||||
|
||||
- name: Install AWS CLI
|
||||
run: |
|
||||
@@ -99,7 +98,6 @@ jobs:
|
||||
with:
|
||||
name: ubuntu-noble-sysroot-arm64
|
||||
path: tools/sysroot/output/
|
||||
retention-days: 1
|
||||
|
||||
- name: Install AWS CLI
|
||||
run: |
|
||||
|
||||
@@ -5,17 +5,9 @@ on:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
# Note: C++ changes trigger shardok_arm64_build.yml instead
|
||||
# Note: Auth changes trigger auth_build.yml instead
|
||||
# Note: Windows installer changes trigger installer_build.yml instead
|
||||
- 'src/main/go/**'
|
||||
- '!src/main/go/net/eagle0/authservice/**'
|
||||
- '!src/main/go/net/eagle0/authcli/**'
|
||||
- '!src/main/go/net/eagle0/clients/**'
|
||||
- 'src/main/scala/**'
|
||||
- 'src/main/protobuf/**'
|
||||
- '!src/main/protobuf/net/eagle0/eagle/api/auth.proto'
|
||||
- '!src/main/protobuf/net/eagle0/eagle/api/admin/**'
|
||||
- '!src/main/protobuf/net/eagle0/eagle/internal/auth_internal.proto'
|
||||
- 'src/main/resources/**'
|
||||
- 'ci/BUILD.bazel'
|
||||
- 'MODULE.bazel'
|
||||
@@ -161,8 +153,6 @@ jobs:
|
||||
ADMIN_IMAGE: ${{ needs.build-all.outputs.admin_image_tag }}
|
||||
JFR_SIDECAR_IMAGE: ${{ needs.build-all.outputs.jfr_sidecar_image_tag }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||||
GPT_MODEL_NAME: ${{ secrets.GPT_MODEL_NAME }}
|
||||
EAGLE_ENABLE_S3: ${{ secrets.EAGLE_ENABLE_S3 }}
|
||||
DO_SPACES_ACCESS_KEY: ${{ secrets.DO_SPACES_ACCESS_KEY }}
|
||||
@@ -172,14 +162,6 @@ jobs:
|
||||
DISCORD_CLIENT_SECRET: ${{ secrets.DISCORD_CLIENT_SECRET }}
|
||||
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
|
||||
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
|
||||
GH_OAUTH_CLIENT_ID: ${{ secrets.GH_OAUTH_CLIENT_ID }}
|
||||
GH_OAUTH_CLIENT_SECRET: ${{ secrets.GH_OAUTH_CLIENT_SECRET }}
|
||||
APPLE_SIGNIN_CLIENT_ID: ${{ secrets.APPLE_SIGNIN_CLIENT_ID }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_SIGNIN_KEY_ID: ${{ secrets.APPLE_SIGNIN_KEY_ID }}
|
||||
APPLE_SIGNIN_PRIVATE_KEY: ${{ secrets.APPLE_SIGNIN_PRIVATE_KEY }}
|
||||
TWITCH_CLIENT_ID: ${{ secrets.TWITCH_CLIENT_ID }}
|
||||
TWITCH_CLIENT_SECRET: ${{ secrets.TWITCH_CLIENT_SECRET }}
|
||||
SHARDOK_ADDRESS: ${{ secrets.SHARDOK_ADDRESS }}
|
||||
SHARDOK_AUTH_TOKEN: ${{ secrets.SHARDOK_AUTH_TOKEN }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
@@ -210,15 +192,16 @@ jobs:
|
||||
# Create directory structure on remote
|
||||
ssh -i ~/.ssh/deploy_key deploy@"$DO_DROPLET_IP" bash -s << 'SETUP_DIRS'
|
||||
set -e
|
||||
mkdir -p /opt/eagle0/scripts/bin /opt/eagle0/nginx
|
||||
mkdir -p /opt/eagle0/scripts/bin /opt/eagle0/nginx /opt/eagle0/deploy
|
||||
rm -f /opt/eagle0/scripts/bin/warmup
|
||||
SETUP_DIRS
|
||||
|
||||
# Copy files
|
||||
scp -i ~/.ssh/deploy_key docker-compose.prod.yml deploy@"$DO_DROPLET_IP":/opt/eagle0/
|
||||
scp -i ~/.ssh/deploy_key nginx/nginx.conf deploy@"$DO_DROPLET_IP":/opt/eagle0/nginx/
|
||||
scp -i ~/.ssh/deploy_key scripts/deploy-blue-green.sh scripts/warmup-eagle.sh scripts/eagle-exec.sh scripts/eagle-logs.sh deploy@"$DO_DROPLET_IP":/opt/eagle0/scripts/
|
||||
scp -i ~/.ssh/deploy_key scripts/deploy-blue-green.sh scripts/warmup-eagle.sh deploy@"$DO_DROPLET_IP":/opt/eagle0/scripts/
|
||||
scp -i ~/.ssh/deploy_key scripts/bin/warmup deploy@"$DO_DROPLET_IP":/opt/eagle0/scripts/bin/
|
||||
scp -i ~/.ssh/deploy_key deploy/env.template deploy/update-env.sh deploy@"$DO_DROPLET_IP":/opt/eagle0/deploy/
|
||||
|
||||
- name: Deploy to production droplet
|
||||
run: |
|
||||
@@ -226,93 +209,57 @@ jobs:
|
||||
set -ex
|
||||
cd /opt/eagle0
|
||||
|
||||
# =================================================================
|
||||
# CRITICAL: Validate environment variables before proceeding
|
||||
# This catches GitHub Actions secret store hiccups early
|
||||
# =================================================================
|
||||
validate_env() {
|
||||
local var_name="\$1"
|
||||
local var_value="\$2"
|
||||
local default_value="\${3:-}"
|
||||
|
||||
if [ -z "\${var_value}" ]; then
|
||||
echo "ERROR: \${var_name} is empty. GitHub Actions secrets may have failed to load."
|
||||
echo "Please retry the workflow."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check if we got a default value instead of the real secret
|
||||
if [ -n "\${default_value}" ] && [ "\${var_value}" = "\${default_value}" ]; then
|
||||
echo "ERROR: \${var_name} has default value '\${default_value}' instead of the actual secret."
|
||||
echo "This indicates GitHub Actions secrets failed to load. Please retry the workflow."
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
echo "Validating critical environment variables..."
|
||||
|
||||
# These are the raw values from GitHub Actions (before export)
|
||||
# We check them before exporting to catch issues early
|
||||
VALIDATION_FAILED=0
|
||||
|
||||
validate_env "SHARDOK_ADDRESS" "${SHARDOK_ADDRESS}" "" || VALIDATION_FAILED=1
|
||||
validate_env "EAGLE_IMAGE" "${EAGLE_IMAGE}" "" || VALIDATION_FAILED=1
|
||||
validate_env "JWT_PRIVATE_KEY" "${JWT_PRIVATE_KEY}" "" || VALIDATION_FAILED=1
|
||||
validate_env "DO_REGISTRY_TOKEN" "${DO_REGISTRY_TOKEN}" "" || VALIDATION_FAILED=1
|
||||
|
||||
if [ "\${VALIDATION_FAILED}" -eq 1 ]; then
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "DEPLOYMENT ABORTED: Missing critical secrets"
|
||||
echo "This is likely a transient GitHub Actions issue."
|
||||
echo "Please retry the workflow."
|
||||
echo "========================================="
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "All critical environment variables validated successfully."
|
||||
|
||||
# =================================================================
|
||||
# Export environment variables for docker compose
|
||||
# These are passed via heredoc and exported so child processes (docker compose) can access them
|
||||
export EAGLE_IMAGE="${EAGLE_IMAGE}"
|
||||
export ADMIN_IMAGE="${ADMIN_IMAGE}"
|
||||
export JFR_SIDECAR_IMAGE="${JFR_SIDECAR_IMAGE}"
|
||||
export OPENAI_API_KEY="${OPENAI_API_KEY}"
|
||||
export ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY}"
|
||||
export GEMINI_API_KEY="${GEMINI_API_KEY}"
|
||||
export GPT_MODEL_NAME="${GPT_MODEL_NAME:-gpt-4o}"
|
||||
export EAGLE_ENABLE_S3="${EAGLE_ENABLE_S3:-false}"
|
||||
export DO_SPACES_ACCESS_KEY="${DO_SPACES_ACCESS_KEY}"
|
||||
export DO_SPACES_SECRET_KEY="${DO_SPACES_SECRET_KEY}"
|
||||
export JWT_PRIVATE_KEY="${JWT_PRIVATE_KEY}"
|
||||
export DISCORD_CLIENT_ID="${DISCORD_CLIENT_ID}"
|
||||
export DISCORD_CLIENT_SECRET="${DISCORD_CLIENT_SECRET}"
|
||||
export GOOGLE_CLIENT_ID="${GOOGLE_CLIENT_ID}"
|
||||
export GOOGLE_CLIENT_SECRET="${GOOGLE_CLIENT_SECRET}"
|
||||
export GH_OAUTH_CLIENT_ID="${GH_OAUTH_CLIENT_ID}"
|
||||
export GH_OAUTH_CLIENT_SECRET="${GH_OAUTH_CLIENT_SECRET}"
|
||||
export APPLE_SIGNIN_CLIENT_ID="${APPLE_SIGNIN_CLIENT_ID}"
|
||||
export APPLE_TEAM_ID="${APPLE_TEAM_ID}"
|
||||
export APPLE_SIGNIN_KEY_ID="${APPLE_SIGNIN_KEY_ID}"
|
||||
export APPLE_SIGNIN_PRIVATE_KEY="${APPLE_SIGNIN_PRIVATE_KEY}"
|
||||
export TWITCH_CLIENT_ID="${TWITCH_CLIENT_ID}"
|
||||
export TWITCH_CLIENT_SECRET="${TWITCH_CLIENT_SECRET}"
|
||||
export SHARDOK_ADDRESS="${SHARDOK_ADDRESS}"
|
||||
export SHARDOK_AUTH_TOKEN="${SHARDOK_AUTH_TOKEN}"
|
||||
export SENTRY_DSN="${SENTRY_DSN}"
|
||||
export FASTMAIL_API_TOKEN="${FASTMAIL_API_TOKEN}"
|
||||
export FASTMAIL_FROM_EMAIL="${FASTMAIL_FROM_EMAIL}"
|
||||
export FASTMAIL_FROM_NAME="${FASTMAIL_FROM_NAME}"
|
||||
export DO_REGISTRY_TOKEN="${DO_REGISTRY_TOKEN}"
|
||||
# Environment variables passed via heredoc
|
||||
EAGLE_IMAGE="${EAGLE_IMAGE}"
|
||||
ADMIN_IMAGE="${ADMIN_IMAGE}"
|
||||
JFR_SIDECAR_IMAGE="${JFR_SIDECAR_IMAGE}"
|
||||
OPENAI_API_KEY="${OPENAI_API_KEY}"
|
||||
GPT_MODEL_NAME="${GPT_MODEL_NAME}"
|
||||
EAGLE_ENABLE_S3="${EAGLE_ENABLE_S3}"
|
||||
DO_SPACES_ACCESS_KEY="${DO_SPACES_ACCESS_KEY}"
|
||||
DO_SPACES_SECRET_KEY="${DO_SPACES_SECRET_KEY}"
|
||||
JWT_PRIVATE_KEY="${JWT_PRIVATE_KEY}"
|
||||
DISCORD_CLIENT_ID="${DISCORD_CLIENT_ID}"
|
||||
DISCORD_CLIENT_SECRET="${DISCORD_CLIENT_SECRET}"
|
||||
GOOGLE_CLIENT_ID="${GOOGLE_CLIENT_ID}"
|
||||
GOOGLE_CLIENT_SECRET="${GOOGLE_CLIENT_SECRET}"
|
||||
SHARDOK_ADDRESS="${SHARDOK_ADDRESS}"
|
||||
SHARDOK_AUTH_TOKEN="${SHARDOK_AUTH_TOKEN}"
|
||||
SENTRY_DSN="${SENTRY_DSN}"
|
||||
FASTMAIL_API_TOKEN="${FASTMAIL_API_TOKEN}"
|
||||
FASTMAIL_FROM_EMAIL="${FASTMAIL_FROM_EMAIL}"
|
||||
FASTMAIL_FROM_NAME="${FASTMAIL_FROM_NAME}"
|
||||
DO_REGISTRY_TOKEN="${DO_REGISTRY_TOKEN}"
|
||||
|
||||
# Check Docker has IPv6 support
|
||||
if ! cat /etc/docker/daemon.json 2>/dev/null | grep -q '"ip6tables"'; then
|
||||
echo "WARNING: Docker IPv6 not configured. Eagle may not reach Hetzner Shardok."
|
||||
fi
|
||||
|
||||
# Update env vars
|
||||
chmod +x deploy/update-env.sh
|
||||
cd deploy && ./update-env.sh \
|
||||
"EAGLE_IMAGE=\${EAGLE_IMAGE}" \
|
||||
"ADMIN_IMAGE=\${ADMIN_IMAGE}" \
|
||||
"JFR_SIDECAR_IMAGE=\${JFR_SIDECAR_IMAGE}" \
|
||||
"OPENAI_API_KEY=\${OPENAI_API_KEY}" \
|
||||
"GPT_MODEL_NAME=\${GPT_MODEL_NAME:-gpt-4o}" \
|
||||
"EAGLE_ENABLE_S3=\${EAGLE_ENABLE_S3:-false}" \
|
||||
"DO_SPACES_ACCESS_KEY=\${DO_SPACES_ACCESS_KEY}" \
|
||||
"DO_SPACES_SECRET_KEY=\${DO_SPACES_SECRET_KEY}" \
|
||||
"JWT_PRIVATE_KEY=\${JWT_PRIVATE_KEY}" \
|
||||
"DISCORD_CLIENT_ID=\${DISCORD_CLIENT_ID}" \
|
||||
"DISCORD_CLIENT_SECRET=\${DISCORD_CLIENT_SECRET}" \
|
||||
"GOOGLE_CLIENT_ID=\${GOOGLE_CLIENT_ID}" \
|
||||
"GOOGLE_CLIENT_SECRET=\${GOOGLE_CLIENT_SECRET}" \
|
||||
"SHARDOK_ADDRESS=\${SHARDOK_ADDRESS:-shardok:40042}" \
|
||||
"SHARDOK_AUTH_TOKEN=\${SHARDOK_AUTH_TOKEN}" \
|
||||
"SENTRY_DSN=\${SENTRY_DSN}" \
|
||||
"FASTMAIL_API_TOKEN=\${FASTMAIL_API_TOKEN}" \
|
||||
"FASTMAIL_FROM_EMAIL=\${FASTMAIL_FROM_EMAIL}" \
|
||||
"FASTMAIL_FROM_NAME=\${FASTMAIL_FROM_NAME}"
|
||||
cd ..
|
||||
|
||||
# Login to registry
|
||||
echo "\$DO_REGISTRY_TOKEN" | docker login registry.digitalocean.com -u "\$DO_REGISTRY_TOKEN" --password-stdin
|
||||
|
||||
@@ -340,43 +287,20 @@ jobs:
|
||||
|
||||
echo "All images pulled successfully"
|
||||
|
||||
# =================================================================
|
||||
# Verify Shardok connectivity before proceeding with deployment
|
||||
# This catches network/firewall issues early
|
||||
# =================================================================
|
||||
echo "Verifying Shardok connectivity..."
|
||||
SHARDOK_HOST=\$(echo "\${SHARDOK_ADDRESS}" | cut -d: -f1)
|
||||
SHARDOK_PORT=\$(echo "\${SHARDOK_ADDRESS}" | cut -d: -f2)
|
||||
|
||||
# Try to connect to Shardok (timeout after 10 seconds)
|
||||
if nc -z -w 10 "\${SHARDOK_HOST}" "\${SHARDOK_PORT}" 2>/dev/null; then
|
||||
echo "Shardok connectivity verified: \${SHARDOK_ADDRESS} is reachable"
|
||||
else
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "ERROR: Cannot reach Shardok at \${SHARDOK_ADDRESS}"
|
||||
echo "This may indicate:"
|
||||
echo " - Shardok server is not running on Hetzner"
|
||||
echo " - Network/firewall issues between DigitalOcean and Hetzner"
|
||||
echo " - Incorrect SHARDOK_ADDRESS configuration"
|
||||
echo ""
|
||||
echo "DEPLOYMENT ABORTED: Shardok must be reachable for battles to work."
|
||||
echo "========================================="
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Stop local shardok container if running (now runs on Hetzner)
|
||||
docker stop shardok-server 2>/dev/null || true
|
||||
docker rm shardok-server 2>/dev/null || true
|
||||
|
||||
# Deploy Eagle with blue-green (handles eagle, nginx, admin, jfr-sidecar)
|
||||
# Note: Shardok runs on Hetzner, deployed separately via shardok_arm64_build.yml
|
||||
# Note: Auth is deployed separately via auth_build.yml - do NOT touch auth here
|
||||
chmod +x /opt/eagle0/scripts/*.sh
|
||||
[ -f "/opt/eagle0/scripts/bin/warmup" ] && chmod +x /opt/eagle0/scripts/bin/warmup
|
||||
GIT_SHA=\$(echo "\${EAGLE_IMAGE}" | sed 's/.*://')
|
||||
/opt/eagle0/scripts/deploy-blue-green.sh "\${GIT_SHA}"
|
||||
|
||||
# Ensure auth is running
|
||||
docker compose -f docker-compose.prod.yml up -d auth
|
||||
|
||||
# Verify
|
||||
sleep 10
|
||||
docker compose -f docker-compose.prod.yml ps
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
name: Eagle Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'src/main/scala/**'
|
||||
- 'src/main/protobuf/net/eagle0/eagle/**'
|
||||
- 'src/main/protobuf/net/eagle0/common/**'
|
||||
- 'WORKSPACE'
|
||||
- 'MODULE.bazel'
|
||||
- 'BUILD.bazel'
|
||||
- '.github/workflows/eagle_build.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/main/scala/**'
|
||||
- 'src/main/protobuf/net/eagle0/eagle/**'
|
||||
- 'src/main/protobuf/net/eagle0/common/**'
|
||||
- 'WORKSPACE'
|
||||
- 'MODULE.bazel'
|
||||
- 'BUILD.bazel'
|
||||
- '.github/workflows/eagle_build.yml'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: [self-hosted, bazel]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: false
|
||||
- name: Build Eagle server
|
||||
run: bazel build //src/main/scala/net/eagle0/eagle:eagle_server
|
||||
@@ -5,16 +5,15 @@ on:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- ".github/workflows/installer_build.yml"
|
||||
- "src/main/go/net/eagle0/clients/win/installer/**"
|
||||
- "src/main/csharp/net/eagle0/clients/win/installer/**"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/installer_build.yml"
|
||||
- "src/main/go/net/eagle0/clients/win/installer/**"
|
||||
- "src/main/csharp/net/eagle0/clients/win/installer/**"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: write # Required to delete artifacts after deploy
|
||||
|
||||
jobs:
|
||||
build-installer:
|
||||
@@ -26,47 +25,47 @@ jobs:
|
||||
lfs: false
|
||||
clean: false
|
||||
|
||||
- name: Build Go installer for Windows
|
||||
- name: Setup .NET 8
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
- name: Inject manifest public key
|
||||
env:
|
||||
MANIFEST_PUBLIC_KEY: ${{ secrets.MANIFEST_PUBLIC_KEY }}
|
||||
run: |
|
||||
# Require manifest public key for production builds
|
||||
if [ -z "$MANIFEST_PUBLIC_KEY" ]; then
|
||||
echo "ERROR: MANIFEST_PUBLIC_KEY secret is not set"
|
||||
echo "The installer requires a public key for manifest signature verification"
|
||||
exit 1
|
||||
if [ -n "$MANIFEST_PUBLIC_KEY" ]; then
|
||||
CONFIG_FILE="src/main/csharp/net/eagle0/clients/win/installer/EagleInstaller/configuration.txt"
|
||||
echo "manifest_public_key = $MANIFEST_PUBLIC_KEY" >> "$CONFIG_FILE"
|
||||
echo "Injected manifest public key into configuration.txt"
|
||||
cat "$CONFIG_FILE"
|
||||
else
|
||||
echo "MANIFEST_PUBLIC_KEY not set, skipping injection"
|
||||
fi
|
||||
|
||||
# Build Windows installer with WebView GUI (uses CGO cross-compilation)
|
||||
# Use --action_env to pass the signing key into the genrule sandbox
|
||||
bazel build //src/main/go/net/eagle0/clients/win/installer:eagle_installer_windows_amd64_webview --stamp --action_env=MANIFEST_PUBLIC_KEY
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore src/main/csharp/net/eagle0/clients/win/installer/EagleInstaller/EagleInstaller.csproj
|
||||
|
||||
# Copy to output directory
|
||||
rm -rf ./installer-output
|
||||
mkdir -p ./installer-output
|
||||
cp bazel-bin/src/main/go/net/eagle0/clients/win/installer/Eagle0.exe ./installer-output/Eagle0.exe
|
||||
|
||||
echo "Go installer size: $(ls -lh ./installer-output/Eagle0.exe | awk '{print $5}')"
|
||||
- name: Build installer
|
||||
run: dotnet publish src/main/csharp/net/eagle0/clients/win/installer/EagleInstaller/EagleInstaller.csproj -c Release -r win-x64 --self-contained true --output ./installer-output
|
||||
|
||||
- name: Archive installer binary
|
||||
if: success() || failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: eagle-installer
|
||||
path: ./installer-output/
|
||||
retention-days: 1
|
||||
path: ./installer-output/EagleInstaller.exe
|
||||
|
||||
- name: Verify installer exists
|
||||
if: success()
|
||||
run: |
|
||||
echo "=== Installer output directory ==="
|
||||
ls -lh ./installer-output/
|
||||
|
||||
if [ ! -f "./installer-output/Eagle0.exe" ]; then
|
||||
echo "ERROR: Eagle0.exe not found"
|
||||
if [ ! -f "./installer-output/EagleInstaller.exe" ]; then
|
||||
echo "ERROR: EagleInstaller.exe not found at expected location"
|
||||
echo "Directory contents:"
|
||||
ls -la ./installer-output/
|
||||
exit 1
|
||||
fi
|
||||
echo "Installer found"
|
||||
echo "Installer found at correct location"
|
||||
|
||||
- name: Deploy installer
|
||||
if: success() && github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
@@ -74,24 +73,25 @@ jobs:
|
||||
ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
run: |
|
||||
INSTALLER_PATH="$(pwd)/installer-output/Eagle0.exe"
|
||||
echo "Deploying Go installer to installer/Eagle0.exe"
|
||||
bazel run //src/main/go/net/eagle0/build/installer_build_handler:installer_build_handler -- "$INSTALLER_PATH" "installer/Eagle0.exe"
|
||||
INSTALLER_PATH="$(pwd)/installer-output/EagleInstaller.exe"
|
||||
echo "Using absolute path: $INSTALLER_PATH"
|
||||
bazel run //src/main/go/net/eagle0/build/installer_build_handler:installer_build_handler -- "$INSTALLER_PATH"
|
||||
|
||||
- name: Update manifest
|
||||
- name: Update unified manifest
|
||||
if: success() && github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
env:
|
||||
ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
MANIFEST_SIGNING_KEY: ${{ secrets.MANIFEST_SIGNING_KEY }}
|
||||
run: |
|
||||
INSTALLER_SHA=$(sha256sum ./installer-output/Eagle0.exe | cut -d' ' -f1)
|
||||
# Create installer manifest content
|
||||
INSTALLER_SHA=$(sha256sum ./installer-output/EagleInstaller.exe | cut -d' ' -f1)
|
||||
echo "installer_version=$INSTALLER_SHA" > /tmp/installer_manifest.txt
|
||||
echo "installer_url=installer/Eagle0.exe" >> /tmp/installer_manifest.txt
|
||||
echo "installer_url=installer/EagleInstaller.exe" >> /tmp/installer_manifest.txt
|
||||
|
||||
echo "=== Manifest content ==="
|
||||
echo "=== Installer manifest content ==="
|
||||
cat /tmp/installer_manifest.txt
|
||||
echo "========================"
|
||||
echo "=================================="
|
||||
|
||||
# Write signing key to temp file (if available)
|
||||
SIGNING_ARGS=""
|
||||
@@ -104,22 +104,8 @@ jobs:
|
||||
echo "Warning: MANIFEST_SIGNING_KEY not set, manifest will be unsigned"
|
||||
fi
|
||||
|
||||
# Update the v2 manifest at installer/v2/eagle0_manifest.txt
|
||||
bazel run //src/main/go/net/eagle0/build/manifest_manager:manifest_manager -- installer-v2 /tmp/installer_manifest.txt $SIGNING_ARGS
|
||||
# Update the unified manifest (with optional signing)
|
||||
bazel run //src/main/go/net/eagle0/build/manifest_manager:manifest_manager -- installer /tmp/installer_manifest.txt $SIGNING_ARGS
|
||||
|
||||
rm -f /tmp/manifest_signing_key
|
||||
|
||||
- name: Delete all installer artifacts
|
||||
if: success() && github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
# Delete ALL eagle-installer artifacts to free up storage
|
||||
echo "Fetching all eagle-installer artifacts..."
|
||||
artifact_ids=$(gh api "repos/${{ github.repository }}/actions/artifacts" \
|
||||
--paginate -q '.artifacts[] | select(.name == "eagle-installer") | .id')
|
||||
for id in $artifact_ids; do
|
||||
echo "Deleting artifact ID: $id"
|
||||
gh api -X DELETE "repos/${{ github.repository }}/actions/artifacts/$id" || true
|
||||
done
|
||||
echo "Cleanup complete"
|
||||
# Cleanup
|
||||
rm -f /tmp/manifest_signing_key
|
||||
@@ -1,62 +0,0 @@
|
||||
name: iOS Addressables Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- ".github/workflows/ios_addressables_build.yml"
|
||||
- "src/main/csharp/net/eagle0/clients/unity/**"
|
||||
- "ci/github_actions/build_ios_addressables.sh"
|
||||
- "ci/github_actions/upload_addressables.sh"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/ios_addressables_build.yml"
|
||||
- "src/main/csharp/net/eagle0/clients/unity/**"
|
||||
- "ci/github_actions/build_ios_addressables.sh"
|
||||
- "ci/github_actions/upload_addressables.sh"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-ios-addressables:
|
||||
runs-on: [self-hosted, macOS, unity-mac]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: true
|
||||
clean: true
|
||||
|
||||
- name: Pull LFS files
|
||||
run: git lfs pull
|
||||
|
||||
- name: Restore Library/
|
||||
env:
|
||||
UNITY_CACHE_PLATFORM: ios
|
||||
run: ./ci/github_actions/restore_library.sh
|
||||
|
||||
- name: Build iOS Addressables
|
||||
run: ./ci/github_actions/build_ios_addressables.sh "/tmp/eagle0/editor_ios_addressables.log"
|
||||
|
||||
- name: Persist Library/
|
||||
if: success()
|
||||
env:
|
||||
UNITY_CACHE_PLATFORM: ios
|
||||
run: ./ci/github_actions/persist_library.sh
|
||||
|
||||
- name: Upload Addressables to CDN
|
||||
if: success() && github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
|
||||
env:
|
||||
ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
run: ./ci/github_actions/upload_addressables.sh iOS
|
||||
|
||||
- name: Archive Build Log
|
||||
if: success() || failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: editor_ios_addressables.log
|
||||
path: /tmp/eagle0/editor_ios_addressables.log
|
||||
retention-days: 5
|
||||
@@ -20,7 +20,6 @@ on:
|
||||
- "scripts/notarize_wait.sh"
|
||||
- "ci/github_actions/build_mac.sh"
|
||||
- "ci/github_actions/build_unity_mac.sh"
|
||||
- "ci/github_actions/upload_addressables.sh"
|
||||
- "ci/mac/**"
|
||||
pull_request:
|
||||
paths:
|
||||
@@ -39,7 +38,6 @@ on:
|
||||
- "scripts/notarize_wait.sh"
|
||||
- "ci/github_actions/build_mac.sh"
|
||||
- "ci/github_actions/build_unity_mac.sh"
|
||||
- "ci/github_actions/upload_addressables.sh"
|
||||
- "ci/mac/**"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -51,7 +49,6 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: write # Required to delete artifacts after deploy
|
||||
|
||||
jobs:
|
||||
build-and-sign:
|
||||
@@ -64,33 +61,21 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: true
|
||||
clean: true # Remove untracked files like old SparklePlugin.bundle
|
||||
clean: false
|
||||
fetch-depth: 0 # For version numbering from git history
|
||||
|
||||
- name: Pull LFS files
|
||||
run: git lfs pull
|
||||
|
||||
- name: Restore Library/
|
||||
env:
|
||||
UNITY_CACHE_PLATFORM: mac
|
||||
run: ./ci/github_actions/restore_library.sh
|
||||
|
||||
- name: Build Mac Unity
|
||||
run: ./ci/github_actions/build_unity_mac.sh "/tmp/eagle0/eagle0MAC"
|
||||
|
||||
- name: Persist Library/
|
||||
if: success()
|
||||
env:
|
||||
UNITY_CACHE_PLATFORM: mac
|
||||
run: ./ci/github_actions/persist_library.sh
|
||||
|
||||
- name: Upload Addressables to CDN
|
||||
if: success() && github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
|
||||
env:
|
||||
ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
run: ./ci/github_actions/upload_addressables.sh StandaloneOSX
|
||||
|
||||
- name: Inject Sparkle Framework
|
||||
if: success()
|
||||
env:
|
||||
@@ -176,7 +161,7 @@ jobs:
|
||||
if: success() && steps.check-deploy.outputs.should_deploy == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: signed-mac-app-${{ github.run_id }}
|
||||
name: signed-mac-app
|
||||
path: /tmp/eagle0/eagle0MAC/eagle0.app.zip
|
||||
retention-days: 1
|
||||
|
||||
@@ -186,7 +171,6 @@ jobs:
|
||||
with:
|
||||
name: editor_mac.log
|
||||
path: /tmp/eagle0/editor_mac.log
|
||||
retention-days: 5
|
||||
|
||||
wait-notarization:
|
||||
needs: build-and-sign
|
||||
@@ -204,7 +188,7 @@ jobs:
|
||||
- name: Download signed app
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: signed-mac-app-${{ github.run_id }}
|
||||
name: signed-mac-app
|
||||
path: /tmp/eagle0/eagle0MAC
|
||||
|
||||
- name: Unzip signed app
|
||||
@@ -232,7 +216,7 @@ jobs:
|
||||
- name: Upload notarized app
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: notarized-mac-app-${{ github.run_id }}
|
||||
name: notarized-mac-app
|
||||
path: /tmp/eagle0/eagle0MAC/eagle0.app.zip
|
||||
retention-days: 1
|
||||
|
||||
@@ -252,7 +236,7 @@ jobs:
|
||||
- name: Download notarized app
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: notarized-mac-app-${{ github.run_id }}
|
||||
name: notarized-mac-app
|
||||
path: /tmp/eagle0/eagle0MAC
|
||||
|
||||
- name: Unzip notarized app
|
||||
@@ -272,61 +256,13 @@ jobs:
|
||||
echo "$SPARKLE_EDDSA_PRIVATE_KEY" > "$SPARKLE_PRIVATE_KEY_PATH"
|
||||
chmod 600 "$SPARKLE_PRIVATE_KEY_PATH"
|
||||
|
||||
# Install dmgbuild (creates .DS_Store programmatically, no AppleScript needed)
|
||||
pip3 install dmgbuild
|
||||
|
||||
# Background image for styled DMG
|
||||
BACKGROUND_PATH="$(pwd)/ci/mac/dmg/background.png"
|
||||
|
||||
# Read version from the built app's Info.plist to ensure appcast matches the actual app
|
||||
APP_PATH="/tmp/eagle0/eagle0MAC/eagle0.app"
|
||||
BUILD_NUMBER=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$APP_PATH/Contents/Info.plist")
|
||||
VERSION=$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$APP_PATH/Contents/Info.plist")
|
||||
VERSION=$(git describe --tags --always)
|
||||
BUILD_NUMBER=$(git rev-list --count HEAD)
|
||||
|
||||
bazel run //src/main/go/net/eagle0/build/mac_build_handler:mac_build_handler -- \
|
||||
"/tmp/eagle0/eagle0MAC/eagle0.app" \
|
||||
"$VERSION" \
|
||||
"$BUILD_NUMBER" \
|
||||
"$BACKGROUND_PATH" \
|
||||
"$SPARKLE_PRIVATE_KEY_PATH"
|
||||
|
||||
rm "$SPARKLE_PRIVATE_KEY_PATH"
|
||||
|
||||
- name: Delete this run's Mac app artifacts
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
# Delete this run's artifacts (names include run ID to avoid conflicts)
|
||||
for artifact_name in signed-mac-app-${{ github.run_id }} notarized-mac-app-${{ github.run_id }}; do
|
||||
echo "Deleting artifact: $artifact_name"
|
||||
artifact_id=$(gh api "repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts" \
|
||||
-q ".artifacts[] | select(.name == \"$artifact_name\") | .id")
|
||||
if [ -n "$artifact_id" ]; then
|
||||
echo "Deleting artifact ID: $artifact_id"
|
||||
gh api -X DELETE "repos/${{ github.repository }}/actions/artifacts/$artifact_id" || true
|
||||
fi
|
||||
done
|
||||
echo "Cleanup complete"
|
||||
|
||||
# Cleanup job runs regardless of success/failure to prevent artifact accumulation
|
||||
cleanup:
|
||||
needs: [build-and-sign, wait-notarization, deploy]
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Delete this run's Mac app artifacts
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
# Delete this run's artifacts (names include run ID to avoid conflicts)
|
||||
for artifact_name in signed-mac-app-${{ github.run_id }} notarized-mac-app-${{ github.run_id }}; do
|
||||
echo "Deleting artifact: $artifact_name"
|
||||
artifact_id=$(gh api "repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts" \
|
||||
-q ".artifacts[] | select(.name == \"$artifact_name\") | .id")
|
||||
if [ -n "$artifact_id" ]; then
|
||||
echo "Deleting artifact ID: $artifact_id"
|
||||
gh api -X DELETE "repos/${{ github.repository }}/actions/artifacts/$artifact_id" || true
|
||||
fi
|
||||
done
|
||||
echo "Cleanup complete"
|
||||
|
||||
@@ -46,26 +46,26 @@ jobs:
|
||||
for REPO in $REPOS; do
|
||||
echo "=== Processing repository: ${REPO} ==="
|
||||
|
||||
# Get all manifests with their tags and dates using JSON output for reliable parsing
|
||||
MANIFESTS_JSON=$(doctl registry repository list-manifests "${REPO}" --output json 2>/dev/null || echo "[]")
|
||||
# Get all manifests with their tags and dates
|
||||
# Filter out header row and empty lines
|
||||
MANIFESTS=$(doctl registry repository list-manifests "${REPO}" --format Digest,UpdatedAt,Tags --no-header 2>/dev/null | grep -v '^Digest' | grep -v '^$' || echo "")
|
||||
|
||||
if [ "$MANIFESTS_JSON" = "[]" ] || [ -z "$MANIFESTS_JSON" ]; then
|
||||
if [ -z "$MANIFESTS" ]; then
|
||||
echo " No manifests found"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Parse JSON and process each manifest
|
||||
echo "$MANIFESTS_JSON" | jq -r '.[] | "\(.digest) \(.updated_at) \(.tags // [] | join(","))"' | while read -r DIGEST UPDATED_AT TAGS; do
|
||||
echo "$MANIFESTS" | while read -r DIGEST UPDATED_AT TAGS; do
|
||||
# Skip if no digest or if it doesn't look like a valid digest (sha256:...)
|
||||
if [ -z "$DIGEST" ] || ! echo "$DIGEST" | grep -q '^sha256:'; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Parse the date (ISO 8601 format from JSON)
|
||||
# Parse the date
|
||||
MANIFEST_DATE=$(date -d "$UPDATED_AT" +%s 2>/dev/null || echo "0")
|
||||
|
||||
# Skip protected tags (latest, arm64-latest)
|
||||
if echo ",$TAGS," | grep -qE ',(latest|arm64-latest),'; then
|
||||
if echo "$TAGS" | grep -qE '(^|,)(latest|arm64-latest)(,|$)'; then
|
||||
echo " KEEP: ${DIGEST:0:20}... (protected tag: $TAGS)"
|
||||
continue
|
||||
fi
|
||||
|
||||
@@ -17,7 +17,6 @@ on:
|
||||
- "ci/github_actions/build_unity.sh"
|
||||
- "ci/github_actions/restore_library.sh"
|
||||
- "ci/github_actions/persist_library.sh"
|
||||
- "ci/github_actions/upload_addressables.sh"
|
||||
- "MODULE.bazel"
|
||||
- "WORKSPACE"
|
||||
workflow_dispatch:
|
||||
@@ -36,7 +35,6 @@ on:
|
||||
- "ci/github_actions/build_unity.sh"
|
||||
- "ci/github_actions/restore_library.sh"
|
||||
- "ci/github_actions/persist_library.sh"
|
||||
- "ci/github_actions/upload_addressables.sh"
|
||||
- "MODULE.bazel"
|
||||
- "WORKSPACE"
|
||||
|
||||
@@ -51,26 +49,15 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: true
|
||||
clean: true # Remove untracked files from previous builds
|
||||
clean: false
|
||||
- name: Pull lfs files
|
||||
run: git lfs pull
|
||||
- name: Restore Library/
|
||||
env:
|
||||
UNITY_CACHE_PLATFORM: windows
|
||||
run: ./ci/github_actions/restore_library.sh
|
||||
- name: Build Windows unity
|
||||
run: ./ci/github_actions/build_unity.sh "/tmp/eagle0/eagle0WIN"
|
||||
- name: Persist Library/
|
||||
if: success()
|
||||
env:
|
||||
UNITY_CACHE_PLATFORM: windows
|
||||
run: ./ci/github_actions/persist_library.sh
|
||||
- name: Upload Addressables to CDN
|
||||
if: success() && github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
|
||||
env:
|
||||
ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
run: ./ci/github_actions/upload_addressables.sh StandaloneWindows64
|
||||
- name: Deploy Windows unity
|
||||
if: success() && github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
env:
|
||||
@@ -96,8 +83,8 @@ jobs:
|
||||
echo "Warning: MANIFEST_SIGNING_KEY not set, manifest will be unsigned"
|
||||
fi
|
||||
|
||||
# Update the v2 manifest at installer/v2/eagle0_manifest.txt
|
||||
bazel run //src/main/go/net/eagle0/build/manifest_manager:manifest_manager -- unity3d-v2 /tmp/unity_manifest.txt $SIGNING_ARGS
|
||||
# Update the unified manifest (with optional signing)
|
||||
bazel run //src/main/go/net/eagle0/build/manifest_manager:manifest_manager -- unity3d /tmp/unity_manifest.txt $SIGNING_ARGS
|
||||
|
||||
# Cleanup
|
||||
rm -f /tmp/manifest_signing_key
|
||||
@@ -106,5 +93,4 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: editor_win.log
|
||||
path: /tmp/eagle0/editor_win.log
|
||||
retention-days: 5
|
||||
path: /tmp/eagle0/editor_win.log
|
||||
-12
@@ -32,15 +32,3 @@ nogo(
|
||||
vet = True,
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
# Dependency constraint tests
|
||||
# These verify architectural boundaries are maintained
|
||||
sh_test(
|
||||
name = "build_deps_test",
|
||||
srcs = ["scripts/check_build_deps.sh"],
|
||||
args = ["--ci"],
|
||||
tags = [
|
||||
"local", # Needs bazel query access
|
||||
"no-sandbox",
|
||||
],
|
||||
)
|
||||
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
# Deproto Migration Plan
|
||||
|
||||
This document tracks the migration from protobuf types to native Scala models inside the Eagle game engine.
|
||||
|
||||
## Architectural Decisions
|
||||
|
||||
1. **Keep proto for persistence**: Yes - protobuf is used for persisting game state
|
||||
2. **Keep proto for Shardok communication**: Yes - protobuf is used for Eagle-Shardok gRPC communication
|
||||
3. **Use Scala views inside the library**: Yes - use native Scala types like `ProvinceView`, `FactionView`, `HeroView`, etc. within the library code
|
||||
|
||||
## Recent Completed Work
|
||||
|
||||
### GameState Round-Trip Elimination (PRs #4913, #4914, #4915)
|
||||
|
||||
Eliminated wasteful Scala→proto→Scala conversions in the hot path:
|
||||
|
||||
1. **LLM Pipeline** (#4913): `LlmRequestWithGameState` now uses Scala `GameState` instead of proto. All ~38 prompt generators updated to use Scala model types (`FactionT`, `HeroT`, `ProvinceT`).
|
||||
|
||||
2. **ActionWithResultingState Caching** (#4914): Added `precomputedScalaState: Option[GameState]` to cache Scala state when available, avoiding `fromProto()` conversion in `stateAfter()`.
|
||||
|
||||
3. **PostResults Simplification** (#4915): Changed `PostResults.gameState` from proto to `Option[GameState]` (Scala), eliminating `toProto()` calls when creating PostResults.
|
||||
|
||||
## Migration Pattern
|
||||
|
||||
The codebase follows a **Legacy* pattern** for separating proto-dependent and protoless code:
|
||||
|
||||
- **Protoless utilities**: `FactionUtils`, `HeroUtils`, `ProvinceUtils`, `ProvinceDistances`, etc.
|
||||
- **Proto-dependent utilities**: `LegacyFactionUtils`, `LegacyHeroUtils`, `LegacyProvinceUtils`, `LegacyProvinceDistances`, etc.
|
||||
|
||||
When migrating a file:
|
||||
1. Create a `Legacy*` version containing the proto-dependent methods
|
||||
2. Keep the original file name for protoless methods
|
||||
3. Update callers to use the appropriate version based on their context
|
||||
|
||||
## Migration Status
|
||||
|
||||
### Fully Protoless (no proto imports)
|
||||
|
||||
**Utilities:**
|
||||
- [x] `FactionUtils` - has protoless `ownedNeighbors` method
|
||||
- [x] `ProvinceDistances` - split into protoless + `LegacyProvinceDistances`
|
||||
- [x] `SwornBrotherChooser` - fully protoless (removed `bestChoiceProto`)
|
||||
|
||||
**Command Selectors (all use native GameState):**
|
||||
- [x] `AllianceOfferCommandSelector`
|
||||
- [x] `AlmsCommandSelector`
|
||||
- [x] `AttackCommandChooser`
|
||||
- [x] `ExpandCommandSelector`
|
||||
- [x] `HeroGiftCommandSelector`
|
||||
- [x] `ImproveCommandSelector`
|
||||
- [x] `MarchTowardProvinceCommandChooser` - in AI folder, uses native GameState (callers convert)
|
||||
- [x] `OrganizeCommandSelector`
|
||||
- [x] `RansomOfferHelpers`
|
||||
- [x] `SeekMoreLeadersCommandChooser` - in AI folder, uses native GameState
|
||||
- [x] `TruceOfferCommandSelector`
|
||||
- [x] `TrustForDiplomacy`
|
||||
|
||||
**Quest Command Selectors (all protoless):**
|
||||
- [x] `AllianceQuestCommandChooser`
|
||||
- [x] `AlmsAcrossRealmQuestCommandChooser`
|
||||
- [x] `AlmsToProvinceQuestCommandChooser`
|
||||
- [x] `DismissSpecificVassalCommandChooser`
|
||||
- [x] `GiveToHeroesAcrossRealmQuestCommandChooser`
|
||||
- [x] `GiveToHeroesInProvinceQuestCommandChooser`
|
||||
- [x] `ImproveQuestCommandChooser`
|
||||
- [x] `QuestCommandChooser`
|
||||
- [x] `TruceCountQuestCommandChooser`
|
||||
- [x] `TruceWithFactionQuestCommandChooser`
|
||||
|
||||
### Fully Protoless
|
||||
|
||||
- [x] `AIClientUtils` - has protoless overloads (`takenHeroIdsForMarchTowardFocus`, `mostPowerfulHeroes`)
|
||||
- [x] `AttackCommandChooser` - uses Scala `GameState` and `BattalionViewC` (Scala view type)
|
||||
- [x] `BattalionPower` - has `estimatedPower(BattalionViewC)` for recon data with optional stats
|
||||
- [x] `CommandChoiceHelpers` - fully protoless, uses Scala `GameState` throughout
|
||||
- [x] `ProvinceGoldSurplusCalculator` - fully protoless (callers use converters)
|
||||
- [x] `HeroSelector` - fully protoless (removed dead `minimallyFatiguedHeroesProto`)
|
||||
|
||||
### AI Layer ✅ COMPLETE
|
||||
|
||||
All AI and command chooser code is now fully protoless:
|
||||
- [x] `AttackDecisionCommandChooser` - uses Scala GameState
|
||||
- [x] `CommandChooser` - trait uses Scala GameState
|
||||
- [x] `FulfillQuestsCommandSelector` - uses Scala GameState
|
||||
- [x] `MidGameAIClient` - uses Scala GameState internally
|
||||
|
||||
### Still Using Proto GameState (Boundary Code)
|
||||
|
||||
These files use proto GameState because they're at system boundaries:
|
||||
|
||||
**View Filters (client projection):**
|
||||
- `view_filters/GameStateViewFilter` - has Scala overload, uses Scala sub-filters
|
||||
- `view_filters/ProvinceViewFilter` - has Scala overloads for some methods
|
||||
- `view_filters/FactionViewFilter` - has Scala overload
|
||||
- `view_filters/HeroViewFilter` - has Scala overload
|
||||
- `view_filters/BattalionNameFilter` - has Scala overload
|
||||
- `view_filters/BattleFilter` - has Scala overload
|
||||
|
||||
**Legacy Utilities (to be deprecated):**
|
||||
- `LegacyProvinceDistances`, `LegacyFactionUtils`, `LegacyHeroUtils`, etc.
|
||||
- Used by code that still needs proto GameState
|
||||
|
||||
**Persistence/Action System:**
|
||||
- `ActionResultTApplier`, `ActionResultProtoApplier` - apply results to proto state
|
||||
- `ActionWithResultingState` - caches both proto and Scala state
|
||||
|
||||
**Shardok Interface (gRPC boundary):**
|
||||
- `ShardokInterfaceGrpcClient`, `ShardokInterfaceProxy` - must use proto for C++ communication
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Phase 1-3: AI Layer ✅ COMPLETE
|
||||
|
||||
The entire AI decision-making layer is now protoless.
|
||||
|
||||
### Phase 4: View Filters ✅ COMPLETE
|
||||
|
||||
The view_filters package migration is complete:
|
||||
|
||||
**Completed:**
|
||||
- [x] `GameStateViewFilter` - added Scala GameState overload (uses Scala sub-filters)
|
||||
- [x] `ProvinceViewFilter` - already has Scala overloads for server-side views
|
||||
- [x] `HumanPlayerClientConnectionState` - updated to pass Scala GameState directly
|
||||
- [x] `HeroViewFilter` - added Scala overload
|
||||
- [x] `FactionViewFilter` - added Scala overload
|
||||
- [x] `Visibility` - added Scala overloads
|
||||
|
||||
**Still Using Proto:**
|
||||
- [x] `BattalionNameFilter` - has Scala overload
|
||||
- [x] `BattleFilter` - has Scala overload
|
||||
- [ ] `ActionResultFilter` - uses proto internally (boundary code)
|
||||
|
||||
**Strategy:**
|
||||
1. Add Scala GameState overloads to view filter methods
|
||||
2. Update callers to pass Scala GameState where available
|
||||
3. Eventually deprecate proto versions
|
||||
|
||||
### Phase 5: Legacy Utility Cleanup (IN PROGRESS)
|
||||
|
||||
Remove Legacy* utilities by migrating remaining callers:
|
||||
1. Identify callers of each Legacy* util
|
||||
2. Update callers to use protoless versions
|
||||
3. Delete Legacy* files when no longer needed
|
||||
|
||||
**Deleted (no production callers):**
|
||||
- [x] `LegacyProvinceDistances` - deleted (no callers)
|
||||
- [x] `LegacyBattalionSuitability` - deleted (no callers)
|
||||
- [x] `LegacyFoodConsumptionUtils` - deleted (no callers)
|
||||
- [x] `LegacyHandleRiotUtils` - deleted (no callers)
|
||||
|
||||
**Refactored to Thin Wrappers (delegating to protoless versions):**
|
||||
- [x] `LegacyRansomValidity` - already a thin wrapper delegating to `RansomValidity`
|
||||
- [x] `LegacyRecruitmentOdds` - refactored to delegate to `RecruitmentOdds`
|
||||
|
||||
**Parallel Implementations (proto mirrors protoless):**
|
||||
- [x] `FactionUtils` / `LegacyFactionUtils` - both have matching APIs; LegacyFactionUtils used by boundary code (24+ callers)
|
||||
- [x] `HeroUtils` / `LegacyHeroUtils` - both have matching APIs; LegacyHeroUtils used by boundary code (10 callers)
|
||||
- [x] `ProvinceUtils` / `LegacyProvinceUtils` - both have matching APIs; LegacyProvinceUtils used by boundary code (20 callers: availability factories, view filters)
|
||||
|
||||
**Parallel Implementations (awaiting migration of callers):**
|
||||
- [x] `BattalionUtils` / `LegacyBattalionUtils` - both have matching core methods; LegacyBattalionUtils used by boundary code (4 callers)
|
||||
- [x] `BattalionViewFilter` / `LegacyBattalionViewFilter` - protoless version exists; Legacy used by view filters, action appliers (3 callers)
|
||||
- [x] `BattalionTypeFinder` / `LegacyBattalionTypeFinder` - protoless version exists; Legacy used by validators (1 caller: RuntimeValidator)
|
||||
|
||||
### Recent Caller Migration
|
||||
|
||||
**CheckForFulfilledQuestsAction** - migrated to use protoless `BattalionTypeFinder`:
|
||||
- Changed `battalionTypes` parameter from proto `Vector[BattalionType]` to Scala `Vector[BattalionType]`
|
||||
- Updated callers (EngineImpl, EndVassalCommandsPhaseAction) to pass Scala types directly
|
||||
- Eliminated wasteful `BattalionTypeConverter.toProto()` conversions
|
||||
|
||||
**ExpandedUnaffiliatedHeroUtils** - added Scala overload:
|
||||
- New overload takes Scala `GameState` and `UnaffiliatedHeroT` directly
|
||||
- Added `UnaffiliatedHeroConverter.unaffiliatedHeroTypeToProto()` helper for efficient enum conversion
|
||||
- Proto overload retained for backward compatibility
|
||||
|
||||
**AvailablePleaseRecruitMeCommandFactory** - eliminated wasteful proto conversions:
|
||||
- Now uses `ExpandedUnaffiliatedHeroUtils.expandedUnaffiliatedHero(ScalaGameState, UnaffiliatedHeroT)` directly
|
||||
- Removed `GameStateConverter.toProto()` and `UnaffiliatedHeroConverter.toProto()` calls
|
||||
- Factory is now fully protoless internally (still returns proto types for API boundary)
|
||||
|
||||
**ProvinceViewFilter** - added Scala overload with faction filtering:
|
||||
- New `filteredProvinceView(province: ProvinceT, gs: ScalaGameState, factionId: FactionId)` overload
|
||||
- Uses protoless `FactionUtils.hasAlliance`, `Visibility.hasFullVisibility`, and `ProvinceUtils.incomingOthers`
|
||||
- Handles reconned provinces directly from Scala `FactionT.reconnedProvinces` (already Scala type)
|
||||
- Added helper methods: `fullProvinceInfoScala`, `maybeIncomingAttackersScala`, `unaffiliatedHeroInfoScala`
|
||||
- Events still converted to proto at the end (ProvinceView.knownEvents uses proto events)
|
||||
|
||||
**GameStateViewFilter** - eliminated GameStateConverter.toProto() call:
|
||||
- Scala overload now fully protoless internally
|
||||
- Uses the new ProvinceViewFilter Scala overload with faction filtering
|
||||
- Converts `battalionTypes` and `chronicleEntries` to proto only at output boundary
|
||||
|
||||
## Key Files
|
||||
|
||||
### Protoless Model Types
|
||||
|
||||
- `src/main/scala/net/eagle0/eagle/model/state/game_state/GameState.scala` - native Scala GameState
|
||||
- `src/main/scala/net/eagle0/eagle/model/state/province/ProvinceView.scala` - province view type
|
||||
- `src/main/scala/net/eagle0/eagle/model/state/faction/FactionView.scala` - faction view type
|
||||
- `src/main/scala/net/eagle0/eagle/model/state/hero/HeroView.scala` - hero view type
|
||||
|
||||
### Proto Converters
|
||||
|
||||
- `src/main/scala/net/eagle0/eagle/model/proto_converters/game_state/` - converts between proto and Scala types
|
||||
|
||||
## Notes
|
||||
|
||||
- The AI client code (`src/main/scala/net/eagle0/eagle/ai/`) is now fully protoless
|
||||
- Proto GameState is still needed at boundaries: persistence, gRPC to Shardok
|
||||
- `PerformUnaffiliatedHeroesAction` and the LLM pipeline use protoless `GameState`
|
||||
- `GameStateViewFilter` Scala overload is now fully protoless internally (converts to proto only at output)
|
||||
- `ProvinceViewFilter` has Scala overloads for all three modes: no filtering, faction filtering, and withdrawn-from view
|
||||
@@ -107,7 +107,6 @@ bazel_dep(name = "gazelle", version = "0.47.0", repo_name = "bazel_gazelle")
|
||||
|
||||
go_sdk = use_extension("@io_bazel_rules_go//go:extensions.bzl", "go_sdk")
|
||||
go_sdk.download(version = "1.23.3")
|
||||
use_repo(go_sdk, "go_default_sdk")
|
||||
|
||||
go_deps = use_extension("@bazel_gazelle//:extensions.bzl", "go_deps")
|
||||
go_deps.from_file(go_mod = "//:go.mod")
|
||||
@@ -119,7 +118,6 @@ use_repo(
|
||||
"com_github_aws_aws_sdk_go_v2_service_s3",
|
||||
"com_github_golang_jwt_jwt_v5",
|
||||
"com_github_google_uuid",
|
||||
"com_github_webview_webview_go",
|
||||
"org_golang_google_grpc",
|
||||
"org_golang_google_protobuf",
|
||||
)
|
||||
@@ -338,19 +336,6 @@ http_file(
|
||||
executable = True,
|
||||
)
|
||||
|
||||
# LLVM MinGW toolchain for Windows cross-compilation from macOS
|
||||
# This provides a complete toolchain for building Windows executables including
|
||||
# the MinGW-w64 libraries needed for CGO cross-compilation
|
||||
LLVM_MINGW_VERSION = "20250305"
|
||||
|
||||
http_archive(
|
||||
name = "llvm_mingw",
|
||||
build_file = "@//external:BUILD.llvm_mingw",
|
||||
sha256 = "32c24fc62fc8b9f8a900bf2c730b78b36767688f816f9d21e97a168289ff44e0",
|
||||
strip_prefix = "llvm-mingw-%s-ucrt-macos-14.4.1-universal" % LLVM_MINGW_VERSION,
|
||||
urls = ["https://github.com/fathonix/llvm-mingw-arm64ec-macos/releases/download/%s/llvm-mingw-%s-ucrt-macos-14.4.1-universal.tar.xz" % (LLVM_MINGW_VERSION, LLVM_MINGW_VERSION)],
|
||||
)
|
||||
|
||||
#
|
||||
# Toolchain Registration
|
||||
#
|
||||
|
||||
Generated
-561
@@ -611,445 +611,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@aspect_rules_js~//npm:extensions.bzl%pnpm": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "T22SdPzhLxF1CM+j9RD/Rq03yJ0NDfH2eE6hAbUcOII=",
|
||||
"usagesDigest": "6rWte4KDbiluq1s7w98bc4+2NjA8w67DKHDj4+DNw/Y=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"pnpm": {
|
||||
"bzlFile": "@@aspect_rules_js~//npm/private:npm_import.bzl",
|
||||
"ruleClassName": "npm_import_rule",
|
||||
"attributes": {
|
||||
"package": "pnpm",
|
||||
"version": "8.6.7",
|
||||
"root_package": "",
|
||||
"link_workspace": "",
|
||||
"link_packages": {},
|
||||
"integrity": "sha512-vRIWpD/L4phf9Bk2o/O2TDR8fFoJnpYrp2TKqTIZF/qZ2/rgL3qKXzHofHgbXsinwMoSEigz28sqk3pQ+yMEQQ==",
|
||||
"url": "",
|
||||
"commit": "",
|
||||
"patch_args": [
|
||||
"-p0"
|
||||
],
|
||||
"patches": [],
|
||||
"custom_postinstall": "",
|
||||
"npm_auth": "",
|
||||
"npm_auth_basic": "",
|
||||
"npm_auth_username": "",
|
||||
"npm_auth_password": "",
|
||||
"lifecycle_hooks": [],
|
||||
"extra_build_content": "load(\"@aspect_rules_js//js:defs.bzl\", \"js_binary\")\njs_binary(name = \"pnpm\", data = glob([\"package/**\"]), entry_point = \"package/dist/pnpm.cjs\", visibility = [\"//visibility:public\"])",
|
||||
"generate_bzl_library_targets": false,
|
||||
"extract_full_archive": true,
|
||||
"exclude_package_contents": [],
|
||||
"system_tar": "auto"
|
||||
}
|
||||
},
|
||||
"pnpm__links": {
|
||||
"bzlFile": "@@aspect_rules_js~//npm/private:npm_import.bzl",
|
||||
"ruleClassName": "npm_import_links",
|
||||
"attributes": {
|
||||
"package": "pnpm",
|
||||
"version": "8.6.7",
|
||||
"dev": false,
|
||||
"root_package": "",
|
||||
"link_packages": {},
|
||||
"deps": {},
|
||||
"transitive_closure": {},
|
||||
"lifecycle_build_target": false,
|
||||
"lifecycle_hooks_env": [],
|
||||
"lifecycle_hooks_execution_requirements": [
|
||||
"no-sandbox"
|
||||
],
|
||||
"lifecycle_hooks_use_default_shell_env": false,
|
||||
"bins": {},
|
||||
"package_visibility": [
|
||||
"//visibility:public"
|
||||
],
|
||||
"replace_package": "",
|
||||
"exclude_package_contents": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"aspect_bazel_lib~",
|
||||
"bazel_lib",
|
||||
"bazel_lib~"
|
||||
],
|
||||
[
|
||||
"aspect_bazel_lib~",
|
||||
"bazel_skylib",
|
||||
"bazel_skylib~"
|
||||
],
|
||||
[
|
||||
"aspect_bazel_lib~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
],
|
||||
[
|
||||
"aspect_bazel_lib~",
|
||||
"tar.bzl",
|
||||
"tar.bzl~"
|
||||
],
|
||||
[
|
||||
"aspect_rules_js~",
|
||||
"aspect_bazel_lib",
|
||||
"aspect_bazel_lib~"
|
||||
],
|
||||
[
|
||||
"aspect_rules_js~",
|
||||
"aspect_rules_js",
|
||||
"aspect_rules_js~"
|
||||
],
|
||||
[
|
||||
"aspect_rules_js~",
|
||||
"bazel_features",
|
||||
"bazel_features~"
|
||||
],
|
||||
[
|
||||
"aspect_rules_js~",
|
||||
"bazel_skylib",
|
||||
"bazel_skylib~"
|
||||
],
|
||||
[
|
||||
"aspect_rules_js~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
],
|
||||
[
|
||||
"bazel_features~",
|
||||
"bazel_features_globals",
|
||||
"bazel_features~~version_extension~bazel_features_globals"
|
||||
],
|
||||
[
|
||||
"bazel_features~",
|
||||
"bazel_features_version",
|
||||
"bazel_features~~version_extension~bazel_features_version"
|
||||
],
|
||||
[
|
||||
"bazel_lib~",
|
||||
"bazel_skylib",
|
||||
"bazel_skylib~"
|
||||
],
|
||||
[
|
||||
"bazel_lib~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
],
|
||||
[
|
||||
"tar.bzl~",
|
||||
"aspect_bazel_lib",
|
||||
"aspect_bazel_lib~"
|
||||
],
|
||||
[
|
||||
"tar.bzl~",
|
||||
"bazel_skylib",
|
||||
"bazel_skylib~"
|
||||
],
|
||||
[
|
||||
"tar.bzl~",
|
||||
"tar.bzl",
|
||||
"tar.bzl~"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@aspect_rules_ts~//ts:extensions.bzl%ext": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "h1hftyFCdJgiHD9blfFcMAiEk5ltEoUdNkuHPIkg/hM=",
|
||||
"usagesDigest": "v0aTa4/gasWF2bvssXYr1bqcYWc3kjV48hcj0z2QVT0=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"npm_typescript": {
|
||||
"bzlFile": "@@aspect_rules_ts~//ts/private:npm_repositories.bzl",
|
||||
"ruleClassName": "http_archive_version",
|
||||
"attributes": {
|
||||
"bzlmod": true,
|
||||
"version": "5.8.3",
|
||||
"integrity": "",
|
||||
"build_file": "@@aspect_rules_ts~//ts:BUILD.typescript",
|
||||
"build_file_substitutions": {
|
||||
"bazel_worker_version": "5.4.2",
|
||||
"google_protobuf_version": "3.20.1"
|
||||
},
|
||||
"urls": [
|
||||
"https://registry.npmjs.org/typescript/-/typescript-{}.tgz"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"aspect_rules_ts~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@cel-spec~//:extensions.bzl%non_module_dependencies": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "/2uyuQa5purSharolRXYOGYMGSGjDeByo6JfQDWsraA=",
|
||||
"usagesDigest": "2f6juplOpWu+UdD1kVgi773xavnFQ+OcH0PRuQduDxY=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"com_google_googleapis": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"sha256": "bd8e735d881fb829751ecb1a77038dda4a8d274c45490cb9fcf004583ee10571",
|
||||
"strip_prefix": "googleapis-07c27163ac591955d736f3057b1619ece66f5b99",
|
||||
"urls": [
|
||||
"https://github.com/googleapis/googleapis/archive/07c27163ac591955d736f3057b1619ece66f5b99.tar.gz"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"cel-spec~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@cel-spec~//:googleapis_ext.bzl%googleapis_ext": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "yun2jmsomFi3bs5bjQWXApBzqQf66zBJ39JEBYigzdc=",
|
||||
"usagesDigest": "OK8FsLndSl2AbwGM1Npe5NdHR1kDAebcw7Ee+KkekE0=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"com_google_googleapis_imports": {
|
||||
"bzlFile": "@@cel-spec~~non_module_dependencies~com_google_googleapis//:repository_rules.bzl",
|
||||
"ruleClassName": "switched_rules",
|
||||
"attributes": {
|
||||
"rules": {
|
||||
"proto_library_with_info": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"moved_proto_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"java_proto_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"java_grpc_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"java_gapic_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"java_gapic_test": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"java_gapic_assembly_gradle_pkg": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"py_proto_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"py_grpc_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"py_gapic_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"py_test": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"py_gapic_assembly_pkg": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"py_import": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"go_proto_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"go_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"go_test": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"go_gapic_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"go_gapic_assembly_pkg": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"cc_proto_library": [
|
||||
"native.cc_proto_library",
|
||||
""
|
||||
],
|
||||
"cc_grpc_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"cc_gapic_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"php_proto_library": [
|
||||
"",
|
||||
"php_proto_library"
|
||||
],
|
||||
"php_grpc_library": [
|
||||
"",
|
||||
"php_grpc_library"
|
||||
],
|
||||
"php_gapic_library": [
|
||||
"",
|
||||
"php_gapic_library"
|
||||
],
|
||||
"php_gapic_assembly_pkg": [
|
||||
"",
|
||||
"php_gapic_assembly_pkg"
|
||||
],
|
||||
"nodejs_gapic_library": [
|
||||
"",
|
||||
"typescript_gapic_library"
|
||||
],
|
||||
"nodejs_gapic_assembly_pkg": [
|
||||
"",
|
||||
"typescript_gapic_assembly_pkg"
|
||||
],
|
||||
"ruby_proto_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"ruby_grpc_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"ruby_ads_gapic_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"ruby_cloud_gapic_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"ruby_gapic_assembly_pkg": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"csharp_proto_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"csharp_grpc_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"csharp_gapic_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"csharp_gapic_assembly_pkg": [
|
||||
"",
|
||||
""
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"cel-spec~",
|
||||
"com_google_googleapis",
|
||||
"cel-spec~~non_module_dependencies~com_google_googleapis"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@envoy_api~//bazel:repositories.bzl%non_module_deps": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "6TqmRfVELxZJRPQYuJpC4JBX4QvdrHqTDOUJGOGODSo=",
|
||||
"usagesDigest": "IivjlawPvqhHPUJ3c6dLiPsH22mn/g/dJtlmv3zdimM=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"prometheus_metrics_model": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/prometheus/client_model/archive/v0.6.1.tar.gz"
|
||||
],
|
||||
"sha256": "b9b690bc35d80061f255faa7df7621eae39fe157179ccd78ff6409c3b004f05e",
|
||||
"strip_prefix": "client_model-0.6.1",
|
||||
"build_file_content": "\nload(\"@envoy_api//bazel:api_build_system.bzl\", \"api_cc_py_proto_library\")\nload(\"@io_bazel_rules_go//proto:def.bzl\", \"go_proto_library\")\n\napi_cc_py_proto_library(\n name = \"client_model\",\n srcs = [\n \"io/prometheus/client/metrics.proto\",\n ],\n visibility = [\"//visibility:public\"],\n)\n\ngo_proto_library(\n name = \"client_model_go_proto\",\n importpath = \"github.com/prometheus/client_model/go\",\n proto = \":client_model\",\n visibility = [\"//visibility:public\"],\n)\n"
|
||||
}
|
||||
},
|
||||
"com_github_bufbuild_buf": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/bufbuild/buf/releases/download/v1.49.0/buf-Linux-x86_64.tar.gz"
|
||||
],
|
||||
"sha256": "ee8da9748249f7946d79191e36469ce7bc3b8ba80019bff1fa4289a44cbc23bf",
|
||||
"strip_prefix": "buf",
|
||||
"build_file_content": "\npackage(\n default_visibility = [\"//visibility:public\"],\n)\n\nfilegroup(\n name = \"buf\",\n srcs = [\n \"@com_github_bufbuild_buf//:bin/buf\",\n ],\n tags = [\"manual\"], # buf is downloaded as a linux binary; tagged manual to prevent build for non-linux users\n)\n"
|
||||
}
|
||||
},
|
||||
"envoy_toolshed": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/envoyproxy/toolshed/archive/bazel-v0.2.2.tar.gz"
|
||||
],
|
||||
"sha256": "443fe177aba0cef8c17b7a48905c925c67b09005b10dd70ff12cd9f729a72d51",
|
||||
"strip_prefix": "toolshed-bazel-v0.2.2/bazel"
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"envoy_api~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
],
|
||||
[
|
||||
"envoy_api~",
|
||||
"envoy_api",
|
||||
"envoy_api~"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@googleapis~//:extensions.bzl%switched_rules": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "vG6fuTzXD8MMvHWZEQud0MMH7eoC4GXY0va7VrFFh04=",
|
||||
@@ -1218,37 +779,6 @@
|
||||
"recordedRepoMappingEntries": []
|
||||
}
|
||||
},
|
||||
"@@pybind11_bazel~//:internal_configure.bzl%internal_configure_extension": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "CyAKLVVonohnkTSqg9II/HA7M49sOlnMkgMHL3CmDuc=",
|
||||
"usagesDigest": "mFrTHX5eCiNU/OIIGVHH3cOILY9Zmjqk8RQYv8o6Thk=",
|
||||
"recordedFileInputs": {
|
||||
"@@pybind11_bazel~//MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34"
|
||||
},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"pybind11": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file": "@@pybind11_bazel~//:pybind11-BUILD.bazel",
|
||||
"strip_prefix": "pybind11-2.12.0",
|
||||
"urls": [
|
||||
"https://github.com/pybind/pybind11/archive/v2.12.0.zip"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"pybind11_bazel~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@rules_foreign_cc~//foreign_cc:extensions.bzl%tools": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "a7qnESofmIRYId6wwGNPJ9kvExU80KrkxL281P3+lBE=",
|
||||
@@ -1589,97 +1119,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@rules_fuzzing~//fuzzing/private:extensions.bzl%non_module_dependencies": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "hVgJRQ3Er45/UUAgNn1Yp2Khcp/Y8WyafA2kXIYmQ5M=",
|
||||
"usagesDigest": "YnIrdgwnf3iCLfChsltBdZ7yOJh706lpa2vww/i2pDI=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"platforms": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://mirror.bazel.build/github.com/bazelbuild/platforms/releases/download/0.0.8/platforms-0.0.8.tar.gz",
|
||||
"https://github.com/bazelbuild/platforms/releases/download/0.0.8/platforms-0.0.8.tar.gz"
|
||||
],
|
||||
"sha256": "8150406605389ececb6da07cbcb509d5637a3ab9a24bc69b1101531367d89d74"
|
||||
}
|
||||
},
|
||||
"rules_python": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"sha256": "d70cd72a7a4880f0000a6346253414825c19cdd40a28289bdf67b8e6480edff8",
|
||||
"strip_prefix": "rules_python-0.28.0",
|
||||
"url": "https://github.com/bazelbuild/rules_python/releases/download/0.28.0/rules_python-0.28.0.tar.gz"
|
||||
}
|
||||
},
|
||||
"bazel_skylib": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"sha256": "cd55a062e763b9349921f0f5db8c3933288dc8ba4f76dd9416aac68acee3cb94",
|
||||
"urls": [
|
||||
"https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz",
|
||||
"https://github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz"
|
||||
]
|
||||
}
|
||||
},
|
||||
"com_google_absl": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/abseil/abseil-cpp/archive/refs/tags/20240116.1.zip"
|
||||
],
|
||||
"strip_prefix": "abseil-cpp-20240116.1",
|
||||
"integrity": "sha256-7capMWOvWyoYbUaHF/b+I2U6XLMaHmky8KugWvfXYuk="
|
||||
}
|
||||
},
|
||||
"rules_fuzzing_oss_fuzz": {
|
||||
"bzlFile": "@@rules_fuzzing~//fuzzing/private/oss_fuzz:repository.bzl",
|
||||
"ruleClassName": "oss_fuzz_repository",
|
||||
"attributes": {}
|
||||
},
|
||||
"honggfuzz": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file": "@@rules_fuzzing~//:honggfuzz.BUILD",
|
||||
"sha256": "6b18ba13bc1f36b7b950c72d80f19ea67fbadc0ac0bb297ec89ad91f2eaa423e",
|
||||
"url": "https://github.com/google/honggfuzz/archive/2.5.zip",
|
||||
"strip_prefix": "honggfuzz-2.5"
|
||||
}
|
||||
},
|
||||
"rules_fuzzing_jazzer": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_jar",
|
||||
"attributes": {
|
||||
"sha256": "ee6feb569d88962d59cb59e8a31eb9d007c82683f3ebc64955fd5b96f277eec2",
|
||||
"url": "https://repo1.maven.org/maven2/com/code-intelligence/jazzer/0.20.1/jazzer-0.20.1.jar"
|
||||
}
|
||||
},
|
||||
"rules_fuzzing_jazzer_api": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_jar",
|
||||
"attributes": {
|
||||
"sha256": "f5a60242bc408f7fa20fccf10d6c5c5ea1fcb3c6f44642fec5af88373ae7aa1b",
|
||||
"url": "https://repo1.maven.org/maven2/com/code-intelligence/jazzer-api/0.20.1/jazzer-api-0.20.1.jar"
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"rules_fuzzing~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@rules_java~//java:rules_java_deps.bzl%compatibility_proxy": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "KIX40nDfygEWbU+rq3nYpt3tVgTK/iO8PKh5VMBlN7M=",
|
||||
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
/bin/mkdir -p win_output
|
||||
/usr/bin/dotnet publish src/main/csharp/net/eagle0/clients/win/installer/EagleInstaller.sln -o win_output
|
||||
SHA=`sha256sum /tmp/EagleInstaller.exe | awk '{print $1 }'`
|
||||
ZIP_FILE="updater__$SHA.zip"
|
||||
|
||||
/usr/bin/zip win_output/$ZIP_FILE win_output/EagleInstaller.exe
|
||||
rm win_output/EagleInstaller.exe
|
||||
rm win_output/EagleInstaller.pdb
|
||||
|
||||
DATE=`date +"%Y-%m-%d %T"`
|
||||
|
||||
cat > win_output/updater.html <<-EOF
|
||||
<html>
|
||||
<head>
|
||||
<title>Download Eagle Updater</title>
|
||||
</head>
|
||||
<body>
|
||||
<a href="http://eagle0.net/assets/$ZIP_FILE">$ZIP_FILE</a> (updated $DATE)
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
|
||||
SSH_KEY_FILE=$1
|
||||
SSH_USER_NAME=$2
|
||||
|
||||
/usr/bin/rsync -r --copy-links -e "/usr/bin/ssh -i $SSH_KEY_FILE -p 9022" win_output/ $SSH_USER_NAME@eagle0.net:/www/assets/
|
||||
@@ -1,33 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Build iOS Addressables only (no player build)
|
||||
# This switches Unity to iOS target and builds addressables for CDN upload
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
. ./ci/unity_version.sh
|
||||
|
||||
WORKSPACE=$(pwd)
|
||||
|
||||
echo "Building protos"
|
||||
./scripts/build_protos.sh
|
||||
UNITY_INSTALL_PATH="/Applications/Unity/Hub/Editor"
|
||||
LOG_PATH=${1:-"/tmp/eagle0/editor_ios_addressables.log"}
|
||||
|
||||
echo "Building iOS Addressables"
|
||||
|
||||
mkdir -p "$(dirname "$LOG_PATH")"
|
||||
|
||||
# Build Addressables for iOS target
|
||||
# -buildTarget iOS switches the editor to iOS before running
|
||||
${UNITY_INSTALL_PATH}/${UNITY_VERSION}/Unity.app/Contents/MacOS/Unity \
|
||||
-nographics \
|
||||
-batchmode \
|
||||
-quit \
|
||||
-buildTarget iOS \
|
||||
-executeMethod BuildScript.BuildAddressables \
|
||||
-logFile "$LOG_PATH" \
|
||||
-projectPath "$WORKSPACE/src/main/csharp/net/eagle0/clients/unity/eagle0"
|
||||
|
||||
echo "iOS Addressables build complete"
|
||||
echo "Bundles should be in: $WORKSPACE/src/main/csharp/net/eagle0/clients/unity/eagle0/ServerData/iOS/"
|
||||
@@ -15,12 +15,10 @@ echo "Cleaning up $BUILD_DIR"
|
||||
/bin/rm -rf "$BUILD_DIR"
|
||||
/bin/mkdir -p "$BUILD_DIR"
|
||||
|
||||
# Use custom build script that builds Addressables before the player
|
||||
${UNITY_INSTALL_PATH}/${UNITY_VERSION}/Unity.app/Contents/MacOS/Unity \
|
||||
-nographics \
|
||||
-batchmode \
|
||||
-quit \
|
||||
-executeMethod BuildScript.BuildMacPlayer \
|
||||
-buildPath "$BUILD_DIR/eagle0.app" \
|
||||
-buildOSXUniversalPlayer "$BUILD_DIR/eagle0.app" \
|
||||
-logFile "$LOG_PATH" \
|
||||
-projectPath "$WORKSPACE/src/main/csharp/net/eagle0/clients/unity/eagle0"
|
||||
|
||||
@@ -9,6 +9,9 @@ COMMIT=$(/usr/bin/git rev-parse --short HEAD)
|
||||
/bin/echo "build protos"
|
||||
./scripts/build_protos.sh
|
||||
|
||||
/bin/echo "build plugins"
|
||||
./scripts/build_windows_plugin.sh
|
||||
|
||||
git log -3
|
||||
|
||||
/bin/echo "build Windows"
|
||||
|
||||
@@ -7,8 +7,8 @@ COMMIT=$(/usr/bin/git rev-parse --short HEAD)
|
||||
/bin/echo "build protos"
|
||||
./scripts/build_protos.sh
|
||||
|
||||
/bin/echo "build Sparkle plugin"
|
||||
./scripts/build_sparkle_plugin.sh
|
||||
/bin/echo "build Mac plugin"
|
||||
./scripts/build_mac_plugin.sh
|
||||
|
||||
git log -3
|
||||
|
||||
|
||||
@@ -15,12 +15,10 @@ echo "Cleaning up $1"
|
||||
/bin/rm -rf $1
|
||||
/bin/mkdir -p $1
|
||||
|
||||
# Use custom build script that builds Addressables before the player
|
||||
${UNITY_INSTALL_PATH}/${UNITY_VERSION}/Unity.app/Contents/MacOS/Unity \
|
||||
-nographics \
|
||||
-batchmode \
|
||||
-quit \
|
||||
-executeMethod BuildScript.BuildWindowsPlayer \
|
||||
-buildPath "$BUILD_DIR/eagle0.exe" \
|
||||
-buildWindows64Player $BUILD_DIR/eagle0.exe \
|
||||
-logFile $LOG_PATH \
|
||||
-projectPath "$WORKSPACE/src/main/csharp/net/eagle0/clients/unity/eagle0"
|
||||
|
||||
@@ -1,25 +1,12 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Persist Unity Library/ cache to persistent storage
|
||||
#
|
||||
# Environment variables:
|
||||
# UNITY_CACHE_PLATFORM - Platform identifier (e.g., "mac", "windows")
|
||||
# Defaults to "mac" if not set
|
||||
#
|
||||
# Note: Library/Bee/ is excluded because it contains DAG files with hardcoded
|
||||
# file paths that become stale when project files change. This prevents
|
||||
# "Data at the root level is invalid" XML errors from stale references.
|
||||
|
||||
set -uxo pipefail
|
||||
|
||||
PLATFORM="${UNITY_CACHE_PLATFORM:-mac}"
|
||||
CACHE_DIR="/tmp/eagle0/Library-${PLATFORM}"
|
||||
|
||||
/bin/echo "persist Library/ to $CACHE_DIR (excluding Bee/)"
|
||||
/bin/echo "persist Library/"
|
||||
|
||||
# rsync may exit with code 23 ("partial transfer due to error") if Unity's
|
||||
# temporary files vanish during the copy. This is acceptable for a cache.
|
||||
/usr/bin/rsync -rtlDvq --exclude='Bee/' src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ "$CACHE_DIR/"
|
||||
/usr/bin/rsync -rtlDvq src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ /tmp/eagle0/Library/
|
||||
rsync_exit=$?
|
||||
|
||||
if [ $rsync_exit -eq 0 ]; then
|
||||
|
||||
@@ -1,16 +1,7 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Restore Unity Library/ cache from persistent storage
|
||||
#
|
||||
# Environment variables:
|
||||
# UNITY_CACHE_PLATFORM - Platform identifier (e.g., "mac", "windows")
|
||||
# Defaults to "mac" if not set
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
PLATFORM="${UNITY_CACHE_PLATFORM:-mac}"
|
||||
CACHE_DIR="/tmp/eagle0/Library-${PLATFORM}"
|
||||
|
||||
/bin/echo "restore Library/ from $CACHE_DIR"
|
||||
/bin/mkdir -p "$CACHE_DIR"
|
||||
/usr/bin/rsync -rtlDvq "$CACHE_DIR/" src/main/csharp/net/eagle0/clients/unity/eagle0/Library/
|
||||
/bin/echo "restore Library/"
|
||||
/bin/mkdir -p /tmp/eagle0/Library
|
||||
/usr/bin/rsync -rtlDvq /tmp/eagle0/Library/ src/main/csharp/net/eagle0/clients/unity/eagle0/Library/
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Upload Addressables bundles to DigitalOcean Spaces
|
||||
# Usage: ./upload_addressables.sh <build_target>
|
||||
# Example: ./upload_addressables.sh StandaloneOSX
|
||||
#
|
||||
# Required environment variables:
|
||||
# ACCESS_KEY_ID - DigitalOcean Spaces access key (same as other deploys)
|
||||
# SECRET_KEY - DigitalOcean Spaces secret key (same as other deploys)
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
BUILD_TARGET=$1
|
||||
WORKSPACE=$(pwd)
|
||||
UNITY_PROJECT="$WORKSPACE/src/main/csharp/net/eagle0/clients/unity/eagle0"
|
||||
SERVER_DATA="$UNITY_PROJECT/ServerData/$BUILD_TARGET"
|
||||
|
||||
# DigitalOcean Spaces configuration (same region as other eagle0 buckets)
|
||||
DO_ENDPOINT="https://sfo3.digitaloceanspaces.com"
|
||||
DO_BUCKET="eagle0-assets"
|
||||
|
||||
if [ ! -d "$SERVER_DATA" ]; then
|
||||
echo "No Addressables bundles found at $SERVER_DATA"
|
||||
echo "Skipping upload (this is expected if Addressables are bundled locally)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Uploading Addressables bundles from $SERVER_DATA"
|
||||
echo "Target: s3://$DO_BUCKET/addressables/$BUILD_TARGET/"
|
||||
|
||||
# Configure AWS CLI for DigitalOcean Spaces
|
||||
export AWS_ACCESS_KEY_ID="$ACCESS_KEY_ID"
|
||||
export AWS_SECRET_ACCESS_KEY="$SECRET_KEY"
|
||||
|
||||
# Sync bundles to Spaces
|
||||
# --delete removes files in destination that don't exist in source
|
||||
# --acl public-read makes files publicly accessible
|
||||
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/addressables/$BUILD_TARGET/" \
|
||||
--endpoint-url "$DO_ENDPOINT" \
|
||||
--acl public-read \
|
||||
--delete
|
||||
|
||||
echo "Addressables upload complete"
|
||||
echo "Files available at: https://assets.eagle0.net/addressables/$BUILD_TARGET/"
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,40 @@
|
||||
# Environment template for production deployment
|
||||
# This file defines all env vars used by docker-compose.prod.yml
|
||||
# Workflows should update their specific vars without overwriting others
|
||||
|
||||
# Container images (managed by respective build workflows)
|
||||
# Note: Shardok runs on Hetzner, deployed via shardok_arm64_build.yml
|
||||
EAGLE_IMAGE=registry.digitalocean.com/eagle0/eagle-server:latest
|
||||
ADMIN_IMAGE=registry.digitalocean.com/eagle0/admin-server:latest
|
||||
JFR_SIDECAR_IMAGE=registry.digitalocean.com/eagle0/jfr-sidecar:latest
|
||||
AUTH_IMAGE=registry.digitalocean.com/eagle0/auth-server:latest
|
||||
|
||||
# OpenAI / LLM
|
||||
OPENAI_API_KEY=
|
||||
GPT_MODEL_NAME=gpt-4o
|
||||
|
||||
# DigitalOcean Spaces (S3-compatible storage)
|
||||
EAGLE_ENABLE_S3=false
|
||||
DO_SPACES_ACCESS_KEY=
|
||||
DO_SPACES_SECRET_KEY=
|
||||
|
||||
# JWT authentication
|
||||
JWT_PRIVATE_KEY=
|
||||
|
||||
# OAuth providers
|
||||
DISCORD_CLIENT_ID=
|
||||
DISCORD_CLIENT_SECRET=
|
||||
GOOGLE_CLIENT_ID=
|
||||
GOOGLE_CLIENT_SECRET=
|
||||
|
||||
# Shardok connection (Hetzner ARM64 server)
|
||||
SHARDOK_ADDRESS=
|
||||
SHARDOK_AUTH_TOKEN=
|
||||
|
||||
# Monitoring
|
||||
SENTRY_DSN=
|
||||
|
||||
# Email (Fastmail JMAP)
|
||||
FASTMAIL_API_TOKEN=
|
||||
FASTMAIL_FROM_EMAIL=
|
||||
FASTMAIL_FROM_NAME=
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
# Update .env file without losing other variables
|
||||
# Usage: ./update-env.sh KEY1=value1 KEY2=value2 ...
|
||||
#
|
||||
# This script:
|
||||
# 1. Creates .env from template if it doesn't exist
|
||||
# 2. Updates only the specified KEY=value pairs
|
||||
# 3. Preserves all other existing values
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ENV_FILE="${ENV_FILE:-/opt/eagle0/.env}"
|
||||
TEMPLATE_FILE="${TEMPLATE_FILE:-$SCRIPT_DIR/env.template}"
|
||||
|
||||
# Create .env from template if it doesn't exist
|
||||
if [ ! -f "$ENV_FILE" ]; then
|
||||
if [ -f "$TEMPLATE_FILE" ]; then
|
||||
echo "Creating .env from template..."
|
||||
grep -v '^#' "$TEMPLATE_FILE" | grep -v '^$' > "$ENV_FILE"
|
||||
else
|
||||
echo "Creating empty .env..."
|
||||
touch "$ENV_FILE"
|
||||
fi
|
||||
chmod 600 "$ENV_FILE"
|
||||
fi
|
||||
|
||||
# Process each KEY=VALUE argument
|
||||
for arg in "$@"; do
|
||||
# Skip empty args
|
||||
[ -z "$arg" ] && continue
|
||||
|
||||
# Parse KEY=VALUE
|
||||
KEY="${arg%%=*}"
|
||||
VALUE="${arg#*=}"
|
||||
|
||||
# Skip if no key
|
||||
[ -z "$KEY" ] && continue
|
||||
|
||||
# Skip setting empty values (keeps existing value)
|
||||
if [ -z "$VALUE" ]; then
|
||||
echo "Skipping $KEY (empty value)"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Remove existing line for this key and add new one
|
||||
if grep -q "^${KEY}=" "$ENV_FILE" 2>/dev/null; then
|
||||
# Key exists, update it
|
||||
sed -i "s|^${KEY}=.*|${KEY}=${VALUE}|" "$ENV_FILE"
|
||||
echo "Updated $KEY"
|
||||
else
|
||||
# Key doesn't exist, add it
|
||||
echo "${KEY}=${VALUE}" >> "$ENV_FILE"
|
||||
echo "Added $KEY"
|
||||
fi
|
||||
done
|
||||
|
||||
chmod 600 "$ENV_FILE"
|
||||
echo "Done updating $ENV_FILE"
|
||||
+14
-22
@@ -18,22 +18,23 @@ services:
|
||||
image: ${EAGLE_IMAGE:-registry.digitalocean.com/eagle0/eagle-server:latest}
|
||||
container_name: eagle-blue
|
||||
command:
|
||||
- "--gpt-model-name"
|
||||
- "${GPT_MODEL_NAME:-gpt-5.1}"
|
||||
- "--shardok-interface-remote-address"
|
||||
- "${SHARDOK_ADDRESS}"
|
||||
- "${SHARDOK_ADDRESS:-shardok:40042}"
|
||||
- "--auth-service-url"
|
||||
- "auth:40033"
|
||||
ports:
|
||||
- "40032:40032"
|
||||
environment:
|
||||
OPENAI_API_KEY: "${OPENAI_API_KEY:-}"
|
||||
ANTHROPIC_API_KEY: "${ANTHROPIC_API_KEY:-}"
|
||||
GEMINI_API_KEY: "${GEMINI_API_KEY:-}"
|
||||
EAGLE_ENABLE_S3: "${EAGLE_ENABLE_S3:-false}"
|
||||
DO_SPACES_ENDPOINT: "${DO_SPACES_ENDPOINT:-https://sfo3.digitaloceanspaces.com}"
|
||||
DO_SPACES_ACCESS_KEY: "${DO_SPACES_ACCESS_KEY:-}"
|
||||
DO_SPACES_SECRET_KEY: "${DO_SPACES_SECRET_KEY:-}"
|
||||
# JWT public key for token validation (auth service handles signing)
|
||||
# Reads from /etc/eagle0/keys/public.pem via shared volume
|
||||
JWT_PRIVATE_KEY: "${JWT_PRIVATE_KEY:-}"
|
||||
DISCORD_CLIENT_ID: "${DISCORD_CLIENT_ID:-}"
|
||||
DISCORD_CLIENT_SECRET: "${DISCORD_CLIENT_SECRET:-}"
|
||||
# Auth token for Shardok on Hetzner (required)
|
||||
SHARDOK_AUTH_TOKEN: "${SHARDOK_AUTH_TOKEN:-}"
|
||||
# Use persistent volume for save data (users, games, etc.)
|
||||
@@ -46,7 +47,7 @@ services:
|
||||
- ./archived:/app/archived # Archived completed games
|
||||
- ./jfr:/app/jfr # JFR recordings - dump with: docker exec eagle-blue jcmd 1 JFR.dump filename=/app/jfr/profile.jfr
|
||||
- jvm-tmp:/tmp # Shared with jfr-sidecar for JVM attach socket files
|
||||
- jwt-keys:/etc/eagle0/keys:ro # JWT public key from auth service (read-only)
|
||||
- jwt-keys:/etc/eagle0/keys # Shared JWT keys with auth service
|
||||
depends_on:
|
||||
- auth
|
||||
restart: unless-stopped
|
||||
@@ -67,22 +68,23 @@ services:
|
||||
container_name: eagle-green
|
||||
profiles: ["blue-green"] # Only started during blue-green deployment
|
||||
command:
|
||||
- "--gpt-model-name"
|
||||
- "${GPT_MODEL_NAME:-gpt-5.1}"
|
||||
- "--shardok-interface-remote-address"
|
||||
- "${SHARDOK_ADDRESS}"
|
||||
- "${SHARDOK_ADDRESS:-shardok:40042}"
|
||||
- "--auth-service-url"
|
||||
- "auth:40033"
|
||||
ports:
|
||||
- "40034:40032" # Different host port for staging
|
||||
environment:
|
||||
OPENAI_API_KEY: "${OPENAI_API_KEY:-}"
|
||||
ANTHROPIC_API_KEY: "${ANTHROPIC_API_KEY:-}"
|
||||
GEMINI_API_KEY: "${GEMINI_API_KEY:-}"
|
||||
EAGLE_ENABLE_S3: "${EAGLE_ENABLE_S3:-false}"
|
||||
DO_SPACES_ENDPOINT: "${DO_SPACES_ENDPOINT:-https://sfo3.digitaloceanspaces.com}"
|
||||
DO_SPACES_ACCESS_KEY: "${DO_SPACES_ACCESS_KEY:-}"
|
||||
DO_SPACES_SECRET_KEY: "${DO_SPACES_SECRET_KEY:-}"
|
||||
# JWT public key for token validation (auth service handles signing)
|
||||
# Reads from /etc/eagle0/keys/public.pem via shared volume
|
||||
JWT_PRIVATE_KEY: "${JWT_PRIVATE_KEY:-}"
|
||||
DISCORD_CLIENT_ID: "${DISCORD_CLIENT_ID:-}"
|
||||
DISCORD_CLIENT_SECRET: "${DISCORD_CLIENT_SECRET:-}"
|
||||
SHARDOK_AUTH_TOKEN: "${SHARDOK_AUTH_TOKEN:-}"
|
||||
EAGLE_SAVE_DIR: "/app/saves"
|
||||
EAGLE_ARCHIVE_DIR: "/app/archived"
|
||||
@@ -93,7 +95,7 @@ services:
|
||||
- ./archived:/app/archived # Same archive directory as blue
|
||||
- ./jfr:/app/jfr # JFR recordings (same as blue)
|
||||
- jvm-tmp:/tmp # Shared with jfr-sidecar-green for JVM attach socket files
|
||||
- jwt-keys:/etc/eagle0/keys:ro # JWT public key from auth service (read-only)
|
||||
- jwt-keys:/etc/eagle0/keys # Shared JWT keys with auth service
|
||||
depends_on:
|
||||
- auth
|
||||
restart: "no" # Don't auto-restart during deployment
|
||||
@@ -131,16 +133,6 @@ services:
|
||||
DISCORD_CLIENT_SECRET: "${DISCORD_CLIENT_SECRET:-}"
|
||||
GOOGLE_CLIENT_ID: "${GOOGLE_CLIENT_ID:-}"
|
||||
GOOGLE_CLIENT_SECRET: "${GOOGLE_CLIENT_SECRET:-}"
|
||||
GH_OAUTH_CLIENT_ID: "${GH_OAUTH_CLIENT_ID:-}"
|
||||
GH_OAUTH_CLIENT_SECRET: "${GH_OAUTH_CLIENT_SECRET:-}"
|
||||
# Apple Sign-In credentials
|
||||
APPLE_SIGNIN_CLIENT_ID: "${APPLE_SIGNIN_CLIENT_ID:-}"
|
||||
APPLE_TEAM_ID: "${APPLE_TEAM_ID:-}"
|
||||
APPLE_SIGNIN_KEY_ID: "${APPLE_SIGNIN_KEY_ID:-}"
|
||||
APPLE_SIGNIN_PRIVATE_KEY: "${APPLE_SIGNIN_PRIVATE_KEY:-}"
|
||||
# Twitch OAuth credentials
|
||||
TWITCH_CLIENT_ID: "${TWITCH_CLIENT_ID:-}"
|
||||
TWITCH_CLIENT_SECRET: "${TWITCH_CLIENT_SECRET:-}"
|
||||
# Server base URL for OAuth callbacks
|
||||
SERVER_BASE_URL: "${SERVER_BASE_URL:-https://prod.eagle0.net}"
|
||||
# JWT keys - PEM files in volume, bootstrapped from JWK on first run
|
||||
|
||||
+46
-67
@@ -109,20 +109,11 @@ All 26 tracks have CC licenses with proper attribution:
|
||||
| Dragon Castle | Makai Symphony | CC BY-SA 3.0 |
|
||||
| Durandal | Makai Symphony | CC BY-SA 3.0 |
|
||||
|
||||
**Tracks with non-CC licenses:**
|
||||
|
||||
| Track | Artist | License | Source |
|
||||
|-------|--------|---------|--------|
|
||||
| Market Day | RandomMind | Free without attribution | [Chosic](https://www.chosic.com/download-audio/27016/) |
|
||||
| Shopping List | Komiku | Free without attribution | [Chosic](https://www.chosic.com/download-audio/24714/) |
|
||||
| Medieval: Victory Theme | RandomMind | CC0 Public Domain | [Chosic](https://www.chosic.com/download-audio/28492/) |
|
||||
| No Time for Greatness | Dima Koltsov | CC BY 4.0 | [YouTube](https://www.youtube.com/watch?v=cQh0OWIFdgM) |
|
||||
| Warriors of Demacia | Dima Koltsov | CC BY 4.0 | [YouTube](https://www.youtube.com/watch?v=yktSUMJn9ao) |
|
||||
| Forest Queen Tale | Dima Koltsov | Presumed CC BY 4.0 | Not found on YouTube; other Dima Koltsov tracks are CC BY 4.0 |
|
||||
| Valor | Dima Koltsov | CC BY 4.0 | [YouTube](https://www.youtube.com/watch?v=uoHYJRPcS2Y) |
|
||||
| Clouds | Dima Koltsov | Presumed CC BY 4.0 | Not found on YouTube; other Dima Koltsov tracks are CC BY 4.0 |
|
||||
|
||||
**Note on Dima Koltsov tracks:** 3 of 5 tracks confirmed CC BY 4.0 via YouTube. 2 remaining tracks (Forest Queen Tale, Clouds) presumed same license but not verified.
|
||||
**Tracks without specific license (verify):**
|
||||
- Market Day
|
||||
- Shopping List
|
||||
- Medieval: Victory Theme
|
||||
- Tracks by Dima Koltsov (AUDIUS): No Time for Greatness, Warriors of Demacia, Forest Queen Tale, Valor, Clouds
|
||||
|
||||
---
|
||||
|
||||
@@ -138,55 +129,42 @@ All 26 tracks have CC licenses with proper attribution:
|
||||
|
||||
## 4. Potentially Problematic Assets (Review Needed)
|
||||
|
||||
### ~~Clip Art (Unknown License)~~ RESOLVED
|
||||
| File | Status |
|
||||
|------|--------|
|
||||
| ~~`Assets/Shardok/commandImages/bridge.png`~~ | **REPLACED** (2026-01-23) with AI-generated wooden rope bridge icon (ChatGPT/DALL-E 3, 512x512 PNG). No licensing restrictions - AI-generated for this project. |
|
||||
| ~~`Assets/Images/startFire.png`~~ | **REPLACED** (2026-01-23) with "Flame Icon" from [UXWing](https://uxwing.com/flame-icon/) (free for commercial use, no attribution required). Consolidated duplicate removed. |
|
||||
| ~~`Assets/Shardok/commandImages/startFire.png`~~ | **DELETED** (2026-01-23) - duplicate removed, all references updated to use `Assets/Images/startFire.png` |
|
||||
### Stock Images (Possible License Issues)
|
||||
These appear to be stock images that may have been used as placeholders:
|
||||
|
||||
| File | Concern |
|
||||
|------|---------|
|
||||
| ~~`Assets/Eagle/79066358-stock-illustration-raster-illustration-medieval-purse-bag...jpg`~~ | **DELETED** (2025-01-04) |
|
||||
| ~~`Assets/Images/kisspng-hammer-hand-saws-tool-clip-art...jpg`~~ | **DELETED** (2025-01-04) |
|
||||
| ~~`Assets/Images/lee-ermy-cropped.jpg`~~ | **DELETED** (2025-01-04) |
|
||||
| ~~`Assets/Eagle/images.jpeg`~~ | **DELETED** (2025-01-04) |
|
||||
|
||||
### Clip Art (Unknown License)
|
||||
| File | Concern |
|
||||
|------|---------|
|
||||
| `Assets/Shardok/commandImages/bridge.png` | Clip art style wooden bridge, unknown source - **needs replacement** |
|
||||
| `Assets/Images/startFire.png` | Icon, unknown source - **needs verification or replacement** |
|
||||
|
||||
### Shardok Sound Effects
|
||||
- **Location:** `Assets/Shardok/soundEffects/`
|
||||
- **Count:** 37 audio files (was incorrectly counted as 56 including .meta files)
|
||||
- **Count:** 56 MP3 files
|
||||
- **Contents:** Spell effects, movement, combat sounds
|
||||
|
||||
**Verified from [Zombie Monster - Undead Collection](https://assetstore.unity.com/packages/audio/sound-fx/creatures/zombie-monster-undead-collection-70662) (Unity Asset Store):**
|
||||
- `raise_undead.mp3`
|
||||
- `undead_break_control.mp3`
|
||||
- `undead_grew.wav`
|
||||
|
||||
**⚠️ MUST REPLACE:**
|
||||
- ~~`anybody.mp3`~~ - **REPLACED** (2026-01-23) with `Positive Effect 6.wav` from Magic Spells Sound Effects LITE
|
||||
- ~~`burnination.mp3`~~ - **REPLACED** (2026-01-23) with `Magic Element Fire 04.wav` from Medieval Combat Sounds
|
||||
- `failure_horn.mp3` - licensing issue, no replacement found in purchased assets
|
||||
- `runaway.mp3` - licensing issue, no replacement found in purchased assets
|
||||
|
||||
**Presumed from Unity Asset Store purchases (31 files):**
|
||||
Owner believes these are from: Fantasy Interface Sounds, Medieval Combat Sounds, Magic Spells Sound Effects LITE, and/or Medieval Battle Sound Pack.
|
||||
- `archery.mp3`, `battle_shout.mp3`, `boo.mp3`, `braved_water.mp3`
|
||||
- `build_bridge.mp3`, `build_bridge_failure.mp3`, `charge.mp3`
|
||||
- `dismiss_unit.mp3`, `duel_challenged.mp3`, `failure_horn.mp3`, `fear.mp3`, `fear_failed.mp3`
|
||||
- `fire_extinguish.mp3`, `fire_spread.mp3`, `fire_start.mp3`, `fire_start_failure.mp3`
|
||||
- `freeze.mp3`, `holy_wave.mp3`, `holy_wave_damage.mp3`, `jail_door.mp3`, `lightning.mp3`
|
||||
- `melee.mp3`, `meteor.mp3`, `mind_control.mp3`, `move.mp3`, `move 1.mp3`
|
||||
- `raging_fire.mp3`, `reduce.mp3`, `repair.mp3`, `repair_failed.mp3`, `splash.mp3`
|
||||
- **Status:** Unknown origin - may be custom or need verification
|
||||
|
||||
### Free Icons
|
||||
- **Location:** `Assets/free_icons/`
|
||||
- **Count:** 8 PNG weather icons
|
||||
- **Status:** Verify "free" means commercially usable
|
||||
|
||||
### ~~Terrain Hexes~~ VERIFIED
|
||||
### Terrain Hexes
|
||||
- **Location:** `Assets/Terrain Hexes/`
|
||||
- **Count:** 85 PNG files
|
||||
- **Status:** ✓ Confirmed Unity Asset Store purchase (2026-01-23)
|
||||
- **Status:** Unknown source - verify licensing
|
||||
|
||||
### ~~StrategyGameIcons~~ VERIFIED
|
||||
### StrategyGameIcons
|
||||
- **Location:** `Assets/StrategyGameIcons/`
|
||||
- **Count:** 138 PNG files
|
||||
- **Publisher:** REXARD
|
||||
- **Asset Store Link:** https://assetstore.unity.com/packages/2d/gui/icons/strategy-game-icons-64816
|
||||
- **Status:** ✓ Confirmed Unity Asset Store purchase (2026-01-23)
|
||||
- **Status:** Unknown source - verify licensing
|
||||
|
||||
---
|
||||
|
||||
@@ -218,24 +196,26 @@ NuGet packages in `Assets/Packages/` all include LICENSE.TXT files:
|
||||
|
||||
### Must Verify Before Opening Public Access:
|
||||
|
||||
1. ~~**Clip art images**~~ - **RESOLVED** (2025-01-23): Replaced with game-icons.net CC BY 3.0 icons
|
||||
1. ~~**Stock images** - The JPG files with stock image filenames need review.~~ **DONE** - Deleted lee-ermy, kisspng, stock-illustration, Yosemite Sam, and images.jpeg (2025-01-04)
|
||||
|
||||
2. **Shardok sound effects** - 3 files must be replaced:
|
||||
- `anybody.mp3` - licensing issue
|
||||
- `burnination.mp3` - licensing issue
|
||||
- `runaway.mp3` - licensing issue
|
||||
2. **Clip art images** - Unknown license, need replacement with properly licensed alternatives:
|
||||
- `Assets/Shardok/commandImages/bridge.png` - wooden bridge icon
|
||||
- `Assets/Images/startFire.png` - fire icon
|
||||
|
||||
Remaining 31 files presumed from Asset Store purchases; 3 verified from Zombie Monster Undead Collection.
|
||||
3. **Shardok sound effects** - 56 MP3 files of unknown origin. Either:
|
||||
- Document their source
|
||||
- Replace with known-licensed alternatives
|
||||
- Confirm they were custom-created
|
||||
|
||||
3. ~~**Terrain Hexes**~~ - **VERIFIED** (2026-01-23): Confirmed Unity Asset Store purchase
|
||||
4. **Terrain Hexes** - 85 hex tiles of unknown source
|
||||
- **TODO:** Confirm if this is a Unity Asset Store purchase (owner believes it is)
|
||||
|
||||
4. ~~**StrategyGameIcons**~~ - **VERIFIED** (2026-01-23): Unity Asset Store purchase (REXARD)
|
||||
5. **StrategyGameIcons** - 138 icons of unknown source
|
||||
- **TODO:** Investigate origin - check Unity Asset Store purchase history
|
||||
|
||||
5. ~~**Medieval: Victory Theme**~~ - **VERIFIED** (2026-01-23): CC0 Public Domain by RandomMind ([Chosic](https://www.chosic.com/download-audio/28492/))
|
||||
6. **AUDIUS music tracks** - Verify Dima Koltsov tracks are licensed for commercial use
|
||||
|
||||
6. ~~**Dima Koltsov tracks**~~ - **MOSTLY VERIFIED** (2026-01-23): 3 of 5 confirmed CC BY 4.0 via YouTube. 2 remaining (Forest Queen Tale, Clouds) presumed same license.
|
||||
|
||||
7. ~~**Discord logo**~~ - **OK** (2026-01-23): Usage complies with Discord brand guidelines for "Login with Discord" button
|
||||
7. **Discord logo** (`Eagle/Discord-Logo-Blurple.png`) - Likely fine for "Login with Discord" button per Discord brand guidelines, but verify usage complies with their terms
|
||||
|
||||
### Already Safe:
|
||||
|
||||
@@ -249,13 +229,12 @@ NuGet packages in `Assets/Packages/` all include LICENSE.TXT files:
|
||||
|
||||
## Recommendation
|
||||
|
||||
Before public release:
|
||||
Before removing HTTP basic auth:
|
||||
|
||||
1. ~~Replace clip art images (`bridge.png`, `startFire.png`) with properly licensed alternatives~~ **DONE** - see Section 4
|
||||
2. ~~Verify source of `Assets/Shardok/soundEffects/` MP3s~~ **MOSTLY DONE** - 3 files flagged for replacement, rest presumed Asset Store
|
||||
3. ~~Verify source of `Assets/Terrain Hexes/`~~ **DONE** - confirmed Asset Store purchase
|
||||
4. ~~Verify source of `Assets/StrategyGameIcons/`~~ **DONE** - Unity Asset Store (REXARD)
|
||||
5. ~~Replace Dima Koltsov Audius tracks~~ **MOSTLY DONE** - 3/5 confirmed CC BY 4.0, 2 presumed same
|
||||
6. ~~Find source of Medieval: Victory Theme or replace~~ **DONE** - CC0 Public Domain by RandomMind
|
||||
1. ~~Delete or replace the 4 suspicious JPG/JPEG files in `Assets/Eagle/` and `Assets/Images/`~~ **DONE** (2025-01-04)
|
||||
2. Replace clip art images (`bridge.png`, `startFire.png`) with properly licensed alternatives (e.g., from [game-icons.net](https://game-icons.net) CC BY 3.0)
|
||||
3. Verify source of `Assets/Shardok/soundEffects/` MP3s
|
||||
4. Verify source of `Assets/Terrain Hexes/` and `Assets/StrategyGameIcons/`
|
||||
5. If any are from early development with unclear licensing, replace them
|
||||
|
||||
The bulk of your assets (95%+) are properly licensed Asset Store purchases or CC content.
|
||||
|
||||
@@ -0,0 +1,388 @@
|
||||
# Deproto Migration Plan
|
||||
|
||||
## Vision
|
||||
|
||||
**Protocol buffers should only be used at the edges** — for network serialization (gRPC) and disk persistence. Inside the Eagle game engine, all logic should operate on native Scala models.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ GRPC BOUNDARY │
|
||||
│ EagleServiceImpl.scala ←→ Proto Messages ←→ Unity Client │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
GameStateConverter
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ SCALA ENGINE │
|
||||
│ │
|
||||
│ GameStateC ───→ Actions ───→ ActionResultT ───→ New GameStateC │
|
||||
│ ↑ │ │
|
||||
│ │ (Pure Scala models) │ │
|
||||
│ └───────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ HeroC, FactionC, ProvinceC, BattalionC, ArmyC, etc. │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
GameStateConverter
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ PERSISTENCE BOUNDARY │
|
||||
│ GameHistory.scala ←→ Proto Messages ←→ File/Database │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Current State
|
||||
|
||||
### Completed Phases
|
||||
|
||||
| Phase | Status | Summary |
|
||||
|-------|--------|---------|
|
||||
| Phase 1: GameStateC | **Complete** | Scala `GameState` model with 22 fields |
|
||||
| Phase 2: EngineImpl | **Complete** | Holds Scala `GameState` internally |
|
||||
| Phase 3: GameHistory | **Complete** | `stateAfter` returns Scala GameState |
|
||||
| Phase 4: ActionResultT | **Complete** | All 59 actions return `ActionResultT` |
|
||||
| Phase 5: Action Base Classes | **Complete** | All `RandomSequentialResultsAction` and `DeterministicSingleResultAction` converted to T-type base classes |
|
||||
| Phase 5b: Base Class Cleanup | **Complete** | `RandomSequentialResultsAction` and `DeterministicSingleResultAction` deleted |
|
||||
| Phase 5c: RoundPhaseAdvancer Actions | **Complete** | All actions called by RoundPhaseAdvancer accept Scala GameState |
|
||||
| Phase 5d: RoundPhaseAdvancer Itself | **Complete** | RoundPhaseAdvancer.checkForPhaseAdvancement takes Scala GameState |
|
||||
|
||||
### Phase 5c/5d Progress (Complete)
|
||||
|
||||
`RoundPhaseAdvancer.checkForPhaseAdvancement` now accepts Scala `GameState` and `ActionResultApplier` directly (PR #4677).
|
||||
|
||||
| Action | PR | Status |
|
||||
|--------|-----|--------|
|
||||
| `PrisonerExchangeAction` | #4670 | ✅ Merged |
|
||||
| `PerformForcedTurnBackAction` | #4671 | ✅ Merged |
|
||||
| `PerformHeroDeparturesAction` | #4672 | ✅ Merged |
|
||||
| `RequestFreeForAllBattlesAction` | #4673 | ✅ Merged |
|
||||
| `EndPlayerCommandsPhaseAction` | #4674 | ✅ Merged |
|
||||
| `EndDiplomacyResolutionPhaseAction` | #4675 | ✅ Merged |
|
||||
| `RoundPhaseAdvancer` itself | #4677 | ✅ Merged |
|
||||
|
||||
### EngineImpl Progress
|
||||
|
||||
| Change | PR | Status |
|
||||
|--------|-----|--------|
|
||||
| `recursiveTransform` deleted | #4677 | ✅ Merged |
|
||||
| `recursiveTransformT` uses `RandomStateTSequencer` | #4677 | ✅ Merged |
|
||||
|
||||
### Current Architecture
|
||||
|
||||
**ActionResultT Production (100% Complete):**
|
||||
- All actions produce `ActionResultT`
|
||||
- Conversion to `ActionResultProto` happens via `ActionResultProtoConverter.toProto()`
|
||||
- No direct `ActionResultProto` construction outside the converter
|
||||
|
||||
**ActionResultProto Consumption (Next Target):**
|
||||
- `ActionResultProtoApplierImpl` - applies proto results to proto GameState
|
||||
- `RoundPhaseAdvancer` - calls converter, passes protos to applier
|
||||
- `InMemoryHistory` / `PersistedHistory` - stores proto results
|
||||
- Service layer (`GameController`, `GamesManager`, etc.) - uses proto for client communication
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Migrate to ActionResultT Consumers
|
||||
|
||||
### Objective
|
||||
|
||||
Eliminate internal consumption of `ActionResultProto`. Everything inside the engine should work with `ActionResultT`.
|
||||
|
||||
### Current Flow (Proto-Heavy)
|
||||
```
|
||||
Action.execute()
|
||||
→ ActionResultT
|
||||
→ ActionResultProtoConverter.toProto()
|
||||
→ ActionResultProto
|
||||
→ ActionResultProtoApplierImpl.applyActionResults()
|
||||
→ GameStateProto
|
||||
→ GameStateConverter.fromProto()
|
||||
→ GameStateC
|
||||
```
|
||||
|
||||
### Target Flow (T-Types Throughout)
|
||||
```
|
||||
Action.execute()
|
||||
→ ActionResultT
|
||||
→ ActionResultApplier.applyActionResults()
|
||||
→ GameStateC
|
||||
|
||||
(Proto conversion only at boundaries)
|
||||
```
|
||||
|
||||
### Key Files to Convert
|
||||
|
||||
**Tier 1 - Core Applier:** ✅ **Complete**
|
||||
```
|
||||
src/main/scala/net/eagle0/eagle/library/actions/applier/ActionResultApplierImpl.scala
|
||||
```
|
||||
`ActionResultApplier` applies `ActionResultT` directly to Scala `GameState`. The legacy `ActionResultTApplierImpl` wraps it and converts to/from proto for callers that still need proto types.
|
||||
|
||||
**Tier 2 - RoundPhaseAdvancer:** ✅ **Complete**
|
||||
```
|
||||
src/main/scala/net/eagle0/eagle/library/RoundPhaseAdvancer.scala
|
||||
```
|
||||
Now accepts Scala `GameState` and `ActionResultApplier`. Only converts to proto lazily for `AvailableCommandsFactory` calls.
|
||||
|
||||
**Tier 3 - Sequencers:**
|
||||
```
|
||||
src/main/scala/net/eagle0/eagle/library/actions/impl/common/RandomStateTSequencer.scala
|
||||
src/main/scala/net/eagle0/eagle/library/actions/impl/common/RandomStateProtoSequencer.scala
|
||||
```
|
||||
Modify `RandomStateTSequencer` to thread Scala `GameState` throughout (currently converts to proto internally). Then evaluate whether `RandomStateProtoSequencer` is still needed at all.
|
||||
|
||||
**Current State**: `RandomStateTSequencer` accepts Scala `GameState` via its `apply()` method but internally converts to proto. All callback methods (`withRandomActionResult`, `withActionResults`, etc.) pass `GameStateProto` to callers, forcing actions that use the sequencer to work with proto types internally.
|
||||
|
||||
**Target State**: Create a fully protoless sequencer where:
|
||||
1. `lastState` returns Scala `GameState` (not `lastStateProto`)
|
||||
2. All callback methods pass Scala `GameState` to callers
|
||||
3. Actions using the sequencer can be fully protoless
|
||||
|
||||
**Migration Path**:
|
||||
1. Add `lastState: GameState` method alongside `lastStateProto` (non-breaking)
|
||||
2. Add parallel callback methods that pass Scala GameState (e.g., `withScalaActionResult`)
|
||||
3. Migrate actions one by one to use the new Scala-based callbacks
|
||||
4. Once all actions migrated, deprecate/remove proto-based callbacks
|
||||
5. Remove `lastStateProto` once no longer used
|
||||
|
||||
**RandomStateSequencer Migration Progress** (PR #4679 introduced protoless `RandomStateSequencer`):
|
||||
|
||||
| Action | Status |
|
||||
|--------|--------|
|
||||
| `TruceTurnBackPhaseAction` | ✅ Migrated (PR #4680) |
|
||||
| `EndHandleRiotsPhaseAction` | ✅ Migrated (PR #4684) |
|
||||
| `PerformVassalCommandsPhaseAction` | ✅ Migrated |
|
||||
| `PerformVassalDefenseDecisionsAction` | ✅ Migrated |
|
||||
| `EndVassalCommandsPhaseAction` | ✅ Migrated |
|
||||
| `PerformReconResolutionAction` | ✅ Migrated |
|
||||
| `NewRoundAction` | ✅ Migrated (PR #4698) |
|
||||
| `EndBattleAftermathPhaseAction` | ✅ Migrated (PR #4699) |
|
||||
| `EndDiplomacyResolutionPhaseAction` | ✅ Migrated |
|
||||
| `PerformUnaffiliatedHeroesAction` | ✅ Migrated |
|
||||
| `EngineImpl.recursiveTransformT` | ✅ Migrated (PR #4704) |
|
||||
| `ProtolessSequentialResultsActionWrapper` | ✅ Migrated (PR #4705) |
|
||||
| `LegacyRandomStateTSequencer` | ✅ **Deleted** (PR #4705) |
|
||||
|
||||
**TCommandFactory Extraction** (PR #4684):
|
||||
|
||||
To enable lightweight mocking of command creation in tests, `TCommandFactory` trait was extracted from `CommandFactory`. This allows tests to mock just the `makeTCommand` method without pulling in all 40+ command dependencies that `CommandFactory` requires.
|
||||
|
||||
- `TCommandFactory` - lightweight trait with just `makeTCommand`
|
||||
- `CommandFactory extends TCommandFactory` - maintains backward compatibility
|
||||
- Actions accepting command factories now use `TCommandFactory` type for better testability
|
||||
|
||||
**Tier 4 - History APIs:**
|
||||
```
|
||||
src/main/scala/net/eagle0/eagle/service/InMemoryHistory.scala
|
||||
src/main/scala/net/eagle0/eagle/service/PersistedHistory.scala
|
||||
```
|
||||
Change APIs to vend Scala `GameState` and `ActionResultT` instead of proto versions. `PersistedHistory` converts to proto internally for disk persistence; `InMemoryHistory` doesn't need proto at all.
|
||||
|
||||
### ActionResultProto Consumer Inventory
|
||||
|
||||
| File | Usage | Status |
|
||||
|------|-------|--------|
|
||||
| `ActionResultApplierImpl.scala` | Applies ActionResultT to Scala GameState | ✅ **Complete** |
|
||||
| `ActionResultTApplierImpl.scala` | Legacy wrapper - converts to/from proto | Keep until all callers migrated |
|
||||
| `RoundPhaseAdvancer.scala` | Uses Scala GameState | ✅ **Complete** |
|
||||
| `RandomStateSequencer.scala` | Threads Scala GameState | ✅ **Complete** |
|
||||
| `VigorXPApplier.scala` | Has both proto and Scala methods | Scala method exists, delete proto method when unused |
|
||||
| `PerformForcedTurnBackAction.scala` | Fully protoless | ✅ **Complete** |
|
||||
| `ResolveBattleAction.scala` | ✅ Fully protoless | Complete (PR #5048) |
|
||||
| `InMemoryHistory.scala` | Stores proto results | Pending - vend Scala types |
|
||||
| `PersistedHistory.scala` | Stores proto results | Pending - vend Scala types, convert for disk |
|
||||
| `GameController.scala` | Uses proto for client communication | Keep proto (gRPC boundary) |
|
||||
|
||||
### Remaining Proto Usage in Actions
|
||||
|
||||
**Progress: 52 of 52 action files (100%) are fully protoless.** ✅
|
||||
|
||||
All action files have been migrated to use Scala types:
|
||||
|
||||
| Action | Status | Notes |
|
||||
|--------|--------|-------|
|
||||
| `ResolveBattleAction` | ✅ Complete | PR #5048 - uses Scala GameState and ActionResultApplier |
|
||||
| `PerformVassalCommandsPhaseAction` | ✅ Complete | Uses Scala types via CommandChoiceHelpers |
|
||||
| `EndHandleRiotsPhaseAction` | ✅ Complete | Uses Scala types via CommandChoiceHelpers |
|
||||
| `PerformVassalDefenseDecisionsAction` | ✅ Complete | Uses Scala types via CommandChoiceHelpers |
|
||||
| `EndVassalCommandsPhaseAction` | ✅ Complete | Uses Scala types via CommandChoiceHelpers |
|
||||
|
||||
**Note:** `NewRoundAction` is now fully protoless after converting `ChronicleEventGenerator` to return Scala `ChronicleEvent` types directly.
|
||||
|
||||
**Deleted Dead Code:**
|
||||
- `UnaffiliatedHeroMovedAction` - Was never called; `PerformUnaffiliatedHeroesAction.heroMovedResult` constructs `ActionResultC` directly
|
||||
- `HeroBackstoryUpdateActionGenerator.fromGameState` - Dead method that converted proto to Scala; only `apply(GameState)` is used
|
||||
|
||||
**Note**: `PerformReconResolutionAction` and `EndBattleAftermathPhaseAction` are now fully protoless after:
|
||||
1. Migrating `FactionT.reconnedProvinces` and `ChangedFactionC.updatedReconnedProvinces` to use Scala `ProvinceView`
|
||||
2. Adding Scala overload of `ProvinceViewFilter.withdrawnFromProvinceView`
|
||||
|
||||
### Estimated Effort (Remaining)
|
||||
|
||||
| Component | Lines | Complexity | Blocks |
|
||||
|-----------|-------|------------|--------|
|
||||
| History API updates | ~100 | Low | - |
|
||||
| **Total Remaining** | **~100** | | |
|
||||
|
||||
**Completed:**
|
||||
- ✅ `ChronicleEventGenerator` converted to return Scala `ChronicleEvent` types directly
|
||||
- ✅ `CommandChoiceHelpers` migrated to Scala types
|
||||
- ✅ `ResolveBattleAction` refactored to use Scala GameState and ActionResultApplier (PR #5048)
|
||||
|
||||
### Enum Type Migrations
|
||||
|
||||
Proto enums are being converted to Scala sealed traits with converters at boundaries:
|
||||
|
||||
| Enum | Scala Type | Status | Notes |
|
||||
|------|------------|--------|-------|
|
||||
| `DiplomacyOfferStatus` | `Status` sealed trait | ✅ **Complete** | PR #5093 - `EligibleDiplomacyStatuses` uses Scala types internally |
|
||||
| `RoundPhase` | `RoundPhase` sealed trait | Partial | Some usages converted |
|
||||
| `BattalionType` | `BattalionType` sealed trait | Partial | Some usages converted |
|
||||
|
||||
**DiplomacyOfferStatus Migration (PR #5093):**
|
||||
- `EligibleDiplomacyStatuses.scala` now returns `Vector[Status]` instead of `Vector[DiplomacyOfferStatus]`
|
||||
- Call sites in `AvailableResolve*CommandFactory` files convert to proto via `StatusConverter.toProto` at the boundary
|
||||
- This pattern should be applied to other proto enums
|
||||
|
||||
### CommandChoiceHelpers Migration Status
|
||||
|
||||
Several command selectors have already been converted to use Scala types:
|
||||
|
||||
| File | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| `AttackCommandChooser.scala` | ✅ **Protoless** | Uses Scala `GameState`, `HeroT`, `ProvinceT` |
|
||||
| `AlmsCommandSelector.scala` | ✅ **Protoless** | Uses Scala `GameState`, `HeroT`, `ProvinceT` |
|
||||
| `FoodConsumptionUtils.scala` | ✅ **Protoless** | Uses Scala `GameState`, `ProvinceT`, `RoundPhase` |
|
||||
| `MarchSuppliesHelpers.scala` | ✅ **Protoless** | Uses `BattalionT` |
|
||||
| `CombatUnitSelector.scala` | ✅ **Protoless** | Uses `HeroT`, `BattalionT`, `BattalionType` |
|
||||
| `ExpandCommandSelector.scala` | ✅ **Protoless** | Uses Scala `GameState`, `ProvinceT`, `FactionT` |
|
||||
| `ImproveCommandSelector.scala` | ✅ **Protoless** | Uses Scala `GameState`, `ProvinceT`, `HeroT` |
|
||||
| `OrganizeCommandSelector.scala` | ✅ **Protoless** | Uses Scala `GameState`, `BattalionT`, `BattalionType` |
|
||||
| `RansomOfferHelpers.scala` | ✅ **Protoless** | Uses Scala `GameState`, `FactionT` |
|
||||
| `CommandChoiceHelpers.scala` | ✅ **Protoless** | Uses Scala `GameState` throughout |
|
||||
| `ProvinceGoldSurplusCalculator.scala` | ✅ **Protoless** | Uses Scala types |
|
||||
|
||||
**All CommandChoiceHelpers selectors have been migrated to Scala types.** ✅
|
||||
|
||||
### Progress Summary
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Action files fully protoless | 52 / 52 (100%) ✅ |
|
||||
| Proto usages in remaining actions | 0 |
|
||||
| Next target | See "Next Candidates" section below |
|
||||
|
||||
### Next Candidates
|
||||
|
||||
Priority candidates for further deproto work:
|
||||
|
||||
1. **More Enum Migrations** - Apply the `DiplomacyOfferStatus` pattern to other proto enums:
|
||||
- Files importing `net.eagle0.eagle.common.round_phase.RoundPhase` (proto) could use Scala `RoundPhase`
|
||||
- Files importing `net.eagle0.eagle.common.battalion_type.BattalionType` (proto) could use Scala `BattalionType`
|
||||
|
||||
2. **AvailableCommandsFactory Files** - Many still use proto `GameState` internally:
|
||||
- These files build proto `AvailableCommand` messages but could use Scala types for internal logic
|
||||
- Convert to accept Scala `GameState`, only convert fields to proto when building the response
|
||||
|
||||
3. **History APIs** - `InMemoryHistory` and `PersistedHistory`:
|
||||
- Change to vend Scala `GameState` and `ActionResultT` instead of proto versions
|
||||
- `PersistedHistory` converts to proto internally for disk persistence
|
||||
|
||||
### Validation
|
||||
- [x] `ActionResultApplier` created and tested
|
||||
- [x] `RandomStateSequencer` threads Scala GameState throughout
|
||||
- [x] `RoundPhaseAdvancer` uses T-types internally
|
||||
- [x] `ProvinceViewFilter` has Scala overload for server-side use (PR #4752)
|
||||
- [x] `FactionT.reconnedProvinces` and `ChangedFactionC.updatedReconnedProvinces` use Scala `ProvinceView`
|
||||
- [x] `ProvinceViewFilter.withdrawnFromProvinceView` has Scala overload
|
||||
- [x] `CommandChoiceHelpers` uses Scala types ✅
|
||||
- [x] All action files (52/52) are fully protoless ✅
|
||||
- [ ] `ProvinceViewFilter` faction-filtered views use Scala types
|
||||
- [ ] History APIs vend Scala types
|
||||
- [ ] No `ActionResultProtoConverter.toProto()` calls except at persistence/gRPC boundaries
|
||||
- [ ] All tests pass
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Clean Up Legacy Utilities
|
||||
|
||||
### Objective
|
||||
Remove remaining direct proto imports from utility classes.
|
||||
|
||||
### Files to Modify
|
||||
|
||||
| File | Status |
|
||||
|------|--------|
|
||||
| `CommandChoiceHelpers.scala` | Accepts proto `GameState`; blocks full deproto of `PerformVassalCommandsPhaseAction` and `PerformVassalDefenseDecisionsAction` |
|
||||
| `LegacyProvinceUtils.scala` | Replace with `ProvinceUtils.scala` - `hasImminentRiot` added (PR #4683) |
|
||||
| `LegacyFactionUtils.scala` | Replace proto imports with `FactionT` |
|
||||
| `LegacyUnaffiliatedHeroUtils.scala` | Replace proto imports with Scala models |
|
||||
| `BattalionTypeLoader.scala` | Keep proto for file loading, convert immediately after |
|
||||
| `BeastUtils.scala` | **Complete** - now uses Scala `BeastInfo` only |
|
||||
|
||||
### View Filters (Partially Complete)
|
||||
|
||||
The view filter utilities now have Scala overloads for server-side use:
|
||||
|
||||
| File | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| `ProvinceViewFilter.scala` | **Partial** | `filteredProvinceView(ProvinceT, ScalaGameState)` added (PR #4752) |
|
||||
| `ArmyFilter.scala` | **Partial** | `filterArmy(ScalaArmy, Map[BattalionId, BattalionT], Option[FactionId])` added |
|
||||
| `BattalionViewFilter.scala` | **Complete** | Uses Scala `BattalionT` throughout |
|
||||
| `GameStateViewFilter.scala` | Pending | Uses proto types throughout |
|
||||
| `GameStateViewDiffer.scala` | Pending | Works with view protos |
|
||||
|
||||
**Unblocked Actions** (PR #4752):
|
||||
- `EndBattleAftermathPhaseAction` - can now use `filteredProvinceView(province, scalaGameState)`
|
||||
- `PerformReconResolutionAction` - can now use Scala overload
|
||||
- `GameStateFactionExtensions` - can now use `updatedReconnedProvinces` with Scala types
|
||||
|
||||
**Remaining Work**:
|
||||
- Faction-filtered `filteredProvinceView(Province, GameState, FactionId)` still uses proto types
|
||||
- `withdrawnFromProvinceView` still uses proto types
|
||||
- These are needed for client-facing views with visibility restrictions
|
||||
|
||||
---
|
||||
|
||||
## Phase 8: Verify Boundaries
|
||||
|
||||
### Objective
|
||||
Confirm protos are used correctly at boundaries — and ONLY there.
|
||||
|
||||
### Expected Proto Usage (Keep)
|
||||
- `EagleServiceImpl.scala` - gRPC boundary
|
||||
- `InMemoryHistory.scala` / `PersistedHistory.scala` - Persistence boundary
|
||||
- `*Converter.scala` - Explicit conversion utilities
|
||||
- `*Loader.scala` - File loading utilities
|
||||
|
||||
### Expected No Proto Usage (Verify)
|
||||
- `/library/actions/impl/` - Pure Scala models
|
||||
- `/library/util/` - Pure Scala models (except loaders)
|
||||
- `/model/state/` - Pure Scala models
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **Persistence Format**: Currently game state is persisted as proto. Should we keep proto for persistence (good for schema evolution) or switch to a different format?
|
||||
|
||||
2. **Shardok Integration**: `ResolveBattleAction` communicates with Shardok. Should the Shardok interface use protos (external service) or Scala models?
|
||||
|
||||
3. **View Generation**: `GameStateViewDiffer` works with view protos for client updates. Views need Scala models (`ProvinceViewT`, etc.) to allow actions like `EndBattleAftermathPhaseAction` to be fully protoless. The Scala views would be converted to proto only at the gRPC boundary when sending updates to clients.
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
### Code Quality
|
||||
- [ ] Zero proto imports in `/library/actions/` (except boundaries)
|
||||
- [ ] Zero proto imports in `/library/` utilities (except loaders)
|
||||
- [ ] `GameStateT` used throughout engine internals
|
||||
- [ ] Proto usage limited to: `EagleServiceImpl`, loaders, converters, persistence
|
||||
|
||||
### Architecture
|
||||
- [ ] Clear separation: Scala models (internal) vs Proto (boundaries)
|
||||
- [ ] Converters as the only bridge between domains
|
||||
- [ ] No "proto creep" into business logic
|
||||
@@ -1,94 +0,0 @@
|
||||
# LLM Model Comparison
|
||||
|
||||
This document compares streaming latency (time-to-first-token) and pricing across OpenAI, Anthropic (Claude), and Google (Gemini) models for use in Eagle's narrative text generation.
|
||||
|
||||
## Test Methodology
|
||||
|
||||
All tests were performed locally using curl with streaming enabled. Each model was tested 3 times with the same prompt:
|
||||
|
||||
> "Write a short paragraph about a brave knight who discovers a hidden cave. Make it vivid and descriptive."
|
||||
|
||||
Time-to-first-token (TTFT) was measured from request initiation to the first text content appearing in the stream.
|
||||
|
||||
## Streaming Latency Results (January 2026)
|
||||
|
||||
| Model | Run 1 | Run 2 | Run 3 | Average TTFT |
|
||||
|-------|-------|-------|-------|--------------|
|
||||
| **Gemini 2.5 Flash-Lite** | 0.76s | 0.54s | 0.53s | **~0.6s** |
|
||||
| **gpt-4.1-mini** | 1.65s | 1.72s | 1.68s | **~1.7s** |
|
||||
| **claude-3-5-haiku** | 1.85s | 1.92s | 1.88s | **~1.9s** |
|
||||
| gpt-5.2 | 3.25s | 3.38s | 3.32s | **~3.3s** |
|
||||
| gpt-5-mini | 2.52s | 5.82s | 3.12s | **~3.8s** (high variance) |
|
||||
| Gemini 3 Flash Preview | 4.11s | 4.77s | 4.28s | **~4.4s** |
|
||||
| claude-sonnet-4 | 4.89s | 5.12s | 4.98s | **~5.0s** |
|
||||
| Gemini 2.5 Flash | 5.60s | 7.00s | 7.79s | **~6.8s** |
|
||||
|
||||
## Pricing Comparison (per 1M tokens)
|
||||
|
||||
| Model | Input Price | Output Price | Notes |
|
||||
|-------|-------------|--------------|-------|
|
||||
| **Gemini 2.5 Flash-Lite** | $0.10 | $0.40 | Cheapest and fastest |
|
||||
| Gemini 2.5 Flash | $0.15 | $0.60 | |
|
||||
| gpt-5-mini | $0.25 | $2.00 | |
|
||||
| **gpt-4.1-mini** | $0.40 | $1.60 | Best OpenAI value |
|
||||
| Gemini 3 Flash Preview | $0.50 | $3.00 | Includes thinking tokens |
|
||||
| **claude-3-5-haiku** | $0.80 | $4.00 | Best Anthropic value |
|
||||
| gpt-5.2 | ~$1.00 | ~$10.00 | Full reasoning model |
|
||||
| Gemini 2.5 Pro | $1.25 | $10.00 | |
|
||||
| Gemini 3 Pro Preview | $2.00 | $12.00 | ≤200K context |
|
||||
| claude-sonnet-4 | $3.00 | $15.00 | |
|
||||
|
||||
## Recommendations
|
||||
|
||||
### For Narrative Text Generation (Default)
|
||||
|
||||
**Gemini 2.5 Flash-Lite** is recommended as the default:
|
||||
- Fastest TTFT (~0.6s) - nearly 3x faster than alternatives
|
||||
- Cheapest pricing ($0.10/$0.40 per 1M tokens)
|
||||
- Quality is acceptable for short narrative snippets
|
||||
|
||||
### Alternative Options
|
||||
|
||||
| Priority | Model | When to Use |
|
||||
|----------|-------|-------------|
|
||||
| Speed + Cost | Gemini 2.5 Flash-Lite | Default for most use cases |
|
||||
| Speed + Quality | gpt-4.1-mini | When you need OpenAI quality with good speed |
|
||||
| Instruction Following | claude-3-5-haiku | Complex multi-step prompts, consistent tone |
|
||||
| Maximum Quality | claude-sonnet-4 or gpt-5.2 | When output quality is paramount |
|
||||
|
||||
### Quality Trade-offs
|
||||
|
||||
For short narrative snippets (1-3 paragraphs):
|
||||
- **Flash-Lite vs Haiku/4.1-mini**: Minor quality difference, significant speed gain
|
||||
- **Haiku vs Sonnet**: Noticeable quality difference in creative writing variety
|
||||
- **gpt-4.1-mini vs gpt-5.2**: Moderate quality difference, significant cost savings
|
||||
|
||||
## Configuration
|
||||
|
||||
LLM settings can be changed at runtime via the admin console:
|
||||
|
||||
1. Navigate to Admin Console → Settings
|
||||
2. Change `LlmProvider` to select vendor (gemini, openai, claude)
|
||||
3. Change the corresponding model name setting:
|
||||
- `GeminiModelName` (default: gemini-2.5-flash-lite)
|
||||
- `OpenAiModelName` (default: gpt-4.1-mini)
|
||||
- `ClaudeModelName` (default: claude-3-5-haiku-20241022)
|
||||
|
||||
Changes take effect on the next LLM request.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
For production deployment, ensure API keys are set:
|
||||
|
||||
```bash
|
||||
OPENAI_API_KEY=sk-...
|
||||
ANTHROPIC_API_KEY=sk-ant-...
|
||||
GEMINI_API_KEY=AIza...
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- **gpt-5-mini** showed high latency variance (2.5s - 5.8s) in testing
|
||||
- **Gemini 2.5 Flash** was surprisingly slower than Flash-Lite, possibly due to internal reasoning overhead
|
||||
- **Gemini 3 Flash** is a frontier model with better quality but higher latency than 2.5 Flash-Lite
|
||||
- All Gemini models have a generous free tier (up to 1,000 daily requests)
|
||||
@@ -1,78 +0,0 @@
|
||||
# The Small Eagle TODO
|
||||
|
||||
## Goals
|
||||
|
||||
Be able to support a small (10-50 user) private alpha, including with strangers.
|
||||
|
||||
Larger set of goals in [The Big Eagle TODO](https://docs.google.com/document/d/17RTt3-4Wl2AAVMRLodaC3a84E4de6xuQTWBPvCRM484/edit?pli=1&tab=t.0), but most of that is not necessary for MVP.
|
||||
|
||||
## Required
|
||||
|
||||
### Gameplay Productionization
|
||||
|
||||
- [x] ~~All functionality works on production eagle / shardok servers~~
|
||||
- [x] ~~Acceptable latency in all regions~~
|
||||
- [x] ~~Shardok performance similar to QA~~
|
||||
- [x] ~~Error logging & alerting~~
|
||||
- [x] ~~Fix long disconnects on deployments~~
|
||||
- [ ] Fix the Mac installer
|
||||
- [x] ~~Still not reconnecting after deployments~~
|
||||
- [ ] Notify about client updates, button to come directly back
|
||||
- [ ] Generatedtext healing
|
||||
- [ ] Kill outstanding shardok requests when game is deleted
|
||||
|
||||
### Other Productionization
|
||||
|
||||
- [x] ~~Oauth sign-in~~
|
||||
- [x] ~~Add Google, others?~~
|
||||
- [ ] User management
|
||||
- [x] ~~Invite codes~~
|
||||
- [ ] Link accounts
|
||||
- [x] ~~Choose display name~~
|
||||
- [x] ~~Just do account setup from the landing page?~~
|
||||
- [x] ~~Download client directly from DO, avoid basic auth/my home network~~
|
||||
- [ ] "Message of the day"
|
||||
- [ ] Support plan
|
||||
|
||||
### Alpha Tester Support
|
||||
|
||||
- [ ] Feedback channel (Discord server? Bug report form?)
|
||||
- [ ] Crash reporting from Unity client
|
||||
- [ ] Known issues doc (so testers don't report the same things)
|
||||
|
||||
### IP / Legal
|
||||
|
||||
- [ ] Document and make available licenses for art & music
|
||||
- [ ] Required open source disclosures
|
||||
- [ ] Audit assets for anything we don't have rights to and replace it
|
||||
- [ ] Replace heroes that are based on real 20th or 21st century people or IP
|
||||
- [ ] Privacy policy (collecting accounts, OAuth data, gameplay data)
|
||||
- [ ] Terms of service (basic liability protection)
|
||||
- [ ] Data deletion capability (user requests account removal)
|
||||
|
||||
### Basic Gameplay
|
||||
|
||||
- [ ] Tutorial
|
||||
- [ ] In the Your Warlord panel, say what the profession is
|
||||
- [x] ~~And separate panels for each profession when you encounter one~~
|
||||
- [x] ~~Command tutorial for each command the first time it's clicked~~
|
||||
- [x] ~~Time to recruit / expand~~
|
||||
- [x] ~~And how expansion works~~
|
||||
- [ ] Province events
|
||||
- [ ] Running low on food
|
||||
- [x] ~~Time to swear brotherhood~~
|
||||
- [x] ~~When you get large, or~~
|
||||
- [x] ~~When you get a good candidate~~
|
||||
- [ ] Shardok tutorial!
|
||||
- [ ] Basic Shardok AI stuff fixed
|
||||
- [ ] Lobby fixes
|
||||
- [ ] Have goals / ending
|
||||
- [ ] First-session onboarding (beyond mechanics tutorial)
|
||||
- [ ] Narrative hook in first few minutes - why should I care about my warlord?
|
||||
- [ ] Clear first-session goal ("try to capture your first province" or similar)
|
||||
- [ ] Early small victory to build momentum
|
||||
- [ ] Guided first scenario vs. overwhelming sandbox?
|
||||
|
||||
## Nice to have
|
||||
|
||||
<!-- Add nice-to-have items here as they come up -->
|
||||
@@ -1,568 +0,0 @@
|
||||
# Sparkle Delta Updates Implementation Plan
|
||||
|
||||
## Overview
|
||||
|
||||
This document outlines the implementation plan for adding delta update support to the Eagle0 macOS auto-update system using Sparkle's BinaryDelta feature.
|
||||
|
||||
### Current State
|
||||
- Full DMG downloads (~200MB) for every update
|
||||
- `mac_build_handler.go` creates DMG, signs it, uploads to S3, updates appcast.xml
|
||||
- Keeps last 10 versions in appcast, deletes older DMGs
|
||||
- Users must download full app even for small changes
|
||||
|
||||
### Goals
|
||||
- Reduce update download size from ~200MB to ~10-30MB (85% reduction)
|
||||
- Maintain backward compatibility with full DMG downloads
|
||||
- Automatic fallback for users who are many versions behind
|
||||
|
||||
## Sparkle Delta Update Architecture
|
||||
|
||||
Sparkle supports binary delta updates through the `<sparkle:deltas>` element in the appcast. When a user updates, Sparkle:
|
||||
1. Checks if a delta patch exists from their current version to the new version
|
||||
2. If found, downloads the smaller delta patch instead of the full DMG
|
||||
3. Applies the patch locally to create the new app version
|
||||
4. Falls back to full DMG if no matching delta exists
|
||||
|
||||
### Appcast XML Structure with Deltas
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle">
|
||||
<channel>
|
||||
<title>Eagle0</title>
|
||||
<link>https://assets.eagle0.net/mac/appcast.xml</link>
|
||||
<description>Eagle0 game updates</description>
|
||||
<language>en</language>
|
||||
<item>
|
||||
<title>Version 1.0.9615</title>
|
||||
<pubDate>Sun, 19 Jan 2026 12:00:00 -0800</pubDate>
|
||||
<sparkle:version>9615</sparkle:version>
|
||||
<sparkle:shortVersionString>1.0.9615</sparkle:shortVersionString>
|
||||
<enclosure
|
||||
url="https://assets.eagle0.net/mac/builds/eagle0-1.0.9615.dmg"
|
||||
length="200000000"
|
||||
type="application/octet-stream"
|
||||
sparkle:edSignature="..." />
|
||||
<sparkle:deltas>
|
||||
<enclosure
|
||||
url="https://assets.eagle0.net/mac/deltas/9614-9615.delta"
|
||||
sparkle:deltaFrom="9614"
|
||||
length="15000000"
|
||||
type="application/octet-stream"
|
||||
sparkle:edSignature="..." />
|
||||
<enclosure
|
||||
url="https://assets.eagle0.net/mac/deltas/9613-9615.delta"
|
||||
sparkle:deltaFrom="9613"
|
||||
length="18000000"
|
||||
type="application/octet-stream"
|
||||
sparkle:edSignature="..." />
|
||||
<enclosure
|
||||
url="https://assets.eagle0.net/mac/deltas/9612-9615.delta"
|
||||
sparkle:deltaFrom="9612"
|
||||
length="22000000"
|
||||
type="application/octet-stream"
|
||||
sparkle:edSignature="..." />
|
||||
</sparkle:deltas>
|
||||
</item>
|
||||
<!-- older versions... -->
|
||||
</channel>
|
||||
</rss>
|
||||
```
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Add S3 Utility Functions
|
||||
|
||||
**File:** `src/main/go/net/eagle0/util/aws/bucket_basics.go`
|
||||
|
||||
Add two new functions to support delta generation:
|
||||
|
||||
```go
|
||||
// ListObjectsWithPrefix returns all object keys matching the given prefix
|
||||
func (bb BucketBasics) ListObjectsWithPrefix(bucket, prefix string) ([]string, error) {
|
||||
var keys []string
|
||||
paginator := s3.NewListObjectsV2Paginator(bb.S3Client, &s3.ListObjectsV2Input{
|
||||
Bucket: aws.String(bucket),
|
||||
Prefix: aws.String(prefix),
|
||||
})
|
||||
|
||||
for paginator.HasMorePages() {
|
||||
page, err := paginator.NextPage(context.TODO())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, obj := range page.Contents {
|
||||
keys = append(keys, *obj.Key)
|
||||
}
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// DownloadFile downloads an object to a local file path
|
||||
func (bb BucketBasics) DownloadFile(bucket, key, localPath string) error {
|
||||
result, err := bb.S3Client.GetObject(context.TODO(), &s3.GetObjectInput{
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(key),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer result.Body.Close()
|
||||
|
||||
file, err := os.Create(localPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(file, result.Body)
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 2: Store App Bundles for Delta Generation
|
||||
|
||||
**File:** `src/main/go/net/eagle0/build/mac_build_handler/mac_build_handler.go`
|
||||
|
||||
Add storage paths:
|
||||
```go
|
||||
var appsRoot = "mac/apps/" // Zipped app bundles for delta generation
|
||||
var deltasRoot = "mac/deltas/" // Delta patches
|
||||
```
|
||||
|
||||
After DMG creation, upload the zipped app bundle:
|
||||
```go
|
||||
func uploadAppBundle(bb aws.BucketBasics, appPath string, buildNumber string) error {
|
||||
appZipPath := filepath.Join("/tmp", fmt.Sprintf("eagle0-%s.app.zip", buildNumber))
|
||||
|
||||
// Create zip of app bundle using ditto (preserves metadata)
|
||||
cmd := exec.Command("ditto", "-c", "-k", "--keepParent", appPath, appZipPath)
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("failed to zip app: %s: %w", string(output), err)
|
||||
}
|
||||
defer os.Remove(appZipPath)
|
||||
|
||||
// Upload to S3
|
||||
remotePath := appsRoot + fmt.Sprintf("eagle0-%s.app.zip", buildNumber)
|
||||
log.Printf("Uploading app bundle to S3: %s", remotePath)
|
||||
return bb.UploadFilePublic(bucketName, remotePath, appZipPath)
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 3: Add Delta XML Structures
|
||||
|
||||
**File:** `src/main/go/net/eagle0/build/mac_build_handler/mac_build_handler.go`
|
||||
|
||||
Add new structs for delta representation:
|
||||
```go
|
||||
// Delta represents a delta patch from a previous version
|
||||
type Delta struct {
|
||||
XMLName xml.Name `xml:"enclosure"`
|
||||
URL string `xml:"url,attr"`
|
||||
DeltaFrom string `xml:"sparkle:deltaFrom,attr"`
|
||||
Length int64 `xml:"length,attr"`
|
||||
Type string `xml:"type,attr"`
|
||||
EdSig string `xml:"sparkle:edSignature,attr"`
|
||||
}
|
||||
|
||||
// Deltas wraps the sparkle:deltas element
|
||||
type Deltas struct {
|
||||
XMLName xml.Name `xml:"sparkle:deltas"`
|
||||
Items []Delta `xml:"enclosure"`
|
||||
}
|
||||
|
||||
// Update Item struct to include Deltas
|
||||
type Item struct {
|
||||
Title string `xml:"title"`
|
||||
PubDate string `xml:"pubDate"`
|
||||
SparkleVersion string `xml:"sparkle:version"`
|
||||
SparkleShortVersion string `xml:"sparkle:shortVersionString"`
|
||||
Description string `xml:"description,omitempty"`
|
||||
Enclosure Enclosure `xml:"enclosure"`
|
||||
Deltas *Deltas `xml:"sparkle:deltas,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 4: Generate Delta Patches
|
||||
|
||||
**File:** `src/main/go/net/eagle0/build/mac_build_handler/mac_build_handler.go`
|
||||
|
||||
```go
|
||||
// Maximum number of versions to generate deltas from
|
||||
const maxDeltaVersions = 5
|
||||
|
||||
// generateDeltas creates delta patches from previous versions to the new version
|
||||
func generateDeltas(bb aws.BucketBasics, newBuildNumber string, newAppPath string, privateKeyPath string) ([]Delta, error) {
|
||||
var deltas []Delta
|
||||
|
||||
// Ensure BinaryDelta tool is available
|
||||
binaryDeltaPath, err := ensureBinaryDelta()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get BinaryDelta: %w", err)
|
||||
}
|
||||
|
||||
// List available app bundles
|
||||
appKeys, err := bb.ListObjectsWithPrefix(bucketName, appsRoot+"eagle0-")
|
||||
if err != nil {
|
||||
log.Printf("Warning: failed to list app bundles: %v", err)
|
||||
return deltas, nil // Continue without deltas
|
||||
}
|
||||
|
||||
// Parse build numbers from keys and sort descending
|
||||
var buildNumbers []string
|
||||
for _, key := range appKeys {
|
||||
// Extract build number from "mac/apps/eagle0-9614.app.zip"
|
||||
base := filepath.Base(key)
|
||||
if strings.HasPrefix(base, "eagle0-") && strings.HasSuffix(base, ".app.zip") {
|
||||
bn := strings.TrimSuffix(strings.TrimPrefix(base, "eagle0-"), ".app.zip")
|
||||
if bn != newBuildNumber {
|
||||
buildNumbers = append(buildNumbers, bn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort descending (most recent first) and limit to maxDeltaVersions
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(buildNumbers)))
|
||||
if len(buildNumbers) > maxDeltaVersions {
|
||||
buildNumbers = buildNumbers[:maxDeltaVersions]
|
||||
}
|
||||
|
||||
// Generate delta from each previous version
|
||||
for _, oldBuild := range buildNumbers {
|
||||
delta, err := generateSingleDelta(bb, binaryDeltaPath, oldBuild, newBuildNumber, newAppPath, privateKeyPath)
|
||||
if err != nil {
|
||||
log.Printf("Warning: failed to generate delta from %s: %v", oldBuild, err)
|
||||
continue // Skip this delta but continue with others
|
||||
}
|
||||
deltas = append(deltas, delta)
|
||||
}
|
||||
|
||||
return deltas, nil
|
||||
}
|
||||
|
||||
func generateSingleDelta(bb aws.BucketBasics, binaryDeltaPath, oldBuild, newBuild, newAppPath, privateKeyPath string) (Delta, error) {
|
||||
// Download old app bundle
|
||||
oldAppZipKey := appsRoot + fmt.Sprintf("eagle0-%s.app.zip", oldBuild)
|
||||
oldAppZipLocal := filepath.Join("/tmp", fmt.Sprintf("eagle0-%s.app.zip", oldBuild))
|
||||
defer os.Remove(oldAppZipLocal)
|
||||
|
||||
if err := bb.DownloadFile(bucketName, oldAppZipKey, oldAppZipLocal); err != nil {
|
||||
return Delta{}, fmt.Errorf("failed to download old app: %w", err)
|
||||
}
|
||||
|
||||
// Unzip old app
|
||||
oldAppDir := filepath.Join("/tmp", fmt.Sprintf("old-app-%s", oldBuild))
|
||||
defer os.RemoveAll(oldAppDir)
|
||||
|
||||
cmd := exec.Command("ditto", "-x", "-k", oldAppZipLocal, oldAppDir)
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return Delta{}, fmt.Errorf("failed to unzip old app: %s: %w", string(output), err)
|
||||
}
|
||||
|
||||
oldAppPath := filepath.Join(oldAppDir, "eagle0.app")
|
||||
|
||||
// Generate delta
|
||||
deltaPath := filepath.Join("/tmp", fmt.Sprintf("%s-%s.delta", oldBuild, newBuild))
|
||||
defer os.Remove(deltaPath)
|
||||
|
||||
cmd = exec.Command(binaryDeltaPath, "create", oldAppPath, newAppPath, deltaPath)
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return Delta{}, fmt.Errorf("failed to create delta: %s: %w", string(output), err)
|
||||
}
|
||||
|
||||
// Get delta size
|
||||
deltaSize, err := getFileSize(deltaPath)
|
||||
if err != nil {
|
||||
return Delta{}, fmt.Errorf("failed to get delta size: %w", err)
|
||||
}
|
||||
log.Printf("Delta %s->%s size: %d bytes (%.1f MB)", oldBuild, newBuild, deltaSize, float64(deltaSize)/1024/1024)
|
||||
|
||||
// Sign delta
|
||||
signature, err := signWithSparkle(deltaPath, privateKeyPath)
|
||||
if err != nil {
|
||||
return Delta{}, fmt.Errorf("failed to sign delta: %w", err)
|
||||
}
|
||||
|
||||
// Upload delta
|
||||
deltaKey := deltasRoot + fmt.Sprintf("%s-%s.delta", oldBuild, newBuild)
|
||||
if err := bb.UploadFilePublic(bucketName, deltaKey, deltaPath); err != nil {
|
||||
return Delta{}, fmt.Errorf("failed to upload delta: %w", err)
|
||||
}
|
||||
|
||||
deltaURL := fmt.Sprintf("https://assets.eagle0.net/%s", deltaKey)
|
||||
return Delta{
|
||||
URL: deltaURL,
|
||||
DeltaFrom: oldBuild,
|
||||
Length: deltaSize,
|
||||
Type: "application/octet-stream",
|
||||
EdSig: signature,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ensureBinaryDelta() (string, error) {
|
||||
binaryDeltaPath := "/tmp/sparkle-cache/Sparkle-2.6.4/bin/BinaryDelta"
|
||||
|
||||
if _, err := os.Stat(binaryDeltaPath); os.IsNotExist(err) {
|
||||
log.Println("Sparkle BinaryDelta not found, downloading...")
|
||||
cmd := exec.Command("bash", "-c", `
|
||||
mkdir -p /tmp/sparkle-cache
|
||||
curl -sL https://github.com/sparkle-project/Sparkle/releases/download/2.6.4/Sparkle-2.6.4.tar.xz | tar -xJ -C /tmp/sparkle-cache
|
||||
`)
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return "", fmt.Errorf("failed to download Sparkle: %s: %w", string(output), err)
|
||||
}
|
||||
}
|
||||
|
||||
return binaryDeltaPath, nil
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 5: Update Main Deploy Flow
|
||||
|
||||
**File:** `src/main/go/net/eagle0/build/mac_build_handler/mac_build_handler.go`
|
||||
|
||||
Modify `main()` to integrate delta generation:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
// ... existing argument parsing ...
|
||||
|
||||
// Create DMG (existing)
|
||||
if err := createDMG(appPath, dmgPath, "Eagle0"); err != nil {
|
||||
log.Fatalf("Failed to create DMG: %v", err)
|
||||
}
|
||||
|
||||
// ... existing DMG upload ...
|
||||
|
||||
if privateKeyPath != "" {
|
||||
// Upload app bundle for future delta generation (NEW)
|
||||
log.Println("Uploading app bundle for delta generation...")
|
||||
if err := uploadAppBundle(bb, appPath, buildNumber); err != nil {
|
||||
log.Printf("Warning: failed to upload app bundle: %v", err)
|
||||
// Continue - delta generation is optional
|
||||
}
|
||||
|
||||
// Generate deltas from previous versions (NEW)
|
||||
log.Println("Generating delta patches...")
|
||||
deltas, err := generateDeltas(bb, buildNumber, appPath, privateKeyPath)
|
||||
if err != nil {
|
||||
log.Printf("Warning: failed to generate deltas: %v", err)
|
||||
} else {
|
||||
log.Printf("Generated %d delta patches", len(deltas))
|
||||
}
|
||||
|
||||
// Update appcast with deltas
|
||||
log.Println("Updating appcast.xml...")
|
||||
appcast, err := fetchAppcast(bb)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to fetch appcast: %v", err)
|
||||
}
|
||||
|
||||
// Create new item with deltas
|
||||
newItem := Item{
|
||||
Title: fmt.Sprintf("Version %s", version),
|
||||
PubDate: time.Now().Format(time.RFC1123Z),
|
||||
SparkleVersion: buildNumber,
|
||||
SparkleShortVersion: version,
|
||||
Description: "",
|
||||
Enclosure: Enclosure{
|
||||
URL: downloadURL,
|
||||
Length: fileSize,
|
||||
Type: "application/octet-stream",
|
||||
EdSig: signature,
|
||||
},
|
||||
}
|
||||
|
||||
// Add deltas if any were generated
|
||||
if len(deltas) > 0 {
|
||||
newItem.Deltas = &Deltas{Items: deltas}
|
||||
}
|
||||
|
||||
// ... rest of appcast handling ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 6: Cleanup Old Artifacts
|
||||
|
||||
**File:** `src/main/go/net/eagle0/build/mac_build_handler/mac_build_handler.go`
|
||||
|
||||
When pruning old versions from appcast, also delete associated artifacts:
|
||||
|
||||
```go
|
||||
// In the appcast pruning section, after removing old items:
|
||||
if len(appcast.Channel.Items) > 10 {
|
||||
oldItems := appcast.Channel.Items[10:]
|
||||
for _, item := range oldItems {
|
||||
oldBuild := item.SparkleVersion
|
||||
|
||||
// Delete old DMG (existing)
|
||||
dmgKey := strings.TrimPrefix(item.Enclosure.URL, "https://assets.eagle0.net/")
|
||||
log.Printf("Deleting old build: %s", dmgKey)
|
||||
bb.DeleteObject(bucketName, dmgKey)
|
||||
|
||||
// Delete old app bundle (NEW)
|
||||
appKey := appsRoot + fmt.Sprintf("eagle0-%s.app.zip", oldBuild)
|
||||
log.Printf("Deleting old app bundle: %s", appKey)
|
||||
bb.DeleteObject(bucketName, appKey)
|
||||
|
||||
// Delete deltas TO this version (NEW)
|
||||
deltaKeys, _ := bb.ListObjectsWithPrefix(bucketName, deltasRoot)
|
||||
for _, key := range deltaKeys {
|
||||
if strings.HasSuffix(key, fmt.Sprintf("-%s.delta", oldBuild)) {
|
||||
log.Printf("Deleting old delta: %s", key)
|
||||
bb.DeleteObject(bucketName, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
appcast.Channel.Items = appcast.Channel.Items[:10]
|
||||
}
|
||||
```
|
||||
|
||||
## S3 Storage Structure
|
||||
|
||||
After implementation, the S3 bucket will have this structure:
|
||||
|
||||
```
|
||||
eagle0-windows/
|
||||
├── mac/
|
||||
│ ├── appcast.xml # Update feed with delta info
|
||||
│ ├── builds/ # Full DMG downloads
|
||||
│ │ ├── eagle0-1.0.9620.dmg
|
||||
│ │ ├── eagle0-1.0.9619.dmg
|
||||
│ │ ├── ...
|
||||
│ │ └── eagle0-latest.dmg # Symlink to latest
|
||||
│ ├── apps/ # Zipped app bundles (NEW)
|
||||
│ │ ├── eagle0-9620.app.zip
|
||||
│ │ ├── eagle0-9619.app.zip
|
||||
│ │ ├── eagle0-9618.app.zip
|
||||
│ │ ├── eagle0-9617.app.zip
|
||||
│ │ └── eagle0-9616.app.zip # Keep last 5 for delta gen
|
||||
│ └── deltas/ # Delta patches (NEW)
|
||||
│ ├── 9619-9620.delta
|
||||
│ ├── 9618-9620.delta
|
||||
│ ├── 9617-9620.delta
|
||||
│ ├── 9616-9620.delta
|
||||
│ ├── 9615-9620.delta
|
||||
│ ├── 9618-9619.delta
|
||||
│ ├── 9617-9619.delta
|
||||
│ └── ...
|
||||
```
|
||||
|
||||
## Storage Impact Analysis
|
||||
|
||||
### Current Storage (without deltas)
|
||||
- 10 DMGs × 200MB = **~2GB**
|
||||
|
||||
### Estimated Storage (with deltas)
|
||||
- 10 DMGs × 200MB = 2GB
|
||||
- 5 app bundles × 150MB = 0.75GB (zip compression)
|
||||
- ~25 delta files × 20MB avg = 0.5GB
|
||||
- **Total: ~3.25GB**
|
||||
|
||||
### Trade-offs
|
||||
- **+1.25GB storage** (~60% increase)
|
||||
- **-170MB per user update** (~85% bandwidth savings)
|
||||
- Break-even: ~8 user updates to recoup storage cost
|
||||
|
||||
## Bandwidth Savings
|
||||
|
||||
| Scenario | Without Deltas | With Deltas | Savings |
|
||||
|----------|---------------|-------------|---------|
|
||||
| 1 version behind | 200MB | ~15MB | 92% |
|
||||
| 2 versions behind | 200MB | ~20MB | 90% |
|
||||
| 3 versions behind | 200MB | ~25MB | 87% |
|
||||
| 5 versions behind | 200MB | ~35MB | 82% |
|
||||
| 6+ versions behind | 200MB | 200MB (full) | 0% |
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
The implementation is backward-compatible and requires no changes to existing clients:
|
||||
|
||||
1. **First deploy after implementation:**
|
||||
- Stores app bundle for the first time
|
||||
- No deltas generated (no previous app bundles exist)
|
||||
- Appcast has no `<sparkle:deltas>` element
|
||||
|
||||
2. **Second deploy:**
|
||||
- Generates delta from previous version
|
||||
- Appcast now has `<sparkle:deltas>` with one entry
|
||||
- Users on previous version get delta update
|
||||
|
||||
3. **Subsequent deploys:**
|
||||
- Generate deltas from last 5 versions
|
||||
- Users within 5 versions get delta updates
|
||||
- Users more than 5 versions behind get full DMG
|
||||
|
||||
4. **Client behavior:**
|
||||
- Sparkle automatically checks for matching delta
|
||||
- Falls back to full DMG if no delta matches
|
||||
- No client code changes required
|
||||
|
||||
## Error Handling
|
||||
|
||||
The implementation handles failures gracefully:
|
||||
|
||||
1. **S3 list/download fails:** Skip delta generation, use full DMG
|
||||
2. **BinaryDelta fails for one version:** Log warning, continue with other versions
|
||||
3. **Signing fails:** Skip that delta, continue with others
|
||||
4. **Upload fails:** Skip that delta, continue with others
|
||||
|
||||
The deploy never fails due to delta issues - deltas are optional enhancements.
|
||||
|
||||
## Verification Plan
|
||||
|
||||
### Manual Testing
|
||||
|
||||
1. **Deploy version N:**
|
||||
- Verify app bundle uploaded to `mac/apps/eagle0-N.app.zip`
|
||||
- Verify appcast has no deltas (first deploy)
|
||||
|
||||
2. **Deploy version N+1:**
|
||||
- Verify delta generated at `mac/deltas/N-(N+1).delta`
|
||||
- Verify appcast contains `<sparkle:deltas>` element
|
||||
- Verify delta signature is valid
|
||||
|
||||
3. **Test update from N to N+1:**
|
||||
- Install version N manually
|
||||
- Check for updates
|
||||
- Monitor download size in Player.log (should be ~15-30MB, not 200MB)
|
||||
- Verify app updated successfully
|
||||
|
||||
4. **Test fresh install:**
|
||||
- Download latest DMG directly
|
||||
- Verify installation works normally
|
||||
|
||||
5. **Test fallback scenario:**
|
||||
- Install a version more than 5 versions behind
|
||||
- Update should download full DMG
|
||||
|
||||
### Automated Verification
|
||||
|
||||
Add to CI workflow (optional):
|
||||
```yaml
|
||||
- name: Verify delta generation
|
||||
run: |
|
||||
# Check app bundle exists
|
||||
aws s3 ls s3://eagle0-windows/mac/apps/ | grep eagle0-${BUILD_NUMBER}.app.zip
|
||||
|
||||
# Check deltas exist (after second deploy)
|
||||
aws s3 ls s3://eagle0-windows/mac/deltas/ | head -5
|
||||
|
||||
# Verify appcast has deltas
|
||||
curl -s https://assets.eagle0.net/mac/appcast.xml | grep "sparkle:deltas"
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **All deltas are EdDSA signed:** Same signature verification as full DMG
|
||||
2. **BinaryDelta is Sparkle's official tool:** Well-audited, production-ready
|
||||
3. **App bundles in S3 are public:** Same as DMGs, no additional exposure
|
||||
4. **Cleanup removes old artifacts:** No indefinite storage of old versions
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
1. **Parallel delta generation:** Generate multiple deltas concurrently
|
||||
2. **Delta size threshold:** Skip uploading deltas larger than X% of full DMG
|
||||
3. **Delta metrics:** Track delta download rates vs full DMG
|
||||
4. **Configurable delta count:** Allow adjusting how many versions to keep
|
||||
Vendored
-48
@@ -1,48 +0,0 @@
|
||||
# LLVM MinGW toolchain for Windows cross-compilation
|
||||
# Provides x86_64-w64-mingw32 target compiler and libraries
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
filegroup(
|
||||
name = "all_files",
|
||||
srcs = glob(["**/*"]),
|
||||
)
|
||||
|
||||
# Compiler binaries
|
||||
filegroup(
|
||||
name = "compiler_files",
|
||||
srcs = glob([
|
||||
"bin/x86_64-w64-mingw32-*",
|
||||
"bin/clang*",
|
||||
"bin/llvm-*",
|
||||
"bin/lld*",
|
||||
]),
|
||||
)
|
||||
|
||||
# Windows x86_64 sysroot (headers and libraries)
|
||||
filegroup(
|
||||
name = "windows_x86_64_sysroot",
|
||||
srcs = glob([
|
||||
"x86_64-w64-mingw32/**/*",
|
||||
"generic-w64-mingw32/include/**/*",
|
||||
]),
|
||||
)
|
||||
|
||||
# All library files needed for linking
|
||||
filegroup(
|
||||
name = "linker_files",
|
||||
srcs = glob([
|
||||
"bin/x86_64-w64-mingw32-*",
|
||||
"bin/lld*",
|
||||
"bin/ld.lld*",
|
||||
"lib/**/*",
|
||||
"x86_64-w64-mingw32/lib/**/*",
|
||||
]),
|
||||
)
|
||||
|
||||
# The main C compiler wrapper script path for CGO
|
||||
# CGO needs CC to point to the cross-compiler
|
||||
exports_files([
|
||||
"bin/x86_64-w64-mingw32-clang",
|
||||
"bin/x86_64-w64-mingw32-clang++",
|
||||
])
|
||||
@@ -11,7 +11,6 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.72.2
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6
|
||||
google.golang.org/grpc v1.68.0
|
||||
google.golang.org/protobuf v1.36.3
|
||||
)
|
||||
|
||||
@@ -41,8 +41,6 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6 h1:VQpB2SpK88C6B5lPHTuSZKb2Qee1QWwiFlC5CKY4AW0=
|
||||
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6/go.mod h1:yE65LFCeWf4kyWD5re+h4XNvOHJEXOCOuJZ4v8l5sgk=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -112,20 +112,6 @@ http {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
# Apple OAuth callback (Apple uses POST with form_post response mode)
|
||||
location /oauth/apple/callback {
|
||||
proxy_pass http://auth:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
# Steam OAuth callback (Steam uses OpenID 2.0)
|
||||
location /oauth/steam/callback {
|
||||
proxy_pass http://auth:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
# Invitation landing page (proxied to Go auth service)
|
||||
location /invite/ {
|
||||
proxy_pass http://auth:8080;
|
||||
|
||||
@@ -23,8 +23,5 @@ mkdir -p "$OUTPUT_DIR"
|
||||
rm -rf "$OUTPUT_DIR/SparklePlugin.bundle"
|
||||
unzip -o "$ZIP_PATH" -d "$OUTPUT_DIR/"
|
||||
|
||||
# Convert Info.plist from binary to XML format (Unity requires XML)
|
||||
/usr/bin/plutil -convert xml1 "$OUTPUT_DIR/SparklePlugin.bundle/Contents/Info.plist"
|
||||
|
||||
echo "=== SparklePlugin built successfully ==="
|
||||
ls -la "$OUTPUT_DIR/SparklePlugin.bundle/"
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Check BUILD.bazel dependency constraints
|
||||
# This script enforces architectural boundaries in the codebase.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/check_build_deps.sh # Check all rules
|
||||
# ./scripts/check_build_deps.sh --ci # CI mode (fail on any violation)
|
||||
# ./scripts/check_build_deps.sh --count # Just count current violations (for tracking progress)
|
||||
# ./scripts/check_build_deps.sh --strict # Same as --ci (strict enforcement)
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
MODE="${1:-check}"
|
||||
EXIT_CODE=0
|
||||
|
||||
# Rule 1: src/main should not depend on src/test
|
||||
check_main_depends_on_test() {
|
||||
echo -e "${YELLOW}Checking: src/main should not depend on src/test...${NC}"
|
||||
|
||||
violations=$(bazel query 'deps(//src/main/...) intersect //src/test/...' 2>/dev/null || true)
|
||||
|
||||
if [ -n "$violations" ]; then
|
||||
echo -e "${RED}VIOLATION: src/main depends on src/test:${NC}"
|
||||
echo "$violations"
|
||||
return 1
|
||||
else
|
||||
echo -e "${GREEN}✓ No violations${NC}"
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# Rule 2: library/ should not depend on Scala proto types
|
||||
# C++/Go proto deps are allowed (they're build-time deps for map generation tools)
|
||||
check_library_depends_on_scala_proto() {
|
||||
echo -e "${YELLOW}Checking: library/ should not depend on Scala proto types...${NC}"
|
||||
|
||||
violations=$(bazel query 'deps(//src/main/scala/net/eagle0/eagle/library/...) intersect //src/main/protobuf/...' 2>/dev/null | grep "_scala_proto" || true)
|
||||
if [ -z "$violations" ]; then
|
||||
count=0
|
||||
else
|
||||
count=$(echo "$violations" | grep -c "^//" || true)
|
||||
fi
|
||||
|
||||
if [ "$count" -gt 0 ]; then
|
||||
echo -e "${RED}VIOLATION: Found $count Scala proto dependencies in library/:${NC}"
|
||||
echo "$violations"
|
||||
return 1
|
||||
else
|
||||
echo -e "${GREEN}✓ No Scala proto dependencies in library/${NC}"
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# Rule 3: library/ should not depend on proto_converters
|
||||
# Proto conversions should happen at service boundaries, not in library code
|
||||
check_library_depends_on_proto_converters() {
|
||||
echo -e "${YELLOW}Checking: library/ should not depend on proto_converters...${NC}"
|
||||
|
||||
violations=$(bazel query 'deps(//src/main/scala/net/eagle0/eagle/library/...) intersect //src/main/scala/net/eagle0/eagle/model/proto_converters/...' 2>/dev/null | grep "^//" || true)
|
||||
|
||||
if [ -n "$violations" ]; then
|
||||
count=$(echo "$violations" | wc -l | tr -d ' ')
|
||||
echo -e "${RED}VIOLATION: library/ depends on $count proto_converters targets:${NC}"
|
||||
echo "$violations"
|
||||
echo ""
|
||||
echo "Proto conversions should happen at service boundaries (ShardokInterfaceGrpcClient,"
|
||||
echo "EagleServiceImpl, etc.), not in library code."
|
||||
return 1
|
||||
else
|
||||
echo -e "${GREEN}✓ No proto_converters dependencies in library/${NC}"
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# Count proto deps for informational purposes
|
||||
count_proto_deps() {
|
||||
echo -e "${YELLOW}=== Proto dependency counts ===${NC}"
|
||||
|
||||
scala_proto_results=$(bazel query 'deps(//src/main/scala/net/eagle0/eagle/library/...) intersect //src/main/protobuf/...' 2>/dev/null | grep "_scala_proto" || true)
|
||||
if [ -z "$scala_proto_results" ]; then
|
||||
scala_proto_count=0
|
||||
else
|
||||
scala_proto_count=$(echo "$scala_proto_results" | wc -l | tr -d ' ')
|
||||
fi
|
||||
echo "library/ Scala proto deps: $scala_proto_count"
|
||||
|
||||
# C++/Go proto deps are expected (map generation tools)
|
||||
all_proto_count=$(bazel query 'deps(//src/main/scala/net/eagle0/eagle/library/...) intersect //src/main/protobuf/...' 2>/dev/null | wc -l | tr -d ' ')
|
||||
echo "library/ all proto deps (includes C++/Go build tools): $all_proto_count"
|
||||
}
|
||||
|
||||
echo "=== BUILD.bazel Dependency Check ==="
|
||||
echo ""
|
||||
|
||||
case "$MODE" in
|
||||
--count)
|
||||
count_proto_deps
|
||||
;;
|
||||
--ci|--strict)
|
||||
check_main_depends_on_test || EXIT_CODE=1
|
||||
check_library_depends_on_scala_proto || EXIT_CODE=1
|
||||
check_library_depends_on_proto_converters || EXIT_CODE=1
|
||||
;;
|
||||
*)
|
||||
check_main_depends_on_test || EXIT_CODE=1
|
||||
check_library_depends_on_scala_proto || EXIT_CODE=1
|
||||
check_library_depends_on_proto_converters || EXIT_CODE=1
|
||||
echo ""
|
||||
count_proto_deps
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
if [ $EXIT_CODE -eq 0 ]; then
|
||||
echo -e "${GREEN}=== All checks passed ===${NC}"
|
||||
else
|
||||
echo -e "${RED}=== Some checks failed ===${NC}"
|
||||
fi
|
||||
|
||||
exit $EXIT_CODE
|
||||
@@ -39,51 +39,32 @@ find "$APP_PATH" -name "*.bundle" -print0 | while IFS= read -r -d '' item; do
|
||||
--sign "$SIGNING_IDENTITY" "$item"
|
||||
done
|
||||
|
||||
# Sign XPC services (but skip ones inside Sparkle.framework - they're already signed)
|
||||
# Sign XPC services (inside Sparkle framework)
|
||||
find "$APP_PATH" -name "*.xpc" -print0 | while IFS= read -r -d '' item; do
|
||||
if [[ "$item" == *"Sparkle.framework"* ]]; then
|
||||
echo "Skipping Sparkle XPC service (pre-signed): $item"
|
||||
continue
|
||||
fi
|
||||
echo "Signing XPC service: $item"
|
||||
codesign --force --verify --verbose --timestamp --options runtime \
|
||||
--sign "$SIGNING_IDENTITY" "$item"
|
||||
done
|
||||
|
||||
# Sign nested apps (but skip ones inside Sparkle.framework - they're already signed)
|
||||
# Sign nested apps (like Sparkle's Updater.app)
|
||||
find "$APP_PATH" -path "*/Frameworks/*.app" -print0 | while IFS= read -r -d '' item; do
|
||||
if [[ "$item" == *"Sparkle.framework"* ]]; then
|
||||
echo "Skipping Sparkle nested app (pre-signed): $item"
|
||||
continue
|
||||
fi
|
||||
echo "Signing nested app: $item"
|
||||
codesign --force --verify --verbose --timestamp --options runtime \
|
||||
--sign "$SIGNING_IDENTITY" "$item"
|
||||
done
|
||||
|
||||
# Sign standalone executables inside frameworks (but skip Sparkle.framework internals)
|
||||
# Sign standalone executables inside frameworks (like Autoupdate)
|
||||
find "$APP_PATH" -path "*/Frameworks/*/Versions/*/Autoupdate" -type f -print0 | while IFS= read -r -d '' item; do
|
||||
if [[ "$item" == *"Sparkle.framework"* ]]; then
|
||||
echo "Skipping Sparkle executable (pre-signed): $item"
|
||||
continue
|
||||
fi
|
||||
echo "Signing executable: $item"
|
||||
codesign --force --verify --verbose --timestamp --options runtime \
|
||||
--sign "$SIGNING_IDENTITY" "$item"
|
||||
done
|
||||
|
||||
# Sign all frameworks (after their contents are signed)
|
||||
# Use --deep for Sparkle.framework to handle its XPC services
|
||||
find "$APP_PATH" -name "*.framework" -print0 | while IFS= read -r -d '' item; do
|
||||
if [[ "$item" == *"Sparkle.framework" ]]; then
|
||||
echo "Signing Sparkle framework with --deep: $item"
|
||||
codesign --deep --force --verify --verbose --timestamp --options runtime \
|
||||
--sign "$SIGNING_IDENTITY" "$item"
|
||||
else
|
||||
echo "Signing framework: $item"
|
||||
codesign --force --verify --verbose --timestamp --options runtime \
|
||||
--sign "$SIGNING_IDENTITY" "$item"
|
||||
fi
|
||||
echo "Signing framework: $item"
|
||||
codesign --force --verify --verbose --timestamp --options runtime \
|
||||
--sign "$SIGNING_IDENTITY" "$item"
|
||||
done
|
||||
|
||||
echo "=== Signing main app bundle ==="
|
||||
|
||||
@@ -35,7 +35,6 @@ WARMUP_SCRIPT="${SCRIPT_DIR}/warmup-eagle.sh"
|
||||
SAVES_DIR="${APP_DIR}/saves"
|
||||
FLUSH_MARKER="${SAVES_DIR}/.flush_complete"
|
||||
DEPLOYMENT_IN_PROGRESS="${SAVES_DIR}/.deployment_in_progress"
|
||||
ACTIVE_INSTANCE_FILE="${APP_DIR}/.active-instance"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
@@ -265,8 +264,7 @@ main() {
|
||||
fi
|
||||
|
||||
# Recreate nginx to pick up new config
|
||||
# Use --no-deps to prevent cascading to auth (which has secrets not available here)
|
||||
docker compose -f "${COMPOSE_FILE}" up -d --force-recreate --no-deps nginx
|
||||
docker compose -f "${COMPOSE_FILE}" up -d --force-recreate nginx
|
||||
|
||||
# Verify nginx picked up the correct config
|
||||
local nginx_backend
|
||||
@@ -299,10 +297,6 @@ main() {
|
||||
create_flush_marker "${deploy_id}" "eagle-${staging}"
|
||||
log_info "[DEPLOY:${deploy_id}] Flush marker created - server will auto-invalidate stale cache"
|
||||
|
||||
# Write active instance file for eagle-exec helper
|
||||
echo "eagle-${staging}" > "${ACTIVE_INSTANCE_FILE}"
|
||||
log_info "[DEPLOY:${deploy_id}] Active instance file updated: eagle-${staging}"
|
||||
|
||||
# Update .env for admin service
|
||||
local env_file="${APP_DIR}/.env"
|
||||
if [ "$staging" = "green" ]; then
|
||||
@@ -318,9 +312,8 @@ main() {
|
||||
fi
|
||||
|
||||
# Restart admin to pick up new .env
|
||||
# Use --no-deps to prevent cascading to auth (which has secrets not available here)
|
||||
log_info "Restarting admin service..."
|
||||
docker compose -f "${COMPOSE_FILE}" up -d --force-recreate --no-deps admin
|
||||
docker compose -f "${COMPOSE_FILE}" up -d --force-recreate admin
|
||||
|
||||
# Clean up old instance
|
||||
log_info "Cleaning up old eagle-${active}..."
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Helper to run docker exec against the active Eagle instance.
|
||||
# Reads the active instance from /opt/eagle0/.active-instance (set by deploy-blue-green.sh).
|
||||
#
|
||||
# Usage:
|
||||
# eagle-exec printenv GEMINI_API_KEY
|
||||
# eagle-exec jcmd 1 VM.flags
|
||||
# eagle-exec sh # Get a shell
|
||||
#
|
||||
# To create an alias, add to ~/.bashrc:
|
||||
# alias eagle-exec='/opt/eagle0/scripts/eagle-exec.sh'
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
APP_DIR="${APP_DIR:-/opt/eagle0}"
|
||||
ACTIVE_FILE="${APP_DIR}/.active-instance"
|
||||
|
||||
# Read active instance from file, with fallback
|
||||
if [ -f "$ACTIVE_FILE" ]; then
|
||||
ACTIVE=$(cat "$ACTIVE_FILE")
|
||||
else
|
||||
# Fallback: check which container is actually running
|
||||
if docker inspect --format='{{.State.Running}}' eagle-blue 2>/dev/null | grep -q true; then
|
||||
ACTIVE="eagle-blue"
|
||||
elif docker inspect --format='{{.State.Running}}' eagle-green 2>/dev/null | grep -q true; then
|
||||
ACTIVE="eagle-green"
|
||||
else
|
||||
ACTIVE=""
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$ACTIVE" ]; then
|
||||
echo "Error: No active Eagle instance found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Active instance: $ACTIVE"
|
||||
echo "Usage: $0 <command> [args...]"
|
||||
echo "Example: $0 printenv GEMINI_API_KEY"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exec docker exec "$ACTIVE" "$@"
|
||||
@@ -1,44 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Helper to tail logs from the active Eagle instance.
|
||||
# Reads the active instance from /opt/eagle0/.active-instance (set by deploy-blue-green.sh).
|
||||
#
|
||||
# Usage:
|
||||
# eagle-logs # Tail logs (follow mode)
|
||||
# eagle-logs -n 100 # Show last 100 lines and follow
|
||||
# eagle-logs --no-follow -n 50 # Show last 50 lines without following
|
||||
#
|
||||
# To create an alias, add to ~/.bashrc:
|
||||
# alias eagle-logs='/opt/eagle0/scripts/eagle-logs.sh'
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
APP_DIR="${APP_DIR:-/opt/eagle0}"
|
||||
ACTIVE_FILE="${APP_DIR}/.active-instance"
|
||||
|
||||
# Read active instance from file, with fallback
|
||||
if [ -f "$ACTIVE_FILE" ]; then
|
||||
ACTIVE=$(cat "$ACTIVE_FILE")
|
||||
else
|
||||
# Fallback: check which container is actually running
|
||||
if docker inspect --format='{{.State.Running}}' eagle-blue 2>/dev/null | grep -q true; then
|
||||
ACTIVE="eagle-blue"
|
||||
elif docker inspect --format='{{.State.Running}}' eagle-green 2>/dev/null | grep -q true; then
|
||||
ACTIVE="eagle-green"
|
||||
else
|
||||
ACTIVE=""
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$ACTIVE" ]; then
|
||||
echo "Error: No active Eagle instance found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Default to follow mode if no args provided
|
||||
if [ $# -eq 0 ]; then
|
||||
exec docker logs -f "$ACTIVE"
|
||||
else
|
||||
exec docker logs "$@" "$ACTIVE"
|
||||
fi
|
||||
+20
-47
@@ -27,59 +27,33 @@ if [ -z "${SPARKLE_EDDSA_PUBLIC_KEY:-}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Always use a fresh Sparkle download to avoid cache corruption issues
|
||||
# Download Sparkle if not cached
|
||||
SPARKLE_DIR="$SPARKLE_CACHE_DIR/Sparkle-$SPARKLE_VERSION"
|
||||
echo "=== Clearing Sparkle cache and downloading fresh copy ==="
|
||||
rm -rf "$SPARKLE_DIR"
|
||||
mkdir -p "$SPARKLE_DIR"
|
||||
|
||||
SPARKLE_URL="https://github.com/sparkle-project/Sparkle/releases/download/${SPARKLE_VERSION}/Sparkle-${SPARKLE_VERSION}.tar.xz"
|
||||
echo "Downloading from: $SPARKLE_URL"
|
||||
curl -L "$SPARKLE_URL" -o /tmp/sparkle.tar.xz
|
||||
tar -xJf /tmp/sparkle.tar.xz -C "$SPARKLE_DIR"
|
||||
rm /tmp/sparkle.tar.xz
|
||||
|
||||
# Show what was extracted
|
||||
echo "=== Extracted contents ==="
|
||||
ls -la "$SPARKLE_DIR/"
|
||||
|
||||
# The tarball extracts files directly, not into a subdirectory
|
||||
# Verify the framework has proper symlink structure
|
||||
echo "=== Verifying Sparkle.framework structure ==="
|
||||
ls -la "$SPARKLE_DIR/Sparkle.framework/"
|
||||
|
||||
if [ ! -L "$SPARKLE_DIR/Sparkle.framework/Sparkle" ]; then
|
||||
echo "ERROR: Sparkle.framework/Sparkle is not a symlink"
|
||||
file "$SPARKLE_DIR/Sparkle.framework/Sparkle"
|
||||
exit 1
|
||||
if [ ! -d "$SPARKLE_DIR/Sparkle.framework" ]; then
|
||||
echo "=== Downloading Sparkle $SPARKLE_VERSION ==="
|
||||
mkdir -p "$SPARKLE_CACHE_DIR"
|
||||
SPARKLE_URL="https://github.com/sparkle-project/Sparkle/releases/download/${SPARKLE_VERSION}/Sparkle-${SPARKLE_VERSION}.tar.xz"
|
||||
curl -L "$SPARKLE_URL" | tar -xJ -C "$SPARKLE_CACHE_DIR"
|
||||
mv "$SPARKLE_CACHE_DIR/Sparkle-$SPARKLE_VERSION" "$SPARKLE_DIR" 2>/dev/null || true
|
||||
# If the extracted directory doesn't match version pattern, it may just be "Sparkle"
|
||||
if [ ! -d "$SPARKLE_DIR" ]; then
|
||||
mkdir -p "$SPARKLE_DIR"
|
||||
mv "$SPARKLE_CACHE_DIR/Sparkle.framework" "$SPARKLE_DIR/" 2>/dev/null || true
|
||||
mv "$SPARKLE_CACHE_DIR/bin" "$SPARKLE_DIR/" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -L "$SPARKLE_DIR/Sparkle.framework/Versions/Current" ]; then
|
||||
echo "ERROR: Sparkle.framework/Versions/Current is not a symlink"
|
||||
ls -la "$SPARKLE_DIR/Sparkle.framework/Versions/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Sparkle framework structure verified OK"
|
||||
|
||||
echo "=== Injecting Sparkle framework ==="
|
||||
FRAMEWORKS_DIR="$APP_PATH/Contents/Frameworks"
|
||||
mkdir -p "$FRAMEWORKS_DIR"
|
||||
|
||||
# Remove any existing Sparkle.framework in the app
|
||||
rm -rf "$FRAMEWORKS_DIR/Sparkle.framework"
|
||||
# Copy Sparkle framework
|
||||
cp -R "$SPARKLE_DIR/Sparkle.framework" "$FRAMEWORKS_DIR/"
|
||||
|
||||
# Copy Sparkle framework (use ditto to preserve symlinks and bundle structure)
|
||||
ditto "$SPARKLE_DIR/Sparkle.framework" "$FRAMEWORKS_DIR/Sparkle.framework"
|
||||
|
||||
# Verify the copied framework still has proper structure
|
||||
echo "=== Verifying copied Sparkle.framework structure ==="
|
||||
ls -la "$FRAMEWORKS_DIR/Sparkle.framework/"
|
||||
if [ ! -L "$FRAMEWORKS_DIR/Sparkle.framework/Sparkle" ]; then
|
||||
echo "ERROR: Copied framework lost symlink structure"
|
||||
exit 1
|
||||
# Also copy the XPC services if present
|
||||
if [ -d "$SPARKLE_DIR/Sparkle.framework/Versions/B/XPCServices" ]; then
|
||||
echo "Sparkle XPC services present"
|
||||
fi
|
||||
echo "Copied framework structure OK"
|
||||
|
||||
echo "=== Updating Info.plist ==="
|
||||
PLIST_PATH="$APP_PATH/Contents/Info.plist"
|
||||
@@ -95,9 +69,8 @@ PLIST_PATH="$APP_PATH/Contents/Info.plist"
|
||||
/usr/libexec/PlistBuddy -c "Add :SUEnableAutomaticChecks bool true" "$PLIST_PATH"
|
||||
|
||||
# Set bundle version from git for Sparkle version comparison
|
||||
# Use commit count for automatic incrementing versions (e.g., 1.0.9548)
|
||||
VERSION=$(git describe --tags --always 2>/dev/null || echo "1.0.0")
|
||||
BUILD_NUMBER=$(git rev-list --count HEAD 2>/dev/null || echo "1")
|
||||
VERSION="1.0.${BUILD_NUMBER}"
|
||||
|
||||
echo "Setting version: $VERSION (build $BUILD_NUMBER)"
|
||||
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION" "$PLIST_PATH" 2>/dev/null || \
|
||||
@@ -110,7 +83,7 @@ echo "=== Adding URL scheme for invitation codes ==="
|
||||
/usr/libexec/PlistBuddy -c "Delete :CFBundleURLTypes" "$PLIST_PATH" 2>/dev/null || true
|
||||
/usr/libexec/PlistBuddy -c "Add :CFBundleURLTypes array" "$PLIST_PATH"
|
||||
/usr/libexec/PlistBuddy -c "Add :CFBundleURLTypes:0 dict" "$PLIST_PATH"
|
||||
/usr/libexec/PlistBuddy -c "Add :CFBundleURLTypes:0:CFBundleURLName string 'net.eagle0.eagle0'" "$PLIST_PATH"
|
||||
/usr/libexec/PlistBuddy -c "Add :CFBundleURLTypes:0:CFBundleURLName string 'com.Shardok-Games.eagle0'" "$PLIST_PATH"
|
||||
/usr/libexec/PlistBuddy -c "Add :CFBundleURLTypes:0:CFBundleURLSchemes array" "$PLIST_PATH"
|
||||
/usr/libexec/PlistBuddy -c "Add :CFBundleURLTypes:0:CFBundleURLSchemes:0 string 'eagle0'" "$PLIST_PATH"
|
||||
|
||||
|
||||
@@ -50,34 +50,22 @@ if [ "$STATUS" != "Accepted" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== Verifying code signature before stapling ==="
|
||||
if ! codesign --verify --deep --strict "$APP_PATH" 2>&1; then
|
||||
echo "ERROR: Code signature verification failed - app may have been damaged during transfer"
|
||||
echo "Attempting to show signature details:"
|
||||
codesign -dvvv "$APP_PATH" 2>&1 || true
|
||||
exit 1
|
||||
fi
|
||||
echo "Code signature verified successfully"
|
||||
|
||||
echo "=== Stapling notarization ticket to app ==="
|
||||
# Retry stapling - Apple's CloudKit can take several minutes to propagate the ticket
|
||||
MAX_STAPLE_ATTEMPTS=10
|
||||
STAPLE_WAIT_SECONDS=30
|
||||
# Retry stapling - Apple's CloudKit can have a brief delay after notarization completes
|
||||
MAX_STAPLE_ATTEMPTS=5
|
||||
STAPLE_ATTEMPT=1
|
||||
while [ $STAPLE_ATTEMPT -le $MAX_STAPLE_ATTEMPTS ]; do
|
||||
echo "Stapling attempt $STAPLE_ATTEMPT/$MAX_STAPLE_ATTEMPTS..."
|
||||
if STAPLE_OUTPUT=$(xcrun stapler staple "$APP_PATH" 2>&1); then
|
||||
echo "$STAPLE_OUTPUT"
|
||||
if xcrun stapler staple "$APP_PATH"; then
|
||||
echo "Stapling successful"
|
||||
break
|
||||
fi
|
||||
echo "Stapler output: $STAPLE_OUTPUT"
|
||||
if [ $STAPLE_ATTEMPT -eq $MAX_STAPLE_ATTEMPTS ]; then
|
||||
echo "ERROR: Stapling failed after $MAX_STAPLE_ATTEMPTS attempts (total wait: $((MAX_STAPLE_ATTEMPTS * STAPLE_WAIT_SECONDS)) seconds)"
|
||||
echo "ERROR: Stapling failed after $MAX_STAPLE_ATTEMPTS attempts"
|
||||
exit 1
|
||||
fi
|
||||
echo "Stapling failed, waiting $STAPLE_WAIT_SECONDS seconds before retry..."
|
||||
sleep $STAPLE_WAIT_SECONDS
|
||||
echo "Stapling failed, waiting 10 seconds before retry..."
|
||||
sleep 10
|
||||
STAPLE_ATTEMPT=$((STAPLE_ATTEMPT + 1))
|
||||
done
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
<Compile Include="Assets/Modern UI Pack/Scripts/Demo/LaunchURL.cs" />
|
||||
<Compile Include="Assets/Shardok/HexCoordinates.cs" />
|
||||
<Compile Include="Assets/Eagle/Table Rows/OrganizeTroopsTableRow.cs" />
|
||||
<Compile Include="Assets/Bluetooth/PickerRowController.cs" />
|
||||
<Compile Include="Assets/TouchHandler.cs" />
|
||||
<Compile Include="Assets/Eagle/CommandSelectors/TravelCommandSelector.cs" />
|
||||
<Compile Include="Assets/Eagle/ConnectionStatusUI.cs" />
|
||||
@@ -97,6 +98,7 @@
|
||||
<Compile Include="Assets/HoveringTooltipTextProvider.cs" />
|
||||
<Compile Include="Assets/Eagle/CommandSelectors/util/KeyModifiedAmount.cs" />
|
||||
<Compile Include="Assets/HoveringTooltip.cs" />
|
||||
<Compile Include="Assets/Bluetooth/DiceInterface.cs" />
|
||||
<Compile Include="Assets/ButtonColors.cs" />
|
||||
<Compile Include="Assets/Eagle/MapController.cs" />
|
||||
<Compile Include="Assets/common/DisclosureTriangle.cs" />
|
||||
@@ -146,12 +148,13 @@
|
||||
<Compile Include="Assets/Modern UI Pack/Scripts/Slider/RadialSlider.cs" />
|
||||
<Compile Include="Assets/Eagle/Notifications/WeatherForcedTurnBackNotificationGenerator.cs" />
|
||||
<Compile Include="Assets/Modern UI Pack/Scripts/Button/ButtonManager.cs" />
|
||||
<Compile Include="Assets/common/GUIUtils/TouchAwareTooltip.cs" />
|
||||
<Compile Include="Assets/Eagle/GeneratedTextUpdater.cs" />
|
||||
<Compile Include="Assets/Eagle/Notifications/ARNNotifications/OutlawApprehendedDetailsNotificationGenerator.cs" />
|
||||
<Compile Include="Assets/Shardok/HexMetrics.cs" />
|
||||
<Compile Include="Assets/Bluetooth/OneDiceRollController.cs" />
|
||||
<Compile Include="Assets/Modern UI Pack/Scripts/Switch/SwitchManager.cs" />
|
||||
<Compile Include="Assets/Eagle/Notifications/ARNNotifications/ProvinceHeldDetailsNotificationGenerator.cs" />
|
||||
<Compile Include="Assets/Bluetooth/DieInfo.cs" />
|
||||
<Compile Include="Assets/common/ResourceFetcher.cs" />
|
||||
<Compile Include="Assets/Tutorial/Content/TutorialStep.cs" />
|
||||
<Compile Include="Assets/Auth/AuthClient.cs" />
|
||||
@@ -159,11 +162,12 @@
|
||||
<Compile Include="Assets/Eagle/Notifications/RiotSuppressedNotificationGenerator.cs" />
|
||||
<Compile Include="Assets/Modern UI Pack/Scripts/Modal Window/ModalWindowManagerEditor.cs" />
|
||||
<Compile Include="Assets/common/GUIUtils/AutoScrollingText.cs" />
|
||||
<Compile Include="Assets/Bluetooth/NativeDiceInterfaceImports.cs" />
|
||||
<Compile Include="Assets/Eagle/Notifications/ProvinceConqueredNotificationGenerator.cs" />
|
||||
<Compile Include="Assets/Eagle/Table Rows/ExtraTroopsRowController.cs" />
|
||||
<Compile Include="Assets/Auth/JwtAuthInterceptor.cs" />
|
||||
<Compile Include="Assets/Tutorial/Content/TutorialContentDefinitions.cs" />
|
||||
<Compile Include="Assets/Eagle/CommandPanelController.cs" />
|
||||
<Compile Include="Assets/Tutorial/Content/TutorialContentDefinitions.cs" />
|
||||
<Compile Include="Assets/Eagle/Notifications/ARNNotifications/ShatteredArmyDetailsNotificationGenerator.cs" />
|
||||
<Compile Include="Assets/Shardok/TurnHistoryPanelController.cs" />
|
||||
<Compile Include="Assets/Eagle/Notifications/ARNNotifications/HeroDepartureDetailsNotificationGenerator.cs" />
|
||||
@@ -175,9 +179,9 @@
|
||||
<Compile Include="Assets/Shardok/BattalionTypeManager.cs" />
|
||||
<Compile Include="Assets/Eagle/CommandSelectors/DefendCommandSelector.cs" />
|
||||
<Compile Include="Assets/Eagle/HeroesAndBattalionsPanelController.cs" />
|
||||
<Compile Include="Assets/common/HexMapStorage.cs" />
|
||||
<Compile Include="Assets/Modern UI Pack/Scripts/Button/ButtonManagerBasic.cs" />
|
||||
<Compile Include="Assets/Eagle/Notifications/ARNNotifications/BreakAllianceAmbassadorImprisonedNotificationGenerator.cs" />
|
||||
<Compile Include="Assets/common/HexMapStorage.cs" />
|
||||
<Compile Include="Assets/Eagle/CommandSelectors/PleaseRecruitMeCommandSelector.cs" />
|
||||
<Compile Include="Assets/Eagle/Notifications/RiotAvertedNotificationGenerator.cs" />
|
||||
<Compile Include="Assets/Modern UI Pack/Scripts/Notification/NotificationStacking.cs" />
|
||||
@@ -208,6 +212,7 @@
|
||||
<Compile Include="Assets/Eagle/CommandSelectors/ExileVassalCommandSelector.cs" />
|
||||
<Compile Include="Assets/Tutorial/TutorialState.cs" />
|
||||
<Compile Include="Assets/Modern UI Pack/Scripts/Button/ButtonManagerBasicWithIconEditor.cs" />
|
||||
<Compile Include="Assets/Bluetooth/DiceVectors.cs" />
|
||||
<Compile Include="Assets/Modern UI Pack/Scripts/UI Manager/UIManagerProgressBarLoop.cs" />
|
||||
<Compile Include="Assets/Eagle/MovingArmiesTableController.cs" />
|
||||
<Compile Include="Assets/ConnectionHandler/ConnectionHandler.cs" />
|
||||
@@ -228,11 +233,9 @@
|
||||
<Compile Include="Assets/Eagle/CommandSelectors/DiplomacyCommandSelector.cs" />
|
||||
<Compile Include="Assets/Tutorial/Triggers/TutorialTriggerRegistry.cs" />
|
||||
<Compile Include="Assets/Eagle/CommandSelectors/ResolveBreakAllianceCommandSelector.cs" />
|
||||
<Compile Include="Assets/common/WindowFocusManager.cs" />
|
||||
<Compile Include="Assets/Eagle/CommandSelectors/ResolveAllianceCommandSelector.cs" />
|
||||
<Compile Include="Assets/Eagle/Notifications/ARNNotifications/WithdrewForTruceDetailsNotificationGenerator.cs" />
|
||||
<Compile Include="Assets/Eagle/CommandSelectors/MarchCommandSelector.cs" />
|
||||
<Compile Include="Assets/Tutorial/TutorialTargetRegistry.cs" />
|
||||
<Compile Include="Assets/Eagle/CommandSelectors/TrainCommandSelector.cs" />
|
||||
<Compile Include="Assets/Eagle/CustomFileLogger.cs" />
|
||||
<Compile Include="Assets/Eagle/Notifications/ARNNotifications/InvitationAcceptedDetailsNotificationGenerator.cs" />
|
||||
@@ -266,6 +269,7 @@
|
||||
<Compile Include="Assets/Shardok/AnimationTestController.cs" />
|
||||
<Compile Include="Assets/Eagle/CommandSelectors/TradeCommandSelector.cs" />
|
||||
<Compile Include="Assets/Eagle/Table Rows/DominionTableRowController.cs" />
|
||||
<Compile Include="Assets/Bluetooth/RollPanelController.cs" />
|
||||
<Compile Include="Assets/Shardok/ChargeAnimator.cs" />
|
||||
<Compile Include="Assets/Eagle/ClientTextProvider.cs" />
|
||||
<Compile Include="Assets/Modern UI Pack/Scripts/UI Manager/UIManager.cs" />
|
||||
@@ -295,13 +299,11 @@
|
||||
<Compile Include="Assets/Modern UI Pack/Scripts/Button/ButtonManagerEditor.cs" />
|
||||
<Compile Include="Assets/UI/Scripts/SceneMigrationHelper.cs" />
|
||||
<Compile Include="Assets/Eagle/Table Rows/UnitSelectorHeroRowController.cs" />
|
||||
<Compile Include="Assets/Shardok/BridgeRotationCalculator.cs" />
|
||||
<Compile Include="Assets/ConnectionHandler/CustomBattleHandler.cs" />
|
||||
<Compile Include="Assets/Eagle/Notifications/ARNNotifications/RansomPaidDetailsNotificationGenerator.cs" />
|
||||
<Compile Include="Assets/Shardok/ControlAnimator.cs" />
|
||||
<Compile Include="Assets/Modern UI Pack/Scripts/Horizontal Selector/HorizontalSelector.cs" />
|
||||
<Compile Include="Assets/Eagle/Table Rows/OrganizeTroopsResultRow.cs" />
|
||||
<Compile Include="Assets/Eagle/SparkleUpdater.cs" />
|
||||
<Compile Include="Assets/Modern UI Pack/Scripts/Slider/SliderManagerEditor.cs" />
|
||||
<Compile Include="Assets/Eagle/Notifications/ARNNotifications/SwearBrotherhoodDetailsNotificationGenerator.cs" />
|
||||
<Compile Include="Assets/Eagle/PersistentClientConnection.cs" />
|
||||
@@ -320,10 +322,10 @@
|
||||
<Compile Include="Assets/Shardok/ShardokGameModel.cs" />
|
||||
<Compile Include="Assets/common/GUIUtils/GeneralClickDetector.cs" />
|
||||
<Compile Include="Assets/Modern UI Pack/Scripts/UI Manager/UIManagerContextMenu.cs" />
|
||||
<Compile Include="Assets/Eagle/SparkleInitializer.cs" />
|
||||
<Compile Include="Assets/Eagle/Notifications/ARNNotifications/TruceAmbassadorImprisonedDetailsNotificationGenerator.cs" />
|
||||
<Compile Include="Assets/Modern UI Pack/Scripts/Button/ButtonManagerBasicWithIcon.cs" />
|
||||
<Compile Include="Assets/Tutorial/UI/TutorialHintIndicator.cs" />
|
||||
<Compile Include="Assets/Bluetooth/UnityDieColors.cs" />
|
||||
<Compile Include="Assets/Eagle/CommandSelectors/OrganizeTroopsCommandSelector.cs" />
|
||||
<Compile Include="Assets/Shardok/ExtinguishAnimator.cs" />
|
||||
<Compile Include="Assets/Eagle/PanelPositions.cs" />
|
||||
@@ -336,6 +338,7 @@
|
||||
<Compile Include="Assets/EagleConnection.cs" />
|
||||
<Compile Include="Assets/GUI Pro Kit Fantasy RPG/Scripts/CtrPanel.cs" />
|
||||
<Compile Include="Assets/UI/Scripts/SceneLoadTester.cs" />
|
||||
<Compile Include="Assets/Bluetooth/RollFetcher.cs" />
|
||||
<Compile Include="Assets/Eagle/CommandSelectors/HandleRiotGiveCommandSelector.cs" />
|
||||
<Compile Include="Assets/Eagle/ProvinceUtils.cs" />
|
||||
<Compile Include="Assets/common/GUIUtils/ProvinceStatUtils.cs" />
|
||||
@@ -369,15 +372,16 @@
|
||||
<Compile Include="Assets/Eagle/Notifications/ARNNotifications/SuppressBeastsFailedDetailsNotificationGenerator.cs" />
|
||||
<Compile Include="Assets/Terrain Hexes/Example Scene/BasicHexArranger.cs" />
|
||||
<Compile Include="Assets/Eagle/CommandSelectors/ManagePrisonersCommandSelector.cs" />
|
||||
<Compile Include="Assets/ConnectionHandler/StoredAccountButton.cs" />
|
||||
<Compile Include="Assets/Tutorial/UI/TutorialUIManager.cs" />
|
||||
<Compile Include="Assets/Eagle/CommandSelectors/SendSuppliesCommandSelector.cs" />
|
||||
<Compile Include="Assets/Tutorial/UI/TutorialUIManager.cs" />
|
||||
<Compile Include="Assets/Modern UI Pack/Scripts/Window/WindowManager.cs" />
|
||||
<Compile Include="Assets/Auth/InvitationCodeManager.cs" />
|
||||
<Compile Include="Assets/Eagle/Notifications/ARNNotifications/CapturedHeroExiledDetailsNotificationGenerator.cs" />
|
||||
<Compile Include="Assets/Eagle/Notifications/ARNNotifications/SuppressBeastsSucceededNotificationGenerator.cs" />
|
||||
<Compile Include="Assets/Shardok/ReservesTableController.cs" />
|
||||
<Compile Include="Assets/Eagle/CommandSelectors/CommandSelector.cs" />
|
||||
<Compile Include="Assets/Eagle/CommandSelectors/RestCommandSelector.cs" />
|
||||
<Compile Include="Assets/Bluetooth/DiceConfigurationPanelController.cs" />
|
||||
<Compile Include="Assets/Modern UI Pack/Scripts/UI Manager/UIManagerDropdown.cs" />
|
||||
<Compile Include="Assets/Modern UI Pack/Scripts/UI Manager/UIManagerButton.cs" />
|
||||
<Compile Include="Assets/common/GUIUtils/GUIUtils.cs" />
|
||||
@@ -413,12 +417,10 @@
|
||||
<None Include="Assets/Packages/Microsoft.Extensions.Options.8.0.0/lib/netstandard2.1/Microsoft.Extensions.Options.xml" />
|
||||
<None Include="Assets/TextMesh Pro/Shaders/TMPro_Mobile.cginc" />
|
||||
<None Include="Assets/TextMesh Pro/Shaders/TMP_SDF SSD.shader" />
|
||||
<None Include="Assets/TextMesh Pro/Shaders/TMPro_Properties.cginc" />
|
||||
<None Include="Assets/Shardok/Painted Medieval Fantasy Items 1/medieval_items1.txt" />
|
||||
<None Include="Assets/TextMesh Pro/Resources/Shaders/TMP_SDF-Surface-Mobile.shader" />
|
||||
<None Include="Assets/Eagle/maskShader.shader" />
|
||||
<None Include="Assets/GUI Pro Kit Fantasy RPG/Extensions/UIParticle/hidden particle.shader" />
|
||||
<None Include="Assets/TextMesh Pro/Shaders/TMPro.cginc" />
|
||||
<None Include="Assets/Terrain Hexes/readme.txt" />
|
||||
<None Include="Assets/Modern UI Pack/Read Me.txt" />
|
||||
<None Include="Assets/TextMesh Pro/Shaders/SDFFunctions.hlsl" />
|
||||
@@ -448,7 +450,7 @@
|
||||
<None Include="Assets/Packages/System.ComponentModel.Annotations.5.0.0/useSharedDesignerContext.txt" />
|
||||
<None Include="Assets/Packages/System.Runtime.CompilerServices.Unsafe.6.0.0/useSharedDesignerContext.txt" />
|
||||
<None Include="Assets/TextMesh Pro/Resources/Shaders/TMP_SDF-Mobile Masking.shader" />
|
||||
<None Include="Assets/Music/Music Credits.txt" />
|
||||
<None Include="Assets/Resources/Music/Music Credits.txt" />
|
||||
<None Include="Assets/Packages/System.Diagnostics.DiagnosticSource.8.0.0/useSharedDesignerContext.txt" />
|
||||
<None Include="Assets/Packages/Microsoft.Extensions.Logging.8.0.0/useSharedDesignerContext.txt" />
|
||||
<None Include="Assets/Packages/Microsoft.Extensions.DependencyInjection.Abstractions.8.0.0/useSharedDesignerContext.txt" />
|
||||
@@ -869,6 +871,9 @@
|
||||
<Reference Include="Microsoft.Extensions.Logging.Abstractions">
|
||||
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/Packages/Microsoft.Extensions.Logging.Abstractions.8.0.0/lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ComponentModel.Annotations">
|
||||
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/Packages/System.ComponentModel.Annotations.5.0.0/lib/netstandard2.1/System.ComponentModel.Annotations.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="nunit.framework">
|
||||
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/PackageCache/com.unity.ext.nunit@d8c07649098d/net40/unity-custom/nunit.framework.dll</HintPath>
|
||||
</Reference>
|
||||
@@ -923,15 +928,6 @@
|
||||
<Reference Include="Unity.Analytics.StandardEvents">
|
||||
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/PackageCache/com.unity.analytics@c9d14a6bdec6/AnalyticsStandardEvents/Unity.Analytics.StandardEvents.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEditor.iOS.Extensions.Xcode">
|
||||
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/PlaybackEngines/iOSSupport/UnityEditor.iOS.Extensions.Xcode.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEditor.iOS.Extensions.Common">
|
||||
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/PlaybackEngines/iOSSupport/UnityEditor.iOS.Extensions.Common.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEditor.Apple.Extensions.Common">
|
||||
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/PlaybackEngines/iOSSupport/UnityEditor.Apple.Extensions.Common.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEditor.iOS.Extensions.Xcode">
|
||||
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/PlaybackEngines/MacStandaloneSupport/UnityEditor.iOS.Extensions.Xcode.dll</HintPath>
|
||||
</Reference>
|
||||
@@ -1316,9 +1312,6 @@
|
||||
<Reference Include="Unity.TextMeshPro">
|
||||
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/Unity.TextMeshPro.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Unity.ResourceManager">
|
||||
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/Unity.ResourceManager.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Unity.VisualStudio.Editor">
|
||||
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/Unity.VisualStudio.Editor.dll</HintPath>
|
||||
</Reference>
|
||||
@@ -1331,15 +1324,9 @@
|
||||
<Reference Include="Unity.AI.Navigation">
|
||||
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/Unity.AI.Navigation.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Unity.Profiling.Core">
|
||||
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/Unity.Profiling.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Unity.Timeline.Editor">
|
||||
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/Unity.Timeline.Editor.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Unity.ScriptableBuildPipeline">
|
||||
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/Unity.ScriptableBuildPipeline.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Cysharp.Net.Http.YetAnotherHttpHandler">
|
||||
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/Cysharp.Net.Http.YetAnotherHttpHandler.dll</HintPath>
|
||||
</Reference>
|
||||
@@ -1364,18 +1351,12 @@
|
||||
<Reference Include="Unity.AI.Navigation.Updater">
|
||||
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/Unity.AI.Navigation.Updater.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Unity.ScriptableBuildPipeline.Editor">
|
||||
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/Unity.ScriptableBuildPipeline.Editor.dll</HintPath>
|
||||
<Reference Include="Unity.Multiplayer.Center.Editor">
|
||||
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/Unity.Multiplayer.Center.Editor.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Unity.PlasticSCM.Editor">
|
||||
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/Unity.PlasticSCM.Editor.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Unity.Addressables">
|
||||
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/Unity.Addressables.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Unity.Multiplayer.Center.Editor">
|
||||
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/Unity.Multiplayer.Center.Editor.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Unity.Services.Core.Components">
|
||||
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/Unity.Services.Core.Components.dll</HintPath>
|
||||
</Reference>
|
||||
@@ -1391,9 +1372,6 @@
|
||||
<Reference Include="Unity.AI.Navigation.Editor.ConversionSystem">
|
||||
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/Unity.AI.Navigation.Editor.ConversionSystem.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Unity.Addressables.Editor">
|
||||
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/Unity.Addressables.Editor.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEditor.UI">
|
||||
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/UnityEditor.UI.dll</HintPath>
|
||||
</Reference>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user