M1-04: Code signing and on-device install #4

Closed
opened 2026-08-06 10:53:40 -04:00 by agent · 1 comment
Member

Get a signed build onto Wes's iPhone.

Why it blocks everything after it

Issues 05 through 12 cannot be validated in the simulator. Background audio with the screen
locked, microphone route changes, phone-call interruptions, Live Activities and on-device
speech all behave differently or not at all on a simulator. Until this lands, nothing
downstream can be honestly marked done.

This is the schedule risk in M1. If provisioning fights us, that is the thing that costs the
day — not any of the audio work.

Scope

  • Signing configuration in project.yml.
  • Device provisioning for Wes's iPhone.
  • A documented one-command path to build and install on the device, in README.md.

⚠️ Public repo

No certificates, no provisioning profiles, no team identifiers committed. Signing config
reads from the local environment; document what has to be set, never the values.

Done when

make install (or equivalent, documented) puts the current build on the phone.

Milestone 1 of the Stash v3 rebuild, scoped 2026-08-06. M1 is a functioning iOS app on Wes's
phone with no sync at all — the entire goal is recording voice memos reliably and getting
them off the device by hand. Sync arrives in M3.

Working agreement for every issue in this repo: feature branch, tested before the PR opens
(on-device where the label says so), then Wes reviews the PR and we walk the code together.
Docs update in the same commit. A PR that takes more than 20 minutes to review is too big —
say so and split it.

Get a signed build onto Wes's iPhone. ## Why it blocks everything after it Issues 05 through 12 cannot be validated in the simulator. Background audio with the screen locked, microphone route changes, phone-call interruptions, Live Activities and on-device speech all behave differently or not at all on a simulator. **Until this lands, nothing downstream can be honestly marked done.** This is the schedule risk in M1. If provisioning fights us, that is the thing that costs the day — not any of the audio work. ## Scope - Signing configuration in `project.yml`. - Device provisioning for Wes's iPhone. - A documented one-command path to build and install on the device, in `README.md`. ## ⚠️ Public repo **No certificates, no provisioning profiles, no team identifiers committed.** Signing config reads from the local environment; document what has to be set, never the values. ## Done when `make install` (or equivalent, documented) puts the current build on the phone. --- *Milestone 1 of the Stash v3 rebuild, scoped 2026-08-06. M1 is a functioning iOS app on Wes's phone with **no sync at all** — the entire goal is recording voice memos reliably and getting them off the device by hand. Sync arrives in M3.* *Working agreement for every issue in this repo: feature branch, tested before the PR opens (on-device where the label says so), then Wes reviews the PR and we walk the code together. Docs update in the same commit. A PR that takes more than 20 minutes to review is too big — say so and split it.*
Author
Member

Branch m1-04-code-signing, one commit. make install puts the current build on the phone.

What's there

A Makefile. make install regenerates the project, builds signed, installs and launches on a connected iPhone. make run does the same for the simulator, make build is the unsigned simulator build CI runs, make lint is the same rules, make help lists them.

Every target regenerates the .xcodeproj first. Making that conditional would buy nothing and would let a stale project survive an edit to project.yml.

project.yml gains CODE_SIGN_STYLE: Automatic and DEVELOPMENT_TEAM: ${STASH_DEV_TEAM}, which XcodeGen expands from the environment at generate time.

Nothing about signing is committed

No certificate, no provisioning profile, no team identifier, no device UDID. Both values a device build needs are discovered from the machine:

Variable Discovered from Override when
STASH_DEV_TEAM The OU of the Apple Development certificate in the keychain You belong to more than one team
STASH_DEVICE The first connected device More than one is attached

The team is the OU of the certificate subject, not the parenthesised identifier on the security find-identity line — those are different values and the visible one is the wrong one. Worth naming because it fails in a confusing way.

README.md documents what can be set and never a value. I also genericised the device name in the examples to my-iphone, so even your phone's name isn't in the repo.

What I validated

From a make clean state, which is what a new machine actually starts in:

  • make lint — exit 0, 0 violations, 7 files.
  • make build — exit 0. Also regenerated with STASH_DEV_TEAM unset and rebuilt for the simulator, since that is exactly the CI case; the unexpanded ${STASH_DEV_TEAM} resolves to empty at build time and the build succeeds. CI is unaffected by this change.
  • make install — exit 0, on wes-iphone (iPhone 17 Pro), after devicectl device uninstall so it was a genuine first install rather than an upgrade over the copy from issue #3. App confirmed present in the device process list afterwards, so it launched rather than launch-and-crash.
  • make run — exit 0, app confirmed running in the simulator.

