Files
ai-resume/content/assets/diagrams/homelab-topology.svg
Sam Rolfe cb45c462be diagram: keep the runtime note inside its own panel
The note under the loopback box ran under the edge panel at 11.5 px. Shortened
so it ends inside the runtime panel at every render size. Re-measured after the
change: performance 100, accessibility 100.

(cherry picked from commit 4fb759cf22c3609542600009536613a04e461746)
2026-10-28 15:56:13 +11:00

110 lines
5.6 KiB
XML

<svg xmlns="http://www.w3.org/2000/svg" width="1110" height="600" viewBox="0 0 1110 600" role="img" aria-labelledby="topoTitle topoDesc" font-family="ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif">
<title id="topoTitle">Homelab topology: build host, runtime host and public edge</title>
<desc id="topoDesc">Three hosts. The build host produces a release; the runtime host runs the site, the vector store, a workflow worker and the model gateway, all bound to loopback; the public edge terminates TLS and is the only host reachable from the internet. All three join one private mesh, and backups run in three tiers.</desc>
<style>
/* On paper the diagram is ink on white, not a dark panel. Presentation
attributes are the weakest source, so these rules win without editing
the drawing. */
@media print {
rect { fill: #fff; }
text { fill: #111; }
text[fill="#6ee7c6"] { fill: #0b5c48; }
text[fill="#8b98a8"] { fill: #444; }
line, path { stroke: #555; }
marker path { fill: #555; }
}
</style>
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0 0 10 5 0 10z" fill="#6ee7c6"/>
</marker>
</defs>
<g fill="#12161b" stroke="#33404d" stroke-width="1.5">
<rect x="30" y="104" width="310" height="268" rx="10"/>
<rect x="400" y="104" width="310" height="268" rx="10"/>
<rect x="770" y="104" width="310" height="268" rx="10"/>
</g>
<!-- internet -->
<g>
<rect x="810" y="24" width="230" height="46" rx="23" fill="#171d24" stroke="#33404d" stroke-width="1.5"/>
<text x="925" y="52" text-anchor="middle" font-size="13" fill="#b3bcc8">public internet · https :443</text>
<line x1="925" y1="70" x2="925" y2="100" stroke="#6ee7c6" stroke-width="1.5" marker-end="url(#arrow)"/>
</g>
<!-- host headers -->
<g fill="#e6edf3" font-size="15" font-weight="600">
<text x="54" y="136">Build and development</text>
<text x="424" y="136">Runtime host</text>
<text x="794" y="136">Public edge</text>
</g>
<g fill="#8b98a8" font-size="12" font-family="ui-monospace, SFMono-Regular, Menlo, monospace">
<text x="54" y="156">.27 · 4-screen desktop</text>
<text x="424" y="156">.13 · 15 GiB · memory-bound</text>
<text x="794" y="156">.35 · Ubuntu · 2 vCPU</text>
</g>
<!-- build host items -->
<g fill="#b3bcc8" font-size="12.5">
<text x="54" y="192">· repository working copy</text>
<text x="54" y="219">· agent fleet, panes and approvals</text>
<text x="54" y="246">· content, diagrams and exhibits</text>
<text x="54" y="273">· release bundles and hashes</text>
</g>
<!-- runtime: loopback trust boundary -->
<rect x="416" y="172" width="278" height="150" rx="8" fill="none" stroke="#6ee7c6" stroke-width="1.2" stroke-dasharray="5 4"/>
<text x="430" y="192" font-size="11" fill="#6ee7c6" font-family="ui-monospace, SFMono-Regular, Menlo, monospace">bound to 127.0.0.1</text>
<g fill="#b3bcc8" font-size="12.5">
<text x="430" y="216">· site server :8096</text>
<text x="430" y="241">· Qdrant :6333 / :6334</text>
<text x="430" y="266">· Prefect worker</text>
<text x="430" y="291">· model gateway (shared)</text>
</g>
<text x="424" y="345" font-size="11.5" fill="#8b98a8">Caps equal swap; nothing listens publicly.</text>
<!-- edge host items -->
<g fill="#b3bcc8" font-size="12.5">
<text x="794" y="192">· Caddy terminates TLS</text>
<text x="794" y="219">· proxies to the runtime host</text>
<text x="794" y="246">· Gitea · Immich · Nextcloud</text>
<text x="794" y="273">· one public listener, many sites</text>
</g>
<!-- hand-off arrows -->
<line x1="340" y1="230" x2="396" y2="230" stroke="#6ee7c6" stroke-width="1.5" marker-end="url(#arrow)"/>
<text x="368" y="218" text-anchor="middle" font-size="10.5" fill="#8b98a8">deploy</text>
<line x1="710" y1="230" x2="766" y2="230" stroke="#6ee7c6" stroke-width="1.5" marker-end="url(#arrow)"/>
<text x="738" y="218" text-anchor="middle" font-size="10.5" fill="#8b98a8">proxy</text>
<!-- mesh band -->
<rect x="30" y="410" width="1050" height="56" rx="10" fill="#12161b" stroke="#33404d" stroke-width="1.2" stroke-dasharray="6 5"/>
<text x="555" y="436" text-anchor="middle" font-size="13" fill="#b3bcc8">private mesh · SSH on every host · remote access without an exposed port</text>
<text x="555" y="454" text-anchor="middle" font-size="11" fill="#8b98a8">no service is reachable because it happened to bind to every interface</text>
<g stroke="#33404d" stroke-width="1" stroke-dasharray="3 4">
<line x1="185" y1="372" x2="185" y2="410"/>
<line x1="555" y1="372" x2="555" y2="410"/>
<line x1="925" y1="372" x2="925" y2="410"/>
</g>
<!-- backup tiers -->
<text x="30" y="506" font-size="13" font-weight="600" fill="#e6edf3">Three backup tiers — each covers a different failure</text>
<g fill="#12161b" stroke="#33404d" stroke-width="1.2">
<rect x="30" y="522" width="330" height="52" rx="8"/>
<rect x="390" y="522" width="330" height="52" rx="8"/>
<rect x="750" y="522" width="330" height="52" rx="8"/>
</g>
<g fill="#b3bcc8" font-size="12.5">
<text x="50" y="545">local snapshots, on the machine</text>
<text x="410" y="545">a copy on another machine</text>
<text x="770" y="545">encrypted object storage, offsite</text>
</g>
<g fill="#8b98a8" font-size="11">
<text x="50" y="562">restore tested, runbook beside it</text>
<text x="410" y="562">over the private mesh</text>
<text x="770" y="562">retention stated in the job</text>
</g>
</svg>