Готовые сценарии
Типовые задачи автоматизации целиком — массовый импорт камер, инвентаризация, перенос между нодами, выгрузка событий и мониторинг.
Все примеры на этой странице — законченные скрипты, а не фрагменты. Перед запуском задайте переменные окружения:
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.