Initial commit

This commit is contained in:
2026-07-11 19:30:52 +02:00
commit 7183ae31bc
8 changed files with 2831 additions and 0 deletions
+213
View File
@@ -0,0 +1,213 @@
# 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)