Pi Car Companion
A local-first Raspberry Pi companion for the MG4. The Pi hosts a secure dashboard while the existing Android infotainment system remains the display and touch interface.
The current MVP slice includes:
- atomic first-run administrator creation with no default credentials;
- Argon2id password hashing and revocable, hashed server-side sessions;
- CSRF, trusted-origin, rate-limit, and secure-header protections;
- SQLite persistence and append-oriented authentication audit events;
- real host, CPU, memory, disk, network, uptime, and service status;
- field-level unavailable states when host data cannot be collected;
- authenticated Server-Sent Events for live status updates;
- allowlisted refresh-health, offline network-diagnostics, and sanitized support-bundle jobs;
- disabled-by-default, serial-pinned ADB status with read-only head-unit identity and display collection;
- a responsive 1920×720-oriented setup, login, and dashboard UI;
- an idempotent Raspberry Pi installer with systemd start-on-boot support;
- atomic, dashboard-triggered Git updates with automatic verification and rollback safety;
- automated API security tests and reproducible quality commands.
The ADB transport, job UI, artifacts, Android companion, and remaining operations work are tracked in PLAN.md.
Requirements
- Node.js 22
- npm 10 or newer
- Linux, macOS, or Windows for development
Raspberry Pi OS Lite / Debian arm64 is the production target but has not yet been hardware-validated.
Raspberry Pi installation
On a clean Raspberry Pi OS or Debian installation:
git clone https://git.molberg.cloud/alex/pi-car-companion.git
cd pi-car-companion
./install.sh
The script asks for sudo access and then:
- installs Node.js 22, Git, Avahi, SQLite, and native build requirements;
- creates a dedicated
pi-companionservice account; - builds and verifies the server and dashboard;
- installs an atomic release under
/opt/pi-car-companion; - stores application data under
/var/lib/pi-car-companion; - stores root-managed configuration under
/etc/pi-car-companion; - enables the companion and Avahi services at boot;
- starts the service and verifies
/healthz.
The installer prints the final dashboard address, normally http://<pi-hostname>.local:8787. Open it and create the first administrator account.
install.sh is idempotent. After pulling changes in the original clone, run it again to deploy that checkout:
git pull --ff-only
./install.sh
Dashboard updates
Open Settings and select Update now. The Pi will:
- fetch the configured branch from
origin; - refuse non-fast-forward history changes;
- create an isolated release worktree;
- install dependencies and run script checks, lint, type checks, tests, and builds;
- atomically activate the release only after every check passes;
- restart the background service and verify its health endpoint;
- automatically restore the previous release if startup verification fails.
If any step fails, the current release stays active and Settings shows the failure. Updates use the Git credentials of the local user who ran install.sh; credentials are not copied into the web service or repository configuration. Non-interactive credentials such as an SSH deploy key or cached HTTPS credential work best.
Useful service commands:
sudo systemctl status pi-car-companion
sudo journalctl -u pi-car-companion -n 100 --no-pager
sudo journalctl -u pi-car-companion-update -n 100 --no-pager
Development
Install dependencies:
npm install
Start the API in one terminal:
npm run dev
Start the Vite dashboard in another:
npm run dev:web
Open http://127.0.0.1:5173. The development server proxies API requests to http://127.0.0.1:8787.
Local development data is stored in server/data/companion.db and ignored by Git.
Quality gate
Run every host-runnable check:
npm run check
This runs ESLint, strict TypeScript checks, tests, and both production builds.
Audit production dependencies separately from development tooling:
npm audit --omit=dev
Production-mode preview
Build and start the combined server/dashboard:
npm run build
NODE_ENV=production \
DATABASE_PATH=./server/data/companion.db \
ALLOWED_ORIGINS=http://127.0.0.1:8787 \
npm start --workspace @pi-car/server
Then open http://127.0.0.1:8787.
The development default remains loopback-only. install.sh explicitly enables LAN access so the car can reach the dashboard.
Configuration
See .env.example. Configuration is validated at startup.
Important values:
HOST: listen address, defaults to127.0.0.1PORT: HTTP port, defaults to8787DATABASE_PATH: SQLite database locationSESSION_TTL_HOURS: session lifetime, defaults to 24 hoursCOOKIE_SECURE: enable for HTTPS deploymentsALLOWED_ORIGINS: comma-separated exact browser origins accepted for state changesUPDATE_STATUS_PATH: update state shared with the systemd update unitVERSION_FILE: revision metadata for the active atomic releaseADB_ENABLED: enables the read-only head-unit collector; defaults tofalseADB_SERIAL: exact device serial used for every ADB command when enabledADB_PATHandADB_TIMEOUT_MS: executable path and per-command timeout
Read-only ADB setup
ADB integration is optional and disabled by default. Every command is pinned to one
configured serial, uses a fixed server-side argument list, and has a timeout and 16 KiB
output limit. It currently reads connection state, selected getprop identity fields,
and wm size/wm density only.
On a deployed Pi, rerun install.sh once so android-tools-adb is present and the
service account receives the standard plugdev device-access group. Connect the head
unit, then establish authorization as the service account:
sudo -u pi-companion -H adb devices
Accept the authorization prompt on the parked infotainment screen. Set these values in
/etc/pi-car-companion/companion.env and restart the service:
ADB_ENABLED=true
ADB_SERIAL=<exact serial shown by adb devices>
If the device is disconnected, offline, unauthorized, partially readable, or ADB is missing, the dashboard reports that state without substituting generated data. Do not add shell strings or state-changing commands to this collector.
Safety boundary
This project does not modify the MG4 firmware, launcher, vehicle controls, CAN bus, or safety systems. It does not expose arbitrary shell commands, SSH, ADB, the Docker socket, or unrestricted filesystem access through the dashboard.