| `TRUSTED_PROXIES` | пусто | Доверенные прокси (CIDR через запятую). Только от них принимается `X-Forwarded-For` для IP клиента; пусто — заголовок всегда игнорируется |
| `UPDATE_CHECK_INTERVAL` | `1800` | Как часто опрос проверяет обновления ROS, с |
| `EVENTS_RETENTION_DAYS`, `EVENTS_MAX_ROWS` | `90`, `100000` | Ротация журнала по умолчанию; действующие значения меняются в UI; `0` — без ограничения |
| В установленную ROS встроена более новая прошивка платы | «↑ X» |
| Записана версия, встроенная в ROS | «актуально» |
| Прошивка платы новее встроенной в ROS (например, плата не откатилась вместе с ROS — 023) | «в ROS: X» — не обновление; откат — отдельной операцией |
| Нет RouterBOARD firmware (CHR) или устройство не опрошено | «—» |
Прошивка сравнивается с версией, встроенной в установленную ROS (`upgrade-firmware`), а не с серверами MikroTik: `/system/routerboard/upgrade` только записывает то, что уже скачано вместе с ROS.
- Устройство — в одной группе или «Без группы». Вкладки групп со счётчиками, страница «Группы»; группу можно создать при добавлении устройства.
- Фильтры устройств: поиск (имя, адрес, модель), статус, наличие обновлений, канал. Фильтры бэкапов: группа, устройство, тип файла, период, имя файла. Фильтры сохраняются в URL.
**Операции и задачи**
- Действия по устройству — меню «⋯» строки; над выбранными — полоса инструментов; через API — и над целой группой.
- Обновление ROS: устройство перезагружается после скачивания. Обновление FW: перезагрузка, как только в журнале устройства появилась запись «Firmware upgraded successfully…».
- Штатное обновление не выполняет откат: версия канала (или встроенной прошивки) старше установленной/записанной — «Обновление не требуется», с подсказкой на соответствующий откат.
**Откат ROS до версии канала / откат FW до версии ROS**
- Только осознанно: окно с вводом целевой версии, кнопка «Откатить» активна после ввода. Одно окно на оба вида отката (параметр `kind`): для ROS — «Откатить ROS…» в меню «⋯» (виден только в состоянии «канал: X») и пункт в меню «Обновление»; для FW (изменение 027) — «Откатить прошивку…» (виден в состоянии «в ROS: X») и «Откатить прошивку до версии ROS…» там же. Устройства, которые откатывать нельзя, показаны списком «Не будут затронуты».
- Задача: проверка на устройстве (целевая версия совпадает с введённой и старше записанной) → **обязательный бэкап** (сбой прерывает) → повторная проверка и запись. Для FW прошивка берётся из установленной ROS, а не с серверов MikroTik.
- Пара файлов: `.backup` (без шифрования) и `.rsc` (с`show-sensitive` — пароли и ключи). Сервер создаёт их через REST, скачивает, загружает в S3 и удаляет с устройства.
- Список бакета, скачивание по временной ссылке, удаление одного файла или выбранных (до 500 за раз).
**Идентификаторы**
-`<префикс>_<uuid7>`: `dev_` устройство, `grp_` группа, `job_` задача, `bkp_` резервная копия, `evt_` запись журнала. Типы не пересекаются, ID сортируются по времени и не переиспользуются. ID чужого типа или неверного формата — 404.
- Резервная копия — пара файлов с одним `bkp_` ID; у файлов в S3 метаданные `backup-id`/`device-id` (Yandex отдаёт `Backup-Id`/`Device-Id`). Копия без файлов в бакете помечается удалённой и остаётся в истории.
**Журнал событий**
- Создание, изменение и удаление устройств и групп, перенос устройств, задачи и бэкапы, online/offline (только смены), вход в UI, ротация и очистка журнала. Запись: `evt_` ID, ID сущности, актор (`ui:<пользователь>`, `api`, `poller`, `system`, `anonymous`), данные.
- Страница «Журнал»: фильтры по типу, актору, устройству, периоду и тексту; «Показать ещё 100»; окно записи с полными ID и данными.
- Ротация по сроку и числу записей (настройки в UI, хранятся в БД): при старте, раз в час и после сохранения; оставляет запись `journal.rotated`.
- Очистка — только в UI, через окно с паролем пользователя; остаётся запись `journal.cleared`. Через API — только чтение журнала и настроек.
## Требования к устройствам
- RouterBOARD и виртуальные **CHR** (у CHR нет `/system/routerboard`: FW не показывается, обновление FW пропускается).
- RouterOS **7.1+**, сервис `www-ssl` (REST, порт 443; **не**`api-ssl` 8729 и не `api` 8728). Для доверенной сети допустим `www` (порт 80) со снятым флагом «HTTPS» — логин и пароль идут открытым текстом.
- Пользователь с политиками `read`, `write`, `policy`, `test`, `ftp`, `reboot`, `sensitive` (проще — группа `full`). Группа `write` не подходит: бэкап падает с `not enough permissions`. После смены прав пересоздайте подключение к устройству.
- Доступ устройств к S3 не нужен: файлы загружает Control Server (нужен доступ к `storage.yandexcloud.net:443`).
- Все ID — строки с префиксом типа; `group_id: null` — без группы; пароль устройства передаётся только при записи.
- Операции над устройствами (бэкап, обновления, откат, групповые) отвечают `202 {"job_ids": [...]}`; результат — в задаче. Смена канала одного устройства и `refresh` — синхронные.
- Ошибки: 400 — неверные данные, 401 — нет или неверный токен, 404 — нет объекта или ID чужого типа, 422 — нарушение схемы, 502 — ошибка S3.
- Блокировка по IP, а не по имени: единственного администратора нельзя заблокировать чужими попытками. За reverse-proxy без `TRUSTED_PROXIES` все клиенты видны с IP прокси — одна блокировка на всех.
-`TRUSTED_PROXIES` (CIDR через запятую) включает разбор `X-Forwarded-For`: только когда адрес соединения (peer) сам входит в доверенную сеть, заголовок берётся в расчёт — цепочка разбирается справа налево, первый адрес не из доверенной сети становится IP клиента (блокировка входа, `data.ip` в `auth.*`). Пустой список (по умолчанию) или недоверенный peer — заголовок полностью игнорируется, подделать IP нельзя. Доверяя сети Docker-моста, вы делаете доверенными и процессы хоста (не только сам reverse-proxy) — используйте узкий CIDR.
- Очистка журнала требует пароль; 5 неверных за 10 минут блокируют её на 10 минут.
- Редиректы после форм — только на локальный путь.
**Данные**
-`.rsc` содержит пароли и ключи, `.backup` не зашифрован: ограничьте доступ к бакету и ссылкам скачивания.
- Порт приложения не открывайте в недоверенную сеть; за TLS включите `SESSION_COOKIE_SECURE=true`.
## Эксплуатация
- Контейнер работает от непривилегированного пользователя (uid 10001), перезапуск `unless-stopped`.
- БД (SQLite) — в томе `ros_data`, переживает пересборку; `docker compose down -v` удаляет её. Схема обновляется при старте; перед миграцией ID создаётся копия `ros_control.db.bak-<метка>`.
- **Один процесс на БД**: файловая блокировка `<файл БД>.lock`; второй процесс (`--workers 2+`, вторая копия контейнера на том же томе) не стартует. В памяти процесса — семафор задач, счётчики неудачных паролей, кэш бакета.
- **Кэш бакета**: страница «Бэкапы» и `GET /api/v1/backups` перечитывают бакет не чаще `BACKUPS_CACHE_TTL`; собственные изменения сбрасывают кэш, внешние видны по «Обновить список».
- **Копия БД**: SQLite работает в режиме WAL — файл БД без `-wal` может быть неполным. Копировать через `sqlite3 <БД> ".backup <копия>"` или вместе с`-wal`/`-shm`.
- Текущий стенд опубликован на порту **8001** — задан `APP_PORT=8001` в `.env` стенда (порт 8000 на хосте занят другим процессом); override-файл не нужен, `docker compose up -d` берёт порт из `.env`.
- Пока строки не выбраны, полоса инструментов показывает фильтры; при выборе — действия над выбранными.
- Добавление и изменение устройств, группы, откат ROS, запись журнала — в окнах поверх страницы; запасные страницы `/devices/new`, `/devices/{id}/edit` работают без JavaScript.
- Меню и выпадающие списки в едином стиле «⋯», не обрезаются таблицей и раскрываются вверх у нижнего края. Списки строятся поверх скрытого `<select>`: без JavaScript работает стандартный.
| 012 | Клик по имени устройства открывает окно изменения | [план](docs/changes/012-clickable-device-name/plan.md) · [итог](docs/changes/012-clickable-device-name/summary.md) |
| 014 | Поддержка CHR, строгое сравнение версий ROS | [план](docs/changes/014-chr-support/plan.md) · [итог](docs/changes/014-chr-support/summary.md) |
| 015 | Имя устройства задаётся только при создании | [план](docs/changes/015-immutable-device-name/plan.md) · [итог](docs/changes/015-immutable-device-name/summary.md) |
| 016 | Уникальные ID сущностей, журнал событий | [план](docs/changes/016-unique-ids-and-event-log/plan.md) · [итог](docs/changes/016-unique-ids-and-event-log/summary.md) |
| 017 | Журнал в UI: ротация, очистка с паролем | [план](docs/changes/017-events-ui-rotation/plan.md) · [итог](docs/changes/017-events-ui-rotation/summary.md) |
| 019 | Производительность: кэш бакета, БД вне event loop, один процесс на БД | [план](docs/changes/019-performance-scaling/plan.md) · [итог](docs/changes/019-performance-scaling/summary.md) |
| 020 | Выпадающие списки в стиле меню «⋯» | [план](docs/changes/020-custom-select-menus/plan.md) · [итог](docs/changes/020-custom-select-menus/summary.md) |
| 023 | Состояния «Upgrade ROS», откат ROS до версии канала | [план](docs/changes/023-ros-downgrade/plan.md) · [итог](docs/changes/023-ros-downgrade/summary.md) |