From a0eb5e4e9d5aeee932e43edd308312687cc72112 Mon Sep 17 00:00:00 2001 From: tajniak81 <13187254+tajniak81@users.noreply.github.com> Date: Tue, 21 Jul 2026 22:46:21 +0200 Subject: [PATCH] Docs: refresh every README against the current code Verified each documented command, path, port and env var against what the code actually does, and corrected the drift. Phone App. Was still titled Car Control. The navigation description was also stale: the app moved to a RootShell bottom nav (Garage, Charging, Settings, and Users for admins), so the Settings gear and admin action the dashboard bullet described no longer exist. Adds the Charging screen, noting its public tab is placeholder data and only the Home tab's OCPP control is real, and rebuilds the lib/ tree, which had lost i18n.dart, theme.dart, widgets/ and three screens. Web App. Node 18+ was wrong. The installed Vite is 8.1.2, whose engines field is ^20.19.0 || >=22.12.0 - Node 18 is EOL and cannot build this. API Server. The config table gained OCPP_REQUIRE_TLS, OCPP_PUBLIC_URL, PB_BOOTSTRAP and DRIVERVAULT_SUPERADMIN_*, plus a note that PLUGINS_FILE and the panel-written .env resolve against the working directory (a volume, in Docker). Plugins. Per-tenant credentials sat under "not yet implemented", but /api/integrations/* has done exactly that for both built-ins for a while. Narrowed the roadmap item to the genuinely missing generic version. New Docker/README.md and Docker AIO/README.md: the root README's component table linked those directories as documentation but neither had any. The root README now points at them. All 8 markdown files pass a relative-link check. Co-Authored-By: Claude Opus 4.8 --- API Server/README.md | 9 +++ API Server/internal/plugins/README.md | 10 ++- Docker AIO/README.md | 96 +++++++++++++++++++++++++++ Docker/README.md | 92 +++++++++++++++++++++++++ Phone App/README.md | 46 ++++++++----- README.md | 8 ++- Web App/README.md | 4 +- 7 files changed, 244 insertions(+), 21 deletions(-) create mode 100644 Docker AIO/README.md create mode 100644 Docker/README.md diff --git a/API Server/README.md b/API Server/README.md index 6953a1b..c3d9c64 100644 --- a/API Server/README.md +++ b/API Server/README.md @@ -258,10 +258,19 @@ Copy `.env.example` to `.env` and fill in. Summary: | `WEBAPP_URL` | `http://localhost:8090` | probed by `/api/status` | | `AUTH_USERS_COLLECTION` | `users` | PocketBase auth collection | | `PLUGINS_FILE` | `plugins.json` | plugin state store | +| `OCPP_REQUIRE_TLS` | `true` | reject chargers that did not connect over TLS | +| `OCPP_PUBLIC_URL` | — | canonical `ws(s)://` base to point chargers at | +| `PB_BOOTSTRAP` | `true` | run the on-boot schema create/reconcile | +| `DRIVERVAULT_SUPERADMIN_EMAIL` / `_PASSWORD` / `_NAME` | — / — / `Administrator` | first `superadmin`, created on boot when absent | `PB_URL`, `PB_ADMIN_EMAIL`, `PB_ADMIN_PASSWORD`, `PORT` and `CORS_ORIGINS` are still honoured for older deployments; the modern names win when both are set. +Two paths are resolved **relative to the working directory**: `PLUGINS_FILE` and +the `.env` the panel rewrites when a superadmin retargets PocketBase. In Docker +the working directory is `/data`, a volume, so both survive a container +recreate — see [`Dockerfile`](Dockerfile) and [`../Docker`](../Docker). + `CORS_ALLOW_ORIGINS` only matters for **browser** clients (the web app). Native mobile apps are not subject to CORS. diff --git a/API Server/internal/plugins/README.md b/API Server/internal/plugins/README.md index 4393909..a220906 100644 --- a/API Server/internal/plugins/README.md +++ b/API Server/internal/plugins/README.md @@ -303,8 +303,14 @@ The contract is shaped for these; see [`doc.go`](doc.go): - **Invocation API** — an endpoint to call `Invoke` from clients, with a normalized request/response envelope and a provider→internal mapper. - **Resilience** — retry/backoff, circuit breaker, per-plugin latency/error metrics. -- **Per-tenant credentials** — config keyed by org/user so users connect their own accounts. -- **Audit logging** of plugin access. +- **Per-tenant credentials _for arbitrary plugins_** — the two built-in connectors + already have them, through the hand-written `/api/integrations/toyota` and + `/api/integrations/anker-solix` routes and their **superadmin → org admin → + user** config cascade. What is missing is the generic version: per-org/per-user + config keyed off `ConfigFields`, so a newly registered plugin gets the same + treatment without new endpoints. +- **Audit logging** of plugin access. (Charger *control* commands are already + audited to the `control_audit` collection; this is the wider plugin case.) Until the invocation API lands, `Invoke` is dormant — plugins are discoverable, configurable, and health-checked, but not yet callable over HTTP. diff --git a/Docker AIO/README.md b/Docker AIO/README.md new file mode 100644 index 0000000..cb8abbf --- /dev/null +++ b/Docker AIO/README.md @@ -0,0 +1,96 @@ +# DriverVault — Docker AIO (all-in-one image) + +**PocketBase + API Server + Web App in a single container**, supervised by +`supervisord` with nginx serving the SPA and proxying `/api/` to the API Server +on localhost. One image, one volume set, no compose network — the simplest way +to stand DriverVault up on a single host. + +Prefer the three-container stack in [`../Docker`](../Docker) when you want to +scale, upgrade or restart the pieces independently. + +``` +:80 nginx ─► SPA, and /api/ ─► API Server on 127.0.0.1:8080 ─► PocketBase on :8070 +``` + +| File | Use | +|---|---| +| `Dockerfile` | the all-in-one image (build context must be the **repo root**) | +| `docker-compose.yml` | **builds from source** — for development and local testing | +| `docker-compose.prod.yml` | **pulls the prebuilt image** from the registry | +| `.env.example` / `.env.prod.example` | copy to `.env` for the matching compose file | + +## Run it + +```bash +cd "Docker AIO" +cp .env.example .env # then edit — PB_ADMIN_* have no safe defaults +docker compose up -d --build +``` + +Production, from the registry: + +```bash +cp .env.prod.example .env # then edit +docker compose -f docker-compose.prod.yml pull +docker compose -f docker-compose.prod.yml up -d +``` + +Then: web app on `http://host:8090/`, the API Server's superadmin panel on +`http://host:8080/`, PocketBase admin on `http://host:8070/_/`. + +Note the port mapping: inside the container the web app is on **80**, published +as `WEB_PORT` (8090 by default) to line up with the other deployment. + +## Building by hand + +The build context **must be the repo root** so the Dockerfile can reach both +`API Server/` and `Web App/`: + +```bash +docker build -f "Docker AIO/Dockerfile" -t drivervault-aio . +``` + +Build args: `VITE_API_BASE` (leave empty so the bundle uses same-origin `/api`) +and `PB_VERSION` (pin PocketBase, or leave empty to fetch the latest release at +build time). + +## First boot + +Identical to the multi-container stack, and idempotent: + +1. PocketBase upserts its superuser from `PB_ADMIN_EMAIL` / `PB_ADMIN_PASSWORD`. +2. The API Server waits for PocketBase to report healthy, then creates any + missing collections, reconciles existing ones, and creates the first app + `superadmin` from `DRIVERVAULT_SUPERADMIN_EMAIL` / `_PASSWORD`. Set + `PB_BOOTSTRAP=false` to skip once the database is established. + +## Volumes + +| Volume | Holds | +|---|---| +| `/pb/pb_data` | the PocketBase SQLite database and uploaded files | +| `/data` | the API Server's `plugins.json`, and the `.env` the panel rewrites when a superadmin retargets the PocketBase connection | + +Both default to Docker-managed named volumes; set `PB_DATA` / `API_DATA` to +absolute host paths in the prod file for bind mounts. + +## Charger control (OCPP) + +Chargers in own/proxy mode dial in to `/ocpp/{serial}` on the **API Server port +(8080)** — not through nginx — authenticating with a per-charger control token +in an OCPP Basic-auth header. Because a plaintext `ws://` would expose that +token, `OCPP_REQUIRE_TLS` defaults to `true`. + +This image serves plain HTTP, so charger control needs TLS terminated in front +of it, with `OCPP_PUBLIC_URL` set to the public `wss://` base. Only drop +`OCPP_REQUIRE_TLS` on a trusted network. + +## Caveats + +- Everything runs as **root** in one container, and a crash of `supervisord` + takes all three services down together. That is the trade for the simplicity. +- Logs from all three processes are interleaved on the container's stdout/stderr + (`docker logs drivervault-aio`). +- `PB_VERSION` empty means the image pulls whatever PocketBase release is latest + **at build time**, so two builds of the same source can differ. Pin it for + reproducibility. diff --git a/Docker/README.md b/Docker/README.md new file mode 100644 index 0000000..8d16983 --- /dev/null +++ b/Docker/README.md @@ -0,0 +1,92 @@ +# DriverVault — Docker (multi-container stack) + +Three containers — **PocketBase**, **API Server**, **Web App** — on one compose +network. This is the deployment to use unless you specifically want everything +in a single image; for that see [`../Docker AIO`](../Docker%20AIO). + +``` +Browser ─► Web App BFF (:8090) ──/api/*──► API Server (:8080) ─► PocketBase (:8070) +``` + +Only the Web App port is meant to be public. The API Server and the PocketBase +admin UI are published for convenience and, in the prod file, bound to +`127.0.0.1` by default. + +| File | Use | +|---|---| +| `docker-compose.yml` | **builds from source** in this repo — for development and local testing | +| `docker-compose.prod.yml` | **pulls prebuilt images** from the registry — for deployment | +| `.env.example` / `.env.prod.example` | copy to `.env` for the matching compose file | +| `pocketbase/` | the PocketBase image (official release binary on alpine) | + +## Run it + +```bash +cd Docker +cp .env.example .env # then edit — PB_ADMIN_* have no safe defaults +docker compose up -d --build +``` + +Production, from the registry: + +```bash +cp .env.prod.example .env # then edit +docker compose -f docker-compose.prod.yml pull +docker compose -f docker-compose.prod.yml up -d +``` + +Then: web app on `http://host:8090/`, the API Server's superadmin panel on +`http://host:8080/`, PocketBase admin on `http://host:8070/_/`. + +## First boot + +Both steps are idempotent, so restarts and upgrades are safe: + +1. **PocketBase** upserts its superuser from `PB_ADMIN_EMAIL` / `PB_ADMIN_PASSWORD`. + This is the only way to create the first superuser — the REST API cannot + bootstrap it. The API Server then authenticates with the same credentials. +2. **The API Server** creates any missing collections and reconciles existing + ones, then creates the first app `superadmin` from + `DRIVERVAULT_SUPERADMIN_EMAIL` / `_PASSWORD` if no such user exists. Set + `PB_BOOTSTRAP=false` to skip once the database is established. + +No manual `setup-pocketbase.mjs` step is needed here — the server runs the same +schema reconcile itself. + +## Volumes + +| Volume | Holds | +|---|---| +| `pb_data` | the PocketBase SQLite database and uploaded files | +| `api_data` | the API Server's `plugins.json`, and the `.env` the panel rewrites when a superadmin retargets the PocketBase connection | + +Both default to Docker-managed named volumes. In the prod file, set `PB_DATA` / +`API_DATA` to absolute host paths for bind mounts instead. + +> The API Server container runs as an unprivileged user, and a **named** volume +> inherits that ownership from the image. A **bind mount** does not — the host +> directory's ownership wins, so `chown` it to the container's `app` user (or +> `chmod` it writable) before setting `API_DATA` to a host path, otherwise the +> server cannot write `plugins.json`. + +## Charger control (OCPP) + +Chargers in own/proxy mode dial in to `/ocpp/{serial}` **on the API Server +port**, authenticating with a per-charger control token in an OCPP Basic-auth +header. A plaintext `ws://` would put that token on the wire in the clear, so +`OCPP_REQUIRE_TLS` defaults to `true` and non-TLS connections are rejected. + +This stack serves plain HTTP, so to actually use charger control you need to +terminate TLS in a reverse proxy in front of it and set `OCPP_PUBLIC_URL` to the +public `wss://` base (behind a proxy, deriving it from request headers is +unreliable). `OCPP_REQUIRE_TLS=false` is for trusted networks only. You will +also need `API_BIND` set so the proxy can reach the port. + +## Notes + +- `docker-compose.yml` builds the API Server and Web App from `../API Server` + and `../Web App`, so run it from this directory with the repo checked out. +- The Web App's Vue bundle is built with an empty `VITE_API_BASE`, so the + browser uses same-origin `/api` and the BFF proxies it — no CORS in play. +- `CORS_ALLOW_ORIGINS` therefore only matters if a browser calls the API Server + directly. Native mobile apps are not subject to CORS at all. diff --git a/Phone App/README.md b/Phone App/README.md index 4b1a26f..4686982 100644 --- a/Phone App/README.md +++ b/Phone App/README.md @@ -1,6 +1,6 @@ -# Car Control — Phone App (Flutter) +# DriverVault — Phone App (Flutter) -A Flutter client for the Car Control maintenance tracker. Talks **only** to the +A Flutter client for the DriverVault maintenance tracker. Talks **only** to the API Server (same contract and auth as the web app — a PocketBase token relayed by the server, not a JWT the server mints). At full feature parity with the web app (data export/import is the only deliberate omission). @@ -11,12 +11,16 @@ deprecated. ## Features +Once signed in, `RootShell` hosts the app behind a persistent **bottom +navigation bar** — Garage, Charging, Settings, and Users for admins — in an +`IndexedStack`, so each section keeps its state as you switch tabs. + - **Login** — email/password against `/api/auth/login`, password show/hide, and a collapsible **Server settings** section to override the API base URL on-device. - **Biometric / face sign-in + app lock** — see the dedicated section below. -- **Dashboard** — car list with next-due status badges (date + km, worst-of), - a "shared" chip on cars owned by someone else, pull-to-refresh, **Add car** - FAB, Settings gear, and an admin action (admins only). +- **Garage (dashboard)** — car list with next-due status badges (date + km, + worst-of), a "shared" chip on cars owned by someone else, pull-to-refresh and + an **Add car** FAB. - **Car detail** — all spec fields (incl. VIN and transmission / differential / brake / coolant specs), a **share** sheet (owner only), quick odometer update, edit car, and delete car (type-to-confirm; cascades). Actions are gated by the @@ -45,13 +49,20 @@ deprecated. workshop visit, refill, document and part (PDF or image, up to 10MB). Picked with `file_picker`, fetched back through the API Server — never a public URL — and opened with the phone's own viewer via `open_filex`. +- **Charging** — mirrors the web `Charging.vue`, split into two tabs. **Public** + is a discovery map with a demo session and nearby stations: presentational + placeholders, because there is no public-charging API yet (same as the web). + **Home** carries the one real piece — an OCPP control card that drives your + own charger through the Anker Solix control endpoints, once you pick Own/Proxy + CSMS under Settings → Integrations. - **Settings** — account (name / email verification / password), appearance (theme + dark mode, **language**, **region**, date format, **currency**, font - size), profile (avatar via `image_picker`, bio), **Security** (biometric - toggle), and the account-deletion state machine. Auth relays PocketBase's own - stateless tokens, so there is no per-device session list to show or revoke. -- **Admin** — user management screen (list / create / role / reset password / - delete), gated by the admin role. + size), profile (avatar via `image_picker`, bio), **integrations** (Toyota, + Anker Solix), **Security** (biometric toggle), and the account-deletion state + machine. Auth relays PocketBase's own stateless tokens, so there is no + per-device session list to show or revoke. +- **Users (admin)** — user management tab (list / create / role / reset password + / delete), shown only for the admin role. Sharing/ownership: `Car.access` drives `isOwner` / `canWrite` / `isReadOnly` getters that gate the UI, mirroring the server's access checks. @@ -134,15 +145,20 @@ Notes: ``` lib/ ├── config.dart # default API base URL (kDefaultApiBase) -├── models.dart # Car (+ access getters), ServiceRecord, Part, AuthUser, UserProfile, Session +├── models.dart # Car (+ access getters), the record types, integrations, profile ├── api.dart # ApiClient — the only thing that calls the API Server ├── auth.dart # AuthService (token persistence, app-lock flag, ChangeNotifier) ├── biometric.dart # BiometricAuth — local_auth + secure storage; biometricAuth singleton ├── app_settings.dart # AppSettings (theme/locale/date/font), persisted; drives MaterialApp +├── i18n.dart # translation lookup — t("key"); en/pl/da with en fallback +├── theme.dart # shared colours/tones (status badges, charging tiles) ├── format.dart # date/km formatting + next-service status (worst-of date/km) -├── main.dart # app root; routes Login / Lock / Dashboard; lifecycle-based re-lock +├── main.dart # app root; routes Login / Lock / RootShell; lifecycle re-lock +├── widgets/ +│ └── attachment_field.dart # pick / view / clear a record's attached file └── screens/ - ├── login_screen.dart dashboard_screen.dart car_detail_screen.dart - ├── car_form_sheet.dart settings_screen.dart admin_users_screen.dart - └── lock_screen.dart + ├── root_shell.dart # bottom-nav shell: Garage, Charging, Settings, Users + ├── login_screen.dart lock_screen.dart dashboard_screen.dart + ├── car_detail_screen.dart car_form_sheet.dart record_form_sheets.dart + └── charging_screen.dart settings_screen.dart admin_users_screen.dart ``` diff --git a/README.md b/README.md index aaf35b3..32ce347 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ nothing talks to PocketBase directly. | **Database** | PocketBase | ✅ running, schema + seed done | — | | **Web App** | Vue 3 + Vite + Tailwind v4 | ✅ full feature set (below) | [Web App/README.md](Web%20App/README.md) | | **Phone App** | Flutter (Android) | ✅ web parity + biometric login | [Phone App/README.md](Phone%20App/README.md) | -| **Docker** | Compose (server / all-in-one) | ✅ deployment configs | [Docker](Docker) · [Docker AIO](Docker%20AIO) | +| **Docker** | Compose (multi-container / all-in-one) | ✅ deployment configs | [Docker/README.md](Docker/README.md) · [Docker AIO/README.md](Docker%20AIO/README.md) | | **Car Agent Device** | ESP32 + SIM7600 (LILYGO TTGO) | 🚧 firmware in progress | [Car Agent Device](Car%20Agent%20Device) | | **Home Assistant Plugin** | — | ⬜ later | — | @@ -113,8 +113,10 @@ Bring up the stack in this order — each app's README has the details: 3. **[Phone App](Phone%20App/README.md)** — `flutter build apk` / `flutter run` with `--dart-define=API_BASE=http://:8080/api`. -Or bring the whole stack up with **Docker** — see [Docker](Docker) (server + -web) and [Docker AIO](Docker%20AIO) (single all-in-one image). +Or skip all of that and bring the whole stack up with **Docker**, which runs the +schema setup itself — see [Docker](Docker/README.md) (PocketBase + API Server + +Web App as three containers) or [Docker AIO](Docker%20AIO/README.md) (all three +in a single image). ## Layout diff --git a/Web App/README.md b/Web App/README.md index 567c002..c7f3b5e 100644 --- a/Web App/README.md +++ b/Web App/README.md @@ -38,7 +38,9 @@ web/ Vue 3 + Vite + Tailwind v4 source ## Requirements -- Node 18+ and Go 1.26+ +- **Node 20.19+ or 22.12+** (Vite 8's floor — Node 18 is end-of-life and will not + build) and **Go 1.26+** (the `go.mod` directive). The Docker image builds on + `node:22-alpine`. - A running **API Server** (see `../API Server`) ## Develop