All apps · 0 apps
GasMoney
Docker app from mmenanno's Repository
Overview
Readme
View on GitHubGas Money
A self-hosted, single-screen calculator that estimates the gas cost of any trip you've driven (or are about to drive). Feed it your GasBuddy CSV exports and it works out per-trip cost from the fuel-economy and pump-price values bracketing your trip date.
Screenshots
![]() |
![]() |
![]() |
![]() |
What it does
- Estimate a trip cost by vehicle, date, and kilometres. Pick whether it's a round trip and the calculator doubles the distance.
- Saved trips: name a route once (e.g. "Commute", "Cottage"), pick it from a dropdown to pre-fill the form for any vehicle / date.
- Vehicle management: add the vehicles you actually drive; pin a subset to the dashboard for the at-a-glance "$/km latest" + "$/km 5-fillup average" tiles.
- CSV import of GasBuddy exports with row-level dedup on
(vehicle, timestamp, odometer, quantity)so re-importing the same file is a no-op. - Auto-sync from GasBuddy (optional): once a day at midnight UTC plus a manual button. Bundles a headless Chromium that drives the actual login form (Cloudflare's JS challenge solves naturally), captures the resulting cookies, and uses them for plain Faraday GraphQL calls. New entries import automatically; existing manually-imported fillups that match an incoming entry are linked rather than duplicated.
- Backfill prior years: a separate button on the Sync page that walks GasBuddy year-by-year back to 2010, stopping after two consecutive empty years. Useful for the first run on a long-existing GasBuddy account; subsequent regular syncs only need to keep the current year up to date.
- Manual fillup entry: each vehicle has a per-vehicle fillups page where you can add a row by hand (same shape as a CSV row). Useful for filling gaps that GasBuddy doesn't have. Each ledger row carries a small provenance glyph —
↻for fillups synced from GasBuddy,✎for ones added manually or via CSV import. - History: every estimate is saved with its math (litres × $/L, fuel economy, calc method) and can be deleted from the dashboard.
How the math works
For a trip on date D with vehicle V and K kilometres, the calculator picks fuel-economy (L/100km) and unit price (¢/L) values via one of four strategies:
exact—Vhas a fillup whose date matchesD. Use that fillup's values directly.between—Dis between two fillups forV. Average the L/100km and ¢/L from the closest fillup before and afterD(skipping any rows flaggedmissingPreviousfor fuel economy).after_latest—Dis later than every fillup forV. Use the most recent fillup with a real fuel-economy reading.before_earliest—Dis earlier than every fillup. Use the earliest fillup with a real fuel-economy reading.
Cost = (L/100km × kilometres ÷ 100) × (¢/L ÷ 100). Stored to the cent.
The "5-fillup average" tile averages cost-per-km across the most recent five fillups that have a real L/100km value (skipping partial fills).
Run it
Docker (recommended)
docker run -d \
--name gasmoney \
-p 9292:9292 \
-v "$(pwd)/state:/app/state" \
ghcr.io/mmenanno/gasmoney:latest
Open http://localhost:9292. The SQLite database lives in /app/state/gasmoney.sqlite3 inside the container; the bind mount above keeps it on the host across upgrades.
If your host's appdata is owned by a non-1000 user, set PUID / PGID on the container — the entrypoint adjusts the in-container app user to match before dropping privileges.
docker-compose
services:
gasmoney:
image: ghcr.io/mmenanno/gasmoney:latest
container_name: gasmoney
restart: unless-stopped
ports:
- "9292:9292"
volumes:
- ./state:/app/state
# environment:
# PUID: "1000"
# PGID: "1000"
# UMASK: "022"
From source (development)
bundle install
bundle exec rackup -p 9292 -o 127.0.0.1
The first boot creates db/gasmoney.sqlite3 and an db/encryption.key file (mode 0600) used to encrypt at-rest credentials. Use the Vehicles page to add your own and pin the ones you want on the dashboard, then Import logs to load a GasBuddy CSV.
Local env via dotenvx
For local testing of the auto-sync code (which spawns a real Chromium and talks to GasBuddy), use dotenvx so the .env file lives encrypted on disk:
brew install dotenvx
cp .env.example .env
# fill in real values, then encrypt in place:
dotenvx encrypt
# .env now stores ciphertext; .env.keys holds the local decryption key.
# Run anything via dotenvx to inject the decrypted values:
dotenvx run -- bundle exec rackup -p 9292 -o 127.0.0.1
Both .env and .env.keys are gitignored. Production deployments set the env vars directly on the container — dotenvx is dev-only.
Local Docker dev loop
Chromium-environment bugs only reproduce inside the production image, so debugging through GHCR + Unraid is too slow. bin/docker-dev runs the production Dockerfile locally with .env decrypted via dotenvx:
bin/docker-dev up # build + run, tail logs
bin/docker-dev shell # exec into the running container
bin/docker-dev sync # POST /sync/run against the local container
bin/docker-dev rebuild # --no-cache rebuild
bin/docker-dev down # stop + remove
Inside the container, bin/test-browser exercises just the Chromium login flow (skips the rest of the app), which is the fastest way to triage Ferrum::DeadBrowserError-class issues:
bin/docker-dev shell
GASBUDDY_USERNAME=… GASBUDDY_PASSWORD=… bin/test-browser
Importing fuel logs
- Export a CSV from GasBuddy (Account → Activity → Export).
- Vehicles → add the vehicle, pin it to the dashboard if you like.
- Import logs → pick the vehicle the CSV is for + select the file → Import.
The importer dedups on (vehicle_id, filled_at, odometer, quantity_liters), so re-importing the same file inserts zero rows. Re-import a CSV after appending new fillups and only the new rows insert.
Auto-sync from GasBuddy
GasBuddy is fully behind Cloudflare's challenge gate, so plain HTTP clients can't authenticate. Gasmoney bundles a headless Chromium and drives the login itself: the browser solves the JS challenge naturally, fills the React form, and hands captured cookies + User-Agent to a plain Faraday client for subsequent data calls. No external CF-bypass service is required.
Setup, on the Sync page:
- GasBuddy account — enter your username/email + password. Stored encrypted at rest (AES-256-GCM via ActiveRecord::Encryption).
- Refresh garage — fetches your GasBuddy vehicle list and persists it locally. The Vehicle linking table then populates so you can map each remote vehicle to a local one (or ignore ones you don't care about — sync skips them permanently). Re-run any time GasBuddy gains/loses a vehicle.
- Sync now — for each linked, non-ignored remote vehicle, fetches the current year's fuel logs and reconciles them. Cookies from the first run are reused for ~30 days (cf_clearance lifespan); after that the next sync launches a fresh browser. The main sync short-circuits with a friendly message if no vehicles are linked yet.
- Backfill — same as Sync now but walks year-by-year back to 2010 (stopping after two consecutive empty years). Run this once after the first link; regular syncs handle current-year updates from then on.
Per-entry behaviour during reconciliation:
- If a fillup already carries the GasBuddy entry's UUID, skip it.
- Else, look for a manually-imported fillup with no UUID that matches the remote entry by date (±36 h) and quantity (±0.5 L). If found, link it (no duplicate row). This is how CSV-imported data and auto-synced data coexist.
- Else, insert a fresh fillup with the GasBuddy UUID set.
Auto-sync runs at 00:00 UTC daily when enabled. Every run records a SyncRun row with counts and an ordered log; the Sync activity section on the page shows the last 10 runs and expands per-run logs for triage when something fails. Running syncs surface their latest log line live so backfill progress (per-year hit counts) is visible without expanding the run details.
Running tests
bundle exec rake test
Process-parallel via ActiveSupport::TestCase's parallelize(workers: :number_of_processors, with: :processes). Each worker boots its own in-memory SQLite database.
Versioning and releases
VERSIONis the single source of truth for the released version.- Bump
VERSIONand add a## [<new>]section toCHANGELOG.mdin the same PR. - CI's VERSION bump check enforces this for every release-relevant change (
*.md,LICENSE,.github/**,.githooks/**are exempt). - A local
pre-pushhook mirrors the same gate so you fail fast on your machine instead of waiting for CI. Activate it once per checkout:
This runsbin/setup-hooksgit config core.hooksPath .githooksand is safe to re-run. - On merge to
main, CI builds multi-arch images, pushes them to GHCR (ghcr.io/mmenanno/gasmoney), tags the git commitv<version>, and creates a GitHub Release whose body is the matching CHANGELOG section.
Media gallery
1 / 4Install GasMoney on Unraid in a few clicks.
Find GasMoney in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.
Categories
Related apps
Explore more like this
Explore allDetails
ghcr.io/mmenanno/gasmoney:latestRuntime arguments
- Web UI
http://[IP]:[PORT:9292]/- Network
bridge- Shell
bash- Privileged
- false
- Extra Params
--restart=unless-stopped
Template configuration
Application web UI.
- Target
- 9292
- Default
- 9292
- Value
- 9292
Persistent SQLite database (vehicles, fillups, saved trips, sync runs, GasBuddy credentials encrypted at rest) plus the auto-generated encryption key.
- Target
- /app/state
- Default
- /mnt/user/appdata/gasmoney
- Value
- /mnt/user/appdata/gasmoney
Host user id the container should run as. Default 99 matches Unraid's `nobody`.
- Default
- 99
- Value
- 99
Host group id the container should run as. Default 100 matches Unraid's `users`.
- Default
- 100
- Value
- 100
File-mode creation mask. 022 (default) = files 644, dirs 755.
- Default
- 022
- Value
- 022



