refactor: daemon self-provisioning — desktop becomes pure gRPC display layer#57
refactor: daemon self-provisioning — desktop becomes pure gRPC display layer#57
Conversation
Privileged host mutations (DNS resolver, Docker socket, routes) are now handled by the Rust arcbox-helper daemon invoked by arcbox-daemon's self-setup or by `arcbox install`. The desktop app no longer manages these operations. Deleted: - ArcBoxHelper/ (XPC daemon target, 6 files) - HelperManager.swift, HelperProtocol.swift (client-side XPC) - LoginItemApprovalSheet.swift (approval UI) - LaunchDaemons/com.arcboxlabs.desktop.helper.plist Cleaned: - ArcBoxApp.swift: remove helperManager lifecycle - ContentView.swift: remove approval sheet - StartupOrchestrator.swift: remove setupHelper step Note: ArcBoxHelper target in project.pbxproj needs manual removal via Xcode (Delete Target).
Adds DOWNLOADING_ASSETS phase, docker_tools_installed field, and GetSetupStatus/WatchSetupStatus RPC methods.
…Manager These responsibilities are now handled by the daemon: - Boot assets: auto-provisioned during daemon startup - Docker CLI tools: installed during daemon recovery phase - CLI runner: no longer needed, desktop doesn't shell out
DaemonManager: Replace /_ping polling with WatchSetupStatus gRPC stream. Daemon liveness is now derived from stream connectivity. Setup phase, infrastructure flags, and status messages are all pushed by the daemon. StartupOrchestrator: Reduce to 2 steps (enableDaemon + connectAndWatch). All provisioning (boot assets, runtime binaries, Docker tools) is handled by the daemon. ArcBoxApp: Remove BootAssetManager, DockerToolSetupManager state. Orchestrator init simplified to daemonManager + onClientsNeeded.
Register the privileged helper as a system-level LaunchDaemon using SMAppService.daemon(). macOS handles the authorization prompt natively (password / Touch ID). This eliminates the need for desktop users to run `abctl install`. Startup sequence is now 3 steps: 1. installHelper — SMAppService.daemon() (one-time, non-critical) 2. enableDaemon — SMAppService.agent() 3. connectAndWatch — gRPC WatchSetupStatus stream The helper plist uses BundleProgram to reference the binary embedded in the app bundle at Contents/Library/HelperTools/ArcBoxHelper.
Makefile wraps the existing package-dmg.sh with dependency targets: make build-rust — cargo build release make prefetch — download boot assets + Docker tools make dmg — unsigned DMG (local testing) make dmg-signed — Developer ID signed DMG Also creates the helper LaunchDaemon plist template for SMAppService.daemon() registration (BundleProgram format).
Replace inline prefetch/build-dmg steps with `make prefetch` and `make dmg-release`. Build logic lives in one place (Makefile + package-dmg.sh); the workflow only handles CI-specific concerns (secrets, artifact upload, Sparkle signing). Local dev and CI now share the exact same build path: Local: make dmg-signed CI: make dmg-release SIGN_IDENTITY="..." NOTARIZE=1
The Swift ArcBoxHelper was replaced by the Rust arcbox-helper in v0.3.0 but the Xcode target and build configurations were left behind. This removes all remnants: - ArcBoxHelper native target + build configurations - Copy Helper Tool build phase (referenced Swift target product) - Target dependency from ArcBox → ArcBoxHelper - Product reference in Products group The Rust helper binary is embedded by fetch-arcbox-binaries.sh and registered via SMAppService.daemon() at runtime. The Generate Helper LaunchDaemon Plist build phase is preserved (it processes the plist template for the Rust helper).
DaemonManager.dockerSocketPath was removed when stripping the polling logic. Inline the path directly since it's a fixed convention.
Copy Rust arcbox-helper binary to Contents/Library/HelperTools/ with reverse-DNS name matching the plist Label, consistent with the daemon naming convention. Fix BundleProgram path in helper plist.
build-rust now runs `make build-agent` (aarch64-unknown-linux-musl) so the Linux guest agent binary is available for bundle embedding. Without this, package-dmg.sh embeds a macOS binary that the guest VM can't execute.
gRPC no longer restarts mid-boot, so 30s timeout is sufficient. Reconnect backoff reduced to 500ms for faster recovery if the daemon restarts.
Replace SMAppService.daemon() with osascript admin password prompt. SMAppService.daemon() is unreliable on macOS 15 — it registers the helper as disabled without notifying the user, and System Settings provides no separate toggle to enable it (see hyperium/tonic#742, hyperium/h2#487, Apple FB13206906). The new approach runs `abctl _install --no-daemon --no-shell` via osascript's 'with administrator privileges', which triggers the standard macOS password dialog. Only prompts on first install; subsequent launches detect the helper socket and skip silently. Also fix gRPC Unix socket connectivity: - Switch from Posix to TransportServices transport - Set http2.authority = "arcbox.local" to avoid RST_STREAM caused by grpc-swift sending the percent-encoded socket path as :authority
SMAppService.daemon() is unreliable on macOS 15 — it registers the helper as disabled in BTM without notifying the user, and System Settings provides no separate toggle to enable it. Replace with osascript 'do shell script ... with administrator privileges' calling `abctl _install --helper-path <bundle-path>`. This triggers the standard macOS password dialog on first launch only. Changes: - DaemonManager: osascript-based installHelper(), checks socket existence - install.rs: add --helper-path flag for custom helper binary location - fetch-arcbox-binaries.sh: copy helper to MacOS/bin/ (next to abctl) - Remove SMAppService.daemon() infrastructure: - LaunchDaemons/com.arcboxlabs.desktop.helper.plist - Xcode "Generate Helper LaunchDaemon Plist" build phase - HelperTools copy in fetch-arcbox-binaries.sh
Paths with spaces (e.g. "ArcBox Desktop.app") were not properly escaped in the do shell script command. Use single-quote wrapping instead of backslash escaping.
…ice implementation)
There was a problem hiding this comment.
Pull request overview
This PR refactors ArcBox Desktop into a thin gRPC UI layer: provisioning/setup responsibilities move fully into the daemon, and the app observes daemon readiness via a new WatchSetupStatus gRPC stream. It also removes the Swift privileged helper target in favor of a Rust helper and updates build/release packaging to embed the new binaries and DRY CI steps via a Makefile.
Changes:
- Replace startup polling (
/_ping) with gRPCWatchSetupStatusstreaming and simplify startup orchestration to 3 steps. - Remove Swift-based provisioning managers (boot assets, Docker tools, CLI runner) and the Swift XPC helper plumbing.
- Update packaging/CI to embed
arcbox-helper, add a Makefile-driven DMG build flow, and regenerate protobuf/gRPC code.
Reviewed changes
Copilot reviewed 26 out of 28 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/fetch-arcbox-binaries.sh | Embed Rust helper alongside abctl in the app bundle (and continue embedding/signing daemon). |
| Packages/ArcBoxClient/Sources/ArcBoxClient/StartupOrchestrator.swift | Reduce startup steps to helper install → daemon enable → connect/watch via gRPC stream. |
| Packages/ArcBoxClient/Sources/ArcBoxClient/HelperProtocol.swift | Remove Swift XPC helper protocol (daemon now owns provisioning). |
| Packages/ArcBoxClient/Sources/ArcBoxClient/HelperManager.swift | Remove Swift helper registration/XPC manager. |
| Packages/ArcBoxClient/Sources/ArcBoxClient/Generated/api.pb.swift | Add SetupStatus message and phases for daemon setup progress reporting. |
| Packages/ArcBoxClient/Sources/ArcBoxClient/Generated/api.grpc.swift | Add GetSetupStatus and WatchSetupStatus RPCs to SystemService. |
| Packages/ArcBoxClient/Sources/ArcBoxClient/DockerToolSetupManager.swift | Remove Docker CLI tools installation manager (daemon now provisions). |
| Packages/ArcBoxClient/Sources/ArcBoxClient/DaemonManager.swift | Observe daemon readiness via gRPC stream; install helper via osascript + abctl _install. |
| Packages/ArcBoxClient/Sources/ArcBoxClient/CLIRunner.swift | Remove local CLI invocation wrapper (daemon-driven provisioning). |
| Packages/ArcBoxClient/Sources/ArcBoxClient/BootAssetManager.swift | Remove boot asset management from desktop app. |
| Packages/ArcBoxClient/Sources/ArcBoxClient/ArcBoxClient.swift | Switch transport to TransportServices and set http2.authority for unix-socket gRPC. |
| Packages/ArcBoxClient/Package.swift | Swap gRPC transport dependency product to TransportServices. |
| Packages/ArcBoxClient/Package.resolved | Update resolved pins (including sentry-cocoa). |
| PLAN.helper.md | Remove stale Swift helper implementation plan doc. |
| Makefile | Add consolidated local/CI targets for building rust binaries, prefetch, and DMG packaging. |
| LaunchDaemons/com.arcboxlabs.desktop.helper.plist | Remove old helper LaunchDaemon plist (Swift helper removed). |
| ArcBoxHelper/main.swift | Remove Swift privileged helper implementation (replaced by Rust helper). |
| ArcBoxHelper/Logging.swift | Remove Swift helper logging utilities. |
| ArcBoxHelper/HelperProtocol.swift | Remove duplicate helper protocol definition in helper target. |
| ArcBoxHelper/HelperOperations.swift | Remove Swift helper operations implementation. |
| ArcBoxHelper/ArcBoxHelperInfo.plist | Remove Swift helper Info.plist. |
| ArcBoxHelper/ArcBoxHelper.entitlements | Remove Swift helper entitlements file. |
| ArcBox/Views/LoginItemApprovalSheet.swift | Remove UI for SMAppService login item approval workflow. |
| ArcBox/Views/ContentView.swift | Remove helper approval sheet wiring from the main UI. |
| ArcBox/Models/DockerTerminalSession.swift | Inline DOCKER_HOST socket path (no longer referencing removed constant). |
| ArcBox/ArcBoxApp.swift | Remove old managers; initialize orchestrator with client factory; use stopWatching(). |
| ArcBox.xcodeproj/project.pbxproj | Remove Swift helper target/copy phases and related project wiring. |
| .github/workflows/release.yml | Use Makefile targets for prefetch and DMG release packaging; include arcbox-helper in rust build. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
💡 Codex Review
arcbox-desktop/.github/workflows/release.yml
Lines 173 to 175 in 0d651e9
arcbox-helper in the prebuilt release path
When the workflow reuses prebuilt arcbox assets, this block only stages abctl, arcbox-daemon, and arcbox-agent. The Xcode build phase in scripts/fetch-arcbox-binaries.sh now looks for target/release/arcbox-helper and only emits a warning if it is missing, while DaemonManager.installHelper() always invokes abctl _install --helper-path .../Contents/MacOS/bin/arcbox-helper. In other words, any DMG built from prebuilt assets will ship without the helper, and first-launch helper installation fails on clean machines, breaking DNS/docker-socket/route integration.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- Sign arcbox-helper in fetch-arcbox-binaries.sh (required for notarization) - Guard helper binary exists before triggering osascript password prompt - Fix misleading log dir comment (plist uses /tmp/, create run/ instead) - Use throwing Task.sleep for cancellation support in connect wait loop - Add TODO for helper version check on app update
Compare installed vs bundled helper version using `--version` output
instead of SHA256 hashing. On app update, if the bundled helper has
a newer version, re-run _install with admin prompt. Uses existing
Cargo version infrastructure (env!("CARGO_PKG_VERSION")), no build-time
preparation needed.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 28 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Fix stale docstring on installHelper (socket → version check) - Fix stale comment on applySetupStatusSync - Remove duplicate installedHelperPath / dead helperSocket constant - Makefile: guard prefetch against missing ABCTL, guard clean against missing ARCBOX_DIR - Add --timestamp to helper codesign for notarization consistency
Summary
Desktop app stripped to a pure gRPC display layer. All provisioning moved to the daemon.
Removed
BootAssetManager.swift,CLIRunner.swift,DockerToolSetupManager.swift— daemon handles all provisioningSMAppService.daemon()helper registration — unreliable on macOS 15 (BTM marks daemon as disabled without user-visible toggle)PLAN.helper.md— stale documentation describing deleted Swift implementationChanged
/_ping→ gRPCWatchSetupStatusstream. Helper install via osascript admin password prompt (first launch only)http2.authority = "arcbox.local"(fixes RST_STREAM with tonic over Unix socket)Build
make dmg-signed)fetch-arcbox-binaries.shembeds helper + agent in bundlebuild-rusttargetProto
Depends on
Test plan
swift build(ArcBoxClient package)make dmg-signed)docker runworks via ArcBox context