Skip to content

feat(app): streamline local install workflow and activation feedback - #86

Draft
gly11 wants to merge 8 commits into
mrmidi:devfrom
gly11:sbp2/install-workflow
Draft

feat(app): streamline local install workflow and activation feedback#86
gly11 wants to merge 8 commits into
mrmidi:devfrom
gly11:sbp2/install-workflow

Conversation

@gly11

@gly11 gly11 commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add install-asfw.sh: build, ad-hoc sign, verify entitlements, replace the app atomically, and optionally drive activation to completion
  • gate the SCSI HBA and fresh dext replacement behind explicit flags, and refuse a build whose SCSI state does not match the flag
  • detect a pending system-extension transition before building, so a run that cannot succeed stops in seconds instead of after a full build
  • translate OSSystemExtensionErrorDomain failures, including the reboot-required state, into copyable activation status text

Relationship to the older fork PR

This carries forward the app feedback changes from gly11#10 and combines them with a local reinstall script for the current app layout. No user home path is hard-coded. The unrelated controller, bus-reset, and old SBP-2 connector changes from that fork branch are excluded.

Changes since the last push

The branch previously carried three commits that also exist in #88 — the XcodeGen pin, the GoogleTest discovery change, and the Swift quiet-mode exit-status fix. Worse, its copy of the pin was the superseded one (hardcoded 2.45.4), which would have failed this PR's own drift check against a pbxproj generated with 2.46.0. All three are dropped; .github/workflows/ and tests/CMakeLists.txt are no longer touched here.

Four defects found during review and hardware validation are fixed:

  • the SIP gate matched disabled anywhere in csrutil status, so a Custom Configuration with any single protection off (Apple Internal: disabled) passed and the install failed later for an unrelated-looking reason
  • activation waited 30s total, but macOS parks the extension in activated waiting for user until someone approves it in System Settings and authenticates — a successful install was reported as a failure because the budget was spent on a human step
  • that timeout also skipped the app reopen, leaving the instance launched with --activate-driver running but never connected, with no indication why
  • replaced apps accumulated in /Applications forever; the three most recent backups are now kept and older ones pruned after the new install verifies

Validation

Host

  • ASFWTests: 195 passed, 0 failed, including 4 new activation-formatter tests
  • bash -n install-asfw.sh
  • SIP gate checked against fully disabled, fully enabled, and Custom Configuration output
  • backup pruning checked in an isolated tree: unrelated bundles and non-conforming names are left alone

ShellCheck was not available locally.

Hardware

Exercised end to end on the test Mac (SIP off, developer mode on):

  • a stuck terminating for uninstall but still running dext was detected before the build, and the run stopped with the reboot instruction rather than continuing into a partial replacement
  • after reboot, a full --fresh --activate run built, signed, verified, installed, and activated; the active dext hash matched the installed build
  • the approval wait and the reopen were then confirmed on a second run, and pruning was confirmed by driving the backup count past the limit

run_maybe_sudo attempts each command unprivileged first. Every privileged step here — ditto, mv, systemextensionsctl uninstall — succeeded without escalating, so that path stayed unused.

Dependency

Retargeted from main to dev. Independent of the other open PRs now that the duplicated CI commits are gone.

gly11 added 7 commits July 29, 2026 01:43
csrutil prints a per-protection breakdown when SIP is in a Custom
Configuration. Searching the whole output for "disabled" matched a single
disabled entry such as "Apple Internal: disabled" on a machine where the
protections that matter are still on, so the installer proceeded and failed
later with an unrelated error.

Match the first line against "status: disabled" instead. Fully disabled and
fully enabled machines behave exactly as before.
Validated on hardware after a reboot. Two defects surfaced on the first
activation of a freshly staged build.

macOS parks the extension in "activated waiting for user" until someone
approves it in System Settings and authenticates. The 30-second budget was
being spent on that human step, so a successful install reported

    The active dext did not switch to the installed build.

even though the dext activated moments later with the expected hash. Give the
approval state its own 300-second budget, and print the exact System Settings
path once on entering it, so the message says what is actually being waited on.

The die() on that timeout also skipped reopen_installed_app_for_debugging. The
instance launched with --activate-driver stays on the activation path and never
calls debugVM.connect(), so the app was left running and disconnected with no
indication why. Reopen before deciding the exit status.

The underlying coupling remains: auto-activation depends on the installer
reopening the app to get a connected instance. Worth removing on the app side
later, but the installer should not be the thing that strands it.
Every install moves the app it replaces to /Applications/ASFW.app.backup-<ts>
so a bad build can be rolled back by hand, but nothing ever removed them. A
few iterations already left three full app bundles sitting in /Applications.

Prune to BACKUP_KEEP_COUNT (3) once the newly installed app has verified — a
failed install must keep every backup it might need to roll back to.

The find pattern matches the exact %Y%m%d-%H%M%S shape install_app_atomically
writes rather than a bare backup-* glob. An unrelated directory under that
prefix would sort above the timestamps and take a slot that should have kept a
real backup, and the timestamps only sort newest-first while every name has the
same width. Each path is re-checked against the prefix and re-tested as a
directory immediately before removal.
@gly11
gly11 force-pushed the sbp2/install-workflow branch from b51a0d8 to f46f8fc Compare July 28, 2026 18:22
@gly11
gly11 changed the base branch from main to dev July 28, 2026 18:22
The committed project on dev was generated with 2.45.4, which orders the
targets array ASFW before ASFWDriver. Homebrew now ships 2.46.0, which orders
it the other way, and the drift check installs xcodegen unpinned — so every PR
targeting dev now fails it regardless of content. The output is otherwise
identical; the diff is one line.

This is the same regeneration mrmidi#88 carries. Whichever lands first makes the
other a no-op. Doing it here lets this PR verify on its own rather than waiting
on the pin work.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant