Files
yaris-obd/server/README.md
T
2026-08-10 18:48:59 -04:00

177 lines
8.1 KiB
Markdown

# yaris-obd remote server
A small Flask + Postgres API that receives trip/GPS log batches and dashcam
video files uploaded by the Pi (see the repo root [readme.md](../readme.md)
and [PLAN.md](../PLAN.md) for the overall project). Designed to run as a
docker-compose stack on an unraid server (or any Docker host).
## What it does
- `GET /health` — unauthenticated liveness check.
- `POST /api/v1/tracking/batch` — bulk-insert a batch of GPS/trip points.
- `POST /api/v1/dashcam/upload` — upload one dashcam video file in a single
request (multipart, content-addressed by sha256). Kept for reference/
compatibility, but the Pi no longer calls this directly for real dashcam
footage — see chunked upload below.
- `GET /api/v1/dashcam/upload/status` — check upload progress for a
`(relative_path, sha256)` pair: `not_started`, `in_progress` (with the list
of already-received chunk indices, for resuming), or `already_uploaded`.
- `POST /api/v1/dashcam/upload/chunk` — upload one chunk of a dashcam video
(keyed by `relative_path`/`sha256`/`chunk_index`/`total_chunks`). Chunks are
staged on disk and reassembled + sha256-verified once all have arrived, then
recorded the same way as the single-shot endpoint. This is what the Pi
actually uses for real dashcam files — see "Why chunked uploads" below.
- `GET /api/device-config` — device-authenticated config fetch used by the Pi's
identity-sync thread and local **Sync now** action.
- `POST /api/v1/device-config/ack` — device-authenticated config-sync
acknowledgement so the server can record the last applied version/time.
- `/settings/device` + `/api/v1/portal/devices*` — login-protected multi-device
management UI/APIs for create, delete, rotate key, edit config, and request
sync.
Both `/api/v1/tracking/batch` and the dashcam upload endpoints require two
headers: `X-Device-Name` and `X-Api-Key` (see
[Registering a device](#registering-a-device-and-getting-an-api-key) below).
Postgres is **not** published to the LAN — only the `api` container talks to
it, over the internal compose network.
**CORS is enabled on every response** (`Access-Control-Allow-Origin: *`) for the
device-authenticated API surface. This keeps header-authenticated device calls
usable from browser-based tooling and same-user cross-origin utilities without
introducing cookie/session theft risk, because device auth is explicit
(`X-Device-Name`/`X-Api-Key`) rather than ambient session auth.
## Deploying on unraid
1. Get the code onto the unraid box (same repo as the Pi, just the
`server/` sub-folder matters here):
```bash
ssh root@<unraid-ip>
mkdir -p /mnt/user/app/git && cd /mnt/user/app/git
git clone [email protected]:quoije/yaris-obd.git
cd yaris-obd/server
```
2. Copy the env template and fill in a real Postgres password:
```bash
cp .env.example .env
nano .env # set POSTGRES_PASSWORD, and DATA_DIR if you want data on the array
```
Recommended `DATA_DIR` for unraid: `/mnt/user/appdata/yaris-obd-server`
(keeps Postgres data + dashcam files on the array, backed up like your
other appdata).
3. Build and start the stack:
```bash
docker compose up -d --build
```
This is also importable into unraid's **Compose Manager** plugin if you'd
rather manage start/stop/updates from the Unraid UI.
4. Confirm it's healthy:
```bash
docker compose ps
curl http://localhost:8098/health
```
5. Sign in to the remote portal and open `/settings/device` to manage devices:
create/delete devices, reveal a one-time API key, rotate keys, edit synced
owner/car/device/avatar values, and request a sync. If the Pi has local
**Sync with server** enabled, those values hot-reload into the running Pi UI
after the next successful sync.
### Upgrading an existing unraid deployment
If the Postgres volume already exists, mounted migration files do **not** run
again automatically. After pulling new code, apply the new migrations manually:
```bash
cd /mnt/user/app/git/yaris-obd/server
docker compose exec db psql -U yarisobd -d yarisobd -f /docker-entrypoint-initdb.d/migrate_phase24.sql
docker compose exec db psql -U yarisobd -d yarisobd -f /docker-entrypoint-initdb.d/migrate_phase25_5a.sql
docker compose exec db psql -U yarisobd -d yarisobd -f /docker-entrypoint-initdb.d/migrate_phase25_5c.sql
docker compose up -d --build
```
Phase 25.5a adds the device identity sync columns (`owner_name`, `car_model`,
`device_name`, `avatar_path`, `config_version`, `config_updated_at`) used by the
Pi/server config sync flow. Phase 25.5c adds multi-device provisioning/status
columns (`last_config_sync_at`, `last_config_sync_version`,
`force_sync_requested_at`, `api_key_rotated_at`). The browser portal page at
`/settings/device` edits those values; the Pi applies them on its next
successful sync when local **Sync with server** is enabled, and Phase 25.5b
hot-reloads the in-process UI fields instead of requiring an app restart.
## Registering a device and getting an API key
Run this once per device (e.g. once for the Pi) after the stack is up:
```bash
docker compose exec api python3 scripts/create_device.py yaris-2013
```
This prints a one-time API key — copy the `YARIS_DEVICE_NAME` /
`YARIS_API_KEY` values into the Pi's upload-client config. Only the
key's sha256 hash is stored server-side; the raw key is never shown again.
On the Pi, those credentials can now be entered through the local Settings page
instead of editing `db/upload_creds.env` over SSH. The same page also exposes
**Test connection** and **Sync now** actions.
The browser portal at `/settings/device` can now do the same workflow without
shell access:
- create a new device
- delete a non-default device
- reveal a one-time API key
- rotate the API key later
- request a sync for the selected device
Current limitation: device management is multi-device, but the main remote
overview/history/dashcam browsing pages still scope themselves to one
configured `DEVICE_NAME` until a portal-wide device selector/filter is added.
## Exposing it outside the LAN
Two options, either works with this API unchanged:
- **Cloudflare Tunnel** (already running on this unraid box): add a public
hostname rule in the Cloudflare dashboard pointing at
`http://<unraid-lan-ip>:8098` (or `http://api:8098` if you point the
tunnel at the `api` container directly on the compose network). Good for
small tracking-data batches; large dashcam uploads may hit the tunnel's
per-request size limit.
- **Router port-forward** (fallback if needed): forward an external port to
`<unraid-lan-ip>:8098` and let your router/firewall (e.g. Unifi) handle
the security side. No changes needed here either way.
For large dashcam uploads from home, consider adding a local DNS
override/rewrite (e.g. in AdGuard Home, same trick used for `yaris.local` on
the Pi) so the hostname resolves directly to the unraid LAN IP while on the
home network, bypassing the tunnel entirely for big transfers.
## Why chunked uploads
Cloudflare Tunnel's free tier enforces a hard ~100MB request-body cap,
returning a bare `413 Payload Too Large` before the request even reaches this
API — confirmed against real dashcam footage (avg ~320MB/file), which the
single-shot `/api/v1/dashcam/upload` endpoint could never pass through the
tunnel. The Pi's upload client instead splits each file into 40MB chunks and
POSTs them one at a time to `/api/v1/dashcam/upload/chunk`, checking
`/api/v1/dashcam/upload/status` first so an interrupted upload (Pi
reboot/crash, network drop) resumes from the last received chunk instead of
restarting the whole file. Verified end-to-end on real hardware: a full
28GB/87-file dashcam backlog uploaded successfully this way, each file
byte-for-byte sha256-verified against the source on the Pi.
## Toggling dashcam uploads off
The Pi's web UI has a setting to pause dashcam file uploads while keeping
GPS/trip log uploads running — no server-side change needed, the Pi simply
stops calling the dashcam upload status/chunk endpoints while the toggle is off.
## Local development / testing without unraid
```bash
cd server
cp .env.example .env # edit POSTGRES_PASSWORD
docker compose up --build
curl http://localhost:8098/health
```