Simple web service for MeshCore Monday check-ins.
- MQTT ingestion for
meshcore/+/+/packets(all IATA regions) with topic metadata extraction per packet - MeshCore packet decoding (header/path/payload extraction)
- Monday check-in board with DiceBear avatars and animated tiles
- Leaderboard with:
- Most check-ins (tracked from configurable date)
- Longest Monday streak (with streak start date)
- SQLite storage with WAL mode and dedupe by packet hash
make setup- Start an MQTT broker locally (
localhost:1883) or point.env.localto your broker. - Run app once:
make run - Run with hot reload:
make dev(auto-installsairif needed) - Seed sample data (optional):
make dev-seed - Replay fixtures (optional):
make replay-checkins - Rebuild checkin packet links from stored raw packets (optional):
make restore-checkin-packets
Open http://127.0.0.1:8080.
- Public channel key is built in:
8b3387e9c5cdea6ac9e5edbaa115cd72. - Deterministic hashtag channels: set
HASHTAG_CHANNELS(comma-separated names, with or without#). - Private channels: set
PRIVATE_CHANNEL_KEYSas comma-separated 16-byte hex keys.
MQTT_MAX_PAYLOAD_BYTEScaps raw MQTT message size before processing (default16384).INGEST_MAX_PACKET_HEX_CHARScaps packet/envelope raw hex length accepted by ingest (default8192).INGEST_MAX_OBSERVER_KEY_CHARScaps observer key length from envelope/topic (default64).
- DiceBear avatar style: set
DICEBEAR_STYLEto any DiceBear style slug (e.g.rings,adventurer,pixel-art,bottts). - Auto-refresh polling interval: set
UI_POLL_SECONDS(default15, set0to disable).
- Ingest subscribes to all IATAs via
meshcore/+/+/packets. - Display/query filtering is controlled by
IATA_FILTERS:ALLfor all regions- comma-separated list such as
SEA,PDX,YVR
- Server entrypoint:
cmd/server/main.go - MQTT replay utility:
cmd/replay/main.go - Seed utility:
cmd/devseed/main.go - SQLite + migrations:
internal/storage/sqlite.go - Decode logic:
internal/meshcore/decode.go - Monday page:
web/templates/index.html - Leaderboard page:
web/templates/leaderboard.html
make testmake test-integration
- Create
.envwith production values. - Run
docker compose up -d --build
- GitHub Actions now builds Docker images automatically:
- Pull requests to
main: build-only validation (no push). - Pushes to
mainand version tags (v*): build and push to GHCR.
- Pull requests to
- Image builds are multi-arch for
linux/amd64andlinux/arm64. - Published image path:
ghcr.io/agessaman/meshmonday
- Run behind TLS termination (reverse proxy or load balancer).
- Keep default server timeouts and header size limits in place.
- Avoid exposing internal errors to clients (API returns generic
internal_erroron failures). - Use MQTT credentials with publish ACLs scoped to expected topics.
Backup helper: scripts/backup_sqlite.sh.
- Rebuild
checkin_packetsfromraw_packetswith current parsing/decryption rules:make restore-checkin-packets
- This is useful after schema cleanup or if packet-link rows were lost.
- Raw packet retention runs inside the server process (
RAW_MONDAY_RETAIN_WEEKS,RETENTION_INTERVAL_MINUTES). Checkins and linked rows are always kept; ambient mesh traffic is trimmed to recent Mondays inTZ. - SQLite connections use a 30s
busy_timeouton every pooled connection so MQTT inserts wait briefly instead of failing while retention deletes run. DELETEdoes not shrink the.dbfile: freed pages go on SQLite’s freelist inside the file; the OS sees the same size until you runVACUUM(or rebuild). Retention logsfreelist_mibafter each prune so you can see how much space is logically free but still inside the file. The server runs a WAL checkpoint after pruning to trim the-walsidecar when possible.raw_packetsvspacket_observations: the first stores each distinct packet payload (large hex blobs). The second stores which observer gateway reported each(packet_hash, observer)pair for Mesh Monday check-in observer badges—not duplicate payloads. Both tables shrink whenraw_packetsrows are deleted (FK cascade); the file still needsVACUUMto give disk space back.- After a large prune, compact on disk:
make vacuum(usesSQLITE_PATHfrom your env file). Run during a low-traffic window;VACUUMneeds temporary disk headroom roughly the size of the database.
Example nightly cron:
0 2 * * * cd /opt/meshmonday && ./scripts/backup_sqlite.sh ./data/meshmonday_prod.db ./backups