SIRINVIDEO CCTV Документация администратора и пользователя На сайт 2026.08

Готовые сценарии

Типовые задачи автоматизации целиком — массовый импорт камер, инвентаризация, перенос между нодами, выгрузка событий и мониторинг.

Все примеры на этой странице — законченные скрипты, а не фрагменты. Перед запуском задайте переменные окружения:

export BASE=https://cctv.example.com
export TOKEN=$(curl -sS -X POST "$BASE/api/v1/auth/login" \
  -H 'Content-Type: application/json' \
  -d '{"login":"integration","password":"ПАРОЛЬ"}' \
  | python3 -c 'import sys,json;print(json.load(sys.stdin)["token"])')
Заведите отдельную учётную запись для интеграции

Не используйте логин живого администратора. Если в организации задан лимит одновременных сессий, каждый вход скриптом будет выбрасывать администратора из админки. Создайте отдельного пользователя с ролью администратора и работайте под ним.

Массовый импорт камер из таблицы#

Задача: есть список камер (название, RTSP-ссылка, глубина архива в сутках) — нужно завести их все на выбранной ноде.

Подготовьте CSV cameras.csv вида:

Подъезд 1;rtsp://user:pass@10.0.0.11/stream0;7
Подъезд 2;rtsp://user:pass@10.0.0.12/stream0;7
Двор;rtsp://user:pass@10.0.0.13/stream0;30
#!/usr/bin/env bash
set -euo pipefail
NODE_ID=<UUID-ноды>
STORAGE=dvr1            # имя хранилища на этой ноде

while IFS=';' read -r name url days; do
  [ -z "${name:-}" ] && continue
  # адрес камеры вычисляем из ссылки: поле host не выводится из неё автоматически
  host=$(python3 - "$url" <<'PY'
import sys
from urllib.parse import urlparse
print(urlparse(sys.argv[1]).hostname or "")
PY
)
  body=$(python3 - "$name" "$url" "$days" "$NODE_ID" "$STORAGE" "$host" <<'PY'
import json, sys
name, url, days, node, storage, host = sys.argv[1:7]
print(json.dumps({
    "name": name, "node_auto": False, "node_id": node, "enabled": True,
    "host": host,
    "dvr_mode": "duration", "dvr_value": int(days) * 86400, "dvr_storage": storage,
    "profiles": [{"kind": "main", "source": "manual", "rtsp_url": url}],
}, ensure_ascii=False))
PY
)
  code=$(curl -sS -o /tmp/resp.json -w '%{http_code}' -X POST "$BASE/api/v1/cameras" \
    -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d "$body")
  if [ "$code" = "200" ] || [ "$code" = "201" ]; then
    echo "OK   $name"
  else
    echo "FAIL $name$code $(cat /tmp/resp.json)"
  fi
  sleep 0.3     # не бомбардируйте плейн: он рассылает конфигурацию нодам на каждое изменение
done < cameras.csv
Обязательно передавайте host

Поле host — отдельное, оно не выводится из rtsp_url. Если его не передать, камера будет работать, но в списке останется пустая колонка «Хост», а ONVIF и PTZ по ней работать не смогут.

Инвентаризация: выгрузка всех камер со статусом#

Задача: получить актуальный список всех камер с нодой, адресом и состоянием — например, для сверки или отчёта.

curl -sS "$BASE/api/v1/cameras?limit=500" -H "Authorization: Bearer $TOKEN" \
| python3 - <<'PY'
import sys, json, csv
rows = json.load(sys.stdin).get("rows", [])
w = csv.writer(sys.stdout, delimiter=';')
w.writerow(["Название", "Хост", "Нода", "Статус", "Переподключений", "Архив"])
for r in rows:
    w.writerow([r.get("name"), r.get("host") or "", r.get("node_name") or "",
                r.get("live_status") or "", r.get("reconnects", 0),
                r.get("dvr_mode") or "нет"])
