Refresh docs and reorder roadmap

Co-authored-by: Copilot <[email protected]>
This commit is contained in:
Mathieu Moran
2026-08-10 18:48:59 -04:00
co-authored by Copilot
parent 457e3dcc08
commit d216aac97e
3 changed files with 425 additions and 386 deletions
+89 -84
View File
@@ -1398,89 +1398,9 @@ including two UX polish passes and multiple bug-fix rounds.
- **Dispatcher hook fix (`deploy/90-yaris-uplink`)** — previously ignored wlan1 events; now listens for both `wlan0` and `wlan1` so uplink transition recording remains accurate on current hardware.
- **README hardware photo** — added a current hardware build image (`pics/yaris-obd-hardware.jpg`) at the bottom of `readme.md`; saved without EXIF metadata and resized for lighter page load.
### Phase 25: Pre-install Yaris 2013 compatibility test — 🚧 PLANNED (core-system gate)
**Added 2026-08-09; intentionally immediately before the Android app.** Bench tests, synthetic
routes, attached-hardware tests, and stationary Pi runs cannot prove the complete system works
when temporarily connected to and powered by the actual 2013 Toyota Yaris. This phase is the
pre-installation acceptance gate for the complete core Pi/web/server system after Phase 24. It is
verification and targeted correction—not permanent installation or another redesign—and the
system is not considered ready to install until the results are recorded here. Permanent mounting,
cable routing, trim work, and final installation are explicitly outside the roadmap and will be
handled by the owner after this phase passes.
Prepare a temporary, removable test setup before driving:
- Connect the Pi, SIM868 HAT/antennas, USB OBD adapter, dashcam, and representative intended power
source without permanently mounting or routing anything. Secure every temporary component and
cable so it cannot interfere with pedals, steering, airbags, visibility, occupants, or loose-item
safety during the controlled drive; remove the setup after testing.
- Resolve the Phase 19/20 undervoltage/throttling warning before the test; confirm
`vcgencmd get_throttled` shows no current power fault at idle and under load with the temporary
representative power setup. This validates electrical compatibility, not final cable routing.
- Back up the Pi SQLite database and record baseline row/trip/video/upload counts on the Pi and
remote server. Use a uniquely timestamped test window so generated records can be reconciled
without deleting unrelated history.
- Verify the service, watchdog, storage mounts, hotspot, `http://yaris.local/`, alert engine, GPS
antenna view, dashcam recording mode, free space, and remote server health before moving.
- Use a second person for UI/log observations while the vehicle is moving, or record diagnostics
for review afterward. The driver must not operate or troubleshoot the UI while driving.
Run a controlled route containing ignition-off/on, stationary idle, low-speed streets, normal road
speed, several complete stops, turns, a short GPS-obstructed segment if safely available, and a
clean trip end:
- **OBD/ECU:** confirm the ELM327 reaches `CAR_CONNECTED`, record negotiated Toyota protocol and
adapter firmware, and verify real speed/RPM/coolant/fuel/throttle/MIL/DTC values. Compare wheel
speed against the instrument cluster at several safe speeds. Record unsupported PIDs (especially
odometer PID A6) as capabilities, not failures, and verify reconnect after ignition cycling and a
deliberate USB disconnect/reconnect.
- **GPS and motion logging:** confirm fixes while stationary and moving, route coordinates on the
offline map, OBD-speed-first motion detection, GPS displacement fallback when OBD is deliberately
unavailable, one-minute idle heartbeat behavior, and no false trip from stationary GPS drift.
- **Trip boundary/replay:** after the inactivity timeout, verify exactly one trip (or the expected
number for deliberate ignition gaps), correct start/end time and coordinates, plausible
distance/average/max speed, km/mi and km/h/mph values, route polyline, start/end markers, timeline
scrubber, play/pause marker position, and stopped portions of the route.
- **Local web UI:** test the final Phase 23 interface on the actual phone over the Pi/phone network at
`http://yaris.local/`; verify the explicit **In-car / Local Pi** identity, dashboard telemetry,
DTC page, trip history, dashcam library, settings, alerts, responsive layout, reconnect behavior,
and no new browser console/API errors.
- **Dashcam workflow:** create a known short real clip, switch the camera safely into USB storage
mode, detect/list it without confusing the OBD adapter, import only that selected file, verify
thumbnail/playback/Range seeking, preserve the source until checks finish, and confirm the camera
returns to recording afterward. Test deletion only with the disposable clip.
- **Alerts:** safely exercise conditions that can be simulated without damaging hardware (upload
backlog, temporary GPS loss while OBD reports motion, and disposable threshold overrides).
Confirm local bell transition/recovery/cooldown behavior and offline alert-state reconciliation
with the Phase 24 server after connectivity returns. Do not intentionally overheat, undervolt,
fill storage, or trigger a real MIL. With a real SIM, explicitly determine whether the SIM868
transport can reliably deliver ntfy notifications while WiFi/Ethernet is unavailable, including
authentication, timeout, retry, and recovery. If that cannot be made reliable on the actual
modem/network, remove the ntfy feature and its settings rather than retain a transport that fails
in the only situation where it is useful.
- **Power/reliability:** perform multiple normal ignition/power cycles, confirm automatic boot,
service/watchdog startup, SIM868 power-on, OBD/GPS recovery, no SQLite corruption, and no current
undervoltage/throttling. The deferred Phase 14 clean-shutdown limitation must be explicitly
accepted or resolved with representative delayed-power/ignition-sense hardware before declaring
the design ready for permanent installation.
Verify the complete remote path after connectivity returns:
- Confirm every test-window tracking row uploads exactly once and the selected dashcam clip resumes
or completes without corruption; compare counts, timestamps, hashes, and byte sizes locally and
remotely.
- On the Phase 24 portal, verify the explicit **Remote server** identity, authenticated
vehicle-scoped trip summary/replay, route and timeline agreement with the local Pi, archived clip
thumbnail/playback/Range seeking/download, synchronized alert history/current state without
duplicate transitions after an offline interval, and absence of local-only OBD/cellular/Pi
controls.
- The Android companion is intentionally outside this core acceptance gate and remains the final,
low-priority Phase 26. Its own hotspot/cellular binding, trip display, and disposable clip relay
require separate real-phone/in-car acceptance when that phase is implemented.
Acceptance requires a written result for every item, captured negotiated protocol/PID support,
measured trip/count/hash comparisons, relevant logs/screenshots, no unexplained tracebacks or data
loss, and cleanup of every disposable record/file while preserving the real drive. Any defect found
must be fixed, redeployed uncommitted, and the affected in-car test repeated before the final commit
and **ready-to-install** sign-off. Afterward, the owner handles permanent installation outside the
phase roadmap.
**Roadmap ordering note (2026-08-10):** the late phases are intentionally sequenced as
Phase 25.5a–25.5d (configuration/sync/status work) -> Phase 25 (real-car acceptance gate) ->
Phase 26 (Android, always last).
### Phase 25.5a: Device configuration sync backend — ✅ COMPLETE (connection-driven server sync)
**Server API and Pi sync thread implemented.** Server stores device config; Pi fetches when internet available and applies changes without restart.
@@ -1744,7 +1664,7 @@ updates its display-facing values without a manual restart once sync lands.
history view still shows one configured car/device at a time until a later
portal-wide device selector/filter is implemented.
### Phase 25.5d: GPS dashboard visibility / live-status debugging — 🚧 IN PROGRESS
### Phase 25.5d: GPS dashboard visibility / live-status debugging — ✅ COMPLETE
- Add an explicit Pi dashboard GPS status indicator (thread alive / live fix / stale fix / no fix)
- Expose a local `/api/gps/status` endpoint so GPS health can be checked without relying on
socket events alone
@@ -1793,6 +1713,91 @@ updates its display-facing values without a manual restart once sync lands.
card now appears before the Live GPS map. Deployed to the real Pi and confirmed the rendered
`/` HTML places `Overview` before `Live GPS`.
---
### Phase 25: Pre-install Yaris 2013 compatibility test — 🚧 PLANNED (core-system gate)
**Added 2026-08-09; intentionally immediately before the Android app.** Bench tests, synthetic
routes, attached-hardware tests, and stationary Pi runs cannot prove the complete system works
when temporarily connected to and powered by the actual 2013 Toyota Yaris. This phase is the
pre-installation acceptance gate for the complete core Pi/web/server system after Phase 24. It is
verification and targeted correction—not permanent installation or another redesign—and the
system is not considered ready to install until the results are recorded here. Permanent mounting,
cable routing, trim work, and final installation are explicitly outside the roadmap and will be
handled by the owner after this phase passes.
Prepare a temporary, removable test setup before driving:
- Connect the Pi, SIM868 HAT/antennas, USB OBD adapter, dashcam, and representative intended power
source without permanently mounting or routing anything. Secure every temporary component and
cable so it cannot interfere with pedals, steering, airbags, visibility, occupants, or loose-item
safety during the controlled drive; remove the setup after testing.
- Resolve the Phase 19/20 undervoltage/throttling warning before the test; confirm
`vcgencmd get_throttled` shows no current power fault at idle and under load with the temporary
representative power setup. This validates electrical compatibility, not final cable routing.
- Back up the Pi SQLite database and record baseline row/trip/video/upload counts on the Pi and
remote server. Use a uniquely timestamped test window so generated records can be reconciled
without deleting unrelated history.
- Verify the service, watchdog, storage mounts, hotspot, `http://yaris.local/`, alert engine, GPS
antenna view, dashcam recording mode, free space, and remote server health before moving.
- Use a second person for UI/log observations while the vehicle is moving, or record diagnostics
for review afterward. The driver must not operate or troubleshoot the UI while driving.
Run a controlled route containing ignition-off/on, stationary idle, low-speed streets, normal road
speed, several complete stops, turns, a short GPS-obstructed segment if safely available, and a
clean trip end:
- **OBD/ECU:** confirm the ELM327 reaches `CAR_CONNECTED`, record negotiated Toyota protocol and
adapter firmware, and verify real speed/RPM/coolant/fuel/throttle/MIL/DTC values. Compare wheel
speed against the instrument cluster at several safe speeds. Record unsupported PIDs (especially
odometer PID A6) as capabilities, not failures, and verify reconnect after ignition cycling and a
deliberate USB disconnect/reconnect.
- **GPS and motion logging:** confirm fixes while stationary and moving, route coordinates on the
offline map, OBD-speed-first motion detection, GPS displacement fallback when OBD is deliberately
unavailable, one-minute idle heartbeat behavior, and no false trip from stationary GPS drift.
- **Trip boundary/replay:** after the inactivity timeout, verify exactly one trip (or the expected
number for deliberate ignition gaps), correct start/end time and coordinates, plausible
distance/average/max speed, km/mi and km/h/mph values, route polyline, start/end markers, timeline
scrubber, play/pause marker position, and stopped portions of the route.
- **Local web UI:** test the final Phase 23 interface on the actual phone over the Pi/phone network at
`http://yaris.local/`; verify the explicit **In-car / Local Pi** identity, dashboard telemetry,
DTC page, trip history, dashcam library, settings, alerts, responsive layout, reconnect behavior,
and no new browser console/API errors.
- **Dashcam workflow:** create a known short real clip, switch the camera safely into USB storage
mode, detect/list it without confusing the OBD adapter, import only that selected file, verify
thumbnail/playback/Range seeking, preserve the source until checks finish, and confirm the camera
returns to recording afterward. Test deletion only with the disposable clip.
- **Alerts:** safely exercise conditions that can be simulated without damaging hardware (upload
backlog, temporary GPS loss while OBD reports motion, and disposable threshold overrides).
Confirm local bell transition/recovery/cooldown behavior and offline alert-state reconciliation
with the Phase 24 server after connectivity returns. Do not intentionally overheat, undervolt,
fill storage, or trigger a real MIL. With a real SIM, explicitly determine whether the SIM868
transport can reliably deliver ntfy notifications while WiFi/Ethernet is unavailable, including
authentication, timeout, retry, and recovery. If that cannot be made reliable on the actual
modem/network, remove the ntfy feature and its settings rather than retain a transport that fails
in the only situation where it is useful.
- **Power/reliability:** perform multiple normal ignition/power cycles, confirm automatic boot,
service/watchdog startup, SIM868 power-on, OBD/GPS recovery, no SQLite corruption, and no current
undervoltage/throttling. The deferred Phase 14 clean-shutdown limitation must be explicitly
accepted or resolved with representative delayed-power/ignition-sense hardware before declaring
the design ready for permanent installation.
Verify the complete remote path after connectivity returns:
- Confirm every test-window tracking row uploads exactly once and the selected dashcam clip resumes
or completes without corruption; compare counts, timestamps, hashes, and byte sizes locally and
remotely.
- On the Phase 24 portal, verify the explicit **Remote server** identity, authenticated
vehicle-scoped trip summary/replay, route and timeline agreement with the local Pi, archived clip
thumbnail/playback/Range seeking/download, synchronized alert history/current state without
duplicate transitions after an offline interval, and absence of local-only OBD/cellular/Pi
controls.
- The Android companion is intentionally outside this core acceptance gate and remains the final,
low-priority Phase 26. Its own hotspot/cellular binding, trip display, and disposable clip relay
require separate real-phone/in-car acceptance when that phase is implemented.
Acceptance requires a written result for every item, captured negotiated protocol/PID support,
measured trip/count/hash comparisons, relevant logs/screenshots, no unexplained tracebacks or data
loss, and cleanup of every disposable record/file while preserving the real drive. Any defect found
must be fixed, redeployed uncommitted, and the affected in-car test repeated before the final commit
and **ready-to-install** sign-off. Afterward, the owner handles permanent installation outside the
phase roadmap.
---
### Phase 26: Native Android companion app — 🚧 PLANNED (low priority, always last)
+34 -15
View File
@@ -59,8 +59,8 @@ Dashboard — mobile (390 px) and desktop (1280 px):
trip history with OSM/CARTO live map and route replay, dashcam archive browsing with inline
video player and Range-request seeking, overview stats dashboard, delete buttons for trips and
clips, and a device-settings page for editing the synced owner/car/avatar values that the Pi
pulls from the server. The portal can register multiple devices, reveal a one-time API key at
creation/rotation time, and request a sync for a selected device. When the Pi has
pulls from the server. The portal can register/delete multiple devices, reveal a one-time API
key at creation/rotation time, and request a sync for a selected device. When the Pi has
**Sync with server** enabled, those identity changes hot-reload into the running local UI on the
next successful sync instead of waiting for a manual restart. Shared `history.js` code runs on
both the Pi and the server — tile source and zoom limits are configured per-deployment via HTML
@@ -71,8 +71,9 @@ Dashboard — mobile (390 px) and desktop (1280 px):
diagnostics, plus quick actions for server test, sync-now, uplink reconnect, and app restart
- 🗄️ **Motion-aware local-first SQLite logging** — five-second motion checks without five-second
parked writes, plus retry-safe remote sync and automatic backoff
- 🌡️ **Pi health monitoring** — dashboard temperature, RAM, disk space, uptime, and explicit
undervoltage/throttling warnings from the Raspberry Pi firmware
- 🌡️ **Pi health + diagnostics** — a dedicated local Status page shows temperature, RAM, disk,
uptime, undervoltage/throttling warnings, service state, uplink/server reachability, GPS state,
OBD presence, and safe quick actions
- 🔔 **Offline-first alerts** — a notification bell on every page reports check-engine state,
storage pressure, upload backlog, temperature/power faults, and GPS loss while moving. Optional
ntfy delivery is available but remains disabled until configured
@@ -122,7 +123,14 @@ For the remote server (receives trip logs + dashcam uploads), see [server/README
## Customizing device identity
The Pi displays a username, car model, and avatar image. All customizable values are stored in `config/device.json` (no code edits needed).
The Pi displays an owner name, car model, device name, and avatar. You can manage those values in
two ways:
- **Locally on the Pi** through the Settings page
- **From the server portal** when **Sync with server** is enabled on the Pi
The local fallback/default source remains [config/device.json](./config/device.json), so the car
still boots with sensible identity values even when fully offline.
### Edit device configuration
@@ -141,10 +149,10 @@ Edit [config/device.json](./config/device.json):
|-------|----------|---------|
| `owner_name` | Top-right of app topbar | Your name/username |
| `car_model` | Left sidebar (next to car icon) | Vehicle model/year |
| `device_name` | (Reserved) | Future server-side use in Phase 25.5 |
| `device_name` | Local settings / remote portal | Device identity used by server sync/provisioning |
| `avatar_path` | Top-right avatar circle | Profile picture (relative to `static/`) |
After editing, restart the app:
If you edit the JSON file directly, restart the app:
```bash
# If running as a systemd service:
@@ -187,15 +195,26 @@ rm db/sql/quoije_Yaris2013.db
## Project status
Phases 1–24.7 are complete. The system is fully built and audited, ready for the Phase 25 real-vehicle test:
The build is now at the endgame stage: the Pi app, remote server, and device-management/sync work
are implemented and verified on the real Pi/unraid setup. The remaining roadmap phases are the
temporary real-vehicle acceptance test and the low-priority Android companion.
- **Phases 1–23.5**: core Pi system — GPS, OBD, dashcam, trip history, web UI redesign, WiFi upgrade, cellular fallback, alerts, settings, upload
- **Phase 24**: remote web portal at `https://yaris.ceaf.dev/` — login-protected, trip history with CARTO live map, dashcam archive, delete, overview stats
- **Phase 24.5**: full code audit — 6 bugs fixed across Pi modules and server, 63 tests passing
- **Phase 24.6**: Yaris 2013 OBD compatibility hardening — `_decode_odometer` no longer kills the OBD session on unsupported PIDs, sensor globals reset on reconnect, `OBD_SKIP_VOLTAGE_CHECK` escape hatch for clone ELM327 adapters
- **Phase 24.7**: diagnostic script (`scripts/diagnose.sh`), map tile switch to CARTO, zoom limit fix
- **Phase 24.8**: deployment/readme cleanup and uplink interface consistency
- **Phases 25.5a–25.5d**: offline-first device identity sync, remote portal device settings,
Pi-side hot-reload, multi-device provisioning, one-time API key rotation/registration, local
Settings UI for remote credentials, Sync now / Test connection, GPS live-status verification,
dedicated local Status page, and status-page quick actions
- **Current known limitation**: the remote portal's device-management page is multi-device, but the
main remote overview/history/dashcam browsing views still follow one configured `DEVICE_NAME`
until a later portal-wide device selector/filter is added
**Next**: Phase 25 — temporary real-vehicle test in the 2013 Yaris (see PLAN.md for the full acceptance checklist). Phase 26 (Android companion app) follows after that.
**Next**: Phase 25 — temporary real-vehicle test in the 2013 Yaris (see [PLAN.md](./PLAN.md) for
the full acceptance checklist). Phase 26 (Android companion app) follows after that.
Phase 14 is partially complete: the Pi's hardware watchdog is deployed and reboot-verified.
Read-only-root storage hardening is deferred until persistent storage is moved off the SD root
@@ -205,12 +224,12 @@ The browser-based phone relay was removed after real-world testing confirmed tha
cannot reliably use the Pi's WiFi and cellular data simultaneously. A native Android companion is
planned later instead (Phase 26).
The local Web UI includes a Settings page for safe runtime configuration: dashcam uploads,
uploaded-video retention, cellular APN fallback, optional ntfy delivery, and alert thresholds.
Notification secrets are write-only in the browser API, and network credentials that could lock
the Pi out remain SSH/file-managed. When cellular is configured, small ntfy notifications use the
SIM868 first and WiFi/Ethernet only as fallback; that modem path is implemented and mock-tested but
still needs real-SIM/carrier verification.
The local web UI includes a Settings page for safe runtime configuration: dashcam uploads,
uploaded-video retention, cellular APN fallback, optional ntfy delivery, alert thresholds, local
device identity, and remote server credentials/API key. Notification secrets remain write-only in
the browser API. When cellular is configured, small ntfy notifications use the SIM868 first and
WiFi/Ethernet only as fallback; that modem path is implemented and mock-tested but still needs
real-SIM/carrier verification.
The Pi can also join a configured phone hotspot for outbound internet when no SIM is available,
including trip synchronization and WiFi dashcam uploads, with automatic recovery to home WiFi or
+33 -18
View File
@@ -21,6 +21,13 @@ docker-compose stack on an unraid server (or any Docker host).
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
@@ -28,13 +35,11 @@ headers: `X-Device-Name` and `X-Api-Key` (see
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: *`) so a
device's registered API key can be used directly from browser JS running on a
*different* origin — specifically, the Pi's phone-relay feature (see
[PLAN.md](../PLAN.md) Phase 11), where a phone's browser calls this API
straight from a page served by the Pi itself. This is safe here because
auth is header-based (`X-Device-Name`/`X-Api-Key`), not cookie/session-based,
so a permissive origin can't be used to steal a logged-in session.
**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
@@ -65,12 +70,11 @@ so a permissive origin can't be used to steal a logged-in session.
docker compose ps
curl http://localhost:8098/health
```
5. Sign in to the remote portal and open `/settings/device` to edit the synced
owner/car/device/avatar values for the configured `DEVICE_NAME`. If the Pi
has local **Sync with server** enabled, those values hot-reload into the
running Pi UI after the next successful sync. Phase 25.5c also adds a
multi-device list, one-time API-key reveal, key rotation, and a request-sync
action on that page.
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
@@ -87,10 +91,12 @@ 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. 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 now hot-reloads the in-process
UI fields instead of requiring an app restart.
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
@@ -104,14 +110,23 @@ 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:
@@ -149,7 +164,7 @@ byte-for-byte sha256-verified against the source on the Pi.
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 `/api/v1/dashcam/upload` while the toggle is off.
stops calling the dashcam upload status/chunk endpoints while the toggle is off.
## Local development / testing without unraid