Files
gitea-macos-vm-orchestrator/Makefile
T

116 lines
4.5 KiB
Makefile

# gitea-macos-runner
#
# Virtualization.framework refuses to start a VM unless the calling process
# carries the `com.apple.security.virtualization` entitlement, and entitlements
# only survive on a signed bundle. So the shipping artifact is not a bare
# executable but a minimal `.app` bundle that we ad-hoc sign. See docs/DESIGN.md
# ("Verified Facts", item 10).
SHELL := /bin/bash
APP_NAME := GiteaMacosRunner
BIN_NAME := gitea-macos-runner
BUILD_DIR := .build
APP_DIR := $(BUILD_DIR)/$(APP_NAME).app
CONTENTS := $(APP_DIR)/Contents
MACOS_DIR := $(CONTENTS)/MacOS
RES_DIR := $(CONTENTS)/Resources
INFO_PLIST := Resources/Info.plist
# The entitlements plist grants exactly one entitlement,
# `com.apple.security.virtualization`. Virtualization.framework refuses to
# create a VM without it, and it is granted by ad-hoc signing
# (`codesign --sign -`) -- no Apple developer account required.
#
# Deliberately absent: com.apple.vm.networking, which would be needed for a
# bridged network attachment. That one IS restricted and requires an approved
# provisioning profile. We use NAT instead, which needs nothing extra and has
# the side benefit of putting each guest into /var/db/dhcpd_leases, which is
# how the daemon discovers guest IPs.
#
# Keep that plist free of XML comments. `plutil` accepts them, but codesign
# hands the file to AMFI's stricter parser, which rejects a `<!-- -->` block
# with "AMFIUnserializeXML: syntax error" -- and the bundle then signs with no
# entitlements at all, so every VM start fails at runtime.
ENTITLEMENTS:= Resources/gitea-macos-runner.entitlements
# Data files the tool reads at runtime. `GuestProvisioner`, `LaunchdService`,
# and `config init` each look in `Contents/Resources` first and only then fall
# back to repo-relative paths, so an installed .app that lacks these is a
# working binary with a broken `image build` / `service install` / `config init`.
APP_RESOURCES := Resources/provision.sh \
Resources/launchd.plist.template \
Resources/config.example.json
INSTALL_DIR := $(HOME)/Applications
LINK_PATH := /usr/local/bin/$(BIN_NAME)
# Release by default; `make dev` overrides to debug.
CONFIG ?= release
BIN_PATH = $(BUILD_DIR)/$(CONFIG)/$(BIN_NAME)
.PHONY: all build bundle sign dev test install uninstall clean help
# These targets are a pipeline, not independent work: `bundle` needs the binary
# `build` produced, and `sign` signs the tree `bundle` assembled — a signature
# that overtook the resource copy would not cover Contents/Resources, and the
# bundle would fail to launch. Expressing that as prerequisites is not an option
# because `dev` reuses `bundle` against a debug build it made itself, so serial
# execution is imposed instead.
.NOTPARALLEL:
all: build bundle sign
## build: compile the release binary for arm64
build:
swift build -c release --arch arm64
## bundle: assemble the minimal .app around the compiled binary
bundle:
@test -x "$(BIN_PATH)" || { echo "error: $(BIN_PATH) not built; run 'make build' (or 'make dev')"; exit 1; }
mkdir -p "$(MACOS_DIR)" "$(RES_DIR)"
cp "$(BIN_PATH)" "$(MACOS_DIR)/$(BIN_NAME)"
cp "$(INFO_PLIST)" "$(CONTENTS)/Info.plist"
cp $(APP_RESOURCES) "$(RES_DIR)/"
chmod +x "$(RES_DIR)/provision.sh"
## sign: ad-hoc sign the bundle with the virtualization entitlement
sign:
codesign --sign - --entitlements "$(ENTITLEMENTS)" --force "$(APP_DIR)"
@echo "--- entitlements ---"
@codesign -d --entitlements - "$(APP_DIR)" 2>/dev/null || true
## dev: debug build + bundle + sign (fast iteration loop)
dev:
swift build --arch arm64
$(MAKE) CONFIG=debug bundle
$(MAKE) sign
## test: run the unit test suite
test:
swift test
## install: copy the signed app to ~/Applications and link the CLI
install: all
mkdir -p "$(INSTALL_DIR)"
rm -rf "$(INSTALL_DIR)/$(APP_NAME).app"
cp -R "$(APP_DIR)" "$(INSTALL_DIR)/$(APP_NAME).app"
@if [ -w "$$(dirname $(LINK_PATH))" ]; then \
ln -sf "$(INSTALL_DIR)/$(APP_NAME).app/Contents/MacOS/$(BIN_NAME)" "$(LINK_PATH)"; \
echo "linked $(LINK_PATH)"; \
else \
echo "note: $$(dirname $(LINK_PATH)) not writable; skipping symlink."; \
echo " run: sudo ln -sf $(INSTALL_DIR)/$(APP_NAME).app/Contents/MacOS/$(BIN_NAME) $(LINK_PATH)"; \
fi
## uninstall: remove the installed app and symlink
uninstall:
rm -rf "$(INSTALL_DIR)/$(APP_NAME).app"
@if [ -L "$(LINK_PATH)" ]; then rm -f "$(LINK_PATH)"; fi
## clean: remove all build products
clean:
rm -rf "$(BUILD_DIR)"
## help: list targets
help:
@grep -E '^## ' $(MAKEFILE_LIST) | sed 's/^## / /'