Skip to content

refactor: daemon self-provisioning — desktop becomes pure gRPC display layer#57

Merged
AprilNEA merged 19 commits intomasterfrom
refactor/daemon-self-provision
Mar 21, 2026
Merged

refactor: daemon self-provisioning — desktop becomes pure gRPC display layer#57
AprilNEA merged 19 commits intomasterfrom
refactor/daemon-self-provision

Conversation

@AprilNEA
Copy link
Member

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 provisioning
  • Swift ArcBoxHelper Xcode target — replaced by Rust arcbox-helper
  • SMAppService.daemon() helper registration — unreliable on macOS 15 (BTM marks daemon as disabled without user-visible toggle)
  • PLAN.helper.md — stale documentation describing deleted Swift implementation

Changed

  • DaemonManager: polling via /_ping → gRPC WatchSetupStatus stream. Helper install via osascript admin password prompt (first launch only)
  • StartupOrchestrator: 6 steps → 3 steps (installHelper → enableDaemon → connectAndWatch)
  • ArcBoxApp: removed dead manager state, simplified orchestrator init
  • ArcBoxClient: Posix → TransportServices transport, http2.authority = "arcbox.local" (fixes RST_STREAM with tonic over Unix socket)

Build

  • Makefile for local DMG builds (make dmg-signed)
  • CI release workflow uses Makefile targets (DRY)
  • fetch-arcbox-binaries.sh embeds helper + agent in bundle
  • Agent cross-compilation in build-rust target

Proto

  • Regenerated from updated api.proto (DOWNLOADING_ASSETS phase, docker_tools_installed field, GetSetupStatus/WatchSetupStatus RPCs)

Depends on

Test plan

  • swift build (ArcBoxClient package)
  • DMG build + sign (make dmg-signed)
  • First launch: osascript password prompt installs helper
  • gRPC stream: Desktop observes INITIALIZING → READY progression
  • Helper installs DNS resolver, Docker socket, container routes
  • Container IP direct access (172.17.0.x) verified
  • docker run works via ArcBox context

AprilNEA added 16 commits March 20, 2026 23:26
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.
Copilot AI review requested due to automatic review settings March 21, 2026 11:44
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 gRPC WatchSetupStatus streaming 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.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

CLI_BIN=$(find "$DOWNLOAD_DIR" -type f -name "abctl" | head -1)
DAEMON_BIN=$(find "$DOWNLOAD_DIR" -type f -name "arcbox-daemon" | head -1)
AGENT_BIN=$(find "$DOWNLOAD_DIR" -type f -name "arcbox-agent" | head -1)

P1 Badge Fetch 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.
Copilot AI review requested due to automatic review settings March 21, 2026 13:02
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
@AprilNEA AprilNEA merged commit c173980 into master Mar 21, 2026
3 checks passed
@AprilNEA AprilNEA deleted the refactor/daemon-self-provision branch March 21, 2026 13:39
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.

2 participants