Skip to content

Latest commit

 

History

457 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

quicklogger

Mobile-first PWA for logging fuel fill-ups to a self-hosted LubeLogger instance.

Status: v0.3.3 — stable. Single-user homelab tool, daily-driven. Public repo so anyone can fork and self-host.

Built with Claude Code

This project was developed with Claude Code — design, implementation, tests, infra, and docs were paired with Anthropic's coding assistant. Code is reviewed and decisions are made by a human; the assistant did the typing.

Why

LubeLogger's web UI is great for review and analytics, but entering a fill-up at the gas pump from a phone is fiddly. quicklogger is a one-form, install-as-PWA front door optimised for the pump:

  • Auto-selects the last vehicle
  • Volume in gallons or liters, cost in any major currency — converted server-side
  • Live MPG-since-last-fill preview as you type
  • Last-fillup strip above the form + odometer prefill + one-tap +300 mi chip — see docs/user/odometer-prefill.md
  • Offline queue that auto-syncs when signal returns
  • iOS Shortcut integration (voice + deep-link)
  • Stays on your network — backend talks to LubeLogger over the internal Docker network, not the public internet

Screenshots

Log Fuel History Maintenance
Log Fuel form History Maintenance reminders
Vehicles Settings Photo Preview
Vehicle picker Settings Photo preview before OCR
Stats
Per-vehicle Stats

Quick start

The fastest path to running quicklogger — standalone container, single host, no other services on the network. Five commands:

git clone https://github.com/varunpan/quicklogger.git
cd quicklogger
cp compose.example.yml docker-compose.yml
# Edit docker-compose.yml — set LUBELOGGER_URL to your LubeLogger container/host,
# and ORIGIN to the URL you'll actually open the app from (see note below)
cp .env.example .env
# Edit .env — set LUBELOGGER_API_KEY
docker compose up -d

quicklogger now serves on port 3000. Open http://<your-host's-LAN-IP>:3000 on your phone (same network — a phone can't resolve localhost on another machine), confirm your fleet appears under Vehicles, log a small dummy fill, and you're done.

ORIGIN must match the URL you open the app from. SvelteKit rejects a mismatched Origin on submit with a 403 — GETs (like the Vehicles check above) still work, so a wrong ORIGIN looks like a bad API key until you try to log a fill. See Reverse proxy below for the HTTPS-fronted version of this.

For deployment behind a reverse proxy alongside an existing LubeLogger stack, see Self-hosting below.

Self-hosting

Prerequisites

  • Docker (any host with docker compose)
  • A running LubeLogger instance with an Editor-scope API key (LubeLogger → Settings → API Keys)
  • A way to expose HTTPS to the phone you'll log from (Traefik, Caddy, Cloudflare Tunnel, Tailscale Funnel, etc.). Plain HTTP works on a LAN, but iOS won't install the PWA.

Alongside an existing LubeLogger stack

If you already run LubeLogger in a docker compose stack, drop quicklogger in next to it. Talking to LubeLogger over Docker DNS skips a public network round-trip:

services:
  quicklogger:
    image: ghcr.io/varunpan/quicklogger:latest
    container_name: quicklogger
    restart: unless-stopped
    environment:
      - LUBELOGGER_URL=http://<lubelog-service>:8080 # whatever your LubeLogger service is named on this network
      - LUBELOGGER_API_KEY=${LUBELOGGER_API_KEY} # in your stack's .env
      - LUBELOGGER_VOLUME_UNIT=gallons_us
      - LUBELOGGER_DISTANCE_UNIT=miles # or km — match your LubeLogger instance
      - LUBELOGGER_CURRENCY=USD
      - ORIGIN=https://quicklog.example.com # your public/internal URL
      - PORT=3000
    volumes:
      - /srv/quicklogger/data:/data # bind-mount for the FX cache
    # Runtime hardening — see docs/deployment.md § "Hardening the runtime"
    read_only: true
    tmpfs:
      - /tmp:rw,size=16m,mode=1777
    cap_drop: [ALL]
    security_opt: ['no-new-privileges:true']
    pids_limit: 100
    mem_limit: 256m
    networks:
      - <same-network-as-lubelogger>

Append LUBELOGGER_API_KEY=<key> to the stack's .env. Then docker compose up -d quicklogger — only that service starts, the others are untouched.

Reverse proxy

The image listens on plain HTTP :3000. Front it with HTTPS. Traefik label snippet (internal-only host):

labels:
  - traefik.enable=true
  - traefik.http.services.quicklogger.loadbalancer.server.port=3000
  - traefik.http.routers.quicklogger.rule=Host(`quicklog.example.com`)
  - traefik.http.routers.quicklogger.entrypoints=websecure
  - traefik.http.routers.quicklogger.tls=true

For Caddy, nginx, or Cloudflare Tunnel: same idea — proxy https://quicklog.example.comhttp://quicklogger:3000.

