ODIAN Launcher: hub, installer, local stack and remembered sign-in (M2)

- Tauri 2 + Svelte 5 hub in ODIAN dark: patch notes and roadmap, install and
  update progress, local stack status, settings with DHP and MCP addresses.
- crates/odian-packs: Rust installer for pack format v1 (signed manifests,
  resumable verified downloads from a DHP or an ODIAN drive), byte-compatible
  with ODIAN_MCP_Server's Python odian_packs.
- Local stack on 127.0.0.1: Postgres -> Engine API -> tile server -> Odian.exe,
  one Job Object, clean shutdown; database snapshot restore on a fresh PC only.
- Keycloak sign-in through the DHP's MCP, remembered until Sign out with a
  launcher session that renews the Engine token silently.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kelorastus
2026-09-15 15:23:17 +02:00
commit b7f3061be3
102 changed files with 47611 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
// The ONLY module that touches Tauri. Everything else talks to `backend`.
// Inside the Tauri webview this is a thin typed wrapper over invoke/listen
// (docs/CONTRACT.md); in a plain browser it is the timer-driven mock.
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { getCurrentWindow } from "@tauri-apps/api/window";
import { createMockBackend } from "./mock";
import type {
Backend,
InstallProgress,
LogEvent,
Settings,
SignInResult,
StackEvent,
Status,
Unlisten,
} from "./types";
export const IN_TAURI = typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
function subscribe<T>(event: string, handler: (payload: T) => void): Promise<Unlisten> {
return listen<T>(event, (e) => handler(e.payload));
}
function createTauriBackend(): Backend {
const win = getCurrentWindow();
return {
kind: "tauri",
mock: null,
window: {
minimize: () => win.minimize(),
toggleMaximize: () => win.toggleMaximize(),
close: () => win.close(),
isMaximized: () => win.isMaximized(),
onResized: (handler) => win.onResized(() => handler()),
},
getStatus: () => invoke<Status>("get_status"),
refresh: () => invoke<Status>("refresh"),
signIn: () => invoke<SignInResult>("sign_in"),
cancelSignIn: () => invoke<void>("cancel_sign_in"),
signOut: () => invoke<Status>("sign_out"),
getNotes: (version) => invoke<string>("get_notes", version === undefined ? {} : { version }),
openExternal: (url) => invoke<void>("open_external", { url }),
getStack: () => invoke<StackEvent[]>("get_stack"),
startInstall: () => invoke<void>("start_install"),
startInstallFromDrive: (path) => invoke<void>("start_install_from_drive", { path }),
cancelInstall: () => invoke<void>("cancel_install"),
launch: () => invoke<void>("launch"),
stop: () => invoke<void>("stop"),
getSettings: () => invoke<Settings>("get_settings"),
setSettings: (settings) => invoke<Status>("set_settings", { settings }),
pickFolder: () => invoke<string | null>("pick_folder"),
onStatus: (handler) => subscribe<Status>("status", handler),
onInstallProgress: (handler) => subscribe<InstallProgress>("install-progress", handler),
onStack: (handler) => subscribe<StackEvent>("stack", handler),
onLog: (handler) => subscribe<LogEvent>("log", handler),
};
}
export const backend: Backend = IN_TAURI
? createTauriBackend()
: createMockBackend(new URLSearchParams(window.location.search));