print(f"# всего камер: {len(rows)}", file=sys.stderr)
PY
Всегда задавайте limit явно

Если параметр limit не передан, ответ вернётся полной страницей в 500 записей. Пустая строка (?limit=) — это ошибка 400, а не «значение по умолчанию»: параметр либо задаётся корректным значением, либо опускается целиком.

Отобрать только проблемные камеры:

curl -sS "$BASE/api/v1/cameras?limit=500&status=problem" -H "Authorization: Bearer $TOKEN" \
| python3 -c 'import sys,json
rows=json.load(sys.stdin).get("rows",[])
print(f"проблемных камер: {len(rows)}")
for r in rows[:20]: print(" -", r["name"], "|", r.get("live_status"), "|", r.get("node_name"))'

Проверка состояния для внешнего мониторинга#

Задача: одна команда, которая возвращает ненулевой код, если во флоте что-то не так — её удобно повесить в Zabbix, Nagios или systemd-таймер.

#!/usr/bin/env bash
set -euo pipefail
OUT=$(curl -sS --max-time 20 "$BASE/api/v1/monitor/fleet/summary" \
      -H "Authorization: Bearer $TOKEN")

python3 - "$OUT" <<'PY'
import json, sys
d = json.loads(sys.argv[1])
problems = []
nodes = d.get("nodes") or []
for n in nodes:
    if n.get("state") != "active":
        problems.append(f"нода {n.get('name')}: состояние {n.get('state')}")
    lic = (n.get("license") or {}).get("status")
    if lic and lic != "licensed":
        problems.append(f"нода {n.get('name')}: лицензия {lic}")
if problems:
    print("ПРОБЛЕМЫ:"); [print(" -", p) for p in problems]; sys.exit(2)
print(f"OK: узлов {len(nodes)}, все активны")
PY

Контрол-плейн не отдаёт метрики в формате Prometheus — для внешнего мониторинга используйте эти JSON-эндпоинты. Prometheus-метрики отдаёт стриминговая нода, см. Мониторинг и метрики.

Перенос камер с одной ноды на другую#

Задача: вывести ноду из эксплуатации или разгрузить её, перенеся камеры на другую.

#!/usr/bin/env bash
set -euo pipefail
FROM_NODE=<UUID-исходной-ноды>
TO_NODE=<UUID-целевой-ноды>
TO_STORAGE=dvr1          # имя хранилища на ЦЕЛЕВОЙ ноде

ids=$(curl -sS "$BASE/api/v1/cameras?limit=500" -H "Authorization: Bearer $TOKEN" \
  | python3 -c "import sys,json;print(' '.join(r['id'] for r in json.load(sys.stdin)['rows'] if r.get('node_id')=='$FROM_NODE'))")

count=0
for id in $ids; do
  curl -sS -o /dev/null -X PATCH "$BASE/api/v1/cameras/$id" \
    -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
    -d "{\"node_id\":\"$TO_NODE\",\"node_auto\":false,\"dvr_storage\":\"$TO_STORAGE\"}"
  count=$((count+1)); sleep 0.25
done
echo "перенесено камер: $count"
Архив при переносе теряется

Записи камеры лежат на диске той ноды, где она была. При смене ноды или хранилища система ставит старые записи в очередь на удаление — архив камеры за прошлый период станет недоступен. Если архив нужен, сначала выгрузите нужные фрагменты (экспорт), и только потом переносите.

Выгрузка событий за период#

FROM=2026-08-01T00:00:00Z
TO=2026-08-31T23:59:59Z

curl -sS "$BASE/api/v1/events?limit=500&occurred_from=$FROM&occurred_to=$TO" \
  -H "Authorization: Bearer $TOKEN" \
| python3 - <<'PY'
import sys, json, csv
rows = json.load(sys.stdin).get("rows", [])
w = csv.writer(sys.stdout, delimiter=';')
w.writerow(["Время", "Камера", "Нода", "Тип", "Важность", "Завершено"])
for e in rows:
    w.writerow([e.get("occurred_at"), e.get("camera_name") or "",
                e.get("node_name") or "", e.get("detector"),
                e.get("severity"), e.get("ended_at") or "открыто"])
