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:
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