mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-13 21:01:21 +08:00
fix CLI artifact download + build arm64 emulator on macOS runner
- Fix 415 on artifact download: use application/vnd.github+json Accept header - Fix EACCES on run-emulator.sh: chmod +x at runtime (npm strips execute bit) - Move arm64 emulator build to a macOS-15 runner with HVF so the snapshot is portable to developer Macs (KVM snapshots from Linux are not resumable under HVF due to differing -cpu max feature sets)
This commit is contained in:
parent
037755ba16
commit
894c1ce77c
134
.github/workflows/qemu-emulator-build-arm64.yaml
vendored
Normal file
134
.github/workflows/qemu-emulator-build-arm64.yaml
vendored
Normal file
@ -0,0 +1,134 @@
|
||||
name: Build QEMU Emulator Image (arm64 / macOS)
|
||||
|
||||
# arm64 emulator images are built on a macOS Apple Silicon runner so the
|
||||
# snapshot is captured under HVF — the same accelerator developer Macs use.
|
||||
# KVM snapshots (from Linux runners) are NOT resumable under HVF because
|
||||
# `-cpu max` expands to different feature sets under each accelerator.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
pull_request:
|
||||
paths:
|
||||
- 'docker/local-emulator/**'
|
||||
- '.github/workflows/qemu-emulator-build-arm64.yaml'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: qemu-arm64-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/dev' }}
|
||||
|
||||
env:
|
||||
EMULATOR_IMAGE_NAME: stack-local-emulator
|
||||
EMULATOR_IMAGE_DIR: ${{ github.workspace }}/docker/local-emulator/qemu/images
|
||||
EMULATOR_RUN_DIR: ${{ github.workspace }}/docker/local-emulator/qemu/run
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build QEMU Image (arm64)
|
||||
runs-on: macos-15
|
||||
timeout-minutes: 120
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.23.0
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
|
||||
- name: Install system dependencies
|
||||
run: brew install qemu socat zstd
|
||||
|
||||
- name: Set up Docker via colima
|
||||
run: |
|
||||
brew install docker docker-buildx colima
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
ln -sfn "$(brew --prefix docker-buildx)/bin/docker-buildx" ~/.docker/cli-plugins/docker-buildx
|
||||
colima start --cpu 4 --memory 6 --disk 60 --arch aarch64
|
||||
docker info
|
||||
docker buildx version
|
||||
|
||||
- name: Verify QEMU + HVF
|
||||
run: |
|
||||
qemu-system-aarch64 --version
|
||||
if qemu-system-aarch64 -accel help 2>&1 | grep -q hvf; then
|
||||
echo "HVF available — snapshot will be portable to developer Macs"
|
||||
else
|
||||
echo "::error::HVF not available on this runner"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build QEMU image
|
||||
run: |
|
||||
chmod +x docker/local-emulator/qemu/build-image.sh
|
||||
EMULATOR_PROVISION_TIMEOUT=6000 \
|
||||
docker/local-emulator/qemu/build-image.sh arm64
|
||||
|
||||
- name: Generate emulator env
|
||||
run: node docker/local-emulator/generate-env-development.mjs
|
||||
|
||||
# HVF gives us native-speed arm64 — we can verify the image boots
|
||||
# and services come up, unlike the old cross-arch TCG path.
|
||||
- name: Build stack-cli
|
||||
run: |
|
||||
pnpm install --frozen-lockfile --filter '@stackframe/stack-cli...'
|
||||
pnpm exec turbo run build --filter='@stackframe/stack-cli...'
|
||||
|
||||
- name: Start emulator and verify
|
||||
env:
|
||||
EMULATOR_ARCH: arm64
|
||||
EMULATOR_READY_TIMEOUT: 3200
|
||||
EMULATOR_IMAGE_DIR: ${{ env.EMULATOR_IMAGE_DIR }}
|
||||
EMULATOR_RUN_DIR: ${{ env.EMULATOR_RUN_DIR }}
|
||||
run: node packages/stack-cli/dist/index.js emulator start
|
||||
|
||||
- name: Verify services are healthy
|
||||
env:
|
||||
EMULATOR_ARCH: arm64
|
||||
EMULATOR_IMAGE_DIR: ${{ env.EMULATOR_IMAGE_DIR }}
|
||||
EMULATOR_RUN_DIR: ${{ env.EMULATOR_RUN_DIR }}
|
||||
run: node packages/stack-cli/dist/index.js emulator status
|
||||
|
||||
- name: Stop emulator
|
||||
if: always()
|
||||
env:
|
||||
EMULATOR_ARCH: arm64
|
||||
EMULATOR_IMAGE_DIR: ${{ env.EMULATOR_IMAGE_DIR }}
|
||||
EMULATOR_RUN_DIR: ${{ env.EMULATOR_RUN_DIR }}
|
||||
run: node packages/stack-cli/dist/index.js emulator stop
|
||||
|
||||
- name: Print serial log on failure
|
||||
if: failure()
|
||||
run: |
|
||||
tail -100 "$EMULATOR_RUN_DIR/vm/serial.log" 2>/dev/null || true
|
||||
|
||||
- name: Package image
|
||||
run: |
|
||||
BASE_IMG="docker/local-emulator/qemu/images/stack-emulator-arm64.qcow2"
|
||||
SAVEVM="docker/local-emulator/qemu/images/stack-emulator-arm64.savevm.zst"
|
||||
cp "$BASE_IMG" "stack-emulator-arm64.qcow2"
|
||||
if [ -f "$SAVEVM" ]; then
|
||||
cp "$SAVEVM" "stack-emulator-arm64.savevm.zst"
|
||||
ls -lh "stack-emulator-arm64.savevm.zst"
|
||||
else
|
||||
echo "::error::Snapshot was not produced — fast-start will be unavailable"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Upload image artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: qemu-emulator-arm64
|
||||
path: |
|
||||
stack-emulator-arm64.qcow2
|
||||
stack-emulator-arm64.savevm.zst
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
compression-level: 0
|
||||
11
.github/workflows/qemu-emulator-build.yaml
vendored
11
.github/workflows/qemu-emulator-build.yaml
vendored
@ -35,16 +35,11 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
# amd64 runs natively under KVM on ubicloud's amd64 runner.
|
||||
# arm64 is built in a separate workflow on a macOS runner (HVF)
|
||||
# so that the snapshot is portable to developer Macs.
|
||||
# See qemu-emulator-build-arm64.yaml.
|
||||
- arch: amd64
|
||||
runner: ubicloud-standard-8
|
||||
# arm64 runs under cross-arch TCG on ubicloud's amd64 runner.
|
||||
# No KVM for arm64 guests on an amd64 host; cortex-a72 + V8
|
||||
# --jitless together sidestep the SIGTRAPs that cross-arch TCG
|
||||
# hits on aggressive arm64 JIT code. Smoke test is still skipped
|
||||
# because the backend can't come up reliably under cross-arch
|
||||
# TCG within any sane window.
|
||||
- arch: arm64
|
||||
runner: ubicloud-standard-8
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Command } from "commander";
|
||||
import { execFileSync, spawn } from "child_process";
|
||||
import extract from "extract-zip";
|
||||
import { createWriteStream, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync } from "fs";
|
||||
import { chmodSync, createWriteStream, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync } from "fs";
|
||||
import { homedir } from "os";
|
||||
import { dirname, join, resolve } from "path";
|
||||
import { Readable } from "stream";
|
||||
@ -143,12 +143,18 @@ async function ghApi<T>(path: string): Promise<T> {
|
||||
function emulatorScriptsDir(): string {
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const bundled = join(here, "emulator");
|
||||
if (existsSync(join(bundled, "run-emulator.sh"))) return bundled;
|
||||
if (existsSync(join(bundled, "run-emulator.sh"))) return ensureExecutable(bundled);
|
||||
const repo = resolve(here, "../../../docker/local-emulator/qemu");
|
||||
if (existsSync(join(repo, "run-emulator.sh"))) return repo;
|
||||
if (existsSync(join(repo, "run-emulator.sh"))) return ensureExecutable(repo);
|
||||
throw new CliError("Emulator scripts not found in CLI bundle.");
|
||||
}
|
||||
|
||||
// npm pack strips the execute bit from non-`bin` files, so restore it here.
|
||||
function ensureExecutable(scriptsDir: string): string {
|
||||
try { chmodSync(join(scriptsDir, "run-emulator.sh"), 0o755); } catch { /* best-effort */ }
|
||||
return scriptsDir;
|
||||
}
|
||||
|
||||
function baseEnvPath(): string {
|
||||
// Lives one directory up from the scripts dir in both bundled and repo
|
||||
// layouts (dist/.env.development vs docker/local-emulator/.env.development).
|
||||
@ -467,7 +473,7 @@ async function downloadArtifactByName(repo: string, runId: string, name: string,
|
||||
console.log(`Downloading artifact '${name}' from run ${runId}...`);
|
||||
await downloadWithProgress(
|
||||
`${GITHUB_API}/repos/${repo}/actions/artifacts/${match.id}/zip`,
|
||||
{ Accept: "application/octet-stream", Authorization: `Bearer ${token}` },
|
||||
{ Accept: "application/vnd.github+json", Authorization: `Bearer ${token}` },
|
||||
zipPath,
|
||||
match.size_in_bytes,
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user