HOWTO: build the Rudder desktop app on macOS or Windows, and what is not proven yet

This commit is contained in:
Doxa Maps 2026-09-28 01:22:06 +00:00
parent ea151b9a56
commit b7333ef970

View file

@ -0,0 +1,164 @@
# HOWTO: build the Rudder desktop app (macOS / Windows)
Rudder (`plugin-designs/rudder-voice`) is voice-to-text that runs on your own computer: a web UI plus a
local engine (`backend/rudder-engine`, Go) that drives NVIDIA's NeMo-Speech.cpp speech server. The
desktop app is a Tauri window around the built web tree, with the engine started as a **sidecar**.
This page is written for an agent following it cold. **Read "What is proven" first**: most of the
desktop path has never been run by anyone, and the steps say so where that is the case.
## What is proven, and what is not
| Piece | State |
|---|---|
| The web tree (`bun run build` → `public/dx-maps/`) | Proven. Every landing builds it. |
| The generic Tauri shell (`deployment/src-tauri/`) | Exists. It opens the Research Gallery, builds only a Linux `.deb`, and has never been packaged on macOS or Windows. |
| The engine on Linux (`bun run dev`, laptop mic) | Proven. |
| The engine taking the **webview's** microphone (`RUDDER_AUDIO=browser`) | Proven on Linux: audio streamed over `/api/rudder/stream` came back as the full test sentence. |
| The engine **compiling** for macOS (arm64, x86_64) and Windows (x86_64) | Proven by cross-compiling. It has **never been run** on either. |
| The engine accepting a WebSocket from the Tauri webview (`tauri://localhost`, `http://tauri.localhost`) | Proven against a running engine; another site's origin is refused. |
| NeMo-Speech.cpp runtime for macOS / Windows | Published by NVIDIA (release v0.1.0: `macos-aarch64-cpu`, `macos-aarch64-metal`, `macos-x86_64-cpu`, `windows-x86_64-cpu` / `-cuda` / `-vulkan`). **Never run here.** |
| Rudder as a sidecar, first-run model download, installer | **Not built.** Steps 5–8 below are the work to do. |
## 1. Prerequisites
Both platforms:
- **Bun** (the repo's package manager and script runner): https://bun.sh
- **Go 1.22 or newer** (the engine).
- **Rust** (stable) via https://rustup.rs, then the Tauri CLI, which the repo already pins as a dev
dependency (`@tauri-apps/cli` 2.x, run through `bun run tauri:build`).
- About 2 GB of free disk space (the speech model is 742 MB) and 2 GB of free RAM while it runs.
macOS:
- Xcode Command Line Tools: `xcode-select --install`.
Windows:
- Microsoft C++ Build Tools ("Desktop development with C++").
- WebView2 runtime (preinstalled on Windows 11).
Tauri's own prerequisite list is the reference if anything above is out of date:
https://v2.tauri.app/start/prerequisites/
## 2. Get the repo and its packages
```bash
git clone <your team's repo URL> dx-maps
cd dx-maps
bun install
```
## 3. Build the web tree
```bash
bun run build
```
This writes `public/dx-maps/`, the tree every deployment serves, including
`public/dx-maps/rudder-voice/`. The Tauri config points at that folder (`frontendDist`); it does **not**
build it for you (`beforeBuildCommand` is deliberately empty; see `deployment/src-tauri/README.md`).
**Build first, then package.**
## 4. Build the engine for this machine
Tauri expects a sidecar binary named with the target triple:
```bash
# macOS, Apple Silicon
cd backend/rudder-engine && go build -o ../../deployment/src-tauri/binaries/rudder-engine-aarch64-apple-darwin . && cd ../..
# macOS, Intel
cd backend/rudder-engine && go build -o ../../deployment/src-tauri/binaries/rudder-engine-x86_64-apple-darwin . && cd ../..
# Windows (PowerShell)
cd backend/rudder-engine; go build -o ..\..\deployment\src-tauri\binaries\rudder-engine-x86_64-pc-windows-msvc.exe .; cd ..\..
```
`rustc -Vv` prints the exact triple (`host:` line) if unsure.
## 5. Make the Tauri shell start the engine (NOT BUILT: this is the work)
1. **Declare the sidecar** in `deployment/src-tauri/tauri.conf.json`:
`"bundle": { "externalBin": ["binaries/rudder-engine"], "targets": ["dmg"] }` on macOS, or
`["nsis"]` (or `["msi"]`) on Windows. Keep the file schema-pure: **no `//` comment keys**, Tauri
rejects unknown fields. Run `bun run check:tauri` after editing it.
2. **Add the shell plugin** (`tauri-plugin-shell`) to `deployment/src-tauri/Cargo.toml` and register it
in `src/main.rs`, then spawn the sidecar at startup with these environment variables:
- `RUDDER_AUDIO=browser`: the engine takes the **webview's** microphone. Its own recorders
(`parec` / `pw-record` / `arecord`) exist only on Linux, so on macOS and Windows this is required.
- `RUDDER_HOME=<the app's data directory>` (Tauri's `app_data_dir()`): where the runtime and model live.
- `RUDDER_PORT=5193` (the default) or a free port you pass on to the page (step 6).
Kill the sidecar when the window closes. On macOS and Windows the engine cannot ask the OS to kill
its own speech server if the engine crashes (Linux-only feature), so the shell should own that.
3. `src/main.rs` currently has no logic by design ("a window around the web tree"). Rudder is the first
desktop plugin with a backend, so this is the first logic it gains; keep it to starting and
stopping the sidecar.
## 6. Open Rudder, and tell it where the engine is (NOT BUILT)
The window opens `doxa-maps-spa/doxa-maps-spa/index.html` today. For a Rudder app, open Rudder's own
page, `rudder-voice/rudder-voice/index.html`, and give the element the engine's **absolute** address:
```
profile-config = {"apiBase": "http://127.0.0.1:5193/api/rudder"}
```
A relative `/api/rudder` would resolve against `tauri://localhost` and reach nothing. How the page
receives that config (a small wrapper page, a query parameter, or the shell injecting it) is not
decided and not built; pick the smallest option and say which in your report. The engine already
answers CORS for the Tauri origins and accepts their WebSocket.
## 7. The speech runtime and model on first run (NOT BUILT)
`backend/rudder-engine/setup-runtime.sh` is the reference: it pins the runtime (NeMo-Speech.cpp
**0.1.0**) and the model (`nemotron-3.5-asr-streaming-0.6b.q8_0.gguf`, Hugging Face revision
`ea30d66debe3`, ~742 MB) by SHA-256, and refuses a mismatch. It is **bash and Linux x86_64 only**, so on
macOS and Windows the app must do the same on first run:
- Download the runtime for this platform from
`https://github.com/NVIDIA/NeMo-Speech.cpp/releases/tag/v0.1.0`. Each asset has a published
`.sha256` next to it; check it. On Apple Silicon, the **`macos-aarch64-metal`** build uses the GPU
and should transcribe far faster than the CPU builds (unmeasured). On a server, the CPU build ran
slower than real time.
- Download the model from the URL and SHA-256 in `setup-runtime.sh`. Verify before use.
- Put them under `RUDDER_HOME`: `bin/nemo-speech-0.1.0-<os>-<arch>-cpu/bin/nemo-speech[.exe]` and
`models/<model file>`. The engine derives that runtime path for its own platform
(`<os>` = `macos` / `windows`, `<arch>` = `aarch64` / `x86_64`). **Unverified:** the layout inside the
macOS and Windows archives. If it differs, or you use the Metal build, set `RUDDER_NEMO_BIN` to the
real path of the `nemo-speech` binary.
- Show progress: the first start downloads ~750 MB and warms the model (~20 s on a laptop CPU).
## 8. Package
```bash
bun run tauri:build
```
Run it from the repo root. If the CLI cannot find the config, run `cargo tauri build` inside
`deployment/src-tauri/` (untested either way on macOS/Windows).
The installer lands under `deployment/src-tauri/target/release/bundle/`: `dmg/` on macOS, `nsis/` or
`msi/` on Windows.
- **macOS:** the webview needs `NSMicrophoneUsageDescription` in the app's `Info.plist` (Tauri: an
`Info.plist` file next to `tauri.conf.json`), or the microphone request is refused silently. An
unsigned app opens with right-click → Open the first time. Signing and notarising are only needed
to give it to others.
- **Windows:** WebView2 asks for microphone permission on first use.
## 9. Check it works
1. Launch the app. Rudder's status reads "Loading speech model…" and then **Ready**.
2. Press **Record**, speak, and watch the words stream into the box. Press **Stop**: the last words
still arrive.
3. `http://127.0.0.1:5193/api/rudder/status` in a browser on the same machine shows
`"engine":"ready"` and `"audio":"browser"`.
## Known gaps (untested; report what you find)
- Nothing in steps 5–8 has been run on macOS or Windows.
- The macOS/Windows runtime archive layout, and whether the engine's derived path matches it.
- The first-run download, including resuming an interrupted 742 MB download.
- Mapbox basemaps in the Research Gallery need the token host to allow the Tauri origin
(`deployment/src-tauri/README.md`). Rudder itself needs no token.
- If the engine crashes, its speech server may keep running (and holding ~1 GB) until the app quits.