Commit Graph

45 Commits

Author SHA1 Message Date
enricobuehler 7d7b5c8611 fix(ci/flatpak): cargo-sources generator needs python3-tomlkit, not toml
flatpak-cargo-generator.py (master) imports `tomlkit` + `aiohttp`; the workflow
installed `python3-toml`, so the "Generate offline cargo sources" step would fail
with ModuleNotFoundError. Install python3-tomlkit instead, and correct the same
note in build-flatpak.sh.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 01:47:09 +02:00
enricobuehler 5faf754b7f feat(packaging/flatpak,decky): Steam Deck client flatpak + plugin deploy + CI
Ship the punktfunk Linux client to the Steam Deck as a Flatpak — the only viable
SteamOS install path, since /usr is read-only and lacks libadwaita/SDL3 — and
publish both it and the Decky plugin through Gitea. Built and validated live on a
Steam Deck (SteamOS 3.7): bundle installs user-scope, all libs resolve, libavcodec
resolves to the codecs-extra HEVC build, devices=all for DualSense hidraw.

packaging/flatpak (new):
- io.unom.Punktfunk.yml on GNOME 50 / freedesktop-sdk 25.08. rust-stable//25.08
  (rustc 1.96 — the GTK4 chain needs >=1.92; the EOL GNOME-48/24.08 rust-stable at
  1.89 could not build it) + llvm20 (libclang for bindgen in ffmpeg-sys-next/sdl3-sys).
  HEVC libavcodec comes from the runtime's auto codecs-extra extension point (no
  app-side codec declaration). Bundled SDL3 3.4.10 (matches sdl3-sys 0.6.6+SDL-3.4.10).
  finish-args: wayland/fallback-x11, --device=all (GPU/VAAPI + evdev + hidraw — flatpak
  cannot bind /dev/hidrawN char devices via --filesystem), pulseaudio, network,
  ~/.config/punktfunk.
- metainfo.xml, desktop, square SVG icon, build-flatpak.sh (offline cargo-sources;
  on-Deck org.flatpak.Builder or CI), README.

clients/decky:
- add LICENSE (MIT), fix package.json license (BSD-3-Clause -> Apache-2.0 OR MIT),
  add scripts/{package.sh,deploy.sh} (the plugins dir is root-owned: stage to /tmp,
  sudo install, restart plugin_loader), align the launcher fallback to the real
  flatpak app id io.unom.Punktfunk, rewrite the install section.

.gitea/workflows:
- flatpak.yml: privileged Fedora container builds the bundle and publishes to the
  Gitea generic registry (+ release attachment on tags).
- decky.yml: pnpm build -> store-layout zip -> registry (stable latest/ URL for
  Decky "install from URL").

docs: packaging/README + packaging/flatpak/README.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 01:43:35 +02:00
enricobuehler b55b7d0ef0 feat(android): scaffold the native Android client (Rust-heavy JNI bridge)
Rust-heavy client model (like punktfunk-client-linux): a new cdylib crate
crates/punktfunk-android links punktfunk-core and exposes the JNI seam;
Kotlin (clients/android) owns only the Android-framework surface. Kotlin can't
import the C header the way Swift can, so the bridge is written in Rust to reuse
the Linux client's orchestration rather than re-port it.

- crates/punktfunk-android: JNI bridge — abiVersion/coreVersion native-link
  proof + session connect/close handle; plane pumps stubbed for M4 stage 1.
- clients/android: Gradle project — :app (Compose) + :kit (Android library with
  a cargo-ndk Exec task -> jniLibs). AGP 9.2 / Gradle 9.4.1 / Kotlin 2.3.21 /
  Compose BOM 2026.05.01 / compileSdk 37 / targetSdk 36 / minSdk 31, shipping
  arm64-v8a + x86_64. Phone + TV (leanback) installable. README rewritten.
- .gitea/workflows/android.yml: CI mirroring apple.yml on a Linux runner.
- punktfunk-core: switch rcgen to the ring backend so the whole quic tree is
  aws-lc-free (smaller client .so, cmake-free cross-compile; a win for all targets).

Validated on this box: :app:assembleDebug -> APK with both ABIs; emulator
first-light renders the bridge linked (core ABI v2) with logcat confirmation;
clippy -D warnings + cargo fmt clean; core tests green on the ring backend.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 01:38:35 +02:00
enricobuehler a318057aca fix(ci/apple): scope iOS/tvOS archive signing to the device SDK
A global PROVISIONING_PROFILE_SPECIFIER on the xcodebuild command line is
applied to every target in the graph, including the shared SwiftPM compiler-
plugin macros (OnceMacro/SwizzlingMacro/AssociationMacro). Those build for the
macOS host and reject a provisioning profile, so the iOS/tvOS device archives
failed at build-description time with "<macro> does not support provisioning
profiles". (The macOS archive is immune: its host-SDK macros carry
CODE_SIGNING_ALLOWED=NO, so the global specifier is silently ignored there.)

