Appearance
Remote-URL Updates
Hive can fetch the daemon (hived), the CLI (hive), and the desktop / mobile app from an arbitrary HTTP(S) URL hosting a JSON manifest. This is independent of the desktop app's download-from-connected-daemon flow - it lets you stand up an update source behind any plain static HTTP server (S3, GitHub Pages, nginx, a raw folder served by python -m http.server for testing, etc.).
Manifest format
A manifest is a single JSON document. Every entry carries a SHA-256 digest that is verified after download - the manifest itself is not signed, so the authenticity of the bytes ultimately rests on the transport (TLS) and the hash check.
json
{
"version": "1.11.1",
"build_date": "2026-05-12T11:00:00Z",
"artifacts": [
{
"kind": "daemon",
"platform": "linux-x64",
"filename": "hived",
"sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"size": 12345678,
"url": "hived",
"build_date": "2026-05-12T11:00:00Z"
},
{
"kind": "cli",
"platform": "linux-x64",
"filename": "hive",
"sha256": "abcd...",
"size": 9876543,
"url": "hive"
},
{
"kind": "app_installer",
"platform": "windows-x64",
"filename": "Hive-win-x64-1.11.1-setup.exe",
"sha256": "feed...",
"size": 87654321,
"url": "Hive-win-x64-1.11.1-setup.exe"
},
{
"kind": "android_apk",
"platform": "android-arm64",
"filename": "Hive-android-universal-1.11.1.apk",
"sha256": "cafe...",
"size": 30000000,
"url": "Hive-android-universal-1.11.1.apk"
}
]
}Notes:
kindis one ofdaemon,cli,app_installer,android_apk.platformslugs:windows-x64,linux-x64,linux-arm64,macos-x64,macos-arm64,android-arm64.urlis the location of the artifact bytes. Absolute URLs are used verbatim. Relative URLs are resolved against the manifest URL, so a folder layout likehttps://updates.example.com/hive/{manifest.json,hived,hive,...}works out of the box.build_dateis optional. When present, it is written next to the artifact as a<filename>.builddatesidecar - the daemon's artifact picker uses that to prefer fresh builds over stale siblings.sha256must be the lowercase hex digest of the artifact bytes. Mismatch aborts the update and removes the partial file.
Release channels (semi-stable / unstable)
Updates are published on two channels that share one accumulating folder of versioned binaries - only the manifest each channel points at differs:
| Channel | Manifest file | Who tracks it | When it moves |
|---|---|---|---|
| semi-stable (default) | manifest.json | the whole fleet, unless overridden | only on an explicit promote |
| unstable | manifest-unstable.json | machines opted in to newest builds | every deploy distribution |
manifest.json is also what the nginx index serves, so a base URL with no filename (https://hive.vazac.dev/) resolves to the semi-stable channel.
Nodes and clients pick a channel rather than a raw URL:
- Node: the
[update].channelfield inconfig.tomldrives hived's own self-update and its auto-check broadcast. Edit it from Cluster → Edit Cluster Configuration → Cluster Update Source; it replicates to peers. - Client (app): the channel is stored as a global preference (
hive-update-channel) so it rides the cluster-preferences push/pull alongside every other setting. Edit it from Settings → Updates.
Publishing and promoting
bash
# Build, then publish the new version to the UNSTABLE channel:
pwsh ./scripts/hive.ps1 build all
pwsh ./scripts/hive.ps1 deploy distribution # writes manifest-unstable.json
# Once an unstable build is vetted, promote it to semi-stable:
pwsh ./scripts/hive.ps1 promote # copies -> manifest.jsondeploy distribution always writes manifest-unstable.json (newest version per artifact) and, on the very first publish, seeds manifest.json from it so existing installs keep resolving. After that, manifest.json only changes when you run promote, which copies the current unstable manifest verbatim - the versioned artifacts it references already live in the folder, so no rebuild is needed.
App updates (Settings → Updates → App updates)
Settings has a standalone Updates tab. Its top card, App updates, drives this app's own self-update from the connected daemon and is built for unattended, interruption-tolerant downloads:
- Background, resumable download. Click Download (or enable auto-download) and the artifact streams to disk in the background. The bytes are saved to a
<filename>.partialsidecar as they arrive; if the transfer is interrupted (you leave, the socket drops, the app restarts), the next attempt sends an HTTPRangerequest and resumes from where it left off instead of starting over. The daemon's/clients/{kind}/{platform}endpoint advertisesAccept-Ranges: bytesand answers206 Partial Contentto make this possible. - Staged reuse — download once, install later. A completed, SHA-256 verified download is kept on disk. When the same update is rediscovered (on reconnect or relaunch) the app detects the staged file and jumps straight to Ready to install — so a cancelled or deferred install never re-downloads.
- Auto-download toggle. Off by default. When on, a newly discovered update is fetched in the background automatically so Install is instant. The preference is stored per device in
localStorage. - Install applies the staged artifact via the platform installer (NSIS on Windows, DMG on macOS,
.deb/AppImage self-replace on Linux, system PackageInstaller on Android).
The same staged + resumable download powers the auto Install now banner, so banner installs are equally interruption-tolerant.
Persistent settings (Settings → Updates → Update source)
The Update source card (below App updates in the same tab) persists the manifest URL, optional bearer token, auto-check toggle, and interval across restarts. The same four fields are also editable from Cluster → Edit Cluster Configuration under the Cluster Update Source section - either surface saves to the same daemon-replicated settings.
- When connected to a daemon: Save writes to the daemon's
[update]section inconfig.tomland broadcasts to every cluster peer viaPeerMessage::SyncRemoteUpdateConfig. Each node ends up with the same URL/token in its local config, so a peer can still self-update on schedule even when other nodes are offline. - When not connected: Save stores the settings in browser
localStorageonly. The app keeps polling the URL locally so the desktop client can still receive an "update available" banner.
The token is propagated alongside the URL because cluster peers already share the cluster_token over the same authenticated peer transport - treat the manifest token with the same care.
Updating the daemon / CLI from the desktop app (Settings → Daemon → Local Daemon)
The Local Daemon card on the Daemon tab exposes one-click updates for hived and hive that pull from the configured manifest URL + channel (the same settings shown on the Updates tab):
- Update - updates the local daemon from the resolved manifest URL.
- When connected to the local node, it asks the daemon to self-update over the existing WebSocket and restart itself (the gentle path).
- When not connected, the app downloads the
daemonartifact for this platform directly (no connection needed), verifies the SHA-256, stops the daemon, replaces the binary in place, and restarts it. As with the CLI update, replacing a binary in a system directory may require elevated privileges and surfaces a clear error when permission is denied.
- Update CLI - downloads the
cliartifact for this platform directly (no daemon involvement), verifies the SHA-256, and replaces the localhivebinary in place. If the binary lives in a system directory (/usr/local/bin,C:\Program Files\Hive), the replacement may require elevated privileges and surfaces a clear error when permission is denied.
[update] section in config.toml
toml
[update]
manifest_url = "https://updates.example.com/hive/" # base directory URL
channel = "semi-stable" # or "unstable"
token = "optional-bearer-token"
auto_check = true
interval_secs = 21600 # 6 hoursmanifest_url is the base directory; the concrete manifest file is derived from channel (see Release channels). A base that already ends in a *.json filename has that segment replaced, so both https://host/ and https://host/manifest.json resolve correctly.
Missing section → defaults to auto_check = true, interval_secs = 21600, channel = "semi-stable", base URL https://hive.vazac.dev/.
Auto-check + manual install confirmation
When auto_check = true and manifest_url is set, hived spawns a background task at startup that:
- Sleeps for
interval_secs. - Fetches the manifest, compares its
versionagainstenv!("CARGO_PKG_VERSION"). - If newer, broadcasts
ServerMessage::RemoteUpdateAvailableto every connected client. The desktop app surfaces an "Install on this device / Update connected daemon" banner - the install itself is always manual: hived never auto-applies an update without explicit user confirmation.
Interval changes propagate on the next tick (no restart needed). Settings re-read every cycle, so cluster-replicated changes via SyncRemoteUpdateConfig take effect within interval_secs on every node.
Endpoints
All three targets accept an optional Bearer token via HIVE_UPDATE_TOKEN env var (CLI) or the dialog (desktop). When set, every HTTP request to the manifest URL and to each artifact gets Authorization: Bearer <token>. Public URLs work with no token.
CLI self-update from URL
bash
# Default channel (semi-stable) from the default base URL:
hive update
# Follow the unstable channel instead:
hive update --channel unstable
# Custom base URL, with a bearer token:
HIVE_UPDATE_TOKEN=secret hive update --from https://updates.example.com/hive/
# or
hive update --from https://... --remote-token secret--from is the base directory URL and --channel selects the manifest file. When either flag is omitted, the CLI reads the local hived config's [update] section and follows the same manifest_url and channel the node itself tracks (falling back to https://hive.vazac.dev/ and semi-stable when there is no config). --remote-token likewise falls back to the config's [update].token. So a plain hive update on an unstable-channel node pulls the unstable CLI with no flags. The CLI downloads the cli artifact for its own platform, verifies the SHA-256, replaces the running binary, and re-execs.
Daemon self-update from URL
The daemon doesn't poll on its own - it self-updates when asked, via the WebSocket. You can drive that from the CLI:
bash
hive update-node --from https://updates.example.com/hive/manifest.jsonA bare hive update-node updates the local node, following the [update] manifest URL and channel the node already tracks.
Add --refresh-artifacts to ask the daemon to additionally download every cli / app_installer / android_apk / cross-platform daemon entry from the manifest into the daemon's local clients/ and daemons/ trees. After the daemon restarts, connected CLIs and desktop apps can pull the fresh artifacts via the existing daemon-bundled-artifacts flow. Each peer daemon is brought up to date by running its own URL self-update against the same manifest.
The daemon side never auto-polls. Trigger an update explicitly - cron, systemd timer, a CI deploy step, or a human at the dialog.
Desktop app update from URL
In the Settings view, open Update from URL…. The dialog lets you pick the target (this app, or the currently connected daemon), paste the manifest URL, and optionally enter a bearer token. The "Also refresh client artifacts on the daemon" checkbox is the UI form of --refresh-artifacts.
On desktop the installer is downloaded into the user's Downloads folder and run automatically (NSIS /S on Windows, open on macOS, pkexec apt install -y for .deb, replace-and-restart for AppImage). On Android the APK is staged into the app cache and handed to the system PackageInstaller via the JS bridge.
URL updates from the Cluster view
The Cluster view's toolbar has two update actions, both shown once a manifest URL is configured:
- Check Updates fetches the configured manifest and refreshes the per-node buttons so you can see which nodes are behind.
- Update All fetches the manifest and then drives a URL self-update on every node (peers first, the connected node last) whose running version is older than the manifest. It reports "All nodes up to date" when nothing is behind.
Each node card also shows an Update from URL (vX.Y.Z) button - the connected node and each peer - once a manifest version newer than that node is known. The manifest version is populated by Check Updates, the Check now button under Cluster → Edit Cluster Configuration → Cluster Update Source, the daemon's auto-check broadcast, or the client-side poll.
Triggering an update asks the connected daemon to drive it:
- Connected node: the daemon runs the same flow as the
update-node --fromCLI path locally. - Peer: the connected daemon forwards a
SelfUpdateFromUrlover the inter-node WebSocket. The peer fetches the manifest itself, picks the daemon artifact for its own platform, verifies the SHA-256, and restarts - the manifest URL/token travel over the authenticated peer transport, the binary bytes never do. A manifest with no artifact for the peer's platform comes back as a failed update ack.
Because every node downloads from the manifest URL directly, no node needs to host binaries for another node's platform.
Producing a manifest
A simple shell helper to generate manifest.json from a dist/ folder:
bash
sha() { sha256sum "$1" | cut -d' ' -f1; }
sz() { stat -c%s "$1"; }
cat > manifest.json <<EOF
{
"version": "$VERSION",
"build_date": "$(date -u +%FT%TZ)",
"artifacts": [
{"kind":"daemon","platform":"linux-x64","filename":"hived","sha256":"$(sha hived)","size":$(sz hived),"url":"hived"},
{"kind":"cli","platform":"linux-x64","filename":"hive","sha256":"$(sha hive)","size":$(sz hive),"url":"hive"}
]
}
EOFIn practice you don't hand-write this: pwsh ./scripts/hive.ps1 deploy distribution stages the versioned binaries and generates the channel manifests for you (unstable on every run, semi-stable on promote). See Release channels. The snippet above is only useful for a one-off static endpoint.
Security model
- TLS is the only end-to-end integrity check on the manifest: there is no manifest signature. Pick
https://URLs from a host you control, or put the static folder behind an authenticated endpoint and use the bearer token. - SHA-256 is verified on every artifact after download; mismatch aborts the install and deletes the staged file. Tampering with bytes alone does not produce a working install.
- No replay protection: a stale manifest still installs cleanly as long as the SHA-256 matches. If you publish a vulnerable older version under the same manifest URL and don't tighten access, clients pointing at that URL will install it. Treat the manifest URL like any other deployment surface.
Pre-existing alternatives
The remote-URL flow doesn't replace the existing update paths - it sits beside them:
hive update(no--from): pull from the daemon you're connected to.- Cluster view → "Update peer from this node": daemon-to-daemon relay over the inter-node WebSocket. No HTTP round-trip; bytes never leave the cluster. The receiving daemon writes the synced artifacts into its local
clients/anddaemons/trees next tohived; on Windows installs (C:\Program Files\Hive) the install step pre-creates those dirs and grantsBUILTIN\UsersModify so the relay succeeds even whenhivedis not running as the elevated LocalSystem service. A relay failing withAccess is denied. (os error 5)means the target was installed before this provisioning existed - re-run the node install to repair the ACLs. - Auto-banner in the desktop app: triggered when the connected daemon's bundled artifact is newer than the running build.
Use the remote-URL flow when there is no upstream daemon to pull from, or when you want one URL to be the source of truth across an unmanaged fleet.