About this project

# IzgoN IzgoN is a delta-sync server designed for fleets of devices that repeatedly report similar state. Instead of uploading full payloads every cycle, each node POSTs its current state; IzgoN compares it to the last state it saw and returns either `NO_CHANGE` (zero payload bytes), a minimal JSON delta, or the full state when a delta would be larger than the state it replaces. ## Key capabilities - **Delta sync**: Only changed fields are sent back to the device, reducing reply payloads dramatically. - **Conditional sync (v1.4.0+)**: Devices that have not changed can send a short checksum token instead of the full report, so the report itself never goes on the wire. - **Savings measurement**: The dashboard and `/api/metrics` endpoint show live byte savings in both directions (reply and uplink). - **Benchmark tool**: `benchmark.py` (stdlib-only) replays your own real reports to measure savings on your data, with synthetic mode for testing. - **Adaptive polling suggestions**: The server can suggest a longer reporting interval after several identical reports, with explicit staleness trade-offs. - **Silence alerts**: Webhook notifications when a node stops reporting and when it returns. - **Batch sync**: Devices that buffered while offline can flush a queue in one request. - **Free tier**: 10,000 syncs without a license key, enough to evaluate. ## How it works A node sends its state inside `state` (or just a `checksum` if unchanged). The server responds with one of four statuses: - `NO_CHANGE` — nothing changed, zero bytes sent. - `SYNC_REQUIRED` — only changed keys are returned; client merges them. - `FULL_STATE` — the complete new state is returned (when a delta would be larger). - `SEND_STATE` — the server has no baseline or the checksum is unknown; client must resend the full state. Nested objects are diffed recursively; lists are compared as a whole (a deliberate limitation). An optional `epoch` token forces a full state after a server restart or lost mirror, preventing silent desync. ## Quick start ```bash docker run -p 8000:8000 -e DATAPULSE_API_KEY=change-me ghcr.io/izgamber/izgon:latest ``` Or with Docker Compose (includes Redis for persistent baselines): ```bash git clone https://github.com/izGamber/IZgoN.git cd IZgoN cp .env.example .env docker compose up -d ``` Dashboard at `http://localhost:8000`. Send a state: ```bash curl -X POST http://localhost:8000/api/nodes/sensor-01/sync \ -H "Content-Type: application/json" \ -H "X-API-Key: dev-local-key" \ -d '{"state": {"temp": 21.5, "hum": 60, "batt": 98}}' ``` Repeat the same state → `NO_CHANGE` with zero delta bytes. Change one field → only that field is returned. ## Benchmark on your own data ```bash python3 benchmark.py --payload-file my-reports.json ``` Accepts JSON arrays or JSON Lines, auto-detects device ID fields, and measures change rate from your data. Synthetic mode: `python3 benchmark.py --nodes 50 --rounds 100 --change-rate 0.05`. Measured savings (5% change rate): ~94% on reply, ~40% on uplink (per-SIM), ~65% for polling clients. At 70% change rate, savings drop to ~35% — the honest boundary. ## API endpoints | Method | Path | Auth | Purpose | |---|---|---|---| | POST | `/api/nodes/{id}/sync` | API key | Submit state or checksum, get delta/full/NO_CHANGE | | POST | `/api/nodes/{id}/sync/batch` | API key | Replay buffered queue in one request | | GET | `/api/nodes` | API key | List nodes and baselines (paged) | | GET | `/api/metrics` | none | Live byte savings totals | | GET | `/api/license` | none | Current tier and remaining free syncs | | GET | `/healthz` | none | Redis reachability, storage mode | | GET | `/` | none | Dashboard | ## Configuration All settings via environment variables (see `.env.example`). Key ones: - `DATAPULSE_REDIS_URL` — Redis connection for baselines - `DATAPULSE_API_KEY` — auth key (default `dev-local-key`, change it) - `DATAPULSE_FREE_TIER_LIMIT` — free syncs before 402 (default 10000) - `DATAPULSE_LICENSE_KEY` — paid license key (Ed25519-signed, offline validation) - `DATAPULSE_ALERT_URL` / `DATAPULSE_ALERT_AFTER` — silence alerts - `DATAPULSE_ADAPTIVE` — enable/disable polling interval suggestions - `DATAPULSE_MAX_STATE_DEPTH` / `DATAPULSE_MAX_STATE_BYTES` — payload limits ## Security notes - API key protects all writes; constant-time comparison. - `/api/metrics` and `/healthz` are unauthenticated by design. - CORS defaults to `*`; narrow it in production. - No built-in rate limiting; put behind a reverse proxy. - State is bounded in depth (32) and size (1 MB). ## Limitations - Lists are not diffed element-by-element; changing one item sends the whole list. - Shrinking payloads may trigger `FULL_STATE` (no saving on that sync). - First report from any node is always full. - Baselines live in Redis; if wiped, nodes resync once. - Single instance, no clustering. - No client SDK yet; integration is plain HTTP POST. ## License and pricing Source-available, not open source. Free tier: 10,000 syncs. Commercial license: one-time payment, no subscription, offline Ed25519 signature validation. No phone-home. ## Status Version 1.4.2. Built and maintained by one person. Live demo at `https://izgon-api.onrender.com` (first request may take 20–40s to wake).