eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1// đŸ¤– Shared script for the planet-scale operations guide.
2//
3// This file is the single source of truth for the guide's structure. It:
4// 1. renders the sidebar navigation on every page (from the PAGES list below),
5// 2. renders the previous / next links at the bottom of every page,
6// 3. renders the "you are here" system map at the top of every page,
7// 4. adds a small anchor link to every heading that has an id.
8//
9// To add a page: create the HTML file (copy an existing page as a template),
10// then add one entry to PAGES below, in reading order. Nothing else to update —
11// every page's sidebar and pager follow this list automatically.
12//
13// All markup injected here is built from static, in-code strings only (the
14// repo rule: never innerHTML untrusted content).
15
16"use strict";
17
18const PAGES = [
19 { file: "index.html", num: "01", short: "Overview", title: "Overview and orientation" },
20 { file: "router.html", num: "02", short: "The router", title: "The router: the front door of every box" },
21 { file: "processes.html", num: "03", short: "Processes on a box", title: "Everything that runs on a box" },
22 { file: "deploys.html", num: "04", short: "Deploys and updates", title: "Installing and updating the software" },
23 { file: "spaces.html", num: "05", short: "Creating spaces", title: "Creating and removing chat spaces" },
24 { file: "hibernation.html", num: "06", short: "Hibernation", title: "Hibernation: how idle spaces sleep in object storage" },
25 { file: "mesh.html", num: "07", short: "The mesh", title: "The mesh: running more than one box" },
26 { file: "operations.html", num: "08", short: "Day-to-day operations", title: "Day-to-day operations and checks" },
27 { file: "dev.html", num: "09", short: "The proving ground", title: "The proving ground: the dev mesh" },
28 { file: "reference.html", num: "10", short: "Reference", title: "Complete reference: commands, files, variables" },
29];
30
31function currentPage() {
32 const explicit = document.body.dataset.page;
33 if (explicit) return explicit;
34 const leaf = location.pathname.split("/").pop();
35 return leaf === "" ? "index.html" : leaf;
36}
37
38// ---- sidebar ----
39function renderSidebar() {
40 const nav = document.querySelector("nav.side");
41 if (!nav) return;
42 const here = currentPage();
43 const items = PAGES.map(p => {
44 const current = p.file === here ? ' aria-current="page"' : "";
45 return `<li><a href="${p.file}"${current}><span class="num">${p.num}</span><span>${p.short}</span></a></li>`;
46 }).join("");
47 nav.innerHTML =
48 `<p class="brand"><a href="index.html">Running the fleet</a></p>` +
49 `<p class="brand-sub">An operator&rsquo;s guide to the planet-scale system</p>` +
50 `<ol>${items}</ol>`;
51 nav.setAttribute("aria-label", "Guide contents");
52}
53
54// ---- previous / next pager + colophon ----
55function renderPager() {
56 const wrap = document.querySelector("main.content .wrap");
57 if (!wrap) return;
58 const here = currentPage();
59 const idx = PAGES.findIndex(p => p.file === here);
60 if (idx === -1) return;
61 const prev = PAGES[idx - 1];
62 const next = PAGES[idx + 1];
63 const pager = document.createElement("footer");
64 pager.className = "pager";
65 let html = "";
66 if (prev) html += `<a class="prev" href="${prev.file}"><span class="dir">Previous</span><span class="dest">${prev.num} &middot; ${prev.short}</span></a>`;
67 if (next) html += `<a class="next" href="${next.file}"><span class="dir">Next</span><span class="dest">${next.num} &middot; ${next.short}</span></a>`;
68 pager.innerHTML = html;
69 wrap.appendChild(pager);
70
71 const colophon = document.createElement("footer");
72 colophon.className = "colophon";
73 colophon.innerHTML =
74 `\u{1F916} This guide was written by an LLM from the code and design documents in this repository. ` +
75 `The code is the source of truth; if the guide and the code disagree, trust the code and please fix the guide. ` +
76 `How to update it: <a href="README.html">maintaining this guide</a>. ` +
77 `Deeper design rationale: <code>docs/planet-scale.md</code> and <code>docs/instance-router.md</code>.`;
78 wrap.appendChild(colophon);
79}
80
81// ---- heading anchors ----
82function renderHeadingAnchors() {
83 document.querySelectorAll("h2[id], h3[id]").forEach(h => {
84 const a = document.createElement("a");
85 a.className = "hlink";
86 a.href = "#" + h.id;
87 a.textContent = "#";
88 a.setAttribute("aria-label", "Link to this section");
89 h.appendChild(a);
90 });
91}
92
93// ---- the system map (the "you are here" diagram) ----
94//
95// One SVG, drawn identically on every page; the page highlights its own
96// component(s) via <div class="sysmap" data-highlight="router bucket">.
97// Node keys: members, router, instances, services, control, updater,
98// bucket, backup, peer.
99function sysmapSVG(highlights) {
100 const hl = key => highlights.includes(key) ? ' class="here"' : "";
101 return `
102<svg viewBox="0 0 960 400" role="img" aria-label="Map of the whole system: members' devices reach a box over the internet; on the box a router forwards traffic to chat space instances and sibling services; a control daemon and provisioner create spaces; an updater installs new versions; object storage holds parked spaces, leases, heartbeats and backups; peer boxes form the mesh.">
103 <!-- zone: the internet -->
104 <rect class="zone" x="10" y="34" width="140" height="330" rx="10"></rect>
105 <text class="zonelabel" x="24" y="24">THE INTERNET</text>
106 <g${hl("members")}>
107 <rect class="node" x="26" y="150" width="108" height="72" rx="7"></rect>
108 <text class="boxlabel" x="80" y="180" text-anchor="middle">Members&rsquo;</text>
109 <text class="boxlabel" x="80" y="196" text-anchor="middle">devices</text>
110 <text class="sublabel" x="80" y="211" text-anchor="middle">browsers, apps</text>
111 </g>
112
113 <!-- zone: one box -->
114 <rect class="zone" x="180" y="34" width="440" height="330" rx="10"></rect>
115 <text class="zonelabel" x="194" y="24">ONE BOX (A LINUX SERVER)</text>
116
117 <g${hl("router")}>
118 <rect class="node" x="200" y="60" width="150" height="86" rx="7"></rect>
119 <text class="boxlabel" x="275" y="88" text-anchor="middle">lchat router</text>
120 <text class="sublabel" x="275" y="104" text-anchor="middle">TLS on ports 80 / 443</text>
121 <text class="sublabel" x="275" y="119" text-anchor="middle">host &rarr; unix socket</text>
122 <text class="sublabel" x="275" y="134" text-anchor="middle">activator lives inside</text>
123 </g>
124
125 <g${hl("instances")}>
126 <rect class="node" x="420" y="52" width="180" height="34" rx="7"></rect>
127 <text class="boxlabel" x="510" y="73" text-anchor="middle">instance: chat-a.example.com</text>
128 <rect class="node" x="420" y="94" width="180" height="34" rx="7"></rect>
129 <text class="boxlabel" x="510" y="115" text-anchor="middle">instance: chat-b.example.com</text>
130 <rect class="node" x="420" y="136" width="180" height="34" rx="7"></rect>
131 <text class="boxlabel" x="510" y="157" text-anchor="middle">bot sidecars (one per instance)</text>
132 </g>
133
134 <g${hl("services")}>
135 <rect class="node" x="420" y="186" width="180" height="46" rx="7"></rect>
136 <text class="boxlabel" x="510" y="205" text-anchor="middle">sibling services</text>
137 <text class="sublabel" x="510" y="221" text-anchor="middle">home server, push relay</text>
138 </g>
139
140 <g${hl("control")}>
141 <rect class="node" x="200" y="186" width="150" height="62" rx="7"></rect>
142 <text class="boxlabel" x="275" y="207" text-anchor="middle">control daemon +</text>
143 <text class="boxlabel" x="275" y="222" text-anchor="middle">cloud provisioner</text>
144 <text class="sublabel" x="275" y="238" text-anchor="middle">creates chat spaces</text>
145 </g>
146
147 <g${hl("updater")}>
148 <rect class="node" x="200" y="272" width="150" height="62" rx="7"></rect>
149 <text class="boxlabel" x="275" y="295" text-anchor="middle">auto-updater</text>
150 <text class="sublabel" x="275" y="311" text-anchor="middle">hourly timer; installs</text>
151 <text class="sublabel" x="275" y="325" text-anchor="middle">signed releases</text>
152 </g>
153
154 <!-- zone: object storage -->
155 <rect class="zone" x="650" y="34" width="300" height="190" rx="10"></rect>
156 <text class="zonelabel" x="664" y="24">OBJECT STORAGE (AMAZON S3)</text>
157
158 <g${hl("bucket")}>
159 <rect class="node" x="668" y="52" width="264" height="86" rx="7"></rect>
160 <text class="boxlabel" x="800" y="76" text-anchor="middle">hibernation bucket</text>
161 <text class="sublabel" x="800" y="93" text-anchor="middle">parked space databases &middot; per-space locks</text>
162 <text class="sublabel" x="800" y="108" text-anchor="middle">mesh membership heartbeats</text>
163 <text class="sublabel" x="800" y="123" text-anchor="middle">one prefix per chat space</text>
164 </g>
165
166 <g${hl("backup")}>
167 <rect class="node" x="668" y="152" width="264" height="56" rx="7"></rect>
168 <text class="boxlabel" x="800" y="176" text-anchor="middle">fleet-backup bucket</text>
169 <text class="sublabel" x="800" y="192" text-anchor="middle">continuous copies of every database</text>
170 </g>
171
172 <!-- zone: peer boxes -->
173 <rect class="zone" x="650" y="252" width="300" height="112" rx="10"></rect>
174 <text class="zonelabel" x="664" y="246">OTHER BOXES &mdash; THE MESH</text>
175
176 <g${hl("peer")}>
177 <rect class="node" x="668" y="268" width="264" height="38" rx="7"></rect>
178 <text class="boxlabel" x="800" y="292" text-anchor="middle">peer box 2 (same software)</text>
179 <rect class="node" x="668" y="314" width="264" height="38" rx="7"></rect>
180 <text class="boxlabel" x="800" y="338" text-anchor="middle">peer box 3 (same software)</text>
181 </g>
182
183 <!-- wires -->
184 <line class="wire" x1="134" y1="186" x2="200" y2="110" marker-end="url(#arr)"></line>
185 <line class="wire" x1="350" y1="90" x2="420" y2="70" marker-end="url(#arr)"></line>
186 <line class="wire" x1="350" y1="100" x2="420" y2="110" marker-end="url(#arr)"></line>
187 <line class="wire" x1="350" y1="120" x2="420" y2="205" marker-end="url(#arr)"></line>
188 <line class="wire" x1="350" y1="80" x2="668" y2="80" marker-end="url(#arr)"></line>
189 <line class="wire" x1="600" y1="180" x2="668" y2="180" marker-end="url(#arr)"></line>
190 <line class="wire" x1="800" y1="268" x2="800" y2="224" marker-end="url(#arr)"></line>
191 <defs>
192 <marker id="arr" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
193 <path d="M0,0 L8,4 L0,8 z" fill="currentColor" opacity="0.55"></path>
194 </marker>
195 </defs>
196</svg>`;
197}
198
199function renderSysmap() {
200 const holder = document.querySelector(".sysmap");
201 if (!holder) return;
202 const highlights = (holder.dataset.highlight || "").split(/\s+/).filter(Boolean);
203 holder.innerHTML = sysmapSVG(highlights);
204}
205
206document.addEventListener("DOMContentLoaded", () => {
207 renderSidebar();
208 renderSysmap();
209 renderHeadingAnchors();
210 renderPager();
211});
212