Both error paths, deliberately triggered:

$ make install STASH_DEV_TEAM=
error: no Apple Development team found in your keychain.
  ...
  Or pass it directly, if you know it:
      make install STASH_DEV_TEAM=ABCDE12345
make: *** [check-team] Error 1

Confirmed the guard actually stops the chain — xcodebuild never ran.

One bug found by testing rather than by reading. The first device detection matched the State column on /available/. That worked, then stopped working ten minutes later: devicectl reports available (paired) or connected depending on what it last did, so detection silently returned nothing and make install claimed no phone was attached while the phone was attached. It now matches the hostname column, which does not move.

What I did not validate

  • Only one machine and one phone. Team and device auto-detection have never run where there are two certificates or two connected devices — that is exactly when the override matters, and the override is untested.
  • No second Apple ID, so the check-team failure text describes a recovery path I have not walked. The message is a guess at the right instructions, informed but unverified.
  • The expiry claim in README.md is from how development profiles work, not from having watched this one expire.
  • CI has not run this. .woodpecker/lint.yml could; the simulator build still has no macOS agent.

Opening a PR. Leaving this issue open for you.

Branch `m1-04-code-signing`, one commit. `make install` puts the current build on the phone. ## What's there A `Makefile`. `make install` regenerates the project, builds signed, installs and launches on a connected iPhone. `make run` does the same for the simulator, `make build` is the unsigned simulator build CI runs, `make lint` is the same rules, `make help` lists them. Every target regenerates the `.xcodeproj` first. Making that conditional would buy nothing and would let a stale project survive an edit to `project.yml`. `project.yml` gains `CODE_SIGN_STYLE: Automatic` and `DEVELOPMENT_TEAM: ${STASH_DEV_TEAM}`, which XcodeGen expands from the environment at generate time. ## Nothing about signing is committed No certificate, no provisioning profile, no team identifier, no device UDID. Both values a device build needs are discovered from the machine: | Variable | Discovered from | Override when | |---|---|---| | `STASH_DEV_TEAM` | The **OU** of the Apple Development certificate in the keychain | You belong to more than one team | | `STASH_DEVICE` | The first connected device | More than one is attached | The team is the OU of the certificate subject, **not** the parenthesised identifier on the `security find-identity` line — those are different values and the visible one is the wrong one. Worth naming because it fails in a confusing way. `README.md` documents what can be set and never a value. I also genericised the device name in the examples to `my-iphone`, so even your phone's name isn't in the repo. ## What I validated From a `make clean` state, which is what a new machine actually starts in: - `make lint` — exit 0, 0 violations, 7 files. - `make build` — exit 0. **Also regenerated with `STASH_DEV_TEAM` unset and rebuilt for the simulator**, since that is exactly the CI case; the unexpanded `${STASH_DEV_TEAM}` resolves to empty at build time and the build succeeds. CI is unaffected by this change. - `make install` — exit 0, on `wes-iphone` (iPhone 17 Pro), after `devicectl device uninstall` so it was a genuine first install rather than an upgrade over the copy from issue #3. App confirmed present in the device process list afterwards, so it launched rather than launch-and-crash. - `make run` — exit 0, app confirmed running in the simulator. **Both error paths, deliberately triggered:** ``` $ make install STASH_DEV_TEAM= error: no Apple Development team found in your keychain. ... Or pass it directly, if you know it: make install STASH_DEV_TEAM=ABCDE12345 make: *** [check-team] Error 1 ``` Confirmed the guard actually stops the chain — `xcodebuild` never ran. **One bug found by testing rather than by reading.** The first device detection matched the `State` column on `/available/`. That worked, then stopped working ten minutes later: `devicectl` reports `available (paired)` or `connected` depending on what it last did, so detection silently returned nothing and `make install` claimed no phone was attached while the phone was attached. It now matches the hostname column, which does not move. ## What I did not validate - **Only one machine and one phone.** Team and device auto-detection have never run where there are two certificates or two connected devices — that is exactly when the override matters, and the override is untested. - **No second Apple ID**, so the `check-team` failure text describes a recovery path I have not walked. The message is a guess at the right instructions, informed but unverified. - The expiry claim in `README.md` is from how development profiles work, not from having watched this one expire. - CI has not run this. `.woodpecker/lint.yml` could; the simulator build still has no macOS agent. Opening a PR. Leaving this issue open for you.
wk closed this issue 2026-08-06 14:50:30 -04:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Stash/stash-ios#4
No description provided.