Files
ignis-core/README.md
2026-05-21 22:22:47 +07:00

9.0 KiB
Raw Permalink Blame History

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.

Быстрый старт

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

python-dotenv подхватывает .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

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-ключами.

Формат основных тел:

{"state": true}
{"brightness": 60}
{"temp": 3200}
{"r": 255, "g": 180, "b": 120}
{"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

Примеры:

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}'
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":"<secret-or-public-id>"}.

Встроенный 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.

Перегенерация:

.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")'

Тесты

Основная команда:

timeout 120s .venv/bin/python -m unittest discover -s tests -v

Дополнительно:

.venv/bin/python -m compileall app tests main.py
node --check static/app.js

Ограничения

  • discovery всё ещё сетевой перебор внутри выбранных подсетей;
  • runtime-state устройств живёт в памяти процесса;
  • UI монолитный, без отдельного frontend-build step;
  • stats — это аудит и summary, а не полноценная аналитика.

License

MIT