-
Notifications
You must be signed in to change notification settings - Fork 1
Architecture
Launcher is a Tauri v2 desktop application with a Rust backend and a Nuxt 4 / Vue 3 frontend.
launcher/
├── app/ # Frontend (Nuxt 4)
│ ├── app.vue # Root: renders the main UI, or the console
│ │ # when running in a console-* webview window
│ ├── assets/css/main.css # Tailwind CSS v4 design tokens
│ ├── components/ # Vue components
│ │ ├── BriefServerInfo.vue # Server row in the list
│ │ ├── ConnectionInput.vue # Reusable form input
│ │ ├── ConsoleLog.vue # Native console window UI (stdout/stderr)
│ │ ├── InsertableDropdown.vue # Dropdown with inline creation
│ │ └── TrustCertModal.vue # Certificate trust / cert-change dialog
│ ├── composables/ # Vue composables
│ │ ├── useConfirmRejectModal.ts # Imperative modal mounting
│ │ ├── useConsole.ts # Subscribes a console window to its stream
│ │ ├── useConsoleTheme.ts # Per-console light/dark toggle
│ │ ├── useTheme.ts # Main-window theme
│ │ └── useZoom.ts # Keyboard zoom (Cmd/Ctrl +/-/0)
│ ├── enums.ts # Shared frontend enums
│ ├── pages/ # File-based routing
│ │ ├── index.vue # Main server list
│ │ └── connections/[id].vue # Connection editor
│ └── types.d.ts # TypeScript interfaces
├── src-tauri/ # Backend (Rust)
│ ├── src/
│ │ ├── main.rs # Tauri commands and app setup
│ │ ├── connection.rs # ConnectionStore, persistence, cert pins
│ │ ├── webstart.rs # JNLP parsing, JAR downloading, Java process
│ │ ├── tls.rs # Per-connection TLS cert pinning (TOFU)
│ │ └── console.rs # Native console: streams stdout/stderr
│ ├── capabilities/
│ │ ├── default.json # Main-window permissions
│ │ └── console.json # Console-window permissions (console-*)
│ ├── Cargo.toml # Rust dependencies
│ └── tauri.conf.json # Tauri app configuration
└── nuxt.config.ts # Nuxt configuration
Commands are the IPC bridge between frontend and backend. They use rename_all = "snake_case", so the JavaScript side must use snake_case parameter names.
| Command | Returns | Description |
|---|---|---|
launch |
Result<String, String> |
Checks Java, verifies the server cert against the connection's pin, downloads JARs, launches the administrator. Takes a force flag that acknowledges a cache collision (see code 4) and overwrites the cache. Ok value is a JSON object with a code (see below) |
set_pin |
Result<(), String> |
Stores the operator-approved leaf-cert fingerprint as the connection's pin |
save |
Result<String, String> |
Saves a connection entry |
delete |
Result<String, String> |
Deletes a connection |
import |
Result<String, String> |
Imports connections from a JSON file (with duplicate detection) |
load_connections |
String |
Returns all connections as JSON array |
load_single_connection |
Result<Value, String> |
Returns one connection by ID |
get_default_connectionentry |
Result<Value, String> |
Returns a default connection template |
get_all_groups |
Result<Value, String> |
Returns all group names |
get_all_engine_types |
Result<Value, String> |
Returns all engine-type names |
console_subscribe |
Result<(), String> |
A console window subscribes to its output: replays the backlog, then streams live lines over a Channel
|
console_save |
Result<(), String> |
Writes the console contents to a chosen file path |
get_launcher_info |
String |
Returns app version info |
The launch Ok string carries a result code so the frontend knows what to do next:
| Code | Meaning |
|---|---|
0 |
Administrator launched |
2 |
First connect to this server — cert details returned for the trust prompt |
3 |
Server certificate changed — cert details returned for a confirm-or-abort warning |
4 |
Cache collision — the shared cache for this engine type and version holds jars whose hashes don't match what the server sent. Details (engine type, version, differing jars) are returned so the frontend can warn and, on confirmation, retry with force = true
|
-1 |
Error — msg describes it (e.g. Java not found, server unreachable) |
- Frontend calls
launchwith a connection ID and a progressChannel - Backend runs a fail-fast
java -versioncheck (using the connection's Java Home, elsejavaon PATH). If Java cannot run it returnscode -1with a "Java (with JavaFX) not found" message — before any network work - Captures the server's leaf TLS certificate via a handshake to
/webstart.jnlpand computes its SHA-256 - Compares that fingerprint to the connection's stored pin (
pinnedCertSha256):- No pin yet → returns
code 2with cert details so the operator can trust it (TOFU) - Fingerprint differs from the pin → returns
code 3with cert details (the cert-changed warning) - Fingerprint matches → proceeds. On a trust prompt the frontend calls
set_pinand retrieslaunch
- No pin yet → returns
- Downloads the JNLP and JARs over a pinned rustls client that rejects any leaf whose SHA-256 does not match the pin, sending progress over the Channel. The vendor/version shared cache is reused, skipping downloads whose SHA-256 already matches. While checking the cache, a single pass classifies each cached jar as needs-download and/or foreign; if the version matches but jars are foreign, it returns
code 4(cache collision) unlessforcewas passed - Sanitizes JVM arguments (blocks dangerous flags like
-javaagent:) and builds the classpath in the JNLP-declared order — it does not re-sort the jars. Preserving that order is what lets an engine's patched overlay jars (e.g. Mirth 4.7.x*-mc-modifications.jar) load ahead of their stock counterparts; sorting alphabetically loaded stock first and broke administrator login with anIllegalAccessErroronorg.mozilla.javascript.NativeDate - Launches the JavaFX administrator, passing the username and password as trailing launch arguments to the main class
- If "Show console" is enabled, captures the process's stdout and stderr and opens a native console window — but only after the process spawns successfully, so a failed launch never pops an empty console
-
ConnectionStoremanages all connections in aMutex<HashMap<String, Arc<ConnectionEntry>>> - Persisted to
~/.launcher/launcher-data.json(written to a temp file, fsynced, then atomically renamed so a crash can't truncate it). Each entry carries its ownpinnedCertSha256. On unix the data file is created0600and the~/.launcherdirectory0700, since the file can hold saved passwords; the permissions are re-applied on write in case a stale temp file predates the fix - JAR cache stored in
~/.launcher/cache/(vendor/version shared cache) - Diagnostic logs in
~/.launcher/logs/ - There is no separate trust-store file — the pin lives inline on the connection
- The trust boundary is per-connection TLS certificate pinning (trust-on-first-use), implemented in
tls.rs. Integration engine servers commonly present self-signed certs, so chain validation is the wrong model - On first connect, the launcher captures the server's leaf-cert SHA-256 and prompts the operator to trust that fingerprint. The approved value is pinned to the connection (
pinnedCertSha256) - On every later connect, the pinned rustls client accepts only a leaf whose SHA-256 equals the pin; a changed cert is rejected and surfaced as the cert-changed warning (
code 3) - The verifier still validates the TLS handshake signature (proof the server holds the private key) via the ring crypto provider — pinning the public cert bytes alone would let anyone replay them
- Hostname/SAN is intentionally not checked; the pin replaces it, which is what self-signed engine certs (which rarely carry a matching SAN) require
-
native-tls(vendored) is retained only for the connectivity status probe, which needs reqwest'sacceptInvalidHostnames; the launch download path uses the pinned rustls client, notdanger_accept_invalid_certs - JNLP version attributes are sanitized to prevent path traversal
- JVM arguments from JNLP are filtered to block code execution flags (
-javaagent:,-agentpath:, etc.)
Defense-in-depth measures added alongside the pinning core:
- A restrictive Content-Security-Policy is set in
tauri.conf.json(default-src 'self',script-src 'self',object-src 'none',base-uri 'self',frame-ancestors 'none', no remoteconnect-src). Tauri injects per-build hashes of the app's inline scripts soscript-src 'self'still works. All icons are bundled locally (via the@nuxt/iconclient bundle) so nothing is fetched from the internet at runtime and the CSP can stay tight - The connections file and
~/.launcherdirectory are created owner-only (0600/0700) on unix - Tauri v2's capability ACL gates only core and plugin commands, not application commands — any window can call a
#[tauri::command]. Soconsole_subscribederives its target from the calling window's own label rather than a caller-supplied argument, which stops one console window from subscribing to another console's output
When "Show console" is enabled, output is streamed to a dedicated Tauri webview window (label console-<id>) instead of a bundled second Java process. console.rs reads the administrator's stdout and stderr on background threads and pushes each line into a per-window buffer. A window that attaches late replays a capped backlog (MAX_BACKLOG_LINES) and then receives live lines over a Channel, with the buffer lock held across the handover so no line is lost or duplicated. The window closes automatically on a clean exit (status 0) and stays open on a non-zero exit so the error stays readable. Relaunching into an open console bumps a generation counter and shows a "relaunched" separator, so a superseded process's later exit can't flip a live console to "exited".
The main window's capabilities are declared in src-tauri/capabilities/default.json:
| Permission | Purpose |
|---|---|
core:default |
Core IPC and event system |
dialog:default |
Native file picker and confirm dialogs |
core:webview:allow-set-webview-zoom |
Keyboard zoom support |
http:default |
HTTP requests (scoped to http/https URLs, for server connectivity checks) |
shell:allow-open |
Open URLs in default browser (restricted to the wiki URL only) |
Console windows have their own capability in src-tauri/capabilities/console.json (matched by the console-* window glob): core:default, dialog:default, clipboard-manager:allow-write-text, and core:webview:allow-set-webview-zoom.
Tailwind CSS v4 with @theme design tokens in app/assets/css/main.css:
| Token | Value | Usage |
|---|---|---|
surface-0 |
#24272e |
Page background |
surface-1 |
#2c2f37 |
Cards, inputs |
surface-2 |
#353840 |
Hover, selected states |
surface-3 |
#3e414a |
Highest elevation |
accent |
#4f7df7 |
Primary actions |
text-primary |
#e2e5eb |
Main text |
text-secondary |
#9ca3b0 |
Supporting text |
Icons use Phosphor Icons via the ph: prefix with the @nuxt/icon module.
-
Channel API:
launchusestauri::ipc::Channelto stream progress from Rust to the frontend, andconsole_subscribeuses one to stream console output to a console window -
Result-code loop:
index.vueinvokeslaunchin a bounded loop so a first-use trust (code 2) or cert-change (code 3) can show a modal, callset_pin, and retry, while a cert that keeps changing can't re-prompt forever -
Imperative modals:
useConfirmRejectModalmountsTrustCertModalprogrammatically for both the first-use trust prompt and the cert-change confirmation, returning a Promise -
Console windows: each console is a separate Tauri webview window;
app.vuedetects aconsole-*window label and rendersConsoleLog.vue, which owns its own light/dark theme and auto-scroll independently of the main window -
Module-level state:
useZoomuses a module-scopedreffor app-wide singleton zoom state -
CSS tooltips: Custom
[data-tooltip]attribute with::afterpseudo-element (nativetitletooltips are unreliable in Tauri's WKWebView). Usedata-tooltip-belowfor elements near the top of the window - localStorage persistence: Sort mode, collapsed groups, zoom level, and console theme all persist to localStorage
-
Rust: Prefer
?operator and.map_err()over.unwrap(). Return errors to frontend viaResult<T, String>. Use.expect("descriptive message")only for mutex locks (poisoning is unrecoverable). -
Frontend: Wrap
invokecalls in try/catch and display errors inline via the error bar at the bottom of the main screen.