mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 20:55:44 +00:00
319 lines
13 KiB
YAML
319 lines
13 KiB
YAML
name: Shardok ARM64 Build and Push
|
|
|
|
on:
|
|
push:
|
|
branches: [ "main" ]
|
|
paths:
|
|
- 'src/main/cpp/**'
|
|
- 'src/main/protobuf/net/eagle0/shardok/**'
|
|
- 'src/main/protobuf/net/eagle0/common/**'
|
|
- 'src/main/go/net/eagle0/build/**'
|
|
- 'src/main/resources/net/eagle0/shardok/**'
|
|
- 'ci/BUILD.bazel'
|
|
- 'ci/github_actions/ensure_bazel_installed.sh'
|
|
- '.github/actions/setup-bazel/**'
|
|
- 'go.mod'
|
|
- 'go.sum'
|
|
- 'MODULE.bazel'
|
|
- 'MODULE.bazel.lock'
|
|
- '.bazelrc'
|
|
- '.github/workflows/shardok_arm64_build.yml'
|
|
workflow_dispatch:
|
|
inputs:
|
|
push_images:
|
|
description: 'Push images to container registry'
|
|
required: true
|
|
default: 'true'
|
|
type: boolean
|
|
|
|
concurrency:
|
|
group: shardok-arm64-deploy
|
|
cancel-in-progress: false
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
build-shardok-arm64:
|
|
runs-on: [self-hosted, bazel]
|
|
outputs:
|
|
image_tag: ${{ steps.push-shardok.outputs.image_tag }}
|
|
steps:
|
|
- name: Prepare non-LFS checkout
|
|
env:
|
|
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig-no-lfs
|
|
GIT_CONFIG_NOSYSTEM: "1"
|
|
run: |
|
|
git config --global --unset-all filter.lfs.process || true
|
|
git config --global filter.lfs.smudge "cat"
|
|
git config --global filter.lfs.clean "cat"
|
|
git config --global filter.lfs.required false
|
|
if [ -d ".git" ]; then
|
|
git config --local --unset-all filter.lfs.process || true
|
|
git config --local filter.lfs.smudge "cat"
|
|
git config --local filter.lfs.clean "cat"
|
|
git config --local filter.lfs.required false
|
|
fi
|
|
rm -f .git/hooks/post-checkout .git/hooks/post-merge .git/hooks/pre-push
|
|
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v7
|
|
env:
|
|
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig-no-lfs
|
|
GIT_CONFIG_NOSYSTEM: "1"
|
|
with:
|
|
persist-credentials: false
|
|
lfs: false
|
|
|
|
- name: Ensure Bazel installed
|
|
uses: ./.github/actions/setup-bazel
|
|
|
|
- name: Build optimized, profileable Shardok ARM64 binary
|
|
run: |
|
|
set -ex
|
|
|
|
echo "=== Building shardok-server binary for linux-aarch64 ==="
|
|
bazel build \
|
|
-c opt \
|
|
--strip=never \
|
|
--copt=-gline-tables-only \
|
|
--copt=-fno-omit-frame-pointer \
|
|
--copt=-mno-omit-leaf-frame-pointer \
|
|
--linkopt=-Wl,--build-id=sha1 \
|
|
--platforms=//:linux_arm64 \
|
|
--extra_toolchains=@llvm_toolchain_linux_arm64//:cc-toolchain-aarch64-linux \
|
|
//src/main/cpp/net/eagle0/shardok:shardok-server
|
|
|
|
LINUX_BIN="bazel-bin/src/main/cpp/net/eagle0/shardok/shardok-server"
|
|
echo "=== Checking binary at: $LINUX_BIN ==="
|
|
|
|
if [ ! -f "$LINUX_BIN" ]; then
|
|
echo "ERROR: Binary not found at $LINUX_BIN"
|
|
exit 1
|
|
fi
|
|
|
|
# Verify it's ELF (Linux) not Mach-O (macOS)
|
|
echo "=== Verifying binary format ==="
|
|
MAGIC=$(head -c 4 "$LINUX_BIN" | xxd -p)
|
|
echo "Binary magic bytes: $MAGIC"
|
|
|
|
if [ "$MAGIC" = "7f454c46" ]; then
|
|
echo "SUCCESS: Binary is ELF format (Linux)"
|
|
# Check if it's ARM64 (e_machine = 0xB7 = 183 for aarch64)
|
|
# od -tx2 reads as a 16-bit value in host byte order (little-endian on macOS),
|
|
# so the little-endian ELF bytes b7 00 are displayed as 00b7.
|
|
E_MACHINE=$(od -An -j18 -N2 -tx2 "$LINUX_BIN" | tr -d ' ')
|
|
echo "ELF e_machine: $E_MACHINE"
|
|
if [ "$E_MACHINE" = "00b7" ]; then
|
|
echo "SUCCESS: Binary is ARM64 (aarch64)"
|
|
else
|
|
echo "WARNING: Binary e_machine is $E_MACHINE (expected 00b7 for aarch64)"
|
|
fi
|
|
|
|
BINARY_DESCRIPTION=$(file "$LINUX_BIN")
|
|
echo "Binary description: $BINARY_DESCRIPTION"
|
|
for REQUIRED_TEXT in "ARM aarch64" "BuildID" "with debug_info" "not stripped"; do
|
|
if [[ "$BINARY_DESCRIPTION" != *"$REQUIRED_TEXT"* ]]; then
|
|
echo "ERROR: Profileable release binary is missing '$REQUIRED_TEXT'"
|
|
exit 1
|
|
fi
|
|
done
|
|
elif [ "$MAGIC" = "cfaeedfe" ] || [ "$MAGIC" = "cffaedfe" ]; then
|
|
echo "ERROR: Binary is Mach-O format (macOS) - cross-compilation failed!"
|
|
exit 1
|
|
else
|
|
echo "WARNING: Unknown binary format: $MAGIC"
|
|
file "$LINUX_BIN" || true
|
|
fi
|
|
|
|
- name: Build optimized, profileable Shardok ARM64 Docker image
|
|
id: build-shardok
|
|
run: |
|
|
set -ex
|
|
|
|
# Keep this in sync with the standalone binary build above.
|
|
bazel build \
|
|
-c opt \
|
|
--strip=never \
|
|
--copt=-gline-tables-only \
|
|
--copt=-fno-omit-frame-pointer \
|
|
--copt=-mno-omit-leaf-frame-pointer \
|
|
--linkopt=-Wl,--build-id=sha1 \
|
|
--platforms=//:linux_arm64 \
|
|
--extra_toolchains=@llvm_toolchain_linux_arm64//:cc-toolchain-aarch64-linux \
|
|
//ci:shardok_server_image_arm64
|
|
|
|
IMAGE_PATH=$(readlink -f bazel-bin/ci/shardok_server_image_arm64)
|
|
echo "Image path: $IMAGE_PATH"
|
|
echo "image_path=$IMAGE_PATH" >> $GITHUB_OUTPUT
|
|
|
|
# Verify the binary inside the tar layer is ARM64 ELF
|
|
echo "=== Verifying binary in image tar ==="
|
|
BINARY_TAR="bazel-bin/ci/shardok_binary_layer_arm64.tar"
|
|
if [ -f "$BINARY_TAR" ]; then
|
|
echo "Checking binary in $BINARY_TAR"
|
|
MAGIC=$(tar -xOf "$BINARY_TAR" app/shardok-server 2>/dev/null | head -c 4 | xxd -p)
|
|
echo "Binary magic in tar: $MAGIC"
|
|
if [ "$MAGIC" = "7f454c46" ]; then
|
|
echo "SUCCESS: Binary in tar is ELF format (Linux)"
|
|
else
|
|
echo "ERROR: Binary in tar is NOT ELF format!"
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "WARNING: Could not find $BINARY_TAR"
|
|
fi
|
|
|
|
- name: Login to DigitalOcean Container Registry
|
|
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true')
|
|
env:
|
|
DO_REGISTRY_TOKEN: ${{ secrets.DO_REGISTRY_TOKEN }}
|
|
run: |
|
|
AUTH=$(echo -n "${DO_REGISTRY_TOKEN}:${DO_REGISTRY_TOKEN}" | base64)
|
|
echo "{\"auths\":{\"registry.digitalocean.com\":{\"auth\":\"${AUTH}\"}}}" > ~/.docker/config.json
|
|
|
|
- name: Push Shardok ARM64 image to DigitalOcean
|
|
id: push-shardok
|
|
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true')
|
|
run: |
|
|
set -ex
|
|
|
|
CROSS_IMAGE="${{ steps.build-shardok.outputs.image_path }}"
|
|
echo "Using cross-compiled image: $CROSS_IMAGE"
|
|
|
|
if [ -z "$CROSS_IMAGE" ] || [ ! -d "$CROSS_IMAGE" ]; then
|
|
echo "ERROR: Cross-compiled image not found at: $CROSS_IMAGE"
|
|
exit 1
|
|
fi
|
|
|
|
# Get crane (cached across runs to avoid rebuilding bazel target,
|
|
# which would discard the ARM64 analysis cache due to platform change)
|
|
CRANE_VERSION="v0.20.2"
|
|
CRANE_DIR="${HOME}/.local/bin"
|
|
CRANE="${CRANE_DIR}/crane"
|
|
if [ ! -x "$CRANE" ] || ! "$CRANE" version 2>/dev/null | grep -q "0.20.2"; then
|
|
echo "Installing crane ${CRANE_VERSION}..."
|
|
mkdir -p "$CRANE_DIR"
|
|
curl -sL "https://github.com/google/go-containerregistry/releases/download/${CRANE_VERSION}/go-containerregistry_Darwin_arm64.tar.gz" | tar xzf - -C "$CRANE_DIR" crane
|
|
chmod +x "$CRANE"
|
|
else
|
|
echo "Using cached crane at $CRANE"
|
|
fi
|
|
|
|
# Push with arm64-prefixed SHA tag (same repo as x86, different tag)
|
|
GIT_SHA=$(git rev-parse --short=8 HEAD)
|
|
IMAGE_TAG="registry.digitalocean.com/eagle0/shardok-server:arm64-${GIT_SHA}"
|
|
echo "Pushing shardok ARM64 image: $IMAGE_TAG"
|
|
$CRANE push "$CROSS_IMAGE" "$IMAGE_TAG"
|
|
|
|
echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT
|
|
|
|
# Also update :arm64-latest tag for convenience
|
|
echo "Copying to :arm64-latest tag"
|
|
$CRANE copy "$IMAGE_TAG" "registry.digitalocean.com/eagle0/shardok-server:arm64-latest"
|
|
|
|
echo "=== Push complete ==="
|
|
echo "Image: $IMAGE_TAG"
|
|
echo "Also tagged as: registry.digitalocean.com/eagle0/shardok-server:arm64-latest"
|
|
|
|
deploy-hetzner:
|
|
# Keep this on a self-hosted Mac runner for IPv6 reachability to Hetzner,
|
|
# but do not require a Bazel runner slot for artifact transport/deploy work.
|
|
runs-on: [self-hosted, macOS, ARM64]
|
|
needs: [build-shardok-arm64]
|
|
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true')
|
|
environment: production
|
|
env:
|
|
SHARDOK_IMAGE: ${{ needs.build-shardok-arm64.outputs.image_tag }}
|
|
steps:
|
|
- name: Setup SSH key
|
|
run: |
|
|
mkdir -p ~/.ssh
|
|
echo "${{ secrets.HETZNER_SSH_KEY }}" > ~/.ssh/hetzner_deploy
|
|
chmod 600 ~/.ssh/hetzner_deploy
|
|
# Add host key to known_hosts to avoid prompt
|
|
ssh-keyscan -H ${{ secrets.HETZNER_IP }} >> ~/.ssh/known_hosts 2>/dev/null || true
|
|
|
|
- name: Pull Shardok image tarball for Hetzner
|
|
env:
|
|
DO_REGISTRY_TOKEN: ${{ secrets.DO_REGISTRY_TOKEN }}
|
|
run: |
|
|
set -ex
|
|
|
|
# Hetzner is IPv6-only, while Docker can be redirected to IPv4-only
|
|
# DigitalOcean registry/blob endpoints. Pull on the runner and copy
|
|
# the Docker-loadable tarball over SSH instead.
|
|
CRANE_VERSION="v0.20.2"
|
|
CRANE_DIR="${HOME}/.local/bin"
|
|
CRANE="${CRANE_DIR}/crane"
|
|
if [ ! -x "$CRANE" ] || ! "$CRANE" version 2>/dev/null | grep -q "0.20.2"; then
|
|
echo "Installing crane ${CRANE_VERSION}..."
|
|
mkdir -p "$CRANE_DIR"
|
|
curl -sL "https://github.com/google/go-containerregistry/releases/download/${CRANE_VERSION}/go-containerregistry_Darwin_arm64.tar.gz" | tar xzf - -C "$CRANE_DIR" crane
|
|
chmod +x "$CRANE"
|
|
else
|
|
echo "Using cached crane at $CRANE"
|
|
fi
|
|
|
|
AUTH=$(echo -n "${DO_REGISTRY_TOKEN}:${DO_REGISTRY_TOKEN}" | base64)
|
|
mkdir -p ~/.docker
|
|
echo "{\"auths\":{\"registry.digitalocean.com\":{\"auth\":\"${AUTH}\"}}}" > ~/.docker/config.json
|
|
|
|
IMAGE_TAR="${RUNNER_TEMP}/shardok-arm64-image.tar"
|
|
"$CRANE" pull "${SHARDOK_IMAGE}" "$IMAGE_TAR"
|
|
echo "SHARDOK_IMAGE_TAR=$IMAGE_TAR" >> "$GITHUB_ENV"
|
|
|
|
- name: Copy Shardok image tarball to Hetzner
|
|
run: |
|
|
set -ex
|
|
ssh -i ~/.ssh/hetzner_deploy -o StrictHostKeyChecking=accept-new deploy@${{ secrets.HETZNER_IP }} \
|
|
"cat > /tmp/shardok-arm64-image.tar" < "$SHARDOK_IMAGE_TAR"
|
|
|
|
- name: Deploy to Hetzner
|
|
run: |
|
|
ssh -i ~/.ssh/hetzner_deploy -o StrictHostKeyChecking=accept-new deploy@${{ secrets.HETZNER_IP }} << 'ENDSSH'
|
|
set -ex
|
|
cd /opt/eagle0
|
|
|
|
SHARDOK_IMAGE="${{ needs.build-shardok-arm64.outputs.image_tag }}"
|
|
echo "Loading Shardok ARM64 image: $SHARDOK_IMAGE"
|
|
docker load -i /tmp/shardok-arm64-image.tar
|
|
rm -f /tmp/shardok-arm64-image.tar
|
|
docker image inspect "$SHARDOK_IMAGE" > /dev/null
|
|
echo "Loaded image ID: $(docker image inspect --format '{{.Id}}' "$SHARDOK_IMAGE")"
|
|
echo "Shardok binary SHA256:"
|
|
docker run --rm --entrypoint sha256sum "$SHARDOK_IMAGE" /app/shardok-server
|
|
|
|
# Stop and remove any container using port 40042 or named shardok*
|
|
docker ps -q --filter "publish=40042" | xargs -r docker stop
|
|
docker ps -aq --filter "name=shardok" | xargs -r docker rm -f
|
|
docker ps -aq --filter "publish=40042" | xargs -r docker rm -f
|
|
|
|
# Run new container
|
|
docker run -d \
|
|
--name shardok-ai \
|
|
--restart unless-stopped \
|
|
-p 40042:40042 \
|
|
-v /opt/eagle0/data:/data \
|
|
-v /etc/shardok:/etc/shardok:ro \
|
|
-v /etc/letsencrypt:/etc/letsencrypt:ro \
|
|
-v /usr/local/share/eagle0:/usr/local/share/eagle0:ro \
|
|
-e SHARDOK_RESOURCES_PATH=/app/resources \
|
|
-e SHARDOK_MAPS_PATH=/app/resources/maps \
|
|
"$SHARDOK_IMAGE"
|
|
|
|
# Wait and verify
|
|
sleep 5
|
|
docker ps | grep shardok-ai
|
|
echo "Running container image ID: $(docker inspect --format '{{.Image}}' shardok-ai)"
|
|
|
|
# Cleanup old images
|
|
docker image prune -f
|
|
|
|
echo "=== Hetzner deployment complete ==="
|
|
ENDSSH
|
|
|
|
- name: Cleanup SSH key
|
|
if: always()
|
|
run: rm -f ~/.ssh/hetzner_deploy
|