- TypeScript 97.9%
- JavaScript 1.1%
- CSS 1%
Operations dashboard + device-facing API for a ~50-unit fleet (fixed and mobile Rayhunter detectors). Built on Next.js 16 App Router with Neon Postgres (Drizzle), Vercel Blob, and Clerk; targeted at a self-hosted Vercel + Marketplace stack. - Drizzle schema: devices, heartbeats, recordings, detections, gps_points - Device API (HMAC-token auth): /api/v1/heartbeat, /api/v1/gps, /api/v1/register, /api/v1/recordings, /api/v1/ntfy - Cron: /api/cron/check-heartbeats marks units degraded/offline - Clerk-gated dashboard: device list, detail (with stats), recording detail, detections, fleet map, and per-device GPS trail (MapLibre) - Provisioning helper script (scripts/enroll-device.ts) and migrate runner; vercel.ts config registers the heartbeat cron - Uses Next 16 conventions: proxy.ts replaces middleware.ts, Clerk v7 Show component replaces SignedIn/SignedOut, awaited params Promises Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| public | ||
| scripts | ||
| src | ||
| .env.example | ||
| .gitignore | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| drizzle.config.ts | ||
| eslint.config.mjs | ||
| next.config.ts | ||
| package-lock.json | ||
| package.json | ||
| postcss.config.mjs | ||
| proxy.ts | ||
| README.md | ||
| tsconfig.json | ||
| vercel.ts | ||
Rayhunter Fleet
Operations dashboard and back-end for a fleet of Rayhunter IMSI-catcher detectors. Built for ~25–100 units, split between fixed sites and mobile (vehicle) units, with the back-end hosted on Vercel.
Architecture
Rayhunter unit ──WebDAV PUT──▶ rclone-serve VPS ──webhook──▶ /api/v1/recordings
│
├──HTTP POST──▶ /api/v1/heartbeat (HMAC device token)
├──HTTP POST──▶ /api/v1/gps (mobile only)
└──ntfy POST──▶ ntfy server ──webhook──▶ /api/v1/ntfy
Operator UI ──▶ /dashboard (Clerk-gated)
Cron ──▶ /api/cron/check-heartbeats (every 5 min)
The dashboard reads from Neon Postgres; recording artifacts live in Vercel Blob (private). PCAP uploads from devices target a small external WebDAV endpoint (Rayhunter has no native S3/Blob target); a sidecar process there POSTs /api/v1/recordings after each file lands and uploads the blob into Vercel Blob.
Stack
- Next.js 16 App Router (proxy.ts replaces middleware.ts,
use cachedirective available) - Neon Postgres via Drizzle ORM
- Vercel Blob (private) for recording artifacts
- Clerk for operator auth
- MapLibre / react-map-gl for GPS visualization
- Zod for input validation
First-run setup
-
Provision infrastructure on Vercel:
vercel link vercel integration add neon # Database vercel integration add clerk # Auth vercel blob store create rayhunter-recordings -
Create shared secrets:
for v in DEVICE_REGISTRATION_SECRET WEBDAV_SYNC_SECRET NTFY_WEBHOOK_SECRET CRON_SECRET; do vercel env add $v production <<< "$(openssl rand -hex 32)" done -
Pull env locally and migrate the DB:
cp .env.example .env.local vercel env pull .env.local npm run db:generate npm run db:migrate -
Run the dashboard:
npm run dev
Enrolling a device
DEVICE_REGISTRATION_SECRET=... FLEET_BASE_URL=https://fleet.example.com \
npm run enroll -- \
--asset RH-001 \
--label "Lobby" \
--hardware "Orbic RC400L" \
--mode fixed \
--lat 37.7749 --lon -122.4194 \
--site "HQ"
The response includes deviceToken (long-lived HMAC bearer for /api/v1/*), webdav.{username,password}, and ntfy.topic. Bake those into the device's Rayhunter config during flashing.
Device-facing endpoints
| Endpoint | Auth | Used by |
|---|---|---|
POST /api/v1/register |
DEVICE_REGISTRATION_SECRET |
Provisioning script (one-shot per device) |
POST /api/v1/heartbeat |
Device token | Each unit, every 60s |
POST /api/v1/gps |
Device token | Mobile units, batched |
POST /api/v1/recordings |
WEBDAV_SYNC_SECRET |
rclone-serve sidecar |
POST /api/v1/ntfy |
NTFY_WEBHOOK_SECRET (x-ntfy-secret header) |
ntfy server forwarding |
GET /api/cron/check-heartbeats |
CRON_SECRET |
Vercel cron (every 5 min) |
Things this repo does not do (yet)
- Push firmware updates to units — handled by Rayhunter's own update mechanism; this dashboard just records which version each unit reports.
- Replay PCAPs in-browser — link out to
wireshark/mobile-sentinel. - Multi-tenant — one fleet per deployment. Use route groups + an
orgstable if you need that.