field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

commit 43f5bedb974a06b0e2da1f24aeaf0bea0d61403b
parent 97ba12e14d78dc048cc4071d156f66243575db9e
Author: triesap <tyson@radroots.org>
Date:   Thu, 20 Aug 2026 19:25:49 +0000

build: add signed physical device builds

- require an exact physical destination and explicit development team
- keep generated device configuration under extbuild-owned output
- fail closed when the selected iPhone is locked or unavailable
- document standalone physical-device security and invocation rules

Diffstat:
MAGENTS.md | 5+++++
MREADME.md | 18++++++++++++++++++
Mscripts/xcode.sh | 56+++++++++++++++++++++++++++++++++++++++++++++++++++++++-
3 files changed, 78 insertions(+), 1 deletion(-)

diff --git a/AGENTS.md b/AGENTS.md @@ -43,6 +43,11 @@ This file applies to the complete standalone iOS app repository. A closer - Services-hardening generated changes must adopt the approved four coverage states and three outcomes across Swift and FFI together. Do not retain prototype evidence, receipt, outcome, or compatibility aliases. +- Physical-device development must use one exact UDID, an explicitly supplied + development team, a Debug `iphoneos` build, and verified TLS endpoints. + Never select the first device, disable signing or certificate verification, + rewrite the checked-in Debug defaults, or treat local-device evidence as + approved remote qualification. ## Generated and project files diff --git a/README.md b/README.md @@ -13,6 +13,24 @@ The current public release is `0.1.0-alpha`. - Rust `1.97.1-aarch64-apple-darwin` with the iOS device and simulator targets - `cargo-extbuild` configured for the checkout +Physical-device development additionally requires one exact paired, connected, +unlocked iPhone with Developer Mode enabled, an Apple development team, and a +generated xcconfig below the extbuild-owned DerivedData root. The governed +parent workspace supplies those machine inputs. The standalone script refuses +name-only destinations, unsigned builds, non-Debug physical builds, and +xcconfig files outside the managed output root: + +```sh +RADROOTS_IOS_PHYSICAL_AUTOMATION=1 \ +RADROOTS_IOS_DEVELOPMENT_TEAM=ABCDEFGHIJ \ +cargo extbuild run -- scripts/xcode.sh physical-app-build \ + id=00000000-0000000000000000 \ + "$XCODE_DERIVED_DATA/radroots-ios-device/config/device.xcconfig" +``` + +The values above are placeholders. Device identities, teams, endpoints, and +certificate material are never checked into this public repository. + ## Bootstrap and verify The first bootstrap requires network access. It checks out the exact Rust diff --git a/scripts/xcode.sh b/scripts/xcode.sh @@ -78,6 +78,60 @@ case "$operation" in "-only-testing:$test_target" \ test ;; + physical-app-build) + destination=${2:?physical app build requires a device destination} + xcconfig=${3:?physical app build requires an xcconfig} + physical_automation=${RADROOTS_IOS_PHYSICAL_AUTOMATION:-} + development_team=${RADROOTS_IOS_DEVELOPMENT_TEAM:?RADROOTS_IOS_DEVELOPMENT_TEAM is required} + if [[ "$physical_automation" != "1" ]]; then + echo "error: physical app build requires RADROOTS_IOS_PHYSICAL_AUTOMATION=1" >&2 + exit 64 + fi + if [[ ! "$destination" =~ ^id=[A-Fa-f0-9-]+$ ]]; then + echo "error: physical app build destination must be one exact device id" >&2 + exit 64 + fi + if [[ ! "$development_team" =~ ^[A-Z0-9]{10}$ ]]; then + echo "error: physical app build development team is invalid" >&2 + exit 64 + fi + if [[ "$xcconfig" != "$XCODE_DERIVED_DATA"/* || ! -f "$xcconfig" ]]; then + echo "error: physical app build xcconfig must be a regular file under XCODE_DERIVED_DATA" >&2 + exit 64 + fi + device_id=${destination#id=} + lock_state_file=$(mktemp "${TMPDIR:-/tmp}/radroots-ios-lock-state.XXXXXX") + trap 'unlink "$lock_state_file"' EXIT + if ! xcrun devicectl device info lockState \ + --device "$device_id" \ + --quiet \ + --timeout 10 \ + --json-output "$lock_state_file" + then + echo "error: physical app build device lock state is unavailable" >&2 + exit 1 + fi + passcode_required=$(/usr/bin/plutil \ + -extract result.passcodeRequired raw -o - "$lock_state_file") + unlocked_since_boot=$(/usr/bin/plutil \ + -extract result.unlockedSinceBoot raw -o - "$lock_state_file") + if [[ "$passcode_required" != "false" || "$unlocked_since_boot" != "true" ]]; then + echo "error: physical app build device is locked; refusing to invoke Xcode" >&2 + exit 1 + fi + exec xcodebuild \ + -project Radroots.xcodeproj \ + -scheme Radroots \ + -configuration Debug \ + -destination "$destination" \ + -xcconfig "$xcconfig" \ + "${output_args[@]}" \ + "${offline_args[@]}" \ + "DEVELOPMENT_TEAM=$development_team" \ + CODE_SIGN_STYLE=Automatic \ + "CODE_SIGN_IDENTITY=Apple Development" \ + build + ;; remote-ui-test) destination=${2:?remote-ui-test requires a simulator destination} test_selector=${3:?remote-ui-test requires a RadrootsUITests selector} @@ -204,7 +258,7 @@ case "$operation" in test-without-building ;; *) - echo "usage: $0 {resolve|package-build|package-test|project-build|project-test|remote-ui-test|physical-ui-build|physical-ui-test}" >&2 + echo "usage: $0 {resolve|package-build|package-test|project-build|project-test|physical-app-build|remote-ui-test|physical-ui-build|physical-ui-test}" >&2 exit 64 ;; esac