HAProxy UI ships two ways: a browser build (SvelteKit, any host that can serve static files) and a desktop build (Tauri 2, native window, Rust backend). Both render the identical UI. The desktop build is the canonical target because it is the only one that can keep API tokens out of the browser.
Releases are published to GitHub Releases and updated in-app via tauri-plugin-updater.
Requirements
Component | Requirement |
Node.js | 20+ |
Package manager | pnpm 9+ |
Rust (desktop only) | stable toolchain via rustup |
macOS (desktop) | Xcode Command Line Tools |
Linux (desktop) | webkit2gtk-4.1, gtk-3, ayatana-appindicator3, rsvg2, soup-3.0, ssl, patchelf, build-essential |
Windows (desktop) | WebView2 runtime (preinstalled on Windows 10+) |
Network | Reachability to the Dataplane API (port 5555 in the reference stack) |
Install from source
git clone https://gitlab.starstorm.dev/starstorm/ha-proxy-ui.gitcd ha-proxy-uipnpm install cp .env.example .env # fill in Dataplane URL + token, or leave for the mock
Development
pnpm dev # Vite dev server, HMR, http://localhost:5173pnpm dev:tauri # native window with HMR against the Vite dev serverpnpm test:unit # Vitest watch modepnpm test:e2e # Playwright
Production builds
pnpm build # browser production build (static, into build/)pnpm build:tauri # Tauri production binary + installerspnpm preview # serve the browser production build locally
Artifacts land in src-tauri/target/release/bundle/ — .app + .dmg on macOS, .deb / .AppImage / .rpm on Linux, .msi / .exe on Windows.
Where the desktop app stores things
The app data directory resolves from the bundle identifier com.haproxy-ui.app:
OS | Path |
macOS |
|
Linux |
|
Windows |
|
Instances (per-target URL, label, auth scheme, mode, default flag) →
instances.jsonin that directory.API tokens → the OS keychain, never
instances.jsonand neverlocalStorage. See Secrets & keychain.Working draft →
localStorageunderhaproxy-ui:draft:v1:<instanceId>, so an in-progress edit survives a relaunch. Cleared on successful apply or explicit discard.
Desktop-only features
Multi-instance picker — register any number of Dataplane targets and switch between them from a sidebar dropdown. Each entry holds a URL, label, auth scheme and mode.
GitOps mode — push or pull the assembled
haproxy.cfgagainst a git remote instead of, or alongside of, the Dataplane API. SSH keys are used viassh-agentfrom the Rust side.
Both route through Rust commands, so the UI renders them in browser mode too — when no Tauri shell is detected, persistence falls back to localStorage. The desktop app remains the canonical target.
Auto-updates
The desktop binary checks for updates on startup and shows a prompt when a newer version is available. Both the endpoint and the public key live in src-tauri/tauri.conf.json under plugins.updater.
Before the first release can update, the updater owner must be set. The repo ships with the literal token
__OWNER__in the endpoint URL, so the startup check fails fast againstgithub.com/__OWNER__/...rather than silently polling the wrong repository. Replace it with the org the repo actually lives under, and make it match the release URL exactly — a mismatch means shipped binaries never find alatest.jsonand the check fails silently.
The pubkey is the Ed25519 public half of the signing keypair. Do not rotate it casually: every shipped release must be signed with the matching private half, or existing installs reject the update. Tauri 2 supports two public keys during a transition via plugins.updater.pubkeys.
Cutting a release
Two CI workflows run on GitHub Actions:
build.yml— PR andmainsmoke builds. Does not publish a release.release.yml— tag-triggered on anyv*push; cross-platform build, signs, and publishes a draft release.
Bump the version in both
package.jsonandsrc-tauri/tauri.conf.json. They are intentionally separate files today — Tauri reads its own for the updater manifest, pnpm reads the other — and must stay in lockstep.Tag and push:
git tag v0.0.1 && git push origin v0.0.1.Each matrix leg uploads to the same draft release, plus a signed
latest.jsonat the root.Un-draft the release when the matrices finish, so users on
prerelease: falseget notified.
Required repo secrets: TAURI_SIGNING_PRIVATE_KEY and TAURI_SIGNING_PRIVATE_KEY_PASSWORD.
Known distribution gaps
macOS notarization is not configured. Bundles are ad-hoc signed, so distributing outside the build machine triggers Gatekeeper. A proper notarization step needs
APPLE_CERTIFICATE,APPLE_CERTIFICATE_PASSWORD,APPLE_SIGNING_IDENTITYand an App Store Connect API key as repo secrets.Windows code signing is not configured. MSI/EXE bundles ship unsigned and will trigger SmartScreen. Add
WINDOWS_CERTIFICATE+ password and abundle.windows.signCommandwhen ready.The updater pipeline is desktop-only. The planned iPad build does not use
tauri-plugin-updater— it is reinstalled via the App Store.
See iPad Roadmap for the mobile target.
Uninstall
rm -rf "/Applications/HAProxy UI.app"rm -rf ~/Library/Application Support/com.haproxy-ui.app/ # secrets live in the OS keychain and must be removed separatelysecurity delete-generic-password -s "instance:<instance-id>" 2>/dev/null || true