176 lines
9.1 KiB
Markdown
176 lines
9.1 KiB
Markdown
# ODIAN Launcher
|
|
|
|
The desktop launcher for ODIAN. It signs an operator in with their site's
|
|
Keycloak account, installs ODIAN from the site (or from an ODIAN drive),
|
|
forces an update before launch while the site is reachable, and runs ODIAN
|
|
fully offline otherwise.
|
|
|
|
| Part | Where | Stack |
|
|
|---|---|---|
|
|
| Hub UI | `src/` | Svelte 5 + Vite, bundled fonts, no network requests of its own |
|
|
| Backend | `src-tauri/` | Tauri 2 (Rust): commands and events in [docs/CONTRACT.md](docs/CONTRACT.md) |
|
|
| Installer | `crates/odian-packs/` | Rust port of the pack format v1 (`ODIAN_MCP_Server/src/odian_packs`) |
|
|
|
|
## Develop
|
|
|
|
```bash
|
|
npm install
|
|
npm run dev # the hub in a normal browser, with a mock backend (?scenario=…)
|
|
npm run tauri dev # the real launcher window
|
|
npm run check # svelte-check
|
|
cargo test --workspace # Rust tests (needs ~/.cargo/bin on PATH)
|
|
```
|
|
|
|
Build the Windows setup (NSIS):
|
|
|
|
```bash
|
|
npm run tauri build
|
|
```
|
|
|
|
## How it finds the site
|
|
|
|
On first run the launcher reads `site.json` next to its executable and copies it
|
|
into its settings (`%APPDATA%\fr.odian.launcher\settings.json`). Operators can
|
|
change the addresses later in Settings. See [site.example.json](site.example.json):
|
|
|
|
```json
|
|
{ "name": "ODIAN test site", "site_url": "http://192.168.1.41:30097", "mcp_url": "http://192.168.1.41:30091" }
|
|
```
|
|
|
|
- `site_url`: the site's update service (`/v1/health`, versions, packs).
|
|
- `mcp_url`: the site's MCP, which runs the Keycloak desktop sign-in (`/auth/desktop/start`, `/auth/desktop/exchange`).
|
|
|
|
## On an operator PC
|
|
|
|
```
|
|
<install_dir>\ D:\ODIAN when a D: drive exists, else C:\ODIAN
|
|
versions\<v>\ app, API, tile server and runtime files for one version
|
|
data\db\<snapshot>\ database dumps restored into the local Postgres
|
|
data\tiles\<style>\<dataset>\ tiles
|
|
data\src\ source files
|
|
state\launcher.json install id, installed version, last operator
|
|
state\notes\<v>.md patch notes, for offline reading
|
|
```
|
|
|
|
The site token lives in Windows Credential Manager under "ODIAN Launcher", one
|
|
entry per site URL. The install id is generated on the PC the first time it saves
|
|
its state, so a cloned drive never carries one.
|
|
|
|
## Dev mode
|
|
|
|
A developer's PC can run the whole stack out of git checkouts instead of the
|
|
installed version, so a code change is one Launch away without cutting a pack.
|
|
It is deliberately not a checkbox anyone can find: the Dev section of Settings
|
|
appears only on a PC holding a **dev licence**, an Ed25519 document signed with
|
|
the same key as a pack manifest and verified by the same code
|
|
(`src-tauri/src/devmode.rs` over `odian_packs::signing::verify`).
|
|
|
|
**This repo carries one.** `dev-licence.json` at the root is a *team* licence: it names no
|
|
machine and no install, and says so in its signed body (`"scope": "team"`). Whoever can
|
|
clone this repo can run dev mode — repo access is the credential, on purpose, because an
|
|
operator has the installed launcher and never the source.
|
|
|
|
- A launcher started **from this repo** (`npm run tauri dev`) finds it by itself: the
|
|
launcher looks beside its own exe and two levels up, which is the repo root.
|
|
- For the launcher **installed on your PC**, run it once:
|
|
`powershell -ExecutionPolicy Bypass -File tools\enable-dev-mode.ps1`
|
|
|
|
It still expires, and it is still refused if the signature does not match the release key.
|
|
A licence that simply *forgot* its binding is refused as unbound — only one that claims the
|
|
team scope is let through, so the weaker rule can never be reached by accident.
|
|
|
|
For a stricter licence — a single PC, or a kit on one drive — mint one on the machine that
|
|
has the signing key:
|
|
|
|
```bash
|
|
python -m odian_packs dev-licence --machine ODIAN-DEV-01 --expires 2027-01-31 --out licence.json
|
|
```
|
|
|
|
`--machine` is `%COMPUTERNAME%`, or use `--install-id` to bind it to one install
|
|
instead (the id in `state\launcher.json`). A licence with neither binding is
|
|
refused — an unbound one would work on every PC it is copied to.
|
|
|
|
The launcher looks in these places on every refresh, best first:
|
|
|
|
1. `<checkout folder>\dev-licence.json` — where a developer's kit lives, so the licence
|
|
travels with the checkouts on the same drive.
|
|
2. Beside the running executable, and two levels above it — the repo root for a launcher
|
|
built from source, which is what makes the committed team licence work with no setup.
|
|
3. `<install_dir>\state\dev-licence.json` — always supported, and the only place a PC with
|
|
**no licence yet** can use: the Dev section is hidden until one is found, so there is no
|
|
way to name a checkout folder before then. This is where `enable-dev-mode.ps1` puts it.
|
|
|
|
It is signed and bound either way, so where it is read from changes nothing about what it
|
|
permits. A refusal is logged once, naming the file it refused.
|
|
|
|
With a licence, Settings gains:
|
|
|
|
| Control | What it does |
|
|
|---|---|
|
|
| Checkout folder | The folder holding `ODIAN_Engine` (e.g. `E:\ODIAN_Dev`) |
|
|
| Launch the whole stack from the checkout | Runs the checkout's API, tile server and `Odian.exe` |
|
|
| Build it first, every launch | Runs `_APIuild.bat` then `toolsuilduild.bat Release` before launching |
|
|
| Run as | An operator name to run as, instead of the one signed in |
|
|
| Launch starts | ODIAN, the web client, or **both** |
|
|
|
|
**Both is the one worth having.** There is one Postgres, one API and one tile server either
|
|
way, so the `.exe` and the browser show the same rows from the same database at the same
|
|
moment. This codebase holds the two clients to each other in a dozen places — the satellite
|
|
propagator, the network graph, the track filters, the APP-6 placement and sizing — and a
|
|
divergence there is invisible in review and obvious the moment both are on screen.
|
|
|
|
The web client is the checkout's `_WEB` on Vite's own `:5173`, with hot reload, and the
|
|
launcher opens a browser at
|
|
`http://localhost:5173/?api=http://127.0.0.1:18080&user=<operator>` — `?api=` because the
|
|
web's own default is `localhost:8080` (the repo's `api.bat`), not this stack's API, and
|
|
`?user=` so both clients author as the same operator, a swapped one included. Its endpoint
|
|
guard already trusts any loopback port, so nothing in `_WEB` needed changing. On first run
|
|
"Build it first, every launch" also runs the web client's `npm install`, whenever
|
|
`package.json` or `package-lock.json` is newer than `node_modules` — so a dependency that
|
|
arrives with a pull is fetched, without paying for npm on every launch. (It has to be the
|
|
manifest times, not "does node_modules exist": a pull leaves the folder in place and Vite
|
|
then fails with `Failed to resolve import "<name>"`, which reads like broken source.)
|
|
|
|
One thing is **not** local yet: `_WEB/vite.config.ts` proxies `/basemap`, `/netlayer` and
|
|
`/mvt` to `https://tiles.odian.fr`, so the browser's map comes from the internet while the
|
|
`.exe` beside it draws from this PC's tiles. The data — the part the two clients are held to
|
|
agree on — is local for both.
|
|
|
|
In dev mode every process gets **its own console window** — the whole point is
|
|
seeing the API's and the client's output — and the same database, tiles and
|
|
Postgres as an operator run, on the launcher's own ports (55432 / 18080 / 18081),
|
|
so a dev run and the repo's `run-local.bat` (8080) do not collide. Stop still
|
|
cuts everything: the job object owns the consoles too.
|
|
|
|
**Run as switches the identity without Keycloak**, because the local API mints
|
|
its own session from a shared secret and the launcher tells it who to be
|
|
(`ODIAN_LAUNCH_USER`). What it cannot do is carry that identity to the DHP — work
|
|
authored as someone else would arrive there under the account that signed in — so
|
|
**while a different name is set, the launcher does not sync at all**, and says so.
|
|
Typing your own name in changes nothing and costs nothing.
|
|
|
|
## The local stack
|
|
|
|
`launch` starts four processes, all on 127.0.0.1, in this order, each waiting for the previous one:
|
|
|
|
| Process | Port | Working dir | Ready when |
|
|
|---|---|---|---|
|
|
| Postgres (`versions\<v>\runtime\pgsql`) | 55432 | `state\pg` | `pg_ctl start -w` returns |
|
|
| Engine API (`versions\<v>\api\odian_api.exe`) | 18080 | `state\api` | `GET /health` answers |
|
|
| Tile server (`versions\<v>\tile_server`, `runtime\python`) | 18081 | `state\tiles` | `GET /health` answers |
|
|
| ODIAN (`versions\<v>\Odian\x64\MinGW-Release\Odian.exe`) | — | `state\client` | still running after 3 s |
|
|
|
|
- Every process starts from a clean environment (Windows basics plus the ODIAN
|
|
variables set in `src-tauri/src/stack.rs`), without a console window, inside
|
|
one Job Object, so nothing outlives the launcher.
|
|
- Odian.exe and the API read and write relative to their working directory.
|
|
`state\client` therefore holds junctions `assets` and `shaders` into the version
|
|
folder, and `versions\<v>\` is never written.
|
|
- The tile server runs cache-only (`ODIAN_TILE_CACHE_ONLY=1`) from a generated
|
|
`state\tiles\datasets.json`, and the client runs with `ODIAN_OFFLINE=1`.
|
|
- ODIAN signs in as the last operator through the local API's `/auth/mint`. The
|
|
mint secret and the Postgres password are generated on the PC and kept in
|
|
`state\launcher.json`.
|
|
- When Odian.exe closes, the rest stops in reverse order; Postgres stops with
|
|
`pg_ctl stop -m fast`.
|