# Shelly Manager API REST API server for Shelly Manager built with Litestar. ## Features - HTTP REST API for device management - OpenAPI documentation at `/docs` - Request/response validation - Health monitoring endpoint - CORS support for web integration - Device configuration backup & restore (encrypted snapshots) - Docker deployment ready ## Quick Start ### Docker ```bash # Run API server docker run -p 8000:8000 \ ghcr.io/jfmlima/shelly-manager-api:latest # Visit API documentation open http://localhost:8000/docs ``` ### Local Development ```bash # Install dependencies (from project root) uv sync --package shelly-manager-api --extra dev # Start development server uv run --package shelly-manager-api python -m api.main # API available at http://localhost:8000 # Docs available at http://localhost:8000/docs ``` ## API Endpoints ### Health & Status ```bash GET /api/health # Service health check ``` ### Device Discovery ```bash GET /api/devices/scan # Scan network for devices ?targets=192.168.1.0/24 # Comma-separated targets &use_mdns=false # Use mDNS discovery &timeout=3.0 # Timeout per device &max_workers=50 # Concurrent workers ``` ### Device Operations ```bash GET /api/devices/{ip}/status # Get device status ?include_updates=true # Include update information POST /api/devices/{ip}/update # Update device firmware ?channel=stable # Update channel (stable/beta) POST /api/devices/{ip}/reboot # Reboot device POST /api/devices/bulk/update # Bulk firmware updates # Body: {"device_ips": ["192.168.1.100", "192.168.1.101"], "channel": "stable"} # Component Actions GET /api/devices/{ip}/components/actions # Discover available actions POST /api/devices/{ip}/components/{id}/action # Execute component action ``` ### Configuration Backup & Restore Per-device configuration snapshots, stored in the local database and encrypted with `SHELLY_SECRET_KEY`. A backup captures every component's config (plus script code and schedules) and is keyed by device MAC. > **Backup/restore vs. bulk config:** restore puts a single device's own captured config back, > for recovery. `POST /api/devices/bulk/config/apply` does the opposite, pushing one config out > to many devices for templating. They cover different needs, so both are kept. ```bash GET /api/backups # List backup summaries (newest first) ?device_mac=AABBCCDDEEFF # Optional: filter by device MAC POST /api/backups # Capture a backup of a device # Body: {"device_ip": "192.168.1.100", "name": "before-upgrade"} GET /api/backups/{id} # Get a backup including its full snapshot POST /api/backups/{id}/restore # Restore a backup onto a device # Body: {"device_ip": "192.168.1.100", # "component_keys": ["switch:0", "sys"], # null = all except network types # "allow_mac_mismatch": false, # restore even if target MAC differs # "reboot": false} # reboot after a successful restore DELETE /api/backups/{id} # Delete a backup ``` Restore is per-component and **excludes network components (`wifi`/`eth`/`mqtt`/`ws`/`cloud`) by default** to avoid locking the device off-network; pass their keys in `component_keys` to include them. Restore is supported on Gen2+ devices only (Gen1 devices back up but cannot be restored per-component). ### Scheduled Backups Run backups automatically on a schedule with optional retention. Schedules live in the database; an in-process scheduler on the API server polls for due schedules and runs them. This relies on the API running as a **single worker** (the default), since one scheduler instance must own the timer. With multiple workers each one would run the scheduler and duplicate every backup. ```bash GET /api/backup-schedules # List schedules (newest first) POST /api/backup-schedules # Create a schedule # Body: {"name": "nightly", # "every": "daily", # or "interval_seconds": 21600 (exactly one) # "target_ips": ["192.168.1.100"], # plus optional "target_macs" and # "all_credentialed": false, # "all_credentialed" (at least one target) # "retention_keep_last": 7, # optional retention # "retention_max_age_days": 30} GET /api/backup-schedules/{id} # Get one schedule PUT /api/backup-schedules/{id} # Partial update DELETE /api/backup-schedules/{id} # Delete POST /api/backup-schedules/{id}/enable # Enable POST /api/backup-schedules/{id}/disable # Disable POST /api/backup-schedules/{id}/run # Run now, ignoring the next run time ``` Targets are the union of `target_ips` (single IPs, or ranges/CIDR that are scanned for live devices at run time), `target_macs` (each resolved to an IP through the device's last-seen address), and `all_credentialed` (every device with stored credentials). A MAC with no known IP is reported as skipped, not failed. Retention only prunes scheduled snapshots, so manual backups are never removed by a schedule. A missed run fires once on catch-up rather than backfilling. ### Component Actions The Component Actions system provides dynamic action discovery and execution for individual device components. #### Discovery ```bash GET /api/devices/{ip}/components/actions # Get all available actions for device ``` #### Execute Component Action ```bash POST /api/devices/{ip}/components/{component_id}/action ``` ### Credentials Management For password-protected Shelly Gen2 devices. Requires `SHELLY_SECRET_KEY` environment variable. ```bash GET /api/credentials # List stored credentials (passwords hidden) POST /api/credentials # Set/update device credentials # Body: {"mac": "AABBCCDDEEFF", "password": "secret", "username": "admin"} DELETE /api/credentials/{mac} # Delete stored credentials ``` ### Monitoring ```bash GET /api/health ``` ## Examples ### Device Scan ```bash curl "http://localhost:8000/api/devices/scan?targets=192.168.1.1-10" ``` **Response:** ```json [ { "ip": "192.168.1.100", "status": "online", "device_type": "shelly1pm", "device_name": "Living Room Light", "firmware_version": "20230913-112003", "response_time": 0.123, "last_seen": "2024-01-15T10:30:00Z" } ] ``` ### Device Update ```bash curl -X POST "http://localhost:8000/api/devices/192.168.1.100/update?channel=stable" ``` **Response:** ```json { "ip": "192.168.1.100", "success": true, "message": "Firmware update initiated", "action_type": "firmware_update" } ``` ### Component Actions Examples #### Discover Device Actions ```bash curl "http://localhost:8000/api/devices/192.168.1.100/components/actions" ``` **Response Example:** ```json { "device_ip": "192.168.1.100", "components": [ { "component_id": "switch:0", "component_type": "switch", "available_actions": [ { "action": "toggle", "description": "Toggle switch state", "parameters": {} }, { "action": "turn_on", "description": "Turn switch on", "parameters": {} } ] } ] } ``` #### Toggle a Switch ```bash curl -X POST "http://localhost:8000/api/devices/192.168.1.100/components/switch:0/action" \ -H "Content-Type: application/json" \ -d '{"action": "toggle", "params": {}}' ``` **Request Body:** ```json { "action": "toggle", "params": {} } ``` **Response:** ```json { "ip": "192.168.1.100", "component_id": "switch:0", "action": "toggle", "success": true, "result": { "new_state": "on" } } ``` #### Open a Cover ```bash curl -X POST "http://localhost:8000/api/devices/192.168.1.100/components/cover:0/action" \ -H "Content-Type: application/json" \ -d '{"action": "open", "params": {}}' ``` ### Credentials Management Examples #### Set Device Credentials ```bash curl -X POST "http://localhost:8000/api/credentials" \ -H "Content-Type: application/json" \ -d '{"mac": "AABBCCDDEEFF", "password": "mypassword", "username": "admin"}' ``` **Response:** ```json { "mac": "AABBCCDDEEFF", "username": "admin", "last_seen_ip": null } ``` #### List Stored Credentials ```bash curl "http://localhost:8000/api/credentials" ``` **Response:** ```json [ { "mac": "AABBCCDDEEFF", "username": "admin", "last_seen_ip": "192.168.1.100" } ] ``` #### Delete Credentials ```bash curl -X DELETE "http://localhost:8000/api/credentials/AABBCCDDEEFF" ``` ### Backup & Restore Examples #### Capture a Backup ```bash curl -X POST "http://localhost:8000/api/backups" \ -H "Content-Type: application/json" \ -d '{"device_ip": "192.168.1.100", "name": "before-upgrade"}' ``` **Response:** ```json { "id": 1, "device_mac": "AABBCCDDEEFF", "device_ip": "192.168.1.100", "device_name": "Living Room Light", "generation": "gen2", "name": "before-upgrade", "source": "manual", "size_bytes": 2048, "created_at": 1718800000 } ``` #### Restore a Backup ```bash curl -X POST "http://localhost:8000/api/backups/1/restore" \ -H "Content-Type: application/json" \ -d '{"device_ip": "192.168.1.100", "component_keys": ["switch:0", "sys"]}' ``` **Response:** ```json { "success": true, "device_ip": "192.168.1.100", "backup_id": 1, "total": 2, "succeeded": 2, "failed": 0, "skipped": 0, "components": [ { "key": "switch:0", "action": "SetConfig", "success": true }, { "key": "sys", "action": "SetConfig", "success": true } ] } ``` ### Error Response ```json { "detail": "Device not reachable", "status_code": 404, "ip": "192.168.1.100" } ``` ## Configuration ### Environment Variables | Variable | Default | Description | | -------------------- | ------------- | -------------------------- | | `HOST` | `127.0.0.1` | API server host | | `PORT` | `8000` | API server port | | `DEBUG` | `false` | Enable debug mode | | `SHELLY_SECRET_KEY` | (required) | Fernet key for credential encryption. Generate with: `python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"` | | `SHELLY_BACKUP_SCHEDULER_ENABLED` | `true` | Run the in-process scheduled-backup poller | | `SHELLY_BACKUP_POLL_INTERVAL_SECONDS` | `60` | How often the scheduler checks for due backups | `SHELLY_SECRET_KEY` also encrypts configuration **backup snapshots** at rest. Backups are stored in the local database (`{data_dir}/data.db`); rotating the key makes existing snapshots undecryptable. Set `SHELLY_BACKUP_SCHEDULER_ENABLED=false` to turn off automated backups while still managing schedules through the API. ## Docker Deployment ### Basic Deployment ```bash docker run -d \ --name shelly-manager-api \ -p 8000:8000 \ -e HOST=0.0.0.0 \ -e PORT=8000 \ ghcr.io/jfmlima/shelly-manager-api:latest ``` ### Docker Compose ```yaml services: api: image: ghcr.io/jfmlima/shelly-manager-api:latest ports: - "8000:8000" environment: - HOST=0.0.0.0 - PORT=8000 - DEBUG=false restart: unless-stopped ``` ### Health Check ```yaml healthcheck: test: ["CMD-SHELL", "curl -f http://localhost:8000/api/health || exit 1"] interval: 30s timeout: 10s retries: 3 start_period: 40s ``` ## Development ### Local Development Setup ```bash # From project root cd shelly-manager # Install with development dependencies uv sync --package shelly-manager-api --extra dev # Run with auto-reload uv run --package shelly-manager-api python -m api.main # Run tests uv run --package shelly-manager-api pytest packages/api/tests/ -v # Run linting uv run ruff check packages/api/ uv run mypy packages/api/src/api ``` ### API Documentation Development The API uses Litestar's built-in OpenAPI generation: - **Interactive Docs**: http://localhost:8000/docs (Swagger UI) - **OpenAPI Spec**: http://localhost:8000/schema/openapi.json ### Adding New Endpoints 1. **Create Controller**: Add to `packages/api/src/api/controllers/` 2. **Define DTOs**: Add request/response models to `packages/api/src/api/presentation/dto/` 3. **Add Route**: Register controller in `packages/api/src/api/main.py` 4. **Add Tests**: Create tests in `packages/api/tests/` Example controller: ```python from litestar import Controller, get from api.presentation.dto.responses import DeviceResponse class DeviceController(Controller): path = "/devices" @get("/{device_ip:str}/status") async def get_device_status(self, device_ip: str) -> DeviceResponse: # Implementation here pass ``` ### Testing ```bash # Run all API tests make test-api # Run specific test files uv run --package shelly-manager-api pytest packages/api/tests/unit/controllers/ -v # Run with coverage uv run --package shelly-manager-api pytest packages/api/tests/ --cov=api --cov-report=html ``` ## Architecture The API follows Clean Architecture principles: ``` packages/api/src/api/ ├── controllers/ # HTTP request handlers ├── dependencies/ # Dependency injection container ├── main.py # Application entry point └── presentation/ ├── dto/ # Data Transfer Objects └── serializers/ # Response serialization ``` ### Dependencies - **Litestar**: Modern async web framework - **Pydantic**: Data validation and serialization - **uvicorn**: ASGI server - **Core Package**: Business logic and domain models ## Troubleshooting ### Common Issues 1. **Port already in use**: Change `PORT` environment variable 3. **CORS errors**: Configure CORS settings for your web UI domain 4. **Health check failures**: Verify API is responding on configured port ### Debug Mode Enable debug mode for detailed error messages: ```bash docker run -p 8000:8000 \ -e DEBUG=true \ ghcr.io/jfmlima/shelly-manager-api:latest ``` ### Logs ```bash # View container logs docker logs shelly-manager-api # Follow logs in real-time docker logs -f shelly-manager-api ``` ## Additional Resources - **Main Documentation**: [../../README.md](../../README.md) - **Development Guide**: [../../DEVELOPMENT.md](../../DEVELOPMENT.md) - **Core Package**: [../core/README.md](../core/README.md) - **CLI Package**: [../cli/README.md](../cli/README.md)