route_points (Pi) and get_trip_route (server) derived GPS speed from a single 5s segment, so GPS position jitter could read 150+ km/h when the car wasn't going that fast. Both now average over a trailing ~30s window (same as the max-speed calc), so spikes are damped.
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 and 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), oralready_uploaded.POST /api/v1/dashcam/upload/chunk— upload one chunk of a dashcam video (keyed byrelative_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 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
- Get the code onto the unraid box (same repo as the Pi, just the
server/sub-folder matters here):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 - Copy the env template and fill in a real Postgres password:
Recommended
cp .env.example .env nano .env # set POSTGRES_PASSWORD, and DATA_DIR if you want data on the arrayDATA_DIRfor unraid:/mnt/user/appdata/yaris-obd-server(keeps Postgres data + dashcam files on the array, backed up like your other appdata). - Build and start the stack:
This is also importable into unraid's Compose Manager plugin if you'd rather manage start/stop/updates from the Unraid UI.
docker compose up -d --build - Confirm it's healthy:
docker compose ps curl http://localhost:8098/health - Sign in to the remote portal and open
/settings/deviceto 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:
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:
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(orhttp://api:8098if you point the tunnel at theapicontainer 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>:8098and 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
cd server
cp .env.example .env # edit POSTGRES_PASSWORD
docker compose up --build
curl http://localhost:8098/health