Files
punktfunk/docs-site/content/docs/ci.md
T
enricobuehler cba3ae48e2
apple / swift (push) Successful in 56s
ci / rust (push) Successful in 1m37s
ci / web (push) Successful in 31s
ci / docs-site (push) Successful in 40s
android / android (push) Successful in 3m19s
deb / build-publish (push) Failing after 1m9s
decky / build-publish (push) Successful in 22s
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 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 3s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m21s
ci / bench (push) Successful in 4m45s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 26s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Failing after 3m22s
docker / deploy-docs (push) Successful in 18s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 10m25s
docs: update README + docs site for public readiness
Refresh the README and documentation for public visitors:

- README: public-facing rewrite with accurate status for all four native
  clients (macOS, Linux, Windows, Android) and the Windows host.
- docs site: fix stale client status (Android is a full client, not a
  scaffold; Windows client is stage-1 complete + signed MSIX), add the
  missing Android client section, correct "which client" guidance.
- Windows host: corrected from "deferred/scoped" to implemented & shipping
  (NVIDIA-only, x64-only) across windows-host, roadmap, status,
  requirements, running-as-a-service, and the README.
- Remove internal infrastructure from public docs (box names, private IPs,
  SSH/token commands, deploy topology); rewrite status.md as a public
  project-status page; sanitize ci.md and implementation-plan.md.
- Update clients/android and clients/apple READMEs to current state.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 18:59:23 +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). The workflows live in .gitea/workflows/; they run across Linux and macOS runners and push a few images to the Gitea container registry.

Workflows

Workflow Trigger Runner What it does
ci.yml push to main, PRs Linux 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 Linux Builds + pushes the images below (latest + sha-<short> tags)
apple.yml push to main, PRs, manual macOS Rust core → PunktfunkCore.xcframeworkswift build + swift test in clients/apple
release.yml v* tags, manual macOS Production Apple builds: sandboxed macOS .dmg (Developer ID, notarized, stapled) attached to the Gitea release + macOS/iOS/tvOS archives uploaded to TestFlight
windows-msix.yml push to main, v* tags, manual Windows Builds the Windows client for x86_64/aarch64 and packages signed MSIX artifacts

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 a repo Actions secret holding a registry token (a PAT with write:package; the login username in docker.yml is the token owner, not the push actor).

Runners

  • Linux runner — runs the Rust/web/docs jobs (as docker containers) and the image build+push jobs.
  • macOS runner — an Apple-silicon Mac running macOS, 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 under ~/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.
  • Windows runner — builds and packages the native Windows client (MSIX) for the release matrix.

Re-provisioning is idempotent — re-running scripts/ci/setup-macos-runner.sh on the macOS runner with a fresh GITEA_RUNNER_TOKEN (org unom → Settings → Actions → Runners → Create new runner) re-registers it without manual cleanup.

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 the docs server and runs docker compose pull && up -d there over SSH, driven by a small set of deploy secrets (DEPLOY_HOST / DEPLOY_USER / DEPLOY_PORT / DEPLOY_SSH_KEY). A reverse proxy in front of that server serves the container as https://docs.punktfunk.unom.io. 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

  • macOS runner offline — check ~/ci/act-runner/runner.log on the runner; 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.