archy/docs/container-architecture.html
Dorian 031b3c34f4 fix: container installs, Tor, kiosk, GRUB, LUKS display, error messages
Critical:
- fix: container installs fail with "statfs: no such file or directory"
  Root cause: NoNewPrivileges=yes in systemd blocks sudo inside backend.
  Fix: use std::fs::create_dir_all + podman unshare chown (no sudo needed)
- fix: Tor services.json never written — \$ARCHY_TOR_DIR escaping bug
- fix: kiosk white screen — increase health wait to 60s, add --disable-gpu

Improvements:
- feat: LUKS encryption badge in Server disk stats (backend detects dm-crypt)
- fix: GRUB theme text scaling on 4:3 monitors — explicit fonts, wider menu
- fix: suppress default Debian MOTD (custom profile.d welcome is enough)
- fix: install error messages now show "Failed to pull/start" instead of
  generic "Operation failed" (middleware.rs allowlist expanded)
- fix: container-tests CI — source cargo env before running tests
- docs: interactive container architecture diagram (HTML)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 16:35:06 +01:00

429 lines
30 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Archipelago Container Architecture</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'SF Pro', system-ui, sans-serif; background: #0a0e1a; color: #e0e0e0; min-height: 100vh; }
.header { padding: 24px 32px; background: linear-gradient(135deg, #0f1629 0%, #1a1040 100%); border-bottom: 1px solid rgba(99,102,241,0.2); }
.header h1 { font-size: 24px; font-weight: 600; color: #fff; }
.header p { font-size: 13px; color: #8b8fa3; margin-top: 4px; }
.controls { padding: 16px 32px; display: flex; gap: 8px; flex-wrap: wrap; background: #0d1120; border-bottom: 1px solid rgba(255,255,255,0.06); }
.filter-btn { padding: 6px 14px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.1); background: transparent; color: #8b8fa3; cursor: pointer; font-size: 12px; transition: all 0.2s; }
.filter-btn:hover { border-color: rgba(99,102,241,0.4); color: #c4c8e0; }
.filter-btn.active { background: rgba(99,102,241,0.15); border-color: rgba(99,102,241,0.5); color: #a5b4fc; }
.grid { padding: 24px 32px; display: grid; gap: 16px; }
.tier-section { margin-bottom: 8px; }
.tier-label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 1.5px; padding: 8px 0; display: flex; align-items: center; gap: 8px; }
.tier-label::after { content: ''; flex: 1; height: 1px; background: rgba(255,255,255,0.06); }
.tier-0 .tier-label { color: #f59e0b; }
.tier-1 .tier-label { color: #ef4444; }
.tier-2 .tier-label { color: #8b5cf6; }
.tier-3 .tier-label { color: #10b981; }
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 10px; }
.card { background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.06); border-radius: 10px; padding: 14px 16px; cursor: pointer; transition: all 0.2s; position: relative; overflow: hidden; }
.card:hover { background: rgba(255,255,255,0.06); border-color: rgba(255,255,255,0.12); transform: translateY(-1px); }
.card.expanded { background: rgba(255,255,255,0.05); border-color: rgba(99,102,241,0.3); }
.card.highlight { animation: pulse 1s ease-in-out; }
@keyframes pulse { 0%,100% { box-shadow: none; } 50% { box-shadow: 0 0 20px rgba(99,102,241,0.3); } }
.card-header { display: flex; justify-content: space-between; align-items: center; }
.card-name { font-size: 14px; font-weight: 600; color: #fff; }
.card-network { font-size: 10px; padding: 2px 6px; border-radius: 4px; font-weight: 500; }
.net-archy { background: rgba(239,68,68,0.15); color: #fca5a5; }
.net-bridge { background: rgba(16,185,129,0.15); color: #6ee7b7; }
.net-host { background: rgba(245,158,11,0.15); color: #fcd34d; }
.card-image { font-size: 11px; color: #6b7280; margin-top: 4px; font-family: 'SF Mono', monospace; }
.card-ports { font-size: 11px; color: #8b8fa3; margin-top: 6px; }
.card-ports span { display: inline-block; background: rgba(255,255,255,0.06); padding: 1px 6px; border-radius: 3px; margin: 1px 2px; font-family: 'SF Mono', monospace; }
.card-details { display: none; margin-top: 10px; padding-top: 10px; border-top: 1px solid rgba(255,255,255,0.06); font-size: 12px; line-height: 1.6; }
.card.expanded .card-details { display: block; }
.detail-row { display: flex; gap: 8px; }
.detail-label { color: #6b7280; min-width: 80px; }
.detail-value { color: #c4c8e0; }
.dep-tag { display: inline-block; background: rgba(139,92,246,0.15); color: #c4b5fd; padding: 1px 6px; border-radius: 3px; margin: 1px 2px; font-size: 11px; cursor: pointer; transition: all 0.15s; }
.dep-tag:hover { background: rgba(139,92,246,0.3); }
.tab-badge { display: inline-block; font-size: 10px; padding: 1px 5px; border-radius: 3px; margin-left: 6px; }
.tab-apps { background: rgba(99,102,241,0.15); color: #a5b4fc; }
.tab-services { background: rgba(245,158,11,0.15); color: #fcd34d; }
.legend { padding: 16px 32px; display: flex; gap: 20px; flex-wrap: wrap; font-size: 12px; color: #6b7280; border-top: 1px solid rgba(255,255,255,0.04); }
.legend-item { display: flex; align-items: center; gap: 6px; }
.legend-dot { width: 10px; height: 10px; border-radius: 3px; }
.dep-graph { padding: 24px 32px; }
.dep-graph h2 { font-size: 16px; font-weight: 600; margin-bottom: 16px; color: #fff; }
.dep-chain { font-family: 'SF Mono', Menlo, monospace; font-size: 13px; line-height: 1.8; color: #8b8fa3; background: rgba(255,255,255,0.02); border-radius: 10px; padding: 20px 24px; border: 1px solid rgba(255,255,255,0.04); white-space: pre; overflow-x: auto; }
.dep-chain .hl { color: #a5b4fc; font-weight: 600; cursor: pointer; }
.dep-chain .hl:hover { text-decoration: underline; }
.dep-chain .arrow { color: #4b5563; }
.dep-chain .comment { color: #4b5563; }
.stats { padding: 16px 32px; display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; }
.stat { background: rgba(255,255,255,0.03); border-radius: 8px; padding: 12px 16px; text-align: center; border: 1px solid rgba(255,255,255,0.04); }
.stat-value { font-size: 24px; font-weight: 700; color: #fff; }
.stat-label { font-size: 11px; color: #6b7280; margin-top: 2px; }
.hidden { display: none !important; }
</style>
</head>
<body>
<div class="header">
<h1>Archipelago Container Architecture</h1>
<p>Interactive map of all containers, dependencies, and networks &middot; Click any container for details</p>
</div>
<div class="stats">
<div class="stat"><div class="stat-value">28</div><div class="stat-label">Total Containers</div></div>
<div class="stat"><div class="stat-value">4</div><div class="stat-label">Tier 0 &middot; DB</div></div>
<div class="stat"><div class="stat-value">2</div><div class="stat-label">Tier 1 &middot; Core</div></div>
<div class="stat"><div class="stat-value">8</div><div class="stat-label">Tier 2 &middot; Service</div></div>
<div class="stat"><div class="stat-value">14</div><div class="stat-label">Tier 3 &middot; App</div></div>
</div>
<div class="controls">
<button class="filter-btn active" data-filter="all">All</button>
<button class="filter-btn" data-filter="tier-0">Databases</button>
<button class="filter-btn" data-filter="tier-1">Core</button>
<button class="filter-btn" data-filter="tier-2">Services</button>
<button class="filter-btn" data-filter="tier-3">Apps</button>
<button class="filter-btn" data-filter="archy-net">archy-net</button>
<button class="filter-btn" data-filter="bridge">bridge</button>
</div>
<div class="grid">
<div class="tier-section tier-0" data-tier="tier-0">
<div class="tier-label">Tier 0 &mdash; Databases</div>
<div class="cards">
<div class="card" data-name="archy-mempool-db" data-tier="tier-0" data-net="archy-net" onclick="toggle(this)">
<div class="card-header">
<span class="card-name">archy-mempool-db</span>
<span class="card-network net-archy">archy-net</span>
</div>
<div class="card-image">mariadb:11.4.10</div>
<div class="card-ports">No ports exposed</div>
<div class="card-details">
<div class="detail-row"><span class="detail-label">UID</span><span class="detail-value">100999:100999</span></div>
<div class="detail-row"><span class="detail-label">Health</span><span class="detail-value">mariadb -uroot -e 'SELECT 1'</span></div>
<div class="detail-row"><span class="detail-label">UI Tab</span><span class="tab-badge tab-services">Services</span></div>
<div class="detail-row"><span class="detail-label">Data</span><span class="detail-value">/var/lib/archipelago/mysql-mempool</span></div>
<div class="detail-row"><span class="detail-label">Deps</span><span class="detail-value">None</span></div>
<div class="detail-row"><span class="detail-label">Needed by</span><span class="detail-value"><span class="dep-tag" onclick="find('mempool-api')">mempool-api</span></span></div>
</div>
</div>
<div class="card" data-name="archy-btcpay-db" data-tier="tier-0" data-net="archy-net" onclick="toggle(this)">
<div class="card-header">
<span class="card-name">archy-btcpay-db</span>
<span class="card-network net-archy">archy-net</span>
</div>
<div class="card-image">postgres:15.17</div>
<div class="card-ports">No ports exposed</div>
<div class="card-details">
<div class="detail-row"><span class="detail-label">UID</span><span class="detail-value">100070:100070</span></div>
<div class="detail-row"><span class="detail-label">Health</span><span class="detail-value">pg_isready -U postgres</span></div>
<div class="detail-row"><span class="detail-label">UI Tab</span><span class="tab-badge tab-services">Services</span></div>
<div class="detail-row"><span class="detail-label">Needed by</span><span class="detail-value"><span class="dep-tag" onclick="find('archy-nbxplorer')">nbxplorer</span> <span class="dep-tag" onclick="find('btcpay-server')">btcpay</span></span></div>
</div>
</div>
<div class="card" data-name="immich_postgres" data-tier="tier-0" data-net="bridge" onclick="toggle(this)" style="opacity:0.5">
<div class="card-header">
<span class="card-name">immich_postgres</span>
<span class="card-network net-bridge">bridge</span>
</div>
<div class="card-image">immich-postgres:14 (optional)</div>
<div class="card-details"><div class="detail-row"><span class="detail-label">Needed by</span><span class="detail-value"><span class="dep-tag" onclick="find('immich_server')">immich</span></span></div></div>
</div>
<div class="card" data-name="immich_redis" data-tier="tier-0" data-net="bridge" onclick="toggle(this)" style="opacity:0.5">
<div class="card-header">
<span class="card-name">immich_redis</span>
<span class="card-network net-bridge">bridge</span>
</div>
<div class="card-image">valkey:8.1.6 (optional)</div>
<div class="card-details"><div class="detail-row"><span class="detail-label">Needed by</span><span class="detail-value"><span class="dep-tag" onclick="find('immich_server')">immich</span></span></div></div>
</div>
</div>
</div>
<div class="tier-section tier-1" data-tier="tier-1">
<div class="tier-label">Tier 1 &mdash; Core Infrastructure</div>
<div class="cards">
<div class="card" data-name="bitcoin-knots" data-tier="tier-1" data-net="archy-net" onclick="toggle(this)">
<div class="card-header">
<span class="card-name">bitcoin-knots</span>
<span class="card-network net-archy">archy-net</span>
</div>
<div class="card-image">bitcoin-knots:latest</div>
<div class="card-ports">Ports: <span>8332</span> <span>8333</span></div>
<div class="card-details">
<div class="detail-row"><span class="detail-label">UID</span><span class="detail-value">100101:100101</span></div>
<div class="detail-row"><span class="detail-label">Health</span><span class="detail-value">bitcoin-cli getblockchaininfo</span></div>
<div class="detail-row"><span class="detail-label">UI Tab</span><span class="tab-badge tab-apps">Apps</span></div>
<div class="detail-row"><span class="detail-label">Memory</span><span class="detail-value">2g (1g low-mem)</span></div>
<div class="detail-row"><span class="detail-label">Disk</span><span class="detail-value">Prunes if &lt;1TB, txindex if &ge;1TB</span></div>
<div class="detail-row"><span class="detail-label">Deps</span><span class="detail-value">None &mdash; ROOT DEPENDENCY</span></div>
<div class="detail-row"><span class="detail-label">Needed by</span><span class="detail-value"><span class="dep-tag" onclick="find('electrumx')">electrumx</span> <span class="dep-tag" onclick="find('lnd')">lnd</span> <span class="dep-tag" onclick="find('mempool-api')">mempool</span> <span class="dep-tag" onclick="find('archy-nbxplorer')">nbxplorer</span> <span class="dep-tag" onclick="find('fedimint')">fedimint</span></span></div>
</div>
</div>
<div class="card" data-name="electrumx" data-tier="tier-1" data-net="archy-net" onclick="toggle(this)">
<div class="card-header">
<span class="card-name">electrumx</span>
<span class="card-network net-archy">archy-net</span>
</div>
<div class="card-image">electrumx:v1.18.0</div>
<div class="card-ports">Ports: <span>50001</span></div>
<div class="card-details">
<div class="detail-row"><span class="detail-label">Health</span><span class="detail-value">curl localhost:8000</span></div>
<div class="detail-row"><span class="detail-label">UI Tab</span><span class="tab-badge tab-apps">Apps</span></div>
<div class="detail-row"><span class="detail-label">Deps</span><span class="detail-value"><span class="dep-tag" onclick="find('bitcoin-knots')">bitcoin-knots</span></span></div>
<div class="detail-row"><span class="detail-label">Needed by</span><span class="detail-value"><span class="dep-tag" onclick="find('mempool-api')">mempool-api</span></span></div>
</div>
</div>
</div>
</div>
<div class="tier-section tier-2" data-tier="tier-2">
<div class="tier-label">Tier 2 &mdash; Services</div>
<div class="cards">
<div class="card" data-name="lnd" data-tier="tier-2" data-net="archy-net" onclick="toggle(this)">
<div class="card-header"><span class="card-name">lnd</span><span class="card-network net-archy">archy-net</span></div>
<div class="card-image">lnd:v0.18.4-beta</div>
<div class="card-ports">Ports: <span>9735</span> <span>10009</span> <span>8080</span></div>
<div class="card-details">
<div class="detail-row"><span class="detail-label">Deps</span><span class="detail-value"><span class="dep-tag" onclick="find('bitcoin-knots')">bitcoin-knots</span></span></div>
<div class="detail-row"><span class="detail-label">UI Tab</span><span class="tab-badge tab-apps">Apps</span></div>
<div class="detail-row"><span class="detail-label">Needed by</span><span class="detail-value"><span class="dep-tag" onclick="find('fedimint-gateway')">fedi-gateway (LND mode)</span></span></div>
</div>
</div>
<div class="card" data-name="mempool-api" data-tier="tier-2" data-net="archy-net" onclick="toggle(this)">
<div class="card-header"><span class="card-name">mempool-api</span><span class="card-network net-archy">archy-net</span></div>
<div class="card-image">mempool-backend:v3.0.0</div>
<div class="card-ports">Ports: <span>8999</span></div>
<div class="card-details">
<div class="detail-row"><span class="detail-label">Deps</span><span class="detail-value"><span class="dep-tag" onclick="find('bitcoin-knots')">bitcoin-knots</span> <span class="dep-tag" onclick="find('electrumx')">electrumx</span> <span class="dep-tag" onclick="find('archy-mempool-db')">mempool-db</span></span></div>
<div class="detail-row"><span class="detail-label">UI Tab</span><span class="tab-badge tab-services">Services</span></div>
<div class="detail-row"><span class="detail-label">Needed by</span><span class="detail-value"><span class="dep-tag" onclick="find('archy-mempool-web')">mempool-web</span></span></div>
</div>
</div>
<div class="card" data-name="archy-mempool-web" data-tier="tier-2" data-net="archy-net" onclick="toggle(this)">
<div class="card-header"><span class="card-name">archy-mempool-web</span><span class="card-network net-archy">archy-net</span></div>
<div class="card-image">mempool-frontend:v3.0.0</div>
<div class="card-ports">Ports: <span>4080</span></div>
<div class="card-details">
<div class="detail-row"><span class="detail-label">Deps</span><span class="detail-value"><span class="dep-tag" onclick="find('mempool-api')">mempool-api</span></span></div>
<div class="detail-row"><span class="detail-label">UI Tab</span><span class="tab-badge tab-apps">Apps</span></div>
</div>
</div>
<div class="card" data-name="archy-nbxplorer" data-tier="tier-2" data-net="archy-net" onclick="toggle(this)">
<div class="card-header"><span class="card-name">archy-nbxplorer</span><span class="card-network net-archy">archy-net</span></div>
<div class="card-image">nbxplorer:2.6.0</div>
<div class="card-ports">Ports: <span>32838</span></div>
<div class="card-details">
<div class="detail-row"><span class="detail-label">Deps</span><span class="detail-value"><span class="dep-tag" onclick="find('bitcoin-knots')">bitcoin-knots</span> <span class="dep-tag" onclick="find('archy-btcpay-db')">btcpay-db</span></span></div>
<div class="detail-row"><span class="detail-label">UI Tab</span><span class="tab-badge tab-services">Services</span></div>
<div class="detail-row"><span class="detail-label">Needed by</span><span class="detail-value"><span class="dep-tag" onclick="find('btcpay-server')">btcpay</span></span></div>
</div>
</div>
<div class="card" data-name="btcpay-server" data-tier="tier-2" data-net="archy-net" onclick="toggle(this)">
<div class="card-header"><span class="card-name">btcpay-server</span><span class="card-network net-archy">archy-net</span></div>
<div class="card-image">btcpayserver:1.13.7</div>
<div class="card-ports">Ports: <span>23000</span></div>
<div class="card-details">
<div class="detail-row"><span class="detail-label">Deps</span><span class="detail-value"><span class="dep-tag" onclick="find('archy-nbxplorer')">nbxplorer</span> <span class="dep-tag" onclick="find('archy-btcpay-db')">btcpay-db</span></span></div>
<div class="detail-row"><span class="detail-label">UI Tab</span><span class="tab-badge tab-apps">Apps</span></div>
</div>
</div>
<div class="card" data-name="fedimint" data-tier="tier-2" data-net="archy-net" onclick="toggle(this)">
<div class="card-header"><span class="card-name">fedimint</span><span class="card-network net-archy">archy-net</span></div>
<div class="card-image">fedimintd:v0.10.0</div>
<div class="card-ports">Ports: <span>8173</span> <span>8174</span> <span>8175</span></div>
<div class="card-details">
<div class="detail-row"><span class="detail-label">Deps</span><span class="detail-value"><span class="dep-tag" onclick="find('bitcoin-knots')">bitcoin-knots</span></span></div>
<div class="detail-row"><span class="detail-label">UI Tab</span><span class="tab-badge tab-apps">Apps</span></div>
<div class="detail-row"><span class="detail-label">Needed by</span><span class="detail-value"><span class="dep-tag" onclick="find('fedimint-gateway')">fedi-gateway</span></span></div>
</div>
</div>
<div class="card" data-name="fedimint-gateway" data-tier="tier-2" data-net="archy-net" onclick="toggle(this)">
<div class="card-header"><span class="card-name">fedimint-gateway</span><span class="card-network net-archy">archy-net</span></div>
<div class="card-image">gatewayd:v0.10.0</div>
<div class="card-ports">Ports: <span>8176</span></div>
<div class="card-details">
<div class="detail-row"><span class="detail-label">Deps</span><span class="detail-value"><span class="dep-tag" onclick="find('bitcoin-knots')">bitcoin-knots</span> <span class="dep-tag" onclick="find('fedimint')">fedimint</span></span></div>
<div class="detail-row"><span class="detail-label">UI Tab</span><span class="tab-badge tab-apps">Apps</span></div>
<div class="detail-row"><span class="detail-label">Note</span><span class="detail-value">Uses LDK (built-in Lightning) if no LND</span></div>
</div>
</div>
<div class="card" data-name="immich_server" data-tier="tier-2" data-net="bridge" onclick="toggle(this)" style="opacity:0.5">
<div class="card-header"><span class="card-name">immich_server</span><span class="card-network net-bridge">bridge</span></div>
<div class="card-image">immich-server:release (optional)</div>
<div class="card-ports">Ports: <span>2283</span></div>
<div class="card-details">
<div class="detail-row"><span class="detail-label">Deps</span><span class="detail-value"><span class="dep-tag" onclick="find('immich_postgres')">immich_postgres</span> <span class="dep-tag" onclick="find('immich_redis')">immich_redis</span></span></div>
</div>
</div>
</div>
</div>
<div class="tier-section tier-3" data-tier="tier-3">
<div class="tier-label">Tier 3 &mdash; Applications (independent, no dependencies)</div>
<div class="cards">
<div class="card" data-name="homeassistant" data-tier="tier-3" data-net="bridge" onclick="toggle(this)">
<div class="card-header"><span class="card-name">homeassistant</span><span class="card-network net-bridge">bridge</span></div>
<div class="card-image">home-assistant:2024.1</div>
<div class="card-ports">Ports: <span>8123</span></div>
</div>
<div class="card" data-name="grafana" data-tier="tier-3" data-net="bridge" onclick="toggle(this)">
<div class="card-header"><span class="card-name">grafana</span><span class="card-network net-bridge">bridge</span></div>
<div class="card-image">grafana:10.2.0</div>
<div class="card-ports">Ports: <span>3000</span></div>
<div class="card-details"><div class="detail-row"><span class="detail-label">UID</span><span class="detail-value">100472:100472</span></div></div>
</div>
<div class="card" data-name="jellyfin" data-tier="tier-3" data-net="bridge" onclick="toggle(this)">
<div class="card-header"><span class="card-name">jellyfin</span><span class="card-network net-bridge">bridge</span></div>
<div class="card-image">jellyfin:10.8.13</div>
<div class="card-ports">Ports: <span>8096</span></div>
</div>
<div class="card" data-name="photoprism" data-tier="tier-3" data-net="bridge" onclick="toggle(this)">
<div class="card-header"><span class="card-name">photoprism</span><span class="card-network net-bridge">bridge</span></div>
<div class="card-image">photoprism:240915</div>
<div class="card-ports">Ports: <span>2342</span></div>
</div>
<div class="card" data-name="vaultwarden" data-tier="tier-3" data-net="bridge" onclick="toggle(this)">
<div class="card-header"><span class="card-name">vaultwarden</span><span class="card-network net-bridge">bridge</span></div>
<div class="card-image">vaultwarden:1.30.0-alpine</div>
<div class="card-ports">Ports: <span>8082</span></div>
</div>
<div class="card" data-name="nextcloud" data-tier="tier-3" data-net="bridge" onclick="toggle(this)">
<div class="card-header"><span class="card-name">nextcloud</span><span class="card-network net-bridge">bridge</span></div>
<div class="card-image">nextcloud:28</div>
<div class="card-ports">Ports: <span>8085</span></div>
</div>
<div class="card" data-name="searxng" data-tier="tier-3" data-net="bridge" onclick="toggle(this)">
<div class="card-header"><span class="card-name">searxng</span><span class="card-network net-bridge">bridge</span></div>
<div class="card-image">searxng:latest</div>
<div class="card-ports">Ports: <span>8888</span></div>
</div>
<div class="card" data-name="uptime-kuma" data-tier="tier-3" data-net="bridge" onclick="toggle(this)">
<div class="card-header"><span class="card-name">uptime-kuma</span><span class="card-network net-bridge">bridge</span></div>
<div class="card-image">uptime-kuma:1</div>
<div class="card-ports">Ports: <span>3001</span></div>
</div>
<div class="card" data-name="filebrowser" data-tier="tier-3" data-net="bridge" onclick="toggle(this)">
<div class="card-header"><span class="card-name">filebrowser</span><span class="card-network net-bridge">bridge</span></div>
<div class="card-image">filebrowser:v2.27.0</div>
<div class="card-ports">Ports: <span>8083</span></div>
<div class="card-details"><div class="detail-row"><span class="detail-label">Note</span><span class="detail-value">Only container created by first-boot (unbundled ISO)</span></div></div>
</div>
<div class="card" data-name="onlyoffice" data-tier="tier-3" data-net="bridge" onclick="toggle(this)">
<div class="card-header"><span class="card-name">onlyoffice</span><span class="card-network net-bridge">bridge</span></div>
<div class="card-image">onlyoffice:latest</div>
<div class="card-ports">Ports: <span>9980</span></div>
</div>
<div class="card" data-name="ollama" data-tier="tier-3" data-net="bridge" onclick="toggle(this)" style="opacity:0.5">
<div class="card-header"><span class="card-name">ollama</span><span class="card-network net-bridge">bridge</span></div>
<div class="card-image">ollama:latest (optional)</div>
<div class="card-ports">Ports: <span>11434</span></div>
</div>
<div class="card" data-name="nginx-proxy-manager" data-tier="tier-3" data-net="bridge" onclick="toggle(this)">
<div class="card-header"><span class="card-name">nginx-proxy-manager</span><span class="card-network net-bridge">bridge</span></div>
<div class="card-image">nginx-proxy-manager:latest</div>
<div class="card-ports">Ports: <span>81</span> <span>8084</span> <span>8443</span></div>
</div>
<div class="card" data-name="portainer" data-tier="tier-3" data-net="bridge" onclick="toggle(this)">
<div class="card-header"><span class="card-name">portainer</span><span class="card-network net-bridge">bridge</span></div>
<div class="card-image">portainer:latest</div>
<div class="card-ports">Ports: <span>9000</span></div>
</div>
</div>
</div>
</div>
<div class="dep-graph">
<h2>Dependency Chain</h2>
<div class="dep-chain"><span class="comment">// Startup order: Tier 0 → 1 → 2 → 3</span>
<span class="comment">// Health monitor restarts in this order too</span>
<span class="hl" onclick="find('archy-mempool-db')">mempool-db</span> <span class="arrow">───┐</span>
<span class="hl" onclick="find('archy-btcpay-db')">btcpay-db</span> <span class="arrow">───┤</span>
<span class="arrow"></span>
<span class="arrow">├──→</span> <span class="hl" onclick="find('bitcoin-knots')">bitcoin-knots</span> <span class="arrow">──→</span> <span class="hl" onclick="find('electrumx')">electrumx</span>
<span class="arrow"></span> <span class="arrow"></span>
<span class="arrow"></span> <span class="arrow">┌────┴────┬──────────┬──────────┐</span>
<span class="arrow"></span> <span class="arrow"></span> <span class="arrow"></span> <span class="arrow"></span> <span class="arrow"></span>
<span class="arrow"></span> <span class="hl" onclick="find('lnd')">lnd</span> <span class="hl" onclick="find('fedimint')">fedimint</span> <span class="hl" onclick="find('mempool-api')">mempool-api</span> <span class="hl" onclick="find('archy-nbxplorer')">nbxplorer</span>
<span class="arrow"></span> <span class="arrow"></span> <span class="arrow"></span> <span class="arrow"></span>
<span class="arrow"></span> <span class="hl" onclick="find('fedimint-gateway')">fedi-gw</span> <span class="hl" onclick="find('archy-mempool-web')">mempool-web</span> <span class="hl" onclick="find('btcpay-server')">btcpay</span>
<span class="comment">// Tier 3: All independent — start in any order</span>
<span class="hl" onclick="find('filebrowser')">filebrowser</span> <span class="hl" onclick="find('grafana')">grafana</span> <span class="hl" onclick="find('homeassistant')">homeassist</span> <span class="hl" onclick="find('jellyfin')">jellyfin</span> <span class="hl" onclick="find('photoprism')">photoprism</span>
<span class="hl" onclick="find('vaultwarden')">vaultwarden</span> <span class="hl" onclick="find('nextcloud')">nextcloud</span> <span class="hl" onclick="find('searxng')">searxng</span> <span class="hl" onclick="find('uptime-kuma')">uptime-kuma</span> <span class="hl" onclick="find('ollama')">ollama</span></div>
</div>
<div class="legend">
<div class="legend-item"><div class="legend-dot" style="background:rgba(239,68,68,0.4)"></div> archy-net (Bitcoin stack, inter-container DNS)</div>
<div class="legend-item"><div class="legend-dot" style="background:rgba(16,185,129,0.4)"></div> bridge (standalone apps, port-mapped)</div>
<div class="legend-item"><div class="legend-dot" style="background:rgba(99,102,241,0.4)"></div> Apps tab (user-facing)</div>
<div class="legend-item"><div class="legend-dot" style="background:rgba(245,158,11,0.4)"></div> Services tab (infrastructure)</div>
</div>
<script>
function toggle(el) {
el.classList.toggle('expanded')
}
function find(name) {
event.stopPropagation()
// Reset all filters
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'))
document.querySelector('[data-filter="all"]').classList.add('active')
document.querySelectorAll('.card').forEach(c => c.classList.remove('hidden'))
document.querySelectorAll('.tier-section').forEach(t => t.classList.remove('hidden'))
const card = document.querySelector(`[data-name="${name}"]`)
if (card) {
card.scrollIntoView({ behavior: 'smooth', block: 'center' })
card.classList.add('expanded', 'highlight')
setTimeout(() => card.classList.remove('highlight'), 1200)
}
}
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'))
btn.classList.add('active')
const filter = btn.dataset.filter
document.querySelectorAll('.card').forEach(card => {
if (filter === 'all') { card.classList.remove('hidden'); return }
const match = card.dataset.tier === filter || card.dataset.net === filter
card.classList.toggle('hidden', !match)
})
document.querySelectorAll('.tier-section').forEach(section => {
if (filter === 'all' || filter === 'archy-net' || filter === 'bridge') {
section.classList.remove('hidden')
return
}
section.classList.toggle('hidden', section.dataset.tier !== filter)
})
})
})
</script>
</body>
</html>