Installation¶
shed-desktop is a Tauri app on one shared Rust core, shipped for macOS (Apple Silicon, a
DMG) and for Linux (WebKitGTK, an apt .deb).
macOS (DMG)¶
The macOS app (Tauri) is an Apple Silicon (arm64) menu-bar app and requires macOS 14 or newer.
Grab the latest ShedDesktop-<version>.dmg from the
releases page, open it, and drag
ShedDesktop.app to Applications.
Official stable release DMGs are always Developer-ID-signed and notarized, and launch
without a Gatekeeper prompt: a stable desktop tag fails to publish without the signing
credentials. Only a prerelease (-rc) tag may ship an ad-hoc-signed, non-notarized DMG,
which carries a FIRST-LAUNCH.txt with the one-time bypass steps.
Updates¶
The app updates through Sparkle, served from the appcast at
https://charliek.github.io/shed/appcast.xml and verified by an EdDSA signature independent
of Apple notarization. Updates are user-invoked only — there are no automatic background
checks. Trigger one from the menu-bar dropdown (or the tray popover) → Check for Updates…;
if a newer build is published, Sparkle offers it and applies it in place.
| Behavior | Value |
|---|---|
| Trigger | User-invoked (Check for Updates…); no automatic/scheduled checks |
| Feed | https://charliek.github.io/shed/appcast.xml (stable channel) |
| Verification | EdDSA signature (SUPublicEDKey), independent of Apple notarization |
| Channels | Stable by default; prerelease (rc) builds subscribe to a beta channel |
The Tauri macOS app is the shipped macOS client as of 0.9.0 — prerelease
(vX.Y.Z-rc.N) tags publish a beta-channel DMG, stable tags publish the stable-channel DMG,
both from the same job. It shares the feed, EdDSA key, and bundle identity
(ai.stridelabs.ShedDesktop) that the earlier Swift app used, so the first Tauri release is
designed to update in place for existing Swift installs (0.9.0 is the first release to exercise
that path; see the 0.9.0 upgrade guide). See
RELEASING.md.
Locally built DMGs (make -C desktop dmg) are ad-hoc signed, so Gatekeeper blocks the
first launch. Clear the quarantine once, after copying it in:
(Or double-click it, dismiss the warning, then System Settings → Privacy & Security → "Open Anyway".)
A Homebrew cask for the macOS app is planned (a later monorepo phase) but not available yet — install from the DMG for now.
Linux (apt)¶
The Linux client is the Tauri app, distributed as the shed-desktop .deb (amd64 + arm64)
through charliek/apt-charliek:
The binary installs to /usr/bin/shed-desktop, with a headless shedctl and the polkit
action alongside. The .deb declares its WebKitGTK runtime dependencies
(libwebkit2gtk-4.1-0, libgtk-3-0, libayatana-appindicator3-1, librsvg2-2,
libsoup-3.0-0) and recommends polkitd. See the
shed apt repo for the repository setup.
Build from source¶
The desktop app lives under desktop/ in the shed monorepo;
its shared Rust core is the sibling crates/ workspace. Every make target below runs from
the monorepo root via the desktop- passthrough (make desktop-<target>) or directly with
make -C desktop <target>.
macOS — prerequisites: Rust stable ≥1.85, Node 20+.
This builds the shipped client, the Tauri app:
git clone https://github.com/charliek/shed
cd shed
make -C desktop tauri-bundle-mac # desktop/build/ShedDesktop.app (ad-hoc signed)
open desktop/build/ShedDesktop.app
make -C desktop tauri-dmg-mac packages it into
desktop/build/ShedDesktop-<version>.dmg. To produce a notarizable build locally, set the
signing identity:
The Swift app still builds, but is not what ships
The original SwiftUI menu-bar client remains in the tree and is still tested in CI, but
its release job retired in 0.9.0 — make -C desktop bundle builds that app, not the
one on the releases page. Building it needs Xcode 16+ (Swift 6 toolchain). Use it only if
you are working on the Swift sources themselves; see
the desktop RELEASING notes.
Linux — the .deb is built (in Docker, to pin the WebKitGTK toolchain) with:
make -C desktop deb # → desktop/out/shed-desktop_<version>_<arch>.deb
make -C desktop deb-validate # build + install-validate in a clean ubuntu:24.04 container
What it needs at runtime¶
~/.shed/config.yaml— the shed-server host list (created by theshedCLI). The app reads this read-only and watches it for changes.- A reachable
shed-serveron at least one configured host. Unreachable hosts are shown as a degraded state, never a hard failure.
Nothing else — for the Tauri client. brew install shed (or apt install shed-server on a Linux host) plus the
Tauri desktop app is a complete install — its embedded broker handles credential approvals
(SSH sign, AWS, Docker) itself, no extra daemon needed. The Swift macOS client (sources
retained pending demolition; not shipped since 0.9.0) still requires the separately-installed
shed-host-agent daemon for credential approvals (Homebrew formula, brew services start
shed-host-agent). See below for how that differs between the two clients.
Credential broker¶
The app's headline feature — SSH-sign approvals, AWS/Docker credential gating, and the Activity audit feed — needs a credential broker in the loop. How that broker runs differs by client:
| Client | Broker |
|---|---|
| Tauri (macOS and Linux) | Embedded — runs in-process, on by default. No extra install. |
| Swift (macOS, not shipped since 0.9.0) | Separate daemon — a standalone shed-host-agent process (Homebrew formula, brew services start shed-host-agent). See Credential approvals. |
The Tauri client can also run against a standalone daemon instead of its embedded broker
— useful if you already run shed-host-agent headless on a server, or want one broker
shared across tools. A Preferences → Credential broker setting (Automatic /
In-app (embedded) / External daemon) controls it, default Automatic:
| Mode | Behavior |
|---|---|
Automatic (default) |
Probes for a running shed-host-agent at startup: a full daemon present ⇒ external (dials it, unchanged from today); a headless daemon (status socket only, no desktop socket) ⇒ coexist (the app doesn't start its own broker — no namespace conflicts — and mints its own secure-server tokens, but gets no in-app approvals, since the headless daemon owns those); neither present ⇒ embedded. |
In-app (embedded) |
Always starts the in-process broker, regardless of any running daemon. If a daemon is also running, the two race for each server's credential-bus namespace — the loser gets a per-namespace 409, surfaced (not hidden) in broker.status; no double approval prompts. |
External daemon |
Always dials the standalone daemon, never starts an in-process broker. |
If the daemon Automatic chose at launch is stopped or uninstalled while the app runs, the
app does not fall back to its embedded broker: there is no credential broker, and sheds lose
ssh-agent and credential forwarding, until you relaunch the app. Relaunch after removing the
daemon, or pin In-app (embedded). A runtime fallback is tracked in
#411.
The resolved mode is fixed for the app's process lifetime — changing the preference
takes effect on the next launch, not live. The active mode, the probe evidence that
produced it, and (for embedded) the resolved SSH backend and per-server connection state
are visible in Preferences and over IPC (broker.status, identify.broker_mode). Full
mechanics — extensions.yaml handling, the synthesized fresh-install default, and the
fail-closed behaviors — are documented in
Architecture → The embedded credential broker.