Files
ignis-core/README.md
2026-05-21 20:46:04 +07:00

226 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Ignis Core
`ignis-core` — локальный FastAPI-сервер для WiZ-ламп и домашней автоматики вокруг них.
## Что есть сейчас
- discovery ламп по локальной сети с `startup`, `manual` и background refresh;
- управление отдельной лампой и группой;
- реальные `status`-опросы и `blink` для идентификации;
- группы в SQLite;
- one-shot и cron-расписания;
- persisted metadata расписаний поверх APScheduler;
- роли `master`, `admin`, `guest`;
- гостевые API-ключи с revoke/activate;
- event log и простая stats summary;
- встроенный локальный UI из `static/`;
- OpenAPI-экспорт в `openapi.json`.
## Архитектура
- `main.py` — инициализация FastAPI, security headers, router wiring, startup lifecycle.
- `app/api/routes/*` — HTTP-маршруты.
- `app/core/discovery.py` — выбор подсетей и UDP discovery WiZ.
- `app/core/state.py` — in-memory runtime-state устройств и групп.
- `app/core/scheduler.py` — APScheduler, reconciliation и cleanup old events.
- `app/models/*` — SQLAlchemy-модели и Pydantic-схемы.
- `static/` — встроенный web UI без внешних CDN.
## Запуск локально
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp deploy/ignis-core.env.example .env
uvicorn main:app --host 0.0.0.0 --port 8000
```
UI: `http://<host>:8000/`
Готовые файлы для `systemd`: `deploy/README.md`
## Конфигурация
Минимальный набор:
```env
IGNIS_API_KEY=change-me
IGNIS_INSTANCE_NAME=Home
APP_TIMEZONE=Asia/Novosibirsk
LOG_LEVEL=INFO
IGNIS_DATABASE_URL=sqlite+aiosqlite:///./ignis.db
IGNIS_SYNC_DATABASE_URL=sqlite:///./ignis.db
```
Параметры server metadata / versioning:
```env
IGNIS_PUBLIC_BASE_URL=https://ignis.example.local
IGNIS_BUILD_VERSION=1.0.0
IGNIS_BUILD_DATE=2026-05-21T12:00:00Z
IGNIS_GIT_SHA=abc1234def56
```
- `IGNIS_INSTANCE_NAME` — человекочитаемое имя инстанса, которое видно в UI и `GET /system/info`.
- `IGNIS_PUBLIC_BASE_URL` — внешний URL сервера, если он стоит за reverse proxy или доступен по доменному имени.
- `IGNIS_BUILD_VERSION`, `IGNIS_BUILD_DATE`, `IGNIS_GIT_SHA` — build metadata установленного сервера для диагностики и сверки версий.
Параметры discovery:
```env
SCAN_NETWORK=192.168.0.0/24
DISCOVERY_INTERVAL_SECONDS=600
DISCOVERY_BACKGROUND_MISSING_THRESHOLD=2
DISCOVERY_ENV_MIN_PREFIX_LEN=16
DISCOVERY_AUTO_MIN_PREFIX_LEN=24
```
Параметры retention:
```env
EVENT_LOG_RETENTION_DAYS=30
```
## Как работает discovery
- Если `SCAN_NETWORK` задан, сервер сканирует только указанные подсети.
- Если `SCAN_NETWORK` пуст, `DiscoveryService` пытается выбрать private IPv4-сегменты обычных интерфейсов и избегает `docker`, `tun`, `wg`, `tailscale` и похожих интерфейсов.
- При старте выполняется `startup_refresh()`.
- `POST /devices/rescan` делает ручной refresh и сразу удаляет оффлайн-устройства.
- Background refresh работает циклически и удаляет устройство только после `DISCOVERY_BACKGROUND_MISSING_THRESHOLD` подряд пропусков.
Для хостов с VPN, несколькими NIC или нетипичной маршрутизацией `SCAN_NETWORK` лучше задавать явно.
## Авторизация и роли
Заголовок: `X-API-Key`
- `master` — значение `IGNIS_API_KEY`, полный доступ.
- `admin` — ключ из БД, доступ к группам, расписаниям, stats и `rescan`.
- `guest` — чтение и обычное управление светом.
Сервер работает в `fail-closed`: если `IGNIS_API_KEY` не задан, защищённые маршруты отвечают `503`.
## HTTP API
Основные маршруты:
- `GET /auth/me`
- `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`
`control/*` и `schedules/*` принимают JSON body.
Поддерживаемые параметры команды:
- `state`
- `brightness`
- `scene`
- `temp`
- `r`, `g`, `b`
Валидация:
- `brightness`: `10..100`
- `temp`: `2200..6500`
- `r/g/b`: `0..255`
- `scene`, `temp` и `rgb` взаимоисключаемы
- `r`, `g`, `b` нужно передавать полной тройкой
- для `schedules/once` нужно передать ровно одно из `run_at` или `hours_from_now`
Пример:
```bash
curl -X POST 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}'
```
## Встроенный UI
- лежит в `static/index.html`, `static/app.js`, `static/ui.css`;
- использует только локальные ассеты;
- не использует `localStorage`;
- может хранить API-ключ только в `sessionStorage` текущей вкладки;
- показывает build/server metadata текущего инстанса;
- умеет базовое управление группами, расписания, API-ключи, stats/log и быстрый таймер на 4 часа.
## Хранилище
SQLite-таблицы:
- `groups`
- `api_keys`
- `event_log`
- `schedules`
- `apscheduler_jobs`
- `devices`
Важно: реальным runtime-источником истины для онлайн-устройств остаётся in-memory `state_manager.devices`. Таблица `devices` пока не используется как полноценный persistent source of truth.
## 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")'
```
## Тесты
На 2026-05-21 в `tests/` лежит 29 `unittest`-сценариев.
Покрыто:
- fail-closed auth и role checks;
- lifecycle API-ключей;
- control/status error handling и partial success;
- validation для scene, control body и schedules body;
- one-shot и cron-расписания;
- reconciliation и миграция legacy jobs;
- auto subnet selection для discovery;
- background offline cleanup threshold;
- manual rescan summary;
- server metadata endpoint и отсутствие утечки секретов в нём;
- security headers и локальные UI-ассеты;
- stats summary без двойного счёта `*_requested`.
Команды:
```bash
.venv/bin/python -m compileall app tests main.py
timeout 120s .venv/bin/python -m unittest discover -s tests -v
```
## Известные ограничения
- discovery по-прежнему основан на переборе IP в подсетях;
- миграций схемы БД нет, используется `Base.metadata.create_all()`;
- runtime-state устройств живёт в памяти процесса;
- встроенный UI остаётся монолитным файлом без отдельной frontend-сборки;
- stats — это audit/summary, а не полноценная аналитика.