refactor(helper): migrate to SMAppService for macOS 13+ helper install#3194
Draft
marxo126 wants to merge 1 commit intoexelban:masterfrom
Draft
refactor(helper): migrate to SMAppService for macOS 13+ helper install#3194marxo126 wants to merge 1 commit intoexelban:masterfrom
marxo126 wants to merge 1 commit intoexelban:masterfrom
Conversation
SMJobBless was deprecated in macOS 13. As of macOS 26.4.1 the OS-level
smd daemon now rejects SMJobBless installs that previously worked,
silently breaking fan control for end users on the latest macOS:
smd: App "eu.exelban.Stats" did not pass helper check
"eu.exelban.Stats.SMC.Helper".
Migrate to the modern SMAppService API for macOS 13+, keeping the
existing SMJobBless flow as a fallback for macOS 11/12.
Changes:
- New `SMC/Helper/eu.exelban.Stats.SMC.Helper.plist` declaring
`Label`, `MachServices`, and `BundleProgram` pointing to the helper
binary at `Contents/Library/LaunchServices/...` (its existing path,
unchanged).
- New Copy Files build phase in `Stats.xcodeproj/project.pbxproj`
copies the plist to `Contents/Library/LaunchDaemons/` in the app
bundle, where SMAppService daemons are loaded from.
- `Kit/helpers.swift`:
- `install(completion:)` branches on macOS version. macOS 13+ calls
`SMAppService.daemon(plistName:).register()`. Older macOS keeps
the existing SMJobBless + AuthorizationCreate flow intact.
- `uninstall()` similarly branches to `service.unregister()` on 13+.
- `isInstalled` now queries `service.status` on 13+.
- New `requiresApproval` property surfaces the
`.requiresApproval` SMAppService status so callers can show
"approve in System Settings → Login Items & Extensions"
guidance. (Not yet wired into popup UI — separate UI patch.)
- Helper code (`SMC/Helper/main.swift`) and existing
`SMC/Helper/Launchd.plist` left untouched. The legacy plist is
still consumed by the Helper target itself for the SMJobBless path.
Note on testing: the changes are compile-verified and the bundled
plist lands at the expected `Contents/Library/LaunchDaemons/...`
path. SMAppService specifically requires a Developer ID-signed app to
register a daemon (unlike SMJobBless which permitted unsigned testing
within reason), so behavioral verification needs to happen against a
properly-signed build of Stats.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
SMJobBless was deprecated in macOS 13. As of macOS 26.4.1 the OS-level smd daemon now rejects SMJobBless installs that previously worked, silently breaking fan control for end users on the latest macOS:
```
smd: App "eu.exelban.Stats" did not pass helper check "eu.exelban.Stats.SMC.Helper".
```
Reproduces consistently on a fresh macOS 26.4.1 install — clicking Install fan helper silently fails, no helper files land at `/Library/PrivilegedHelperTools/`, and the popup keeps showing the placeholder forever. Affects every user who upgrades to macOS 26.
Change
Migrate the install/uninstall flow to the modern `SMAppService` API (macOS 13+) while keeping the existing SMJobBless code path intact as a fallback for macOS 11/12.
User flow on macOS 13+
Net diff
```
3 files changed, +89 lines net
SMC/Helper/eu.exelban.Stats.SMC.Helper.plist | +14 (new)
Kit/helpers.swift | +55
Stats.xcodeproj/project.pbxproj | +14 (PBXFileReference, PBXBuildFile, PBXCopyFilesBuildPhase, group children, build phase wiring)
```
Testing — important caveat
Compile-verified (`BUILD SUCCEEDED`, plist lands at `Contents/Library/LaunchDaemons/eu.exelban.Stats.SMC.Helper.plist` in the built bundle). Direct API probe of `SMAppService.daemon(plistName:).register()` against the dev build returns `SMAppServiceErrorDomain code 3 — Codesigning failure loading plist (-67028)`, which is the expected rejection of unsigned dev builds — `SMAppService` strictly requires a Developer ID signature.
Behavioural verification therefore requires a Developer ID-signed build, which I don't have locally. The PR is filed as Draft so you can build with your signing cert, register the daemon once, and confirm the user-side flow (System Settings prompt → approval → helper bootstraps).
If the registration call succeeds and the System Settings approval prompt appears, the migration is good. If `register()` throws on a properly-signed build, the most likely culprits are:
Happy to iterate on any of the above.
Pre-13 fallback
`installSMJobBless()` and the older uninstall path are unchanged — verbatim from main. Any users on macOS 11/12 see no behavioural difference.