plane.toml — все параметры
Полный справочник конфигурации контрол-плейна: каждый параметр, значение по умолчанию, что ломается при ошибке.
Файл /etc/hastreamer-cctv/plane.toml — единственный конфигурационный файл контрол-плейна.
Его читает бинарь /usr/local/bin/hastreamer-plane, запускаемый юнитом
hastreamer-cctv-plane.service.
В файле хранится только инфраструктура: на каком сокете слушать, по какому адресу Plane доступен снаружи, какая база PostgreSQL используется, чем шифруется его собственный слушатель, где лежат материалы событий, какими ключами подписываются сессии и насколько подробен журнал. Всего 30 параметров.
Срок хранения аудита, лимиты приёма событий, порог вынесения вложений в блобы, таймаут
ONVIF-интервалов и предельная длительность экспорта архива хранятся в таблице settings
базы PostgreSQL и правятся из админки. Список — в разделе
Чего в этом файле НЕТ.
Настройки записи видео (DVR) принадлежат ноде и задаются во Fleet, а не здесь.
Три правила, которые действуют на весь файл#
1. Неизвестный ключ верхнего уровня — фатальная ошибка. Модель конфигурации объявлена с
deny_unknown_fields, поэтому опечатка (lissten = …) не игнорируется молча, а останавливает
запуск. То же строгое поведение действует внутри таблиц [tls], [event_storage_policy],
[keys] и [jwks].
[attachment_storage]Эта таблица разобрана как перечисление с тегом backend, а такой тип не поддерживает
deny_unknown_fields. Опечатка внутри неё (regoin = "us-east-1" вместо region)
игнорируется молча, и применяется значение по умолчанию. Проверяйте написание ключей в этой
таблице особенно внимательно.
2. Файл читается ровно один раз — при старте процесса. Горячей перечитки нет ни у одного параметра. Любая правка вступает в силу только после:
sudo systemctl restart hastreamer-cctv-plane
Единственное частичное исключение — log_level, изменённый через админку: он применяется к
живому фильтру журнала сразу и одновременно записывается в файл. Правка log_level руками всё
равно требует перезапуска.
3. Валидация громкая. Любое отвергнутое значение прерывает запуск сообщением в stderr (то есть в журнале systemd):
hastreamer-plane: fatal: invalid plane network configuration: <причина>
Юнит объявлен как Restart=on-failure с RestartSec=3s, поэтому ошибочная правка даёт не
«частично работающий» Plane, а цикл перезапусков. Причина всегда в строке fatal::
sudo journalctl -u hastreamer-cctv-plane -n 50 --no-pager | grep -i fatal
Как бинарь читает файл#
hastreamer-plane [<путь-к-конфигу>] [--reset-superadmin]
Первый аргумент без дефисов — путь к конфигу; если его не передать, берётся plane.toml в
текущем каталоге. Штатный юнит всегда передаёт /etc/hastreamer-cctv/plane.toml явно.
Флаг --reset-superadmin выпускает и печатает новый пароль суперадминистратора, записывает это
в аудит и завершает процесс — это разовая команда восстановления доступа, а не режим работы.
Что меняется из админки, а что только руками#
Суперадминистратор может переписать часть этого файла из админки: Настройки → параметры
Plane (PATCH /api/v1/settings/plane). Разделение жёсткое:
| Правится из админки | Только правкой файла |
|---|---|
listen, public_url, log_level |
database_url |
вся таблица [tls] |
db_pool_max |
вся таблица [attachment_storage] |
[keys] session_key_pem |
вся таблица [event_storage_policy] |
[keys] tunnel_control_key_pem |
[jwks] session_ttl_secs |
Логика разделения простая: строка подключения к базе и пути к приватным ключам никогда не проходят через API — их нельзя ни прочитать, ни записать снаружи.
Запись файла атомарна (временный файл рядом → fsync → rename) и сохраняет режим доступа
(записанный установщиком 0640 останется 0640). Байты и комментарии вне затронутых ключей
сохраняются дословно.
Но каждая переписываемая таблица сначала очищается целиком. Сохранение сетевых настроек
стирает и заново формирует всю таблицу [tls]; сохранение настроек хранилища — всю
[attachment_storage] и всю [event_storage_policy]. Любой ваш комментарий внутри этих
трёх таблиц будет уничтожен без предупреждения.
Правило: пишите свои комментарии над заголовком таблицы или рядом с параметрами верхнего
уровня, но не внутри [tls], [attachment_storage] и [event_storage_policy].
Ещё три особенности записи из админки, о которых полезно знать:
- Неприменимые ключи выбрасываются. Таблица формируется заново из типизированной модели,
поэтому
certиkeyостаются только приmode = "pem",hosts— только если список непустой,store_dir— только если задан явно, а три ключаacme_*— только приmode = "acme". - Файл проверяется дважды — сначала в памяти, затем повторным разбором ровно тех байтов, которые будут записаны. Через админку нельзя оставить незапускаемый конфиг.
- Защита от одновременной правки. Если файл изменился после того, как форма настроек была
загружена, сохранение отклоняется с сообщением
"/etc/hastreamer-cctv/plane.toml changed after the settings form was loaded"— ваша ручная правка не будет затёрта молча, админка попросит перезагрузить форму. Одновременные сохранения из админки выстраиваются в очередь на внутренней блокировке и через 10 секунд ожидания возвращают409 settings_busy.
Правьте файл руками свободно. Если вы при этом пользуетесь страницей настроек, держите
комментарии вне трёх переписываемых таблиц и перезапускайте сервис после правки любого вида.
Страница настроек показывает и сохранённое значение слушателя, и фактически действующее, и
поднимает признак pending_restart, пока Plane не перезапущен.
Полный перечень параметров#
| # | Параметр | Тип | По умолчанию | Обязателен | Из UI |
|---|---|---|---|---|---|
| 1 | listen |
адрес сокета | "127.0.0.1:8080" |
нет | да |
| 2 | database_url |
строка | — | да | нет |
| 3 | log_level |
перечисление | "info" |
нет | да |
| 4 | db_pool_max |
целое ≥ 5 | 16 |
нет | нет |
| 5 | public_url |
origin-URL | — | да | да |
| 6 | [attachment_storage] backend |
"local" или "s3" |
"local" |
если таблица есть | да |
| 7 | [attachment_storage] dir |
абсолютный путь | "/var/lib/hastreamer-cctv/attachments" |
нет | да |
| 8 | [attachment_storage] endpoint |
host[:port] |
— | для s3 |
да |
| 9 | [attachment_storage] bucket |
строка | — | для s3 |
да |
| 10 | [attachment_storage] region |
строка | "us-east-1" |
нет | да |
| 11 | [attachment_storage] https |
логический | true |
нет | да |
| 12 | [attachment_storage] access_key |
строка | "" |
для s3 |
да, только запись |
| 13 | [attachment_storage] secret_key |
строка | "" |
для s3 |
да, только запись |
| 14 | [attachment_storage] addressing |
"path" или "virtual_host" |
"path" |
нет | да |
| 15 | [event_storage_policy] max_bytes |
целое, байты | не задан | для s3 |
да |
| 16 | [event_storage_policy] volume_retention |
перечисление | "disabled" |
нет | да |
| 17 | [event_storage_policy] low_watermark_percent |
1–97 | 75 |
нет | да |
| 18 | [event_storage_policy] high_watermark_percent |
целое | 85 |
нет | да |
| 19 | [event_storage_policy] critical_watermark_percent |
≤ 99 | 95 |
нет | да |
| 20 | [keys] session_key_pem |
путь | <каталог конфига>/signing.pem |
нет | нет |
| 21 | [keys] tunnel_control_key_pem |
путь | <каталог конфига>/tunnel-control.pem |
нет | нет |
| 22 | [jwks] session_ttl_secs |
целое, секунды | 3600 |
нет | нет |
| 23 | [tls] mode |
перечисление из 5 значений | "off" |
нет | да |
| 24 | [tls] cert |
путь | не задан | для pem |
да |
| 25 | [tls] key |
путь | не задан | для pem |
да |
| 26 | [tls] hosts |
массив строк | [] |
для self_signed и acme |
да |
| 27 | [tls] store_dir |
путь | <каталог конфига>/plane-tls |
нет | да |
| 28 | [tls] acme_directory |
URL https:// |
"https://acme-v02.api.letsencrypt.org/directory" |
нет | да |
| 29 | [tls] acme_contact |
строка, e-mail | "" |
нет | да |
| 30 | [tls] acme_listen |
адрес сокета | "0.0.0.0:80" |
нет | да |
Обязательных без значения по умолчанию — ровно два: database_url и public_url.
Отсутствие любого из них — ошибка разбора файла, Plane не стартует вообще. Остальные
«обязательные» в таблице обязательны условно: только если выбран соответствующий режим
хранилища или TLS.
Параметры верхнего уровня#
listen#
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
listen |
Строка IP:порт. IPv4 (0.0.0.0:8080, 127.0.0.1:8080) или IPv6 в квадратных скобках ([::]:8443). Порт 1–65535 |
"127.0.0.1:8080" |
рестарт |
TCP-сокет, который Plane открывает для собственного /api/v1, админки, мобильного интерфейса и
плеера. Это адрес привязки, а не адрес, который набирают пользователи — последний задаётся
в public_url. Процесс работает от root, поэтому привилегированные порты 80 и 443 доступны.
Имя хоста не принимается — только IP-адрес. Иначе запуск прерывается:
invalid plane network configuration: listen must be an IP socket address (hostnames are not bind addresses)
Если удалить строку listen, действует зашитое значение 127.0.0.1:8080 — только петлевой
интерфейс. Установщик и пример конфигурации пишут 0.0.0.0:<порт>, поэтому удаление строки
не «вернёт как было», а закроет Plane для всех, кроме локального хоста.
В режиме acme действует дополнительное правило: listen обязан отличаться от
[tls] acme_listen — см. acme_listen.
Значение не является секретом. Привязка к 0.0.0.0 открывает админку на всех интерфейсах;
если TLS терминирует реверс-прокси на том же хосте, привязывайтесь к 127.0.0.1.
database_url — обязателен, значения по умолчанию нет#
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
database_url |
Строка подключения PostgreSQL: postgres://пользователь:пароль@хост:порт/база, допустимы параметры вида ?sslmode=require |
нет — параметр обязателен | рестарт |
Строка подключения к PostgreSQL. Plane владеет этой схемой: при старте применяет свои
миграции, создаёт организацию по умолчанию, суперадминистратора и системные шаблоны, а дальше
хранит там все камеры, ноды, пользователей, гранты, события, записи аудита и продуктовые
настройки. Подключения помечаются как application_name=hastreamer-plane.
Синтаксис строки при разборе конфигурации не проверяется — она передаётся драйверу как есть.
Ошибочный или недоступный адрес фатален при старте и, с учётом Restart=on-failure, даёт цикл
перезапусков с интервалом 3 секунды, пока PostgreSQL не станет доступен. Если ключ вообще
отсутствует, файл не разбирается:
parsing /etc/hastreamer-cctv/plane.toml: missing field `database_url`
Сколько подключений планировать. Пул ограничен параметром db_pool_max. Дополнительно Plane
держит одно отдельное подключение вне пула для своей аренды-одиночки
(application_name=hastreamer-plane-singleton) — это рекомендательная блокировка, гарантирующая
ровно один процесс Plane на одну базу. Закладывайте db_pool_max + 1 подключений PostgreSQL на
каждый Plane. Потеря этой аренды намеренно завершает процесс.
database_url содержит пароль базы открытым текстом. Именно из-за неё файл должен быть
0640 root:root: его нельзя делать доступным для чтения всем, вставлять в заявку в техподдержку
или коммитить в git. Смена пароля базы — это правка этой строки плюс перезапуск сервиса.
Через API этот параметр не читается и не записывается никогда.
log_level#
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
log_level |
Ровно одно из "debug", "info", "warn", "error" (строчными буквами) |
"info" |
горячее из админки / рестарт при правке файла |
Минимальный уровень сообщений, попадающих в stderr (то есть в журнал systemd) и в ограниченное по объёму кольцо оперативного журнала в памяти, которое показывает админка. Кольцо вычищает учётные данные из сообщений.
Это закрытое перечисление, а не выражение RUST_LOG. Значения "trace", "verbose" или
"cctv_plane=debug" отвергаются при разборе файла:
parsing /etc/hastreamer-cctv/plane.toml: unknown variant `verbose`, expected one of `debug`, `info`, `warn`, `error`
Пофильтровать журнал по конкретным подсистемам можно только переменной окружения RUST_LOG —
см. Переменные окружения. Пока RUST_LOG задана и непуста, она
полностью замещает log_level и блокирует соответствующий элемент управления в
админке: попытка сохранить уровень журнала вернёт 409 rust_log_override.
Это единственный параметр, изменение которого из админки применяется немедленно, без
перезапуска. Уровень debug безопасно включить ненадолго для диагностики (учётные данные
вычищаются), но он многословен — не оставляйте его включённым постоянно.
db_pool_max#
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
db_pool_max |
Целое без знака, минимум 5. Верхнего предела в Plane нет | 16 |
рестарт |
Максимальное число подключений PostgreSQL в пуле. Оно ограничивает параллелизм всех API-запросов, транзакций приёма событий и фоновых задач.
Нижняя граница не случайна: короткие транзакции приёма событий должны сосуществовать с трафиком авторизации и чтения, с обслуживанием жизненного цикла и восстановлением, поэтому бюджет приёма резервирует четыре подключения под пути контрол-плейна. Значение меньше 5 прерывает запуск:
invalid plane network configuration: db_pool_max must be at least 5 so short event-ingest transactions leave four database connections available
Реальный потолок сверху задаёт max_connections самого PostgreSQL; не забудьте про +1
подключение аренды-одиночки. Сопутствующие тайм-ауты пула зашиты и параметрами не
настраиваются: ожидание подключения — 10 секунд, простой — 2 минуты, предельное время жизни
подключения — 30 минут.
Из админки не меняется — только правкой файла.
public_url — обязателен, значения по умолчанию нет#
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
public_url |
Только origin: схема (http или https) + хост + необязательный порт. Без учётных данных, пути, параметров запроса и якоря. Допустимы и DNS-имя, и IP-литерал (кроме режима acme) |
нет — параметр обязателен | рестарт |
Канонический внешний адрес Plane — то, по чему до него достучатся браузеры и ноды. Из него
собираются ссылки, перенаправления, публичные ссылки на фрагменты и адрес обратного вызова,
который сообщается нодам. Он законно может отличаться от listen: реверс-прокси, NAT,
проброс портов контейнера, внешний терминатор TLS. Завершающий / нормализуется при старте.
Ошибочное значение прерывает запуск одним из двух сообщений:
invalid plane network configuration: public_url must be an HTTP(S) origin invalid plane network configuration: public_url must be an HTTP(S) origin without credentials, path, query or fragment
Отсутствие ключа — такая же ошибка разбора, как и у database_url:
parsing /etc/hastreamer-cctv/plane.toml: missing field `public_url`
Перекрёстная проверка с [tls] mode — самая частая причина невзлетевшего Plane:
tls.mode |
Требуемая схема public_url |
Сообщение при несовпадении |
|---|---|---|
off |
http:// |
tls.mode=off requires an http:// public_url |
external, pem, self_signed, acme |
https:// |
tls.mode=<режим> requires an https:// public_url |
Дополнительно для режимов self_signed и acme хост из public_url обязан присутствовать в
списке tls.hosts, иначе:
invalid plane network configuration: tls.hosts must include the public_url hostname "cctv.example.com"
Сравнение регистронезависимое, скобки IPv6 снимаются перед сравнением.
Ошибка в public_url не ломает собственный слушатель Plane — сервис поднимется и будет
отвечать. Ломаются обратные вызовы нод, публичные ссылки и перенаправления в браузере, поэтому
симптом выглядит как «всё запустилось, но ничего не работает».
Хранилище материалов событий#
Таблицы [attachment_storage] и [event_storage_policy] настраивают материалы событий,
принадлежащие Plane: кадры по детекции движения и ONVIF, загруженные вложения к событиям и
маленькие генерируемые превью (<ключ>.preview).
Записи DVR принадлежат ноде и настраиваются на каждой ноде отдельно в разделе Fleet.
Ни один параметр plane.toml не влияет на хранилище видеоархива.
Обе таблицы можно не писать вообще: по умолчанию используется локальный каталог
/var/lib/hastreamer-cctv/attachments, стандартные пороги заполнения и отсутствие байтового
бюджета.
[attachment_storage] backend#
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
backend |
"local" или "s3" |
"local" (вся таблица необязательна) |
рестарт |
Это ключ-тег перечисления: если таблица присутствует, backend в ней обязателен. Неизвестное
значение — ошибка разбора:
parsing /etc/hastreamer-cctv/plane.toml: unknown variant `obj-store`, expected `local` or `s3`
Смена бэкенда меняет то, куда пишутся новые материалы. Прежде чем менять её на работающей установке, прочитайте Перенос хранилища — это самое неочевидное поведение во всём файле.
[attachment_storage] dir — локальный бэкенд#
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
dir |
Абсолютный путь, выделенный под эту задачу. Не /, без компонентов . и .., без пробелов по краям |
"/var/lib/hastreamer-cctv/attachments" |
рестарт |
Каталог, где физически лежат блобы материалов. Именно он растёт со временем — размещайте его на просторном диске. При первой активации Plane размечает каталог (создаёт в нём файл-маркер принадлежности), а при обычном перезапуске просто открывает его. Благодаря этому непримонтированный диск обнаруживается как ошибка, а не подменяется молча созданным пустым каталогом.
Недопустимый путь прерывает запуск:
invalid plane network configuration: local event storage directory must be a dedicated absolute path below the filesystem root
Содержимое каталога — доказательная база по событиям; установщик создаёт его с правами
0750 root:root.
[attachment_storage] — бэкенд S3#
[attachment_storage] backend = "s3" endpoint = "s3.example.com" # без схемы: host[:port] bucket = "cctv-evidence" region = "us-east-1" https = true access_key = "ЗАМЕНИТЕ" secret_key = "ЗАМЕНИТЕ" addressing = "path" # или "virtual_host"
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
endpoint |
host[:port] без схемы: без префикса https://, без пути, учётных данных, параметров запроса и якоря. IP-литерал допустим только при addressing = "path" |
— (обязателен) | рестарт |
bucket |
Непустая строка до 255 символов, без пробелов и без разделителей / и \ |
— (обязателен) | рестарт |
region |
Непустая строка, используется при подписи запросов SigV4 | "us-east-1" |
рестарт |
https |
Логический. Выбирает схему обращения к endpoint. false — открытый HTTP, допустим только в изолированной локальной сети |
true |
рестарт |
access_key |
Непустая строка. Секрет | "" (пустая отвергается) |
рестарт |
secret_key |
Непустая строка. Секрет | "" (пустая отвергается) |
рестарт |
addressing |
"path" — адрес вида хост/бакет/ключ; "virtual_host" — адрес вида бакет.хост/ключ |
"path" |
рестарт |
Все проверки фатальны при старте и возвращаются кодом 400 при сохранении из админки:
S3 event storage endpoint must be schemeless host[:port] invalid S3 event storage endpoint S3 event storage endpoint must be schemeless host[:port] without credentials or path S3 event storage bucket must be a non-empty bucket name without path separators S3 event storage region must not be empty S3 event storage credentials must not be empty virtual-host S3 addressing requires a DNS hostname endpoint and a DNS-compatible bucket name
Последняя проверка требует одновременно DNS-имени в endpoint (не IP) и совместимого с DNS
имени бакета: от 3 до 63 символов, только строчные буквы, цифры, - и ., каждая метка между
точками начинается и заканчивается буквой или цифрой, и само имя не является IP-адресом.
У https и addressing отдельных сообщений нет: неверный тип (https = "true" вместо
https = true) или неизвестное значение addressing останавливают уже разбор файла —
parsing /etc/hastreamer-cctv/plane.toml: <ошибка toml>. Помните также, что опечатка в имени
ключа именно в этой таблице не диагностируется вообще, а молча заменяется значением по
умолчанию.
У объектного хранилища нет переносимого способа спросить «сколько осталось места», поэтому
вместе с backend = "s3" обязательно задавать [event_storage_policy] max_bytes. Иначе:
invalid plane network configuration: S3 event storage requires an explicit max_bytes budget
Отдельного переключателя проверки сертификатов (tls_verify и подобных) у этого бэкенда нет —
единственный транспортный переключатель — https.
access_key и secret_key — вторая причина, по которой файл должен быть 0640 root:root.
API устроен намеренно асимметрично: записать пару можно через настройки суперадминистратора, но
прочитать её обратно нельзя никогда — наружу отдаются только признаки
"access_key_configured": true/false и "secret_key_configured": true/false, и сами значения
не попадают в запись аудита.
Если при сохранении из админки оба поля опущены, сохранённая пара остаётся прежней. Передать только одно из двух нельзя:
S3 access and secret keys must be supplied together S3 event storage credentials are required when selecting the backend
Выдавайте этим ключам доступ строго к одному бакету.
Перенос хранилища#
Прочитайте этот раздел перед тем, как менять dir или backend на работающей установке.
-
Запуск не ломается никогда. Настроенный бэкенд применяется при старте всегда; смена каталога не приводит к циклу перезапусков. Если в прежнем хранилище остались блобы, которых новое не видит, Plane пишет об этом в журнал и поднимает постоянное оповещение — «материалы событий лежат в ПРЕДЫДУЩЕМ каталоге, они не потеряны, верните прежний каталог в
plane.toml, чтобы восстановить доступ», — продолжая работать с новым каталогом. В журнале это сообщение вида:event-storage evidence in bucket … is in the PREVIOUS directory … It is not lost — revert the directory in plane.toml to recover it
Возврат прежнего значения восстанавливает доступ. Пустое прежнее хранилище не помечается.
-
Через админку правило строже, чем при правке файла. Смена каталога или бэкенда из админки отклоняется с
409 event_storage_rotation_blocked, пока хоть в одном незакрытом хранилище ещё лежат физические блобы. Дополнительно выполняется живая проверка нового бэкенда: если он недоступен или недоступен на запись, возвращается502с сообщением самого бэкенда. -
Переезжают только материалы событий Plane. Видеоархив нод это не затрагивает.
[event_storage_policy] — политика заполнения#
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
max_bytes |
Целое, байты. Не менее 64 МиБ; должно помещаться в тип bigint PostgreSQL |
не задан (для локального бэкенда — ёмкость файловой системы) | рестарт |
volume_retention |
"disabled" или "oldest_purge_day" |
"disabled" |
рестарт |
low_watermark_percent |
Целое 1–97. Ниже этого порога давление снимается | 75 |
рестарт |
high_watermark_percent |
Целое. На этом пороге и выше давление включается | 85 |
рестарт |
critical_watermark_percent |
Целое ≤ 99. Порог жёсткого давления | 95 |
рестарт |
max_bytes — это учётный потолок для материалов событий, а не квота организации. Для локального
бэкенда его можно не задавать: ёмкость берётся из файловой системы. Для S3 он обязателен.
Пороги задаются в процентах от ёмкости, и порядок строгий — равные значения отвергаются, зазор между ними является преднамеренным гистерезисом:
event storage watermarks must satisfy 1 <= low < high < critical <= 99 event storage max_bytes must be at least 64 MiB event storage max_bytes must fit the PostgreSQL bigint accounting domain S3 event storage requires an explicit max_bytes budget
При "disabled" (значение по умолчанию) телеметрия заполнения и контроль приёма работают, но
давление никогда не уничтожает историю. При "oldest_purge_day" полный цикл давления
«высокий → низкий» списывает целиком самое старое поколение суток очистки — вместе с
лежащими в нём материалами событий.
Переключение этого значения из админки требует явного подтверждения, иначе возвращается
400 destructive_volume_retention_acknowledgement_required. При правке файла руками никакого
подтверждения не спрашивается: вы просто включаете необратимую политику удаления данных.
Байтовый бюджет сам по себе никогда не даёт права на удаление — удаляет только эта настройка.
Ключи подписи и время жизни сессии#
[keys] session_key_pem#
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
session_key_pem |
Абсолютный путь к приватному ключу ES256 (P-256) в формате PKCS#8 PEM | <каталог plane.toml>/signing.pem, то есть /etc/hastreamer-cctv/signing.pem |
рестарт |
Ключ, которым подписываются JWT сессий браузера и API. Его открытая половина публикуется по
адресу /.well-known/jwks.json, и ноды проверяют билеты сессий по ней автономно — именно
поэтому недоступность Plane не прерывает уже идущее видео. Обратная сторона: замена этого ключа
обесценивает все выданные сессии и заставляет ноды обновить закешированный JWKS.
Установщик записывает ключ явно в plane-session.pem, поэтому имя по умолчанию signing.pem
встречается только в конфигурациях, написанных вручную.
Поведение при старте:
| Состояние файла | Что делает Plane |
|---|---|
| Файла нет | Генерирует новый ключ ES256, сохраняет его с правами 0600, создавая при необходимости родительские каталоги, и пишет в журнал generated a new ES256 plane session key (0600) |
| Файл повреждён или это не ES256 | Прерывает запуск — молча подменять личность недопустимо |
Права шире, чем 0600 |
Прерывает запуск: private key /etc/hastreamer-cctv/plane-session.pem must have mode 0600 (проверка — mode & 0o077 != 0) |
Если файл ключа пропал, Plane не остановится: он сгенерирует новый. Все пользователи будут разлогинены, а закешированный нодами JWKS станет недействительным. Ключ обязателен к резервному копированию — см. Что обязательно бэкапить.
Файл-спутник. Рядом с ключом Plane ведёт эпоху идентификатора ключа: тот же путь с
расширением, заменённым на .kid (/etc/hastreamer-cctv/plane-session.kid). В нём целое число
(1 при первом запуске), а публикуемый идентификатор имеет вид k<эпоха>. Копируйте его
вместе с ключом.
Ротация — это действие API, а не параметр конфигурации. Запрос
POST /api/v1/admin/keys/rotate (суперадминистратор) либо увеличивает эпоху — старый
идентификатор исчезает из публикуемого JWKS, и выданные ранее токены перестают проходить выбор
ключа, — либо, в форме восстановления после компрометации, заменяет сам ключевой материал
(новая пара ключей с правами 0600 плюс увеличение эпохи). Ключи управления нодами при этом не
затрагиваются.
Через API этот параметр не редактируется: приватный ключевой материал не ездит по настройкам.
Файл обязан быть 0600 root:root — тот, кто может его прочитать, может выпустить действительную
сессию от имени любого пользователя.
[keys] tunnel_control_key_pem#
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
tunnel_control_key_pem |
Абсолютный путь к приватному ключу ES256 | <каталог plane.toml>/tunnel-control.pem |
рестарт |
Отдельная личность ES256 для управления ретрансляторами и агентами со стороны Plane. Она
намеренно независима от сессий браузера: ротация ключа сессий не должна отрезать ретрансляторы,
изолированные по входящему трафику. Файл создаётся автоматически с правами 0600 при первом
запуске — это одна из причин, по которым сервис работает от root в своём каталоге конфигурации.
Правила старта те же, что и у ключа сессий: нет файла — сгенерируется, повреждён — запуск
прерывается, права шире 0600 — запуск прерывается.
Рядом Plane создаёт 32-байтный корень выдачи туннелей: тот же путь с расширением,
заменённым на .provisioning.key (/etc/hastreamer-cctv/tunnel-control.provisioning.key,
сырые байты, права 0600). Из этого корня выводится каждая конфигурация ретранслятора и
агента, которую выдаёт Plane. При его потере уже развёрнутые туннели продолжат работать, но их
конфигурации больше нельзя воспроизвести: часть конфигураций невозможно вывести из текущего
корня выдачи, и такие экземпляры остаются изолированными. В журнале это предупреждение вида:
some tunnel configs cannot be reproduced with the current provisioning root; affected instances remain isolated
[keys] signing_key_pem и [keys] node_bearer_key_pem больше не существуют, и из-за
deny_unknown_fields в таблице [keys] они прерывают разбор файла. При переносе старой
конфигурации переименуйте signing_key_pem в session_key_pem, а node_bearer_key_pem
просто удалите: подписанты управления для каждой ноды теперь хранятся в её собственной строке
базы данных.
[jwks] session_ttl_secs#
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
session_ttl_secs |
Целое без знака, секунды. Проверки диапазона нет | 3600 (1 час) |
рестарт |
Срок жизни выданного JWT сессии — фактически то, сколько времени вход остаётся действительным до повторной аутентификации.
Разбор принимает 0 (каждый выданный токен окажется просроченным немедленно) и сколь угодно
большие значения. Типичный выбор: 3600 — один час; 28800 — восемь часов для дежурной смены.
Индивидуального отзыва сессий на сервере нет: список выданных токенов не хранится. Если нужно
сократить окно для украденного токена, есть два рычага — уменьшить это значение либо провести
ротацию ключа сессий (POST /api/v1/admin/keys/rotate). Из админки параметр не меняется.
TLS собственного слушателя Plane#
[tls] mode — пять режимов доступа#
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
mode |
"off", "external", "pem", "self_signed", "acme" |
"off" (в том числе если таблицы [tls] нет вовсе) |
рестарт |
| Значение | Слушатель | Схема public_url |
Ещё требуется | Когда применять |
|---|---|---|---|---|
"off" |
открытый HTTP | http:// |
— | внутренняя сеть, TLS не используется нигде |
"external" |
открытый HTTP | https:// |
— | TLS терминирует реверс-прокси или балансировщик впереди |
"pem" |
HTTPS силами Plane | https:// |
cert, key |
у вас есть собственный сертификат: корпоративного УЦ, wildcard или купленный |
"self_signed" |
HTTPS силами Plane | https:// |
hosts |
HTTPS без удостоверяющего центра; браузер будет предупреждать |
"acme" |
HTTPS силами Plane | https:// |
hosts, желательно acme_contact |
публичный домен и автоматические сертификаты Let's Encrypt |
external есть в системе, но его нет в примере конфигурацииПоставляемый файл-пример упоминает четыре режима, однако external — полноценный
поддерживаемый режим, доступный в том числе в админке. В нём Plane отдаёт открытый HTTP на
listen, но объявляет https:// в public_url, поэтому все генерируемые ссылки,
перенаправления и обратные вызовы нод получаются HTTPS, а шифрует трафик прокси впереди.
За терминирующим TLS прокси используйте именно external, а не off: режим off откажется
принимать https:// в public_url с сообщением
tls.mode=off requires an http:// public_url.
Неизвестное значение — ошибка разбора файла. Несовпадение схемы public_url разобрано в
public_url.
[tls] cert и [tls] key — режим pem#
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
cert |
Путь к цепочке сертификатов в PEM: сначала конечный сертификат, затем промежуточные | не задан | рестарт |
key |
Путь к приватному ключу этой цепочки в PEM | не задан | рестарт |
[tls] mode = "pem" cert = "/etc/hastreamer-cctv/plane-cert.pem" key = "/etc/hastreamer-cctv/plane-key.pem"
Plane читает оба файла один раз при старте, каждый — с ограничением 1 МиБ. При mode = "pem"
оба ключа обязательны и не должны быть пустыми:
invalid plane network configuration: tls.mode=pem requires cert and key paths
Отсутствующий файл или несоответствие сертификата ключу — фатальная ошибка старта. Параметр
hosts в этом режиме не требуется и не проверяется: имена берутся из SAN вашего сертификата.
pem — ваша задачаНикакой логики обновления в этом режиме нет. Заменили файлы — перезапустите сервис.
Права на файл ключа Plane здесь не проверяет (проверка 0600 касается только таблицы
[keys]), поэтому выставьте их сами:
sudo chown root:root /etc/hastreamer-cctv/plane-key.pem sudo chmod 0600 /etc/hastreamer-cctv/plane-key.pem
Из админки редактируются только пути, но не содержимое файлов.
[tls] hosts#
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
hosts |
Массив строк. DNS-имя: до 253 символов, каждая метка до 63 символов, только буквы, цифры и -, без - в начале и конце. Для self_signed допустимы IP-литералы, для acme — нет. Маски вида *.example.com отвергаются в обоих режимах |
[] |
рестарт |
Набор имён, под которыми Plane выпускает сертификат: SAN для self_signed и список доменов,
запрашиваемых у удостоверяющего центра, для acme.
hosts = ["cctv.example.com", "cctv-alt.example.com"]
Проверки действуют только в режимах self_signed и acme:
tls.mode=self_signed requires at least one hostname tls.hosts must include the public_url hostname "cctv.example.com" invalid TLS DNS hostname "cctv_example.com" ACME HTTP-01 requires DNS hostnames, not IP literals
В режиме self_signed сохранённая личность, набор SAN которой перестал соответствовать
hosts, перевыпускается при следующем запуске. Так же перевыпускается сертификат, оставшийся
от предыдущей работы в режиме acme. В админке список задаётся через запятую.
[tls] store_dir#
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
store_dir |
Путь к каталогу, куда Plane складывает выпущенный им TLS-материал | <каталог plane.toml>/plane-tls, то есть /etc/hastreamer-cctv/plane-tls |
рестарт |
Каталог используется только в режимах self_signed и acme, в off, external и pem он не
задействован. Внутри:
identity.pem— обслуживаемая сейчас пара «сертификат + приватный ключ», права0600;account.pem— ключ учётной записи ACME, только в режимеacme, права0600.
Каталог создаётся при отсутствии и принудительно приводится к правам 0700. Он содержит живой
приватный ключ TLS — не делайте его доступным для чтения всем.
[tls] acme_listen#
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
acme_listen |
Адрес сокета IP:порт, по тем же правилам, что и listen. Обязан отличаться от listen |
"0.0.0.0:80" |
рестарт |
Отдельный нешифрованный слушатель, единственная задача которого — отвечать на проверку
ACME HTTP-01 по адресу /.well-known/acme-challenge/<токен>. Удостоверяющий центр обращается
за этим URL строго по порту 80: для HTTP-01 это не опция, и именно поэтому Plane работает
от root.
tls.acme_listen must be an IP socket address tls.acme_listen and the main listen address must be different
На порту 80 не должно быть другого веб-сервера, а межсетевой экран и NAT обязаны пропускать к нему запросы из интернета — в том числе после установки, при каждом продлении сертификата. Слушатель проверок ограничен: одновременно обслуживается не более 64 токенов, и он останавливается вместе с Plane.
[tls] acme_directory#
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
acme_directory |
URL, обязан начинаться с https://. Единственное исключение — http://127.0.0.1… для локальных тестовых центров |
"https://acme-v02.api.letsencrypt.org/directory" |
рестарт |
Адрес каталога ACME, в котором Plane регистрируется и заказывает сертификаты.
ACME directory must use HTTPS (loopback HTTP is test-only)
Перед боевым выпуском укажите здесь тестовый (staging) каталог удостоверяющего центра: так вы проверите DNS и доступность порта 80, не расходуя лимиты боевого выпуска. После успешной проверки верните значение по умолчанию и перезапустите сервис.
[tls] acme_contact#
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
acme_contact |
Строка с адресом электронной почты. Формат самим Plane не проверяется | "" |
рестарт |
Контакт, регистрируемый в удостоверяющем центре для уведомлений об истечении срока и проблемах; отправляется только если строка непуста. Пустое значение допустимо, но тогда вы не получите предупреждений об истечении сертификата. Некорректный адрес может отвергнуть сам центр при создании учётной записи.
Что в режиме ACME не настраивается#
Эти величины зашиты в код, параметров для них нет:
| Поведение | Значение |
|---|---|
| Обновлять сертификат, если до истечения осталось менее | 30 суток |
| Периодичность проверки необходимости обновления | каждые 12 часов |
| Пауза после неудачной попытки обновления | 1 час |
| Минимальная пауза, пока обслуживается временный сертификат | 2 минуты |
| Тайм-аут рукопожатия TLS | 10 секунд |
| Одновременных рукопожатий | 256 |
| Одновременных проверок ACME | 64 |
Предельный размер чтения identity.pem и account.pem |
1 МиБ и 128 КиБ |
Если в режиме acme сертификат не удалось получить при старте (домен не резолвится, порт 80
закрыт, центр недоступен), Plane всё равно поднимется: он отдаст временный
самоподписанный сертификат из памяти (браузер покажет предупреждение) и продолжит попытки.
Админка отражает это признаком temporary_self_signed: true. Временный сертификат никогда не
сохраняется в identity.pem, поэтому в режиме acme в этом файле лежит только настоящий
сертификат от удостоверяющего центра.
Чего в этом файле НЕТ#
Ни одного из перечисленных ниже параметров в plane.toml не существует. Добавление любого из
них — не «неподдерживаемая настройка», а остановка запуска из-за неизвестного ключа.
| Что обычно ищут | Как обстоит дело |
|---|---|
| Секрет подписи сессий | Такого ключа нет. Сессии подписываются файлом ключа ES256 из [keys] session_key_pem |
| Расписание ротации ключа | Такого ключа нет. Ротация — действие суперадминистратора через API: POST /api/v1/admin/keys/rotate |
| Ограничение числа сессий или одновременных входов | Не реализовано. Единственное ограничение сессии — [jwks] session_ttl_secs |
| Политика паролей: длина, сложность, срок, блокировка | Параметров нет. Чистая база создаёт суперадминистратора admin с паролем SirinVideo — смените его при первом входе. Восстановление доступа: sudo hastreamer-plane /etc/hastreamer-cctv/plane.toml --reset-superadmin |
| Список доверенных прокси или доверенных IP | Такого ключа нет — см. предупреждение ниже |
| CORS и список разрешённых источников | Такого ключа нет, и слоя CORS тоже нет: API по построению работает в пределах одного источника |
| Тайм-ауты запроса, чтения, записи, keep-alive | Параметров нет. Зашиты: ожидание подключения к базе 10 с, простой подключения 2 мин, предельное время жизни подключения 30 мин, мягкое завершение HTTP 5 с, блокировка записи настроек 10 с, рукопожатие TLS 10 с, исходящее подключение 5 с |
Точка сбора метрик Prometheus (/metrics) |
Не публикуется. Метрики собираются внутри и отдаются через аутентифицированный API и админку, а не через открытый порт для сбора |
| Срок хранения событий, обслуживание секций базы | Не здесь. Обслуживание секций выполняется одним проходом при старте, дальше ежечасно, и это не настраивается. Срок хранения — продуктовая настройка в базе |
| Путь к файлу журнала, выбор приёмников журнала | Параметров нет. Журнал уходит в stderr и далее в journald (journalctl -u hastreamer-cctv-plane), плюс ограниченное кольцо в памяти для админки. Фильтры по подсистемам — только переменная RUST_LOG |
| Число рабочих потоков или ядер | Такого ключа нет. Ограничивайте процесс средствами юнита: MemoryHigh, MemoryMax, CPUQuota, TasksMax |
| Настройки DVR, записи и медиа | Принадлежат ноде и задаются во Fleet |
Plane берёт первый переход из заголовка X-Forwarded-For как IP-адрес клиента для записей
аудита и для ограничения частоты запросов. Списка доверенных прокси не существует, и проверить
этот заголовок нечем.
Отсюда следствие: если Plane доступен из сети напрямую, клиент может подделать адрес, который попадёт в аудит и в счётчики ограничения частоты. Поэтому:
- ставьте Plane только за тот прокси, которым управляете сами;
- настройте прокси так, чтобы он перезаписывал
X-Forwarded-For, а не дописывал в него; - привязывайте
listenк127.0.0.1, если прокси работает на том же хосте, чтобы напрямую Plane не отвечал никому.
Аутентификация ретрансляторов намеренно игнорирует X-Forwarded-For и использует адрес
сокета-собеседника, поэтому на неё подделка заголовка не влияет.
Продуктовые настройки, которые живут в базе#
Эти значения создаются при первом запуске, правятся из админки и в plane.toml не выносятся:
| Настройка | Значение по умолчанию |
|---|---|
audit.retention_days — срок хранения аудита |
365 суток |
ingest.rate_default — частота приёма событий на один токен |
100 событий в секунду |
blob.threshold_kib — порог вынесения вложения в блоб |
16 КиБ |
undeclared_blob.policy — что делать с необъявленным вложением |
store |
onvif.interval_timeout_secs — тайм-аут ONVIF-интервала |
10 секунд |
dvr_export_max_secs — предельная длительность экспорта архива |
задаётся через тот же API настроек, допустимо от 60 с до абсолютного предела экспорта |
Переменные окружения#
Переменные установщика#
Установщик задаёт вопросы интерактивно; заданная переменная окружения снимает соответствующий вопрос, поэтому тот же скрипт выполняет установку без участия оператора.
Установщик никогда не перезаписывает существующий /etc/hastreamer-cctv/plane.toml — он
печатает plane.toml exists — left unchanged (delete it to re-run guided setup). При запуске
без терминала и без заданных переменных используются значения по умолчанию.
| Переменная | Какой параметр задаёт | Допустимые значения | Если не задана |
|---|---|---|---|
HASTREAMER_PLANE_TLS_MODE |
[tls] mode |
http → off, self-signed → self_signed, own-cert → pem, acme → acme; принимается и номер пункта меню от 1 до 4 |
http. Неизвестное значение прерывает установку: invalid HASTREAMER_PLANE_TLS_MODE='…' (choose one of: http self-signed own-cert acme) |
HASTREAMER_PLANE_HOSTNAME |
[tls] hosts (одна запись, для self_signed и acme) и хост в собираемом public_url |
Имя хоста или IP | Результат hostname -f; если пусто или localhost — первый адрес из hostname -I; иначе 127.0.0.1 |
HASTREAMER_PLANE_PORT |
Порт в listen (всегда записывается как 0.0.0.0:<порт>) |
Целое 1–65535, иначе invalid port: … |
8080 для http; 443 для self-signed, own-cert и acme |
HASTREAMER_PLANE_PUBLIC_URL |
public_url |
Полный origin-URL | <схема>://<хост>, если порт стандартный для схемы, иначе <схема>://<хост>:<порт> |
HASTREAMER_PLANE_CERT |
[tls] cert, только для own-cert |
Путь | /etc/hastreamer-cctv/plane-cert.pem |
HASTREAMER_PLANE_KEY |
[tls] key, только для own-cert |
Путь | /etc/hastreamer-cctv/plane-key.pem |
HASTREAMER_PLANE_ACME_EMAIL |
[tls] acme_contact, только для acme |
Адрес почты | admin@<хост> |
HASTREAMER_PLANE_EVIDENCE_DIR |
[attachment_storage] dir |
Абсолютный путь, иначе must be an absolute path |
/var/lib/hastreamer-cctv/attachments. Учитывается только если plane.toml ещё не существует |
Значения, которые установщик пишет всегда и переменной не переопределяются: database_url
(собирается из сгенерированного пароля), log_level = "info", db_pool_max = 16,
[keys] session_key_pem, [jwks] session_ttl_secs = 3600, [tls] acme_listen = "0.0.0.0:80".
Установка без вопросов:
HASTREAMER_PLANE_TLS_MODE=acme \ HASTREAMER_PLANE_HOSTNAME=cctv.example.com \ HASTREAMER_PLANE_ACME_EMAIL=admin@example.com \ HASTREAMER_PLANE_EVIDENCE_DIR=/srv/cctv-evidence \ sudo -E installers/hastreamer-cctv-plane/install.sh
Переменные, читаемые работающим сервисом#
| Переменная | Что делает | По умолчанию |
|---|---|---|
RUST_LOG |
Полностью замещает log_level выражением фильтра журнала, например info,cctv_plane::reconciler=debug. Пока переменная задана и непуста, элемент управления уровнем журнала в админке заблокирован и возвращает 409 rust_log_override. Некорректное выражение фатально при старте |
не задана — действует log_level |
Задаётся дополнением к юниту:
sudo systemctl edit hastreamer-cctv-plane # в открывшемся редакторе: # [Service] # Environment=RUST_LOG=info,cctv_plane::tls=debug sudo systemctl restart hastreamer-cctv-plane
DATABASE_URL работающим сервисом не используется: она нужна только тестовому стенду
самой сборки. Адрес базы берётся исключительно из plane.toml.
HASTREAMER_ACTIVATION_CODE существует в общих библиотеках установщиков, но установщик Plane
не вызывает помощник активации: активация относится к лицензируемым продуктам — ноде, агенту и
ретранслятору, — а не к контрол-плейну.
Файлы и каталоги, которыми владеет Plane#
| Путь | Права и владелец | Кто создаёт | Содержимое |
|---|---|---|---|
/etc/hastreamer-cctv/ |
0755 root:root |
установщик | каталог конфигурации |
/etc/hastreamer-cctv/plane.toml |
0640 root:root |
установщик и админка | СЕКРЕТ — пароль базы, ключ доступа S3 |
/etc/hastreamer-cctv/plane-session.pem |
0600 root:root |
установщик или Plane при первом запуске | СЕКРЕТ — ключ подписи сессий ES256 |
/etc/hastreamer-cctv/plane-session.kid |
0644 root:root |
Plane | эпоха идентификатора ключа — не секрет, но восстанавливать вместе с ключом |
/etc/hastreamer-cctv/tunnel-control.pem |
0600 root:root |
Plane при первом запуске | СЕКРЕТ — личность управления ретрансляторами и агентами |
/etc/hastreamer-cctv/tunnel-control.provisioning.key |
0600 root:root |
Plane при первом запуске | СЕКРЕТ — 32-байтный корень выдачи туннелей |
/etc/hastreamer-cctv/plane-tls/ |
0700 root:root |
Plane в режимах self_signed и acme |
хранилище TLS, путь задаётся в [tls] store_dir |
/etc/hastreamer-cctv/plane-tls/identity.pem |
0600 root:root |
Plane | СЕКРЕТ — обслуживаемый сертификат вместе с приватным ключом |
/etc/hastreamer-cctv/plane-tls/account.pem |
0600 root:root |
Plane в режиме acme |
СЕКРЕТ — ключ учётной записи ACME |
/etc/hastreamer-cctv/plane-cert.pem и plane-key.pem |
ключ — 0600, за права отвечает оператор |
оператор, режим pem |
СЕКРЕТ (ключ) — ваши сертификат и ключ |
/etc/hastreamer-cctv/.dbpass |
0600 root:root |
установщик | СЕКРЕТ — сгенерированный пароль роли PostgreSQL |
/var/lib/hastreamer-cctv/ |
0750 root:root |
установщик | рабочий каталог Plane |
/var/lib/hastreamer-cctv/attachments/ или каталог из [attachment_storage] dir |
0750 root:root |
установщик и Plane | блобы материалов событий и превью |
/usr/local/bin/hastreamer-plane |
0755 root:root |
установщик | исполняемый файл |
/etc/systemd/system/hastreamer-cctv-plane.service |
0644 root:root |
установщик | юнит systemd |
База cctv и роль hastreamer_cctv в PostgreSQL |
— | установщик и миграции Plane | всё состояние системы |
Права 0600 Plane проверяет сам — на обоих файлах из [keys] и на
tunnel-control.provisioning.key; при более широких правах запуск прерывается сообщением
private key <путь> must have mode 0600. Своё хранилище TLS он создаёт сам с правами 0700 и
0600. За остальные файлы — plane.toml, .dbpass и ваши собственные сертификат с ключом в
режиме pem — отвечает оператор.
sudo find /etc/hastreamer-cctv -type f \( -perm /o+rwx -o -perm /g+w \) -ls
Ожидаемый вывод — пустой. Любая напечатанная строка означает файл, который читает или может изменить кто-то кроме root.
Что обязательно бэкапить#
Минимальный набор, которого достаточно, чтобы восстановить работающий Plane. Полная процедура — в разделе Резервное копирование.
| # | Что | Почему без этого не восстановиться |
|---|---|---|
| 1 | База PostgreSQL cctv (pg_dump -Fc) |
Все организации, пользователи, гранты, камеры, ноды, события, интервалы, аудит и продуктовые настройки |
| 2 | /etc/hastreamer-cctv/plane.toml |
Пароль базы, адрес и режим TLS, путь к материалам событий, ключ доступа S3 |
| 3 | /etc/hastreamer-cctv/plane-session.pem и plane-session.kid |
Без них Plane не остановится — он молча создаст новую личность, разлогинив всех пользователей и обесценив закешированный нодами JWKS |
| 4 | /etc/hastreamer-cctv/tunnel-control.pem и tunnel-control.provisioning.key |
Без корня выдачи Plane не может воспроизвести конфигурации ретрансляторов и агентов; такие экземпляры остаются изолированными |
| 5 | Каталог материалов событий — [attachment_storage] dir, по умолчанию /var/lib/hastreamer-cctv/attachments |
Блобов нет в базе, в ней только метаданные. Восстановление одной базы даёт битые кадры и вложения у каждого события. Пропустить можно только при бэкенде S3, если бакет копируется отдельно |
Копия каталога конфигурации целиком закрывает пункты 2, 3 и 4 сразу:
sudo tar czf /root/cctv-etc-$(date +%F).tgz -C / etc/hastreamer-cctv sudo chmod 0600 /root/cctv-etc-*.tgz # в архиве приватные ключи и пароли
Настоятельно рекомендуется добавить:
/etc/hastreamer-cctv/.dbpass;/etc/hastreamer-cctv/plane-tls/account.pem— восстановим сам, но повторное использование учётной записи ACME экономит лимиты на создание новых;- ваши
plane-cert.pemиplane-key.pemв режимеpem— Plane их не воспроизведёт; - юнит
/etc/systemd/system/hastreamer-cctv-plane.serviceили его дополнение, если вы его правили.
Не нужны: plane-tls/identity.pem (самоподписанный перевыпустится, ACME закажет заново),
исполняемый файл и превью (они выводятся заново из сохранённых блобов).
Если восстановить plane.toml, но не .dbpass, и затем заново запустить установщик, он
сгенерирует новый пароль и выполнит ALTER ROLE … PASSWORD …, при этом отказавшись трогать уже
существующий plane.toml. В результате восстановленный database_url перестанет
аутентифицироваться. Либо сохраняйте .dbpass, либо после повторного запуска установщика
верните роли пароль из plane.toml:
sudo -u postgres psql -c "ALTER ROLE hastreamer_cctv LOGIN PASSWORD 'пароль-из-plane.toml'"
Полный пример: боевая установка с ACME на публичном домене#
Локальное хранилище материалов, PostgreSQL на том же сервере, сертификаты Let's Encrypt. Прокомментирована каждая строка. Все секреты — заглушки: замените их и никогда не коммитьте этот файл.
# /etc/hastreamer-cctv/plane.toml — контрол-плейн SirinVideo CCTV, боевой пример. # Владелец root:root, права 0640. Содержит пароль базы: не давать доступ на чтение всем. # Читается ОДИН раз при старте — после любой правки: systemctl restart hastreamer-cctv-plane # ── Слушатель ─────────────────────────────────────────────────────────────────────────────── # Сокет, который открывает Plane. Только IP:порт (имя хоста отвергается). 0.0.0.0 — все # интерфейсы; 127.0.0.1 — только если впереди локальный реверс-прокси. Если удалить строку, # действует зашитое 127.0.0.1:8080 (петля), а НЕ 0.0.0.0. listen = "0.0.0.0:443" # Канонический адрес для браузеров и нод. Только origin — без пути, параметров запроса и # учётных данных. Обязан быть https://, потому что ниже включён режим TLS, и его хост обязан # присутствовать в tls.hosts. Отсюда строятся ссылки, перенаправления и обратные вызовы нод. public_url = "https://cctv.example.com" # ── База данных (обязательно, значения по умолчанию нет) ──────────────────────────────────── # Plane владеет этой схемой и применяет к ней миграции при старте. Пароль лежит здесь открытым # текстом — из-за этого права 0640. Помимо пула Plane открывает ОДНО дополнительное соединение # для аренды-одиночки, поэтому max_connections в PostgreSQL считайте как db_pool_max + 1. database_url = "postgres://hastreamer_cctv:ЗАМЕНИТЕ-ПАРОЛЬ@127.0.0.1:5432/cctv" # Предельный размер пула подключений. Минимум 5: четыре подключения резервируются под пути # контрол-плейна, пока идёт приём событий. По умолчанию 16. Из админки не меняется. db_pool_max = 16 # ── Журнал ────────────────────────────────────────────────────────────────────────────────── # Одно из debug | info | warn | error — закрытый набор, а не выражение RUST_LOG. Уходит в # stderr → journald и в ограниченное кольцо в памяти, которое показывает админка. Заданная # переменная RUST_LOG замещает этот параметр И блокирует управление уровнем в админке. log_level = "info" # ── Материалы событий (принадлежат Plane; видеоархив принадлежит ноде и настраивается во Fleet) # Сюда попадают кадры по движению и ONVIF, вложения к событиям и их превью. Разместите на # просторном диске. Путь обязан быть абсолютным и выделенным (без "." и "..", никогда не "/"). # Перенести каталог позже можно, но сначала прочитайте правила переноса: админка откажет, # пока в текущем каталоге лежат блобы, а правка файла руками стартует на новом каталоге и # поднимет оповещение об оставшихся в старом материалах (обратимо). [attachment_storage] backend = "local" dir = "/var/lib/hastreamer-cctv/attachments" # Политика заполнения для каталога выше. Пороги — проценты от ёмкости, обязателен строгий # порядок 1 <= low < high < critical <= 99 (зазор между ними — намеренный гистерезис). [event_storage_policy] # Необязательный жёсткий потолок в байтах. Для локального бэкенда можно не задавать — тогда # берётся ёмкость файловой системы; если задан, то не меньше 64 МиБ. Для S3 обязателен. # Здесь показано 200 ГиБ: max_bytes = 214748364800 # "disabled" — давление никогда не удаляет историю (безопасное значение по умолчанию). # "oldest_purge_day" — РАЗРУШИТЕЛЬНО: при цикле давления «высокий → низкий» списывается целиком # самое старое поколение суток очистки. Байтовый бюджет сам по себе удалять не разрешает. volume_retention = "disabled" low_watermark_percent = 75 # ниже этого порога давление снимается high_watermark_percent = 85 # на этом пороге и выше давление включается critical_watermark_percent = 95 # жёсткое давление # ── Личности подписи (файлы приватных ключей; через API не передаются никогда) ─────────────── [keys] # Ключ ES256 в PKCS#8 PEM, которым подписываются JWT сессий; ноды проверяют билеты по его # открытой половине через /.well-known/jwks.json. Если файла нет — будет создан с правами 0600, # но ПОТЕРЯННЫЙ файл означает НОВУЮ личность (все пользователи разлогинены), поэтому его надо # бэкапить. При правах шире 0600 Plane откажется стартовать. # По умолчанию, если строку убрать: <каталог конфига>/signing.pem session_key_pem = "/etc/hastreamer-cctv/plane-session.pem" # Отдельная личность ES256 для управления ретрансляторами и агентами, чтобы ротация ключа # сессий не отрезала ретрансляторы. Создаётся автоматически с правами 0600 рядом с plane.toml # вместе с 32-байтным tunnel-control.provisioning.key, из которого выводятся ВСЕ конфигурации # туннелей — бэкапьте оба файла. # По умолчанию, если строку убрать: <каталог конфига>/tunnel-control.pem tunnel_control_key_pem = "/etc/hastreamer-cctv/tunnel-control.pem" # ── Время жизни сессии ────────────────────────────────────────────────────────────────────── [jwks] # Срок жизни выданного JWT сессии в секундах. По умолчанию 3600 (1 час). Диапазон не # проверяется — 0 просрочит каждый токен немедленно. Индивидуального отзыва сессий нет: # сокращайте это значение либо делайте ротацию ключа (POST /api/v1/admin/keys/rotate). session_ttl_secs = 3600 # ── TLS собственного слушателя API и админки ──────────────────────────────────────────────── # Режимы: off (открытый HTTP, public_url http://) · external (открытый HTTP, но public_url # https:// — для терминирующего TLS реверс-прокси) · pem (свои сертификат и ключ) · # self_signed (самоподписанный, браузер предупреждает) · acme (автоматический Let's Encrypt). # Любой режим, кроме off, требует https:// в public_url. [tls] mode = "acme" # Набор имён сертификата. Для acme это ОБЯЗАТЕЛЬНО публичные DNS-имена, указывающие на ЭТОТ # сервер (IP-литералы и маски отвергаются), и хост из public_url обязан быть в списке. hosts = ["cctv.example.com"] # Куда Plane складывает выпущенный им TLS-материал: identity.pem (обслуживаемая пара # «сертификат + ключ») и account.pem (ключ учётной записи ACME). Каталог приводится к 0700, # файлы — к 0600. По умолчанию, если строку убрать: <каталог конфига>/plane-tls store_dir = "/etc/hastreamer-cctv/plane-tls" # Контакт для уведомлений удостоверяющего центра об истечении срока. Пустая строка допустима, # но тогда предупреждений не будет. acme_contact = "admin@example.com" # ОТДЕЛЬНЫЙ нешифрованный слушатель, отвечающий на проверку HTTP-01 по адресу # /.well-known/acme-challenge/<токен>. Порт 80 должен быть доступен из интернета и свободен от # любого другого веб-сервера. Обязан отличаться от listen. По умолчанию "0.0.0.0:80". acme_listen = "0.0.0.0:80" # Каталог ACME. Только https:// (исключение — http://127.0.0.1 для тестовых центров). Для # пробного прогона укажите здесь staging-каталог, чтобы не расходовать боевые лимиты. # По умолчанию — боевой каталог Let's Encrypt, показанный ниже. acme_directory = "https://acme-v02.api.letsencrypt.org/directory" # Продление автоматическое и НЕ настраивается: обновление при остатке менее 30 суток, проверка # каждые 12 часов, повтор раз в час после неудачи. Если сертификат не удалось получить при # старте, Plane отдаёт ВРЕМЕННЫЙ самоподписанный сертификат из памяти и продолжает попытки.
Минимальный пример: за реверс-прокси, режим external#
Прокси терминирует TLS на https://cctv.example.com и передаёт запросы в Plane по петлевому
интерфейсу. Все не указанные параметры берут значения по умолчанию.
# /etc/hastreamer-cctv/plane.toml — минимальная внутренняя установка (TLS терминирует прокси). # Права 0640 root:root — пароль базы лежит здесь так же, как и в боевом примере. # Привязка только к петле: напрямую до Plane не должен доставать никто, кроме локального # прокси. Это важно ещё и потому, что Plane принимает ПЕРВЫЙ переход X-Forwarded-For как адрес # клиента для аудита и ограничения частоты — настройте прокси на ПЕРЕЗАПИСЬ этого заголовка. listen = "127.0.0.1:8080" # Адрес, который набирают пользователи, — адрес ПРОКСИ, https, хотя сам Plane говорит по HTTP. public_url = "https://cctv.example.com" # Обязательно. Значения по умолчанию нет. database_url = "postgres://hastreamer_cctv:ЗАМЕНИТЕ-ПАРОЛЬ@127.0.0.1:5432/cctv" [tls] # "external": Plane отдаёт открытый HTTP, объявляя при этом https:// в public_url, потому что # шифрует прокси впереди. За терминирующим TLS прокси нужен именно этот режим, а НЕ "off": # "off" отверг бы https://-адрес сообщением "tls.mode=off requires an http:// public_url". mode = "external" # Всё остальное — по умолчанию: # log_level = "info" # db_pool_max = 16 # [attachment_storage] backend = "local", dir = "/var/lib/hastreamer-cctv/attachments" # [event_storage_policy] пороги 75/85/95, max_bytes не задан, volume_retention = "disabled" # [keys] session_key_pem = "/etc/hastreamer-cctv/signing.pem" (создастся 0600) # tunnel_control_key_pem = "/etc/hastreamer-cctv/tunnel-control.pem" (создастся 0600) # [jwks] session_ttl_secs = 3600
Если прокси нет вообще и установка живёт в изолированной локальной сети по открытому HTTP,
уберите таблицу [tls] целиком и используйте http:// в public_url:
listen = "0.0.0.0:8080" public_url = "http://10.0.0.10:8080" # http://, потому что tls.mode по умолчанию "off" database_url = "postgres://hastreamer_cctv:ЗАМЕНИТЕ-ПАРОЛЬ@127.0.0.1:5432/cctv"
Справочник сообщений об ошибках#
Каждое сообщение из списка ниже печатается в виде
hastreamer-plane: fatal: invalid plane network configuration: <сообщение> и видно в
journalctl -u hastreamer-cctv-plane. Те же строки возвращаются с кодом 400 при сохранении
из админки.
db_pool_max must be at least 5 so short event-ingest transactions leave four database connections available listen must be an IP socket address (hostnames are not bind addresses) public_url must be an HTTP(S) origin public_url must be an HTTP(S) origin without credentials, path, query or fragment tls.mode=off requires an http:// public_url tls.mode=<mode> requires an https:// public_url tls.mode=pem requires cert and key paths tls.mode=<mode> requires at least one hostname tls.hosts must include the public_url hostname "<host>" invalid TLS DNS hostname "<host>" tls.acme_listen must be an IP socket address tls.acme_listen and the main listen address must be different ACME HTTP-01 requires DNS hostnames, not IP literals ACME directory must use HTTPS (loopback HTTP is test-only) local event storage directory must be a dedicated absolute path below the filesystem root S3 event storage endpoint must be schemeless host[:port] S3 event storage endpoint must be schemeless host[:port] without credentials or path invalid S3 event storage endpoint S3 event storage bucket must be a non-empty bucket name without path separators S3 event storage region must not be empty S3 event storage credentials must not be empty virtual-host S3 addressing requires a DNS hostname endpoint and a DNS-compatible bucket name event storage watermarks must satisfy 1 <= low < high < critical <= 99 event storage max_bytes must be at least 64 MiB event storage max_bytes must fit the PostgreSQL bigint accounting domain S3 event storage requires an explicit max_bytes budget
Остальные фатальные ошибки старта:
reading /etc/hastreamer-cctv/plane.toml: <ошибка ввода-вывода> # файла нет или он нечитаем
parsing /etc/hastreamer-cctv/plane.toml: <ошибка toml> # синтаксис, неизвестный ключ,
# отсутствующий database_url или
# public_url, неверное значение
# перечисления
private key /etc/hastreamer-cctv/plane-session.pem must have mode 0600
Ошибки, возможные только при сохранении из админки (PATCH /api/v1/settings/plane) и никогда
не встречающиеся при старте:
400 no_settings_supplied 400 invalid_public_url 400 dvr_export_max_secs_out_of_range 409 settings_busy 409 rust_log_override 409 event_storage_rotation_blocked 400 destructive_volume_retention_acknowledgement_required 502 <детали проверки бэкенда> 500 plane_config_unreadable:<ошибка> "/etc/hastreamer-cctv/plane.toml changed after the settings form was loaded" S3 access and secret keys must be supplied together S3 event storage credentials are required when selecting the backend
После правки — проверка#
sudo systemctl restart hastreamer-cctv-plane systemctl is-active hastreamer-cctv-plane # ожидается: active sudo journalctl -u hastreamer-cctv-plane -n 80 --no-pager
Журнал исправного запуска содержит по порядку: hastreamer-plane booting → attachment storage
→ connected to PostgreSQL → migrations applied → seeds applied →
event evidence backend ready → initial partition maintenance ok →
Plane signing identities ready → serving с фактическими listen и tls_mode.
Ключевая строка — последняя:
INFO hastreamer_plane: serving listen=0.0.0.0:443 tls_mode="acme"
Если её нет, а сервис перезапускается каждые 3 секунды — в файле ошибка, её текст находится в
строке fatal:.
Смежные разделы: Установка с нуля: Plane, Резервное копирование.