Skip to content

Repository files navigation

ss-notifier — служба уведомлений о прогонах

Крутится там, где есть постоянный адрес (JumpHost, облако), ловит хук и пишет в Telegram или ntfy. Токен бота лежит у неё одной; всем остальным довольно уметь POST.

curl -X POST http://jumphost:8790/notify \
  -H "X-SS-Token: $SS_NOTIFY_TOKEN" \
  --data-urlencode "level=done" \
  --data-urlencode "title=серия stage-final" \
  --data-urlencode "text=готово за 4 ч 12 мин"

Зачем

Серия экспериментов идёт ~4 часа, свип веса — под сутки, и всё это время за стендом никто не сидит. Наблюдаемость в sensitivityscore-hpc-bench была целиком pull: вотчдог честно писал в лог, что серия зависла, что port-forward к Redis умер (а значит placement_regret с этого момента — NaN до конца прогона) и что прогон кончился, — но лог лежит на хосте, а статус-страница видна только из домашней сети. Узнавали постфактум: 20.07 хост ушёл в сон на 6.4 часа, и метрика решения пропала у 120 строк из 180 — молча.

Почему служба, а не утилита на каждой машине:

  • секрет в одном месте. Токен бота не расползается по ноутбуку, JumpHost'у и прод-узлу; отозвать или сменить его — одна правка в одном файле;
  • вызывающему нужен только curl. Ни docker, ни python, ни установки — в run-series.sh уведомление это буквально один вызов curl;
  • есть куда направить хуки, которые сейчас не идут никуда: в первую очередь Alertmanager с правилами SSLLCAxisSaturated и SSAxisDegenerate — они долетают до Grafana и на этом останавливаются.

Служба НИЧЕГО не знает про эксперименты: что считать бедой, решает отправитель. Здесь только доставка.

Поднять

make config      # config.env из образца
# вписать TELEGRAM_BOT_TOKEN (@BotFather) и SS_NOTIFY_TOKEN (openssl rand -hex 24)
make up          # сборка + запуск + ожидание готовности
make chat-id     # написать боту любое сообщение, потом эта команда покажет chat_id
make check       # живая проверка: служба + канал (отправит одно сообщение)

На выделенном хосте — ещё и автозапуск:

make unit        # systemd-юнит: служба переживает перезагрузку

Юнит не дублирует restart: unless-stopped из compose, а закрывает случай, до которого та политика не достаёт: после docker compose down возвращать нечего, и служба не поднимется уже никогда, причём молча. А отсутствие уведомлений выглядит ровно как «всё хорошо» — потому эту дыру и затыкаем отдельно.

Эндпойнты

путь что делает
POST /notify уведомление: level, title, text, необязательные file_name + file_b64
POST /alert вебхук Alertmanager: сам разбирает firing/resolved, метки и аннотации
GET /healthz живость; ничего не отправляет и токена не требует
GET / что это за служба и какой у неё канал

/notify принимает и форму, и JSON. Форма — ради shell: --data-urlencode экранирует хвост лога сам, а собирать JSON в bash значит городить escaping для кавычек и переносов и однажды на нём ошибиться.

Уровни: info, warn, error, done — значок в сообщении и приоритет у ntfy.

Вложение — base64 в поле file_b64 (клиент ss-notify -f делает это сам):

curl -X POST http://jumphost:8790/notify -H "X-SS-Token: $tok" \
  --data-urlencode "title=отчёт готов" \
  --data-urlencode "file_name=summary.md" \
  --data-urlencode "file_b64=$(base64 < summary.md | tr -d '\n')"

Alertmanager

receivers:
  - name: ss-notifier
    webhook_configs:
      - url: http://192.168.1.72:8790/alert
        http_config:
          authorization: { type: Bearer, credentials: <SS_NOTIFY_TOKEN> }

Снятый алерт приходит как done, сработавший — как error; в тексте имя правила, severity, узел и summary.

Доступ

Служба держит токен бота и пишет от вашего имени, поэтому без SS_NOTIFY_TOKEN она не стартует. Открыть приём без проверки можно, но только явно — SS_NOTIFY_ALLOW_ANON=1, и это осмысленно лишь внутри доверенной сети.

Токен приёма обязан быть ASCII: он едет HTTP-заголовком X-SS-Token, а значения заголовков латиницей и ограничены. Кириллический токен служба отвергает при старте — иначе отказ вылезал бы у клиента на первом уведомлении, и не про токен, а про latin-1.

Контейнер работает не от root и с read-only файловой системой: служба ничего не пишет на диск, и это зафиксировано, а не задекларировано.

Клиент (не обязателен)

ss-notify — тонкий клиент: то же самое, что curl выше, но с ключами, сухим прогоном и --check.

make install                                   # -> ~/.local/bin/ss-notify
export SS_NOTIFY_URL=http://jumphost:8790 SS_NOTIFY_TOKEN=…
ss-notify -l done -t "серия stage-final" "готово"
ss-notify -l error -t "серия зависла" -f harness/stage-final.log "лог не растёт"

Без SS_NOTIFY_URL тот же скрипт шлёт напрямую в Telegram/ntfy по своему ~/.config/ss-notify/config.env — режим для ноутбука, где службы нет. Вызовы и коды возврата в обоих режимах одинаковы.

Коды возврата: 0 — отправлено (либо выключено, либо сухой прогон), 1 — не удалось, 2 — ошибка вызова или конфигурации. Вызывающему, для которого уведомление второстепенно, полагается дописывать || true.

Что уходит наружу

Сообщения и вложения уезжают на серверы Telegram. Для прогонов это хвост лога и summary.md отчёта — неопубликованные числа. Если это неприемлемо, в config.env:

SS_NOTIFY_BACKEND=ntfy
NTFY_URL=https://ntfy.example.org      # свой инстанс
NTFY_TOPIC=<длинная случайная строка>  # тема — это и есть весь доступ

Отправители об этом не знают и не меняются: канал выбирает служба.

Проверки

make test    # служба (python, без сети) + клиент (bash)
make check   # живая проверка канала
make lint    # shellcheck + компиляция python

Тесты службы поднимают свой приёмник вместо api.telegram.org — поэтому адрес API и сделан настройкой (TELEGRAM_API), а не константой: иначе проверить можно было бы только «функция не упала», а не «ушло именно то, что надо». Проверяется в том числе то, на чём легко ошибиться: ok:false при HTTP 200 (обычный ответ Bot API на протухший токен) обязан давать 502, а не рапорт об успехе; текст с кавычками и переносами обязан доезжать без порчи; /healthz не должен ничего отправлять.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages