Independent Rust/egui desktop reimplementation of supportos. Local-first. No telemetry. No cloud AI.
SupportOS+ is a ground-up rewrite of the SupportOS support-desk platform in pure Rust, trading the original TypeScript/React stack for a single-language workspace that compiles to a fast, self-contained desktop binary. Everything the original does — unified inbox, real-time conversation sync, semantic docs search, SLA reporting — is rebuilt on an embedded, zero-service architecture that never leaves your machine.
Repository: https://github.com/kimpearce888/SupportOS-Plus · Releases: https://github.com/kimpearce888/SupportOS-Plus/releases
Naming layers (applied where each is allowed): SupportOS+ is the display
name — window title, desktop menu entry, release title. SupportOS-Plus is the
product identifier — repository, window/app ID, artifact base name.
supportos-plus is the Debian package name and, since 1.0.0, also the
installed command and internal crate identifier. (Pre-1.0.0 builds shipped
the command supportos-plusplus; see the upgrade note below.)
Install-ready Linux packages are published on the Releases page, built and verified by CI from the tagged source commit. Both packages carry the same release binary, including the Landlock + seccomp parse sandbox.
| Package | File | Target |
|---|---|---|
.deb |
supportos-plus_<version>_amd64.deb |
Ubuntu 22.04 LTS / 24.04 LTS, Debian 12+ |
.AppImage |
SupportOS-Plus-<version>-x86_64.AppImage |
any modern x86_64 desktop (glibc >= 2.35) |
System requirements: x86_64 (amd64) Linux with glibc 2.35 or newer, a desktop session (X11 or Wayland) and a GL or Vulkan driver (Mesa software rendering works on GPU-less machines). ~200 MB free disk space. No compilation, no development tools, no Docker, no external services, no manual configuration.
sudo apt install ./supportos-plus_<version>_amd64.debAPT downloads and resolves every declared dependency automatically — the
package's Depends set covers the full X11/GL/Wayland client-library surface
the GUI loads at runtime. Launch SupportOS+ from the desktop application
menu, or run supportos-plus.
chmod +x SupportOS-Plus-<version>-x86_64.AppImage
./SupportOS-Plus-<version>-x86_64.AppImageRuns without installation; the type-2 AppImage bundles the binary, icons and desktop entry (AppImageLauncher integrates it into the menu). Only the host display server and GL/Vulkan driver are used from the system.
First run initializes itself: the SQLite database is created (all
migrations applied) under ~/.local/share/supportos-plus/ (override with
SPP_DATA_DIR), background workers start, a loopback listener serves Help
Scout webhooks and the OAuth callback on 127.0.0.1, and the shell boots
empty — no sample, demo or simulated data of any kind — until Help
Scout credentials are configured. No setup steps.
Connecting your Help Scout account (updated in 1.2.0): open Settings → Help Scout and fill in the credentials form — client ID, client secret, webhook secret (and, optionally, the Docs API key) from your Help Scout custom app. The values are stored encrypted in the local secrets envelope. Then:
- On your Help Scout profile create a custom app and set its callback
URL to exactly
http://localhost:3000/oauth/callback(shown in the connection card; change the port withHELPSCOUT_REDIRECT_URI— the registered URL and the setting must match). - Save the credentials in the form and restart SupportOS+.
- That is all. Since 1.2.0 the app signs in with the stored client credentials automatically (no token dance required) and immediately starts the initial sync — conversations, customers, threads, ratings and the rest start arriving on the first boot. The browser OAuth flow and the in-page "connect with client credentials" button remain available if you prefer an explicit connect.
Every failure along the way — a rejected login, a rate limit, a failed resource sync, a rejected webhook — is recorded and shown on the Diagnostics page (see below) instead of vanishing into stdout logs.
Environment variables (HELPSCOUT_CLIENT_ID, HELPSCOUT_CLIENT_SECRET,
HELPSCOUT_WEBHOOK_SECRET, HELPSCOUT_DOCS_API_KEY,
HELPSCOUT_REDIRECT_URI) still work and override the stored values per
field — useful for CI and headless setups. If the callback listener cannot
bind its port (another program is using it), the connect button says so
instead of leaving a dead browser tab.
The remaining environment knobs (audit §4: none of these were documented before):
| Variable | Default | Purpose |
|---|---|---|
SPP_LOOPBACK_PORT |
OS-assigned | Pin the localhost OAuth/webhook listener port — register the SAME port in the Help Scout app's redirect URI |
LOG_LEVEL |
info |
tracing filter level |
LMSTUDIO_BASE_URL |
http://127.0.0.1:1234 |
Local LLM server (copilot, embeddings) |
LMSTUDIO_CHAT_MODEL / LMSTUDIO_EMBEDDING_MODEL |
server default | Model overrides |
LMSTUDIO_TIMEOUT_MS / LMSTUDIO_CONCURRENCY |
120000 / 2 |
Request budget |
QDRANT_URL / QDRANT_ENABLED |
http://127.0.0.1:6333 / true |
Vector store endpoint |
SYNC_INTERVAL_MINUTES |
1 |
Incremental-sync quiet ceiling |
SYNC_API_CONCURRENCY |
2 |
Parallel Help Scout requests during sync |
Working in real time (updated in 1.3.0): the local mirror polls Help Scout every minute by default (configurable in Settings), streams conversations into the inbox while the initial sync is still running (live progress on Sync Health), and applies what you do immediately — replies, notes, status changes and assignments are dispatched to Help Scout within seconds, with a per-conversation status line that says "delivering…", "delivered ✓" or the exact failure. A "Sync now" button sits on the Inbox header, every conversation has its own refresh, and Dashboard / Reports / Customers / Organizations / Knowledge all carry reload buttons that re-pull real data rather than repaint a stale cache. (True push via webhooks is impossible for a localhost desktop app — Help Scout needs a publicly reachable URL — so fast polling plus everywhere-refresh is the honest real-time design.)
The Copilot (new in 1.3.0): open the spark icon on any conversation and the panel reads the entire thread locally — no configuration, no AI service: what the customer needs (extractive summary), their open questions, intent / urgency / sentiment / escalation signals, the customer's history, similar past issues with the resolution that worked and why they matched, relevant Help Center articles, and a suggested reply assembled from that evidence (every line traces to a real past resolution or article — nothing is invented) that inserts into the composer for editing. Install LM Studio and set a chat model in Settings to add generative drafts on top; the local engine keeps working without it.
Help Center sync (fixed in 1.3.0): Help Scout's Docs API is used at
its real v1 endpoints (the previous build called a /v2/ that does not
exist, so articles never arrived). Collections, categories and full
article contents sync on the initial pull, on every incremental pass
and via the "Sync Help Center now" button. The Docs API uses a
separate key from the Mailbox API (Help Scout → Profile → Security &
Access → Docs API); the Knowledge page says exactly that when the key
is missing instead of showing a silently empty tab.
Diagnostics & reports (new in 1.2.0): the Diagnostics page shows every recorded error, warning and info event — sync failures with the failing resource and reason, automatic sign-ins, OAuth callback outcomes, webhook rejections, background job failures — with level filters and a clear button, plus the outcome of every recent sync run (records processed, errors, state). If "nothing is syncing", the answer is on this page.
Resetting workspace data (updated in 1.2.0): Settings → Help Scout → Workspace data deletes every synced record (conversations, customers, threads, ratings, knowledge base, incidents, reports, AI records — plus anything left over from the demo dataset that shipped before 1.2.0), resets sync state and the vector index in one click after a two-step confirmation, and arms a fresh first sync for the next start. Credentials, application settings, business hours, automation rules, saved views/replies and custom object types are kept.
Upgrading from 0.2.x: the first 1.0.0 launch moves your existing data from
the old ~/.local/share/supportos-plusplus/ directory to the new location
(and renames the database inside it) automatically — one atomic directory
rename, nothing lost, no manual steps. Downgrading back to 0.2.x afterwards
is not supported.
Checksums: every release ships SHA256SUMS.txt covering both packages;
sha256sum -c SHA256SUMS.txt after download.
A single Cargo workspace, split by concern:
| Crate | Role |
|---|---|
crates/catalog |
Shared type catalog re-exported by every other crate |
crates/core |
Pure-Rust business core — SQLite persistence, sync engine, Help Scout provider (real), AI providers, sqlite-vec vector store, loopback listener, diagnostics event log. No UI deps. |
crates/app-shell |
Native desktop shell (eframe/egui) — 23 pages render in-process against core, no HTTP hop |
crates/xtask |
Developer entry point — dev / lint / verify-icons / package / deb / db ops (migrate/backup/restore/healthcheck/vector-count) |
The 23 shell pages: Shell, Dashboard, Inbox, Customers, Knowledge, Reports, Operations, AI Center, Automation, Connectors, Custom Objects, Incidents, Issue Radar, Notifications, Onboarding, Organizations, Outreach, Search, Settings, Support Graph, Sync Health, Diagnostics, Live Chats.
- Native egui shell. The Phase 4 cutover replaced the Leptos/WASM frontend
and the Tauri 2 webview shell with a single eframe/egui desktop app — no
browser engine, no CSP plumbing, no wasm target. Pages query the core
directly through the
DbHandlefacade (writer lane + 2 WAL reader connections behind it); live updates flow over the in-processEventBusand wake the render loop on demand. - sqlite-vec vector index (decision D4). The chunk count sits far below the
100k gate, so dense vectors live in a
vec0SQLite table behind theVectorStoretrait — the store stays swappable, and there is no embedded qdrant engine to compile or babysit. - Loopback listener. A minimal
127.0.0.1HTTP listener serves exactly two routes:POST /webhook(Help Scout event ingress, HMAC-SHA1 verified) andGET /oauth/callback(single-use state, code exchange) — the only inbound network surface of the desktop app. - SQLite everywhere.
rusqlite(bundled) for tickets, conversations and docs; business-minutes SLA math with full IANA timezone parity (chrono-tz). - One crypto module. All encrypted artifacts (
.sosyncsync bundles, settings exports) sharecore::crypto: scrypt key derivation + AES-256-GCM sealing, written once and tested. - Local AI adapters. LM Studio, Ollama and generic OpenAI-compatible endpoints
via
reqwestwith SSE streaming — pointed atlocalhostservices only. - Hardened input handling.
ammoniaHTML sanitization, bounded NFA regex (no ReDoS surface), SSRF-guarded URL parsing for connectors, and a Landlock + seccomp sandbox around pdf/docx parsing — compiled into the shipped packages (kernels without Landlock degrade to seccomp-only, logged). - Small release binaries. LTO +
opt-level = "s"+ stripped symbols.
Requires the pinned Rust toolchain (see rust-toolchain.toml) and no build-time
system packages — the egui shell needs no webkit and no wasm target. At runtime
a machine needs the usual Linux desktop graphics stack: an X11 (or Wayland)
session, libxkbcommon-x11, and a GL or Vulkan implementation (Mesa;
software rendering via llvmpipe/lavapipe works fine on GPU-less and headless
setups).
cargo xtask dev
cargo xtask lint
cargo xtask package # AppImage → target/appimage/SupportOS-Plus-x86_64.AppImage
cargo xtask deb # .deb → target/deb/supportos-plus_<version>_amd64.deb
cargo test # unit tests (crypto round-trips, HMAC vectors, SLA math, page models)cargo xtask works because .cargo/config.toml aliases it to
cargo run --locked -p supportos-plus-xtask -- (audit §4: the README
cited the alias long before it existed).
xtask package stages the AppDir (release binary, .desktop entry, hicolor
icons, AppRun) and runs appimagetool — located via APPIMAGETOOL, cached
under target/tools/, or downloaded from the official continuous release.
The AppImage bundles the X11 keyboard libraries (libxkbcommon,
libxkbcommon-x11, libxcb-xkb) from the build host so it also opens windows
on GUI-less systems, so the build host must have libxkbcommon0 libxkbcommon-x11-0 libxcb-xkb1 installed (every desktop machine has them; CI
installs them explicitly). xtask deb stages the same payload for dpkg and
packs it with dpkg-deb --root-owner-group. The Tauri-era msi/nsis/dmg/app
targets are gone; the two Linux packages are the complete packaging surface.
- CI (every push to main): fmt, clippy (
-D warnings), the full workspace test suite, the sandbox-feature tests,cargo-denyadvisories/licenses, plus an AppImage build, a.debbuild, and boot-chain smoke runs of both packages (SPP_SMOKE=1verifies logging, single-instance lock, migrations, boot services, background workers and the loopback listener). - Release pipeline (every tag): builds both packages on the ubuntu:22.04
glibc 2.35 baseline, installs the
.debthrough APT on clean ubuntu:22.04 / ubuntu:24.04 / debian:12 containers (dependency resolution, desktop entry + icons, boot smoke, GUI launch under Xvfb using only the declared dependencies, reinstall, uninstall with user data preserved, purge), launches the AppImage under Xvfb, then publishes the release with SHA-256 checksums and live-verified download links. Nothing is published unless every step passes.
MIT