Move the signing settings into a generated -xcconfig and condition the profile
+ identity on the device SDK ([sdk=iphoneos*] / [sdk=appletvos*]). xcconfig
conditionals are honored and a command-line -xcconfig outranks target settings,
whereas a CLI "SETTING[sdk=..]=val" is mis-parsed — both verified via
xcodebuild -showBuildSettings against the real project. The profile now lands on
the app/framework slices only; the macosx-host macros get nothing.

macOS App Store archive is unchanged (already green; installer cert now present
on the runner). tvOS upload may still need tvOS on the App Store Connect record,
but that step is continue-on-error.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 12:58:23 +02:00
enricobuehler 16e8ec7b39 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
enricobuehler 3ca646edf4 fix(ci/release): skip Swift macro/plugin validation in archives
tvOS archive failed 'Macro AssociationMacro/SwizzlingMacro/OnceMacro must be
enabled before it can be used' — Xcode 15+ requires interactive trust for SPM
Swift macros (objc-runtime-tools, swift-once-macro via swiftui-navigation-
transitions), which a headless build can't grant. Add -skipMacroValidation
-skipPackagePluginValidation to all three archive commands so CI never hits the
trust prompt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 23:36:36 +00:00
enricobuehler 53595e0f87 feat(ci/release): add tvOS TestFlight build + use renamed iOS profile
tvOS is scaffolded (Punktfunk-tvOS target/scheme + build-xcframework BUILD_TVOS).
Wire it: install nightly + rust-src (tier-3 -Zbuild-std), build the xcframework
with BUILD_TVOS=1, and add a tvOS archive+export+upload step mirroring iOS
(manual signing with the 'Punktfunk tvOS App Store Distribution' profile, since
the App-Manager ASC key can't cloud-sign). Also point iOS at the renamed
'Punktfunk iOS App Store Distribution' profile. macOS App Store/TestFlight still
pending (needs App Sandbox entitlements). Needs tvOS on the App Store Connect
app record + the tvOS platform installed on the runner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 23:23:01 +00:00
enricobuehler c75586ebf2 fix(ci/release): quit Xcode before iOS build so it can't prune the profile
A running Xcode.app manages ~/Library/Developer/Xcode/UserData/Provisioning
Profiles/ and deletes manually-installed (unrecognized) distribution profiles —
which is why the App Store profile vanishes. Quit Xcode at the start of the iOS
step so the manually-installed 'Punktfunk App Store Distribution' profile
survives for manual signing; headless xcodebuild doesn't need the GUI app.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 22:02:44 +00:00
enricobuehler aac023cd3b fix(ci/release): iOS manual App Store signing (App-Manager key can't cloud-sign)
macOS Developer ID + notarize + DMG now works with the clean login-keychain
workflow. iOS export failed with 'Cloud signing permission error' — with
-allowProvisioningUpdates Xcode forces cloud-managed signing, which the
App-Manager-role ASC key can't authorize. Switch iOS to MANUAL signing with the
local (valid) Apple Distribution identity + the 'Punktfunk App Store
Distribution' provisioning profile; ASC key stays only for the upload. Profile
must be installed via Xcode -> Accounts -> Download Manual Profiles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 21:52:50 +00:00
enricobuehler b041070a98 refactor(ci/release): xcodebuild-native signing via login keychain
The runner now runs as a user LaunchAgent in the logged-in Aqua session, so it
uses the login keychain directly, where Developer ID Application + Apple
Distribution are installed and VALID (the missing WWDR intermediate — the real
root cause of the whole iOS saga — is now present). Delete all the throwaway-
keychain / secret-cert-import / raw-keychain-plumbing / Xcode-quit / diagnostic
machinery: macOS = archive-unsigned + a single Developer ID codesign + notarize/
DMG; iOS = standard xcodebuild archive + export with -allowProvisioningUpdates
(automatic signing manages the App Store cert + profile). Only ASC_API_KEY_*
secrets remain; DEVID_CERT_*/IOS_DIST_CERT_*/IOS_PROFILE_B64 no longer needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 21:42:47 +00:00
enricobuehler 0de83d31d4 diag(ci/release): sign iOS by identity hash + max-verbose codesign
The throwaway-keychain codesign still fails 'unable to build chain to self-signed
root / errSecInternalComponent' despite cert/chain/key all verifying. Sign by the
Apple Distribution identity's SHA-1 hash (eliminates name-matching ambiguity, a
known cause) and run codesign --verbose=4 + print valid/matching identities at
sign time, to surface the exact failure on the next run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 20:29:45 +00:00
enricobuehler e7ae45e148 fix(ci/release): re-set key partition list + stage full chain before iOS codesign
iOS codesign still failed with 'unable to build chain to self-signed root /
errSecInternalComponent' after the keychain re-assert. verify-cert proves the
chain is trusted, so this is the private-key ACL (errSecInternalComponent is
classically that) and/or codesign not finding the chain certs in the identity's
keychain. Right before the iOS codesign: re-run set-key-partition-list (re-grant
codesign access to the key) and import the WWDR G3 intermediate + Apple Root CA
into the throwaway keychain so the full leaf->WWDR->root chain is present there.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 20:22:27 +00:00
enricobuehler 50f2590750 fix(ci/release): re-assert keychain before the iOS codesign
The iOS archive SUCCEEDS now (raw-codesign path), but codesign failed with
'unable to build chain to self-signed root / errSecInternalComponent'. Cause:
xcodebuild archive (run in the same step, just before codesign) resets the user
keychain search list, so codesign can no longer find the WWDR intermediate that
lives only in the throwaway keychain. The macOS sign avoids this by running in a
separate step after its re-assert. Re-assert the search list + default keychain
(and unlock, via KEYCHAIN_PASS now exported to GITHUB_ENV, masked) immediately
before the iOS codesign.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 20:08:56 +00:00
enricobuehler c5268c56d7 feat(ci/release): iOS — raw codesign + altool upload (bypass xcodebuild)
xcodebuild's signing-identity selection enforces an online revocation/OCSP check
that excludes the freshly-minted Apple Distribution cert (find-identity -v drops
it) even though verify-cert confirms it's valid and codesign signs with it fine.
So sign iOS the same way as the macOS DMG: archive CODE_SIGNING_ALLOWED=NO, embed
the profile, raw 'codesign --keychain' with the profile's entitlements (extracted
via plutil), package the .ipa, and upload with 'xcrun altool --upload-app'. Drops
the xcodebuild manual-signing path entirely — no profile-dir install, no
Xcode-quit, no provisioning-profile discovery.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 19:53:14 +00:00
enricobuehler 5af63d0e9c fix(ci/release): quit Xcode.app so it stops pruning the iOS profile
Root cause of 'No profile matching Punktfunk App Store Distribution': the GUI
Xcode.app was running on the runner and actively manages
~/Library/Developer/Xcode/UserData/Provisioning Profiles, pruning our
manually-installed App Store profile from the exact dir xcodebuild reads, right
before signing (the legacy ~/Library/MobileDevice copy survives but Xcode 26's
xcodebuild doesn't read it). Quit Xcode.app at the top of the iOS signing block;
xcodebuild runs independently and headless CI doesn't need the GUI app.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 19:25:33 +00:00
enricobuehler af9a56b86f bench(ci): report-only regression harness — Tier-1/2 in CI + Tier-3 GPU runner
- scripts/bench/compare.py: diff criterion medians (target/criterion/**/estimates.json) vs a
  committed baseline, print a markdown table to the job summary, flag >threshold regressions, always
  exit 0 (shared CI hardware is too noisy to gate on). --update rewrites the baseline.
- ci.yml `bench` job: runs Tier-1 (criterion) + Tier-2 (loss-harness FEC recovery) GPU-free in the
  rust-ci container, then compare.py — report-only visibility per push/PR.
- scripts/bench/gpu-stream.sh + bench-gpu.yml: Tier-3 real pipeline (virtual output → zero-copy →
  NVENC → punktfunk/1 → reassemble) on a self-hosted GPU runner; captures encode_us/tx_mbps/
  send_dropped + client capture→reassembled latency, compares to gpu-baseline.json (20% threshold).
  Needs the dev box registered as a `[self-hosted, gpu]` act_runner (one-time, see the workflow
  header) — the dedicated hardware makes its absolute baseline meaningful, unlike shared CI.
- baseline.json: dev-box Tier-1 numbers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 19:24:52 +00:00
enricobuehler 6982a2d3d4 diag(ci/release): dump provisioning-profile dirs around the iOS archive
iOS manual signing fails 'No profile matching Punktfunk App Store Distribution'
despite the profile being installed (content verified: right name/team/iOS/app-id).
The profile is in ~/Library/MobileDevice but Xcode 26 reads
~/Library/Developer/Xcode/UserData/Provisioning Profiles, which is empty. Print
both dirs before the archive and again at failure to confirm whether Xcode
regenerates/prunes the UserData copy during the build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 19:23:16 +00:00
enricobuehler dd7e9ec0a8 fix(ci/release): robust iOS provisioning-profile extraction + diagnostics
The profile-name/UUID read used 'security cms -D ... || true' which masked a
failed decode, then PlistBuddy printed 'Error Reading File' to stdout and that
got captured as the UUID, producing a garbage cp path. Now: check the extracted
plist is non-empty, fall back to 'openssl smime' if 'security cms' fails,
validate the UUID is actually hex+dashes, and print the decoded byte count +
decoder stderr + first bytes so a bad IOS_PROFILE_B64 is obvious in-log. Still
non-fatal (skips iOS, never blocks the macOS release).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 19:05:35 +00:00
enricobuehler 07df27afe1 feat(ci/release): iOS App Store manual distribution signing + profile
Automatic signing during the iOS archive resolved to App *Development* (wanted
an Apple Development cert + tried to revoke the account's orphaned one, and no
dev profile) — wrong for App Store. Switch to MANUAL distribution signing:
import an App Store provisioning profile from IOS_PROFILE_B64, read its
Name/UUID, install it, and archive with CODE_SIGN_STYLE=Manual + Apple
Distribution + that profile; export with manual signingStyle +
provisioningProfiles map. Step self-skips until IOS_PROFILE_B64 is set.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 17:09:46 +00:00
enricobuehler d78cf506a2 fix(ci/release): gate iOS signing on matching identity, not find-identity -v
The Apple Distribution identity has its key + intermediate + valid dates (it's
in 'Matching identities') but stayed out of 'Valid identities only' — a trust
strictness (most likely a pending online revocation check on an hour-old cert)
that codesign/xcodebuild do NOT enforce. Gate the iOS step on the MATCHING list
so the archive actually attempts signing, and print 'security verify-cert -p
codeSign' in the import step so the exact trust verdict shows if it still balks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 16:30:57 +00:00
enricobuehler e97385a9be fix(ci/release): retry Apple intermediate fetch + chain/clock diagnostic
The iOS Apple Distribution identity imported WITH its private key (it's a
'Matching identity') but was dropped from find-identity -v — i.e. an untrusted
chain: the WWDR G3 intermediate it chains through didn't land, while Developer
ID's DeveloperIDG2CA did. The fetch was a single 'curl || warn' with no retry, so
a transient miss silently breaks iOS only. Retry each intermediate 3x, and print
the runner UTC date + whether the WWDR intermediate is present, to separate a
chain miss from the cert's notBefore being ahead of the runner clock.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 16:22:32 +00:00
enricobuehler 45dd1232e0 ci+docs: Fedora 44 RPM channel + reproducible Fedora KDE host guide
- docker.yml: build the punktfunk-fedora44-rpm builder image (parameterized Dockerfile,
  FEDORA_VERSION=44) alongside the F43/Bazzite one.
- rpm.yml: matrix the build/publish over both channels — fedora-fedora-rpm→bazzite (F43,
  libavcodec.so.61) and fedora44-rpm→fedora-44 (F44, libavcodec.so.62). fail-fast:false so one
  channel's break doesn't sink the other. (Bootstrap: the F44 builder image must be pushed by
  docker.yml once before rpm.yml's fedora-44 job can pull it — same dance as the other images.)
- fedora-kde.md: rewrite as the reproducible RPM-install guide validated live on a Fedora 44
  KDE box (RTX 4090): RPM Fusion + akmod-nvidia + the ffmpeg-free→RPM-Fusion swap for NVENC +
  Secure Boot MOK enroll; the fedora-44 dnf repo + `dnf install punktfunk`; and the headless
  punktfunk-kde-session.service (kwin --virtual with NO_PERMISSION_CHECKS — an interactive
  Plasma session won't hand its privileged zkde_screencast protocol to an external client).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 16:20:40 +00:00
enricobuehler 985c9dc9db fix(ci/release): self-diagnosing iOS cert import + non-fatal validity gate
The iOS Apple Distribution cert imported (1 identity imported) but never
appeared in find-identity -v, and the iOS step then silently skipped. Make the
import step explain itself without exposing secrets or blocking the macOS
release: print secret byte-lengths + decoded p12 size + import rc, strip
stray whitespace/newlines before base64 -d, and after the partition-list warn
(not fail) with the likely cause + an incl-invalid identity list when the iOS
secret is set but yields no valid Apple Distribution identity. The shared import
step must not hard-fail on an iOS-cert problem — that would also block the
proven macOS DMG path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 16:14:12 +00:00
enricobuehler 00739a273b feat(ci/release): wire iOS App Store signing via an Apple Distribution secret
deb / build-publish (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Failing after 7s
rpm / build-publish (push) Has been cancelled
release / apple (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
Prepares the iOS/TestFlight path. The runner has the iOS 26.5 SDK but no
signing identities, so import an Apple Distribution cert+key from
IOS_DIST_CERT_P12_B64 / IOS_DIST_CERT_PASSWORD into the same throwaway keychain
(the WWDR intermediates already fetched chain it). The iOS archive uses
automatic signing (-allowProvisioningUpdates + the ASC key creates/downloads the
App Store profile against the present cert, so no keychain-write that would hit
the macOS -61). Re-assert the keychain on the search list like the macOS sign
step. Until the secret is set the step self-skips with a warning, so it stays
green. Still needs an App Store Connect app record for io.unom.punktfunk to
upload.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 15:09:56 +00:00
enricobuehler df85e93510 fix(ci/release): skip iOS archive cleanly when the iOS SDK is absent
The macOS Developer ID DMG path is green (signed + notarized + stapled). The
iOS/TestFlight step (already best-effort + continue-on-error) was failing on
this runner with 'iOS 26.5 is not installed' — the iOS platform SDK is a
separate Xcode component that isn't installed. Guard the step on
`xcodebuild -showsdks | grep iphoneos` and exit 0 with a warning when it's
missing, so runs are unambiguously green. Install on the runner with
`xcodebuild -downloadPlatform iOS` when iOS goes live.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 14:51:09 +00:00
enricobuehler ba6e4e9bbb fix(ci/release): scope codesign to the throwaway keychain (--keychain)
codesign --sign 'Developer ID Application' reported 'no identity found' even
though the import step's find-identity saw it: the bare lookup relies on the
default keychain search list, which doesn't reliably carry the throwaway
keychain across steps on this runner. Re-assert the search list + default
keychain in the signing step and pass --keychain "$KEYCHAIN" so the identity
search is scoped to it (it stays unlocked with a codesign-allowed partition
list from the import step, so no password is needed). Adds a find-identity
diagnostic right before signing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 14:43:33 +00:00
enricobuehler ae6bdc7c6c fix(ci/release): archive unsigned + codesign Developer ID directly
xcodebuild's archive gate demands a provisioning profile for the app's
keychain-access-groups entitlement (the 'Keychain Sharing' capability) under
both automatic AND manual signing — even though a Developer ID app honours that
team-prefixed entitlement at runtime with no profile. So manual signing just
traded the -61 keychain error for 'requires a provisioning profile'.

Sidestep the gate: archive with CODE_SIGNING_ALLOWED=NO, then codesign the app
bundle directly with the Developer ID identity, hardened runtime and a secure
timestamp, applying the entitlements via --entitlements (with $(AppIdentifierPrefix)
resolved to the team prefix, which codesign won't expand). Safe because the
bundle is a single statically-linked binary — static PunktfunkCore.xcframework,
SPM static products, macOS 14 target, no Embed-Frameworks phase — so there is no
nested code to sign inside-out. No Apple Developer portal profile or new secret
needed. iOS App Store path unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 14:35:16 +00:00
enricobuehler 8b008c7da6 fix(ci/release): sign the macOS archive with Developer ID, not auto dev signing
The cert import now yields a valid 'Developer ID Application' identity, but
the macOS `xcodebuild archive` step still inherited the project's automatic
'Apple Development' signing via -allowProvisioningUpdates. That made Xcode try
to mint an Apple Development cert (install fails in the CI keychain,
DVTSecErrorDomain -61 'Write permissions error') and locate a 'Mac App
Development' provisioning profile for io.unom.punktfunk (none exists) —
** ARCHIVE FAILED ** before signing even happened.

A Developer ID DMG needs neither: pin CODE_SIGN_STYLE=Manual + the Developer ID
identity + no profile, mirroring what the export step already does. The app is
non-sandboxed and its only entitlement (keychain-access-groups, team-prefixed)
is authorized by the Developer ID team, so no provisioning profile is required.
ENABLE_HARDENED_RUNTIME=YES is already set, so notarization stays happy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 13:46:00 +00:00
enricobuehler 56ebf90967 fix(ci): install ca-certificates in the bun web/docs-site jobs
The oven/bun:1 image is Debian-slim and ships no CA bundle, so
actions/checkout's git-over-HTTPS fetch died with 'Problem with the SSL
CA cert (path? access rights?)' — curl error 77 (no CA bundle file),
not an untrusted cert; git.unom.io serves a public Let's Encrypt cert.
The rust/deb/rpm builder images already install ca-certificates; do the
same in the two slim bun jobs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 13:36:22 +00:00
enricobuehler 7bda3d8059 ci(packaging): punktfunk-client .deb + RPM subpackage
Hook the Linux client into the existing packaging CI:

- deb.yml builds both binaries and publishes punktfunk-host AND
  punktfunk-client to the Gitea apt registry; new
  packaging/debian/build-client-deb.sh mirrors the host script
  (shlibdeps auto-Depends — GTK4/libadwaita/SDL3/FFmpeg/PipeWire
  sonames; no NVIDIA filter, the client links no CUDA). Built and
  inspected locally on Ubuntu 26.04.
- punktfunk.spec gains a "client" subpackage (binary + desktop entry +
  udev rule); rpm.yml's publish loop picks it up unchanged.
- New shared assets: packaging/linux/io.unom.Punktfunk.desktop and
  scripts/70-punktfunk-client.rules — DualSense hidraw uaccess (USB +
  Bluetooth, steam-devices style) so SDL's HIDAPI driver gets
  touchpad/motion/lightbar/triggers instead of degrading to evdev.
- Builder images learn the client link deps (rust-ci already had
  them; fedora-rpm adds gtk4/libadwaita/SDL3-devel) with idempotent
  install steps in deb.yml/rpm.yml since jobs run against the
  previous push's image.

Workspace check CI (build/clippy/test) already covers the crate since
a601022.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 23:18:12 +00:00
enricobuehler 1fd4524510 fix(ci): POSIX shell in deb/rpm Version step (dash "Bad substitution")
deb.yml runs in the Ubuntu rust-ci image whose /bin/sh is dash, where the bash
substring `${GITHUB_SHA::8}` is a "Bad substitution" — the deb build failed at the
Version step every run. Compute the short SHA with `cut` instead. (rpm.yml ran fine
because the Fedora image's /bin/sh is bash, but fix it the same way for robustness.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 22:48:12 +00:00
enricobuehler eccf93e4f4 ci(rpm): publish punktfunk-host RPM to the Gitea registry (Bazzite)
Mirrors the apt pipeline for Fedora Atomic / Bazzite. New `rpm` workflow builds
the host RPM in a Fedora 43 builder image (ci/fedora-rpm.Dockerfile — matches
Bazzite's libavcodec.so.61, with a self-contained 16-symbol libcuda link stub so
no NVIDIA packages are needed in CI) and uploads to Gitea's public RPM registry
(group "bazzite") on every main push (rolling 0.0.1-0.ciN.<sha>) and v* tag
(clean X.Y.Z-1). Bazzite hosts then track it with `rpm-ostree upgrade`.

- packaging/rpm/build-rpm.sh: git-archive tarball + rpmbuild (--nodeps, since the
  toolchain is rustup + dnf, not RPMs); copies to dist/, asserts no cuda/nvidia leak.
- punktfunk.spec: overridable pf_version/pf_release for CI snapshots; exclude
  libcuda.so from auto-Requires (NVENC/EGL come from the driver, out of band) —
  same NVIDIA filter as the .deb; fix a bogus changelog weekday.
- docker.yml builds+pushes the new fedora-rpm image; packaging README + rpm/README
  document the rpm-ostree install/update path (recommended option).

Builder image seeded to the registry so rpm.yml's first run finds it. RPM build +
clean-Requires verified locally in the image (libavcodec.so.61 / libavutil.so.59,
no cuda).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 21:32:46 +00:00
enricobuehler ba65febe84 ci(deb): publish punktfunk-host .deb to the Gitea apt registry
Wires up the half-built Debian packaging: build-deb.sh existed but nothing
invoked or published it. Adds a `deb` workflow that builds the release host in
the Ubuntu 26.04 rust-ci image, packages it (dpkg-shlibdeps-resolved Depends,
NVIDIA driver filtered out), and uploads to Gitea's public Debian registry on
every main push (rolling 0.0.1~ciN.<sha>) and v* tag (clean X.Y.Z). Ubuntu hosts
then track it with `apt update && apt upgrade`.

Also: box-setup docs (packaging/debian/README.md), a pointer from the packaging
README, ignore dist/, and drop backticks from the package Description (the
unquoted control heredoc ran them as a command substitution).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 21:14:40 +00:00
enricobuehler a601022e79 ci: GTK4/libadwaita/SDL3 dev packages for punktfunk-client-linux
Baked into the rust-ci image, plus an idempotent apt step in the rust
job itself — ci.yml runs against the previous push's image (docker.yml
bootstrap note), so the image change alone would leave this push and
the next one red.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 20:17:54 +00:00
enricobuehler 51ae1ac8fe ci(release): make the throwaway keychain the default keychain
release / apple (push) Failing after 3m33s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
exportArchive's signing lookup consults the default keychain; search
list membership alone leaves the (valid) identity invisible to it.
Restored to login.keychain in cleanup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 16:06:04 +00:00
enricobuehler 9c91cb1684 ci(release): manual Developer ID export — cloud signing has no fallback
With -allowProvisioningUpdates, exportArchive prefers cloud-managed
Developer ID signing; the App-Manager API key can't ("Cloud signing
permission error") and the valid local identity is never tried.
signingStyle=manual + explicit signingCertificate, cloud flags off
this step (archive keeps them for profile fetch).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 16:01:12 +00:00
enricobuehler b5fc25d62a ci(release): stage Apple intermediate CAs in the signing keychain
Fresh boxes lack the Developer ID / WWDR intermediates; without the
issuing chain the imported identity is invalid and xcodebuild says
"No signing certificate Developer ID Application found".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 15:55:09 +00:00
enricobuehler c26a747b9e ci(release): TestFlight upload best-effort until the ASC app record exists
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 14:39:19 +00:00
enricobuehler a32314e9a6 feat(release): production Apple builds — notarized macOS dmg + iOS TestFlight
release.yml (v* tags / dispatch, macos-arm64 runner): universal mac +
iOS xcframework -> xcodebuild archive -> Developer ID export ->
notarytool + staple -> dmg on the Gitea release; iOS archive uploads
to TestFlight (app-store-connect/upload). Per-run throwaway keychain;
ASC API key authenticates notarization, upload, and automatic-signing
profile fetch. macOS App Store lane deferred (needs App Sandbox);
tvOS deferred (tier-3 Rust targets).

All app targets now share bundle ID io.unom.punktfunk — ONE App Store
listing with universal purchase (decided pre-submission; effectively
unchangeable after). ITSAppUsesNonExemptEncryption=false declared
(standard-algorithm AES-GCM, exempt).

build-xcframework.sh resolves Apple toolchains itself: cargo's HOST
artifacts (proc-macros, build scripts) are loaded by the running OS,
and a newer-than-OS beta Xcode ld emits LINKEDIT layouts dyld rejects
("mis-aligned LINKEDIT string pool" -> misleading E0463) — so prefer
a non-beta Xcode for everything, fall back to CLT for mac-only slices
(env untouched: an explicit DEVELOPER_DIR=<CLT> trips xcrun's license
check), refuse iOS/tvOS without a real Xcode (CLT has no iOS SDK).
The runner plist no longer injects DEVELOPER_DIR for the same reason.

punktfunk_Logo.icon: dropped the Xcode-27-beta-only Icon Composer
features (refractivity, specular-location) — 26.5's actool crashes on
them, and store builds must use release Xcode. Visual delta is the
refraction/specular nuance only; re-author when 27 ships.

Validated on home-mac-mini-1 with Xcode 26.5: mac+iOS xcframework
slices, unified bundle IDs, signing-free app build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 14:34:45 +00:00
enricobuehler 027d6bc7c7 fix(ci): deploy target is unom-1, not home-main-2
website/cms deploy to the unom-1 DMZ VM (192.168.50.50) — the
website README's home-main-2 mention is stale. Caddy upstream fixed
in unom/reverse-proxy 6ae79b8, firewall port in unom/infra 9670aa8.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 13:15:16 +00:00
enricobuehler a472bc8745 feat(ci): deploy the docs site to home-main-2 (docs.punktfunk.unom.io)
docker.yml gains a deploy-docs job after the image pushes: scp
compose.production.yml to ~/punktfunk-docs on home-main-2, then
docker compose pull + up over SSH — the unom/website / unom/cms
deploy pattern, same DEPLOY_* secret set (unom-ci-deploy key). Docs
bind host port 3220; the docs.punktfunk.unom.io vhost lives in
unom/reverse-proxy (306d9c0).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 13:03:16 +00:00
enricobuehler 4aed324850 fix(ci): match real runner labels + survivable Mac runner daemon
runs-on: ubuntu-24.04 (the label the existing Linux runner actually
advertises — ubuntu-latest queued forever). Mac runner: strip the
docker:// default labels generate-config seeds (they override the
host-mode registration labels and make the daemon demand a Docker
engine), and ship the service as a root LaunchDaemon — macOS Local
Network privacy silently blocks LAN dials from unbundled CLI binaries
in gui/user launchd domains ("no route to host"), system daemons are
exempt. Without sudo the script leaves an interim nohup daemon. CI
surface documented in CLAUDE.md + docs-site ci.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 12:40:36 +00:00
enricobuehler a5ba9d9459 feat(ci): Gitea Actions — dockerized web/docs/rust-ci images, Apple client CI, Mac runner
Three workflows: ci.yml (Rust workspace inside the punktfunk-rust-ci
builder image + web/docs-site build+typecheck), docker.yml (build+push
punktfunk-web, punktfunk-docs, punktfunk-rust-ci to git.unom.io — host
and native clients stay un-dockerized by design), apple.yml (host-mode
macos-arm64 runner: Rust core -> PunktfunkCore.xcframework ->
swift build + swift test).

ci/rust-ci.Dockerfile: Ubuntu 26.04 with the workspace's link deps
(FFmpeg 8, PipeWire, Opus, GL/EGL/GBM, xkbcommon, libcuda via the
580-server userspace as a link stub) + pinned rustup + node for the JS
actions. Verified end to end in-container: build, 141/141 tests, C ABI
harness; all three images seeded to the registry manually.

scripts/ci/setup-macos-runner.sh provisions the Mac (rustup + darwin
targets, Node tarball, gitea-runner 1.0.8 host mode, LaunchAgent with
DEVELOPER_DIR auto-detect for sudo-free Xcode selection). Docs in
docs-site/content/docs/ci.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 12:28:13 +00:00
enricobuehler 8cceb93541 rename: lumen → punktfunk, everywhere
Full project rename, decided 2026-06-10:
- Crates/binaries: punktfunk-core / punktfunk-host / punktfunk-client-rs.
- C ABI: punktfunk_* symbols, Punktfunk* types, include/punktfunk_core.h,
  PUNKTFUNK_FEATURE_QUIC guard (header regenerated; cbindgen renames updated, incl.
  PUNKTFUNK_BTN_*/PUNKTFUNK_AXIS_* wire constants).
- Protocol: punktfunk/1 — control-plane magic LMN1 → PKF1, nonce salt lmn1 → pkf1.
  WIRE BREAK: clients must be rebuilt from this revision.
- Env knobs: PUNKTFUNK_VIDEO_SOURCE / PUNKTFUNK_COMPOSITOR / PUNKTFUNK_ZEROCOPY / ….
- Host config dir: ~/.config/punktfunk (the box's dir was migrated in place — the
  persistent identity is unchanged, pinned fingerprints stay valid).
- Swift package: PunktfunkKit + PunktfunkCore.xcframework + PunktfunkConnection
  (Sources/PunktfunkClient app + tests renamed with it); build-xcframework.sh updated.
- scripts/: 60-punktfunk.rules, punktfunk-host.service; OpenAPI doc regenerated.

Also: scripts/headless/run-headless-kde.sh — full headless Plasma bringup. Root cause of
"desktop but no apps/settings" over the stream: plasmashell launched without
XDG_MENU_PREFIX=plasma-, so the launcher resolved a nonexistent applications.menu and
rendered an empty menu. The script sets the complete KDE session env (menu prefix,
KDE_FULL_SESSION, session version) and rebuilds ksycoca before starting plasmashell.

Gate: 97/97 tests, clippy -D warnings (both feature sets), fmt, C-ABI harness PASS,
zero lumen references left outside .git.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:11:59 +00:00
enricobuehler a988909196 feat: M1 lumen-core (FEC/crypto/packet/session + C ABI) and workspace scaffold
Ground-up low-latency streaming stack per docs/implementation-plan.md. M1 is
complete and tested; Linux host backends are cfg-gated stubs to be filled in on
real hardware (M0/M2).

lumen-core (built + tested on macOS/aarch64 — 21 tests):
- fec: ErasureCoder over GF(2^8) (reed-solomon-erasure, Moonlight-compatible)
  and GF(2^16) Leopard-RS (reed-solomon-simd, the >1 Gbps wall-breaker); proptested
- packet: zero-copy #[repr(C)] framing, multi-block, FEC-aware reassembly
- crypto: AES-128-GCM with per-direction nonce salts + sequence-as-AAD
- session: host submit / client poll hot paths + input; loopback & UDP transports
- abi: opaque handles, versioned LumenConfig, panic guards; cbindgen-generated header
- acceptance: Rust loopback+proptest and a C harness that links the staticlib

Scaffold (compiles green on all platforms): lumen-host (vdisplay/capture/encode/
inject/web/pipeline seams under cfg(linux)), lumen-client-rs, tools/{loss-harness,
latency-probe}, Apple/Android client stubs, Gitea CI, docs.

Hardened against a multi-agent adversarial review (13 verified findings fixed,
regression-tested): reassembler memory-DoS bounds + block-consistency validation,
GCM nonce-reuse direction separation, ABI struct_size guard + range checks, FEC
shard-length guards, shard_payload datagram bound, key zeroization + Debug redaction.

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