doc: add server migration plan docs
This commit is contained in:
@@ -0,0 +1,436 @@
|
|||||||
|
# Synology NAS Deployment Guide
|
||||||
|
|
||||||
|
This guide covers migrating the terra-view stack from a generic Linux host
|
||||||
|
(currently the home server at `10.0.0.44`) to an always-on Synology NAS in
|
||||||
|
the office, including data migration and the minimal external-access
|
||||||
|
networking layer.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Architecture overview](#architecture-overview)
|
||||||
|
2. [Pre-requisites](#pre-requisites)
|
||||||
|
3. [Phase 1 — Pre-stage on the NAS (no downtime)](#phase-1--pre-stage-on-the-nas-no-downtime)
|
||||||
|
4. [Phase 2 — Data migration (~10 min window)](#phase-2--data-migration-10-min-window)
|
||||||
|
5. [Phase 3 — Repoint the watcher (download2-PC)](#phase-3--repoint-the-watcher-download2-pc)
|
||||||
|
6. [Phase 4 — External access for remote operators](#phase-4--external-access-for-remote-operators)
|
||||||
|
7. [Phase 5 — Decommission home server](#phase-5--decommission-home-server)
|
||||||
|
8. [Verification checklist](#verification-checklist)
|
||||||
|
9. [Rollback plan](#rollback-plan)
|
||||||
|
10. [Gotchas](#gotchas)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture overview
|
||||||
|
|
||||||
|
The terra-view stack is three containers:
|
||||||
|
|
||||||
|
| Service | Port | What writes to it | Where it lives |
|
||||||
|
|---------------|-------|-----------------------------|----------------|
|
||||||
|
| terra-view | 8001 | Operators (UI), watchers (heartbeat) | Synology NAS |
|
||||||
|
| SFM | 8200 | Watchers (Blastware ACH forwards) | Synology NAS |
|
||||||
|
| SLMM | 8100 | terra-view (proxied), SLMs on LAN | Synology NAS |
|
||||||
|
|
||||||
|
Everything that **writes** to the stack lives inside the office LAN:
|
||||||
|
|
||||||
|
- **download2-PC** is the series3-watcher host. It has a static office IP and
|
||||||
|
POSTs to terra-view's heartbeat endpoint plus SFM's Blastware import
|
||||||
|
endpoint. Both flows are LAN-internal.
|
||||||
|
- **Sound level meters (NL-43)** sit on the office LAN; SLMM reaches them
|
||||||
|
via `network_mode: host`.
|
||||||
|
|
||||||
|
The **only** thing that needs to cross the office firewall is operator UI
|
||||||
|
access from outside the office (laptops, phones, working from home). That
|
||||||
|
makes the external networking layer trivial — see Phase 4.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pre-requisites
|
||||||
|
|
||||||
|
On the Synology side:
|
||||||
|
|
||||||
|
- **DSM 7.2+** with **Container Manager** installed (Package Center).
|
||||||
|
Older "Docker" package works too — same engine, different menu names.
|
||||||
|
- **x86_64 model** (Plus / Value / XS series). ARM j-series will build but
|
||||||
|
expect a slower first build.
|
||||||
|
- **Static LAN IP** reserved for the NAS in the office router's DHCP table.
|
||||||
|
Devices on the LAN must have a stable target.
|
||||||
|
- **SSH enabled** — Control Panel → Terminal & SNMP → Enable SSH service.
|
||||||
|
- **Shared folder** for the stack — e.g. `/volume1/docker/`.
|
||||||
|
|
||||||
|
On the home server side:
|
||||||
|
|
||||||
|
- Working terra-view / SFM / SLMM stack you want to migrate.
|
||||||
|
- `rsync` available (it almost certainly is).
|
||||||
|
|
||||||
|
You will also need:
|
||||||
|
|
||||||
|
- An admin account on the Synology with sudo privileges.
|
||||||
|
- Network access between the home server and the NAS during the migration
|
||||||
|
window (or USB-drive shuttle if not).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1 — Pre-stage on the NAS (no downtime)
|
||||||
|
|
||||||
|
Goal: get the NAS booting an empty stack so you can validate the build and
|
||||||
|
networking *before* touching any production data.
|
||||||
|
|
||||||
|
### 1.1 Clone the repos
|
||||||
|
|
||||||
|
SSH to the NAS as admin:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo mkdir -p /volume1/docker
|
||||||
|
cd /volume1/docker
|
||||||
|
sudo git clone <your-terra-view-remote> terra-view
|
||||||
|
sudo git clone <your-slmm-remote> slmm
|
||||||
|
sudo git clone <your-seismo-relay-remote> seismo-relay
|
||||||
|
cd terra-view
|
||||||
|
sudo git checkout main # or whichever branch you ship from
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Build images
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /volume1/docker/terra-view
|
||||||
|
sudo docker compose build
|
||||||
|
```
|
||||||
|
|
||||||
|
First build takes 5–15 min depending on model.
|
||||||
|
|
||||||
|
### 1.3 Boot the empty stack
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Hit `http://<nas-lan-ip>:1001` (dev profile) or `:8001` (prod profile) from
|
||||||
|
another office machine. You should see an empty fleet roster. If that
|
||||||
|
works, the NAS can run the stack — proven before any production data is
|
||||||
|
at risk.
|
||||||
|
|
||||||
|
### 1.4 Stop the NAS stack again
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo docker compose stop
|
||||||
|
```
|
||||||
|
|
||||||
|
We're ready for the data migration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2 — Data migration (~10 min window)
|
||||||
|
|
||||||
|
The terra-view stack is stateful in three places. All three must be moved
|
||||||
|
together for consistency.
|
||||||
|
|
||||||
|
| Service | Data location (home server) |
|
||||||
|
|------------|----------------------------------------------|
|
||||||
|
| terra-view | `/home/serversdown/terra-view/data/` |
|
||||||
|
| SLMM | `/home/serversdown/slmm/data/` |
|
||||||
|
| SFM | `/home/serversdown/seismo-relay/data/` |
|
||||||
|
|
||||||
|
### 2.1 Stop writes on both sides
|
||||||
|
|
||||||
|
On the NAS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /volume1/docker/terra-view
|
||||||
|
sudo docker compose stop
|
||||||
|
```
|
||||||
|
|
||||||
|
On the home server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/serversdown/terra-view
|
||||||
|
docker compose stop terra-view slmm sfm
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 rsync the data dirs
|
||||||
|
|
||||||
|
From the home server (or anywhere with SSH access to both):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rsync -avh /home/serversdown/terra-view/data/ admin@<nas-lan-ip>:/volume1/docker/terra-view/data/
|
||||||
|
rsync -avh /home/serversdown/slmm/data/ admin@<nas-lan-ip>:/volume1/docker/slmm/data/
|
||||||
|
rsync -avh /home/serversdown/seismo-relay/data/ admin@<nas-lan-ip>:/volume1/docker/seismo-relay/data/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Fix ownership on the NAS
|
||||||
|
|
||||||
|
Synology admin is usually UID `1026`, GID `100`. Inside containers running
|
||||||
|
as root, this doesn't matter — but if you've configured `user:` in any
|
||||||
|
compose file it will. Safe default:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh admin@<nas-lan-ip> "sudo chown -R 1026:100 \
|
||||||
|
/volume1/docker/terra-view/data \
|
||||||
|
/volume1/docker/slmm/data \
|
||||||
|
/volume1/docker/seismo-relay/data"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 Run any pending migrations
|
||||||
|
|
||||||
|
Some earlier feature work added migration scripts that need to run once
|
||||||
|
per database. After the rsync, before starting the stack, check what's
|
||||||
|
pending:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh admin@<nas-lan-ip>
|
||||||
|
cd /volume1/docker/terra-view
|
||||||
|
ls backend/migrate_*.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Run each one inside the container (after starting it temporarily) or apply
|
||||||
|
them on the host with the same Python environment. Idempotent migrations
|
||||||
|
re-run safely.
|
||||||
|
|
||||||
|
### 2.5 Start the NAS stack
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh admin@<nas-lan-ip> \
|
||||||
|
"cd /volume1/docker/terra-view && sudo docker compose up -d"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.6 Spot-check
|
||||||
|
|
||||||
|
- Dashboard loads with real units
|
||||||
|
- `/sfm` page lists historical events
|
||||||
|
- A photo loads on a unit detail page
|
||||||
|
- SFM/HB badge mix on the active table matches what you saw on the home
|
||||||
|
server
|
||||||
|
|
||||||
|
If anything's off, see [Rollback plan](#rollback-plan).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3 — Repoint the watcher (download2-PC)
|
||||||
|
|
||||||
|
The download2-PC is the one client we have to reconfigure. It currently
|
||||||
|
POSTs to the home server. Two endpoints to change:
|
||||||
|
|
||||||
|
1. **terra-view heartbeat URL** —
|
||||||
|
`http://<old-home-ip>:8001/api/series3/heartbeat`
|
||||||
|
→ `http://<new-nas-lan-ip>:8001/api/series3/heartbeat`
|
||||||
|
|
||||||
|
2. **SFM Blastware import URL** —
|
||||||
|
`http://<old-home-ip>:8200/db/import/blastware_file`
|
||||||
|
→ `http://<new-nas-lan-ip>:8200/db/import/blastware_file`
|
||||||
|
|
||||||
|
Or, if you want to keep SFM container-internal and not publish 8200 on
|
||||||
|
the LAN at all, point it through terra-view's existing SFM proxy:
|
||||||
|
→ `http://<new-nas-lan-ip>:8001/api/sfm/db/import/blastware_file`
|
||||||
|
|
||||||
|
Update the config, restart the watcher service, and confirm the next
|
||||||
|
heartbeat lands in the NAS DB (check the Recent Call-Ins card on the
|
||||||
|
dashboard).
|
||||||
|
|
||||||
|
> **Tip:** keep the home server running in parallel for 1–2 days. If you
|
||||||
|
> forget to repoint something, it'll still flow into the old DB and you
|
||||||
|
> can resync.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4 — External access for remote operators
|
||||||
|
|
||||||
|
Only the terra-view UI needs to be reachable from outside the office. Two
|
||||||
|
clean options — pick one.
|
||||||
|
|
||||||
|
### Option A — Tailscale (recommended for small teams)
|
||||||
|
|
||||||
|
Zero port forwards, zero certs, zero public DNS, zero reverse proxy.
|
||||||
|
|
||||||
|
1. Install Tailscale from Synology Package Center, sign in.
|
||||||
|
2. Install Tailscale on each operator's laptop/phone, sign in to the same
|
||||||
|
tailnet.
|
||||||
|
3. Operators access `http://<nas-tailscale-ip>:8001` from anywhere.
|
||||||
|
|
||||||
|
That's the whole setup. The office network has no external exposure at
|
||||||
|
all.
|
||||||
|
|
||||||
|
### Option B — Reverse proxy with Let's Encrypt
|
||||||
|
|
||||||
|
If you want a `https://terraview.yourdomain.com` URL that any browser can
|
||||||
|
reach:
|
||||||
|
|
||||||
|
#### B.1 Port forward on the office router
|
||||||
|
|
||||||
|
```
|
||||||
|
WAN 443 → <nas-lan-ip>:443
|
||||||
|
WAN 80 → <nas-lan-ip>:80 (only needed for Let's Encrypt HTTP-01;
|
||||||
|
skip if you use DNS-01 challenge)
|
||||||
|
```
|
||||||
|
|
||||||
|
Do **not** forward 1001, 8001, 8100, or 8200.
|
||||||
|
|
||||||
|
#### B.2 Public DNS
|
||||||
|
|
||||||
|
- Free: Synology DDNS (Control Panel → External Access → DDNS) — gives
|
||||||
|
you `something.synology.me`.
|
||||||
|
- Better: your own domain with an A record → office WAN IP, or a CNAME →
|
||||||
|
Synology DDNS hostname (handles dynamic IPs automatically).
|
||||||
|
|
||||||
|
#### B.3 Let's Encrypt certificate
|
||||||
|
|
||||||
|
Control Panel → Security → Certificate → Add → "Get a certificate from
|
||||||
|
Let's Encrypt." DSM handles renewal.
|
||||||
|
|
||||||
|
#### B.4 Synology reverse proxy
|
||||||
|
|
||||||
|
Control Panel → Login Portal → Advanced → Reverse Proxy → Create:
|
||||||
|
|
||||||
|
```
|
||||||
|
Source: Hostname terraview.yourdomain.com
|
||||||
|
Protocol HTTPS
|
||||||
|
Port 443
|
||||||
|
Destination: Hostname localhost
|
||||||
|
Protocol HTTP
|
||||||
|
Port 8001
|
||||||
|
```
|
||||||
|
|
||||||
|
Under "Custom Header", add:
|
||||||
|
|
||||||
|
| Header | Value |
|
||||||
|
|---------------------|------------------------------------|
|
||||||
|
| `X-Forwarded-For` | `$proxy_add_x_forwarded_for` |
|
||||||
|
| `X-Forwarded-Proto` | `$scheme` |
|
||||||
|
| `Host` | `$host` |
|
||||||
|
|
||||||
|
Tick the WebSocket support checkbox.
|
||||||
|
|
||||||
|
#### B.5 DSM firewall
|
||||||
|
|
||||||
|
Control Panel → Security → Firewall → enable:
|
||||||
|
|
||||||
|
- 443/TCP from `Anywhere` — allow
|
||||||
|
- 80/TCP from `Anywhere` — allow (cert renewal only)
|
||||||
|
- Everything else from WAN — deny
|
||||||
|
- All from LAN — allow
|
||||||
|
|
||||||
|
Optional: geo-block to your country if your operators are domestic only.
|
||||||
|
Big reduction in scanning noise.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5 — Decommission home server
|
||||||
|
|
||||||
|
After 1–2 weeks of stable NAS operation:
|
||||||
|
|
||||||
|
1. Take a final `docker compose down` on the home server.
|
||||||
|
2. Archive `/home/serversdown/{terra-view,slmm,seismo-relay}/data/` to a
|
||||||
|
backup volume.
|
||||||
|
3. Free the home server hardware.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verification checklist
|
||||||
|
|
||||||
|
After Phase 2 (data migration):
|
||||||
|
|
||||||
|
- [ ] `http://<nas-lan-ip>:8001/` loads dashboard with real units
|
||||||
|
- [ ] Recent Alerts, Call-Ins (2 cols), Fleet Summary across the top
|
||||||
|
- [ ] SFM/HB badge mix on the active table looks sane
|
||||||
|
- [ ] `/sfm` page lists historical events (the same count as before)
|
||||||
|
- [ ] A unit detail page loads with photos rendering
|
||||||
|
- [ ] `/api/recent-event-callins` returns 200 with real data
|
||||||
|
- [ ] `/api/status-snapshot` returns 200, `sfm_reachable: true`
|
||||||
|
|
||||||
|
After Phase 3 (watcher cutover):
|
||||||
|
|
||||||
|
- [ ] Next heartbeat from download2-PC lands in NAS DB
|
||||||
|
- [ ] A new event arrives in `/sfm` page on the NAS within the next
|
||||||
|
Blastware ACH cycle
|
||||||
|
- [ ] No errors in `docker logs terra-view-terra-view-1`
|
||||||
|
|
||||||
|
After Phase 4 (external access):
|
||||||
|
|
||||||
|
- [ ] (Option A) Operator laptop on tailnet can reach
|
||||||
|
`http://<nas-tailscale-ip>:8001`
|
||||||
|
- [ ] (Option B) `https://terraview.yourdomain.com` resolves, cert is
|
||||||
|
valid, dashboard loads
|
||||||
|
- [ ] (Option B) Office DSM admin (5001) is **not** reachable from outside
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rollback plan
|
||||||
|
|
||||||
|
The home server stays alive in parallel through Phases 2–3 as a safety
|
||||||
|
net. If anything goes wrong on the NAS:
|
||||||
|
|
||||||
|
1. On the home server:
|
||||||
|
```bash
|
||||||
|
cd /home/serversdown/terra-view
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
2. Point download2-PC back at the home server IP.
|
||||||
|
3. NAS data isn't lost — it's just sitting idle. Investigate, fix, retry.
|
||||||
|
|
||||||
|
The "irreversible" point is when you decommission the home server in
|
||||||
|
Phase 5. Until then, you can always fall back.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
1. **Synology UID/GID quirks.** Synology admin is usually `1026:100`.
|
||||||
|
Containers running as root inside don't care, but if your compose
|
||||||
|
files set `user:`, mismatched UIDs cause SQLite "readonly database"
|
||||||
|
errors. Easiest fix: omit `user:` and let containers run as root.
|
||||||
|
|
||||||
|
2. **`network_mode: host` for SLMM.** Required for LAN-direct comms with
|
||||||
|
sound level meters. On Synology this binds to the NAS's interface —
|
||||||
|
confirm nothing else on the NAS uses ports 8100 or 21 (FTP).
|
||||||
|
|
||||||
|
3. **Auto-start on boot.** Container Manager → Project → Settings →
|
||||||
|
enable "Auto-restart". Otherwise a DSM update or NAS reboot drops the
|
||||||
|
stack.
|
||||||
|
|
||||||
|
4. **`restart: unless-stopped` in compose.** Verify every service has it.
|
||||||
|
DSM occasionally restarts Docker during DSM updates — this flag
|
||||||
|
ensures everything comes back.
|
||||||
|
|
||||||
|
5. **Hyper Backup.** Schedule a daily snapshot of
|
||||||
|
`/volume1/docker/terra-view/data/` to a USB drive or off-site. SQLite
|
||||||
|
+ small photo dir = trivially small backups. The DB-Management UI's
|
||||||
|
built-in snapshots are an additional layer but not a replacement.
|
||||||
|
|
||||||
|
6. **NAT loopback (Option B only).** If your office router doesn't
|
||||||
|
support hairpinning, machines INSIDE the office can't reach the NAS
|
||||||
|
by its public hostname — they have to use the LAN IP. Most modern
|
||||||
|
routers handle this; some ISP-provided ones don't. Test from a laptop
|
||||||
|
on the office Wi-Fi.
|
||||||
|
|
||||||
|
7. **Let's Encrypt rate limits (Option B only).** 5 issuances per domain
|
||||||
|
per week. Don't fat-finger DNS or you'll be locked out. Test with the
|
||||||
|
staging endpoint first if unsure.
|
||||||
|
|
||||||
|
8. **`host.docker.internal` resolution.** terra-view's
|
||||||
|
`SFM_BASE_URL=http://host.docker.internal:8200` relies on Docker's
|
||||||
|
internal DNS. Works on DSM 7.2+ in bridge mode. If you see "name not
|
||||||
|
resolved" errors, fall back to explicit container names with a custom
|
||||||
|
network in compose.
|
||||||
|
|
||||||
|
9. **SFM stale rows.** The SFM SQLite has a few rows in `monitor_log`
|
||||||
|
and `ach_sessions` from earlier Python-ACH experiments. Harmless
|
||||||
|
to bring over — invisible to terra-view's UI under the
|
||||||
|
watcher-forward pipeline.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Suggested timeline
|
||||||
|
|
||||||
|
For a low-risk migration:
|
||||||
|
|
||||||
|
- **Week 1**: Phase 1. Get the NAS booting an empty stack. No production
|
||||||
|
touch.
|
||||||
|
- **Week 2, day 1**: Phase 2. Migrate data. 10-min window. Keep home
|
||||||
|
server alive in parallel.
|
||||||
|
- **Week 2, day 1**: Phase 3. Repoint download2-PC. Watch heartbeats
|
||||||
|
land on the NAS for the rest of the day.
|
||||||
|
- **Week 3**: Phase 4. Add Tailscale or reverse-proxy access for remote
|
||||||
|
operators.
|
||||||
|
- **Week 4–5**: Monitor. Confirm everything's stable. Then Phase 5
|
||||||
|
(decommission home server).
|
||||||
|
|
||||||
|
Splitting "make it work on LAN" from "expose it remotely" means you
|
||||||
|
debug one thing at a time.
|
||||||
Reference in New Issue
Block a user