## Summary - Adds `POST /api/admin/prune-geo-filter` endpoint — dry-run by default, `?confirm=true` to permanently delete nodes outside the current geofilter polygon + buffer. Requires `X-API-Key` header. - Adds **Prune nodes** section inside the GeoFilter customizer tab (write-access only, same `writeEnabled` gate as PUT). **Preview** lists affected nodes; **Confirm delete** removes them. - Adds `GetNodesForGeoPrune` and `DeleteNodesByPubkeys` DB helpers. - Updates `docs/user-guide/geofilter.md` — documents the UI button as primary workflow, CLI script as alternative. > **Depends on M3** (`feat/geofilter-m3-customizer`, PR #736). Merge M3 first. ## Test plan - [x] `cd cmd/server && go test ./...` — all pass - [x] Customizer GeoFilter tab without `apiKey` — Prune section not visible - [x] With `apiKey` + polygon active — Prune section visible - [x] **Preview** returns list of nodes outside polygon (no deletions) - [x] **Confirm delete** removes nodes, list clears - [x] `POST /api/admin/prune-geo-filter` without `X-API-Key` → 401 - [x] `POST /api/admin/prune-geo-filter` with no polygon configured → 400 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
6.5 KiB
Configuration
CoreScope is configured via config.json in the server's working directory. Copy config.example.json to get started.
Core settings
| Field | Default | Description |
|---|---|---|
port |
3000 |
HTTP server port |
apiKey |
— | Secret key for admin API endpoints (POST/PUT routes) |
dbPath |
— | Path to SQLite database file (optional, defaults to meshcore.db) |
MQTT
"mqtt": {
"broker": "mqtt://localhost:1883",
"topic": "meshcore/+/+/packets"
}
The ingestor connects to this MQTT broker and subscribes to the topic pattern.
Multiple MQTT sources
Use mqttSources for multiple brokers:
"mqttSources": [
{
"name": "local",
"broker": "mqtt://localhost:1883",
"topics": ["meshcore/#"]
},
{
"name": "remote",
"broker": "mqtts://mqtt.example.com:8883",
"username": "user",
"password": "pass",
"topics": ["meshcore/SJC/#"]
}
]
Branding
| Field | Description |
|---|---|
branding.siteName |
Site title shown in the nav bar |
branding.tagline |
Subtitle on the home page |
branding.logoUrl |
URL to a custom logo image |
branding.faviconUrl |
URL to a custom favicon |
Theme
Colors used throughout the UI. All values are hex color codes.
| Field | Description |
|---|---|
theme.accent |
Primary accent color (links, buttons) |
theme.navBg |
Navigation bar background |
theme.navBg2 |
Secondary nav background |
theme.statusGreen |
Healthy status color |
theme.statusYellow |
Degraded status color |
theme.statusRed |
Silent/error status color |
See Customization for the full list — the theme customizer exposes every color.
Node colors
Default marker colors by role:
"nodeColors": {
"repeater": "#dc2626",
"companion": "#2563eb",
"room": "#16a34a",
"sensor": "#d97706",
"observer": "#8b5cf6"
}
Health thresholds
How long (in hours) before a node is marked degraded or silent:
| Field | Default | Description |
|---|---|---|
healthThresholds.infraDegradedHours |
24 |
Repeaters/rooms → degraded after this many hours |
healthThresholds.infraSilentHours |
72 |
Repeaters/rooms → silent after this many hours |
healthThresholds.nodeDegradedHours |
1 |
Companions/others → degraded |
healthThresholds.nodeSilentHours |
24 |
Companions/others → silent |
Retention
| Field | Default | Description |
|---|---|---|
retention.nodeDays |
7 |
Nodes not seen in N days move to inactive |
retention.packetDays |
30 |
Packets older than N days are deleted daily |
Note: Lowering retention does not immediately shrink the database file. SQLite marks deleted pages as free but does not return them to the filesystem unless incremental auto-vacuum is enabled. New databases created after v0.x.x have auto-vacuum enabled automatically. Existing databases require a one-time migration — see the Database guide.
Database
| Field | Default | Description |
|---|---|---|
db.vacuumOnStartup |
false |
Run a one-time full VACUUM on startup to enable incremental auto-vacuum (blocks for minutes on large DBs) |
db.incrementalVacuumPages |
1024 |
Free pages returned to the OS after each retention reaper cycle |
See Database for details on SQLite auto-vacuum, WAL, and manual maintenance. See #919 for background.
Channel decryption
| Field | Description |
|---|---|
channelKeys |
Object of "label": "hex-key" pairs for decrypting channel messages |
hashChannels |
Array of channel names (e.g., "#LongFast") to match by hash |
See Channels for details.
Map defaults
"mapDefaults": {
"center": [37.45, -122.0],
"zoom": 9
}
Initial map center and zoom level.
Regions
"regions": {
"SJC": "San Jose, US",
"SFO": "San Francisco, US"
}
Named regions for the region filter dropdown. The defaultRegion field sets which region is selected by default.
Cache TTL
All values in seconds. Controls how long the server caches API responses:
"cacheTTL": {
"stats": 10,
"nodeList": 90,
"nodeDetail": 300,
"analyticsRF": 1800
}
Lower values = fresher data but more server load.
Packet store
| Field | Default | Description |
|---|---|---|
packetStore.maxMemoryMB |
1024 |
Maximum RAM for in-memory packet store |
packetStore.estimatedPacketBytes |
450 |
Estimated bytes per packet (for memory budgeting) |
packetStore.retentionHours |
0 |
Only load packets younger than N hours on startup and keep them in memory. Set this on any instance with a large DB. 0 = unlimited (loads full DB history — causes OOM on cold start when the DB has hundreds of thousands of paths). Recommended: same as retention.packetDays × 24 (e.g. 168 for 7 days). |
Warning: Leaving
retentionHoursat0on a large database will cause the server to OOM-kill itself on every cold start. The full packet history is loaded into the subpath index at startup; a DB with ~280K paths produces ~13M index entries before the process is killed.
Timestamps
| Field | Default | Description |
|---|---|---|
timestamps.defaultMode |
"ago" |
Display mode: "ago" (relative) or "absolute" |
timestamps.timezone |
"local" |
"local" or "utc" |
timestamps.formatPreset |
"iso" |
Date format preset |
Live map
| Field | Default | Description |
|---|---|---|
liveMap.propagationBufferMs |
5000 |
How long to buffer observations before animating |
HTTPS
"https": {
"cert": "/path/to/cert.pem",
"key": "/path/to/key.pem"
}
Provide cert and key paths to enable HTTPS.
Geographic filtering
"geo_filter": {
"polygon": [[51.55, 3.80], [51.55, 5.90], [50.65, 5.90], [50.65, 3.80]],
"bufferKm": 20
}
Restricts ingestion and API responses to nodes within the polygon plus a buffer margin. Remove the block to disable filtering. Nodes with no GPS fix always pass through.
Can also be configured live via the 🗺️ GeoFilter tab in the Customizer (requires apiKey).
See Geographic Filtering for the full guide.
Home page
The home section customizes the onboarding experience. See config.example.json for the full structure including steps, checklist, and footerLinks.