258 lines
7.2 KiB
Markdown
258 lines
7.2 KiB
Markdown
# Synology Snapshot Admin POC
|
|
|
|
This guide enables external access to the Python snapshot admin service on Synology without exposing the raw service port to the internet.
|
|
|
|
## Recommended topology
|
|
|
|
1. Keep the Python service bound to loopback only:
|
|
|
|
```bash
|
|
python tools/run_snapshot_admin_server_v1.py \
|
|
--host 127.0.0.1 \
|
|
--port 8787 \
|
|
--db outputs/snapshot_admin/snapshot_admin.db \
|
|
--seed GatherTradingData.json
|
|
```
|
|
|
|
2. Put Synology DSM reverse proxy in front of it:
|
|
- Source: `https://<public-host>:443`
|
|
- Destination: `http://127.0.0.1:8787`
|
|
- Keep the service port closed from direct WAN access.
|
|
|
|
3. Add browser authentication with the built-in Basic Auth gate:
|
|
- Set `SNAPSHOT_ADMIN_AUTH_USER`
|
|
- Set `SNAPSHOT_ADMIN_AUTH_PASSWORD`
|
|
- Or pass `--auth-user` and `--auth-password` on the wrapper command
|
|
|
|
4. Verify from the NAS:
|
|
|
|
```bash
|
|
curl -i http://127.0.0.1:8787/api/state
|
|
curl -u "$SNAPSHOT_ADMIN_AUTH_USER:$SNAPSHOT_ADMIN_AUTH_PASSWORD" http://127.0.0.1:8787/api/state
|
|
```
|
|
|
|
5. Verify from outside the NAS:
|
|
- Open `https://<public-host>/`
|
|
- The browser should prompt for Basic Auth
|
|
- `https://<public-host>/tables` should render after login
|
|
|
|
## DSM Checklist
|
|
|
|
Use these exact values for the first POC.
|
|
|
|
1. **DSM app path**
|
|
- `Control Panel`
|
|
- `Login Portal`
|
|
- `Advanced`
|
|
- `Reverse Proxy`
|
|
|
|
2. **Create reverse proxy rule**
|
|
- Description: `snapshot-admin`
|
|
- Source protocol: `HTTPS`
|
|
- Source hostname: your public DNS name, for example `admin.example.com`
|
|
- Source port: `443`
|
|
- Source path: `/`
|
|
- Destination protocol: `HTTP`
|
|
- Destination hostname: `127.0.0.1`
|
|
- Destination port: `8787`
|
|
|
|
3. **Certificate**
|
|
- Attach a valid TLS certificate for the public hostname
|
|
- Prefer a Synology-managed or imported certificate that matches `admin.example.com`
|
|
|
|
4. **Firewall**
|
|
- Allow inbound `443/TCP` only for the reverse proxy endpoint
|
|
- Do not expose `8787/TCP` on WAN
|
|
- If the NAS must be reachable only from a VPN or office IP range, allowlist those ranges and block the rest
|
|
|
|
5. **Service start policy**
|
|
- Start the Python service on boot or via DSM Task Scheduler
|
|
- Keep it bound to `127.0.0.1` unless you intentionally use direct bind mode
|
|
- If you use direct bind mode, keep `--allow-remote` and Basic Auth enabled together
|
|
- For Gitea Actions runner verification, register `act_runner` with a dedicated host label (`self-hosted:host,snapshot-admin-host:host`) if you want to avoid Docker job containers and the `Cleaning up container` log line
|
|
- Preferred launcher script: `tools/run_snapshot_admin_synology.sh`
|
|
- Gitea CI deploy path: trigger `.gitea/workflows/snapshot_admin_deploy.yml` `workflow_dispatch` and let the host runner call the launcher script
|
|
- Runner bootstrap: `tools/re_register_act_runner_synology.sh`
|
|
- Runner daemon start: `tools/start_act_runner_synology.sh`
|
|
|
|
6. **Runner re-registration**
|
|
- Use this when you want to switch an existing runner from Docker mode to host mode:
|
|
|
|
```bash
|
|
cd /volume1/projects/data_feed
|
|
REG_TOKEN="<runner-registration-token>" \
|
|
GITEA_URL="http://192.168.123.100:8418" \
|
|
bash tools/re_register_act_runner_synology.sh
|
|
```
|
|
|
|
- Expected effect:
|
|
- removes the existing `.runner` registration file
|
|
- registers `self-hosted:host,snapshot-admin-host:host`
|
|
- writes an updated `config.yaml`
|
|
- If the old runner remains listed in Gitea, remove it from the repository runner page and re-run the command above
|
|
|
|
7. **Runner start**
|
|
- After re-registration, start the daemon:
|
|
|
|
```bash
|
|
bash tools/start_act_runner_synology.sh
|
|
```
|
|
|
|
- Expected effect:
|
|
- launches `act_runner daemon` using the existing config
|
|
- records `runner.pid` and `runner.log` under the runner directory
|
|
|
|
## DSM Task Scheduler
|
|
|
|
Create two scheduled tasks in `Control Panel > Task Scheduler`.
|
|
|
|
1. **Boot task**
|
|
- Task name: `snapshot-admin-start`
|
|
- User: `root` or a dedicated service account with access to the project folder
|
|
- Event: `Boot-up`
|
|
- Command:
|
|
|
|
```bash
|
|
bash /volume1/projects/data_feed/tools/run_snapshot_admin_synology.sh start
|
|
```
|
|
|
|
2. **Healthcheck task**
|
|
- Task name: `snapshot-admin-healthcheck`
|
|
- User: same as boot task
|
|
- Event: `Scheduled Task`
|
|
- Repeat: every 5 minutes
|
|
- Command:
|
|
|
|
```bash
|
|
bash /volume1/projects/data_feed/tools/run_snapshot_admin_synology.sh healthcheck
|
|
```
|
|
|
|
3. **Manual restart task**
|
|
- Task name: `snapshot-admin-restart`
|
|
- User: same as boot task
|
|
- Event: `Scheduled Task`
|
|
- Repeat: manual only, or keep disabled until needed
|
|
- Command:
|
|
|
|
```bash
|
|
bash /volume1/projects/data_feed/tools/run_snapshot_admin_synology.sh restart
|
|
```
|
|
|
|
## Direct bind mode
|
|
|
|
Direct binding to `0.0.0.0` is allowed only when both auth values are configured:
|
|
|
|
```bash
|
|
python tools/run_snapshot_admin_server_v1.py \
|
|
--host 0.0.0.0 \
|
|
--port 8787 \
|
|
--allow-remote \
|
|
--auth-user "$SNAPSHOT_ADMIN_AUTH_USER" \
|
|
--auth-password "$SNAPSHOT_ADMIN_AUTH_PASSWORD"
|
|
```
|
|
|
|
Use this only if you have a separate firewall or VPN rule in place. The default POC path is still loopback + reverse proxy.
|
|
|
|
## Validation
|
|
|
|
Run the unit/web checks before and after deployment:
|
|
|
|
```bash
|
|
python -m pytest tests/unit/test_snapshot_admin_web_v1.py -q
|
|
python tools/validate_snapshot_admin_web_v1.py
|
|
```
|
|
|
|
The auth gate is part of the service now, so public exposure without credentials is rejected by the server itself.
|
|
|
|
## Curl checklist
|
|
|
|
Use this as the POC run sheet.
|
|
|
|
1. Local service check:
|
|
|
|
```bash
|
|
curl -i http://127.0.0.1:8787/api/state
|
|
```
|
|
|
|
Expected:
|
|
- `200 OK`
|
|
- JSON payload contains `version.app`
|
|
|
|
2. Reverse proxy auth challenge:
|
|
|
|
```bash
|
|
curl -i https://<public-host>/api/state
|
|
```
|
|
|
|
Expected:
|
|
- `401 Unauthorized`
|
|
- `WWW-Authenticate: Basic`
|
|
|
|
3. Reverse proxy authenticated access:
|
|
|
|
```bash
|
|
curl -u '<user>:<password>' https://<public-host>/api/state
|
|
```
|
|
|
|
Expected:
|
|
- `200 OK`
|
|
- JSON payload contains the same `version.app`
|
|
|
|
4. UI rendering:
|
|
|
|
```bash
|
|
curl -I https://<public-host>/
|
|
curl -I https://<public-host>/tables
|
|
```
|
|
|
|
Expected:
|
|
- `200 OK` after auth
|
|
- HTML response, not a redirect to the raw port
|
|
|
|
5. Restart persistence:
|
|
|
|
```bash
|
|
bash tools/run_snapshot_admin_synology.sh restart
|
|
bash tools/run_snapshot_admin_synology.sh healthcheck
|
|
```
|
|
|
|
Expected:
|
|
- `healthcheck ok`
|
|
- The proxy URL continues to answer after the service restarts
|
|
|
|
## Live verification
|
|
|
|
Use this sequence on the actual Synology box after the reverse proxy rule is in place:
|
|
|
|
1. Start the service and confirm the local health endpoint:
|
|
|
|
```bash
|
|
curl -i http://127.0.0.1:8787/api/state
|
|
```
|
|
|
|
2. Confirm the auth gate:
|
|
|
|
```bash
|
|
curl -i https://<public-host>/api/state
|
|
```
|
|
|
|
Expected result:
|
|
- `401 Unauthorized` when no credentials are provided
|
|
- `200 OK` when valid Basic Auth credentials are supplied
|
|
|
|
3. Confirm the browser surface:
|
|
- Open `https://<public-host>/`
|
|
- Sign in with the Basic Auth credentials
|
|
- Open `https://<public-host>/tables`
|
|
- Confirm rows render from the three SQLite sources
|
|
|
|
4. Confirm the deployment survives a process restart:
|
|
- Restart the Python service or the task that launches it
|
|
- Re-run `curl -i http://127.0.0.1:8787/api/state`
|
|
- Re-open the browser URL and confirm login still works
|
|
|
|
5. Archive evidence:
|
|
- Save the `curl` outputs
|
|
- Save a screenshot of `/` and `/tables`
|
|
- Record the DSM reverse proxy rule values and certificate name
|