Evan's personal location service at https://location.evanrthomas.com. The iPhone (Overland app) posts GPS fixes; the service stores them, answers "where is Evan / where was he", and fires subscriptions (geofence enter/exit, "landed" after a flight, "stale" when the phone goes quiet) as signed webhooks or OpenClaw agent turns that end up in Evan's Telegram.
| Doc | What's in it |
|---|---|
| Set up Overland on iPhone | URL to paste, where the token goes, recommended settings |
| API reference | Every endpoint with curl examples |
| Subscriptions & callbacks | Rules, event payloads, retries, signature verification |
| OpenClaw integration | How events become Telegram messages; the location skill |
| Swapping trackers | Adding a new source adapter (one file) |
| Operations runbook | Deploy, logs, restart, rotate tokens, backups, DNS |
Interactive API explorer (Swagger): /api-docs · OpenAPI JSON: /openapi.json
One small Python service (FastAPI + SQLite). The "layers" are modules, not network services.
iPhone: Overland ──HTTPS POST /ingest/overland──┐ (OwnTracks HTTP mode → /ingest/owntracks)
▼
┌───────────── OpenClaw EC2 box (34.198.157.4) ──────────────────────────────────────────────┐
│ Caddy :443 location.evanrthomas.com (Let's Encrypt, no Google login) │
│ │ │
│ ▼ │
│ location-service 127.0.0.1:18810 (systemd, user `location`) │
│ sources/overland.py, sources/owntracks.py ──► LocationFix {lat, lon, accuracy_m, │
│ timestamp, battery, motion, source,device}│
│ processing.py ──► db.py (SQLite /var/lib/location-service/location.db) │
│ │ fixes · subscriptions(+state) · events(= delivery queue) │
│ └──► events.py (pure): geofence entered/exited · landed · stale │
│ delivery.py (retry loop) ──► webhook callback_url (HMAC-signed) │
│ └─► OpenClaw POST 127.0.0.1:18789/hooks/agent │
│ └─► agent turn ──► Telegram (Evan) │
│ OpenClaw agent ── `location` skill ── curl 127.0.0.1:18810 (API token) ───────────────────┘
└────────────────────────────────────────────────────────────────────────────────────────────┘
Code map (backend/):
| File | Role |
|---|---|
main.py |
Entry point: cfg = config.Config.from_env(), runs uvicorn (single worker) |
config.py |
Pydantic Config; every field is env var LOCATION_<NAME> |
app.py |
FastAPI routes, token auth, /docs markdown rendering |
sources/*.py |
Tracker adapters → schemas.LocationFix |
events.py |
Pure detection functions (no I/O) |
processing.py |
Ingest + rule evaluation (serialized by a lock), stale loop |
delivery.py |
Webhook / OpenClaw request building, HMAC, retry loop |
db.py / models.py / schemas.py |
Storage functions / ORM tables / pydantic objects |
geocode.py |
Best-effort reverse geocode (Nominatim) for landed |
Infra: DNS in terraform/ (A record → OpenClaw EIP), deploy in scripts/deploy.sh + deploy/.
The box itself is managed in ethomas2/openclaw-ec2 (see its INFRA.md).