From 13fbe97126206da09097ea6a043acd7aa4520b8e Mon Sep 17 00:00:00 2001 From: TZGyn Date: Mon, 26 Oct 2026 21:31:14 +0800 Subject: [PATCH] Add iOS build, wireless install, and signing workflows --- README.md | 5 +- SKILL.md | 224 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 218 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 6880f78..076a88f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # TZGyn-skill My personal Crush skill: conventions for starting a project on Gitea, publishing -to the TZGyn Homebrew tap (casks for GUI apps, formulae for CLIs), and signing / -notarizing / releasing macOS apps. +to the TZGyn Homebrew tap (casks for GUI apps, formulae for CLIs), signing / +notarizing / releasing macOS apps, and building native SwiftUI iOS apps and +installing them over the network. See [SKILL.md](./SKILL.md). diff --git a/SKILL.md b/SKILL.md index cdb0ef0..8eb6d36 100644 --- a/SKILL.md +++ b/SKILL.md @@ -11,7 +11,7 @@ skill at `~/.agents/skills/TZGyn-skill` (a symlink to the checkout, so editing the repo updates the live skill). The reference implementations to copy from are `~/Git/kapture` and -`~/Git/konference` (macOS menu-bar apps). +`~/Git/konference` (macOS menu-bar apps); for iOS, `~/Git/koins/apps/ios`. ## Ground rules @@ -23,11 +23,12 @@ The reference implementations to copy from are `~/Git/kapture` and `gitea.tzgyn.dev`. SSH: `git@gitea.tzgyn.dev:TZGyn/.git`. - **Always print the upgrade script** (the `brew` commands below) in the final answer after any release or version bump. The user wants it every time. -- **Every GUI release must be notarized and stapled.** An unnotarized build is - not a release: never publish one, and never paper over Gatekeeper with - `xattr -dr com.apple.quarantine` or `brew install --cask --no-quarantine`. +- **Every macOS GUI release must be notarized and stapled.** An unnotarized + build is not a release: never publish one, and never paper over Gatekeeper + with `xattr -dr com.apple.quarantine` or `brew install --cask --no-quarantine`. If notarization fails, fix the cause (see - [Troubleshooting](#notarization-troubleshooting)) before publishing. + [Troubleshooting](#notarization-troubleshooting)) before publishing. (iOS + apps are not notarized — Apple has no such step; see section 6.) - Never commit build artifacts. `.gitignore` covers `.build/`, `*.app/`, `dist/`, `.swiftpm/`, `xcuserdata/`, `DerivedData/`, `.DS_Store`. (`go build ./...` drops a binary next to the source — ignore it explicitly.) @@ -242,11 +243,216 @@ xcrun notarytool history --key ... --key-id ... --issuer ... # 403 here = acco problem is the account, not the bundle, so there is nothing to change in the app or the scripts. -## 6. Mobile +## 6. iOS apps -For mobile (iOS/Android), notarization does not apply; the equivalent is store -signing (App Store Connect / Play Console) or EAS. Ask for the specific stack -before assuming; this skill currently only codifies the macOS path. +Native SwiftUI iOS apps are built with **XcodeGen** — no CocoaPods, no SwiftPM +packages. `project.yml` is the source of truth; the generated +`.xcodeproj/` is gitignored and regenerated after any file is added or +removed. Working reference: `~/Git/koins/apps/ios`. + +One-time tool install: `brew install xcodegen`. + +### Layout and project file + +``` +apps/ios/ + project.yml XcodeGen project (committed) + / Swift sources, assets, Info.plist + /Bridge/ optional ObjC/ObjC++ bridge + bridging header + /Libs/ optional prebuilt static archives (not bundled) + .gitignore .xcodeproj/, build/, DerivedData/, xcuserdata/ +``` + +Minimal `project.yml`: + +```yaml +name: +options: + bundleIdPrefix: dev.tzgyn + deploymentTarget: + iOS: "16.0" + xcodeVersion: "16.0" + +settings: + base: + SWIFT_VERSION: "5.0" + ENABLE_USER_SCRIPT_SANDBOXING: NO + +targets: + : + type: application + platform: iOS + sources: + - path: + excludes: + - "Libs" # static archives are linked, never bundled + settings: + base: + PRODUCT_BUNDLE_IDENTIFIER: dev.tzgyn. + CODE_SIGN_STYLE: Automatic + DEVELOPMENT_TEAM: RX6767S6BW + CODE_SIGNING_ALLOWED[sdk=iphonesimulator*]: NO + SWIFT_OBJC_BRIDGING_HEADER: /Bridge/-Bridging-Header.h + LIBRARY_SEARCH_PATHS: + - "$(SRCROOT)//Libs" + OTHER_LDFLAGS[sdk=iphoneos*]: + - "$(inherited)" + - "-l_device" + OTHER_LDFLAGS[sdk=iphonesimulator*]: + - "$(inherited)" + - "-l_merged" + info: + path: /Info.plist + properties: + UILaunchScreen: {} + UISupportedInterfaceOrientations: + - UIInterfaceOrientationPortrait + UIUserInterfaceStyle: Dark + NSCameraUsageDescription: Scan a QR code. + NSFaceIDUsageDescription: Unlock the app and approve payments. +``` + +- Bundle id convention: `dev.tzgyn.` (Koins predates it and uses + `com.koins.app`; keep an existing id rather than migrating). +- Privacy usage strings are mandatory. A missing `NSCameraUsageDescription`, + `NSPhotoLibraryUsageDescription`, or `NSFaceIDUsageDescription` crashes the + app the first time that API is used. +- Prebuilt static archives need separate device and simulator builds; select + them with `OTHER_LDFLAGS[sdk=…]` as above. If ObjC categories fail to load, + link with `-Wl,-force_load,$(SRCROOT)//Libs/lib.a` instead. +- The simulator target uses `CODE_SIGNING_ALLOWED[sdk=iphonesimulator*] = NO`; + device builds use automatic signing with the TZGyn team. + +After adding/removing files or changing `project.yml`: + +```bash +cd apps/ios && xcodegen generate +``` + +### Build + +```bash +cd apps/ios +xcodegen generate + +# Simulator (Apple Silicon) +xcodebuild -project .xcodeproj -scheme -configuration Debug \ + -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' \ + ARCHS=arm64 ONLY_ACTIVE_ARCH=NO -derivedDataPath /tmp/-dd build + +# Device +xcodebuild -project .xcodeproj -scheme -configuration Debug \ + -sdk iphoneos -destination 'generic/platform=iOS' \ + -allowProvisioningUpdates -derivedDataPath /tmp/-dd-device build +``` + +`-allowProvisioningUpdates` creates/refreshes the development profile +automatically; no manual profile management is needed with the paid team. +Derived data under `/tmp` is lost on reboot — rebuild rather than chasing a +missing `.app`. + +### Install on a physical iPhone (over the network, no cable) + +This is the normal way to update a TZGyn iPhone app: build the `iphoneos` slice, +then install over Wi-Fi with `devicectl`. `simctl` only works with simulators. + +One-time setup: + +1. Connect the iPhone once with a cable, open Xcode → **Window → Devices and + Simulators**, select the phone, and tick **Connect via network**. +2. On the phone: **Settings → Privacy & Security → Developer Mode → On** + (requires a reboot), then trust the Mac when prompted. + +Wireless install and launch: + +```bash +# 1. Find the paired phone (UDID, ECID, name and DNS name are all accepted). +xcrun devicectl list devices +# want: .coredevice.local available (paired) + +# 2. Install the device build. This replaces the app in place and keeps its data. +xcrun devicectl device install app --device --timeout 180 \ + /tmp/-dd-device/Build/Products/Debug-iphoneos/.app + +# 3. Launch it (the phone must be unlocked). +xcrun devicectl device process launch --device +``` + +- Use the **device** build path (`-dd-device/.../Debug-iphoneos`), not the + simulator build; installing the wrong slice fails. +- `--timeout 180` is deliberate: the app bundle transfer over Wi-Fi can take a + while, and the default timeout is easy to hit. +- Installing over the network **does not delete app data**. Wallet files, seeds + and preferences survive updates (seeds live in the Keychain). +- If the device shows `unavailable`, or install fails with + `Network.NWError error 60 - Operation timed out`, the phone is asleep, + locked, or off Wi-Fi. Wake/unlock it, keep it on the same network, retry. A + timed-out install leaves the previous build installed. +- Launch errors with `BSErrorCodeDescription = Locked` mean exactly that: the + phone must be unlocked for the launch. +- `devicectl` is for physical devices only; use `simctl` for simulators. + +### Simulator workflow + +```bash +xcrun simctl list devices available +xcrun simctl boot +open -a Simulator + +xcrun simctl install /tmp/-dd/Build/Products/Debug-iphonesimulator/.app +xcrun simctl launch +xcrun simctl io screenshot /tmp/screen.png +xcrun simctl get_app_container data +``` + +- Files the app writes (self-test reports, exported data) can be read from the + data container printed by `get_app_container`. +- Simulator preferences are cached by `cfprefsd`; `xcrun simctl spawn + defaults delete …` often does not stick. To reset a preference: terminate the + app, `xcrun simctl spawn launchctl stop com.apple.cfprefsd.xpc.daemon`, + then edit or delete the key in the plist directly. + +### Local self-tests + +Gate verification code behind launch arguments, write the report into the app's +Documents folder, and read it back from the container (console output is not +always reachable): + +```bash +xcrun simctl launch -SelfTest +DATA=$(xcrun simctl get_app_container data) +cat "$DATA/Documents/-selftest.txt" +``` + +For unified-log checks: + +```bash +xcrun simctl spawn log show --last 1m \ + --predicate 'subsystem == ""' --style compact +``` + +Keep network tests explicit (`-SelfTestNetwork`) so the default launch is +offline and fast. + +### Signing and distribution + +- **Team: `RX6767S6BW` for every TZGyn app, macOS and iOS.** iOS device builds + use automatic signing (`CODE_SIGN_STYLE: Automatic`, `DEVELOPMENT_TEAM: + RX6767S6BW`) and `-allowProvisioningUpdates`; no manual profiles. +- **iOS apps are not notarized.** Apple has no notarization step for iOS — + device installs are gated by code signing + Developer Mode. Do not add a + `notarytool`/`stapler` step to an iOS build, and do not `spctl` an + `iphoneos` app. +- **macOS GUI releases are always notarized and stapled** (section 5), with the + shared App Store Connect key: + `~/.config/kapture/AuthKey_K5KXMMSB28.p8`, key ID `K5KXMMSB28`, issuer + `3e25e5dc-8b75-453f-a71c-c7f3feaac628`, team `RX6767S6BW`. Never publish a + macOS release until `spctl -a -vv` reports `accepted` / + `source=Notarized Developer ID`. +- **App Store Connect** (only if TestFlight/App Store is ever used) reuses the + same key; koins does not use it yet. +- Android is not codified yet; ask which stack (native, Compose, RN, EAS) + before assuming. ## 7. Extending this skill (self-improvement)