Skip to content

v0.11.0

Choose a tag to compare

@joszz joszz released this 16 Oct 23:20
· 124 commits to main since this release
Immutable release. Only release title and notes can be modified.
5d774ae

Upgrade Notes

  • Four database migrations run automatically at API startup: AddParkedPosition, AddApiTokens, ReplaceGpsIndex and RestoreChartIndex. No action needed. Rebuilding the GPS index and restoring the statistics index (see Performance) each take a moment on a large telemetry table (#484, #485, #488)
  • All-in-one: PostgreSQL authentication is tightened on the first start. The embedded database trusted every local connection, so any process in the container, the gateway included, could open a superuser session. On the first start the role's password is hashed again as SCRAM from POSTGRES_PASSWORD, and the database then takes peer on its socket and scram-sha-256 over TCP. It runs once, and nothing has to be done (#465)
  • AUTH_COOKIE_SECURE has a new auto value, now the default in Compose, the all-in-one image and the Unraid template. It marks the session cookie Secure whenever the browser comes in over HTTPS, through a reverse proxy too, and plain HTTP still signs in. true and false mean what they did. An existing Unraid container keeps the false it was created with: set it to auto if you reach GarageStack over HTTPS (#465)
  • Push notifications no longer send your MG login email to Google, Mozilla and Apple: the VAPID contact used to default to mailto: plus SAIC_USER. The new VAPID_SUBJECT sets it; left blank, it is CORS_ORIGIN when that is https, and a placeholder otherwise. Compose now passes CORS_ORIGIN to the Worker too, since the Worker signs the pushes (#465)
  • Two new notification types are on by default: Slow puncture and Moved while parked. Untick them per device under Settings > Notifications, or set TYRE_PRESSURE_DROP_BAR to 0 to switch the slow-puncture alert off for everyone. Moved while parked starts once the car has parked on this version, since only then is its parking spot kept (#483, #484)
  • Compose: update with git pull and docker compose up -d --build to pick up the new wiring: the API mounts the Worker's log volume read-only for the log viewer, the Worker gets CORS_ORIGIN, the broker gets a health check the gateway waits for, every container's console log rotates (3 x 10 MB), and MAP_STYLE_* reach the frontend build from .env. A compose file of your own needs WORKER_LOG_DIRECTORY and that mount for the log viewer to show the Worker; without them it shows the API alone (#465, #466, #472, #487)
  • The Unraid template has three new fields: VAPID_SUBJECT, TYRE_PRESSURE_DROP_BAR and LOG_RETAINED_FILES, and AUTH_COOKIE_SECURE defaults to auto. They are all optional, so an existing container keeps running; update the template to see them (#465, #466, #467, #483)

Features

  • Read-only API tokens. A script, a dashboard or a watch can read GarageStack's data without signing in: make a token under Settings > API tokens and send it as Authorization: Bearer gs_.... A token reads as the account that made it and can only read: commands, changes and the token endpoints answer 403, so a leaked token can neither send a command nor make more tokens. The token is shown once, only its hash and first nine characters are stored, and an account holds up to 20 (#485)
  • Full data export. Settings > Your data downloads one zip with everything GarageStack keeps: your settings and notifications and, per car, the car, its trips, maintenance, climate schedules, home and work places, and every telemetry row as a CSV a spreadsheet opens. A read-only API token can download it too. Five downloads per quarter hour (#486)
  • Log viewer. The new Logs page (in the sidebar, after Maintenance) shows the newest entries of the API's and the Worker's log files, newest first, filtered by service, lowest level and text. An exception's lines fold away under their entry, and Live reads again every ten seconds (#487)
  • Tyre history and a slow-puncture alert. Tap a tyre pressure on the car diagram to see a month of tyre history and how each tyre is doing against the other three. A tyre that falls TYRE_PRESSURE_DROP_BAR (0.15 bar) behind the others over two weeks sends a Slow puncture alert, at most once every three days. Comparing with the other three tyres cancels out the weather, so a slow leak shows before the tyre is low. It needs a week of days on which all four tyres reported (#483)
  • Moved while parked. An alert when a parked car reports a position more than 300 m from where it parked while the engine is off: towed, lifted onto a truck or taken on a ferry. Once per parking. A sleeping car only reports now and then, so the alert comes with the car's first report after the move (#484)
  • Commands for plug-in cars, each in the card that shows its state: a charge target slider (40 to 100%, on cars that take one), start and stop charging, lock and unlock the charging cable, battery heating on and off, and open the boot. Unlocking the cable and opening the boot take a second tap. A command counts as done once the car reports the change, not on the gateway's "Success" alone, and a charge target the gateway would silently drop is refused up front (#462)
  • Charging and battery heating schedules. The charging session card changes the car's charging schedule (until a set time, until the charge target, or off, with start and end times), and the battery heating card switches its schedule and sets its start time. The times are in the car's own time zone (#463)
  • Browser errors reach the API log. An error in the web app used to show only in that browser's console. While someone is signed in it is now reported to the API log too, each distinct message once and at most ten per page load (#467)
  • Push on plain HTTP explains itself: Settings > Notifications says push needs an https:// address, instead of leaving the push switch out (#466)
  • The demo carries commands out. Each command changes the demo car two seconds later, where every button used to wait out the 45-second timeout. Charging with the cable unplugged is refused with a reason, so the demo shows a refusal too (#482)
  • A day's log file rolls over at 100 MB instead of stopping at 1 GB, and LOG_RETAINED_FILES (30) sets how many files each service keeps (#467)

Bug Fixes

  • Plain-HTTP installs load again. Since v0.7.0 the page stayed blank on http://<LAN address>:8080, the address the README and the Unraid WebUI link use: the Content-Security-Policy made the browser fetch the app's own scripts over https. Browsers exempt localhost, and a reverse proxy that sets its own policy hides it, so it went unnoticed. A browser test now opens the demo over plain HTTP on a host name (#464)
  • Plug-in cards on a first visit. A new account with a plug-in car never showed the charging session and battery heating cards. A first visit now takes the drivetrain's defaults and saved layouts are untouched, so if you never saw these cards, switch them on in edit mode (#462)
  • Maintenance: a refused delete closed the dialog as if it had worked, and a refused service log delete showed nothing; both now stay open and say why. Add is disabled until the car is known, instead of doing nothing, and an item that was never serviced has the same colour everywhere (#464, #468, #481)
  • Notifications: a failed load read "No notifications"; the panel now gives the reason and a Try again button, and a failed action names its reason instead of "Error" (#464)
  • Quieter logs during an outage. While the broker or PostgreSQL was down, the Worker and the API logged an error with a stack trace every 5 seconds, over 700 an hour. Now it is the first failure in full, a reminder every 5 minutes, and the recovery with how long it took (#467)
  • A malformed payload on a known topic is logged as such instead of as an unknown topic, an unknown topic warns once instead of on every poll, and an OpenChargeMap rate limit logs one warning instead of every failure twice (#467)
  • Keyboard and screen readers: trips in the trip list can be selected from the keyboard; dialogs and the notification panel take focus when they open, keep Tab inside and hand focus back when they close; Escape closes the mobile menu, the notification panel and the install prompt; the remember-me switch on the login page has a name (#470, #471, #481)
  • The map's loading pills spin again (#464)
  • command.unsupported and trip.tooManyIds refusals have real messages instead of "bad request" (#464)
  • The API refuses NaN and Infinity in map queries (NaN used to fetch and cache tile 0,0), and refuses a maintenance note over 1000 characters instead of failing with a 500 (#464)
  • The statistics tyre legend is translated (LV, RV, LA and RA in Dutch) (#468)
  • The demo controls show speed and temperature in your own units (#471)
  • The Unlock button in the doors card has its warning style, and a link drawn as a button is no longer underlined (#462, #486)
  • On a day when midnight repeats at a clock change, statistics start the day at the first midnight instead of the second (#469)

Security

  • Your VIN, email and push addresses are out of the logs. Every /api/vehicles/{vin}/... request logged the full VIN; it now reads ***0001. Sign-in lines shorten an email-shaped name, a failed push logs only the push service's host, and the POI cap warning rounds its area to about 10 km (#465)
  • Push subscriptions must be an https URL on a public host name and the default port, without credentials, an IP address or a LAN-only suffix, and their lengths are capped. The Worker posts to that URL, so any signed-in user could make it call an address on your network (#465)
  • Rate limits behind an outer proxy. Only one forwarded hop was unwrapped, so behind Traefik or Nginx Proxy Manager every visitor got the outer proxy's address and shared one login limit. Trusted hops are now unwrapped all the way (#465)
  • All-in-one: PostgreSQL no longer trusts local connections (see Upgrade Notes), the image no longer ships the ssl-cert package's private key, and a local .env can no longer reach the build (#465)
  • Home Assistant discovery only creates a vehicle for a VIN, so other devices announcing themselves on the broker no longer do (#465)
  • The frontend's nginx no longer names its version (#465)
  • Browser error reports are accepted from a signed-in session only, rate limited, capped and kept on one line, so a report cannot forge log entries (#467)
  • CI: every GitHub Action is pinned to a commit SHA, auto-merge checks the pull request's author instead of whoever triggered the run, and image publishing runs with read-only default permissions (#465)

Performance

  • Trip routes read faster. The GPS index is now in time order: a week of fixes takes 0.4 ms instead of 2.2 ms and 90 days 3.1 ms instead of 8.8 ms on a real install's data, and the index is smaller (#488)
  • Statistics get their index back. A migration in v0.3.0 dropped it, so on most installs statistics read every telemetry row. It is built again once (#488)
  • POI pre-caching asks each car for its newest fix, instead of running a query per GPS row (146 ms on a real install, and growing with every fix) and loading whole telemetry rows (#471, #488)
  • The topics endpoint (/api/vehicles/{vin}/topics) reads the last 7 days instead of every row the car ever sent (#471)
  • The Worker image is 225 MB instead of 252 MB (#472)
  • Only the hashed /assets/ files are cached for a year. Icons and screenshots keep their names across releases, so they are cached for a day and a new icon shows up (#472)

Documentation

  • The README gains HTTPS (what plain HTTP cannot do), reverse proxy examples for Caddy, Traefik and Nginx Proxy Manager, upgrade notes on migrations and PostgreSQL majors, API tokens with a curl example, and the log viewer. Its feature list, alert table and requirements (Docker 23, Compose 2.20) are current (#466, #483, #484, #485, #487)
  • CONFIGURATION.md covers the build-time variables (MAP_STYLE_*, VITE_API_URL, VITE_DEMO_MODE), the variables outside Docker and every new setting, and corrects AUTH_SESSION_LIFETIME_HOURS, POSTGRES_PASSWORD and MQTT_HOST (#465, #466, #467, #483, #487)
  • The cosign verify command matches the certificate's joszz/GarageStack (#464)
  • CONTRIBUTING.md explains adding a language and the coverage commands, AUTHENTICATION.md covers API tokens, and DEMO.md, ARCHITECTURE.md, RELEASING.md and the all-in-one README are current (#466, #468, #472, #482, #485)

Internal

  • Test coverage is measured for the first time (backend 79.8%, frontend 59.7%) and shown in CI, with tests where the review found none, among them the service worker, which is now type-checked too (#468)
  • Duplicated code folded together: one POI tile pipeline, one clock-change rule, helpers for store actions and command drafts, composables for dialog focus and Escape, and repositories for maintenance, notifications and push (#469, #470, #481)
  • main.css goes from 2421 to about 1790 lines, split per view, with z-index tokens; screenshots of every view before and after are pixel-identical (#481)
  • CI: a project-rules job fails on em dashes, a new infra-lint job runs shellcheck, hadolint and actionlint, pull requests no longer build every image twice, every job has a timeout, and the e2e job caches its browser (#464, #472)
  • Dead code removed: an unused telemetry query, the demo push sender and the API's memory cache (#471)
  • OpenChargeMap and Overpass requests name the project in their user agent, as the other upstream services already did (#469)

Dependencies

  • Vue 3.5.44, vue-i18n 11.4.14, maplibre-gl 6.14.0 and Scalar.AspNetCore 2.18.0 (#473, #474, #479)
  • The frontend image runs on nginx:1.31-alpine and the Worker image on dotnet/runtime:10.0 (#472)
  • Development tools: @vitejs/plugin-vue 6.0.10, vue-tsc 3.3.13, oxlint 1.88, Prettier 3.9.10, @types/node 26.6.5, @vitest/coverage-v8 5.0.4, xunit.v3 4.0.2 and xunit.runner.visualstudio 4.0.1. npm-run-all2 and vite-plugin-vue-devtools are removed, vite-plugin-pwa is a dev dependency, coverlet gives way to Microsoft.Testing.Extensions.CodeCoverage, and the undici override is gone (#464, #468, #472, #473, #475, #476, #477, #480)

Full Changelog

v0.10.0...v0.11.0: v0.10.0...v0.11.0