Files
punktfunk/scripts/ci/setup-macos-runner.sh
T
enricobuehler 0bfc7fe913
apple / swift (pull_request) Successful in 1m43s
apple / distribute (pull_request) Skipped
apple / screenshots (pull_request) Skipped
windows-drivers / probe-and-proto (pull_request) Successful in 30s
windows-drivers / driver-build (pull_request) Successful in 1m49s
ci / docs-site (pull_request) Successful in 1m21s
ci / bun-nix (pull_request) Successful in 31s
ci / web (pull_request) Successful in 3m50s
ci / rust-arm64 (pull_request) Successful in 8m32s
android / android (pull_request) Successful in 7m22s
ci / rust (pull_request) Successful in 14m7s
windows-client / client (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (pull_request) Successful in 3m6s
windows-client / client (x64, , x86_64-pc-windows-msvc, C:\t) (pull_request) Successful in 8m7s
ci: fold release.yml into apple.yml and the two Windows client workflows into one
Two merges, both of which exist to express an ordering Gitea cannot express across
files, and both of which delete a duplicated build.

release.yml -> apple.yml (as the `distribute` job)
  The name described neither what it did (Apple only — every other platform's release
  is its own packaging workflow attaching to the same Gitea release on a v* tag, with
  announce.yml as the manual "go") nor anything a reader would guess. The name was the
  smaller problem. Gitea has no cross-workflow `needs`, so nothing sequenced it against
  apple.yml's tests: a canary main push uploaded iOS, macOS and tvOS builds to
  TestFlight even when `swift test` had just failed on that same commit. It is now
  `needs: swift`, which is only expressible in one file.

  The two files' paths: filters had also drifted — apple.yml watched crates/**,
  release.yml watched crates/punktfunk-core/**. The merged filter takes the NARROW one,
  because that is the correct one: everything on this runner is built from
  punktfunk-core via build-xcframework.sh, and punktfunk-core's only path dependency is
  its own vendored fec-rs. That is checkable in one command, and the header says so, and
  says to widen it if that ever stops being true. Net effect on the shared mac mini:
  pushes that touch host-side crates no longer build or upload anything Apple.

windows.yml + windows-msix.yml -> windows-client.yml
  The pair built the same three crates FOUR times per client push on ONE runner: debug
  x64 + arm64 for lint/test, release x64 + arm64 for packaging. windows-host.yml already
  records why a second (debug) dep tree on this machine is a liability rather than a
  cost — it re-runs openh264-sys2's vendored C++ through cc-rs's cl.exe fan-out and tips
  the runner into C1069, which is disk exhaustion wearing a compiler error's clothes. So
  there is one release build per arch now and clippy/fmt/test run against it, exactly as
  windows-host.yml does. The paths list went from three copies to one; PRs get the
  build/lint/test signal and stop before packaging.

The rename is safe, and this is worth recording because the GitHub instinct is wrong
here: `github.run_number` is REPO-WIDE in Gitea, not per-workflow — consecutive runs of
DIFFERENT workflows get consecutive numbers (verified against the API: android 13226,
apple 13227, arch 13228, ci 13229, deb 13230). The canary MSIX version <minor>.<run>.0
and Apple's CURRENT_PROJECT_VERSION therefore keep climbing across a rename. On GitHub
the same rename would reset both to 1, sorting every new canary below the published ones
and getting the TestFlight uploads rejected outright.

25 workflows, down from 27, and every `name:` now matches its filename. Cross-references
in windows-host.yml, windows-drivers.yml, android.yml, flatpak.yml, sbom.yml, the
provisioning scripts, gitea-release.sh and clients/windows/packaging/README.md updated.
2026-08-13 12:10:36 +02:00

157 lines
7.6 KiB
Bash

#!/usr/bin/env bash
# Provision a Mac as the Gitea Actions runner for the Apple client CI
# (.gitea/workflows/apple.yml). Idempotent — safe to re-run. Run ON THE MAC, or from a
# dev box:
#
# ssh <mac> GITEA_RUNNER_TOKEN=<registration token> bash -s < scripts/ci/setup-macos-runner.sh
#
# Installs: rustup (+ both darwin targets for the universal xcframework), Node.js (the
# runner executes JS actions like actions/checkout via `node` from PATH — host mode does
# not auto-provision it), the act_runner binary (host mode — jobs run directly on macOS,
# no containers), and a root LaunchDaemon that keeps the runner daemon alive (see the
# launchd section for why it can't be a user LaunchAgent). Registration only happens once
# (.runner file); the token is NOT persisted by this script.
#
# Env knobs: GITEA_INSTANCE (default https://git.unom.io), GITEA_RUNNER_TOKEN (required
# for first-time registration only), RUNNER_NAME (default: LocalHostName), RUNNER_LABELS
# (default macos-arm64:host — matches apple.yml's runs-on), ACT_RUNNER_VERSION,
# NODE_VERSION.
#
# NOT installed here: Xcode. swift build/test work with Command Line Tools, but
# scripts/build-xcframework.sh needs xcodebuild (-create-xcframework) from a full Xcode.
set -euo pipefail
INSTANCE="${GITEA_INSTANCE:-https://git.unom.io}"
VERSION="${ACT_RUNNER_VERSION:-1.0.8}"
RUNNER_NAME="${RUNNER_NAME:-$(scutil --get LocalHostName)}"
LABELS="${RUNNER_LABELS:-macos-arm64:host}"
RUNNER_HOME="$HOME/ci/act-runner"
BIN_DIR="$HOME/.local/bin"
# --- Rust toolchain (the xcframework is built from the Rust core) -----------------------
if [ ! -x "$HOME/.cargo/bin/rustup" ]; then
echo "==> installing rustup"
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --no-modify-path --profile minimal
fi
"$HOME/.cargo/bin/rustup" target add aarch64-apple-darwin x86_64-apple-darwin
# --- Node.js (actions runtime; sudo-free tarball install) --------------------------------
NODE_VERSION="${NODE_VERSION:-22.22.3}"
mkdir -p "$BIN_DIR"
if ! "$BIN_DIR/node" --version 2>/dev/null | grep -q "^v${NODE_VERSION}$"; then
echo "==> installing node v$NODE_VERSION"
NODE_DIR="$HOME/.local/node-v$NODE_VERSION"
mkdir -p "$NODE_DIR"
curl -fL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-darwin-arm64.tar.gz" \
| tar -xz --strip-components=1 -C "$NODE_DIR"
ln -sf "$NODE_DIR/bin/node" "$BIN_DIR/node"
fi
"$BIN_DIR/node" --version
# --- act_runner binary -------------------------------------------------------------------
# Renamed upstream to "gitea-runner" as of 1.0 (dl.gitea.com/act_runner/ stops at 0.6.x);
# we keep the local name act_runner — the CLI surface is unchanged.
mkdir -p "$BIN_DIR" "$RUNNER_HOME"
if ! "$BIN_DIR/act_runner" --version 2>/dev/null | grep -q "$VERSION"; then
echo "==> installing act_runner (gitea-runner) $VERSION"
curl -fL "https://dl.gitea.com/gitea-runner/${VERSION}/gitea-runner-${VERSION}-darwin-arm64" \
-o "$BIN_DIR/act_runner.tmp"
chmod +x "$BIN_DIR/act_runner.tmp"
mv "$BIN_DIR/act_runner.tmp" "$BIN_DIR/act_runner"
fi
"$BIN_DIR/act_runner" --version
# --- config + one-time registration ------------------------------------------------------
cd "$RUNNER_HOME"
[ -f config.yaml ] || "$BIN_DIR/act_runner" generate-config > config.yaml
# generate-config seeds runner.labels with docker:// defaults, which (a) override the
# host-mode labels registered in .runner and (b) make the daemon demand a Docker engine
# ("Docker Engine socket not found"). Empty them so .runner's labels rule.
sed -i '' -e '/docker.gitea.com\/runner-images/d' \
-e 's|^\([[:space:]]*\)labels:$|\1labels: []|' config.yaml
if [ ! -f .runner ]; then
if [ -z "${GITEA_RUNNER_TOKEN:-}" ]; then
echo "ERROR: not registered yet — re-run with GITEA_RUNNER_TOKEN=<token>" >&2
echo " (org unom -> Settings -> Actions -> Runners -> Create new runner)" >&2
exit 1
fi
"$BIN_DIR/act_runner" register --no-interactive \
--instance "$INSTANCE" \
--token "$GITEA_RUNNER_TOKEN" \
--name "$RUNNER_NAME" \
--labels "$LABELS"
fi
# --- launchd service ---------------------------------------------------------------------
# macOS Local Network privacy (15+) silently denies LAN connections ("no route to host")
# to unbundled CLI binaries in gui/user launchd domains — a user LaunchAgent can NOT reach
# a Gitea instance on the LAN (curl over ssh works, the same dial from the agent fails).
# System-domain daemons are exempt and survive reboots with nobody logged in, so the
# runner ships as a root LaunchDaemon; installing it needs sudo once. Without sudo this
# script still leaves a working (but reboot-volatile) nohup daemon behind.
# PATH must carry the CLT tools, cargo, node and act_runner itself; jobs inherit it.
# Deliberately NO DEVELOPER_DIR here: cargo (rust ld) must stay on the system default —
# a newer-than-OS Xcode's ld emits dylibs the running dyld rejects ("mis-aligned
# LINKEDIT string pool"), breaking every proc-macro build. Steps that need a full Xcode
# (xcodebuild) resolve it themselves (build-xcframework.sh, apple.yml's `distribute` job).
PLIST_STAGE="$RUNNER_HOME/io.gitea.act_runner.plist"
PLIST_SYSTEM="/Library/LaunchDaemons/io.gitea.act_runner.plist"
cat > "$PLIST_STAGE" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>io.gitea.act_runner</string>
<key>UserName</key><string>$USER</string>
<key>ProgramArguments</key>
<array>
<string>$BIN_DIR/act_runner</string>
<string>daemon</string>
<string>--config</string>
<string>$RUNNER_HOME/config.yaml</string>
</array>
<key>WorkingDirectory</key><string>$RUNNER_HOME</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>$HOME/.cargo/bin:$BIN_DIR:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
<key>HOME</key><string>$HOME</string>
</dict>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>StandardOutPath</key><string>$RUNNER_HOME/runner.log</string>
<key>StandardErrorPath</key><string>$RUNNER_HOME/runner.log</string>
</dict>
</plist>
EOF
launchctl bootout "gui/$(id -u)/io.gitea.act_runner" 2>/dev/null || true
if sudo -n true 2>/dev/null; then
sudo install -m 644 -o root -g wheel "$PLIST_STAGE" "$PLIST_SYSTEM"
pkill -x act_runner 2>/dev/null || true
sudo launchctl bootout system/io.gitea.act_runner 2>/dev/null || true
sudo launchctl bootstrap system "$PLIST_SYSTEM"
echo "==> runner LaunchDaemon bootstrapped (system domain)"
else
if ! pgrep -x act_runner >/dev/null; then
echo "==> no sudo: starting an interim daemon (dies on reboot)"
(cd "$RUNNER_HOME" && \
PATH="$HOME/.cargo/bin:$BIN_DIR:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" \
nohup "$BIN_DIR/act_runner" daemon --config config.yaml >> runner.log 2>&1 &)
fi
echo "==> for the permanent (reboot-safe) runner, run once on the Mac:"
echo " sudo install -m 644 -o root -g wheel $PLIST_STAGE $PLIST_SYSTEM"
echo " sudo launchctl bootstrap system $PLIST_SYSTEM"
fi
sleep 2
tail -5 "$RUNNER_HOME/runner.log" 2>/dev/null || true
if ! /usr/bin/xcodebuild -version >/dev/null 2>&1 && ! ls -d /Applications/Xcode*.app >/dev/null 2>&1; then
echo "WARNING: no full Xcode found — the xcframework/release steps need one in"
echo " /Applications, with its license accepted once: sudo xcodebuild -license accept"
fi
echo "OK: runner '$RUNNER_NAME' labels=$LABELS instance=$INSTANCE"