Files
enricobuehler b140cd6837
ci / bench (push) Successful in 1m33s
apple / swift (push) Successful in 1m15s
ci / web (push) Successful in 31s
ci / docs-site (push) Successful in 30s
ci / rust (push) Successful in 2m5s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 3s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 18s
deb / build-publish (push) Successful in 2m1s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 5m5s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 5m7s
docker / deploy-docs (push) Successful in 17s
feat(apple/macos): App Sandbox + entitlements, wire Mac App Store TestFlight
The Mac App Store requires App Sandbox, which the macOS app didn't declare.
App Sandbox is macOS-only (invalid on iOS/tvOS, fails upload validation), so
the macOS target now uses a dedicated Config/Punktfunk-macOS.entitlements while
iOS/tvOS keep the shared Config/Punktfunk.entitlements (unchanged). The single
macOS app is sandboxed for BOTH channels — the Developer ID DMG is codesigned
with the same file — so the local build equals what App Store users get.

Entitlement set (verified against the code + Apple docs):
- app-sandbox, network.client.
- network.server: NOT optional despite the client being outbound-only — the
  sandbox gates the bind() syscall as network-bind, and quinn (quic.rs) + the
  raw-UDP plane (transport/udp.rs) both bind explicitly, so host->client
  datagrams never arrive without it (the classic QUIC-under-sandbox trap).
- device.audio-input (mic uplink), device.bluetooth + device.usb (Xbox/DualSense
  controllers over BT/USB via GameController), keychain-access-groups (existing).
Omitted: device.hid (undocumented), files.user-selected.* (no pickers),
networking.multicast (Bonjour browse is exempt; requesting it breaks signing).

