Files

159 lines
8.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# OTA firmware store
This directory (mounted into the container at `/app/firmware`) is managed by
the hub at runtime:
- `firmware.bin` – the latest image, available via `GET /ota/firmware`.
- `manifest.json` – version / size / sha256 / url of the stored image.
- Immutable images referenced by manifest URLs keep an already confirmed OTA
download from changing when another image is uploaded.
- `ota_state.json` and `pcb_state.json` are legacy single-device files. A global
auto-update setting is NOT inherited by newly registered devices.
Upload a new image from the dashboard (Firmware / OTA panel) or via
`POST /ota/upload`. Nothing here needs to be committed; the folder is created
on first start if missing.
## Multiple devices
Select a registered device in the dashboard before using its graphs, logs,
calibration, PCB controls, commands or OTA menu. Uploading an image only changes
the shared available image; it does not send an update command. The manual
update button confirms the selected device's name, ID, version and image digest.
Auto-update, if explicitly enabled, applies only to that device on reconnect.
An OTA request being sent is not proof of successful installation: check the
version reported after the device reconnects.
Device records, configuration and token hashes persist separately in
`/app/state/devices.sqlite3` through the `./state` volume. Processed graph buffers
are memory-only and start empty after a restart. Raw logs are stored under
`data_logs/<device_id>/`; old mixed logs at the root are not reassigned to a
device. Do not delete these volumes to rebuild the image.
Raw logs default to 30 days of retention and a 50 MiB daily cap per device;
`LOG_RETENTION_DAYS` and `LOG_DAILY_MAX_BYTES` can override these bounds. When
logging is degraded, live telemetry continues and the device status reports
`log_degraded` and `log_error_count`. Docker's operational logs rotate separately.
## Provisioning And Connection Contract
The ESP32 firmware now accepts a logical `PCB_ID` build flag in
`platformio_microtracking/platformio.ini`. The hub representation is `esp32-`
plus exactly 12 lowercase hex digits, padded with zeroes:
```text
-DPCB_ID=0x01 -> esp32-000000000001
-DPCB_ID=0x02 -> esp32-000000000002
```
`PCB_ID` must be unique, nonzero and at most 48 bits. It is independent of
`PCB_VERSION`, which selects the hardware pins. Default profiles use ID 0x01 for
the V1 environment and 0x02 for V2; change the ID as needed even when both boards
have the same hardware revision. A factory MAC can still be used as the 48-bit
value to preserve an existing registration. IDs are public, not passwords.
Register the exact IDs that will be compiled into the two boards:
```sh
docker compose exec tracker-hub python -m app.devices add --device-id esp32-000000000001 --alias "Tracker 1"
docker compose exec tracker-hub python -m app.devices add --device-id esp32-000000000002 --alias "Tracker 2"
docker compose exec tracker-hub python -m app.devices list
```
The CLI writes the generated token once to
`/app/state/provision/<device_id>.v1.token`, not stdout. Provision real secrets
inside the Linux container where file mode `0600` is enforced. Windows file
modes do not establish an equivalent private ACL. Configure the ESP32 through a
trusted local channel; remove the delivery file after provisioning. Configure
`platformio_microtracking/include/cloud_credentials.h` using the structure in
`cloud_credentials.example.h`: one `CLOUD_DEVICE_TOKEN` for each `PCB_ID`.
The private header is ignored by Git; never publish it or place actual tokens
in `platformio.ini`, command arguments, browser JavaScript, logs or URL queries.
Compilation fails if the ID is invalid or its token is missing/not 64 lowercase
hex characters. The firmware necessarily contains its selected token, so treat
the generated binary as sensitive as well.
Build using the correct hardware environment, without an upload target:
```sh
pio run -e waveshare_esp32_s3_nano
pio run -e waveshare_esp32_s3_nano_v2
```
The OTA image is `.pio/build/<environment>/firmware.bin`, not the bootloader or
the full flash image. Set the OTA version to `1.1.3-ws`. If changing `PCB_ID`
within one environment, its next build replaces the previous `firmware.bin`.
Keep OTA auto-update OFF when using per-ID binaries: upload and manually push
the matching image to board 1, then build/upload/push board 2's image. Never
flash one ID's image into both cards, which would duplicate identity and token.
Version `1.1.3-ws` fixes the duplicate CRLF in the Bearer extra header from
`1.1.2-id`. The WebSockets library appends its own terminator; including another
one ends HTTP headers before User-Agent and can immediately break the upgrade.
Existing registrations and tokens do not need to be changed for this fix.
The user-managed firmware must connect using validated TLS and this contract:
```text
WSS /ws/esp32?device_id=esp32-000000000001
Authorization: Bearer <individual-device-token>
```
The new firmware sends the ID in the handshake and its Bearer token only in
the Authorization header, including on reconnect. WSS and its OTA downloader
validate the server certificate against ISRG Root X1 (the current hub chain)
and require valid NTP time. Changing the server to another CA requires updating
`include/cloud_config.h`; no insecure fallback is enabled.
Existing `VER:`, `POS:`, sample and TEL messages stay unchanged. Old anonymous
firmware will not connect to the authenticated hub. For the initial transition,
build the new image without replacing the running service, and provision with
`docker compose run --rm --no-deps tracker-hub python -m app.devices add ...`
if the old running image does not yet contain the CLI. Prepare the ESP32 client
and its credentials before switching the production service. Firmware changes
and OTA installations are performed by the operator, not by automated tests.
If the authenticated hub is already deployed, an old anonymous client cannot
receive its first cloud OTA command. Use the board's local web interface OTA
uploader (`POST /update`, on a trusted LAN) for that first image; do not enable an anonymous
fallback on the hub.
```sh
docker compose exec tracker-hub python -m app.devices rotate --device-id esp32-000000000001 --grace-hours 1
docker compose exec tracker-hub python -m app.devices retire-previous --device-id esp32-000000000001
docker compose exec tracker-hub python -m app.devices revoke --device-id esp32-000000000001
docker compose exec tracker-hub python -m app.devices rename --device-id esp32-000000000001 --alias "New name"
```
Rotation creates a new versioned delivery file. Install the new token and verify
its connection before retiring the previous token. Revocation invalidates both
tokens and closes the active session; it does not erase the device's settings.
Use `--help` for enable/disable and explicit `migrate-primary --device-id ...`
to import the old PCB setting into ONE registered device. Record any old
in-memory calibration before stopping the old hub and apply it to that device.
`PRIMARY_DEVICE_ID` is only needed for compatibility with old unscoped API URLs;
the new dashboard always addresses a device explicitly.
## Deployment Boundary
The hub has one Uvicorn worker and no application-level operator login. Protect
the dashboard, browser WebSocket, operator APIs and OTA administration with a
VPN or authenticated reverse proxy before public deployment. CORS and per-device
tokens do not provide operator authentication. Allow the ESP32 connection path
to forward its own Bearer header; firmware download URLs must remain reachable
by the ESP32, separately from protected upload/push controls.
Compose defaults to `127.0.0.1:4000`. If Nginx Proxy Manager shares the hub's
Docker network, forward to `esp32_tracker_hub:8000`. Otherwise explicitly set
`HUB_BIND_IP` in the server's private `.env` to its LAN address and keep the
proxy pointed at that address on port 4000. Include the LAN dashboard origin
in `ALLOWED_ORIGINS` when direct LAN browser access is required.
Set `ALLOWED_ORIGINS` and `OTA_PUBLIC_URL` for the actual dashboard origin and
download host. Do not expose an alternative unprotected container/host port.
Back up state, firmware and raw logs before migration or rollback; keep the old
image and compatible state backup until the new hub is verified.
Stop the service before taking a filesystem backup of SQLite, or use SQLite's
online backup API. A live `devices.sqlite3` file alone may omit committed data
still held in its WAL file.
Older firmware still uses its old OTA downloader for the first transition.
New firmware validates TLS, but signed images/on-device SHA-256 pinning are not
implemented by this change; a hub-side digest is not a device-side signature.