App 2.41.0, server 2.9.0: recordings reach the server

A finished signal recording goes to an outbox and is sent gzipped with
the car's token (PUT /v1/logs/{name}); the service retries every 2
minutes while the car is offline. The server keeps the newest 200 on a
new volume (LOGS_DIR); reading takes a token of its own (LOGS_TOKEN).
fetch-logs in the dev shell fetches new ones into logs/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
2026-10-27 10:22:40 +01:00
co-authored by Claude Opus 5.5
parent 16e6f349da
commit eebe840b6a
20 changed files with 840 additions and 13 deletions
+23
View File
@@ -25,6 +25,8 @@ Environment only; the server refuses to start when a required value is missing.
| `FORGE_URL`, `FORGE_REPO`, `FORGE_TOKEN` | optional; app updates (below). The token only needs to read the repository |
| `MAP_TILES_URL` | a TileJSON naming the vector tiles the map's roads come from; default OpenFreeMap's `https://tiles.openfreemap.org/planet`, `off` switches maps off, otherwise https only |
| `MAP_CACHE_DIR` | where fetched map tiles are kept across restarts (the manifest mounts a volume there); empty keeps them in memory only |
| `LOGS_DIR` | where the files the car sends from its Logs tab are kept (the manifest mounts a volume there); empty switches logs off |
| `LOGS_TOKEN` | optional, at least 24 characters and not the `INGEST_TOKEN`; reads those files (below). Empty: the car can send, nobody can read |
| `LISTEN_ADDR` | default `:8080` |
## Endpoints
@@ -43,6 +45,7 @@ with (403 otherwise); the shared token works for any.
- `PUT /v1/charging-sessions/{sessionId}` — a finished charging session (AC or DC, as far as the car was awake) with its curve.
- `GET /v1/consumption/history/{installationId}` — the car reads its own totals back.
- `GET /v1/health` — `{"status":"ok"}`.
- `PUT /v1/logs/{name}` — a file from the car's Logs tab (below).
A malformed request is a 400 naming the field; a database failure is a 500 without detail.
@@ -79,6 +82,26 @@ and is kept, and the car hears 503 with `Retry-After: 10` and gets the tile on i
404 when maps are off. The log says `map tile fetched` with the size, never the tile.
Map data © OpenStreetMap contributors (ODbL), tiles © OpenMapTiles, served by OpenFreeMap.
## Logs from the car
So a signal recording reaches the owner without a USB stick. The car sends each one when it ends
(again later when it is offline) with its own token: `PUT /v1/logs/{name}`, plain UTF-8 text,
gzipped (`Content-Encoding: gzip`) or not; at most 2 MB as sent and 8 MB unpacked. The name is
the app's (`signal-recording-20261027-085240.txt`: lower-case letters, digits and dashes,
`.txt`); the same name again replaces the file, so a retry is harmless. 204 when stored. The
newest 200 files are kept, none older than 180 days.
Reading takes `LOGS_TOKEN`, which can do nothing else, and the car's token cannot read:
```sh
curl -H "Authorization: Bearer $LOGS_TOKEN" https://car.k8s.stinnesbeck.com/v1/logs # {logs: [{name, bytes, received}]}, newest first
curl -H "Authorization: Bearer $LOGS_TOKEN" https://car.k8s.stinnesbeck.com/v1/logs/<name> # the text
```
`fetch-logs` in the dev shell fetches the new ones into `logs/` (token from
`~/.config/nilsmg4/logs-token`). 404 when reading is off. The log says `log received` with the
name and size, never the contents.
## Settings from a phone
The ABRP credentials are long to type on the car. With pairing on, the car (with its own token)