Set ORIGIN to your public URL. SvelteKit uses it for CSRF protection on POSTs; a mismatched ORIGIN returns 403 on submit.

First run

  1. Open https://quicklog.example.com on your phone.
  2. iOS: Share → Add to Home Screen.
  3. Tap Vehicles → confirm your fleet from LubeLogger appears.
  4. Go back to Log fillup, enter a small dummy fill, submit. Confirm it lands in LubeLogger.

Security posture

Defaults intended to be reasonable for a single-user homelab tool. The deeper write-up lives in docs/deployment.md § Hardening the runtime — short version:

  • No app-side auth. quicklogger has no login screen. Front it with HTTPS and either keep it on a private network (Tailscale, LAN, an internal-only hostname) or put it behind a forward-auth middleware (Authentik, Cloudflare Access, etc.).
  • CSRF / origin check. Mutating API requests (/api/fuelup, /api/ocr, /api/log) are rejected with a 403 if they arrive with a browser Origin that doesn't match your configured ORIGIN — defense-in-depth beyond SvelteKit's form-only default, so a cross-site application/json POST is covered too. Requests with no Origin (Apple Shortcuts, server-to-server) are unaffected.
  • Container runs as node (UID 1000), not root.
  • Image is multi-stage — runtime layer has only the built build/ output, prod-only node_modules, and package.json. No build tools, no source.
  • Image is vulnerability-scanned — every release build is scanned with Trivy and fails on fixable critical/high CVEs before it's published. The base image's OS packages are upgraded and its unused npm is stripped at build time. See docs/deployment.md § Vulnerability scanning.
  • Recommended compose hardening (in both compose patterns above): read_only: true, cap_drop: [ALL], security_opt: [no-new-privileges:true], pids_limit: 100, mem_limit: 256m, plus a 16 MB tmpfs for /tmp. Verified per-release.
  • Secrets surface: LUBELOGGER_API_KEY (Editor-scope on your LubeLogger). Sits in .env, never logged. If it leaks, rotate it in LubeLogger.
  • What's still your responsibility: rate-limiting / WAF in front (CrowdSec, Traefik middlewares); TLS cert management; network segmentation; LubeLogger's own threat model.

Configuration

Minimum vars to run:

Var Required Default Purpose
LUBELOGGER_URL yes URL of your LubeLogger (use container DNS if same network)
LUBELOGGER_API_KEY yes Editor-scope API key from LubeLogger
LUBELOGGER_CURRENCY no USD Target currency for storage
ORIGIN no SvelteKit CSRF origin (set to your public URL)
PORT no 3000 App listen port

For the full reference (every var, type, default, override scenarios), see docs/user/configuration.md.

Logging

Structured JSON to stdout by default. Set LOG_FILE_PATH to also write a rotating logfile. Full reference in docs/user/configuration.md and the internals in docs/technical/logging.md.

Development

Dev prerequisites

  • Node 24 (pin via nvm or asdf)
  • npm 10+
  • A reachable LubeLogger for integration testing — any of:
    • The LubeLogger you already self-host
    • A throwaway one: docker run --rm -p 8080:8080 ghcr.io/hargata/lubelogger:latest
  • Docker + docker compose — only for the compose.dev.yml prod-mirror UAT (not needed for the npm dev loop)

Tech stack

The app ships a single runtime npm dependencyrotating-file-stream (log rotation); everything else is devDependencies that get bundled into the production artifact at build time. Exact versions live in package.json; the categories below are the load-bearing pieces.

Layer Package Purpose
Framework @sveltejs/kit ^2.68 Full-stack framework (file-based routing, SSR, server endpoints)
svelte ^5.56 UI framework (runes-mode component model)
@sveltejs/adapter-node ^5.5 Production adapter — emits a node build entrypoint
Build vite ^8.0 Dev server + production bundler
@sveltejs/vite-plugin-svelte ^7.0 Vite integration for Svelte (HMR, compilation)
typescript ^6.0 Type system
svelte-check ^4.6 Svelte/TS type-checker
Styling tailwindcss ^4.2 + @tailwindcss/vite Utility-first CSS via Vite plugin
Client state idb ^8.0 Promise-based IndexedDB wrapper for the offline submission queue
Unit / integration tests vitest ^4.1 + @vitest/coverage-v8 Test runner + coverage
@testing-library/svelte ^5.3 + @testing-library/jest-dom ^6.9 Component testing + DOM matchers
msw ^2.14 Mock LubeLogger upstream in route-handler tests
jsdom ^29.1 Browser DOM shim for Node-side unit tests
fake-indexeddb ^6.2 IndexedDB shim for tests of the offline queue
E2E tests @playwright/test ^1.60 Mobile-Safari profile against the production build
Lint / format eslint ^10.5 + @eslint/js ^10.0 Linter (flat config)
eslint-plugin-svelte ^3.19 + svelte-eslint-parser ^1.6 Svelte ESLint integration
typescript-eslint ^8.61 TypeScript-aware ESLint rules
prettier ^3.8 + prettier-plugin-svelte ^4.1 Code formatter
Runtime node:24-alpine (Docker) Dockerfile switches to the unprivileged node user (UID 1000) via USER node before the app starts

