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
+84
View File
@@ -0,0 +1,84 @@
// Patch notes come from the site (or its cached copy) and are untrusted:
// Markdown is rendered with marked, then every byte of HTML goes through
// DOMPurify with a small allowlist before it reaches {@html}.
import { Marked, type Token, type TokensList } from "marked";
import DOMPurify from "dompurify";
const marked = new Marked({ gfm: true, breaks: false, async: false });
const ALLOWED_TAGS = [
"h1", "h2", "h3", "h4", "h5", "h6",
"p", "br", "hr", "blockquote",
"ul", "ol", "li",
"strong", "em", "b", "i", "del", "s", "code", "pre", "kbd", "sub", "sup",
"a",
"table", "thead", "tbody", "tr", "th", "td",
"input", // GFM task-list checkboxes (forced disabled below)
];
const ALLOWED_ATTR = ["href", "title", "align", "start", "type", "checked", "disabled"];
const SAFE_HREF = /^(https?:|mailto:|#)/i;
DOMPurify.addHook("afterSanitizeAttributes", (node) => {
if (node.tagName === "A") {
const href = node.getAttribute("href") ?? "";
if (!SAFE_HREF.test(href.trim())) node.removeAttribute("href");
node.removeAttribute("target");
node.setAttribute("rel", "noopener noreferrer");
if (href && !node.getAttribute("title")) node.setAttribute("title", href);
}
if (node.tagName === "INPUT") {
if (node.getAttribute("type") !== "checkbox") node.remove();
else node.setAttribute("disabled", "");
}
});
function sanitize(html: string): string {
return DOMPurify.sanitize(html, {
ALLOWED_TAGS,
ALLOWED_ATTR,
ALLOW_DATA_ATTR: false,
ALLOW_ARIA_ATTR: false,
});
}
function render(tokens: Token[], links: TokensList["links"]): string {
const list = Object.assign(tokens, { links }) as TokensList;
return sanitize(marked.parser(list));
}
function isRoadmapHeading(token: Token): boolean {
return token.type === "heading" && token.depth === 2 && /^roadmap$/i.test(token.text.trim());
}
export interface RenderedNotes {
/** Everything except the Roadmap section. */
notes: string;
/** Contents under `## Roadmap` (heading excluded), or null when absent/empty. */
roadmap: string | null;
}
export function renderNotes(markdown: string): RenderedNotes {
const tokens = marked.lexer(markdown);
const notes: Token[] = [];
const roadmap: Token[] = [];
let inRoadmap = false;
let sawRoadmap = false;
for (const token of tokens) {
if (isRoadmapHeading(token)) {
inRoadmap = true;
sawRoadmap = true;
continue;
}
if (inRoadmap && token.type === "heading" && token.depth <= 2) inRoadmap = false;
(inRoadmap ? roadmap : notes).push(token);
}
const hasRoadmapContent = roadmap.some((t) => t.type !== "space");
return {
notes: render(notes, tokens.links),
roadmap: sawRoadmap && hasRoadmapContent ? render(roadmap, tokens.links) : null,
};
}