A lightweight REST API for managing configuration files for ESP32-based IoT devices. Configurations are stored as JSON files on the filesystem, making changes immediately available to devices without redeployment.
- Configuration Management - CRUD operations for device configurations identified by MAC address
- No Database Required - Configurations stored directly on filesystem (CephFS in production)
- Asset Upload - Upload and manage device assets with cryptographic signing
- LVGL Image Proxy - Convert images to LVGL-compatible format for ESP32 displays
- OpenAPI Documentation - Auto-generated API docs at
/api/docs - Prometheus Metrics - Operational metrics at
/metrics - Health Checks - Kubernetes liveness/readiness probes at
/api/health
- Python 3.11+
- Poetry
-
Install dependencies
poetry install
-
Configure environment
cp .env.example .env # Edit .env with your settings -
Run the development server
poetry run dev
The API will be available at
http://localhost:3201
Environment variables (can be set in .env):
| Variable | Description | Default |
|---|---|---|
ESP32_CONFIGS_DIR |
Path to configuration files directory | (required) |
ASSETS_DIR |
Path to assets upload directory | (required) |
SIGNING_KEY_PATH |
Path to RSA signing key for asset uploads | (required) |
CORS_ORIGINS |
Allowed CORS origins (JSON array) | ["http://localhost:3000"] |
DEBUG |
Enable debug mode | true |
HOST |
Server bind address | 0.0.0.0 |
PORT |
Server port | 3201 |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/configs |
List all configurations |
GET |
/api/configs/{mac} |
Get configuration by MAC address |
PUT |
/api/configs/{mac} |
Create or update configuration |
DELETE |
/api/configs/{mac} |
Delete configuration |
MAC addresses must be in lowercase, hyphen-separated format: xx-xx-xx-xx-xx-xx
{
"content": { ... },
"allow_overwrite": true
}Set allow_overwrite to false to prevent overwriting existing configurations (returns 409 Conflict if exists).
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/assets/upload |
Upload a signed asset |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/images/proxy |
Proxy and convert image to LVGL format |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/health |
Health check (returns 200/503) |
GET |
/metrics |
Prometheus metrics |
GET |
/api/docs |
OpenAPI documentation |
poetry run pytestWith coverage:
poetry run pytest --cov=app --cov-report=term-missingpoetry run ruff check .poetry run mypy .app/
├── api/ # HTTP endpoints (Flask blueprints)
│ ├── configs.py # Device configuration CRUD
│ ├── assets.py # Asset upload
│ ├── images.py # LVGL image proxy
│ ├── health.py # Health checks
│ └── metrics.py # Prometheus metrics
├── services/ # Business logic layer
│ ├── config_service.py
│ ├── asset_upload_service.py
│ ├── image_proxy_service.py
│ └── metrics_service.py
├── schemas/ # Pydantic request/response models
├── utils/ # Shared utilities
└── config.py # Application settings
Need an iotsupport client for user authentication. It's authenticated and Direct access grants must be set. Custom roles named admin and pipeline need to be added.
The iotsupport client needs an audience mapper so that access tokens include the client in the aud claim. Without this, authentication fails with "Token issuer or audience does not match expected values".
- Go to Clients → iotsupport
- Go to Client scopes tab
- Click on iotsupport-dedicated
- Go to Mappers tab → Add mapper → By configuration
- Select Audience and configure:
- Name:
iotsupport-audience - Included Client Audience:
iotsupport - Add to ID token: OFF
- Add to access token: ON
- Name:
Users need the admin role assigned to access the application:
- Go to Users → select the user
- Go to Role mapping tab
- Click Assign role → filter by clients → select
iotsupport→admin
Need an iotsupport-admin client with administrative access. That's also authenticated. The only authentication flow that needs to be checked is Service account roles. This enables the Service account roles tab. manage-clients must be added.
Need an iotsupport-pipeline client for CI/CD pipeline access (e.g., firmware uploads).
- Create a new client named
iotsupport-pipeline - Set Client authentication to ON
- Under Authentication flow, check only "Service account roles"
- Save the client
- Go to Client scopes tab
- Click on iotsupport-pipeline-dedicated
- Go to Mappers tab → Add mapper → By configuration
- Select Audience and configure:
- Name:
iotsupport-pipeline-audience - Included Client Audience:
iotsupport - Add to ID token: OFF
- Add to access token: ON
- Name:
- Go to Service account roles tab
- Click Assign role → filter by clients → select
iotsupport→pipeline
Device clients (created automatically when provisioning devices) need specific scopes in their tokens:
-
Audience mapper - Required for backend authentication. Without this, device authentication fails with "Token issuer or audience does not match expected values".
-
Standard OIDC scopes - Required for Mosquitto MQTT broker JWT validation:
openid- Required for OIDC complianceprofile- Includes name claimsemail- Includes email claim
Configure a client scope for the audience mapper:
- Go to Client scopes and create a new scope named
iot-device-audience(or setKEYCLOAK_DEVICE_SCOPE_NAMEto use a different name) - In the scope's Mappers tab, create a new mapper:
- Mapper type: Audience
- Name: iot-device-audience
- Included Client Audience: Set to the value of
OIDC_AUDIENCE(orOIDC_CLIENT_IDifOIDC_AUDIENCEis not set) - Add to access token: ON
The backend automatically adds the following scopes to device clients when they are created:
iot-device-audience(or custom name fromKEYCLOAK_DEVICE_SCOPE_NAME)profileemail
Note: The openid scope is automatically included for all OIDC clients and doesn't need to be explicitly assigned.
If any scope doesn't exist in Keycloak, a warning is logged but client creation continues.
See LICENSE file for details.