PY

Создание оператора с ограниченным доступом#

Задача: завести дежурного, который видит только камеры с тегом «Подъезды» — живое видео и архив, без экспорта и PTZ.

# 1) пользователь
USER_ID=$(curl -sS -X POST "$BASE/api/v1/users" -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"login":"duty1","password":"СЛОЖНЫЙ-ПАРОЛЬ","role":"user","display_name":"Дежурный смены 1"}' \
  | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])')

# 2) находим нужный тег
TAG_ID=$(curl -sS "$BASE/api/v1/tags" -H "Authorization: Bearer $TOKEN" \
  | python3 -c 'import sys,json
rows=json.load(sys.stdin);rows=rows if isinstance(rows,list) else rows.get("rows",[])
print([t["id"] for t in rows if t["name"]=="Подъезды"][0])')

# 3) выдаём права: живое видео (1) + архив (2) = маска 3
curl -sS -X PUT "$BASE/api/v1/users/$USER_ID/grants" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d "{\"grants\":[{\"scope\":\"tag\",\"ref_id\":\"$TAG_ID\",\"privileges\":3}]}"

# 4) проверяем, что получилось
curl -sS "$BASE/api/v1/users/$USER_ID/effective" -H "Authorization: Bearer $TOKEN" | python3 -m json.tool
Сохранение грантов — это полная замена

PUT /users/{id}/grants заменяет весь набор прав пользователя целиком и отзывает его активные сессии. Чтобы добавить одно право, отправляйте полный список — старый плюс новый. Смена роли или организации пользователя удаляет все его гранты.

Регулярная очистка старых событий#

CUTOFF=$(date -u -d '90 days ago' +%Y-%m-%dT%H:%M:%SZ)
ORG_ID=<UUID-организации>

# 1) СНАЧАЛА предпросмотр — сколько будет удалено
curl -sS -X POST "$BASE/api/v1/events/purge/preview" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d "{\"org_id\":\"$ORG_ID\",\"to\":\"$CUTOFF\"}" | python3 -m json.tool

# 2) и только потом — сама очистка (ставится в очередь, выполняется асинхронно)
JOB=$(curl -sS -X POST "$BASE/api/v1/events/purge" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d "{\"org_id\":\"$ORG_ID\",\"to\":\"$CUTOFF\"}" \
  | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])')

# 3) следим за выполнением
curl -sS "$BASE/api/v1/cleanup-jobs/$JOB" -H "Authorization: Bearer $TOKEN" | python3 -m json.tool
Очистка необратима

Удалённые события восстановить нельзя. Всегда запускайте предпросмотр и убеждайтесь, что число и период соответствуют ожиданиям.

Автоматическая установка новой ноды#

#!/usr/bin/env bash
set -euo pipefail
NAME=$1                       # например node-3
PUBLIC=$2                     # например http://node3.example.com:8090

# 1) создаём ноду в плейне и сразу получаем её конфигурацию
curl -sS -X POST "$BASE/api/v1/nodes" -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d "{\"name\":\"$NAME\",\"public_url\":\"$PUBLIC\"}" \
  | python3 -c 'import sys,json;print(json.load(sys.stdin)["config"])' > node.conf

echo "node.conf получен, скопируйте его на сервер ноды:"
echo "  scp node.conf root@<сервер>:/tmp/"
echo "  ssh root@<сервер> 'installers/hastreamer-cctv-node/install.sh &&"
echo "     install -m 0644 /tmp/node.conf /etc/hastreamer-cctv-node/node.conf &&"
echo "     systemctl restart hastreamer-cctv-node'"

Дальнейшие шаги (лицензия, хранилище, TLS) — в разделе Установка с нуля: Node.