#!/bin/bash # PAILot iOS Deploy Script # # Installs a personal (Pro-unlocked) PAILot build onto one or more iOS devices. # # ── Remote / "fully remote" targets ──────────────────────────────────────── # Installation uses `xcrun devicectl device install`, which reaches the device # over Apple's CoreDevice tunnel — USB, local network, OR any routable network # path where the device is paired (e.g. across Tailscale). There is NO special # remote logic to add: once a device has been paired, `--device ` works # whether it sits on your desk or across the world, as long as it is awake and # reachable. That is exactly how the iPad here installs — it is a `localNetwork` # device, never plugged in. (First-time discovery of a NEW device uses .local / # mDNS, which does not cross networks, so a device must be paired at least once # on the local network before it can be driven fully remotely.) # Check reachability any time with: bash tools/deploy.sh --check # # ── Usage ────────────────────────────────────────────────────────────────── # bash tools/deploy.sh # Matthias' iPhone (default) # bash tools/deploy.sh -a # Amelie's iPhone # bash tools/deploy.sh -i # Matthias' iPad # bash tools/deploy.sh --all # every known device (continues on failure) # bash tools/deploy.sh --device ID # explicit CoreDevice identifier (any remote device) # bash tools/deploy.sh --build # force a fresh Pro-unlocked build first # bash tools/deploy.sh --check # list devices devicectl can currently reach set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" APP_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" cd "$APP_DIR" IPA="build/ios/ipa/pailot.ipa" # Known devices — CoreDevice identifiers from `xcrun devicectl list devices`. IPHONE_MATTHIAS="8708CADC-3330-50B6-AA0A-1655526A573A" IPHONE_AMELIE="E3F823F4-8DC8-5C88-948E-C7FA2B4C4C72" IPAD_MATTHIAS="8037E780-6BC0-5B9D-9ED4-D8F28DEEFD3F" TARGETS=() # entries: "identifier|label" FORCE_BUILD=false while [[ $# -gt 0 ]]; do case "$1" in -a|--amelie) TARGETS+=("$IPHONE_AMELIE|Amelie's iPhone"); shift ;; -i|--ipad) TARGETS+=("$IPAD_MATTHIAS|Matthias' iPad"); shift ;; --iphone) TARGETS+=("$IPHONE_MATTHIAS|Matthias' iPhone"); shift ;; --all) TARGETS+=("$IPHONE_MATTHIAS|Matthias' iPhone" \ "$IPAD_MATTHIAS|Matthias' iPad" \ "$IPHONE_AMELIE|Amelie's iPhone") shift ;; --device) TARGETS+=("$2|device $2"); shift 2 ;; --build) FORCE_BUILD=true; shift ;; --check) echo "=== Devices devicectl can reach ==="; xcrun devicectl list devices; exit 0 ;; -h|--help) sed -n '2,30p' "$0"; exit 0 ;; *) echo "Unknown option: $1" >&2; exit 2 ;; esac done # Default target: Matthias' iPhone. if [[ ${#TARGETS[@]} -eq 0 ]]; then TARGETS+=("$IPHONE_MATTHIAS|Matthias' iPhone") fi # ── Build a Pro-unlocked personal IPA when needed ────────────────────────── # PAILOT_PRO unlocks Pro for personal / sideloaded installs ONLY. App Store # builds go through tools/build-appstore.sh / tools/release.sh, which do NOT # pass this define — paying users still see the paywall. if [[ "$FORCE_BUILD" == true || ! -f "$IPA" ]]; then echo "=== Building Pro-unlocked IPA (PAILOT_PRO=true) ===" flutter build ipa --release --no-pub --export-method development \ --no-tree-shake-icons --dart-define=PAILOT_PRO=true ARCHIVE="build/ios/archive/Runner.xcarchive/Info.plist" if [ -f "$ARCHIVE" ]; then plutil -replace Name -string "PAILot" "$ARCHIVE" 2>/dev/null || true plutil -replace SchemeName -string "PAILot" "$ARCHIVE" 2>/dev/null || true fi else echo "=== Using existing IPA: $IPA ===" echo " (Pro status depends on how it was built — use --build to force a fresh Pro build)" fi install_one() { local id="$1" label="$2" echo "" echo "=== Installing on $label ($id) ===" if xcrun devicectl device install app --device "$id" "$IPA"; then echo " OK: $label" else echo " WARNING: install on $label failed — is it awake & reachable?" echo " Try: bash tools/deploy.sh --check" return 1 fi } fail=0 for t in "${TARGETS[@]}"; do install_one "${t%%|*}" "${t#*|}" || fail=1 done echo "" if [[ $fail -eq 0 ]]; then echo "=== Done — force-quit and reopen PAILot on the device(s). ===" else echo "=== Done, with failures (see WARNINGs above). ===" exit 1 fi