Migrate Harbor state and traffic history to SQLite
Build and Deploy Gateway / build-and-push (push) Successful in 1m22s
Build and Deploy Gateway / deploy (push) Successful in 16s

This commit is contained in:
2026-09-10 19:21:21 +03:00
parent ab14fc979e
commit 1ae23d848b
48 changed files with 1703 additions and 446 deletions
+18 -2
View File
@@ -87,7 +87,7 @@ http://АДРЕС-GATEWAY:3456
Откройте «Устройства» в правой панели Gateway — подписка для просмотра списка не требуется. Harbor раз в 15 секунд читает локальную таблицу соседей и показывает каждое устройство одной компактной строкой: заданное название, hostname или IP, последний контакт, выбранный график трафика и иконку применённого маршрута. По умолчанию график показывает приблизительный выход `VPN`/`Direct`; переключатель `Вход` возвращает накопленную разбивку `Gateway`/`Прокси`. Наведите курсор на имя или переведите на него фокус, чтобы открыть IP, MAC и доступный hostname; нажатие на значение копирует его. Hostname определяется через локальное обратное разрешение имён и может отсутствовать, если сеть его не публикует. Технические interface и manufacturer продолжают храниться для идентификации, но не занимают место в строке. Список разделён на «Закреплённые», «Остальные» и «Фоновые»: последняя группа сохраняется между перезапусками, показывает только identity/presence и кнопку возврата без графика, traffic и route controls. Название, закрепление, фоновое положение и накопленные totals сохраняются в volume Gateway, пока устройство остаётся в inventory.
Левая панель списка ищет по имени, hostname, IP, MAC и тегам, фильтрует новые, закреплённые, фоновые или устройства без тегов и позволяет выбрать несколько тегов по правилу «хотя бы один». Каталог тегов общий для Gateway: в нём можно создать до 32 тегов и назначить устройству до 8. Назначения сохраняются вместе с `devices.json`, но маршруты не меняют. После удаления устройства по 30-дневному retention его назначения удаляются, сам каталог остаётся; вернувшееся позже устройство появляется без тегов. Если Mac-клиент подключён к старой версии Gateway, список продолжает работать, а управление тегами скрывается до обновления Gateway.
Левая панель списка ищет по имени, hostname, IP, MAC и тегам, фильтрует новые, закреплённые, фоновые или устройства без тегов и позволяет выбрать несколько тегов по правилу «хотя бы один». Каталог тегов общий для Gateway: в нём можно создать до 32 тегов и назначить устройству до 8. Назначения сохраняются в документе устройств внутри `harbor.sqlite`, но маршруты не меняют. После удаления устройства по 30-дневному retention его назначения удаляются, сам каталог остаётся; вернувшееся позже устройство появляется без тегов. Если Mac-клиент подключён к старой версии Gateway, список продолжает работать, а управление тегами скрывается до обновления Gateway.
Красная кнопка `Сбросить данные` после отдельного подтверждения обнуляет вход и выход всех устройств и начинает считать их заново. Общий график скорости на Home и уже сохранённая история Prometheus/Grafana не очищаются: входной counter выглядит для Prometheus как стандартный reset, а для выхода Harbor сохраняет только baseline отображения и не изменяет raw dataplane counters.
@@ -288,8 +288,18 @@ SING_BOX_TRAFFIC_SOURCE=snapshot \
docker compose -f docker-compose.gateway.yml up -d --build
```
## Локальная история трафика
Harbor полностью работает без Prometheus. В существующем drawer «Трафик» режимы `Сейчас / История` разделяют текущие соединения и локальные суммы. История поддерживает `24 часа / 7 дней / 30 дней / 90 дней`, поиск, маршрут и устройство на Gateway; строки раскрываются как сервис → домен → полное имя → IP. Например, `www.yandex.ru` и `mail.yandex.com` остаются разными именами внутри группы «Яндекс». IP без наблюдённого домена не выдаётся за распознанный сайт.
`traffic.sqlite` хранит рабочие данные за 90 дней: завершённые минуты за последние 7 дней, далее часы. Текущая история отстаёт не более чем на минуту при исправном сборе; API сообщает фактически доступный период, детализацию и пропуски. История начинается с включения нового native-сбора. Данные по доменам относятся только к соединениям, наблюдаемым sing-box, и не восстанавливают ранее накопленные общие счётчики.
Запись и запросы выполняются в отдельном рабочем потоке. Ошибка базы или переполнение ограниченной очереди отмечает историю как неполную, но не останавливает VPN или экспорт метрик. Для защиты от повторного учёта сохраняются позиции счётчиков: активные — пока нужны их исходные значения, закрытые — до 90 дней либо смены процесса sing-box. Размер зависит не только от доменов, но и от числа соединений; это не база фиксированного размера. Освобождённые страницы переиспользуются без обязательного немедленного уменьшения файла.
## Prometheus и Grafana
Prometheus необязателен. Он хранит только экспортируемые метрики по политике своего владельца, а не копию всей SQLite. Полную доменную/IP-детализацию внешнего архива этот релиз не обещает. Нет синхронизации баз, автоматического восполнения пропущенных scrape, восстановления SQLite из Prometheus или переключения интерфейса на него. Очистка локальной истории не удаляет внешнюю; отсутствие Prometheus не продлевает локальные 90 дней.
Gateway публикует уже накопленные Harbor traffic counters по адресу `http://<gateway>:3456/metrics`. Scrape не запускает дополнительный netfilter read и не меняет сохранённое состояние. Harbor обновляет snapshot раз в 15 секунд, поэтому рекомендуемый начальный scrape interval и refresh dashboard — 30 секунд:
```yaml
@@ -308,7 +318,7 @@ scrape_configs:
Dashboard начинает со скорости скачивания и отправки в конце выбранного периода, общего трафика и фактического VPN / Direct за этот период. Под обзором полоса `Применённый режим` показывает applied policy устройства, а график `Фактический VPN / Direct` независимо показывает маршрут наблюдённых байтов. Поэтому компьютер с policy Direct, браузер которого использует Harbor Proxy, остаётся Direct на полосе режима, но его proxy-соединения учитываются в VPN. Единый фильтр `Устройства` управляет режимом, скоростью, общим трафиком, сервисами, доменами и технической детализацией. Таблица «Все устройства за период» намеренно остаётся общей и выбирает устройство в том же фильтре. Блок «Куда уходит трафик» показывает основные назначения и Top-15 доменов без пагинации. Свёрнутая техническая детализация показывает `source × outbound`, включая `proxy · vpn`, и раздельные Direct-пути через sing-box и Linux мимо sing-box. Автообновление настроено на 30 секунд; индикатор предупреждает после 60 секунд и считает данные устаревшими после 120 секунд.
В `snapshot` и `shadow` domain и sing-box outbound counters снимаются с активных соединений раз в 2 секунды. В `native` dataplane получает полный lifecycle, включая короткие соединения и финальный хвост; данные всё равно хранятся в памяти только до перезапуска, а историю и retention хранит Prometheus. Перед routing sing-box до 1 секунды распознаёт HTTP Host, TLS SNI и QUIC Server Name. YouTube и OpenAI / ChatGPT объединяются по известным связанным доменам в label `service`, остальные значения сохраняют домен как имя сервиса. Если устройство и Harbor source известны, но hostname недоступен (например, ECH или IP-only), трафик попадает в `domain="_unknown",service="Не распознано"` и не теряется. Новые domain series сверх process limit складываются в `_other`.
В `snapshot` и `shadow` domain и sing-box outbound counters снимаются с активных соединений раз в 2 секунды. В `native` dataplane получает полный lifecycle, включая короткие соединения и финальный хвост; существующая проекция экспортируемых domain/outbound counters хранится в памяти до перезапуска, а необязательный Prometheus независимо сохраняет полученные метрики. Полный поток также поступает в отдельную локальную `traffic.sqlite`; её очистка не сбрасывает эту проекцию. Перед routing sing-box до 1 секунды распознаёт HTTP Host, TLS SNI и QUIC Server Name. YouTube и OpenAI / ChatGPT объединяются по известным связанным доменам в label `service`, остальные значения сохраняют домен как имя сервиса. Если устройство и Harbor source известны, но hostname недоступен (например, ECH или IP-only), трафик попадает в `domain="_unknown",service="Не распознано"` и не теряется. Новые domain series сверх process limit складываются в `_other`.
Состояние collector и сравнение `shadow` экспортируются отдельными bounded gauges `harbor_traffic_collector_*` и `harbor_traffic_shadow_*`. Они не содержат UUID, IP, домены или пользовательские имена и не заменяют canonical traffic counters.
@@ -383,6 +393,8 @@ docker compose -f docker-compose.client.local.yml up -d --build
### Команды npm
Для сборки и backend закреплён Node **24.21.0** (`.node-version`); используется встроенная SQLite без ORM. `npm run check:runtime` проверяет точную Node-версию, движок SQLite не старше 3.51.3 и точность 64-битных счётчиков. Та же проверка выполняется в сборочных и конечных Docker-образах. При использовании fnm: `fnm use 24.21.0`.
| Команда | Назначение |
| --- | --- |
| `npm ci` | Установить точные версии зависимостей из `package-lock.json` |
@@ -406,4 +418,8 @@ docker compose -f docker-compose.client.local.yml up -d --build
Подписка, выбранный сервер и состояние подключения хранятся в именованных Docker volumes. Поэтому обычные команды `restart`, `down`, обновление проекта и повторная сборка не удаляют настройки.
На каждом Mac/Gateway свои `harbor.sqlite` (настройки, подписки, устройства, правила, накопленные счётчики и журнал) и `traffic.sqlite` (ограниченная история). Журнал сохраняет прежний предел 30 дней/10 000 событий; настройки не подчиняются retention истории. Секреты, hardware ID, генерируемый конфиг и кеш sing-box остаются файлами.
Первый запуск транзакционно импортирует прежние JSON, сохраняя IDs, revisions и исходные значения счётчиков. После успеха SQLite становится единственным рабочим хранилищем; исходные JSON остаются неизменными резервными копиями, без параллельной записи. Повреждение или неизвестная версия останавливает миграцию без обнуления. Старый бинарник не читает новые данные: простой downgrade вернул бы устаревшие JSON. Правила backup и восстановления описаны в [state recovery](docs/recovery/state-recovery.md).
Не публикуйте файл `.env`, ссылку подписки и содержимое Docker volumes. `.env` уже исключён из Git.