---
title: adotom · Synology lift
kind: ops-walkthrough
captured: 2026-05-21
back_reference: ../-bedrock.md
---

# Adotom on the Synology, in one rsync + one vhost edit

The captain's portability test: *"if the Synology lift requires more than a
rsync and a Web Station vhost edit, local-first portability failed."* This
doc is the SOP that satisfies the test.

## Pre-flight (one-time, on the NAS)

1. **Package Center →** install **Python3** (DSM 7+ ships 3.9+; that's enough).
2. **Package Center →** install **Web Station**. Skip Apache/Nginx packages —
   the built-in Python http.server is the runtime; Web Station only provides
   the URL surface and reverse-proxy.
3. **Control Panel → Task Scheduler →** confirm you can create a user-defined
   script (used below for hourly backups).

## Step 1 · rsync the project up

From the Mac (replace `<nas>` with the Synology hostname):

```bash
rsync -avh --delete \
  --exclude='backups/' \
  --exclude='*.command' \
  ~/Desktop/root/adotom-automaton/ \
  shalaco@<nas>:/volume1/web/adotom/
```

Notes:
- `--exclude backups/` keeps the laptop's hourly snapshots local; the NAS
  grows its own snapshot trail under its own Task Scheduler.
- `--exclude *.command` skips the Mac-only double-click launcher.
- `/volume1/web/` is the default Web Station document root; adjust if your
  DSM uses a different volume.

## Step 2 · Web Station vhost

Web Station → **Web Service Portal → Create**:

- **Portal type:** Name-based
- **Hostname:** `adotom.<nas-domain>` (or `adotom.local` if you serve over LAN
  only — pair with a hosts-file entry on the Mac/iPad/phone)
- **Service:** **Reverse Proxy**
- **Source:** the hostname above, HTTP (or HTTPS if you have a cert)
- **Destination:** `http://localhost:8077` (the port `serve.py` listens on)
- **Document root:** (leave empty — reverse proxy handles it)

## Step 3 · Run serve.py as a service

DSM doesn't have native systemd. Two options, easiest first.

### Option A · Task Scheduler "Triggered Task" at boot

Control Panel → **Task Scheduler → Create → Triggered Task → User-defined script**:

- **Task name:** `adotom-server`
- **User:** the user that owns `/volume1/web/adotom/`
- **Event:** Boot-up
- **Run command:**
  ```bash
  cd /volume1/web/adotom && /usr/local/bin/python3 server/serve.py 8077 \
    >> backups/_server.out.log 2>> backups/_server.err.log
  ```

This survives reboots; logs go to the project itself.

### Option B · Container Manager (formerly Docker) if you prefer

If Container Manager is installed:

```bash
docker run -d --name adotom --restart unless-stopped \
  -p 8077:8077 \
  -v /volume1/web/adotom:/app \
  python:3.12-slim \
  python3 /app/server/serve.py 8077
```

## Step 4 · Hourly backup via Task Scheduler

Control Panel → **Task Scheduler → Create → Scheduled Task → User-defined script**:

- **Task name:** `adotom-backup`
- **User:** the user that owns `/volume1/web/adotom/`
- **Schedule:** Daily, frequency every 1 hour
- **Run command:**
  ```bash
  /bin/bash /volume1/web/adotom/server/cron-backup.sh
  ```

The script auto-prunes to the newest 720 snapshots (~30 days hourly). Bump
the `RETAIN=720` line in `cron-backup.sh` if you want more history.

## Step 5 · Verify

From a phone or iPad on the same network, hit `http://adotom.<nas-domain>/`.
Expected: the to-do view loads, the connection chip says **`live · <host>`**,
adding a task and reloading shows the task survived a round-trip through
`/api/nodes.json`.

## Held open / disclosed gaps

- **Cross-device write conflict.** Two devices editing the same nodes
  simultaneously will last-write-wins each other. If this starts mattering,
  add a `version` integer to the payload and let the server reject stale
  PUTs (412 Precondition Failed). Stub the field now, enforce later.
- **HTTPS.** The walkthrough assumes either LAN-only or you already have a
  Let's Encrypt cert via Synology DDNS. If exposing to the public internet,
  add a reverse-proxy authentication step (Web Station supports basic auth
  or you can front with a Synology VPN).
- **Secrets handling.** When the Clockify/Asana adapters get wired, set the
  keys via `Control Panel → Task Scheduler` task environment variables, not
  in the file tree. The adapters already read from env in preference to
  CONFIG.
