Weather & Aviation Intelligence — no API keys required.
UAVChum is a Go web app that brings together weather forecasts, drone flight assessments, and aviation data (METARs, TAFs, NOTAMs, SIGMETs) into a single clean interface. Everything runs off free, open data sources.
- Current conditions with feels-like, humidity, wind, gusts, pressure, and cloud cover
- 24-hour hourly scroll
- 7-day forecast
- Interactive location map
- Go / Marginal / No-Go flight assessment based on live weather
- 24-hour fly window with per-hour colour coding
- Pre-flight checklist
- Interactive airspace map with toggleable layers:
- OpenAIP — controlled airspace for the UK, EU, and beyond (CTR, ATZ, MATZ, Restricted, Prohibited, Danger zones)
- FAA Class B/C/D — US controlled airspace
- FAA LAANC grids — UAS facility map / drone altitude ceilings
- TFRs — active Temporary Flight Restrictions
- Airport advisory circles — proximity warnings worldwide
- Data sources panel showing feature counts, live vs cached status, and last fetch time
- Local drone laws tile — links to the official regulator for US, CA, AU, NZ, and UK
- METAR with full decode (flight category, wind, visibility, ceiling, altimeter)
- TAF terminal forecast
- SIGMETs & AIRMETs
- Pilot Reports (PIREPs)
- NOTAMs
| Source | Used for |
|---|---|
| Open-Meteo | Weather forecasts & current conditions |
| Open-Meteo Geocoding | Location search |
| OpenAIP | EU/global airspace (cached 24 h) |
| FAA ArcGIS | US Class B/C/D airspace & LAANC grids |
| aviationweather.gov | METARs, TAFs, SIGMETs, PIREPs, TFRs |
| Blitzortung | Real-time lightning strikes |
| ADS-B Exchange / adsb.lol | Live aircraft traffic |
No accounts, no API keys, no rate-limit tokens needed.
- Go 1.23+
go mod downloadgo run .Or build and run:
go build -o uavchum .
./uavchumThen open http://localhost:5555.
Browser assets are served locally — no CDN dependencies:
- Leaflet loaded from repo-local static files
- Weather Icons loaded from repo-local static files
Go server security defaults:
- Strict CSP with per-request nonces (no
unsafe-inlinefor scripts) X-Frame-Options: DENY,X-Content-Type-Options: nosniff, HSTS in production- All API inputs validated with regex before use
- Rate limiting per IP: 2 req/s burst + per-endpoint per-minute limits
read_onlycontainer filesystem, all capabilities dropped,no-new-privileges
For production behind Cloudflare Tunnel, set:
UAVCHUM_ENV=productionThe .env on the server also holds these keys (see deploy/cloudflare.md):
| Variable | Why |
|---|---|
TUNNEL_TOKEN |
Cloudflare Tunnel sidecar (compose) |
TURNSTILE_SITE_KEY / TURNSTILE_SECRET_KEY |
bot protection; widget hides itself if unset |
CARTO_BASERMAPS_KEY |
CARTO basemap tiles now require a key (free, e-mail only, at https://carto.com/basemaps/apikey/). It is a public origin-restricted key: the server passes it to the browser, which appends it to tile requests. |
# Lint (requires golangci-lint)
make lint
# Static analysis
make vet
# Vulnerability scan (requires govulncheck)
make vulnInstall tools:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
go install golang.org/x/vuln/cmd/govulncheck@latestdocker build -t uavchum:local .
docker run --rm -it -p 5555:5555 -e UAVCHUM_ENV=production uavchum:localThen open http://localhost:5555.
See deploy/cloudflare.md for the recommended Cloudflare Tunnel rollout.
compose.yml runs the app and a cloudflared tunnel sidecar, and does not expose any ports to the host.
It requires a real Cloudflare TUNNEL_TOKEN in your .env.
An Ansible playbook is provided to sync and redeploy to the server:
ansible-playbook deploy/deploy.ymlRequires the ansible.posix collection (ansible-galaxy collection install ansible.posix).
The playbook rsyncs all files to ~/uavchum on the rocky host (from your SSH config),
then runs podman compose build --no-cache && podman compose up -d --force-recreate
if anything changed. The .env file on the server is never overwritten.
Running podman ps may print errors like:
ERRO[0000] Refreshing container <id>: acquiring lock 1 for container <id>: file exists
Why this happens. Podman is daemonless — systemd-logind destroys /run/user/$UID/ on logout,
wiping lock files. The containers themselves are unaffected; this is a known upstream bug
(#16784).
Permanent fix (run once on the Rocky host):
loginctl enable-linger $USERImmediate recovery:
podman system renumber
podman compose down && podman compose up -d.
├── main.go # Server, routing, startup
├── middleware.go # CSP nonce, security headers
├── decode.go # WMO codes, METAR decode, drone assessment
├── weather.go # /api/weather
├── aviation.go # /api/aviation (METAR/TAF/NOTAM/SIGMET/PIREP)
├── airspace.go # /api/airspace + OpenAIP cache
├── adsb.go # /api/adsb
├── lightning.go # Blitzortung goroutine + /api/lightning
├── search.go # /api/search, /api/station, /api/flightroute
├── go.mod / go.sum
├── Dockerfile # Multi-stage: builder + alpine runtime (~20 MB image)
├── compose.yml # App + cloudflared tunnel sidecar
├── Makefile # build / lint / vet / vuln / deploy
├── .golangci.yml # Linter config
├── deploy/
│ ├── deploy.yml # Ansible deploy playbook
│ ├── inventory.ini # Ansible inventory (rocky host)
│ ├── cloudflare.md # Rollout instructions
│ ├── setup.sh # Bare-metal setup (no container)
│ └── uavchum.service # systemd unit
├── static/
│ ├── app.js # Frontend logic
│ ├── style.css
│ ├── sw.js # Service worker
│ └── manifest.json
└── templates/
└── index.html # Single-page app shell