Files
2026-07-11 19:30:52 +02:00

214 lines
7.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Travel Monitor
A self-hosted real-time travel time dashboard. Shows how long it takes to drive from a fixed HQ location to multiple offsite destinations, with color-coded status based on current traffic conditions relative to a learned baseline.
Built for homelabs. Runs as a single Docker container.
---
## Features
- **Real-time routing** via pluggable provider (Google, Mapbox, HERE, OpenRouteService)
- **Baseline-relative status** — green/yellow/red/dark-red based on how much worse current traffic is vs. normal
- **Interactive map** with numbered pins, route display on click, and multiple map styles (Dark Matter, Voyager, Positron, OSM, Satellite)
- **Glanceable sidebar** with color-coded rows, travel time both ways, and delay percentage
- **Stats tab** — sortable table by time, distance, or delay
- **Location management** — add by address or coordinates, rename inline, drag to reorder, delete with confirmation
- **Favicon + tab title** update to reflect worst current status
- **Debug mode** — type `thisisdebug` anywhere on the page to unlock traffic simulation tools
- **Keyboard shortcuts** — `R` refresh, `19` jump to location, `Tab` switch tabs, `?` show shortcuts, `Esc` close
- **Resizable sidebar** — drag the edge, persists across reloads
- **Provider status indicator** — shows in header when any location fails to fetch, with error details
- **Rate limiting** on all API endpoints via slowapi
- **Mobile layout** — stacked map/list, bottom navigation, touch drag-to-reorder, collapsible map
- **Dark mode only** — as it should be
---
## Requirements
- Docker + Docker Compose
- API key for your chosen routing provider (see Routing Providers below)
---
## Setup
```bash
# 1. Install Docker on your LXC (if not already)
curl -fsSL https://get.docker.com | sh
# 2. Copy files to the LXC
scp -r travel-monitor/ root@<LXC_IP>:/opt/travel-monitor
# 3. SSH in and configure
ssh root@<LXC_IP>
cd /opt/travel-monitor
cp .env.example .env
nano .env
# 4. Build and start
docker compose up -d
# 5. Check logs
docker compose logs -f
```
Open `http://<LXC_IP>:<PORT>` in your browser.
---
## Configuration (`.env`)
| Variable | Default | Description |
|---|---|---|
| `PORT` | `8000` | Host port to expose |
| `APP_TITLE` | `Travel Monitor` | Title shown in header and browser tab |
| `HQ_NAME` | — | Friendly name for HQ shown in header (optional) |
| `HQ_ADDRESS` | — | HQ street address (geocoded on startup) |
| `HQ_LAT` / `HQ_LNG` | — | HQ coordinates (alternative to address) |
| `ROUTING_PROVIDER` | `openrouteservice` | See routing providers below |
| `ORS_API_KEY` | — | OpenRouteService key |
| `GOOGLE_API_KEY` | — | Google Maps key |
| `MAPBOX_API_KEY` | — | Mapbox key |
| `HERE_API_KEY` | — | HERE key |
| `REFRESH_INTERVAL` | `300` | Seconds between auto-refreshes |
| `THRESHOLD_YELLOW` | `1.20` | Ratio where green → yellow (20% worse than baseline) |
| `THRESHOLD_RED` | `1.50` | Ratio where yellow → red (50% worse) |
| `THRESHOLD_DARK_RED` | `1.70` | Ratio where red → dark red (70% worse) |
| `DATA_DIR` | `/data` | Path inside container for locations.json |
---
## Routing Providers
Set `ROUTING_PROVIDER` to one of the following and supply the matching API key.
| Provider | Value | Free tier | Traffic-aware | Notes |
|---|---|---|---|---|
| OpenRouteService | `openrouteservice` | 2,000 req/day | ❌ No | Returns static historical times. Fine for testing, not for real traffic monitoring. |
| Google Maps | `google` | ~$200/month credit | ✅ Yes | Best routing quality. Uses `departure_time=now` + `duration_in_traffic`. |
| Mapbox | `mapbox` | 100,000 req/month | ✅ Yes | Uses `driving-traffic` profile. Generous free tier. |
| HERE | `here` | 250,000 req/month | ✅ Yes | Uses `departureTime=now`. Good European coverage. |
**Recommendation:** Mapbox or Google for real traffic data. OpenRouteService only if you don't need live conditions.
---
## Status colors
| Color | Ratio | Meaning |
|---|---|---|
| 🟢 Green | < 1.20× | Normal — within 20% of baseline |
| 🟡 Yellow | 1.201.50× | Worsened — 2050% slower than baseline |
| 🔴 Red | 1.501.70× | Bad — 5070% slower |
| ⬛ Dark red | > 1.70× | Reconsider — more than 70% slower |
Baselines are learned from the first successful fetch per location. To reset a baseline, use the `⋯` menu on any location → Reset baseline.
---
## Data persistence
Locations and baselines are stored in `./data/locations.json` on the host, mounted into the container. Survives container restarts and rebuilds.
To manually reset a baseline without the UI, edit `./data/locations.json` and set `"baseline_seconds": null` for that entry.
---
## Rate limits
All API endpoints are rate-limited per IP via slowapi:
| Endpoint category | Limit |
|---|---|
| `GET /api/config`, `GET /api/locations` | 60/min |
| `GET /api/status` | 30/min |
| `POST /api/refresh` | 10/min |
| Write operations (add, rename, delete, reorder, reset) | 20/min |
| Debug (simulate-delay) | 20/min |
---
## Docker commands
```bash
docker compose up -d # start
docker compose down # stop
docker compose up -d --build # rebuild after code changes
docker compose restart # restart without rebuild
docker compose logs -f # follow logs
```
---
## Exposing via Caddy + Authentik
The app has no built-in authentication. For external access, put it behind a reverse proxy with forward auth.
### DNS
Add an A record: `travel.yourdomain.com` → your Caddy LXC IP.
### Authentik
1. Create a **Proxy Provider** (Forward auth, single application)
- External host: `https://travel.yourdomain.com`
2. Create an **Application** pointing at the provider
3. Add the application to your existing **Outpost**
4. Optionally add a policy to restrict access to specific users or groups
### Caddyfile
```caddy
travel.yourdomain.com {
header {
Strict-Transport-Security "max-age=31536000;"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin"
}
forward_auth http://<AUTHENTIK_OUTPOST_IP>:9000 {
uri /outpost.goauthentik.io/auth/caddy
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email X-Authentik-Uid
trusted_proxies private_ranges
}
reverse_proxy http://<TRAVEL_MONITOR_LXC_IP>:<PORT>
}
```
### Port binding
When running behind a reverse proxy, bind Docker to the Caddy LXC IP only (already set in `docker-compose.yml`):
```yaml
ports:
- "127.0.0.1:${PORT:-8000}:8000"
```
If Caddy runs on a different host than the Travel Monitor LXC, change `127.0.0.1` to the Caddy LXC's IP.
---
## Security notes
- The app has no authentication of its own — rely on the reverse proxy layer
- API keys are stored in `.env` on the host filesystem — secure the LXC accordingly
- The `simulate-delay` endpoint is a debug tool — unlock it in the UI by typing `thisisdebug`; it is not accessible from the browser by default and is only useful for verifying threshold logic
- All API endpoints are rate-limited to prevent API quota exhaustion
---
## Hardening checklist
- [x] Rate limiting on all endpoints (slowapi)
- [x] Docker port bound to localhost / Caddy IP only
- [x] No user input executed or evaluated server-side
- [x] XSS protection via `escHtml()` on all user-supplied strings in the frontend
- [ ] Caddy security headers (add to Caddyfile block)
- [ ] Authentik group policy restricting access to your account only
- [ ] Firewall rule on Travel Monitor LXC allowing only Caddy LXC on the app port
---
## TODO
- [ ] Mobile layout polish (touch edge cases)
- [ ] Authentik + Caddy deployment (documented above, not yet deployed)