Skip to content

feat(dispatch): unblock one-click CLI install and add model config sync - #1884

Merged
bobleer merged 2 commits into
GCWing:mainfrom
bobleer:bob/dispatch-cli-install-and-model-sync
Jul 30, 2026
Merged

feat(dispatch): unblock one-click CLI install and add model config sync#1884
bobleer merged 2 commits into
GCWing:mainfrom
bobleer:bob/dispatch-cli-install-and-model-sync

Conversation

@bobleer

@bobleer bobleer commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Preparing an SSH dispatch target failed at two gates that had no path forward from the UI: the CLI showed "not installed or unreachable" while one-click install refused to run, and the model check reported "no model configuration available on the target" with no way to supply one.

One-click bitfun-cli install

The install UI already existed; it was blocked at two fail-closed points in the backend.

1. Missing trust root. The release signing key was only injected at build time from BITFUN_RELEASE_PUBKEY, so any build without that secret refused to install, reporting this build has no BitFun release signing key; refusing to install executable code.

This embeds the official minisign public key (key ID 50F47CBE6CC0A376) as the default trust root. It is public data — every release already ships it as the minisign.pub asset — and downloads stay pinned to the official repository, so trusting the matching official key here does not widen what a build will execute. BITFUN_RELEASE_PUBKEY still takes precedence, so forks publishing their own releases are unaffected.

The CLI self-updater in self_update.rs uses the same default, which upgrades its previous "no key, fall back to checksum-only" behaviour to mandatory signature verification.

Before embedding it, the published v0.2.14 CLI archive signature was verified against this key with a local minisign binary to confirm it matches.

2. .tar.gz.sha256.sig returns 404 on existing releases. The checksum sidecars were not yet signed when those releases were published. Only a definite 404 now degrades to reading the unsigned .sha256 (used solely to show the digest for consent); every other failure stays fatal, so a flaky network cannot silently be taken as "unsigned sidecar". The archive's own minisign signature remains mandatory — the install path never degrades to checksum-only.

One-click model configuration sync

Adds dispatch_sync_model_config, wired from the Rust transport through to the web UI:

  • Locates the target's config directory per OS (macOS ~/Library/Application Support/bitfun/config, Linux ${XDG_CONFIG_HOME:-$HOME/.config}/bitfun/config)
  • Merges only the four ai model keys (models, default_models, agent_model_defaults, func_agent_models); every other target setting is preserved
  • Aborts rather than overwrite when an existing app.json cannot be read or parsed
  • Writes atomically via a temp file plus mv -f, with chmod 600
  • The controller first confirms this device has at least one enabled model

The payload carries API credentials, so the UI gates it behind its own explicit confirmation that says so. The entry point appears only when the target CLI answered but reported no usable model, and the dialog re-probes the target once the sync completes.

The new command is registered in the Desktop invoke handler and the Server Host route table, and added to all three peer-host keep-local tables so the existing cross-language contract test keeps it off the wire.

Verification

  • bitfun-services-integrations remote_ssh: 101 passed (including the new trust-root test and two model-config merge tests)
  • bitfun-server: 14 passed, including the contract test claims_only_the_narrow_dispatch_contract
  • bitfun-cli peer_host: 71 passed; self_update: 10 passed
  • bitfun-desktop / bitfun-core: cargo check clean
  • Web UI: tsc --noEmit clean, dispatch suite 34 passed (3 new cases covering when the sync card appears, that declining the confirmation writes nothing, and that a late sync acknowledgement is discarded after the dialog closes)

Notes

dispatch/README.md gains invariant 18, recording the boundary for model-config sync: separate explicit confirmation, merges only the ai model keys, aborts on an unreadable target config, and writes owner-only via a temp-file rename.

bobleer added 2 commits July 29, 2026 23:45
Preparing an SSH dispatch target failed at two gates that had no path
forward from the UI.

**One-click CLI install was blocked by a missing trust root.** The release
signing key was only injected at build time from `BITFUN_RELEASE_PUBKEY`,
so any build without that secret refused to install with "this build has no
BitFun release signing key". Embed the official minisign public key
(`50F47CBE6CC0A376`) as the default trust root — it is public data that every
release already ships as its `minisign.pub` asset, and downloads stay pinned
to the official repository, so this does not widen what a build will execute.
`BITFUN_RELEASE_PUBKEY` still takes precedence for forks publishing their own
releases. The CLI self-updater uses the same default, which upgrades its
previous checksum-only fallback to mandatory signature verification.

Releases published before the CLI checksum sidecars were signed return 404
for `.tar.gz.sha256.sig`. Treat only a definite 404 as "unsigned sidecar" and
keep every other failure fatal, so a flaky network cannot silently downgrade
verification. The archive's own minisign signature stays mandatory — the
install path never degrades to checksum-only.

**No way to give the target a model.** Add `dispatch_sync_model_config`,
which merges only the four `ai` model keys into the target's `app.json`,
preserves every other target setting, aborts rather than overwrite an
unreadable or unparseable config, and writes owner-only via a temp-file
rename. The payload carries API credentials, so the UI gates it behind its
own explicit confirmation that says so, and it is offered only when the
target CLI answered but reported no usable model.

The new command is registered in the Desktop invoke handler and the Server
Host route table, and added to all three peer-host keep-local tables so the
existing cross-language contract test keeps it off the wire.
`every_registered_command_declares_a_remote_workspace_policy` failed on all
three platforms: `dispatch_sync_model_config` was registered in
`generate_handler!` but missing from `REMOTE_WORKSPACE_COMMAND_POLICIES`.

It is `WorkspaceAgnostic` like every other dispatch command — the target is
addressed by its own connection id, never by the currently open workspace.
@bobleer
bobleer merged commit 6d3caea into GCWing:main Jul 30, 2026
7 checks passed
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