# 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.
