Repository navigation
feat: bridge an external MCP gateway into Apple Container guests #11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Apple Container | |
| # Hosted CI coverage for the Apple Container backend. | |
| # | |
| # Nothing here launches a VM, and nothing here can: Apple Container needs | |
| # Virtualization.framework, and GitHub-hosted macOS runners are themselves | |
| # virtualized and report `kern.hv_support=0`. Live end-to-end validation runs on | |
| # a self-hosted bare-metal Apple Silicon runner via | |
| # `.github/workflows/smoke-apple-container.yml`. | |
| # | |
| # What this workflow proves without a live VM is the part the design depends on: | |
| # the guest binary compiles for Linux arm64 and its relay logic behaves, the | |
| # compiled-in host/guest contract halves agree, the init image build encodes the | |
| # same contract, and the host-side runtime selection, compatibility matrix, | |
| # infrastructure publication, mounts, environment, and lifecycle ordering are | |
| # all what they claim to be. | |
| on: | |
| workflow_dispatch: | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| paths: | |
| - '.github/workflows/test-apple-container.yml' | |
| - 'guest/apple-container-init/**' | |
| - 'src/apple-container/**' | |
| - 'src/apple-container-runtime-backend.ts' | |
| - 'containers/apple-init/**' | |
| - 'scripts/build-apple-init-image.sh' | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: apple-container-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| guest-init: | |
| name: Guest init shim (Linux arm64) | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 | |
| - name: Set up Go | |
| uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 | |
| with: | |
| go-version: '1.25.0' | |
| cache-dependency-path: guest/apple-container-init/go.mod | |
| - name: Vet guest init shim | |
| working-directory: guest/apple-container-init | |
| run: go vet ./... | |
| # The relay tests bind real loopback sockets and Unix sockets, so they | |
| # catch forwarding, half-close, and teardown defects here rather than as | |
| # an unreachable capability inside a real VM. | |
| - name: Test guest init shim | |
| working-directory: guest/apple-container-init | |
| run: go test ./... | |
| - name: Build guest init shim for Linux arm64 | |
| working-directory: guest/apple-container-init | |
| run: ./build.sh | |
| - name: Verify the build is deterministic | |
| working-directory: guest/apple-container-init | |
| run: | | |
| first=$(cut -d' ' -f1 awf-apple-guest-init.sha256) | |
| rm -f awf-apple-guest-init awf-apple-guest-init.sha256 | |
| ./build.sh | |
| second=$(cut -d' ' -f1 awf-apple-guest-init.sha256) | |
| if [ "$first" != "$second" ]; then | |
| echo "guest init build is not deterministic: $first != $second" >&2 | |
| exit 1 | |
| fi | |
| echo "deterministic digest: $first" | |
| - name: Verify the binary is a static Linux arm64 executable | |
| working-directory: guest/apple-container-init | |
| run: | | |
| file awf-apple-guest-init | tee /dev/stderr | \ | |
| grep -q 'ELF 64-bit LSB executable, ARM aarch64' | |
| file awf-apple-guest-init | grep -q 'statically linked' | |
| host-runtime: | |
| name: Host runtime unit tests | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 | |
| with: | |
| node-version: '20' | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Type-check | |
| run: npm run type-check | |
| - name: Lint the Apple Container module | |
| run: npx eslint src/apple-container | |
| # transport-contract-sync.test.ts parses guest/apple-container-init/ | |
| # contract.go, and init-image-contract.test.ts parses | |
| # containers/apple-init/Dockerfile, so a divergence between the host half, | |
| # the guest half, and the shipped init image fails here instead of inside a | |
| # VM nobody can reproduce. | |
| - name: Run Apple Container unit tests | |
| run: | | |
| npx jest \ | |
| src/apple-container \ | |
| src/apple-container-runtime-backend.test.ts \ | |
| src/apple-container-runtime-selection.test.ts \ | |
| --testTimeout=15000 | |
| - name: Verify the generated JSON Schema is in sync | |
| run: | | |
| npm run generate:schema | |
| git diff --exit-code docs/awf-config.schema.json src/awf-config-schema.json | |
| init-image: | |
| name: Init image build (Linux arm64) | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 20 | |
| # Requires a digest-pinned reference to Apple's own vminit image. There is | |
| # deliberately no default, so forks and PRs from forks skip this job rather | |
| # than building against a floating base. | |
| if: vars.APPLE_VMINIT_IMAGE != '' | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 | |
| with: | |
| platforms: arm64 | |
| - name: Build the AWF Apple init image | |
| env: | |
| AWF_VMINIT_IMAGE: ${{ vars.APPLE_VMINIT_IMAGE }} | |
| run: ./scripts/build-apple-init-image.sh awf-apple-init:ci | |
| # The image has no shell, so the only way to prove the relocation happened | |
| # is to export and inspect it. | |
| - name: Verify Apple's init was relocated and the shim installed | |
| run: | | |
| set -euo pipefail | |
| id=$(docker create --platform linux/arm64 awf-apple-init:ci) | |
| trap 'docker rm -f "$id" >/dev/null 2>&1 || true' EXIT | |
| docker export "$id" > init.tar | |
| tar -tf init.tar | grep -qx 'sbin/vminitd' | |
| tar -tf init.tar | grep -qx 'sbin/vminitd.apple' | |
| mkdir -p extracted | |
| tar -xf init.tar -C extracted sbin/vminitd | |
| file extracted/sbin/vminitd | tee /dev/stderr | \ | |
| grep -q 'ELF 64-bit LSB executable, ARM aarch64' | |
| file extracted/sbin/vminitd | grep -q 'statically linked' | |
| - name: Verify the recorded contract labels match the host half | |
| run: | | |
| set -euo pipefail | |
| src=src/apple-container/transport-capabilities.ts | |
| min=$(sed -n "s/^export const APPLE_CONTAINER_TRANSPORT_MIN_CLI_VERSION = '\\([^']*\\)';$/\\1/p" "$src") | |
| max=$(sed -n "s/^export const APPLE_CONTAINER_TRANSPORT_MAX_CLI_VERSION_EXCLUSIVE = '\\([^']*\\)';$/\\1/p" "$src") | |
| labels=$(docker image inspect awf-apple-init:ci --format '{{json .Config.Labels}}') | |
| echo "$labels" | |
| echo "$labels" | grep -q "\"io.github.gh-aw-firewall.apple-init.cli-min-version\":\"$min\"" | |
| echo "$labels" | grep -q "\"io.github.gh-aw-firewall.apple-init.cli-max-version-exclusive\":\"$max\"" | |