CI (release.yml): add a macOS App Store archive+upload-to-TestFlight step
mirroring the iOS lane (manual Apple Distribution signing + the 'Punktfunk macOS
App Store Distribution' profile, app-store-connect/upload, installer-signed pkg),
continue-on-error until the portal prereqs exist; point the Developer ID DMG
codesign at the sandboxed entitlements. Docs (ci.md) + clients/apple README
updated; the runner additionally needs the macOS platform on the App Store
Connect record + the '3rd Party Mac Developer Installer' cert.

Verified: signed Debug build embeds exactly the intended entitlements
(codesign -d --entitlements), swift build green against the rebuilt xcframework.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 02:39:06 +02:00

7.7 KiB

title, description
title description
CI & Docker Gitea Actions setup — workflows, the dockerized pieces, and the runners.

CI runs on Gitea Actions (git.unom.io, org unom). Three workflows in .gitea/workflows/, two runners, three images in the Gitea container registry.

Workflows

Workflow Trigger Runner What it does
ci.yml push to main, PRs ubuntu-24.04 Rust workspace (fmt · clippy -D warnings · build · test · C-ABI harness · generated-header drift) inside the punktfunk-rust-ci image; web/ and docs-site/ build + typecheck in oven/bun:1
docker.yml push to main, v* tags, manual ubuntu-24.04 Builds + pushes the three images below (latest + sha-<short> tags)
apple.yml push to main, PRs, manual macos-arm64 Rust core → PunktfunkCore.xcframeworkswift build + swift test in clients/apple
release.yml v* tags, manual macos-arm64 Production Apple builds: sandboxed macOS .dmg (Developer ID, notarized, stapled) attached to the Gitea release + macOS/iOS/tvOS archives uploaded to TestFlight

Dockerized pieces

The host and the native clients are intentionally not containerized (the host needs the GPU/compositor stack of the box it runs on). What is:

Image Source Notes
git.unom.io/unom/punktfunk-web web/Dockerfile (repo-root context — orval needs docs/api/openapi.json) Nitro bun bundle; PORT (3000) and PUNKTFUNK_MGMT_URL env at runtime
git.unom.io/unom/punktfunk-docs docs-site/Dockerfile This site; PORT (3000)
git.unom.io/unom/punktfunk-rust-ci ci/rust-ci.Dockerfile Ubuntu 26.04 + FFmpeg 8/PipeWire/GL/GBM dev libs + a libcuda link stub (driver userspace, no kernel module) + pinned rustup — the container ci.yml's Rust job runs in

Registry pushes authenticate with the repo Actions secret REGISTRY_TOKEN (a PAT with write:package; the login username in docker.yml is the token owner, not the push actor).

Runners

  • ubuntu-24.04 — the pre-existing Linux runner; runs the Rust/web/docs jobs (as docker containers) and the image build+push jobs.
  • macos-arm64home-mac-mini-1 (M-series, macOS 26), a host-mode act_runner (upstream now ships it as gitea-runner) provisioned by scripts/ci/setup-macos-runner.sh: rustup (+ both darwin targets for the universal xcframework), Node.js (host-mode runners execute JS actions via node from PATH — nothing auto-provisions it), the runner binary in ~/.local/bin, state in ~/ci/act-runner/ (config, .runner registration, runner.log), kept alive by the io.gitea.act_runner root LaunchDaemon — it cannot be a user LaunchAgent: macOS Local Network privacy silently blocks LAN dials ("no route to host") from unbundled CLI binaries in gui/user launchd domains, while system daemons are exempt. Needs full Xcode for xcodebuild -create-xcframework (CLT alone only covers swift build/test); if xcode-select still points at CLT, the script auto-detects /Applications/Xcode*.app and bakes a DEVELOPER_DIR override into the daemon environment — no xcode-select -s required.

Re-provisioning (idempotent) or first-time registration from a dev box:

# token: org unom → Settings → Actions → Runners → Create new runner
ssh enricobuehler@192.168.1.135 GITEA_RUNNER_TOKEN=<token> bash -s \
    < scripts/ci/setup-macos-runner.sh

Apple releases

release.yml produces the production client builds on the Mac runner. All three app targets share the bundle ID io.unom.punktfunk (one App Store listing, universal purchase — effectively unchangeable after first submission). Signing is not secret-based: the runner uses its login keychain directly, so install the Developer ID Application, Apple Distribution, and (for the Mac App Store .pkg) 3rd Party Mac Developer Installer identities once via Xcode, with the WWDR intermediate present so they show as valid. The only secrets are ASC_API_KEY_P8/ASC_API_KEY_ID/ASC_API_ISSUER_ID (App Store Connect API key — notarization + TestFlight upload). Per-platform state:

  • macOS (Developer ID) — sandboxed app (Config/Punktfunk-macOS.entitlements) → export → notarytool → stapled .dmg on the Gitea release.
  • macOS (App Store) — manual-signed archive (Apple Distribution + the Punktfunk macOS App Store Distribution profile) → upload to TestFlight. App Sandbox is mandatory here and is now declared (app-sandbox + network client/server + audio-input + bluetooth/usb). Prereqs (one-time, Apple portal): add the macOS platform to the App Store Connect app record (universal purchase), install the Mac App Store distribution profile + the installer cert above. continue-on-error until those exist.
  • iOS — archive + upload to TestFlight (method: app-store-connect, destination: upload). Crypto is declared exempt (ITSAppUsesNonExemptEncryption, Config/Info.plist) so builds don't stall on the compliance question.
  • tvOS — archive + upload to TestFlight (Rust core built from tier-3 targets, nightly -Zbuild-std via build-xcframework.sh).

Each macOS target uses its own entitlements: Config/Punktfunk-macOS.entitlements (App Sandbox is macOS-only) for the macOS app, and the shared Config/Punktfunk.entitlements (keychain-access-groups only) for iOS/tvOS — com.apple.security.app-sandbox is invalid on iOS/tvOS and would fail upload validation.

The runner needs a release (non-beta) Xcode — App Store processing rejects beta-SDK builds, and a beta is unusable for the Rust side too: a newer-than-OS ld emits dylibs the running dyld rejects ("mis-aligned LINKEDIT string pool"), killing every proc-macro build with a misleading E0463 can't find crate. build-xcframework.sh therefore resolves toolchains itself: non-beta Xcode for everything; with only CLT + a beta present it builds macOS slices against CLT (packaging via any Xcode — -create-xcframework does no linking) and refuses iOS/tvOS slices (CLT has no iOS SDK).

Deployment

docker.yml's deploy-docs job ships this docs site after every image push: it syncs compose.production.yml to ~/punktfunk-docs on unom-1 (the DMZ services VM website and cms deploy to) and runs docker compose pull && up -d there over SSH (same pattern and secret set as unom/website: DEPLOY_HOST / DEPLOY_USER / DEPLOY_PORT / DEPLOY_SSH_KEY, the unom-ci-deploy key). The container binds host port 3220; Caddy on home-reverse-proxy-1 serves it as https://docs.punktfunk.unom.io (vhost in unom/reverse-proxy, UniFi firewall allowlist Caddy→unom-1:3220 in unom/infra proxmox/unom-1). The host and the web console are NOT deployed — the console fronts a punktfunk host's management API on whatever box runs the host.

Troubleshooting

  • Mac runner offlinessh <mac> tail -50 '~/ci/act-runner/runner.log'; restart with sudo launchctl kickstart -k system/io.gitea.act_runner. "no route to host" in the log means the daemon is running in a gui/user domain again — see the Local Network note above.
  • apple.yml fails at the xcframework step — Xcode missing or unselected: sudo xcode-select -s /Applications/Xcode.app/Contents/Developer and accept the license (sudo xcodebuild -license accept), then re-run.
  • Rust job can't pull punktfunk-rust-ci — the runner host's docker daemon needs a docker login git.unom.io if the org/registry isn't anonymously readable.
  • Stale builder image after toolchain/dep changesdocker.yml re-pushes it on every main push; a manual workflow_dispatch of docker.yml forces a rebuild.