Desktop Tray Client
Platform: the tray currently ships for macOS only. The design is portable —
fyne.io/systraysupports Linux (appindicator/GTK) and Windows — but the built and tested binary is the macOS menu-bar app. The Linux/Windows behaviour described below (e.g. the Open folder file-manager calls) is aspirational until a native build for those platforms is exercised.
Overview
dropbear-tray is a menu-bar/tray client for the dropbear sync daemon. It runs as a separate native binary and provides an always-present status icon that reflects whether your synced folders are up to date, plus quick actions for starting or stopping the daemon, triggering a sync, opening the web dashboard, or revealing a root folder.
Architecture
The tray is a thin client: it holds no sync state and performs no object-storage, manifest, or local-state I/O. All displayed information comes from the daemon's loopback status API (GET /v1/status). The actions it takes are requesting a manual sync (POST /v1/roots/{id}/sync) and starting/stopping the daemon through the OS service manager (see
Discovery and Authentication
On startup and on each daemon-down → reachable transition, the tray reads the runtime handoff file at $XDG_CONFIG_HOME/dropbear/runtime/daemon.json (or $HOME/Library/Application Support/dropbear/runtime/daemon.json on macOS). The file contains {pid, url, token, started_at}. The tray verifies the recorded PID is alive, then uses url + token for all API calls as Authorization: Bearer <token>.
A missing file, dead PID, or connection-refused response maps to the "daemon down" state — no error dialog is shown. This is the same discovery path used by dropbear daemon status, factored into internal/daemonclient.
Icon States
The menu-bar icon is the dropbear logo with a status badge composited into the corner. The badge — not the logo — carries the state, since at menu-bar size (~22px) the badge is what stays legible:
Precedence: down > attention > syncing > idle. The badge carries the sync sub-state (idle/syncing/attention); the top menu line instead shows the daemon's lifecycle state (Dropbear Daemon: running / stopped) and doubles as the start/stop toggle described below.
Menu Actions
- Dropbear Daemon: running / stopped — the top line is the daemon start/stop toggle. Clicking it flips the daemon on or off via the OS service manager. See
Starting and Stopping the Daemon . - Open dashboard — opens the daemon's embedded web UI (
urlfrom the runtime file) in the default browser. Available when daemon is reachable. - Open folder — reveals a root's local path in the OS file manager (
open -Ron macOS,xdg-openon Linux, Explorer on Windows). - Sync now — posts
POST /v1/roots/{id}/sync, reads the returnedtick_id, shows the syncing icon, and polls until that tick completes. - Quit — exits the tray process only. The daemon keeps running and syncing. Always available, and pinned to the bottom of the menu.
Each root appears as <path> (<online|offline>), grouped with Open dashboard in one section. The tray polls GET /v1/status every 2s; the extra polls are an immediate one after a "Sync now" returns and one after a start/stop toggle.
Starting and Stopping the Daemon
The top menu line — Dropbear Daemon: running / stopped — is also the start/stop toggle. Clicking it while the daemon is running stops it; clicking while it is stopped starts it. This drives the platform service manager through internal/daemonctl:
- macOS (launchd): stop =
launchctl disable+bootout; start =launchctl enablethenbootstrap(orkickstart -kif already loaded). - Linux (systemd
--user): stop =systemctl --user disable+stop; start =systemctl --user enable+restart. - Windows / other: unsupported — the click is logged and otherwise ignored.
Off means off. Stop is persistent: it disables the service so it stays down across logouts and reboots, not just for the current session. A bare stop would be undone at the next login by launchd's RunAtLoad or systemd's enabled symlink, so the toggle would silently pop back on; disable is what prevents that. Start re-enables and relaunches. On macOS you can confirm the persistent state without rebooting via launchctl print-disabled gui/$(id -u) (the disabled bit) and launchctl print gui/$(id -u)/net.tfks.dropbear (whether it is loaded right now).
Requires an installed service. The daemon must already be installed (dropbear daemon install). If it isn't, the toggle logs guidance to run that command rather than installing anything itself — the tray stays a thin client, and install stays the one code path shared with headless/server setups.
In-flight behaviour. On click the line immediately shows the requested direction (starting… / stopping…) and disables itself until the next 2s status poll confirms the real state, so a slow service manager can't invite a double-click. If a start fails (or the daemon is still coming up), the next poll reconciles the line — a failed start reverts to stopped. A lifecycle error never crashes the tray: it is logged and left for the poll to resolve.
Root Membership Is Frozen Per Session
The tray builds its per-root menu entries once, from the first status poll that returns a non-empty roots array, and keeps that set fixed for the process lifetime. Later polls refresh only each entry's online/offline title and the aggregate icon — not the set of entries. Roots added to or removed from the daemon are not reflected until the tray is restarted.
This is deliberate: fyne.io/systray is append-only with no insert/reorder API, and AddSeparator() returns no handle (separators can't be moved or removed). A fixed root set is what lets the entries sit in a stable section while Quit stays pinned to the bottom — Quit is re-pinned (with a divider above it) exactly once, at build time. Root membership is config-driven and changes rarely, and the thin-client design means a stale menu never affects sync correctness.