# Ignis Core Локальный FastAPI-сервер для WiZ-ламп: discovery, управление, группы, расписания, API-ключи, аудит и встроенная веб-морда. ## Что умеет - искать лампы в локальной сети при старте, вручную и в фоне; - управлять лампой или группой; - мигать лампой (`blink`) и читать живой статус; - хранить группы, ключи, события и расписания в SQLite; - выполнять `one-shot` и `cron`-задачи через APScheduler; - разделять доступ на `master`, `admin`, `guest`; - отдавать встроенный web UI из `static/`; - публиковать OpenAPI. ## Структура - `main.py` — FastAPI, middleware, lifecycle, статика. - `app/api/routes/` — HTTP API. - `app/core/discovery.py` — discovery и выбор подсетей. - `app/core/scheduler.py` — расписания и reconciliation. - `app/core/state.py` — runtime-state устройств и групп. - `app/models/` — SQLAlchemy и схемы данных. - `static/` — встроенный UI. - `deploy/` — `systemd`-деплой и пример env. ## Быстрый старт ```bash python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt cp deploy/ignis-core.env.example .env .venv/bin/python -m uvicorn main:app --host 0.0.0.0 --port 8000 ``` - UI: `http://127.0.0.1:8000/` - OpenAPI runtime: `http://127.0.0.1:8000/openapi.json` - Коммитнутый экспорт: [openapi.json](openapi.json) `python-dotenv` подхватывает `.env` автоматически. ## Конфиг Минимум: ```env IGNIS_API_KEY=change-me IGNIS_INSTANCE_NAME=Home APP_TIMEZONE=Asia/Novosibirsk SCAN_NETWORK=192.168.0.0/24 IGNIS_DATABASE_URL=sqlite+aiosqlite:///./ignis.db IGNIS_SYNC_DATABASE_URL=sqlite:///./ignis.db ``` Основные переменные: - `IGNIS_API_KEY` — мастер-ключ. Без него сервер работает в `fail-closed` и защищённые маршруты отвечают `503`. - `IGNIS_INSTANCE_NAME` — имя инстанса в UI и `GET /system/info`. - `APP_TIMEZONE` — таймзона расписаний. - `SCAN_NETWORK` — подсеть или список подсетей для discovery. На хостах с VPN или несколькими NIC лучше задавать явно. - `DISCOVERY_INTERVAL_SECONDS` — период фонового refresh. - `DISCOVERY_BACKGROUND_MISSING_THRESHOLD` — сколько циклов подряд лампа может не отвечать до удаления. - `DISCOVERY_ENV_MIN_PREFIX_LEN` — минимально допустимая маска для `SCAN_NETWORK`. - `DISCOVERY_AUTO_MIN_PREFIX_LEN` — минимально допустимая маска для auto-discovery. - `EVENT_LOG_RETENTION_DAYS` — срок хранения event log. - `IGNIS_PUBLIC_BASE_URL` — внешний URL, если сервер стоит за reverse proxy. - `IGNIS_BUILD_VERSION`, `IGNIS_BUILD_DATE`, `IGNIS_GIT_SHA` — build metadata для диагностики. Полный пример: [deploy/ignis-core.env.example](deploy/ignis-core.env.example) ## Discovery - при старте выполняется `startup_refresh()`; - `POST /devices/rescan` делает ручной refresh и сразу убирает оффлайн-устройства; - фоновый refresh удаляет устройство только после `DISCOVERY_BACKGROUND_MISSING_THRESHOLD` подряд промахов; - если `SCAN_NETWORK` пуст, сервер сам выбирает private IPv4 подсети и старается не лезть в `docker`, `tun`, `wg`, `tailscale` и похожие интерфейсы. ## Роли Заголовок авторизации: `X-API-Key` - `master` — значение `IGNIS_API_KEY`, полный доступ. - `admin` — ключ из БД с `is_admin=true`, доступ к группам, рескану, расписаниям, stats и расширенному `system/info`. - `guest` — управление светом и чтение безопасной части API. `GET /auth/me` возвращает текущую роль и имя ключа. ## HTTP API Основные группы маршрутов: - `GET /devices` - `GET /devices/groups` - `GET /devices/scenes` - `POST /devices/groups` - `DELETE /devices/groups/{group_id}` - `POST /devices/rescan` - `POST /control/device/{device_id}` - `POST /control/group/{group_id}` - `POST /control/device/{device_id}/blink` - `GET /control/device/{device_id}/status` - `GET /control/group/{group_id}/status` - `POST /schedules/once` - `POST /schedules/cron` - `GET /schedules/tasks` - `DELETE /schedules/{job_id}` - `GET /api-keys` - `POST /api-keys` - `POST /api-keys/revoke` - `POST /api-keys/activate` - `GET /stats/summary` - `GET /stats/log` - `GET /system/info` Ролевые ограничения: - `guest`: чтение устройств/групп/сцен, управление светом, `status`, `blink`, безопасный `system/info`. - `admin`: всё выше плюс создание/удаление групп, `rescan`, расписания, stats/log, расширенный `system/info`. - `master`: всё выше плюс управление API-ключами. Формат основных тел: ```json {"state": true} ``` ```json {"brightness": 60} ``` ```json {"temp": 3200} ``` ```json {"r": 255, "g": 180, "b": 120} ``` ```json {"scene": "Cozy"} ``` Валидация: - `brightness`: `10..100` - `temp`: `2200..6500` - `r/g/b`: `0..255` - можно передать только один режим из `scene`, `temp` или `rgb` - `r/g/b` нужно передавать полной тройкой - для `POST /schedules/once` нужно передать ровно одно из `run_at` или `hours_from_now` Примеры: ```bash curl -sS http://127.0.0.1:8000/control/group/bedroom \ -H 'X-API-Key: change-me' \ -H 'Content-Type: application/json' \ -d '{"state":true,"brightness":60}' ``` ```bash curl -sS http://127.0.0.1:8000/api-keys \ -H 'X-API-Key: change-me' ``` Замечания по ключам: - `GET /api-keys` возвращает публичные `key_id`, а не полный секрет; - `POST /api-keys` показывает полный секрет только один раз; - `POST /api-keys/revoke` и `POST /api-keys/activate` принимают JSON вида `{"key":""}`. ## Встроенный UI UI лежит в `static/` и не требует отдельной сборки. Что есть сейчас: - вход по API-ключу; - роль-зависимые вкладки; - пульт групп со сценами, яркостью, температурой, цветом и таймером на 4 часа; - список устройств и сборка групп; - one-shot и cron; - серверная вкладка с метаданными инстанса; - аудит, stats и API-ключи для нужных ролей. Свойства UI: - использует только локальные ассеты; - не использует `localStorage`; - может хранить ключ только в `sessionStorage` текущей вкладки; - гость не видит чувствительные поля `system/info`. ## Хранилище SQLite-таблицы: - `groups` - `api_keys` - `event_log` - `schedules` - `apscheduler_jobs` - `devices` Важно: - онлайн-устройства и статусы живут в runtime-памяти процесса; - таблица `devices` пока не является полноценным source of truth; - миграций схемы нет, используется `Base.metadata.create_all()`. ## OpenAPI Актуальная схема закоммичена в `openapi.json`. Перегенерация: ```bash .venv/bin/python -c 'import json, os, pathlib; os.environ.setdefault("IGNIS_API_KEY", "openapi-export"); from main import app; pathlib.Path("openapi.json").write_text(json.dumps(app.openapi(), ensure_ascii=False, indent=2) + "\n", encoding="utf-8")' ``` ## Тесты Основная команда: ```bash timeout 120s .venv/bin/python -m unittest discover -s tests -v ``` Дополнительно: ```bash .venv/bin/python -m compileall app tests main.py node --check static/app.js ``` ## Ограничения - discovery всё ещё сетевой перебор внутри выбранных подсетей; - runtime-state устройств живёт в памяти процесса; - UI монолитный, без отдельного frontend-build step; - stats — это аудит и summary, а не полноценная аналитика.