Add signed iOS device debug workflow (#179)
Add a protected development-signing workflow, isolated debug app identity, Linux USB device tooling, documentation, and focused validation coverage.
This commit is contained in:
@@ -0,0 +1,382 @@
|
||||
# Cable-Connected iPhone Debug IPA Runbook
|
||||
|
||||
This runbook covers development-signed iOS builds installed from an Ubuntu
|
||||
workstation over USB. It is separate from the public App Store release path in
|
||||
`docs/app-store-release.md`.
|
||||
|
||||
The device build is deliberately a second app:
|
||||
|
||||
- Debug bundle ID: `io.truckwash.app.debug`
|
||||
- Debug display name: `Truck Wash Debug`
|
||||
- Production bundle ID: `io.truckwash.app`
|
||||
- Capacitor/Android app ID: `io.truckwash.twa`
|
||||
- API: `https://api-v2.truckwash.io/master/api`
|
||||
|
||||
Installing or uninstalling the debug app must not replace or remove the
|
||||
production app. The debug workflow builds the Vue application in production
|
||||
mode against the stable API; it does not use Vite's development `/api` default
|
||||
or a live-reload server.
|
||||
|
||||
## What Ubuntu Can And Cannot Do
|
||||
|
||||
The current Ubuntu workstation already has `usbmuxd`, the libimobiledevice
|
||||
utilities, and `ideviceinstaller`. The current iPhone has previously been
|
||||
trusted and paired. Run the repository doctor before every install because the
|
||||
phone can still be locked, trust can be reset, or Developer Mode can be off.
|
||||
|
||||
This workflow supports:
|
||||
|
||||
- Inspecting pairing, activation, lock, Developer Mode, and install-service
|
||||
readiness.
|
||||
- Installing and upgrading a valid development-signed IPA.
|
||||
- Reading filtered device syslog and copying crash reports.
|
||||
|
||||
Apple does not provide Xcode, LLDB device debugging, or Safari Web Inspector on
|
||||
Linux. `CAPACITOR_DEBUG` and `get-task-allow` make the IPA suitable for a
|
||||
development device, but they do not create an official Linux LLDB or WebKit
|
||||
debugger. Use a physical Mac with Xcode/Safari for breakpoints or Web Inspector.
|
||||
Use TestFlight or a physical Mac if a new or beta iOS release is incompatible
|
||||
with libimobiledevice; never weaken device security or signing validation as a
|
||||
workaround.
|
||||
|
||||
## One-Time iPhone Preparation
|
||||
|
||||
1. Connect the iPhone directly with a data-capable USB cable.
|
||||
2. Unlock the phone and keep it awake. Tap **Trust** if iOS asks whether to
|
||||
trust this computer, then enter the device passcode.
|
||||
3. On iOS 16 or newer, open **Settings -> Privacy & Security -> Developer
|
||||
Mode**, turn Developer Mode on, and accept the restart. iOS 15 does not have
|
||||
this setting and the helper does not require it there.
|
||||
4. After the restart, unlock the phone, confirm **Turn On** in the Developer
|
||||
Mode prompt, and enter the passcode again.
|
||||
5. Reconnect the cable and run the doctor described below.
|
||||
|
||||
Developer Mode is an iOS security control and cannot be bypassed from Ubuntu.
|
||||
If the Developer Mode setting is absent, connect the phone once to a physical
|
||||
Mac and use Apple's supported Xcode or Apple Configurator device preparation,
|
||||
then return to Ubuntu after the phone has restarted and Developer Mode is on.
|
||||
|
||||
Trust, pairing, and Developer Mode can be cleared by device resets, iOS updates,
|
||||
or privacy/location resets. Repeat these steps if the doctor reports that the
|
||||
previously working device is no longer ready.
|
||||
|
||||
## Apple Developer Setup
|
||||
|
||||
This requires the paid Truck Wash ApS Apple Developer team and a user permitted
|
||||
to manage certificates, identifiers, and devices.
|
||||
|
||||
### Register the device and debug App ID
|
||||
|
||||
1. Connect and unlock the iPhone, then get its UDID locally with
|
||||
`idevice_id -l`. Treat the full UDID as sensitive operational data: do not
|
||||
commit it or paste it into ordinary build logs.
|
||||
2. In Apple Developer **Certificates, Identifiers & Profiles -> Devices**, add
|
||||
the iPhone using that UDID.
|
||||
3. Under **Identifiers**, create an explicit App ID for
|
||||
`io.truckwash.app.debug`.
|
||||
4. Enable only capabilities required by the current Xcode project. Do not copy
|
||||
unrelated production entitlements into the debug App ID.
|
||||
|
||||
### Create the certificate and development profile
|
||||
|
||||
1. Create a dedicated **Apple Development** certificate for CI device-debug
|
||||
signing. Keep its private key under the team's normal credential controls.
|
||||
2. Export the certificate and private key together as a password-protected
|
||||
`.p12` file.
|
||||
3. Create an **iOS App Development** provisioning profile that selects:
|
||||
- App ID `io.truckwash.app.debug`
|
||||
- The dedicated Apple Development certificate
|
||||
- Every approved physical test iPhone, including the cable-connected device
|
||||
4. Download the `.mobileprovision` file.
|
||||
5. Confirm the profile has not expired, includes the intended device UDIDs, and
|
||||
grants `get-task-allow=true`. An App Store or ad-hoc profile is not valid for
|
||||
this workflow.
|
||||
|
||||
Base64-encode both files without line wrapping before adding them to GitHub. On
|
||||
Ubuntu, for example:
|
||||
|
||||
```sh
|
||||
base64 -w 0 TruckWashDebug.p12 > TruckWashDebug.p12.base64
|
||||
base64 -w 0 TruckWashDebug.mobileprovision > TruckWashDebug.mobileprovision.base64
|
||||
```
|
||||
|
||||
Store the encoded values in GitHub immediately, verify one successful build,
|
||||
then securely remove the local `.p12`, profile, encoded copies, CSR, and any
|
||||
other private-key intermediates that are no longer required. Never commit
|
||||
signing files or their encoded contents.
|
||||
|
||||
## GitHub Environment And Dispatch Approval
|
||||
|
||||
Create a repository environment named `mobile-device-debug`. Store the debug
|
||||
signing configuration only in that environment. Required environment reviewers
|
||||
are not available for this private repository's current GitHub plan, so the
|
||||
manual `workflow_dispatch` inputs are the signing approval boundary.
|
||||
|
||||
Restrict the environment's custom deployment branches to the exact `master`
|
||||
branch. The checked-in workflow also refuses any other workflow ref. This keeps
|
||||
signing secrets behind the reviewed workflow on `master`, while `source_ref`
|
||||
can still select a separately inspected same-repository commit to build.
|
||||
|
||||
Add these environment variables exactly:
|
||||
|
||||
- `APPLE_TEAM_ID`
|
||||
- `IOS_DEBUG_BUNDLE_ID=io.truckwash.app.debug`
|
||||
- `IOS_DEBUG_API_URL=https://api-v2.truckwash.io/master/api`
|
||||
|
||||
Add these environment secrets exactly:
|
||||
|
||||
- `IOS_DEBUG_CERTIFICATE_BASE64`: base64 of the password-protected `.p12`
|
||||
- `IOS_DEBUG_CERTIFICATE_PASSWORD`: password used to export the `.p12`
|
||||
- `IOS_DEBUG_PROVISION_PROFILE_BASE64`: base64 of the development
|
||||
`.mobileprovision`
|
||||
- `IOS_DEBUG_ALLOWED_UDIDS`: newline-delimited UDIDs for every device that the
|
||||
profile is expected to contain
|
||||
|
||||
The workflow generates and masks a new random password for its temporary macOS
|
||||
keychain on every run. Do not create or store an
|
||||
`IOS_DEBUG_KEYCHAIN_PASSWORD` secret.
|
||||
|
||||
Do not reuse the `mobile-store-production` distribution secrets. The debug job
|
||||
must use an Apple Development certificate and iOS App Development profile; the
|
||||
existing `io.truckwash.app` App Store workflow remains unchanged.
|
||||
|
||||
The person dispatching a run must inspect the intended commit first. Do not
|
||||
dispatch when:
|
||||
|
||||
- The exact 40-character SHA is not the branch, tag, or commit intended.
|
||||
- The source comes from a fork or another repository.
|
||||
- The requested change is not appropriate to sign for a physical device.
|
||||
- The signing profile is expired or no longer covers the intended device.
|
||||
|
||||
The workflow independently resolves `source_ref` inside this repository and
|
||||
requires it to equal `expected_sha`. It also requires the exact typed
|
||||
confirmation `SIGN IOS DEBUG IPA`. A missing/mismatched SHA or confirmation
|
||||
stops the unprivileged resolver before the environment signing secrets are used.
|
||||
|
||||
## Build And Download A Debug IPA
|
||||
|
||||
1. Open **Actions -> iOS Device Debug IPA -> Run workflow** and keep **Use
|
||||
workflow from** set to `master`.
|
||||
2. Inspect the intended commit and copy its complete 40-character SHA.
|
||||
3. Enter `source_ref`. It may be a branch, tag, or commit in this repository and
|
||||
defaults to `master`.
|
||||
4. Enter the complete SHA as `expected_sha` and enter the exact confirmation
|
||||
`SIGN IOS DEBUG IPA`. Submitting these inputs is approval to sign that source.
|
||||
5. The resolver pins `source_ref` inside this repository and verifies it equals
|
||||
`expected_sha`. A mismatch stops the run before signing.
|
||||
6. Wait for the signed macOS job to finish. It builds a Debug archive against
|
||||
`https://api-v2.truckwash.io/master/api`, exports it with method
|
||||
`development`, validates the embedded profile and app identity, and never
|
||||
uploads the result to App Store Connect.
|
||||
7. Download the `truck-wash-debug-<version>-<12-character-SHA>` GitHub Actions
|
||||
artifact for the run. Keep its same-prefix `.ipa`, `.dSYM.zip`,
|
||||
`manifest.json`, and `SHA256SUMS` together in one directory.
|
||||
8. From that directory, verify the download before connecting it to a device:
|
||||
|
||||
```sh
|
||||
sha256sum --check SHA256SUMS
|
||||
```
|
||||
|
||||
Do not install an artifact after a checksum failure. The manifest records the
|
||||
source ref and SHA, build/run numbers, bundle identity, stable API, minimum iOS,
|
||||
Xcode/Capacitor versions, signing method, and provisioning-profile expiration.
|
||||
It intentionally does not contain device UDIDs or secrets.
|
||||
|
||||
Artifacts are retained for seven days. Keep the zipped dSYM with any crash
|
||||
report from that build so a Mac/Xcode crash-symbolication path remains
|
||||
available.
|
||||
|
||||
## Ubuntu Device Commands
|
||||
|
||||
Run commands from the repository root. The npm interface is:
|
||||
|
||||
```sh
|
||||
npm run mobile:ios:device -- <command>
|
||||
```
|
||||
|
||||
If the npm wrapper is unavailable, use the equivalent direct entrypoint:
|
||||
|
||||
```sh
|
||||
node scripts/mobile/ios-device.mjs <command>
|
||||
```
|
||||
|
||||
The helper uses USB devices only. With one connected iPhone, omit `--udid`.
|
||||
With multiple devices connected, provide `--udid ID`; the command fails instead
|
||||
of guessing. Normal output redacts full UDIDs.
|
||||
|
||||
### Check readiness
|
||||
|
||||
Unlock the phone and run:
|
||||
|
||||
```sh
|
||||
npm run mobile:ios:device -- doctor
|
||||
```
|
||||
|
||||
The doctor verifies required host commands, USB discovery, pairing, activation,
|
||||
unlocked state, Developer Mode, and installation-proxy access. Resolve every
|
||||
reported failure before attempting an install.
|
||||
|
||||
For a specific connected device:
|
||||
|
||||
```sh
|
||||
npm run mobile:ios:device -- doctor --udid DEVICE_UDID
|
||||
```
|
||||
|
||||
### Install or upgrade
|
||||
|
||||
Keep the downloaded artifact files together and run:
|
||||
|
||||
```sh
|
||||
npm run mobile:ios:device -- install ./truck-wash-debug-VERSION-SHA.ipa --manifest ./manifest.json
|
||||
```
|
||||
|
||||
The helper requires and verifies `SHA256SUMS` and the complete workflow
|
||||
manifest, then inspects the IPA and its embedded profile. It rejects missing or
|
||||
mismatched artifact metadata, the wrong repository/source/API/bundle/executable,
|
||||
an App Store/ad-hoc or expired profile, `get-task-allow=false`, a profile
|
||||
missing the connected UDID, or an invalid app payload before calling
|
||||
`ideviceinstaller`.
|
||||
|
||||
If `io.truckwash.app.debug` is absent, the helper installs it. If it is already
|
||||
present, the helper upgrades it and confirms the resulting version/build on the
|
||||
phone. It never uninstalls or replaces `io.truckwash.app`.
|
||||
|
||||
Launch **Truck Wash Debug** manually from the iPhone Home Screen. Keep the phone
|
||||
online for the first launch so iOS can perform Apple's PPQ validation for the
|
||||
provisioning profile. A firewall, DNS filter, VPN, or captive portal that
|
||||
blocks Apple's validation service can prevent a correctly signed development
|
||||
app from opening.
|
||||
|
||||
### Collect filtered logs
|
||||
|
||||
Start logging, then reproduce the issue on the phone:
|
||||
|
||||
```sh
|
||||
npm run mobile:ios:device -- logs
|
||||
npm run mobile:ios:device -- logs --output ./truck-wash-debug.log
|
||||
```
|
||||
|
||||
The debug executable is deliberately named `TruckWashDebug`, distinct from the
|
||||
production executable. The helper verifies that exact name and filters
|
||||
`idevicesyslog` output for it. It streams child-tool output through UDID
|
||||
redaction; `--output` files are written by the helper with mode `0600` after
|
||||
redaction. Logs should make it possible to correlate the app with its source SHA
|
||||
and stable API target without exposing signing secrets or full device IDs.
|
||||
|
||||
### Copy crash reports
|
||||
|
||||
Create a destination directory and copy reports from the phone:
|
||||
|
||||
```sh
|
||||
mkdir -p ./ios-crashes
|
||||
npm run mobile:ios:device -- crashes ./ios-crashes
|
||||
```
|
||||
|
||||
Crash retrieval always keeps the original reports on the iPhone. Preserve the
|
||||
matching IPA manifest and dSYM with each report.
|
||||
|
||||
### Remove only the debug app
|
||||
|
||||
Uninstall requires the exact debug bundle ID as typed confirmation:
|
||||
|
||||
```sh
|
||||
npm run mobile:ios:device -- uninstall --confirm io.truckwash.app.debug
|
||||
```
|
||||
|
||||
The helper refuses to remove the production bundle or any other bundle ID.
|
||||
|
||||
## Adding Devices And Renewing Signing
|
||||
|
||||
A provisioning profile is a snapshot. Registering another iPhone in Apple
|
||||
Developer does not update an already downloaded profile.
|
||||
|
||||
When adding a device:
|
||||
|
||||
1. Obtain its UDID locally and register it in the Apple Developer portal.
|
||||
2. Regenerate the `io.truckwash.app.debug` iOS App Development profile with the
|
||||
new and existing approved devices selected.
|
||||
3. Replace `IOS_DEBUG_PROVISION_PROFILE_BASE64`.
|
||||
4. Add the UDID to the newline-delimited `IOS_DEBUG_ALLOWED_UDIDS` secret.
|
||||
5. Dispatch a new build; an existing IPA does not gain access to the new device.
|
||||
|
||||
Monitor the profile expiration recorded in each artifact manifest and the Apple
|
||||
Development certificate expiration in the portal. Before either expires,
|
||||
create/renew the signing material, regenerate the profile, replace the affected
|
||||
GitHub secrets, and prove the result with a new build and real-device install.
|
||||
Revoked or expired signing material invalidates later installation and can stop
|
||||
an already installed development build from launching.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No device, device locked, or installation proxy unavailable
|
||||
|
||||
- Use a direct data-capable cable and avoid an unreliable hub.
|
||||
- Unlock the iPhone, keep its screen awake, reconnect it, and rerun `doctor`.
|
||||
- Close other tools that may be exclusively interacting with the device.
|
||||
- Do not repeatedly retry installation while the doctor reports a lock/service
|
||||
failure.
|
||||
|
||||
### Pairing or trust failure
|
||||
|
||||
- Unlock the phone and accept the Trust prompt.
|
||||
- If no prompt appears and `doctor` reports invalid pairing, use the explicit
|
||||
repair guidance printed by the helper, reconnect, and confirm trust again.
|
||||
- Device privacy resets and some iOS updates require a new trust decision.
|
||||
|
||||
### Developer Mode is disabled or absent on iOS 16 or newer
|
||||
|
||||
- Enable it under **Settings -> Privacy & Security -> Developer Mode**, restart,
|
||||
and confirm after the reboot.
|
||||
- If the switch is absent, use a Mac with Xcode or Apple Configurator for
|
||||
Apple's supported one-time preparation. There is no Ubuntu bypass.
|
||||
|
||||
### IPA, profile, certificate, or UDID mismatch
|
||||
|
||||
- Confirm the IPA is from **iOS Device Debug IPA**, not **Mobile Store Artifacts**.
|
||||
- Check `manifest.json` for `io.truckwash.app.debug`, development signing, the
|
||||
intended source SHA, and a future profile expiration.
|
||||
- Regenerate the development profile when a device was added, a certificate was
|
||||
replaced, or the profile expired; then replace the GitHub secret and rebuild.
|
||||
- Never suppress the helper's profile, entitlement, bundle, or checksum checks.
|
||||
|
||||
### App installs but will not launch
|
||||
|
||||
- Keep the phone online for Apple's initial PPQ validation.
|
||||
- Check whether VPN, DNS, firewall, captive-portal, or device-management policy
|
||||
blocks Apple developer-app verification.
|
||||
- Confirm Developer Mode is still on and the certificate/profile has not expired
|
||||
or been revoked.
|
||||
- Collect syslog and crash reports before reinstalling so evidence is preserved.
|
||||
|
||||
### iOS beta or new major iOS version breaks device tools
|
||||
|
||||
- Record the device model, exact iOS version, helper error, source SHA, and IPA
|
||||
checksum.
|
||||
- Update libimobiledevice only through a trusted package/source and rerun the
|
||||
doctor. Do not install arbitrary device images or disable signing checks.
|
||||
- If compatibility remains broken, distribute through TestFlight or install and
|
||||
debug from a physical Mac with a compatible Xcode version.
|
||||
|
||||
## Real-Device Acceptance Checklist
|
||||
|
||||
For the first setup, after signing changes, and after major iOS upgrades:
|
||||
|
||||
- `doctor` passes while the phone is unlocked.
|
||||
- `SHA256SUMS` verifies and the manifest identifies the intended immutable SHA.
|
||||
- **Truck Wash Debug** installs as `io.truckwash.app.debug` while the production
|
||||
app and its data remain unchanged.
|
||||
- Authentication, camera/QR permission, and foreground-location behavior work.
|
||||
- Logs show the expected build/source context and stable API target.
|
||||
- The debug app still launches and reaches the API after the cable is removed.
|
||||
- A higher-numbered IPA upgrades the debug app without clearing its local state.
|
||||
- Crash reports are copied without being deleted from the phone.
|
||||
- The test record includes artifact checksum, source SHA, device model, iOS
|
||||
version, outcome, and any residual iOS/libimobiledevice compatibility risk.
|
||||
|
||||
## References
|
||||
|
||||
- [Apple: enable Developer Mode on a device](https://developer.apple.com/documentation/xcode/enabling-developer-mode-on-a-device)
|
||||
- [Apple: run an app on a physical device](https://developer.apple.com/documentation/Xcode/running-your-app-on-simulated-or-physical-devices)
|
||||
- [Apple: register a single device](https://developer.apple.com/help/account/devices/register-a-single-device/)
|
||||
- [Apple: create a development provisioning profile](https://developer.apple.com/help/account/provisioning-profiles/create-a-development-provisioning-profile/)
|
||||
- [libimobiledevice project](https://github.com/libimobiledevice/libimobiledevice)
|
||||
Reference in New Issue
Block a user