node.conf — все директивы
Полный справочник конфигурации ноды: что можно править руками, что принадлежит Plane, и почему опечатка останавливает сервис.
Вся конфигурация ноды — один файл /etc/hastreamer-cctv-node/node.conf. Его читает бинарь
/usr/local/bin/hastreamer-cctv при старте и повторно — при перезагрузке конфигурации
(systemctl reload, то есть сигнал SIGHUP). Юнит systemd называется hastreamer-cctv-node.
Файл состоит из двух половин. Верхняя — системная: порты, TLS, сертификаты, политика доступа,
тюнинг. Она ваша, правится руками и сохраняется навсегда. Нижняя — область между маркерами
MANAGED BY cctv-plane: камеры, диски архива, настройка авторизации просмотра. Она принадлежит
Plane и восстанавливается при каждом старте.
Здесь описаны все 112 форм директив, которые понимает разборщик: 38 директив верхнего уровня и 74 вложенных. Директивы, которой нет в этом справочнике, не существует — нода такой файл не примет.
Модель владения файлом#
Область, которой владеет Plane#
Plane владеет фрагментом файла, ограниченным двумя строками-маркерами:
# >>> MANAGED BY cctv-plane — edits will be overwritten >>> storage archive /srv/cctv/archive { … } auth_backend { … } stream cam/<UUID>/main { … } # <<< MANAGED BY cctv-plane <<<
Маркеры сравниваются побайтово (тире в первом — длинное, «—»), пара должна встречаться ровно
один раз и в правильном порядке. Половина пары или второй экземпляр — жёсткая ошибка:
malformed cctv-plane managed region (need exactly one ordered marker pair).
Последняя одобренная Plane копия этой области лежит отдельным файлом —
/etc/hastreamer-cctv-node/cctv-node/managed.conf. При каждом старте нода собирает конфигурацию
заново: ваша системная часть плюс этот снимок, — и атомарно записывает результат обратно в
node.conf. Поэтому нода переживает недоступность Plane со всем набором камер целиком; поэтому
же правки внутри маркеров не выживают.
Исходов два, и второй хуже.
- Правка внутри маркеров — молча заменяется снимком при следующем старте или перезагрузке конфигурации. Ни ошибки, ни предупреждения: вашего текста просто больше нет.
- Блок
stream,auth_backend,principalилиpublic_pathsвне маркеров — нода отказывается принять файл:stream may appear only in the plane-managed region(несколько имён перечисляются через,). При старте этоhastreamer-cctv: managed config rejected: …и выход с кодом 1. systemd поднимает процесс заново каждые 2 секунды — нода уходит в цикл перезапусков и не отдаёт ни одного кадра.
Восстановление:
sudo journalctl -u hastreamer-cctv-node -n 50 --no-pager # причина отказа — в первых строках # дальше одно из двух: вернуть резервную копию… sudo cp /root/node.conf.bak /etc/hastreamer-cctv-node/node.conf # …либо открыть файл и удалить лишний блок sudo -e /etc/hastreamer-cctv-node/node.conf sudo systemctl restart hastreamer-cctv-node systemctl is-active hastreamer-cctv-node # active
Камеры добавляйте в Plane — Добавление камер, диски — Хранилища DVR.
Три класса владения#
| Класс | Директивы | Что произойдёт при ручной правке |
|---|---|---|
| Владеет Plane | stream, storage, auth_backend |
Возвращаются из снимка managed.conf при следующем старте или перезагрузке конфигурации. Менять их нужно в интерфейсе или API Plane |
| Владеет Plane, только внутри области | principal, public_paths |
Plane их сегодня не пишет, а вне маркеров они запрещены — на управляемой ноде пользоваться ими нельзя |
| Выдал Plane, но лежит снаружи | cctv |
Формально ваша половина файла, фактически — удостоверение ноды: любая правка ломает спаривание, см. cctv { } |
| Ваши (node-local) | всё, что не перечислено выше: слушатели, tls, certificate, acme_directory, acme_contact, trusted_proxies, allow_origins, auth, session_keys, все директивы тюнинга, cluster, template |
Сохраняются при перезапусках, перезагрузках конфигурации и любых обновлениях от Plane |
Есть ещё два ограничения на файл целиком: он должен быть корректным UTF-8 и не длиннее 128 МиБ,
иначе managed config is not UTF-8 или managed config exceeds 128 MiB cap. Ведущая метка порядка
байтов (BOM), которую дописывают некоторые редакторы под Windows, снимается разборщиком и файл не
ломает.
Plane пишет в область только хранилища, auth_backend и потоки. Настройка билетов
session_keys { } в область не попадает никогда, поэтому сроки жизни билетов вы правите руками
и они переживают любую синхронизацию с Plane.
Грамматика файла#
Директивы, блоки и точка с запятой#
Инструкция — это имя [аргументы] ; либо имя [аргументы] { вложенные инструкции }.
http 9443; # одно значение, обязательная точка с запятой trusted_proxies 10.0.0.0/8 ::1; # несколько значений в одной строке acme; # флаг без значения — означает «включено» tls { # блок: вместо ';' — фигурные скобки cert /etc/ssl/node.crt; key /etc/ssl/node.key; } storage archive /srv/cctv { # именованный блок: имя, аргументы и тело identity 3f2a9c14-1c0e-4f7b-9d21-8a6f0b4e5c77; }
Точка с запятой обязательна у каждой директивы со значением; без неё —
line 12: directive "http" not terminated with ';'. Блок точкой с запятой не закрывается: писать
tls { … }; не нужно (одиночная лишняя ; игнорируется, но полагаться на это не стоит).
Незакрытая скобка даёт unexpected end of file inside a block (missing '}'), лишняя —
line N: unmatched '}'.
Тело { } обязательно у cctv, tls, auth, auth_backend, session_keys, cluster,
principal, certificate, stream и template. У storage и vod тело необязательно:
storage fast /srv/dvr; — корректное однострочное объявление со значениями по умолчанию.
Комментарии#
# начинает комментарий до конца строки. Блочных комментариев нет. # внутри кавычек —
обычный символ, а не комментарий. Ваши комментарии сохраняются побайтово: при перезаписи файла
из Plane или через API заменяется только управляемая область.
Кавычки и экранирование#
Слово без кавычек кончается на пробеле, ;, {, } или #. Если значение содержит любой из
этих символов — возьмите его в двойные кавычки:
plane_url "https://cctv.example.com:8443"; input "rtsp://svc:pa$$;word@10.20.0.11:554/Streaming/Channels/101";
| Последовательность внутри кавычек | Результат |
|---|---|
\" |
символ " |
\\ |
символ \ |
любая другая \x |
сохраняется как есть, вместе с обратной косой чертой |
| перенос строки | остаётся переносом в значении — так в файле хранится PEM-ключ |
Незакрытая кавычка — line N: unterminated quote.
Как значение получает тип#
Разборщик определяет тип каждого значения сам, по порядку: целое число → дробное число →
on/true как истина и off/false как ложь → в остальных случаях строка.
Из этого следует, что значение, похожее на число, станет числом. Там, где это было бы вредно —
URL, адреса почты, секреты, ключи S3, acme_directory, acme_contact, поля auth_backend,
storage s3 и principal, — разборщик принудительно оставляет строку, поэтому секрет из одних
цифр не испортится. Кавычки в таких местах не обязательны, но всегда безопасны.
Флаги без значения#
Каждая логическая директива понимает обе формы — это общее правило, а не особенность конкретной директивы:
on_demand; # флаг без значения → включено on_demand on; # то же самое явно on_demand off; # выключено
Так пишутся: acme (в certificate), admin_ip_bind (в auth), bind_ip (в session_keys)
и флаги потока http_ts, hls_ts, audio_only_playlist, disabled, on_demand.
Повторы и порядок#
- Порядок директив свободный — разборщик собирает их в набор, а не читает как сценарий.
- Механизма
includeнет. Файл ровно один. Снимокcctv-node/managed.conf— не include. - Повторённая скалярная директива молча берёт последнее значение:
http 8080;и нижеhttp 9443;дают9443без предупреждения. Держите по одной строке на директиву. - Три директивы накапливаются, а не перезаписываются:
trusted_proxies,allow_origins,public_paths. Очистить накопленный список можно только удалением строк. - Повторённые
inputвнутри одногоstreamобразуют цепочку резервирования по порядку; повторённыеpermissionвнутриprincipal— список его прав. - Повторённые блоки
certificate,storage,stream,principal,template,vodдобавляют записи; совпадение имён — ошибка валидации видаduplicate certificate name "x".
Неизвестное имя — это ошибка#
Неизвестные директивы отвергаются на разборе, с номером строки, на любом уровне вложенности:
line 7: unknown directive "cors" line 12: unknown stream directive "frobnicate" line 4: unknown tls directive "bogus" line 9: unknown cctv directive "plane_key"
Формат файла выбирается по расширению: .toml — TOML, .json — JSON, любое другое (в том числе
.conf) — описанная здесь текстовая грамматика. Не называйте конфигурацию ноды node.json.
Единицы измерения#
Сроки и объёмы хранения архива записываются суффиксом сразу после числа, без пробела. Суффиксы времени и размера различаются регистром и длиной, и здесь легко ошибиться на два порядка.
| Суффикс времени | Значение | Пример |
|---|---|---|
s |
секунды | 90s |
m |
минуты | 30m — тридцать минут |
h |
часы | 12h |
d |
сутки | 14d |
w |
недели | 8w |
mo |
месяцы | 6mo — полгода |
y |
годы | 1y |
| Суффикс размера | Значение | Пример |
|---|---|---|
K |
КиБ, 1024 байта | 512K |
M |
МиБ | 900M |
G |
ГиБ | 500G |
T |
ТиБ | 2T |
Суффиксы размера пишутся только заглавной буквой: 500g не размер и не время — такая строка
не будет принята. Суффикс времени m — это минуты, месяцы обозначаются двумя буквами, mo.
dvr archive 30m; # НЕВЕРНО, если имелись в виду месяцы: это 30 МИНУТ архива dvr archive 30mo; # верно: 30 месяцев dvr archive 500g; # НЕВЕРНО: суффикс размера только заглавной буквой dvr archive 500G; # верно: 500 ГиБ
Опечатка в суффиксе опаснее любой другой в этом файле: 30m — совершенно корректная строка,
нода спокойно запустится и через полчаса начнёт удалять записи. Неверный суффикс даёт
stream "x": bad retention_time "…" (s/m/h/d/w/mo/y) или
stream "x": bad retention_size "…" (K/M/G/T), а верный, но не тот — молчание и потерю архива.
Строку dvr на управляемой ноде формирует Plane; проверяйте её глазами после каждого изменения
срока хранения.
Что происходит, если в файле ошибка#
Фатальны при загрузке все четыре класса ошибок:
- неизвестное имя директивы —
line 7: unknown directive "cors"; - недопустимое значение перечисления —
source_transport must be "tcp", "udp", or "udp-then-tcp" (got "tpc"); - значение вне диапазона —
udp_ingest_shards 32 out of range (1..=16); - директива протокола, которого нет в этой сборке, — например
webrtc 8000;даёт`webrtc_udp_port` is set but WebRTC is not in this build (feature `egress-webrtc`).
Ни один из них не превращается в «значение по умолчанию» и не пишется в журнал как предупреждение. Всегда держите резервную копию файла до правки и проверяйте изменение перезагрузкой конфигурации, а не перезапуском.
| Когда | Поведение |
|---|---|
| При старте | Ошибка печатается в журнал (hastreamer: config error: …), процесс завершается с кодом 1. systemd (Restart=on-failure, RestartSec=2s) поднимает его снова — получается цикл перезапусков |
При systemctl reload (SIGHUP) |
config reload REJECTED: … (running config unchanged). Работающая нода продолжает отдавать видео со старой конфигурацией — это безопасный способ проверить правку |
| Несколько ошибок сразу | Валидатор собирает их все и печатает через ; — файл чинится за один проход, а не по одной ошибке за перезапуск |
Найти строку с ошибкой:
sudo systemctl reload hastreamer-cctv-node sudo journalctl -u hastreamer-cctv-node -n 30 --no-pager
config reload REJECTED: line 42: unknown directive "cors" (running config unchanged)
Номер строки — от начала файла, включая комментарии и управляемую область.
Горячее применение и перезапуск#
В таблицах этого справочника столбец «Применение» означает:
горячее— изменение действует послеsystemctl reload, без перезапуска и без разрыва соединений;рестарт— новое значение записывается в файл, но работающий процесс живёт со старым доsystemctl restart;рестарт потока(илирестарт потоков) — перезапускается только затронутый поток: его зрители переподключаются, остальные потоки изменения не замечают;рабочая нагрузка— набор объектов сверяется поштучно, сервис не перезапускается; что именно будет перезапущено, зависит от того, какие объекты изменились.
Требуют перезапуска сервиса — 23 директивы#
http · https · rtsp · rtsps · rtmp · rtmps · webrtc (порт) · webrtc public_ip= ·
cores · low_latency · rtsp_frame_source · source_transport · rtp_max_payload ·
max_sessions · dvr_cleanup_secs · media_timeout · on_demand_grace · udp_ingest_rcvbuf ·
udp_ingest_shards · udp_ingest_pool_slots · udp_pull_rate · acme_directory ·
acme_contact
Изменение такой директивы помечается как требующее перезапуска: в ответе API и в интерфейсе
Plane появляется строка вида HLS/API listener (http_port 8080 → 9443). Это уведомление, а не
применение — работающий процесс продолжает слушать старый порт.
Применяются горячо#
trusted_proxies · allow_origins · auth { } · tls { } · certificate { } ·
auth_backend { } · session_keys { } · principal { } · public_paths · log_level
Рабочая нагрузка — сверяется по каждому объекту#
stream · template · storage · cluster · vod
Поток перезапускается только тогда, когда изменился его собственный отпечаток — весь блок
stream, кроме labels. Практические следствия:
- правка
input,dvr,segment_seconds,on_demand— перезапускается этот один поток, остальные сохраняют аптайм и сессии зрителей; - правка только
labels— не перезапускается ничего, новые права доступа действуют сразу; - правка
path,segment_secsилиidentityу хранилища — перезапускаются все потоки, которые в него пишут; - правка адреса или ключей учётной записи S3 — перезапускаются все потоки, которые в неё копируют.
Объект доверия к Plane строится один раз при старте, а в таблицу рестартовых директив cctv не
входит — перезагрузка конфигурации о его изменении не предупредит. Считайте любую правку cctv
рестартовой. Правильный ответ, впрочем, — не править его руками вообще.
Часть 1. Что редактируется вручную#
Всё в этом разделе — ваше. Пишите это выше открывающего маркера управляемой области, в любом порядке.
Слушатели: http, https, rtsp, rtsps#
http 9443; # HLS-fMP4, MSE-WS, превью, REST API, плеер, ответчик ACME HTTP-01 https 8443; # то же самое поверх TLS rtsp 8554; # RTSP-раздача: rtsp://node.example.com:8554/<путь-потока> rtsps 8555; # RTSP поверх TLS, использует тот же сертификат
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
http |
номер порта 1–65535; ноль запрещён | 8080; в стартовой конфигурации от Plane — 80, 9080 или порт из http://-адреса управления |
рестарт |
https |
номер порта 1–65535, 0 — выключено |
0; в стартовой конфигурации от Plane — порт из https://-адреса управления |
рестарт |
rtsp |
номер порта 1–65535; ноль запрещён | 8554 |
рестарт |
rtsps |
номер порта 1–65535, 0 — выключено |
0 |
рестарт |
rtmp |
номер порта, 0 — выключено; в этой сборке допустим только 0 |
0 |
рестарт |
rtmps |
номер порта, 0 — выключено; в этой сборке допустим только 0 |
0 |
рестарт |
webrtc |
webrtc <порт> [public_ip=<адрес>], 0 — выключено; в этой сборке допустим только 0 |
0, public_ip пустой |
рестарт |
Слушатели в стартовой конфигурации выводятся из схемы эффективного адреса управления
(control_url, а если он пуст — public_url), и вручную их согласовывать не нужно:
| Эффективный адрес управления | Что в файле |
|---|---|
https://node.example.com |
http 80; и https 443; плюс блок tls { self_signed_names node.example.com; } |
https://cctv.example.com:9443 |
http 9080; и https 9443; плюс тот же блок tls { } (нода на одном хосте с Plane) |
http://node.example.com:8090 |
только http 8090;, блока tls { } нет (TLS терминирует прокси впереди) |
Поэтому нода поднимается по HTTPS с первого запуска — самоподписанной парой на своё настоящее имя. Прежней последовательности «сначала обычный http, потом руками перевести на https» больше нет.
Слушатели http и rtsp обязательны и не выключаются: http_port and rtsp_port must be non-zero. Все порты, которые вы задали ненулевыми, должны различаться —
http_port and rtsp_port must differ (both 8080),
https and rtsps both use port 8443 — listener ports must be distinct. Номер порта — 16-битное
число: значение больше 65535 файл не пройдёт. webrtc без аргумента даёт
line N: 'webrtc' needs a UDP port, а с ненулевым портом — отказ по отсутствующей возможности
сборки (см. Чего в этом файле нет).
REST API ноды, встроенный плеер, HLS-fMP4, MSE-over-WebSocket, превью и ответчик ACME HTTP-01
работают на слушателе http (и на https, если он включён). Отдельного порта или отдельной
директивы для API нет и не будет — не ищите. Управляющие запросы к этому API принимаются только
с подписью ключа Plane, локального логина у ноды нет.
Plane обращается к ноде по её адресу управления. Если сменить порт или включить/выключить https
на ноде и не обновить адреса в Plane, нода останется живой, но перейдёт в состояние offline и
перестанет получать конфигурацию. Меняйте оба значения в одно окно обслуживания: в админке —
Ноды → нода → Сеть и TLS → Адреса ноды → Изменить, через API — PATCH /api/v1/nodes/<UUID>
(см. Ноды и хранилища).
Отдельно запомните: ноду, которая терминирует TLS, нельзя опрашивать по http:// — она
перенаправит запрос на HTTPS, а при перенаправлении теряется заголовок авторизации, и Plane
получит 401.
tls { } — сертификат по умолчанию#
Блок задаёт удостоверение, которым нода отвечает, когда клиент не прислал имя домена (SNI) или
когда ни один блок certificate под это имя не подошёл.
tls { cert node-snapshot.cert.pem; # путь относительно хранилища TLS key node-snapshot.key.pem; sync_from /var/lib/hastreamer-cctv-plane/store/identity.pem; self_signed_names node.example.com localhost; }
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
cert |
путь к PEM-цепочке: абсолютный или относительно хранилища TLS | пусто | горячее |
key |
путь к PEM-ключу, то же правило | пусто | горячее |
sync_from |
путь к исходному PEM, который копируется в cert и key |
пусто | горячее |
self_signed_names |
список доменных имён | localhost |
горячее |
Хранилище TLS — это tls-state внутри рабочего каталога сервиса, то есть
/var/lib/hastreamer-cctv-node/tls-state. Относительные пути отсчитываются от него.
Если cert и key пусты, нода один раз выпускает самоподписанную пару на имена из
self_signed_names и сохраняет её в <хранилище TLS>/self-signed-cert.pem и
self-signed-key.pem. Отпечаток не меняется при перезапусках — на него можно закрепиться в
клиенте.
sync_from нужен там, где Plane и нода стоят на одном сервере: нода следит за исходным файлом,
проверяет его, атомарно переименовывает в свои cert и key и на лету подменяет сертификат в
резолвере — без перезапуска и без отдельного юнита systemd. Отсюда правило валидации:
tls: `sync_from` requires `cert` and `key` (the node-owned snapshot paths the source is copied into). Никогда не указывайте в cert живой файл Plane напрямую: в момент продления он
перезаписывается, и нода прочитает его наполовину.
certificate «имя» { } — сертификаты по доменам (SNI)#
Именованный блок — один сертификат на один или несколько доменов. Их может быть сколько угодно; выбор происходит по имени домена из запроса клиента.
certificate node-public { # сертификат, загруженный файлами hosts node.example.com *.cam.example.com; cert certs/node-public/fullchain.pem; key certs/node-public/privkey.pem; ca certs/node-public/chain.pem; } certificate node-auto { # сертификат, выпускаемый автоматически hosts node.example.com; acme; }
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
hosts |
список имён: точное имя или маска *.суффикс в одну метку |
пусто | горячее |
cert |
путь к PEM-цепочке, абсолютный или относительно хранилища TLS | пусто | горячее |
key |
путь к PEM-ключу | пусто | горячее |
ca |
путь к промежуточным сертификатам, необязателен | пусто | горячее |
acme |
флаг: acme;, acme on;, acme off; |
выключено | горячее |
Хотя бы одно имя обязательно, иначе блок никогда не будет выбран:
certificate "x": needs at least one hosts entry (else it is never selected). Маска — ровно одна
метка: *.cam.example.com допустимо, **.example.com — нет
(bad wildcard host "**.x" (use *.suffix, one label)). Имена блоков должны быть уникальными
(duplicate certificate name "x") и непустыми (a certificate needs a non-empty name).
Без acme пути обязательны:
certificate "x": `cert` and `key` paths are required (or set `acme` to auto-issue).
С acme они, наоборот, запрещены:
certificate "x": `acme` and explicit `cert`/`key` are mutually exclusive.
ACME — автоматический выпуск сертификатов#
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
acme_directory |
URL каталога центра сертификации | https://acme-v02.api.letsencrypt.org/directory |
рестарт |
acme_contact |
адрес электронной почты для уведомлений | пусто | рестарт |
acme_directory https://acme-staging-v02.api.letsencrypt.org/directory; # тестовый каталог acme_contact ops@example.com;
Чтобы сертификат действительно выпустился, нужны четыре условия — по порядку:
- Включён хотя бы один TLS-слушатель —
httpsилиrtspsс ненулевым портом. Без него выпускать сертификат некуда, и задача ACME вообще не запускается: в журнале не будет ни одной строки про ACME. - Есть блок
certificate <имя> { … acme; }с конкретными именами доменов. Проверки по DNS нет, поэтому маска*.example.comневозможна:certificate "x": ACME/HTTP-01 cannot issue the wildcard "*.y" — give a concrete hostname. - Центр сертификации должен достучаться до
http://<домен>/.well-known/acme-challenge/<токен>по порту 80. Нода отвечает на этот путь открытым текстом на своём слушателеhttp, до всякой авторизации и маршрутизации. - Разрешён исходящий HTTPS до адреса из
acme_directory.
Центр сертификации всегда стучится на порт 80 — этот номер нельзя изменить настройкой. Если у
ноды http 9443, проверка не дойдёт и сертификат не выпустится никогда. Есть два решения:
поставить http 80; (нода тогда раздаёт видео и API на 80-м порту) либо пробросить внешний порт
80 на порт слушателя правилом DNAT на межсетевом экране. Порт нужен не только при первом
выпуске, но и при каждом продлении — не закрывайте его после установки.
Как это работает дальше:
- Задача на нулевом ядре просыпается раз в 60 секунд; первый проход отложен, чтобы слушатели успели подняться до того, как центр сертификации начнёт проверку.
- Каждый проход читает действующую конфигурацию, поэтому блок
certificate { acme; }, добавленный через Plane или API, подхватывается в течение минуты — без перезапуска. - Продление начинается за 30 суток до конца срока действия: у неудачного продления есть недели на повторные попытки, прежде чем живой сертификат истечёт.
- После неудачи — выдержка с удвоением: 15 минут, затем 30, 60 и так далее, но не больше 6 часов. Так центр сертификации не упирается в лимиты запросов. Причина последней неудачи видна в API и в бейдже сертификата в интерфейсе.
- Выпущенное складывается в
<хранилище TLS>/certs/<имя>/acme.cert.pemиacme.key.pem, рядомacme.meta.json. Ключ учётной записи ACME сохраняется в<хранилище TLS>/acme/account.key.pemпри первом выпуске, поэтому учётная запись переживает перезапуски. - Свежая пара подставляется в резолвер SNI на лету: перезапуска нет, зрители не отваливаются. Если пара на диске повреждена, домен обслуживает сертификат по умолчанию, а пара выпускается заново на следующем проходе.
Пока настраиваете новый домен, используйте тестовый каталог
(https://acme-staging-v02.api.letsencrypt.org/directory): сертификаты будут недоверенными, зато
лимиты мягкие. Затем переключитесь на боевой каталог и перезапустите сервис — директива
рестартовая.
trusted_proxies — кому верить в заголовках#
trusted_proxies 10.0.0.0/8 192.168.0.0/16 ::1;
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
trusted_proxies |
список адресов и подсетей CIDR, накапливается при повторении | пусто | горячее |
Заголовкам X-Forwarded-For, Forwarded и X-Real-IP нода верит только тогда, когда
непосредственный собеседник по TCP входит в этот список. Пустой список (значение по умолчанию)
означает, что заголовкам не верят никогда, — прямой клиент не сможет подделать свой адрес в
журнале и в правилах доступа. Ошибочная подсеть отвергается тем же разборщиком, который
применяет живую политику, поэтому «записалось, но не применилось» тут невозможно.
allow_origins — список разрешённых источников CORS#
allow_origins https://cctv.example.com https://ops.example.com;
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
allow_origins |
список источников для заголовка Access-Control-Allow-Origin, накапливается |
* — любой источник |
горячее |
По умолчанию видео и API доступны браузерным скриптам с любой страницы. В рабочей установке
перечислите здесь адреса своих интерфейсов. Ограничение хотлинка страницами конкретных сайтов —
это другая настройка, allowed_referers у потока.
auth { } — общий секрет ноды#
auth { session_secret "ОБЩИЙ-СЕКРЕТ-ФЛОТА"; }
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
session_secret |
строка, непустая | не задан — случайный секрет на каждый запуск | горячее |
admin_ip_bind |
флаг: admin_ip_bind; или admin_ip_bind off; |
выключено | горячее |
admin_password |
запрещён на CCTV-ноде | не задан | горячее |
session_secret на ноде — это не пароль администратора. Это детерминированный корень, из
которого выводится ключ подписи билетов медиасессий, когда session_keys { secret } не
задан. Что это даёт: билет, выданный одной нодой, проходит проверку на другой после
перенаправления зрителя, а сами билеты переживают перезапуск сервиса. Поэтому значение имеет
смысл делать одинаковым на всех нодах установки. Пустая строка отвергается:
auth.session_secret must not be empty (omit it for an auto-generated per-boot secret).
admin_password на CCTV-ноде запрещён — управление идёт только из Plane:
auth.admin_password is not allowed on a cctv-node — it has no admin login; management is via the plane. admin_ip_bind привязывает выданный админ-токен к адресу входа; админского входа у ноды
нет, поэтому директива здесь ни на что не влияет.
session_keys { } — билеты медиасессий#
Билет — короткоживущий ключ, с которым плеер забирает сегменты видео после проверки прав. Пока включена авторизация просмотра, билеты обязательны; этот блок только настраивает их.
session_keys { ttl 3600; vod_session_ttl 86400; bind_ip off; secret "ОТДЕЛЬНЫЙ-СЕКРЕТ-ПОДПИСИ"; }
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
ttl |
секунды, целое | 3600 |
горячее |
vod_session_ttl |
секунды, от 300 до 604800 | 86400 |
горячее |
bind_ip |
флаг: bind_ip; или bind_ip off; |
выключено | горячее |
secret |
строка | пусто — ключ выводится из auth.session_secret |
горячее |
ttl — срок жизни билета живого просмотра; он продлевается сам, пока плеер опрашивает плейлист.
vod_session_ttl ограничивает билет продолжения для конечных записей и жёстко проверяется:
session_keys.vod_session_ttl must be 300..=604800 seconds; got N.
bind_ip привязывает билет к адресу зрителя. Включайте, если зрители сидят в фиксированной сети;
выключайте, если они переходят между мобильной сетью и Wi-Fi или сидят за пулом NAT —
иначе просмотр будет обрываться при каждой смене адреса.
Смена secret немедленно обесценивает все выданные билеты: зрителям придётся переоткрыть поток.
Устаревшая директива gate не существует — line N: unknown session_keys directive "gate".
Тюнинг и производительность#
Четырнадцать скалярных директив верхнего уровня. Все, кроме log_level, требуют перезапуска.
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
cores |
целое; 0 — все производительные ядра |
0 |
рестарт |
low_latency |
on или off |
on |
рестарт |
log_level |
одно из: debug, info, warn, error |
info |
горячее |
rtsp_frame_source |
cmaf или raw_au |
cmaf |
рестарт |
source_transport |
tcp, udp или udp-then-tcp |
tcp |
рестарт |
rtp_max_payload |
байты, применяется в диапазоне 376–65507 | 1200 |
рестарт |
max_sessions |
целое; 0 — без ограничения |
0 |
рестарт |
dvr_cleanup_secs |
секунды | 600 |
рестарт |
media_timeout |
секунды; 0 — сторож выключен |
20 |
рестарт |
on_demand_grace |
секунды | 30 |
рестарт |
udp_ingest_rcvbuf |
байты, от 64 КиБ до 1 ГиБ | 16777216 (16 МиБ) |
рестарт |
udp_ingest_shards |
целое от 1 до 16 | 2 |
рестарт |
udp_ingest_pool_slots |
целое от 64 до 4096 | 1024 |
рестарт |
udp_pull_rate |
попыток в секунду, от 0 до 100000; 0 — без ограничения |
100 |
рестарт |
cores — сколько ядер занимает нода. 0 означает «все производительные»: на гибридных
процессорах берутся P-ядра, энергоэффективные пропускаются; на однородных — все. Положительное
число закрепляет ровно столько ядер, начиная с самых быстрых.
Директива задаёт, сколько ядер занимает нода целиком, а не сколько ядер достаётся камере.
cores 1 сводит обработку всех камер на одно ядро: оно упирается в полку, видео начинает
рассыпаться, остальные ядра простаивают. Если не уверены — оставьте cores 0.
low_latency включает режим малой задержки в плейлистах HLS. Выключение (off) отменяет его
для всех зрителей сразу; отдельный зритель может отказаться от него сам параметром ?ll=off.
log_level — минимальная важность сообщений, попадающих в журнал ноды. Единственная
директива тюнинга, которая применяется горячо. Отладочная диагностика в релизной сборке
отсутствует физически, поэтому debug включает лишь то, что скомпилировано.
rtsp_frame_source выбирает, из какого кольца RTSP-раздача берёт кадры: cmaf — из общего
кольца фрагментов (дешевле по процессору, задержка на один фрагмент), raw_au — покадрово
(меньше задержка, дороже). Неверное значение:
rtsp_frame_source must be "cmaf" or "raw_au" (got "x").
source_transport — транспорт по умолчанию, которым нода забирает поток с камер:
tcp (RTP внутри управляющего соединения), udp или udp-then-tcp (сначала UDP, при неудаче
TCP). Ошибка: source_transport must be "tcp", "udp", or "udp-then-tcp" (got "x"). У камеры,
заведённой в Plane, транспорт можно переопределить индивидуально.
rtp_max_payload — предел полезной нагрузки пакета RTP при раздаче по RTSP/UDP. Значение
1200 безопасно проходит через туннели VPN без фрагментации на уровне IP. Если весь трафик идёт
внутри одной локальной сети с MTU 1500, значение можно поднять примерно до 1460.
max_sessions ограничивает общее число одновременных сессий раздачи по всем протоколам.
Запрос сверх лимита получает по RTSP отказ 453. 0 снимает ограничение.
dvr_cleanup_secs — период обслуживания архива: применение сроков хранения, вытеснение по
заполненности диска, уборка осиротевших файлов.
media_timeout — сторож замершего видео: если соединение с камерой живо, но кадры не идут
столько секунд, нода принудительно переподключается. 0 выключает сторож — не рекомендуется.
on_demand_grace — сколько секунд поток с признаком on_demand живёт без единого зрителя,
прежде чем нода перестанет тянуть камеру. Тот же таймер задаёт окно ожидания при подъёме, чтобы
поток не «моргал» при редких обращениях.
Последние четыре директивы имеют смысл только при приёме потоков по RTSP/UDP, то есть при
source_transport udp или udp-then-tcp; при транспорте tcp они ни на что не влияют.
udp_ingest_rcvbuf — размер приёмного буфера сокета (ядро дополнительно ограничивает его
значением net.core.rmem_max), udp_ingest_shards — сколько пар сокетов RTP/RTCP заводится
на ядро, udp_ingest_pool_slots — сколько буферов io_uring выделено на ядро (по 64 КиБ,
выделяются по мере надобности; поднимайте в первую очередь, если /api/v1/system показывает рост
счётчика ENOBUFS), udp_pull_rate — темп попыток подключения к источникам на ядро, который
превращает «стадо» одновременных переподключений после сбоя в ровный подъём. Значения вне
диапазона отвергаются дословно, например udp_ingest_shards 32 out of range (1..=16).
cluster { } — сборка из нескольких нод#
Блок скомпилирован в эту сборку, но в стандартной установке видеонаблюдения не используется: Plane его не пишет, потоки распределяет он сам. Приведён для полноты.
cluster { key "СЕКРЕТ-КЛАСТЕРА"; peer node-a 10.0.0.10:9000 { public https://node-a.example.com; zone dc1; http 9443; rtsp 8554; storage archive /srv/cctv/archive { identity 3f2a9c14-1c0e-4f7b-9d21-8a6f0b4e5c77; } } }
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
key |
строка — общий секрет для рукопожатия между нодами | пусто | рабочая нагрузка |
peer <имя> <адрес> |
блок; ровно два аргумента, адрес вида host:port |
— | рабочая нагрузка |
Внутри peer { } все директивы необязательны:
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
public |
базовый URL этой ноды для зрителей | пусто — берётся хост из адреса | рабочая нагрузка |
zone |
строка: ЦОД, стойка, сегмент — для выбора ближайшей ноды | пусто | рабочая нагрузка |
http, https, rtsp, rtsps, rtmp, rtmps, webrtc |
номера портов этой ноды; 0 — взять значение верхнего уровня |
0 |
рабочая нагрузка |
storage <имя> <путь> { } |
хранилища этой ноды; перекрывают одноимённое хранилище верхнего уровня | — | рабочая нагрузка |
Себя в списке нода находит по имени из аргумента запуска --node <имя> или переменной окружения
HS_NODE. Неверное число аргументов: line N: `peer` takes <name> <addr>. Вложенное
storage имеет те же поля и те же проверки, что и обычное (segment_secs, evict_at_percent,
identity).
template «имя» { } — общие настройки для потоков#
template описывает набор значений по умолчанию и принимает ровно те же вложенные
директивы, что и stream (см. следующий раздел). Поток подключает шаблон
директивой template <имя>;, и его собственные директивы перекрывают унаследованные.
template cam-defaults { segment_seconds 2; prepush 4.5; rtsp_transports tcp; }
Наследование только одноуровневое: шаблон, ссылающийся на другой шаблон, — ошибка, как и ссылка
на несуществующее имя. Директива prefix допустима только внутри шаблона и превращает его в
шаблон публикации, материализующий потоки по пути <prefix>/<имя>; значение — один непустой
сегмент пути: template "x": publish prefix must be a single non-empty path segment (no '/').
На управляемой ноде потоки создаёт Plane, поэтому шаблон применим ограниченно — он полезен как
заготовка на будущее и при ручной отладке.
Часть 2. Что принадлежит Plane#
Эти блоки Plane пишет сам — внутри управляемой области, а cctv { } вне её. Читать и сверять их
полезно, править бесполезно или опасно. Ниже они описаны затем, чтобы вы понимали, что видите в
файле, и могли проверить, то ли прислал Plane, чего вы ожидали.
cctv { } — удостоверение ноды (не редактировать)#
cctv { id 9190375b-4b9c-4281-8a55-f16afc89e0e0; # идентификатор ноды в Plane plane_url "https://cctv.example.com"; # куда нода обращается за конфигурацией generation 1; # поколение спаривания plane_pubkey "-----BEGIN PUBLIC KEY----- REDACTED -----END PUBLIC KEY----- "; # единственный ключ, чьи команды нода выполняет }
Блок лежит снаружи маркеров, в вашей половине файла, но формирует его Plane в момент создания ноды. Это удостоверение целиком: без него нода не спарится, а с изменённым — перестанет принимать команды. В CCTV-сборке блок обязателен.
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
id |
UUID в каноническом виде с дефисами | пусто — не пройдёт проверку | рестарт |
plane_url |
URL, начинается с http:// или https://, до 2048 символов, без пробелов |
пусто — не пройдёт проверку | рестарт |
generation |
целое от 1 и выше | 0 — не пройдёт проверку |
рестарт |
plane_pubkey |
открытый ключ ES256 в формате PEM (SPKI) | пусто — не пройдёт проверку | рестарт |
Что означает каждое поле:
id— идентификатор логической ноды в базе Plane. По нему Plane узнаёт, чью конфигурацию отдавать и чьи хранилища считать своими. Ошибка:cctv.id must be a canonical UUID.plane_url— адрес контрол-плейна, по которому нода забирает конфигурацию и ключи проверки прав. Ошибка:cctv.plane_url must be a bounded HTTP(S) origin.generation— поколение спаривания. Plane увеличивает его при перевыпуске файла, чтобы старый скачанныйnode.confнельзя было подсунуть обратно. Ошибка:cctv.generation must be positive.plane_pubkey— открытый ключ, подписью которого Plane заверяет управляющие запросы. Нода выполняет команду только если подпись верна, срок жизни предъявленного токена не больше 60 секунд и он не использовался повторно. Другого способа управлять нодой нет: локального пароля администратора у неё не существует. Ошибка:cctv.plane_pubkey must be an ES256 SPKI PEM public key.
Нода либо не пройдёт проверку конфигурации и уйдёт в цикл перезапусков, либо запустится и будет
молча отвергать команды Plane — в админке она станет offline при живом процессе. Восстанавливать
блок по памяти нельзя: скачайте свежий файл конфигурации в карточке ноды в Plane, перенесите из
него блок cctv { } целиком и перезапустите сервис.
stream «путь» { } — камера#
Один блок — один поток одной камеры. Путь блока — это адрес раздачи:
https://node.example.com/<путь>/master.m3u8 для HLS и rtsp://node.example.com:8554/<путь> для
RTSP. Ровно те же вложенные директивы принимает template.
stream cam/0f3d5b2a-11c4-4a90-93d7-2b6c8e0f41aa/main { input "rtsp://svc:REDACTED@10.20.0.11:554/Streaming/Channels/101"; labels 6mIWXn0nSA6QaHnHRk4KaQ north-wing; on_demand on; dvr archive 14d; }
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
input |
URL источника и необязательные ключ=значение; повторяется |
обязателен хотя бы один | рестарт потока |
segments_count |
целое, сегментов в окне плейлиста; 0 — значение по умолчанию |
0, то есть 5 |
рестарт потока |
segment_seconds |
дробное, секунды; 0 — значение по умолчанию |
0, то есть 2 с |
рестарт потока |
prepush |
дробное, секунды отставания нового зрителя от «живого края»; 0 — по протоколу |
0 |
рестарт потока |
audio_only_playlist |
флаг: добавить в мастер-плейлист вариант «только звук» | выключено | рестарт потока |
http_ts |
флаг; в этой сборке недопустим | выключено | рестарт потока |
hls_ts |
флаг; в этой сборке недопустим | выключено | рестарт потока |
multicast_out |
группа:порт и параметры ttl=, iface=, loopback=, tos=; в этой сборке недопустим |
пусто | рестарт потока |
disabled |
флаг: поток не запускать, настройки сохранить | выключено | рестарт потока |
on_demand |
флаг: тянуть камеру только под зрителя | выключено | рестарт потока |
tls_verify |
system, insecure, pin: плюс 64 шестнадцатеричных символа, ca: плюс путь |
пусто, равно system |
рестарт потока |
rtsp_transports |
both, tcp или udp — что разрешено зрителям по RTSP |
пусто, равно both |
рестарт потока |
caps |
список вкладок плеера: либо только разрешающие имена, либо только запрещающие с минусом | пусто — набор выводится автоматически | рестарт потока |
allowed_referers |
список доменов, которым разрешено встраивать плеер; none разрешает и прямую ссылку |
пусто — без ограничения | рестарт потока |
labels |
список меток авторизации, не более 40, каждая от 1 до 64 байт | пусто | горячее |
dvr |
строка записи архива, см. ниже | запись выключена | рестарт потока |
host |
список имён нод, которым разрешено захватывать поток (только кластер) | пусто | рестарт потока |
restream |
список имён нод, которым разрешено ретранслировать (только кластер) | пусто | рестарт потока |
template |
имя шаблона, значения которого наследуются | не задан | рестарт потока |
prefix |
один сегмент пути; допустим только внутри template |
не задан | рестарт потока |
input повторяется и образует цепочку резервирования в порядке записи: нода спускается вниз
по списку при отказе и поднимается обратно при восстановлении — по ключевому кадру и с выдержкой,
чтобы не мигать между источниками. Параметр source_timeout=N задаёт для конкретного источника
время без кадров, после которого он считается неживым (0 — значение по умолчанию протокола).
Ошибки: line N: `input` needs a url, line N: `input` option "x" must be key=value,
line N: unknown `input` option "x". Совсем без источника —
stream "x": needs `source` or a non-empty `sources` chain.
on_demand экономит трафик и процессор на редко просматриваемых камерах, но на пишущем
потоке игнорируется: запись означает постоянный приём, иначе архив был бы с дырами.
tls_verify — как проверять сертификат источника с адресом rtsps:// или https://.
Значение pin: с отпечатком SHA-256 листового сертификата в шестнадцатеричном виде подходит для
камер с самоподписанными сертификатами; insecure отключает проверку. Опечатка отвергается
сразу: stream "x": bad tls_verify "…": <детали>.
caps ограничивает вкладки протоколов на странице плеера /<путь>/embed.html. Словарь:
hls, hls-ll, mse-ws, dash, webrtc, whip, dvr, timeshift, preview. Либо
перечисляете разрешённые, либо только запрещённые с минусом; смешивать нельзя:
stream "x": caps cannot MIX deny (-x) and allow (bare) tokens — use one mode. Протокол, которого
эта сборка не отдаёт, разрешать бессмысленно: stream "x": cap "webrtc" not served (not in the derivable set […]).
allowed_referers — защита от встраивания видео на чужие страницы, а не аутентификация.
Совпадение по границе точки: example.com разрешает и cdn.example.com, но никогда
evilexample.com. Указывать надо домены, не ссылки:
stream "x": allowed_referers token "…" must be a host, not a URL path.
labels — непрозрачные метки: зритель видит поток, если его выданные права пересекаются с
этим списком. UUID с дефисами приводится к 22-символьной короткой форме при загрузке. Ограничения:
stream "x": at most 40 labels (N given), label "…" exceeds 64 bytes,
label "…" must not contain whitespace/control chars. Изменение меток никогда не перезапускает
поток — права меняются на лету, картинка не моргает.
Пути потоков должны быть непустыми и уникальными: duplicate stream path "x",
a stream has an empty path.
Строка dvr — запись архива#
dvr archive 14d 500G copy=s3s://bucket@coldtier 90d 2T;
Аргументы позиционные, но распознаются по форме. Единственное, что важно в порядке: всё до
copy= относится к локальному диску, всё после — к копии в S3.
| Форма аргумента | Как трактуется |
|---|---|
| слово | имя локального хранилища (диска) |
число с суффиксом s, m, h, d, w, mo, y |
срок хранения по времени |
число с суффиксом K, M, G, T |
срок хранения по объёму |
mode=async или mode=cross |
режим репликации архива в кластере |
copy=s3://bucket@account или copy=s3s://bucket@account |
ссылка на холодный ярус S3, s3s — поверх TLS |
| значение с косой чертой | цель нода/том в кластере |
Проверки, которые чаще всего срабатывают, — дословно:
stream "x": record needs a known local DISK storage (got "y"); use copy= for an s3 cold tierstream "x": bad retention_time "…" (s/m/h/d/w/mo/y)иbad retention_size "…" (K/M/G/T)stream "x": copy= target "y" is not a declared s3 storagestream "x": copy= needs a LOCAL retention — `dvr <storage> <time|size> copy=…` — else local media never evictsstream "x": copy= requires local storage "y" to have a canonical non-nil lowercase UUID identitystream "x": s3 retention time "7d" is shorter than local retention time "14d"; cold retention must be uncapped or cover local retention to prevent delete/re-upload churn
Последнее правило стоит понять: холодная копия обязана жить не меньше локальной, иначе система будет удалять запись в S3 и тут же загружать её снова.
storage «имя» «путь» { } — диск под архив#
storage archive /srv/cctv/archive { identity 3f2a9c14-1c0e-4f7b-9d21-8a6f0b4e5c77; segment_secs 1800; evict_at_percent 85; }
Два позиционных аргумента обязательны — имя и путь:
line N: `storage` takes <name> <path>, or <name> s3 { … }.
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
identity |
строка до 128 символов без пробелов и косых черт; на CCTV-ноде обязательна | пусто | рестарт потоков |
segment_secs |
ровно одно из: 900, 1800, 3600 | 900 |
рестарт потоков |
evict_at_percent |
процент занятости файловой системы, 0 — вытеснение выключено |
90 |
рестарт потоков |
identity — неизменяемый идентификатор диска в Plane. Он же записывается маркером на сам том,
поэтому размонтированный архив отличим от пустой точки монтирования — и нода не начнёт писать в
корневую файловую систему вместо диска. Ошибки: storage "x": a cctv-node disk requires its Plane-managed identity, storage "x": invalid managed disk identity.
segment_secs — целевая длительность файла записи. Набор закрыт:
storage "x": segment_secs must be one of 900, 1800 or 3600 seconds. Более короткие значения
множат число файлов и объектов, более длинные затягивают завершение записи и восстановление после
сбоя.
evict_at_percent — клапан по заполненности: когда файловая система переходит порог, самые
старые записи удаляются, пока занятость не опустится на 2 процентных пункта ниже порога.
Путь на CCTV-ноде должен быть абсолютным и нормализованным, без ., .. и символических ссылок
(storage "x": a cctv-node disk path must be absolute and normalized); корень запрещён
(storage "x": filesystem root cannot be a managed DVR storage); два хранилища не могут
указывать в один каталог (managed DVR storages "a" and "b" share path "/srv/x" in node scope "node"); имена уникальны (duplicate storage name "x").
storage «имя» s3 { } — холодный ярус в S3#
storage coldtier s3 { endpoint s3.example.com; region eu-central-1; access_key REDACTED; secret_key REDACTED; addressing vhost; tls_verify system; }
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
endpoint |
хост или хост:порт без схемы; обязателен |
— | рестарт потоков |
region |
строка | us-east-1 |
рестарт потоков |
access_key |
строка; обязателен | — | рестарт потоков |
secret_key |
строка; обязателен | — | рестарт потоков |
addressing |
vhost или path |
пусто, равно vhost |
рестарт потоков |
tls_verify |
system, insecure, pin: плюс 64 шестнадцатеричных символа, ca: плюс путь |
пусто, равно system |
рестарт потоков |
Схема (s3:// или s3s://) в endpoint не пишется — она живёт в ссылке copy= у потока:
s3 storage "x": endpoint is a bare host[:port] — the s3://|s3s:// scheme lives on the stream's copy= reference. Имя корзины тоже задаётся в copy=, а не здесь. Способ адресации path
нужен объектным хранилищам, которые не поддерживают имя корзины в домене.
У учётной записи S3 нет ни path, ни identity — это поля дискового хранилища. Писать поток
напрямую в S3 нельзя: S3 — только холодная копия того, что уже записано на диск.
auth_backend { } — авторизация просмотра#
Определяет, как нода проверяет права зрителя. Plane всегда пишет type jwks: ключи для
проверки подписи токена нода забирает по HTTP из его набора ключей.
auth_backend { type jwks; url https://cctv.example.com/.well-known/jwks.json; issuer hastreamer-cctv-plane; perms_claim grants; revoked_url https://cctv.example.com/.well-known/cctv-revoked; }
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
type |
одно из: none, internal, hs256, jwks, http |
пусто | горячее |
url |
адрес набора ключей для jwks или адрес проверки для http |
пусто | горячее |
issuer |
ожидаемый издатель токена; проверяется, если задан | пусто | горячее |
audience |
ожидаемая аудитория токена; проверяется, если задана | пусто | горячее |
perms_claim |
имя поля токена, в котором лежит список прав | grants |
горячее |
secret |
общий секрет для hs256; в выдаче API скрыт |
пусто | горячее |
revoked_url |
адрес списка отозванных токенов, который нода периодически опрашивает | пусто — список не опрашивается | горячее |
Ошибки: auth_backend.type must be one of none|internal|hs256|jwks|http (got "x"),
auth_backend type "jwks" requires a url, auth_backend type "hs256" requires a secret,
auth_backend type "internal" needs at least one `principal` block.
Если блока нет вообще, раздача открыта всем. Поэтому у управляемой ноды он обязан присутствовать
в области: managed region must contain the plane playback auth_backend. Токен, чей идентификатор
попал в список по revoked_url, получает отказ 403 в пределах интервала опроса — так работает
немедленное отключение доступа.
principal и public_paths — только внутри области#
Обе директивы существуют в грамматике, но на управляемой ноде бесполезны: Plane их не формирует, а вне маркеров они запрещены и останавливают загрузку.
principal alice { pass "REDACTED"; token "REDACTED"; permission read live/*; permission playback dvr/cam1 hls rtsp; permission read label=north label=vip hls; } public_paths health status/*;
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
pass |
пароль для базовой аутентификации | пусто | горячее |
token |
токен-предъявитель; сосуществует с pass |
пусто | горячее |
permission |
действие, затем путь и метки label=, затем протоколы; повторяется |
пусто | горячее |
public_paths |
список путей раздачи, освобождённых от проверки прав; накапливается | пусто | горячее |
Имя блока — это имя пользователя; principal any { } описывает анонимного зрителя. В строке
permission первый элемент — действие, следующий считается путём, если не начинается с label=;
остальные label= — метки, всё прочее — протоколы. Путь и метки складываются по «или».
Работает всё это только при auth_backend { type internal; }.
Чего в этом файле нет#
- Директивы
apiне существует. REST API, плеер, HLS-fMP4, MSE-WS, превью и ответчик ACME живут на слушателяхhttpиhttps. Отдельного порта у API нет. - Блока
license { }не существует. Путь к файлу лицензии, адрес активации и логика ограничений зашиты в бинарь и не настраиваются. Сама лицензия — отдельный файл/etc/hastreamer-cctv/license.txt, см. Лицензирование. Попытка описать лицензию в конфигурации даётline N: unknown directive "license". - Механизма
includeнет. Один файл; снимокcctv-node/managed.confвключением не является. - Нет протоколов, не вошедших в сборку. CCTV-сборка содержит: приём с камер по RTSP, раздачу по RTSP, HLS-fMP4, MSE-over-WebSocket, превью одним ключевым кадром в MP4 и fMP4, запись и воспроизведение архива со сдвигом по времени, холодный ярус S3, членство в кластере. Директива, называющая отсутствующий протокол, — это ошибка загрузки, а не молчаливое игнорирование:
| Что вы написали | Что ответит нода |
|---|---|
rtmp <порт>; или rtmps <порт>; |
`rtmp_port`/`rtmps_port` is set but RTMP ingest is not in this build (feature `ingest-rtmp`) |
webrtc <порт>; |
`webrtc_udp_port` is set but WebRTC is not in this build (feature `egress-webrtc`) |
vod <имя> <корень> { } |
`vod` libraries are configured but VoD is not in this build (feature `vod`) |
hls_ts; в потоке |
stream "X": `hls_ts` egress needs the `egress-hls-ts` feature — not in this build |
http_ts; в потоке |
stream "X": `http_ts` egress needs the `egress-http-ts` feature — not in this build |
multicast_out …; в потоке |
stream "X": `multicast_out` egress needs the `egress-multicast` feature — not in this build |
input file://… |
stream "X": a `file://` source needs the `ingest-file` feature — not in this build |
input rtmp://… |
stream "X": an `rtmp(s)://` source needs the `ingest-rtmp` feature — not in this build |
input http://…, udp://…, multicast://… |
stream "X": an `http(s)/udp/multicast` source needs the `ingest-hls` feature — not in this build |
Пригодные схемы источника на ноде видеонаблюдения — rtsp:// и rtsps://; дополнительно всегда
доступна встроенная синтетическая схема demo:// для проверки раздачи без камеры.
Блок видео по запросу (VoD) разбирается, но отвергается валидацией: и форма
vod <имя> <корень> { segment_duration N; } с библиотекой, и одиночный настроечный блок
vod { index_cache N; index_ttl N; }. Настроечный блок сам по себе загрузку не ломает
(значения index_cache от 1 до 4096, index_ttl от 1 до 86400 секунд), но в этой сборке ни на
что не влияет.
Полный пример node.conf#
Управляемая нода на отдельном сервере: HTTPS с автоматическим сертификатом, один диск архива и одна камера в двух профилях. Домены — примеры, все секреты вырезаны.
# /etc/hastreamer-cctv-node/node.conf # Всё ВЫШЕ маркеров — ваше и сохраняется. Всё МЕЖДУ маркерами принадлежит Plane и # восстанавливается при каждом старте из /etc/hastreamer-cctv-node/cctv-node/managed.conf. # ── Слушатели (требуют перезапуска) ───────────────────────────────────────────── http 80; # HLS-fMP4, MSE-WS, превью, REST API, плеер, проверка ACME HTTP-01. # Нулём быть не может. Порт 80 выбран, чтобы работал выпуск # сертификата; Plane обращается к ноде по этому же адресу — # держите Control URL в Plane согласованным. https 443; # видео и API поверх TLS; 0 выключил бы TLS и вместе с ним ACME rtsp 8554; # RTSP-раздача: rtsp://node.example.com:8554/<путь-потока> rtsps 8555; # RTSP поверх TLS, тот же сертификат; 0 — выключено # rtmp, rtmps и webrtc не указаны: их нет в сборке видеонаблюдения # ── Размер рабочего пула и журнал ─────────────────────────────────────────────── cores 0; # 0 = все производительные ядра. НЕ ставьте 1 «на всякий случай»: # это сведёт все камеры на одно ядро log_level info; # debug, info, warn, error — применяется горячо # ── Удостоверение ноды: выдал Plane, руками не трогать ────────────────────────── cctv { id 9190375b-4b9c-4281-8a55-f16afc89e0e0; # идентификатор ноды в Plane plane_url "https://cctv.example.com"; # адрес контрол-плейна generation 1; # поколение спаривания plane_pubkey "-----BEGIN PUBLIC KEY----- REDACTED -----END PUBLIC KEY----- "; # ключ, которым Plane подписывает команды } # ── Сертификат по умолчанию: чем нода отвечает без имени домена (горячее) ─────── tls { self_signed_names node.example.com; # cert и key пусты → нода один раз выпускает # самоподписанную пару и хранит её в tls-state: # отпечаток не меняется при перезапусках. # Настоящий сертификат для домена — ниже. # Если Plane стоит на этом же сервере, укажите # здесь cert, key и sync_from (порты тогда # берите не 80/443 — их занимает Plane) } # ── Автоматический сертификат для домена ноды ─────────────────────────────────── acme_directory https://acme-v02.api.letsencrypt.org/directory; # рестартовая; на этапе настройки # используйте тестовый каталог acme_contact ops@example.com; # рестартовая; уведомления о сроках certificate node-public { hosts node.example.com; # только конкретное имя: маска через HTTP-01 невозможна acme; # флаг без значения; вместе с cert и key не сочетается } # Условие выпуска: центр сертификации обращается на порт 80 по адресу # http://node.example.com/.well-known/acme-challenge/<токен>. Здесь http = 80, поэтому проверка # проходит. При http 9443 понадобился бы проброс внешнего порта 80. # ── Политика доступа по HTTP (горячее) ────────────────────────────────────────── trusted_proxies 10.0.0.0/8 ::1; # верить заголовкам прокси только с этих адресов allow_origins https://cctv.example.com; # по умолчанию разрешены любые источники # ── Билеты медиасессий (горячее) ──────────────────────────────────────────────── session_keys { ttl 3600; # срок жизни билета живого просмотра, секунды vod_session_ttl 86400; # для записей; допустимо от 300 до 604800 bind_ip off; # off, если зрители переходят между Wi-Fi и мобильной сетью } auth { session_secret "REDACTED-ОБЩИЙ-СЕКРЕТ-ФЛОТА"; # это НЕ пароль администратора (он на CCTV-ноде запрещён), а корень # ключа подписи билетов. Одинаковый на всех нодах — билеты переживают # перезапуск и проходят проверку после перенаправления зрителя } # ── Приём с камер и обслуживание архива (требуют перезапуска) ─────────────────── source_transport tcp; # транспорт по умолчанию: tcp, udp или udp-then-tcp media_timeout 20; # переподключить камеру, если кадры не идут 20 секунд (0 — выключено) on_demand_grace 30; # через сколько секунд без зрителей отпускать камеру on_demand dvr_cleanup_secs 600; # период уборки архива: сроки хранения, вытеснение, осиротевшие файлы # ════════════════════════════════════════════════════════════════════════════════ # НИЖЕ — ОБЛАСТЬ PLANE. Правки здесь исчезают при следующем старте. # Камеры и диски меняйте в интерфейсе Plane. # ════════════════════════════════════════════════════════════════════════════════ # >>> MANAGED BY cctv-plane — edits will be overwritten >>> storage archive /srv/cctv/archive { # диск архива: имя и абсолютный нормализованный путь identity 3f2a9c14-1c0e-4f7b-9d21-8a6f0b4e5c77; # идентификатор диска и маркер на самом томе segment_secs 1800; # длительность файла записи: только 900, 1800 или 3600 evict_at_percent 85; # вытеснять старое при занятости выше 85 % (0 — не вытеснять) } auth_backend { # кто имеет право смотреть type jwks; # подпись токена проверяется по набору ключей Plane url https://cctv.example.com/.well-known/jwks.json; issuer hastreamer-cctv-plane; # ожидаемый издатель токена perms_claim grants; # поле токена со списком прав revoked_url https://cctv.example.com/.well-known/cctv-revoked; # список отозванных токенов } stream cam/0f3d5b2a-11c4-4a90-93d7-2b6c8e0f41aa/main { # путь раздачи этого потока input "rtsp://svc:REDACTED@10.20.0.11:554/Streaming/Channels/101"; # основной профиль камеры; # в кавычках, потому что в логине и пароле бывают # символы, значимые для грамматики. Повторный input — # это резервный источник labels 6mIWXn0nSA6QaHnHRk4KaQ north-wing; # метки прав: организация и тег. Их правка # НЕ перезапускает поток dvr archive 14d; # писать на диск archive, хранить 14 суток # (14d — сутки; 14m было бы 14 МИНУТ, 14mo — месяцев) } stream cam/0f3d5b2a-11c4-4a90-93d7-2b6c8e0f41aa/sub { # второй профиль той же камеры input "rtsp://svc:REDACTED@10.20.0.11:554/Streaming/Channels/102"; labels 6mIWXn0nSA6QaHnHRk4KaQ north-wing; on_demand on; # тянуть камеру только под зрителя; отпускать через # on_demand_grace. На пишущем потоке игнорируется } # <<< MANAGED BY cctv-plane <<<
Порядок безопасного редактирования#
- Сделайте копию файла. Это единственный способ вернуться за секунду.
- Правьте только выше открывающего маркера.
- Проверьте правку перезагрузкой конфигурации — при ошибке нода останется работать на старой.
- Прочитайте журнал — там либо отчёт о применённых изменениях, либо ошибка с номером строки.
- Перезапустите сервис, только если меняли рестартовую директиву (список — в разделе Горячее применение и перезапуск).
sudo cp /etc/hastreamer-cctv-node/node.conf /root/node.conf.bak # 1. копия sudo -e /etc/hastreamer-cctv-node/node.conf # 2. правка вне маркеров sudo systemctl reload hastreamer-cctv-node # 3. проверка без риска sudo journalctl -u hastreamer-cctv-node -n 30 --no-pager # 4. вердикт sudo systemctl restart hastreamer-cctv-node # 5. только для рестартовых
После шага 3 в журнале не должно быть строки config reload REJECTED. Успешная перезагрузка
выглядит так:
config reload: applied epoch 7 (hash 68d0b9f93ab3999d)
Если файл после правки совпал с действующей конфигурацией, будет config reload: no change.
После перезапуска убедитесь, что сервис жив и слушатели подняты:
systemctl is-active hastreamer-cctv-node # active sudo ss -tlnp | grep hastreamer # ожидаемые порты в состоянии LISTEN
Если нода настроена вручную и её конфигурацию нельзя менять извне, создайте файл-замок:
sudo touch /etc/hastreamer-cctv-node/node.conf.locked
После этого любой записывающий запрос к конфигурации через API возвращает 423 Locked.
Чтение состояния и перезапуск продолжают работать. Снимается замок удалением файла.
Учтите: на спаренной ноде тем же замком блокируется и доставка конфигурации из Plane — новые камеры и диски на такую ноду не приедут. Ставьте его на время разбирательства, а не насовсем.
Частые ошибки при ручной правке#
| Симптом | Причина | Что делать |
|---|---|---|
| Нода в цикле перезапусков после правки | Ошибка в файле, выход с кодом 1 | journalctl -u hastreamer-cctv-node -n 50 — в сообщении есть номер строки; вернуть копию |
stream may appear only in the plane-managed region |
Камера дописана руками вне маркеров | Удалить блок, добавить камеру в Plane |
| Правка исчезла после перезапуска | Она была внутри маркеров | Повторить её выше открывающего маркера |
managed region exists but no plane-approved snapshot is present |
Область есть, снимка managed.conf нет |
Дать Plane прислать конфигурацию заново либо удалить область целиком |
| Порт сменили, ничего не изменилось | Слушатели рестартовые | systemctl restart, а не reload |
| Нода стала offline после смены порта | В Plane остался старый Control URL | Обновить адрес управления ноды в Plane |
| Сертификат так и не выпустился | Порт 80 недоступен снаружи, либо нет ни одного TLS-слушателя, либо в hosts маска |
Пробросить порт 80, включить https, указать конкретное имя |
| Архив исчезает через полчаса | В сроке хранения m вместо mo |
Исправить срок в Plane: 30mo — месяцы |
| Перезапустились все потоки от мелкой правки | Изменено поле хранилища или учётной записи S3 | Так и задумано: от них зависят все пишущие потоки |
Установка и спаривание ноды — Установка с нуля: Node. Разбор сбоев в работе — Диагностика неисправностей.