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>
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.xcframework → swift 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 asgitea-runner) provisioned byscripts/ci/setup-macos-runner.sh: rustup (+ both darwin targets for the universal xcframework), Node.js (host-mode runners execute JS actions vianodefrom PATH — nothing auto-provisions it), the runner binary in~/.local/bin, state under~/ci/act-runner/(config,.runnerregistration,runner.log), kept alive by theio.gitea.act_runnerroot 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 forxcodebuild -create-xcframework(CLT alone only coversswift build/test); ifxcode-selectstill points at CLT, the script auto-detects/Applications/Xcode*.appand bakes aDEVELOPER_DIRoverride into the daemon environment — noxcode-select -srequired. - 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.dmgon 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-erroruntil 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-stdviabuild-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.logon the runner; restart withsudo 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.ymlfails at the xcframework step — Xcode missing or unselected:sudo xcode-select -s /Applications/Xcode.app/Contents/Developerand 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 adocker login git.unom.ioif the org/registry isn't anonymously readable. - Stale builder image after toolchain/dep changes —
docker.ymlre-pushes it on everymainpush; a manualworkflow_dispatchofdocker.ymlforces a rebuild.