---
title: Внешние скрипты для оповещений
url: https://doc2.pstreamer.tv/ru/manual/extras/alert_scripts.html
lang: ru
product: Perfect Streamer
version: 2.0.2.415
---

# Внешние скрипты для оповещений

Канал «Процесс» внешней доставки оповещений запускает на узле вашу программу или скрипт на каждое событие оповещения: возникновение инцидента и его снятие. Так оповещения попадают в любую систему, которую можно вызвать из скрипта: SMS-шлюз, мессенджер, вебхук, собственный мониторинг или система заявок. Страница описывает, как узел запускает команду, что она получает и как обрабатывается результат, и даёт две готовые заготовки: на `/bin/sh` и на Python 3.

Общая модель внешней доставки и два других канала, Telegram и почта, описаны в [Внешняя доставка алертов](../meshwork/alerts.md#meshwork-alert-delivery), окно настройки — в [Назначения оповещений](../webui/administration.md#webui-alert-destinations).

## Настройка канала

Канал настраивается на вкладке «Процесс» окна «Назначения оповещений». Окно открывается кнопкой «Назначения оповещений…» в настройках оповещателя ([Назначения оповещений](../webui/administration.md#webui-alert-destinations)). Те же значения лежат в секции `alerter` файла настроек.

| Поле | Ключ | Назначение |
| --- | --- | --- |
| «Включить назначение «Процесс»» | `process-enable` | Включает канал. По умолчанию выключен. |
| «Команда» | `process-command` | Командная строка: программа и её аргументы. По умолчанию пусто; включённый канал с пустой командой ничего не запускает. |
| «Минимальная важность» | `process-min-severity` | Нижний порог важности события: `Info` (3), `Warning` (4, по умолчанию), `Error` (5), `Critical` (6), `AdminAction` (7). |
| «Таймаут, ms» | `process-timeout-ms` | Сколько миллисекунд даётся на один запуск: от 100 до 600000, по умолчанию 5000. |

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

## Как запускается команда

**Без оболочки.** Строка «Команда» делится на слова по правилам оболочки, но сама оболочка не запускается. Слова разделяются пробелами; текст в одинарных кавычках берётся как есть; в двойных кавычках обратная косая черта экранирует только `"` и `\`; вне кавычек обратная косая черта экранирует следующий символ; пустые кавычки `''` дают пустой аргумент. Первое слово — программа, остальные — её аргументы. Конвейеры `|`, перенаправления `>`, подстановка переменных `$VAR`, `~` и шаблоны имён файлов не работают: они уходят программе обычным текстом. Всё это делается внутри скрипта.

**Поиск программы.** Если в первом слове есть `/`, это путь к файлу: файл должен существовать и быть исполняемым, иначе каждое событие заканчивается ошибкой `process-command not executable`. Имя без `/` ищется по `PATH` службы. Надёжнее всегда указывать полный путь.

**Окружение запуска.**

- Пользователь и группа — те же, что у службы: `pss`.
- Рабочий каталог — корень `/`; пути в скрипте указывайте полностью.
- Переменные окружения наследуются от службы (в том числе её `PATH`), к ним добавляются переменные `PSS_ALERT_*`.
- Открытые файлы и сокеты службы скрипту не передаются: у него есть только стандартные вход, вывод и поток ошибок.
- Стандартный вывод скрипта уходит в системный журнал службы, то есть в вывод `journalctl -u pss` ([Журнал службы](../support/index.md#support-logs)). Стандартный поток ошибок узел читает сам, после завершения скрипта (Результат, таймаут и журнал).

## Что получает скрипт

Каждое событие передаётся двумя способами одновременно: полями в переменных окружения и целиком в формате JSON на стандартный вход. Аргументов от узла нет — только те, что записаны в «Команде».

**Переменные окружения**

| Переменная | Значение |
| --- | --- |
| `PSS_ALERT_ID` | Устойчивый идентификатор инцидента, одинаковый у его возникновения и снятия, например `2:12:3`. |
| `PSS_ALERT_STATE` | `raised` — инцидент возник, `cleared` — снят. |
| `PSS_ALERT_SEVERITY` | Важность числом: 3–7 (см. «Минимальная важность» выше). |
| `PSS_ALERT_SEVERITY_STR` | Важность словом: `Info`, `Warning`, `Error`, `Critical`, `AdminAction`. |
| `PSS_ALERT_CODE` | Код вида инцидента числом ([Каталог кодов алертов](../meshwork/alerts.md#meshwork-alert-codes)). |
| `PSS_ALERT_CODE_STR` | Тот же код текстовым обозначением, например `no-data`, `io-error`. |
| `PSS_ALERT_SRC_TYPE` | Тип источника: `System`, `Stream`, `Cluster`, `EPG`, `Mosaic`, `DVB` или `CS`. |
| `PSS_ALERT_SRC_ID` | Идентификатор источника, например номер потока. |
| `PSS_ALERT_NODE` | Имя узла, на котором возник инцидент. У оповещения, полученного от соседа по домену Meshwork, это имя соседа. |
| `PSS_ALERT_DISPLAY_NAME` | Имя объекта для человека, например имя потока. |
| `PSS_ALERT_MESSAGE` | Текст оповещения. |
| `PSS_ALERT_TS` | Время этого события, секунды Unix (UTC). |
| `PSS_ALERT_RAISED_TS` | Время возникновения инцидента. |
| `PSS_ALERT_CLEARED_TS` | Время снятия; `0`, пока инцидент активен. |

Переменные удобны в `/bin/sh`: текст с пробелами и переводами строк передаётся в них без искажений, а в списке процессов (`ps`) они не видны.

**JSON на стандартном входе.** Одна строка без завершающего перевода строки, после неё вход закрывается. В JSON те же сведения, но важность и тип источника заданы только числами (тип источника: 1 — `System`, 2 — `Stream`, 3 — `Cluster`, 4 — `EPG`, 5 — `Mosaic`, 6 — `DVB`, 7 — `CS`), а текстовых обозначений кода и важности нет. Поля:

- всегда — `id`, `state`, `ts`, `raised_ts`, `cleared_ts`, `severity`, `src_type`, `src_id`, `node`, `display_name`, `code`, `message`;
- у инцидентов отдельного входа или выхода — `io_id` и `io_kind` (`input` или `output`);
- у оповещений `AdminAction` — `admin_action_src`;
- у «мерцающего» инцидента — `flapping` (`true`) и `flap_cycles`, число его циклов (Когда запускается скрипт).

Читать стандартный вход скрипт не обязан: всё нужное есть и в переменных. Если же он читает вход, читать нужно до конца, а не одну строку.

Пример: на узле `edge-1` у потока `Sport HD` (номер 12) 15 секунд нет данных. Возникновение:

```json
{"id":"2:12:3","state":"raised","ts":1761134400,"raised_ts":1761134400,
 "cleared_ts":0,"severity":4,"src_type":2,"src_id":12,"node":"edge-1",
 "display_name":"Sport HD","code":3,
 "message":"[Sport HD] no data 15s (no-data threshold 15s)"}
```

Снятие того же инцидента через две минуты:

```json
{"id":"2:12:3","state":"cleared","ts":1761134520,"raised_ts":1761134400,
 "cleared_ts":1761134520,"severity":4,"src_type":2,"src_id":12,"node":"edge-1",
 "display_name":"Sport HD","code":3,
 "message":"[Sport HD] no data 15s (no-data threshold 15s)"}
```

Здесь JSON для наглядности разбит на строки; узел передаёт его одной строкой. У снятия сохраняются важность и текст инцидента — текста «восстановлено» нет, о снятии говорит поле `state`. Переменные окружения того же возникновения:

```sh
PSS_ALERT_ID=2:12:3
PSS_ALERT_STATE=raised
PSS_ALERT_SEVERITY=4
PSS_ALERT_SEVERITY_STR=Warning
PSS_ALERT_CODE=3
PSS_ALERT_CODE_STR=no-data
PSS_ALERT_SRC_TYPE=Stream
PSS_ALERT_SRC_ID=12
PSS_ALERT_NODE=edge-1
PSS_ALERT_DISPLAY_NAME=Sport HD
PSS_ALERT_MESSAGE=[Sport HD] no data 15s (no-data threshold 15s)
PSS_ALERT_TS=1761134400
PSS_ALERT_RAISED_TS=1761134400
PSS_ALERT_CLEARED_TS=0
```

## Когда запускается скрипт

- **Возникновение и снятие.** Скрипт запускается один раз при возникновении инцидента и один раз при его снятии. Промежуточные обновления активного инцидента (повтор, новый текст, смена важности) скрипту не передаются.
- **Порог важности.** «Минимальная важность» применяется к инциденту, а не к каждому событию. Инцидент ниже порога не передаётся. Если его важность позже поднялась до порога, скрипт один раз получает `raised` в момент этого повышения. `cleared` приходит тогда и только тогда, когда было передано `raised`, какой бы ни была важность в момент снятия. Переданным считается событие, отданное каналу: если доставка не удалась, инцидент всё равно считается открытым, и его снятие будет передано.
- **Главный выключатель.** При выключенном оповещателе узла ([Оповещатель](../webui/administration.md#webui-alerter)) скрипт не запускается вовсе, в том числе для снятия инцидента, о котором уже сообщалось.
- **Оповещения домена.** При разносе оповещений по домену Meshwork скрипт получает и оповещения соседей; узел-источник указан в `PSS_ALERT_NODE` ([Разнос оповещений по домену](../meshwork/alerts.md#meshwork-alert-replication)). Если канал включён на нескольких узлах домена, об одном инциденте сообщит каждый из них. Чтобы не было дублей, включайте канал на одном узле.
- **Мерцающие инциденты.** Если инцидент раз за разом возникает и снимается (по умолчанию три цикла подряд с промежутками не больше 600 секунд), узел перестаёт снимать его на каждом цикле: инцидент остаётся активным, а одно снятие приходит, когда условие продержится восстановленным не меньше 600 секунд. Скрипт получает одно возникновение и одно снятие вместо пары на каждый цикл. Значения задаются ключами `flap-suppress`, `flap-cycles`, `flap-window-sec` и `flap-settle-sec` секции `alerter` файла настроек ([Файл настроек pss.json](config_file.md#extras-config-file)).
- **Липкие оповещения,** которые снимаются только вручную, присылают снятие, когда оператор снимает оповещение ([Просмотр и снятие алертов](../meshwork/alerts.md#meshwork-alerts-view)).
- **Перезапуск службы.** Список активных инцидентов хранится в памяти и при перезапуске службы теряется без снятий. Условие, которое всё ещё действует, возникает заново, и скрипт получает второе `raised` с тем же `PSS_ALERT_ID`.

Поэтому событие лучше обрабатывать по `PSS_ALERT_ID` и `PSS_ALERT_STATE`: повторное `raised` обновляет известный инцидент, а не открывает новый.

## Очередь и порядок

- Скрипт запускается строго по одному: следующий запуск начинается после завершения предыдущего, события идут в порядке поступления.
- Запуски ведёт отдельный фоновый поток канала. Медленный скрипт не задерживает ни другие каналы, ни работу узла, но задерживает следующие события этого же канала.
- Пока скрипт работает, новые события ждут в очереди канала на 512 событий. Когда очередь заполнена, самые новые события отбрасываются, а в журнал службы записывается `delivery queue full, dropped alert(s)`.
- При остановке и перезапуске службы события из очереди отбрасываются; служба дожидается только запуска, который уже идёт.

Считайте бюджет: при шторме из сотни событий скрипт, работающий две секунды, отстаёт от событий на три с лишним минуты. Долгую доставку выносите в фон (см. следующий раздел).

## Результат, таймаут и журнал

- **Код завершения 0** — событие доставлено.
- **Ненулевой код** — ошибка доставки. В журнал службы ([Журнал службы](../support/index.md#support-logs)) пишется строка вида `[alert:process] deliver failed: process-command '/usr/local/bin/pss-alert.sh' exit 1: <текст из stderr>` с последними 4 КБ потока ошибок.
- **Поток ошибок** узел читает, пока скрипт работает, и показывает только вместе с ненулевым кодом. Вывод в stderr при коде 0 ошибкой не считается и ни на что не влияет; обильный вывод в stderr не замедляет скрипт и не приводит к таймауту.
- **Таймаут.** Если скрипт не завершился за «Таймаут, ms», узел принудительно завершает его (`SIGKILL`) и пишет `timed out after <N> ms`. Завершается только сам скрипт; запущенные им процессы продолжают работать.
- **Повторов нет.** Неудачное событие не отправляется повторно; канал переходит к следующему.
- **Ограничение частоты записей.** Первая ошибка канала пишется сразу, следующие — не чаще раза в минуту, с числом ошибок за это время в конце строки (`(x12)`).

> **Примечание**
>
> **Фоновые процессы.** Процесс, оставленный скриптом в фоне, следующие события канала не задерживает, даже если унаследовал поток ошибок. В версиях до 2.0.2.415 такой процесс останавливал канал до своего завершения, поэтому на узлах, где могут работать прежние версии, и вообще для аккуратности запускайте фоновую работу с закрытым вводом и выводом:
>
> ```sh
> some-command </dev/null >/dev/null 2>&1 &
> ```
>
> или с перенаправлением в собственный файл журнала:
>
> ```sh
> setsid some-command </dev/null >>/var/log/pss-alerts/bg.log 2>&1 &
> ```

## Безопасность

- Скрипт работает с правами службы, пользователя `pss`. Владельцем файла скрипта делайте `root`, а пользователю `pss` давайте только чтение и запуск (режим `750`, группа `pss`): тогда служба не может изменить скрипт, который сама запускает.
- Не передавайте секреты в аргументах «Команды». Аргументы видны всем пользователям системы в списке процессов (`ps`), хранятся в файле настроек и видны в веб-интерфейсе роли Admin (ролям Restricted admin и Viewer строка команды показывается скрытой). Токены и пароли храните в самом скрипте или в отдельном файле, доступном на чтение только пользователю `pss`.
- Текст оповещения (имя потока, сообщение) приходит из настроек и с источников. Не подставляйте его в команды оболочки без кавычек и не передавайте в `eval`. Заготовки ниже передают значения только в кавычках.

## Заготовка на /bin/sh

Скрипт пишет одну строку в журнал на каждое событие и завершается с кодом 0. Место для настоящей доставки отмечено комментарием.

```sh
#!/bin/sh
# Получатель оповещений Perfect Streamer (канал «Процесс»).
# Узел запускает скрипт на каждое возникновение и снятие инцидента.
# Поля события лежат в переменных PSS_ALERT_*, весь JSON - на стандартном входе.

LOG=/var/log/pss-alerts/alerts.log

# JSON со стандартного входа: одна строка без перевода строки в конце,
# поэтому читаем через cat, а не через read.
json=$(cat)

# Одна строка в журнал. Ошибка записи - ненулевой код: узел запишет её
# в свой журнал вместе с текстом из stderr.
printf '%s %s %s %s [%s] %s: %s\n' \
    "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$PSS_ALERT_STATE" "$PSS_ALERT_SEVERITY_STR" \
    "$PSS_ALERT_NODE" "$PSS_ALERT_CODE_STR" "$PSS_ALERT_DISPLAY_NAME" \
    "$PSS_ALERT_MESSAGE" >> "$LOG" || exit 1

# Здесь подключается настоящая доставка, например вебхук.
# Синхронный вызов должен уложиться в «Таймаут, ms» (по умолчанию 5 с):
#
# printf '%s' "$json" | curl -sS -m 3 -X POST \
#     -H 'Content-Type: application/json' --data-binary @- \
#     https://hooks.example.com/pss >/dev/null || exit 1
#
# Долгую доставку - только в фон и только с закрытым вводом и выводом,
# иначе канал встанет (см. «Результат, таймаут и журнал»):
#
# ( printf '%s' "$json" | curl -sS -m 30 -X POST \
#     -H 'Content-Type: application/json' --data-binary @- \
#     https://hooks.example.com/pss ) </dev/null >/dev/null 2>&1 &

exit 0
```

## Заготовка на Python 3

То же на Python 3, только стандартная библиотека (Python 3.6 и новее).

```python
#!/usr/bin/env python3
# Получатель оповещений Perfect Streamer (канал «Процесс»).
# Узел запускает скрипт на каждое возникновение и снятие инцидента.
# Поля события лежат в переменных PSS_ALERT_*, весь JSON - на стандартном входе.

import json
import os
import sys
import time

LOG = "/var/log/pss-alerts/alerts.log"

def main():
    # Весь стандартный вход: одна строка JSON без перевода строки в конце.
    raw = sys.stdin.read()
    alert = json.loads(raw) if raw else {}
    env = os.environ

    line = "{} {} {} {} [{}] {}: {}\n".format(
        time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
        alert.get("state", env.get("PSS_ALERT_STATE", "")),
        env.get("PSS_ALERT_SEVERITY_STR", ""),
        alert.get("node", ""),
        env.get("PSS_ALERT_CODE_STR", ""),
        alert.get("display_name", ""),
        alert.get("message", ""),
    )
    # Ошибка записи завершит скрипт с ненулевым кодом, а текст ошибки
    # уйдёт в stderr - узел запишет его в свой журнал.
    with open(LOG, "a", encoding="utf-8") as f:
        f.write(line)

    # Здесь подключается настоящая доставка, например вебхук.
    # Вызов должен уложиться в «Таймаут, ms» (по умолчанию 5 с):
    #
    # import urllib.request
    # req = urllib.request.Request(
    #     "https://hooks.example.com/pss",
    #     data=raw.encode("utf-8"),
    #     headers={"Content-Type": "application/json"},
    # )
    # urllib.request.urlopen(req, timeout=3).close()
    #
    # Фоновые процессы (subprocess.Popen) запускайте с
    # stdin/stdout/stderr=subprocess.DEVNULL и start_new_session=True
    # (см. «Результат, таймаут и журнал»).

    return 0

if __name__ == "__main__":
    sys.exit(main())
```

## Установка и проверка

1. Создайте каталог журнала, доступный на запись пользователю `pss`:

   ```sh
   sudo install -d -o pss -g pss -m 750 /var/log/pss-alerts
   ```
2. Сохраните заготовку, например как `/usr/local/bin/pss-alert.sh` (или `pss-alert.py`), и выставьте владельца и права:

   ```sh
   sudo chown root:pss /usr/local/bin/pss-alert.sh
   sudo chmod 750 /usr/local/bin/pss-alert.sh
   ```
3. Проверьте скрипт вручную от имени пользователя `pss` с образцом события:

   ```sh
   echo '{"id":"2:12:3","state":"raised","severity":4,"node":"edge-1","display_name":"Sport HD","code":3,"message":"test"}' | \
     sudo -u pss env PSS_ALERT_ID=2:12:3 PSS_ALERT_STATE=raised \
       PSS_ALERT_SEVERITY=4 PSS_ALERT_SEVERITY_STR=Warning \
       PSS_ALERT_CODE=3 PSS_ALERT_CODE_STR=no-data \
       PSS_ALERT_NODE=edge-1 PSS_ALERT_DISPLAY_NAME='Sport HD' \
       PSS_ALERT_MESSAGE=test \
       /usr/local/bin/pss-alert.sh
   echo "exit=$?"
   tail -n 1 /var/log/pss-alerts/alerts.log
   ```

   Ожидается `exit=0` и новая строка в журнале. Заодно засеките время: оно должно быть заметно меньше «Таймаута, ms».
4. На вкладке «Процесс» окна «Назначения оповещений» укажите в «Команде» полный путь `/usr/local/bin/pss-alert.sh`, задайте «Минимальную важность», включите назначение и сохраните.
5. Проверьте доставку на тестовом потоке: прервите подачу данных на его вход (например, остановите передатчик). Через таймаут отсутствия данных (по умолчанию 15 секунд, [Настройки алертера](../meshwork/alerts.md#meshwork-alerts-settings)) возникнет оповещение `no-data` с важностью `Warning`, а после возобновления данных — его снятие. В журнале скрипта должны появиться строки `raised` и затем `cleared`.

## Типичные проблемы

- **Скрипт не запускается вовсе.** Проверьте главный выключатель оповещателя и «Включить назначение «Процесс»», что «Команда» не пуста и что важность события не ниже порога. По умолчанию порог `Warning`: события `Info` наружу не уходят.
- **В журнале службы** `process-command not executable`. Файл по указанному пути не существует или у пользователя `pss` нет права на запуск. Проверьте `ls -l` и права на все каталоги пути.
- **В журнале службы** `posix_spawnp failed`. Программу не удалось запустить: чаще всего имя указано без пути и не найдено в `PATH` службы, или неверна первая строка скрипта (`#!`). Указывайте полный путь.
- **В журнале службы** `exit <N>`. Скрипт завершился с ошибкой, её текст из stderr приведён в той же строке. Частая причина — нет права на запись в каталог журнала или в рабочие файлы: скрипт работает от `pss`, а не от `root`, и в каталоге `/`.
- **В журнале службы** `timed out after <N> ms`. Скрипт не уложился в таймаут: ускорьте доставку (таймаут сетевого вызова меньше «Таймаута, ms»), увеличьте «Таймаут, ms» или вынесите долгую часть в фон с закрытым выводом.
- **Оповещения приходят с большой задержкой или пропадают; в журнале** `delivery queue full`. Скрипт слишком медленный для потока событий: очередь канала переполнена. Ускорьте скрипт или поднимите «Минимальную важность».
- **Канал замолчал без единой ошибки в журнале** (версии до 2.0.2.415). Скорее всего, скрипт оставил фоновый процесс с унаследованным потоком ошибок, и узел ждёт его завершения. Найдите процесс (`ps -u pss -f`), завершите его и исправьте запуск фона: с `</dev/null >/dev/null 2>&1` или с перенаправлением в файл. С версии 2.0.2.415 такой процесс канал не останавливает.
- **Скрипт работает вручную, но не из узла.** Причина в окружении: другой пользователь (`pss`), рабочий каталог `/`, другой `PATH`, нет переменных вашего входа в систему. Проверяйте командой из шага 3 раздела Установка и проверка, через `sudo -u pss`.
- **Пришло второе** `raised` **с тем же** `PSS_ALERT_ID`. Служба перезапускалась, и действующий инцидент был обнаружен заново (см. Когда запускается скрипт).
- **Не пришло** `cleared`. Оповещатель узла выключили, пока инцидент был активен, либо служба перезапустилась раньше снятия (см. Когда запускается скрипт).
- **Одно и то же оповещение приходит с нескольких узлов.** Канал включён на нескольких узлах домена, разносящих оповещения. Оставьте его на одном.
- **Вывод скрипта не виден.** Стандартный вывод уходит в `journalctl -u pss`, а поток ошибок попадает в журнал службы только при ненулевом коде завершения. Пишите диагностику в собственный файл журнала.
