- Full deployment reference for office.rmf44.xyz - Architecture, procedure, troubleshooting, operations pages - 4 SVG diagrams (topology, container-tree, data-flow, request-flow) - Mirrors gite_replacement template structure - Verified via 70/70 ad-hoc checks on 2026-08-10
266 lines
11 KiB
HTML
266 lines
11 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Architecture — Nextcloud Office</title>
|
||
<link rel="stylesheet" href="assets/style.css">
|
||
</head>
|
||
<body>
|
||
<div id="wrapper">
|
||
|
||
<div class="crumbs"><a href="index.html">Nextcloud Office</a> › Architecture</div>
|
||
|
||
<ul class="nav">
|
||
<li><a href="index.html">Overview</a></li>
|
||
<li><a href="architecture.html" class="active">Architecture</a></li>
|
||
<li><a href="procedure.html">Procedure</a></li>
|
||
<li><a href="troubleshooting.html">Troubleshooting</a></li>
|
||
<li><a href="operations.html">Operations</a></li>
|
||
</ul>
|
||
|
||
<h1>Architecture</h1>
|
||
<p>
|
||
Three layers to understand: <strong>network</strong> (public → Caddy →
|
||
NetBird → AIO Apache), <strong>containers</strong> (8 AIO processes on
|
||
one host, single docker network), and <strong>data flow</strong>
|
||
(NFS for everything, plus a postgres dump that hits NFS too).
|
||
</p>
|
||
|
||
<h2>Topology — public ingress</h2>
|
||
<p>
|
||
Three active layers, plus the retired OnlyOffice tier that's been
|
||
torn down:
|
||
</p>
|
||
<p><img src="assets/diagrams/topology.svg" alt="Topology — Caddy on hawker, NetBird mesh, AIO on homework03, NFS to desslok" class="diagram"></p>
|
||
|
||
<ol>
|
||
<li>
|
||
<strong>Public ingress</strong> — Cloudflare DNS points
|
||
<code>office.rmf44.xyz</code> directly at <code>hawker
|
||
(192.255.159.202)</code>. Caddy in the <code>caddy-caddy-1</code>
|
||
container terminates TLS with a Let's Encrypt cert and
|
||
reverse-proxies to <code>100.79.142.164:11000</code>
|
||
(homework03's NetBird IP, AIO Apache port).
|
||
</li>
|
||
<li>
|
||
<strong>NetBird mesh</strong> — WireGuard P2P between hawker
|
||
(<code>100.79.4.103</code>) and homework03
|
||
(<code>100.79.142.164</code>). Direct host-host (no relay) over
|
||
UDP 51820.
|
||
</li>
|
||
<li>
|
||
<strong>Compute + data</strong> — 8 AIO containers on homework03,
|
||
sharing host network via <code>network_mode: host</code> on the
|
||
mastercontainer. Apache listens on host :11000; mastercontainer
|
||
owns :80, :8080, :8443, :9000.
|
||
</li>
|
||
<li>
|
||
<strong>Storage</strong> — NFSv4.1 from
|
||
<code>desslok:/slab/container_storage/office</code> mounted at
|
||
<code>/srv/nc-files/</code> on homework03. Subdirs:
|
||
<code>nextcloud/</code> for user files, <code>backups/</code> for
|
||
daily pgdump + config + user-files tars.
|
||
</li>
|
||
<li>
|
||
<strong>Retired (torn down)</strong> — OnlyOffice container
|
||
(<code>onlyoffice-files-1</code>) on hawker, plus its nginx
|
||
vhost, plus its daily backup pipeline on hector. Replaced by
|
||
the AIO stack.
|
||
</li>
|
||
</ol>
|
||
|
||
<h2>Container tree — what's running on homework03</h2>
|
||
<p>
|
||
AIO manages its own container lifecycle; you start the
|
||
<em>mastercontainer</em> via <code>docker compose up -d</code> and
|
||
it spawns the rest. Each side container has a named docker volume
|
||
bound to a host directory so the data survives mastercontainer
|
||
restarts.
|
||
</p>
|
||
<p><img src="assets/diagrams/container-tree.svg" alt="AIO container tree with bind mounts and ports" class="diagram"></p>
|
||
|
||
<table>
|
||
<tr><th>Container</th><th>Role</th><th>Bind target</th><th>Owner</th><th>Port</th></tr>
|
||
<tr>
|
||
<td><code>nextcloud-aio-mastercontainer</code></td>
|
||
<td>Orchestrator, domain validator, admin UI</td>
|
||
<td><code>./nextcloud-aio-mastercontainer/</code></td>
|
||
<td><code>33:33</code> (www-data)</td>
|
||
<td>:80 (acme), :8080 (admin UI), :8443 (alt admin), :9000 (nextcloud-fcgi via apache)</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>nextcloud-aio-apache</code></td>
|
||
<td>Reverse proxy → nextcloud-fcgi, public-facing</td>
|
||
<td><code>./nextcloud-aio-apache/</code></td>
|
||
<td><code>33:33</code></td>
|
||
<td>:11000 (host) → :11000 (container)</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>nextcloud-aio-nextcloud</code></td>
|
||
<td>PHP-FPM + Nextcloud app code</td>
|
||
<td><code>./nextcloud-aio-nextcloud/</code> + <code>/mnt/nc-data/nextcloud-data</code> via <code>NEXTCLOUD_DATADIR</code></td>
|
||
<td>root (entrypoint)</td>
|
||
<td>:9000 (PHP-FPM)</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>nextcloud-aio-database</code></td>
|
||
<td>PostgreSQL 16</td>
|
||
<td><code>./nextcloud-aio-database/</code></td>
|
||
<td><code>999:999</code></td>
|
||
<td>:5432 (internal only)</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>nextcloud-aio-redis</code></td>
|
||
<td>Cache + file locking</td>
|
||
<td><code>./nextcloud-aio-redis/</code></td>
|
||
<td><code>999:999</code></td>
|
||
<td>:6379 (internal only)</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>nextcloud-aio-collabora</code></td>
|
||
<td>CODE Office (Word/Excel/PowerPoint editing)</td>
|
||
<td><code>./nextcloud-aio-collabora/</code></td>
|
||
<td><code>100:101</code></td>
|
||
<td>:9980 (internal only, called by apache)</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>nextcloud-aio-whiteboard</code></td>
|
||
<td>Built-in collaborative whiteboard</td>
|
||
<td><code>./nextcloud-aio-whiteboard/</code></td>
|
||
<td>(n/a)</td>
|
||
<td>:3002 (internal only)</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>nextcloud-aio-notify-push</code></td>
|
||
<td>Push notification backend (websocket)</td>
|
||
<td><code>./nextcloud-aio-notify-push/</code></td>
|
||
<td>(n/a)</td>
|
||
<td>:7867 (internal only)</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>nextcloud-aio-imaginary</code></td>
|
||
<td>(DISABLED — saves RAM)</td>
|
||
<td>—</td>
|
||
<td>—</td>
|
||
<td>—</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>nextcloud-aio-fulltextsearch</code></td>
|
||
<td>(DISABLED — saves RAM)</td>
|
||
<td>—</td>
|
||
<td>—</td>
|
||
<td>—</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>nextcloud-aio-clamav</code></td>
|
||
<td>(DISABLED — saves RAM)</td>
|
||
<td>—</td>
|
||
<td>—</td>
|
||
</tr>
|
||
</table>
|
||
|
||
<div class="callout info">
|
||
<p>
|
||
<strong>Why host network?</strong> The AIO mastercontainer runs
|
||
with <code>network_mode: host</code> so it can publish ports :80
|
||
and :8443 directly on the host's network namespace. Apache (the
|
||
sidecar) is reached via host :11000 because AIO's domain
|
||
validation flow requires mastercontainer own host :80.
|
||
</p>
|
||
</div>
|
||
|
||
<h2>Data flow — where each piece lives</h2>
|
||
<p>
|
||
Most of AIO's data is on NFS (<code>/srv/nc-files</code> on
|
||
homework03). The Postgres database is inside the database
|
||
container; its data directory is a docker named-volume bind, not
|
||
on NFS — keeping PostgreSQL's WAL writes off NFS is critical for
|
||
durability.
|
||
</p>
|
||
<p><img src="assets/diagrams/data-flow.svg" alt="Data flow — NFS for user files, named volumes for container state" class="diagram"></p>
|
||
|
||
<table>
|
||
<tr><th>Path</th><th>Filesystem</th><th>Why</th></tr>
|
||
<tr>
|
||
<td><code>/srv/nc-files/nextcloud/</code></td>
|
||
<td>NFSv4.1 from desslok</td>
|
||
<td>User-uploaded files. Snapshotted daily via desslok's existing ZFS path.</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>/srv/nc-files/backups/</code></td>
|
||
<td>NFSv4.1 from desslok</td>
|
||
<td>Daily pgdump + AIO config tar + user-files tar. 14-day retention.</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>/mnt/nc-data/nextcloud-data/</code></td>
|
||
<td>ext4 (local)</td>
|
||
<td>Bind mount, mounted INTO the nextcloud container as <code>/nextcloud-aio</code>. Holds app config, theme, install state.</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>/usr/local/containers/nextcloudaio/nextcloud-aio-{mastercontainer,database,redis,apache,...}/</code></td>
|
||
<td>ext4 (local)</td>
|
||
<td>Named-volume bind targets per container. Each holds the writable state for that one container.</td>
|
||
</tr>
|
||
</table>
|
||
|
||
<div class="callout warn">
|
||
<p>
|
||
<strong>Why isn't the postgres data on NFS?</strong>
|
||
PostgreSQL's WAL writes are sensitive to NFS close-to-open
|
||
consistency. The official AIO image puts the database on a local
|
||
named volume by default and we kept that. <code>pg_dumpall</code>
|
||
(which is what the backup pipeline runs) produces a
|
||
crash-consistent snapshot at dump time, so the daily backup is
|
||
good — but live writes from postgres go to local ext4 only.
|
||
</p>
|
||
</div>
|
||
|
||
<h2>Request flow — file upload via WebDAV</h2>
|
||
<p>
|
||
Swimlane sequence diagram of a single file upload:
|
||
<code>curl -T smoke-test.md https://office.rmf44.xyz/remote.php/dav/files/admin/smoke-test.md</code>
|
||
as run during the smoke test on 2026-08-10:
|
||
</p>
|
||
<p><img src="assets/diagrams/request-flow.svg" alt="Request flow — curl PUT through Caddy, NetBird, AIO Apache, PHP-FPM, NFS" class="diagram"></p>
|
||
|
||
<ol>
|
||
<li><strong>DNS</strong> — <code>office.rmf44.xyz</code> resolves to <code>192.255.159.202</code> (hawker).</li>
|
||
<li><strong>TLS handshake</strong> — Caddy presents the Let's Encrypt cert for <code>office.rmf44.xyz</code>.</li>
|
||
<li><strong>HTTPS PUT</strong> arrives at hawker on :443 with path <code>/remote.php/dav/files/admin/smoke-test.md</code>.</li>
|
||
<li><strong>Caddy route</strong> matches the <code>Host: office.rmf44.xyz</code> block and forwards to <code>100.79.142.164:11000</code>.</li>
|
||
<li><strong>NetBird tunnel</strong> encapsulates the request in WireGuard (UDP 51820), P2P from hawker to homework03.</li>
|
||
<li><strong>AIO Apache</strong> (nextcloud-aio-apache container) terminates the TLS-stripped HTTP and forwards to <code>127.0.0.1:9000</code> (PHP-FPM in nextcloud-aio-nextcloud).</li>
|
||
<li><strong>PHP-FPM (Nextcloud)</strong> authenticates the user via the session cookie, authorizes the path under <code>/admin/files/</code>, and writes the file via WebDAV.</li>
|
||
<li><strong>NFS write</strong> of the file to <code>/srv/nc-files/nextcloud/admin/files/smoke-test.md</code> on homework03 → <code>/slab/container_storage/office/nextcloud/admin/files/smoke-test.md</code> on desslok.</li>
|
||
<li><strong>Response</strong>: <code>HTTP/2 201 Created</code> with empty body (WebDAV semantics).</li>
|
||
</ol>
|
||
|
||
<h2>Collabora editing flow</h2>
|
||
<p>
|
||
When a user opens a .docx in the web UI, Nextcloud embeds
|
||
Collabora in an iframe via WOPI. The full chain:
|
||
</p>
|
||
<ol>
|
||
<li>User clicks "Open in Collabora" in the Nextcloud file UI.</li>
|
||
<li>Nextcloud generates a one-time WOPI token for the file.</li>
|
||
<li>Iframe loads <code>https://office.rmf44.xyz/apps/richdocuments/index?fileId=123&requesttoken=...</code>.</li>
|
||
<li>Browser fetches the iframe content from Caddy → Apache → PHP-FPM.</li>
|
||
<li>PHP-FPM serves the richdocuments app HTML.</li>
|
||
<li>Browser opens a second HTTPS connection to <code>https://office.rmf44.xyz:9980</code>... no, actually: AIO proxies Collabora internally at <code>http://nextcloud-aio-apache.nextcloud-aio:23973</code>. The browser never sees Collabora directly.</li>
|
||
<li>Collabora loads the file via WOPI (read from Nextcloud's WebDAV), serves the editor in the iframe, autosaves back through WOPI.</li>
|
||
</ol>
|
||
|
||
<div class="callout info">
|
||
<p>
|
||
<strong>Verified:</strong> <code>docker exec nextcloud-aio-nextcloud
|
||
sudo -u www-data php occ config:app:get richdocuments wopi_url</code>
|
||
returned <code>http://nextcloud-aio-apache.nextcloud-aio:23973</code>
|
||
— internal network address, not exposed publicly. The WOPI secret
|
||
never leaves the docker network.
|
||
</p>
|
||
</div>
|
||
|
||
</div>
|
||
</body>
|
||
</html> |