Ship DMG install with SMAppService and durable locks - #8
Merged
Conversation
Users can install from a disk image into Applications with Login Items registration, and ad-hoc helper upgrades rebind instead of sitting in launchd's codesign throttle. Resource locks now live in the daemon so a mid-lock crash still resumes paused servers.
There was a problem hiding this comment.
Pull request overview
This PR adds a DMG-first installation flow for devctl.app that relocates into /Applications, registers the daemon via Login Items (SMAppService), and improves daemon resilience: durable resource locks across daemon crashes plus faster helper recovery after ad-hoc upgrades (unregister/re-register rebind path). It also tightens process teardown correctness (PID reuse revalidation), improves health probing behavior, and upgrades Spotlight indexing to preserve engagement signals across incremental syncs.
Changes:
- Add an in-app setup panel and installer plumbing (app/CLI/daemon packaging, relocation, hook offers), plus a notarized DMG build + release workflow.
- Persist daemon resource locks (
locks.json) so paused servers can be resumed after crashes, and move lock pause/resume ownership into the daemon. - Improve lifecycle reliability: SMAppService agent (ensure/unregister deep links, rebind policy), process-tree teardown revalidation, and health probing with a dedicated ephemeral URLSession.
Reviewed changes
Copilot reviewed 48 out of 51 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| Tests/DevCtlKitTests/WireTests.swift | Adds regression test for atomic concurrent writes. |
| Tests/DevCtlKitTests/SpotlightLabelTests.swift | Tests alternate names and ranking hint tiers. |
| Tests/DevCtlKitTests/SetupPlannerTests.swift | Adds unit tests for setup/upgrade planning and installs. |
| Tests/DevCtlKitTests/DeepLinkNotificationActionTests.swift | Tests default notification tap mapping to .open. |
| Tests/DevCtlKitTests/DaemonRecoveryPolicyTests.swift | Tests recovery/rebind policies and launchd ExitTimeOut ceiling. |
| Tests/DevCtlDaemonCoreTests/ResourceLockTests.swift | Adds durable-lock behavior tests (pause/refuse/resume/recover). |
| Tests/DevCtlDaemonCoreTests/ProcessTreeTests.swift | Tests PID-reuse-safe identities and sweep result shapes. |
| Sources/DevCtlKit/Spotlight/SpotlightLabel.swift | Adds Spotlight alternateNames + rankingHint helpers. |
| Sources/DevCtlKit/Setup/SetupPlanner.swift | New installer planning + staged installs + quarantine clearing. |
| Sources/DevCtlKit/Protocol/Wire.swift | Extends lock wire types and adds locks file/result models. |
| Sources/DevCtlKit/Paths/Paths.swift | Adds agent.path, agent.rebind, locks.json paths; fixes temp naming. |
| Sources/DevCtlKit/Launchd/LaunchdAdmin.swift | New shared LaunchdAdmin with app-first SMAppService path and legacy fallback. |
| Sources/DevCtlKit/Launchd/DaemonRecoveryPolicy.swift | Adds poll-loop recovery decision logic with cooldown/intent. |
| Sources/DevCtlKit/Launchd/AgentRebindPolicy.swift | Adds rebind heuristics for post-upgrade helper recovery. |
| Sources/DevCtlKit/DeepLink/DeepLinkNotificationAction.swift | Supports body-tap default action id mapping to open. |
| Sources/DevCtlDaemonCore/Supervisor/ServerSupervisor.swift | Uses descendant sweep result + PID revalidation on escalation. |
| Sources/DevCtlDaemonCore/Supervisor/ProcessTree.swift | Introduces ProcessIdentity + result-typed sweep + revalidation. |
| Sources/DevCtlDaemonCore/Health/HealthProber.swift | Uses dedicated ephemeral URLSession with short timeouts. |
| Sources/DevCtlDaemonCore/Control/ControlServer.swift | Persists locks, adds crash recovery for locks, improves recoverAtStartup parallelism, and adds NW listener/connection state logging. |
| Sources/devctld/main.swift | Applies agent.path PATH, improves fd limit handling, and unregisters power notifications on SIGTERM. |
| Sources/devctld/Info.plist | Adds embedded Info.plist for helper identity. |
| Sources/DevCtlApp/SpotlightIndexer.swift | Switches to incremental indexing with engagement preservation + ranking tiers. |
| Sources/DevCtlApp/SetupSession.swift | Adds shared launch-time setup evaluation state. |
| Sources/DevCtlApp/SetupPerformer.swift | Implements setup execution (copy/install/register hooks/relaunch). |
| Sources/DevCtlApp/SetupPanel.swift | Adds first-run/upgrade UI with confirm flow. |
| Sources/DevCtlApp/DevCtlApp.swift | Wires setup window + improves notification presentation + daemon-down UI. |
| Sources/DevCtlApp/DaemonModel.swift | Adds auto-recovery state machine with intent/cooldown and SMAppService preference. |
| Sources/DevCtlApp/AppDeepLink.swift | Adds daemon control deep links (ensure/unregister) and best-effort browser open. |
| Sources/DevCtlApp/AgentService.swift | Implements SMAppService registration/unregister/rebind/approval handling. |
| Sources/devctl/LaunchdAdmin.swift | Removes CLI-local LaunchdAdmin (now in DevCtlKit). |
| Sources/devctl/HookSupport.swift | Adds CLISelf.daemonSibling for install candidates. |
| Sources/devctl/CLI.swift | Routes daemon lifecycle through new LaunchdAdmin + updates lock to daemon-owned pause/resume. |
| scripts/smoke.sh | Strengthens cleanup (stray grandchild reap) + validates app bundle contents. |
| scripts/smoke-launchd.sh | Forces legacy path for deterministic launchd smoke. |
| scripts/notarize.sh | Adds notarytool submit/staple script with safer key handling. |
| scripts/make-dmg.sh | Adds DMG builder with optional Finder layout enforcement. |
| scripts/make-dmg-background.swift | Generates DMG background image at build time. |
| scripts/make-app-bundle.sh | Bundles CLI/daemon + in-bundle LaunchAgent, adds signing improvements. |
| README.md | Updates quick start and build instructions for DMG/app flow. |
| Package.swift | Embeds helper Info.plist via linker flags. |
| Makefile | Adds dmg target; installs devctld sibling in make install. |
| docs/design.md | Updates design doc for DMG/SMAppService/locks/Spotlight changes. |
| docs/cli-contract.md | Updates lock semantics to daemon-owned + durable lock recovery. |
| CONTRIBUTING.md | Documents DMG release workflow and required secrets. |
| CLAUDE.md | Updates codebase map and operational notes for new installer/agent paths. |
| BACKLOG.md | Removes now-delivered DMG item; updates remaining release work. |
| .gitignore | Ignores dist/. |
| .github/workflows/release-dmg.yml | Adds macOS workflow to build/sign/notarize/staple and upload DMG. |
| .changeset/dmg-smappservice-rebind.md | Adds minor changeset for DMG + locks + rebind behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+202
to
+203
| proc.standardOutput = Pipe() | ||
| proc.standardError = Pipe() |
Comment on lines
+309
to
+312
| let pipe = Pipe() | ||
| proc.standardOutput = pipe | ||
| proc.standardError = Pipe() | ||
| do { |
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.
Summary
/Applicationswith an in-app setup panel and Login Items (SMAppService) agent registrationTest plan
make testscripts/smoke.shmake dmg(double-click app on volume → confirm → Applications handoff + Login Items)/Applications/devctl.app: daemon answers within a few seconds (no ~20s throttle hang)devctl lock, killdevctld, confirm paused servers resume when the daemon returnsscripts/smoke-launchd.shif touching agent lifecycle locally (mutates user launchd; refuses when an agent is already installed)