Setup

git clone https://github.com/varunpan/quicklogger.git
cd quicklogger
npm install
cat > .env <<EOF
LUBELOGGER_URL=http://localhost:8080
LUBELOGGER_API_KEY=<your key>
EOF
npm run dev   # http://localhost:5173

Scripts

Command Purpose
npm run dev Vite dev server with hot reload (localhost only)
npm run dev:lan Same, exposed on the LAN — for testing on a real phone
npm run build Production build (adapter-node → build/)
npm run preview Run the production build locally
npm run preview:lan Same, exposed on the LAN — for testing the production bundle on a real phone
npm run uat Production-mirror server (node --env-file=.env build) — rebuilds until precompressed assets are complete, smoke-tests, then prints the URL. macOS onlyscripts/uat.sh detects the LAN IP via ipconfig getifaddr; on Linux it exits 1 after a successful build. Use npm run uat:docker instead.
npm run uat:docker Build + run the real production image locally (compose.dev.yml) — prod-mirror UAT with the service worker live on localhost
npm test Vitest — unit + route handler tests
npm run test:watch Vitest watch mode
npm run test:e2e Playwright (mobile-Safari profile)
npm run lint ESLint flat config
npm run check svelte-kit sync + svelte-check
npm run format Prettier across the tree (config in .prettierrc)
npm run format:check Prettier in check mode — the CI format gate

Testing layers

  • Vitest (unit + integration)src/**/*.test.ts. Server modules (env, currency, lubelogger client, FX cache) and SvelteKit route handlers (with MSW mocking the LubeLogger upstream) are covered here.
  • Playwright (E2E)tests/e2e/*.spec.ts. One mobile-Safari profile to match the target device. The service worker is set to block per-spec so Playwright route mocks aren't intercepted.

Testing on a real phone before release

Local dev server, real phone, same WiFi:

  1. Find the dev machine's LAN IP:

    ipconfig getifaddr en0          # macOS, Wi-Fi
    hostname -I | awk '{print $1}'  # Linux
  2. Run the dev or preview server on the LAN:

    npm run dev:lan        # http://<lan-ip>:5173 — hot reload
    # or, to test the production bundle:
    npm run build && npm run preview:lan   # http://<lan-ip>:4173
  3. On the phone (same WiFi), open http://<lan-ip>:5173 (or :4173) in Safari.

Caveats:

  • This is plain HTTP. iOS won't let you "Add to Home Screen" as a real PWA, and the service worker won't activate — so offline-queue behaviour is unverifiable this way. Use it for layout, touch interactions, form flow, and live FX preview. For PWA install + service-worker testing, use the prod-mirror container below — localhost is a secure context so the SW registers with no deploy (front it with a reverse proxy for on-device HTTPS).
  • LUBELOGGER_URL in .env must be reachable from the dev machine. If your LubeLogger is on the same network the dev machine is on, point at it directly (https://lubelogger.example.com). Otherwise, run a throwaway LubeLogger locally and point at http://localhost:8080.
  • Don't pollute your real fuel log — when testing against a live LubeLogger, create a dedicated TEST – DELETE ME vehicle and submit fillups against that. Clean it up periodically.

Prod-mirror via Docker (compose.dev.yml)

To test the real production image locally — including the service worker and PWA install, which the plain-HTTP preview above can't exercise — run:

docker compose -f compose.dev.yml up --build

On http://localhost:3000 the service worker registers (localhost is a secure context), so offline/PWA behaviour is testable in a desktop browser with no deploy. For on-device phone testing over HTTPS, set the TRAEFIK_* + ORIGIN vars in .env — see docs/deployment.md § Dev prod-mirror compose.

Architecture pointers

User guides:

Technical / internals:

Operations:

Contributing

PRs welcome. The repo is small enough to read in one sitting:

  1. Open an issue describing the change before large work — especially anything that touches the server ↔ LubeLogger contract or the mobile form layout.

  2. Branch from main. Conventional-commit-style messages preferred (feat:, fix:, chore:, docs:).

  3. Lint + check + test must pass locally and in CI before merge:

    npm run lint && npm run check && npm test && npm run build
  4. Branch protection on main requires a green lint-and-test check and a PR (no direct pushes).

  5. For changes that touch visible UI, run through docs/uat.md on a real phone before requesting review.

License

MIT — see LICENSE.

About

Mobile-first PWA for logging fuel fillups to self-hosted LubeLogger

Resources

Stars

25 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages