Files
archy/docs/architecture.html
T

207 lines
14 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark">
<meta name="description" content="Visual guide to the Archipelago OS architecture, source boundaries, trust, and release evidence.">
<title>OS Architecture · Archipelago</title>
<style>
:root {
color-scheme: dark;
--bg: #0b0d12;
--surface: rgba(25, 29, 39, .78);
--surface-strong: #191d27;
--line: rgba(255, 255, 255, .13);
--text: #f5f2ed;
--muted: #b6b5b8;
--orange: #fb923c;
--peach: #ffbd82;
--mint: #8fe0c3;
--blue: #a8c8ff;
--violet: #c9b6ff;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
color: var(--text);
background: radial-gradient(circle at 82% 3%, rgba(251, 146, 60, .15), transparent 28rem),
radial-gradient(circle at 5% 35%, rgba(96, 145, 208, .11), transparent 32rem),
var(--bg);
font: 16px/1.6 "Avenir Next", Avenir, "Segoe UI", system-ui, sans-serif;
}
a { color: var(--peach); text-underline-offset: .18em; }
a:hover { color: #ffe0b8; }
a:focus-visible { outline: 2px solid var(--orange); outline-offset: 4px; border-radius: 4px; }
.skip { position: absolute; left: -9999px; top: .5rem; z-index: 10; }
.skip:focus { left: .5rem; background: var(--surface-strong); padding: .5rem 1rem; }
.shell { width: min(1180px, calc(100% - 2.5rem)); margin: auto; }
.topbar { border-bottom: 1px solid var(--line); background: rgba(11, 13, 18, .78); backdrop-filter: blur(18px); }
.topbar .shell { min-height: 72px; display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.brand { color: var(--text); text-decoration: none; font-weight: 800; letter-spacing: -.04em; font-size: 1.2rem; }
.brand-mark { display: inline-grid; place-items: center; width: 31px; height: 31px; margin-right: .6rem; border-radius: 10px;
color: #1c1008; background: linear-gradient(135deg, #ffc281, var(--orange)); box-shadow: 0 0 24px rgba(251, 146, 60, .28); }
.toplinks { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 1.25rem; font-size: .9rem; }
.toplinks a { color: var(--muted); text-decoration: none; }
.toplinks a:hover { color: var(--text); }
.hero { padding: clamp(3.5rem, 8vw, 7rem) 0 3.5rem; }
.eyebrow { color: var(--orange); text-transform: uppercase; font-size: .72rem; letter-spacing: .18em; font-weight: 800; }
h1, h2, h3, p { margin-top: 0; }
h1, h2, h3 { line-height: 1.16; letter-spacing: -.045em; }
h1 { max-width: 870px; font-size: clamp(3.2rem, 7.8vw, 6.8rem); margin: 1rem 0 1.5rem; }
h1 span { color: var(--orange); }
.lede { max-width: 720px; color: #d5d1d0; font-size: clamp(1.1rem, 2vw, 1.35rem); }
.hero-meta { display: flex; flex-wrap: wrap; gap: .65rem; margin-top: 2rem; }
.pill { display: inline-block; border: 1px solid var(--line); background: rgba(255,255,255,.04); color: var(--muted);
border-radius: 999px; padding: .4rem .8rem; font-size: .77rem; letter-spacing: .02em; }
.pill.accent { border-color: rgba(251, 146, 60, .38); color: var(--peach); background: rgba(251, 146, 60, .08); }
section { margin-bottom: 5rem; }
.section-heading { display: flex; justify-content: space-between; align-items: end; flex-wrap: wrap; gap: 1rem; margin-bottom: 1.5rem; }
.section-heading h2 { font-size: clamp(2rem, 4vw, 3.2rem); margin: .4rem 0 0; }
.section-heading p { color: var(--muted); max-width: 470px; margin: 0; }
.diagram { border: 1px solid var(--line); border-radius: 28px; padding: clamp(1.2rem, 3vw, 2.2rem);
background: linear-gradient(135deg, rgba(255,255,255,.06), rgba(255,255,255,.015)); box-shadow: 0 24px 80px rgba(0,0,0,.22); }
.flow { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1.4rem; align-items: stretch; }
.node { position: relative; min-height: 230px; display: flex; flex-direction: column; border: 1px solid var(--line);
border-radius: 20px; padding: 1.3rem; background: var(--surface); box-shadow: inset 0 1px rgba(255,255,255,.07); }
.node:not(:last-child)::after { content: "→"; position: absolute; right: -1.15rem; top: 43%; z-index: 2; color: var(--orange); font-size: 1.45rem; font-weight: 800; }
.node .num { color: var(--muted); font-size: .72rem; letter-spacing: .12em; font-weight: 800; }
.node h3 { font-size: 1.35rem; margin: 1.1rem 0 .5rem; }
.node p { color: var(--muted); font-size: .91rem; line-height: 1.5; margin-bottom: 1rem; }
.node a { margin-top: auto; font-size: .85rem; font-weight: 700; }
.node.browser { border-top: 3px solid var(--blue); }
.node.gateway { border-top: 3px solid var(--violet); }
.node.backend { border-top: 3px solid var(--orange); }
.node.apps { border-top: 3px solid var(--mint); }
.underflow { display: grid; grid-template-columns: 1.35fr 1fr; gap: 1rem; margin-top: 1rem; }
.band { border: 1px solid var(--line); border-radius: 17px; background: rgba(255,255,255,.035); padding: 1rem 1.25rem; }
.band strong { display: block; margin-bottom: .2rem; }
.band p { color: var(--muted); font-size: .87rem; margin: 0; }
.band.data { border-left: 3px solid var(--mint); }
.band.host { border-left: 3px solid var(--violet); }
.grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1rem; }
.card { border: 1px solid var(--line); border-radius: 20px; padding: 1.5rem; background: var(--surface); }
.card .icon { display: inline-grid; place-items: center; width: 36px; height: 36px; border-radius: 11px;
color: var(--orange); background: rgba(251,146,60,.12); font-weight: 800; }
.card h3 { font-size: 1.22rem; margin: 1.1rem 0 .6rem; }
.card p, .card li { color: var(--muted); font-size: .92rem; }
.card p:last-child { margin-bottom: 0; }
.card ul { padding-left: 1.1rem; margin-bottom: 0; }
.path-list { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: .75rem; }
.path { display: flex; flex-direction: column; gap: .18rem; padding: 1rem 1.2rem; border: 1px solid var(--line);
border-radius: 14px; background: rgba(255,255,255,.035); text-decoration: none; }
.path:hover { border-color: rgba(251,146,60,.5); background: rgba(251,146,60,.07); }
.path strong { color: var(--text); font-size: .95rem; }
.path span { color: var(--muted); font-size: .82rem; overflow-wrap: anywhere; }
.note { border: 1px solid rgba(251,146,60,.34); border-radius: 20px; padding: 1.5rem;
background: linear-gradient(110deg, rgba(251,146,60,.12), rgba(251,146,60,.025)); }
.note h2 { font-size: 1.65rem; margin-bottom: .5rem; }
.note p { color: #ded4cd; margin-bottom: 0; }
footer { border-top: 1px solid var(--line); color: var(--muted); padding: 2rem 0 3rem; font-size: .86rem; }
footer .shell { display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
@media (max-width: 900px) {
.flow, .grid { grid-template-columns: repeat(2, minmax(0,1fr)); }
.node:nth-child(2)::after { display: none; }
}
@media (max-width: 600px) {
.shell { width: min(100% - 1.5rem, 1180px); }
.topbar .shell { align-items: flex-start; padding: 1rem 0; flex-direction: column; }
.toplinks { justify-content: flex-start; }
.flow, .underflow, .grid, .path-list { grid-template-columns: 1fr; }
.node { min-height: 0; }
.node:not(:last-child)::after { content: "↓"; right: 49%; top: auto; bottom: -1.38rem; }
.node:nth-child(2)::after { display: block; }
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
@media print {
:root { color-scheme: light; --bg: #fff; --text: #111; --muted: #333; --surface: #fff; --line: #bbb; --peach: #963e00; }
body { background: white; color: black; }
.topbar, footer { display: none; }
.hero { padding: 1rem 0; }
section { break-inside: avoid; margin-bottom: 1.5rem; }
.diagram, .card, .node, .band, .path, .note { box-shadow: none; background: white; }
.lede, .note p, .path strong { color: #111; }
a { color: #963e00; }
}
</style>
</head>
<body>
<a class="skip" href="#main">Skip to architecture</a>
<header class="topbar">
<div class="shell">
<a class="brand" href="../README.md"><span class="brand-mark" aria-hidden="true">A</span>Archipelago</a>
<nav class="toplinks" aria-label="Documentation">
<a href="README.md">Docs index</a>
<a href="architecture.md">Text version</a>
<a href="../Release%20%26%20Testing/README.md">Release &amp; Testing</a>
</nav>
</div>
</header>
<main id="main" class="shell">
<div class="hero">
<div class="eyebrow">Developer architecture / source map</div>
<h1>One node.<br><span>Clear boundaries.</span></h1>
<p class="lede">How the browser, Rust backend, rootless apps and protected host fit together. Follow each layer to the source that owns it.</p>
<div class="hero-meta">
<span class="pill accent">Reviewed 12 Nov 2026</span>
<span class="pill">Current main source map</span>
<span class="pill">Release acceptance is separate</span>
</div>
</div>
<section aria-labelledby="flow-title">
<div class="section-heading">
<div><div class="eyebrow">01 / The system</div><h2 id="flow-title">Request to runtime</h2></div>
<p>The node presents one web surface. Each layer has its own owner and trust boundary.</p>
</div>
<div class="diagram">
<div class="flow">
<article class="node browser"><div class="num">01 · CLIENT</div><h3>Browser &amp; Companion</h3><p>Vue dashboard and Android Companion present controls and show node state.</p><a href="../neode-ui/src/">Explore frontend →</a></article>
<article class="node gateway"><div class="num">02 · EDGE</div><h3>Nginx routes</h3><p>Serves the UI and routes RPC and app traffic to the right local service.</p><a href="external-access-and-websites.md">Routing contract →</a></article>
<article class="node backend"><div class="num">03 · CONTROL</div><h3>Rust backend</h3><p>Owns API, sessions, desired app state, reconciliation and update decisions.</p><a href="../core/archipelago/src/api/">Explore API source →</a></article>
<article class="node apps"><div class="num">04 · RUNTIME</div><h3>Rootless apps</h3><p>Systemd user services and Podman run manifest-driven containers and networks.</p><a href="container-lifecycle.md">Lifecycle contract →</a></article>
</div>
<div class="underflow">
<div class="band data"><strong>Persistent data &amp; secrets</strong><p>App state lives under <code>/var/lib/archipelago</code>. Cleanup and upgrades follow explicit data contracts.</p></div>
<div class="band host"><strong>Protected host</strong><p>Debian and systemd own boot, packages and protected OS maintenance.</p></div>
</div>
</div>
</section>
<section aria-labelledby="boundary-title">
<div class="section-heading">
<div><div class="eyebrow">02 / Trust</div><h2 id="boundary-title">Boundaries that matter</h2></div>
<p>Presence in source or on a node does not by itself establish an accepted release.</p>
</div>
<div class="grid">
<article class="card"><span class="icon" aria-hidden="true">01</span><h3>Manifest to app</h3><p><code>apps/&lt;id&gt;/manifest.yml</code> is a development input. On published nodes, a verified signed catalog can supply the embedded manifest that wins over disk.</p><p><a href="app-manifest-spec.md">Manifest specification →</a></p></article>
<article class="card"><span class="icon" aria-hidden="true">02</span><h3>App to host</h3><p>Rootless app processes do not own host packages, unrestricted container-engine access or live storage cleanup. Backend and protected executor paths enforce those boundaries.</p><p><a href="os-updates.md">Update boundaries →</a></p></article>
<article class="card"><span class="icon" aria-hidden="true">03</span><h3>Source to release</h3><p>Source checks, disposable integration, signed artifacts, installed-node evidence and physical acceptance are distinct gates.</p><p><a href="../Release%20%26%20Testing/README.md">Acceptance index →</a></p></article>
</div>
</section>
<section aria-labelledby="source-title">
<div class="section-heading">
<div><div class="eyebrow">03 / Explore</div><h2 id="source-title">Start in the right place</h2></div>
<p>These are source and contract entry points. Read the exact handler and tests before changing behavior.</p>
</div>
<div class="path-list">
<a class="path" href="../core/archipelago/src/api/rpc/dispatcher.rs"><strong>RPC dispatch</strong><span>core/archipelago/src/api/rpc/dispatcher.rs</span></a>
<a class="path" href="../core/archipelago/src/container/"><strong>App orchestration</strong><span>core/archipelago/src/container/</span></a>
<a class="path" href="../core/container/src/manifest.rs"><strong>Manifest schema</strong><span>core/container/src/manifest.rs</span></a>
<a class="path" href="../neode-ui/src/api/rpc-client.ts"><strong>Frontend RPC client</strong><span>neode-ui/src/api/rpc-client.ts</span></a>
<a class="path" href="../image-recipe/"><strong>Installer inputs</strong><span>image-recipe/</span></a>
<a class="path" href="OS-OTA-CONTRACT-20261103.md"><strong>Protected OS updater</strong><span>docs/OS-OTA-CONTRACT-20261103.md</span></a>
</div>
</section>
<section class="note" aria-labelledby="evidence-title">
<h2 id="evidence-title">Keep the evidence attached to the build</h2>
<p>This page maps current source. It does not certify an installed node, installer or OTA. Use the <a href="../Release%20%26%20Testing/README.md">release and testing index</a> for the exact acceptance boundary, and the <a href="architecture.md">text architecture guide</a> for more detail.</p>
</section>
</main>
<footer><div class="shell"><span>Archipelago · OS architecture</span><span>Visual companion to <a href="architecture.md">architecture.md</a></span></div></footer>
</body>
</html>