Skip to content

feat: bridge an external MCP gateway into Apple Container guests #11

feat: bridge an external MCP gateway into Apple Container guests

feat: bridge an external MCP gateway into Apple Container guests #11

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\""