Files
office/procedure.html
T
race d172771aa1 Initial docs: Nextcloud AIO office suite (Collabora + Whiteboard)
- 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
2026-08-10 15:43:46 -05:00

414 lines
15 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Procedure — 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> &nbsp;›&nbsp; Procedure</div>
<ul class="nav">
<li><a href="index.html">Overview</a></li>
<li><a href="architecture.html">Architecture</a></li>
<li><a href="procedure.html" class="active">Procedure</a></li>
<li><a href="troubleshooting.html">Troubleshooting</a></li>
<li><a href="operations.html">Operations</a></li>
</ul>
<h1>Procedure</h1>
<p>
The deployment ran in four phases. Each phase was operator-gated
before destructive steps. Commands shown are the ones actually
executed during the 2026-08-10 deployment, cleaned up.
</p>
<div class="toc">
<h2>Phases</h2>
<ul>
<li><a href="#phase-1">Phase 1 — Discovery</a> (read-only)</li>
<li><a href="#phase-2">Phase 2 — Storage + NFS</a></li>
<li><a href="#phase-3">Phase 3 — AIO mastercontainer + setup wizard</a></li>
<li><a href="#phase-4">Phase 4 — Public ingress + cutover</a></li>
<li><a href="#phase-5">Phase 5 — Backup pipeline</a></li>
</ul>
</div>
<h2 id="phase-1">Phase 1 — Discovery</h2>
<p>
All read-only. Goal: confirm AIO is supported on the target host,
find the existing OnlyOffice config (to know what we're tearing
down), check NetBird is up.
</p>
<h3>1.1 Confirm homework03 hardware + Docker</h3>
<pre><code>ssh homework03
# Memory + CPU
free -h | head -3
# 15 GB total, expect ~5-9 GB free after system
nproc
# 4 cores
# Docker
docker --version
# Docker version 29.6.2, build ...
docker compose version
# Docker Compose version v2.40.0
# Existing containers (the old OnlyOffice might have siblings)
docker ps --format 'table {{.Names}}\t{{.Status}}'</code></pre>
<h3>1.2 Confirm NetBird IP on homework03</h3>
<pre><code>ip a show wt0 2>&1 | grep inet
# Expect: inet 100.79.142.164/16
# Verify the NetBird connection is up
netbird status
# Expect: connected peers including hawker (100.79.4.103)
# Verify reachability from hawker
ssh tigo@hawker
ip a show wt0 | grep inet
# Expect: inet 100.79.4.103/16
ping -c 3 100.79.142.164
# Expect: 0% loss</code></pre>
<h3>1.3 Find the existing OnlyOffice backup pipeline (to replace it)</h3>
<pre><code>ssh tigo@hector
cat /etc/systemd/system/office-backup.service
cat /etc/systemd/system/office-backup.timer
systemctl list-timers office-backup*
# Read the existing office-backup.sh on hector
less /usr/local/bin/office-backup.sh
# Expect: 3-step pipeline (hawker dump → scp → rename)
# This is what we're going to replace with the AIO pipeline</code></pre>
<h3>1.4 Pick the storage layout</h3>
<pre><code>ssh desslok
# Confirm the slab path exists and is exported via NFS
ls -la /slab/container_storage/ | grep office
# Expect: drwxr-xr-x tigo tigo office
# Confirm NFS export
showmount -e 10.0.0.105 | grep office
# Expect: /slab/container_storage/office 10.0.0.0/24
# If not yet exported, add it (FreeBSD exports):
sudo -e /etc/exports
# Append:
# /slab/container_storage/office -mapall=root -network 10.0.0.0/24
sudo /etc/rc.d/mountd restart</code></pre>
<h2 id="phase-2">Phase 2 — Storage + NFS</h2>
<p>
Create the live data dir on desslok, mount via NFS on homework03,
add bind targets for AIO's named volumes.
</p>
<h3>2.1 Create the live data dir on desslok</h3>
<pre><code>ssh desslok
sudo -n mkdir -p /slab/container_storage/office/nextcloud
sudo -n mkdir -p /slab/container_storage/office/backups
sudo -n chown -R tigo:tigo /slab/container_storage/office
sudo -n chmod 755 /slab/container_storage/office</code></pre>
<h3>2.2 Mount via NFS on homework03</h3>
<pre><code>ssh homework03
sudo -n mkdir -p /srv/nc-files
sudo -n mount -t nfs -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600 \
desslok:/slab/container_storage/office /srv/nc-files
df -h /srv/nc-files
ls -la /srv/nc-files
# Expect: nextcloud/ backups/</code></pre>
<p>
Add to <code>/etc/fstab</code> for boot persistence:
</p>
<pre><code>ssh homework03
sudo -n bash -c 'cat >> /etc/fstab <<EOF
# Nextcloud Office NFS share (2026-08-10)
desslok:/slab/container_storage/office /srv/nc-files nfs nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,_netdev 0 0
EOF'</code></pre>
<h3>2.3 Create local bind targets</h3>
<pre><code>ssh homework03
# AIO install root
sudo -n mkdir -p /usr/local/containers/nextcloudaio
# Container bind targets (named volumes)
for sub in mastercontainer database database-dump redis apache nextcloud \
collabora whiteboard notify-push imaginary talk fulltextsearch clamav; do
sudo -n mkdir -p "/usr/local/containers/nextcloudaio/nextcloud-aio-$sub"
done
# /mnt/nc-data for the Nextcloud data dir (lives on local ext4)
sudo -n mkdir -p /mnt/nc-data/nextcloud-data
# Local backup stash (so the script can write the pgdump into NFS without recursion)
ls -la /usr/local/containers/nextcloudaio/</code></pre>
<h3>2.4 Pre-chown the bind targets</h3>
<p>
AIO's entrypoint scripts chown their bind target to the runtime
UID. Doing it once explicitly avoids a startup warning:
</p>
<pre><code>ssh homework03
sudo -n chown -R 33:33 /usr/local/containers/nextcloudaio/nextcloud-aio-mastercontainer
sudo -n chown -R 33:33 /usr/local/containers/nextcloudaio/nextcloud-aio-apache
sudo -n chown -R 999:999 /usr/local/containers/nextcloudaio/nextcloud-aio-database
sudo -n chown -R 999:999 /usr/local/containers/nextcloudaio/nextcloud-aio-database-dump
sudo -n chown -R 999:999 /usr/local/containers/nextcloudaio/nextcloud-aio-redis
sudo -n chown -R root:root /usr/local/containers/nextcloudaio/nextcloud-aio-nextcloud
sudo -n chown -R 100:101 /usr/local/containers/nextcloudaio/nextcloud-aio-collabora</code></pre>
<h2 id="phase-3">Phase 3 — AIO mastercontainer + setup wizard</h2>
<p>
Write the compose file, start the mastercontainer, and walk the
setup wizard via the admin UI.
</p>
<h3>3.1 docker-compose.yaml</h3>
<pre><code>ssh homework03
sudo -n tee /usr/local/containers/nextcloudaio/docker-compose.yaml > /dev/null &lt;&lt;'EOF'
services:
nextcloud-aio-mastercontainer:
image: nextcloud/all-in-one:latest
restart: always
container_name: nextcloud-aio-mastercontainer
network_mode: host
environment:
APACHE_PORT: "11000"
APACHE_DISABLE_REWRITE_IP: "1"
NEXTCLOUD_DATADIR: "/mnt/nc-data/nextcloud-data"
NEXTCLOUD_UPLOAD_LIMIT: "10G"
NEXTCLOUD_MAX_TIME: "3600"
AIO_DISABLE_BACKUP: "true"
SKIP_DOMAIN_VALIDATION: "true"
COLLABORA_ENABLED: "yes"
ONLYOFFICE_ENABLED: "no"
IMAGINARY_ENABLED: "no"
TALK_ENABLED: "no"
WHITEBOARD_ENABLED: "yes"
FULLTEXTSEARCH_ENABLED: "no"
CLAMAV_ENABLED: "no"
NEXTCLOUD_DOMAIN: "office.rmf44.xyz"
NEXTCLOUD_TRUSTED_CACERTS_DIR: "/usr/local/share/ca-certificates"
volumes:
- ./nextcloud-aio-mastercontainer:/container-volume
- /var/run/docker.sock:/var/run/docker.sock:ro
- /srv/nc-files:/srv/nc-files
- /mnt/nc-data/nextcloud-data:/mnt/nc-data/nextcloud-data
EOF</code></pre>
<h3>3.2 Start mastercontainer + pull the AIO passphrase</h3>
<pre><code>ssh homework03
cd /usr/local/containers/nextcloudaio
sudo -n docker compose up -d
sleep 10
sudo -n docker logs nextcloud-aio-mastercontainer 2>&1 | grep -E 'passphrase|AIO'
# Get the 12-word passphrase from the logs
sudo -n docker logs nextcloud-aio-mastercontainer 2>&1 | grep -oE '[a-z]+(?: [a-z]+){11}' | head -1</code></pre>
<h3>3.3 Walk the setup wizard</h3>
<p>
Open the admin UI on homework03 LAN IP :8443 (self-signed cert is
fine — accept the warning):
</p>
<pre><code>ssh homework03
hostname -I | awk '{print $1}'
# 10.0.0.73
# Open https://10.0.0.73:8443 in browser</code></pre>
<ol>
<li>Paste the 12-word passphrase.</li>
<li>Enter <code>office.rmf44.xyz</code> as the desired Nextcloud domain.</li>
<li>Click "Start AIO setup" — this triggers mastercontainer to pull the other 7 containers and run the installation.</li>
<li>Wait ~10 minutes. The container list grows one by one. Status column cycles through "starting" → "running" → "healthy".</li>
<li>When all 8 are healthy, the admin UI shows "Open Nextcloud" — click it. Nextcloud loads at <code>https://office.rmf44.xyz:11000</code> (LAN-side, before DNS cutover).</li>
<li>Log in as <code>admin</code> with the auto-generated password printed in the admin UI's "Nextcloud admin user" panel — save this. The user must change it on first login.</li>
<li>Verify Collabora: Files → + → New Document → Word Document. Document opens in the richdocuments iframe (no separate login prompt = working WOPI).</li>
<li>Verify Whiteboard: + → New Whiteboard. Whiteboard canvas loads.</li>
</ol>
<h3>3.4 Verify the configuration persisted</h3>
<pre><code>ssh homework03
sudo -n cat /usr/local/containers/nextcloudaio/nextcloud-aio-mastercontainer/configuration.json
# Expect: "officeSuite": "collabora", "isWhiteboardEnabled": true,
# "domain": "office.rmf44.xyz", "nextcloud_datadir": "/mnt/nc-data/nextcloud-data"</code></pre>
<h2 id="phase-4">Phase 4 — Public ingress + cutover</h2>
<p>
Make <code>office.rmf44.xyz</code> reachable via the public Caddy
on hawker.
</p>
<h3>4.1 Confirm Cloudflare DNS</h3>
<pre><code># Confirm office.rmf44.xyz A record points at hawker
dig office.rmf44.xyz +short
# 192.255.159.202
# If missing, add it via Cloudflare dashboard or:
curl -X POST https://api.cloudflare.com/.../zones/$ZONE/dns_records \
-H "Authorization: Bearer $CF_API_TOKEN" \
-d '{"type":"A","name":"office","content":"192.255.159.202","proxied":false}'</code></pre>
<h3>4.2 Add Caddy block on hawker</h3>
<p>
The first attempt used <code>:80</code> as the upstream — that was
the bug. Apache listens on <strong>:11000</strong>:
</p>
<pre><code>ssh tigo@hawker
# Edit /etc/caddy/Caddyfile
sudo -n sed -i '/^office.rmf44.xyz {/{
N
s|office.rmf44.xyz {\n\treverse_proxy 100.79.142.164:80|office.rmf44.xyz {\n\treverse_proxy 100.79.142.164:11000|
}' /etc/caddy/Caddyfile
# Validate + reload
sudo -n docker exec caddy-caddy-1 caddy validate \
--config /etc/caddy/Caddyfile --adapter caddyfile
sudo -n docker exec caddy-caddy-1 caddy reload \
--config /etc/caddy/Caddyfile --adapter caddyfile</code></pre>
<h3>4.3 Verify the cutover</h3>
<pre><code>curl -skI https://office.rmf44.xyz/
# HTTP/2 200
# content-type: text/html; charset=UTF-8
# ...
curl -s https://office.rmf44.xyz/ | grep -oE '<title>[^<]+</title>'
# &lt;title&gt;Login – Nextcloud&lt;/title&gt;
# Test login
# 1. GET /login → grab requesttoken + cookies
# 2. POST /login with user=admin + password + requesttoken
# 3. Expect HTTP 303 → /apps/dashboard/</code></pre>
<h3>4.4 Tear down the old OnlyOffice</h3>
<pre><code>ssh tigo@hawker
cd /home/tigo/onlyoffice-stack 2>/dev/null || cd /opt/onlyoffice-stack
docker compose down -v
# Removes containers and anonymous volumes
# Remove the Caddy vhost block (if it's separate)
sudo -n python3 -c "
p = '/etc/caddy/Caddyfile'
with open(p) as f: s = f.read()
s = s.replace('\n\n# onlyoffice\nonlyoffice.rmf44.xyz {\n\treverse_proxy 127.0.0.1:9980\n}\n', '')
with open(p, 'w') as f: f.write(s)
"
sudo -n docker exec caddy-caddy-1 caddy validate \
--config /etc/caddy/Caddyfile --adapter caddyfile
sudo -n docker exec caddy-caddy-1 caddy reload \
--config /etc/caddy/Caddyfile --adapter caddyfile</code></pre>
<h3>4.5 Disable the old hector backup pipeline</h3>
<pre><code>ssh tigo@hector
sudo -n systemctl disable --now office-backup.timer
sudo -n rm /etc/systemd/system/office-backup.{service,timer}
sudo -n rm /usr/local/bin/office-backup.sh
sudo -n systemctl daemon-reload</code></pre>
<h2 id="phase-5">Phase 5 — Backup pipeline</h2>
<p>
A new daily backup runs on homework03, writing back to NFS. The
hector timer triggers it over SSH.
</p>
<h3>5.1 office-backup.sh on homework03</h3>
<pre><code>ssh homework03
sudo -n tee /usr/local/bin/office-backup.sh > /dev/null &lt;&lt;'EOF'
#!/usr/bin/env bash
# office-backup.sh — daily backup of Nextcloud AIO
# runs on homework03, writes to /srv/nc-files/backups (NFS → desslok)
set -euo pipefail
BACKUP_DIR="/srv/nc-files/backups"
STAMP="$(date -u +%Y%m%d)"
NAME="office-${STAMP}"
mkdir -p "${BACKUP_DIR}"
# 1. Postgres dump from the database container
docker exec nextcloud-aio-database \
pg_dumpall -U nextcloud --no-owner --clean --if-exists \
| gzip > "${BACKUP_DIR}/${NAME}-pgdump.sql.gz"
# 2. Tar the AIO container state (mastercontainer config + database-dump)
tar -C /usr/local/containers/nextcloudaio \
-czf "${BACKUP_DIR}/${NAME}-aio-config.tar.gz" \
nextcloud-aio-mastercontainer nextcloud-aio-database-dump
# 3. Tar user files (excluding the backups/ subdir to avoid recursion)
tar -C /srv/nc-files \
--exclude='backups' \
-czf "${BACKUP_DIR}/${NAME}-ncdata.tar.gz" \
nextcloud
# 4. Prune anything older than 14 days
find "${BACKUP_DIR}" -maxdepth 1 -type f -name 'office-*' -mtime +14 -delete
echo "OK: wrote ${NAME}-{{pgdump.sql.gz,aio-config.tar.gz,ncdata.tar.gz}} to ${BACKUP_DIR}"
EOF
sudo -n chmod 755 /usr/local/bin/office-backup.sh
sudo -n chown root:root /usr/local/bin/office-backup.sh</code></pre>
<h3>5.2 hector systemd unit (SSHes to homework03)</h3>
<pre><code>ssh tigo@hector
sudo -n tee /etc/systemd/system/office-backup.service > /dev/null &lt;&lt;'EOF'
[Unit]
Description=Nextcloud AIO backup (homework03 -> desslok via NFS)
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
User=root
ExecStart=/usr/bin/ssh -o BatchMode=yes -o ConnectTimeout=30 \
homework03 sudo /usr/local/bin/office-backup.sh
EOF
sudo -n tee /etc/systemd/system/office-backup.timer > /dev/null &lt;&lt;'EOF'
[Unit]
Description=Daily Nextcloud AIO backup timer
[Timer]
OnCalendar=*-*-* 03:30:00
RandomizedDelaySec=900
Persistent=true
[Install]
WantedBy=timers.target
EOF
sudo -n systemctl daemon-reload
sudo -n systemctl enable --now office-backup.timer
systemctl list-timers office-backup*
# Expect: NEXT 8h14min ... office-backup.timer office-backup.service</code></pre>
<h3>5.3 Test run + verify</h3>
<pre><code>ssh tigo@hector
sudo -n systemctl start office-backup.service
# Wait 10s, then check status
systemctl status office-backup.service | head -5
# Expect: Active: inactive (dead), Result: success
# Verify the files made it to desslok
ssh tigo@desslok ls -la /slab/container_storage/office/backups/
# Expect: office-20260810-pgdump.sql.gz (a few hundred KB)
# office-20260810-aio-config.tar.gz (a few KB)
# office-20260810-ncdata.tar.gz (a few hundred B, empty until you upload files)
# Spot-check the pgdump
zcat /slab/container_storage/office/backups/office-20260810-pgdump.sql.gz | \
grep -cE '^CREATE TABLE'
# Expect: 155 (Nextcloud has ~155 oc_* tables)</code></pre>
</div>
</body>
</html>