Files
openhuman/docs/ios/SETUP.md
T

7.2 KiB

iOS Client Setup

This document covers everything a developer needs to build, run, and test the OpenHuman iOS client.


Prerequisites

  • macOS 14+ with Xcode 15.4+
  • iOS 17+ physical device or simulator
  • Rust toolchain with aarch64-apple-ios target
  • pnpm (version pinned in root package.json)
  • Apple Developer account with a provisioning profile
rustup target add aarch64-apple-ios aarch64-apple-ios-sim

Initial setup

Run the helper script from the repo root. It calls tauri ios init with the correct working directory and prints next steps.

bash scripts/ios-init.sh

tauri ios init scaffolds app/src-tauri-mobile/gen/apple/. That directory is gitignored (it contains bundle-identifier-specific Xcode project files that differ per developer account).

Info.plist privacy keys

tauri ios init creates a generated Info.plist at:

app/src-tauri-mobile/gen/apple/<bundle-id>_iOS/Info.plist

scripts/ios-init.sh injects these privacy keys into the generated plist:

<key>NSCameraUsageDescription</key>
<string>OpenHuman uses the camera to scan the pairing QR code from your desktop.</string>

<key>NSMicrophoneUsageDescription</key>
<string>OpenHuman uses the microphone for push-to-talk voice messages.</string>

<key>NSSpeechRecognitionUsageDescription</key>
<string>OpenHuman uses on-device speech recognition to transcribe your voice messages.</string>

Development workflow

# Start the iOS dev build (hot-reload via Vite, deployed to simulator or device):
pnpm tauri:ios:dev

# From the repo root:
pnpm tauri:ios:dev

The tauri:ios:dev script uses @tauri-apps/cli@^2 directly (via npx --package), not the vendored CEF-aware CLI. The CEF CLI is only needed for the desktop build.

Set your development team in Xcode (generated project > Signing & Capabilities) before deploying to a physical device.


Production build

pnpm tauri:ios:build
# or from repo root:
pnpm tauri:ios:build

App Store Connect delivery

.github/workflows/ios-appstore.yml builds a signed iphoneos archive, exports an IPA, uploads the IPA to App Store Connect/TestFlight with altool, and stores the IPA + dSYMs as GitHub Actions artifacts.

Run it from GitHub Actions > iOS App Store. Inputs:

  • ref -- optional git ref to build.
  • build_number -- optional CFBundleVersion; defaults to github.run_number.
  • upload_to_app_store_connect -- set false for a signed archive/export dry run.

Required GitHub environment: App-Store.

Required secrets:

  • APPLE_TEAM_ID -- Apple Developer Team ID.
  • IOS_KEYCHAIN_PASSWORD -- temporary CI keychain password.
  • IOS_DISTRIBUTION_CERTIFICATE_BASE64 -- base64-encoded .p12 Apple Distribution certificate.
  • IOS_DISTRIBUTION_CERTIFICATE_PASSWORD -- password for that .p12.
  • IOS_APPSTORE_PROVISIONING_PROFILE_BASE64 -- base64-encoded App Store provisioning profile for com.tinyhumansai.openhuman.
  • APP_STORE_CONNECT_API_KEY_ID -- App Store Connect API key ID.
  • APP_STORE_CONNECT_ISSUER_ID -- App Store Connect issuer ID.
  • APP_STORE_CONNECT_API_PRIVATE_KEY_BASE64 -- base64-encoded AuthKey_<key id>.p8.

Local encoding helpers:

base64 -i ios_distribution.p12 | pbcopy
base64 -i OpenHuman_AppStore.mobileprovision | pbcopy
base64 -i AuthKey_XXXXXXXXXX.p8 | pbcopy

The workflow uploads a build to App Store Connect. It does not submit the build for App Review; that remains a deliberate App Store Connect action.

Local upload script

After downloading an App Store provisioning profile and App Store Connect API key, you can build/export/upload from this Mac:

TEAM_ID=XXXXXXXXXX \
IOS_APPSTORE_PROVISIONING_PROFILE_PATH=/path/to/OpenHuman_AppStore.mobileprovision \
ASC_KEY_ID=XXXXXXXXXX \
ASC_ISSUER_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
ASC_KEY_PATH=/path/to/AuthKey_XXXXXXXXXX.p8 \
UPLOAD=1 \
scripts/ios-appstore-upload.sh

Use UPLOAD=0 to stop after IPA export.

Updating without a new App Store build

iOS cannot self-update native code or replace the installed app binary outside App Store/TestFlight distribution. For OpenHuman, this means changes to Rust, Tauri plugins, native permissions, bundled frontend code, or the app shell need a new reviewed build.

Safe server-side updates include model/provider configuration, feature flags, prompt/content changes, remote data, and backend behavior that the shipped client already knows how to render. Be conservative with remote JavaScript or plugin-style features: Apple allows some software/content delivered outside the binary under specific rules, but it must stay within App Review limits and must not expose native platform APIs without permission.


Pairing flow

Desktop                              iOS
  |                                    |
  |-- Settings > Devices > "Pair"      |
  |-- devices_create_pairing RPC       |
  |   (backend issues channelId,       |
  |    pairingToken, sessionToken)      |
  |-- QR shown                         |
  |                    scan QR --------|
  |                    (extract cid,   |
  |                     pt, cpk, rpc?) |
  |                    iOS connects    |
  |                    to backend      |
  |                    tunnel:connect  |
  |                    (role:client,   |
  |                     channelId,     |
  |                     pairingToken)  |
  |                    backend returns |
  |                    iOS sessionToken|
  |                    X25519 handshake|
  |                    over tunnel     |
  |<-- DevicePaired event              |
  |-- device appears in Devices list   |

Transport selection (handled by TransportManager):

  1. LAN HTTP -- fast, zero-latency, requires same network.
  2. Socket.io tunnel -- E2E encrypted via XChaCha20-Poly1305 over X25519 key agreement.
  3. Cloud HTTP -- fallback when LAN and tunnel are unreachable.

Security notes

  • The tunnel backend is a blind forwarder. It never sees plaintext payloads.
  • pairingToken is single-use and hashed at rest on the backend.
  • sessionToken is per-peer, revocable from the desktop Devices panel.
  • X25519 key agreement runs on first connect; the derived symmetric key is stored in-memory for the session.
  • TODO (follow-up PR): migrate the iOS symmetric key to the iOS Keychain for persistence across app restarts without re-pairing.

Known limitations

  • Single backend instance only (no multi-region failover).
  • No APNs push notifications -- app must be foregrounded for real-time delivery.
  • Event-driven pairing detection on the desktop side uses 2-second polling until an SSE/socket event bridge lands.

CI

The .github/workflows/ios-compile.yml workflow runs as an iOS compile sanity check. It provides:

  • Hard gate: cargo check on the iOS target for app/src-tauri-mobile and a host-target check for packages/tauri-plugin-ptt.
  • Hard gate: TypeScript compile (pnpm compile).
  • Hard gate: iOS-related Vitest suites.

Full signed App Store builds run through .github/workflows/ios-appstore.yml.


Backend dependency

The tunnel transport requires tinyhumansai/backend#709 to be merged and deployed before end-to-end pairing works. The devices_create_pairing RPC will return a tunnel registration error until that backend is live.