Push notifications to Gotify on every SSH login and logout - with source IP, rDNS, geolocation, auth method and session duration. Installer script for Debian/Ubuntu, NixOS module included.
  • Shell 63.3%
  • Nix 36.7%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Mimic890 7ab4a60522 Rewrite cmd_status: compact colored output, fix set -e abort bug
- cmd_status больше не дампит сырой конфиг с комментариями и
  journalctl -n 10 — вместо этого короткая цветная сводка (пути,
  PAM/UsePAM, сессии, кто логинился сегодня/сейчас,
  Gotify/гео/TTY/выход,
  списки исключений только если непустые) и 3 последних события из лога
  в том же стиле подсветки, что и ssh-notify logs. Уважает NO_COLOR и
  неинтерактивный вывод.
- Убрано выравнивание через printf %-Ns: оно считает байты, а не
  символы, и с кириллицей (2 байта на букву) колонки съезжали вкривь.
  Формат упрощён до "Метка: значение".
- Исправлен реальный баг: последней командой сабшелла с настройками была
  "[ -n "$IGNORE_PAIRS" ] && row ..." — при пустом списке она возвращает
  код 1, и под set -e это молча обрывало весь ssh-notify status кодом
  выхода 1 без единого сообщения об ошибке. Добавлен true последней
  командой сабшелла.
2026-09-23 22:00:43 +03:00
tools Rename ssh-notify-ctl to ssh-notify, simplify install, add update и 2026-09-23 21:50:40 +03:00
.gitignore Add ssh-notify-ctl: установщик, сервис уведомлений и история входов 2026-09-17 22:08:26 +03:00
install-ssh-notify.sh Rewrite cmd_status: compact colored output, fix set -e abort bug 2026-09-23 22:00:43 +03:00
LICENSE Initial commit 2026-09-17 21:13:52 +03:00
README.md Rename ssh-notify-ctl to ssh-notify, simplify install, add update и 2026-09-23 21:50:40 +03:00
ssh-notify.nix Rewrite cmd_status: compact colored output, fix set -e abort bug 2026-09-23 22:00:43 +03:00
ssh-notify.sh Rename ssh-notify-ctl to ssh-notify, simplify install, add update и 2026-09-23 21:50:40 +03:00

ssh-gotify-notify

Кто, откуда и насколько зашёл на твой сервер по SSH.

Подключается к PAM через pam_exec и шлёт уведомление в Gotify при открытии и закрытии сессии.

Вход: пользователь, IP, rDNS, город/страна/ASN (через ipinfo.io), метод аутентификации (publickey/password), TTY, время, uptime хоста.

Выход: пользователь, IP, длительность сессии.

SSH вход: alice@web-01
Пользователь: alice
IP:           203.0.113.42
rDNS:         host.example.net
Гео:          Berlin, Berlin, DE, AS3320 Deutsche Telekom AG
Метод:        publickey
TTY:          pts/0
Время:        2026-09-17 21:30:24 MSK
Uptime:       up 12 days, 4 hours

Установка

Скачай управляющую команду в /usr/local/bin и сделай исполняемой:

sudo curl -fsSL https://raw.githubusercontent.com/Mimic890/ssh-gotify-notify/main/install-ssh-notify.sh -o /usr/local/bin/ssh-notify && sudo chmod +x /usr/local/bin/ssh-notify

Дальше всё делается командой ssh-notify, путь помнить не нужно:

sudo ssh-notify install

Установщик спросит только URL и токен (это разово, дальше не спрашивает ничего), поставит curl и jq, отправит два тестовых сообщения — и только если они дошли, тронет PAM. Не дошли — ничего не меняется. Остальные настройки (гео, списки исключений, файл лога, приоритеты) — не через диалог, а прямо в /etc/ssh-notify.conf: файл со своими комментариями на каждую настройку, правится руками и перечитывается на каждый вход без перезапуска.

Поддерживаются Debian, Ubuntu, Arch, Fedora, openSUSE и Alpine: пакеты ставятся через apt-get, pacman, dnf, yum, zypper или apk — какой найдётся. Проверялось вживую только на Debian/Ubuntu.

Команды

ssh-notify

Без аргументов (нужен root) — список команд, версия, статус, кто сегодня заходил и кто залогинен прямо сейчас. То же самое, что ssh-notify status, только со справкой сверху.

sudo ssh-notify status
sudo ssh-notify test
sudo ssh-notify logs
sudo ssh-notify install
sudo ssh-notify update

Скачивает свежую версию установщика с GitHub, ставит её поверх себя и сразу переустанавливается с уже сохранёнными в конфиге настройками — ничего заново не спрашивает.

sudo ssh-notify uninstall

install можно запускать повторно: если конфиг уже есть, install ничего не спрашивает, просто обновляет свои файлы и PAM-строку поверх. Сменить настройки — не через install, а прямым редактированием /etc/ssh-notify.conf.

История входов

Каждое событие пишется в /var/log/ssh-notify.log тем же блоком, что уходит в Gotify, независимо от того, дошло ли уведомление. Если Gotify был недоступен, событие всё равно в истории, с пометкой:

=== 2026-09-17 21:30:24 MSK · вход · alice · 203.0.113.42 ===
Пользователь: alice
IP:           203.0.113.42
rDNS:         host.example.net
Гео:          Berlin, Berlin, DE, AS3320 Deutsche Telekom AG
Метод:        publickey
TTY:          pts/0
Время:        2026-09-17 21:30:24 MSK
Uptime:       up 12 days, 4 hours

Смотреть можно прямо файлом, а можно командой — она умеет фильтровать и подсвечивать:

sudo ssh-notify logs -n 5
sudo ssh-notify logs -f
Опция Что делает
-n N, --lines N последние N записей (по умолчанию 20)
-a, --all все записи
-f, --follow следить в реальном времени
-u, --user ПОДСТР фильтр по пользователю
-i, --ip ПОДСТР фильтр по адресу
--open / --close только входы / только выходы
--failed только недоставленные в Gotify
-g, --grep ТЕКСТ поиск по всему тексту записи
--since КОГДА новее указанного: today, вчера, 2026-09-17, -2 hours
--stats сводка: сколько, кто чаще, откуда чаще
--path путь к файлу лога
--no-color без подсветки

Фильтры комбинируются и работают в том числе с -f:

sudo ssh-notify logs -u alice --since today --failed

Ротацию ставит сам установщик в /etc/logrotate.d/ssh-notify: еженедельно, 8 архивов, сжатие. Файл создаётся с правами 640 root:adm — внутри имена пользователей и адреса. Выключить логирование можно, задав пустой LOG_FILE в конфиге или ответив нет при установке.

Параллельно каждое событие уходит в syslog одной структурированной строкой — это выручит, если файл удалят, и хорошо грепается:

journalctl -t ssh-notify | grep 'user=alice'

uninstall убирает всё: строку из PAM, скрипты, состояние, свои бэкапы PAM и саму команду ssh-notify. Про конфиг с токеном спросит отдельно (--yes — удалить без вопросов). sshd_config не трогается.

NixOS

Установщик здесь не нужен и откажется работать. Возьми из репозитория один файл — ssh-notify.nix, — положи в свои модули и подключи:

imports = [ ./ssh-notify.nix ];

services.sshNotify = {
  enable = true;
  url = "https://gotify.example.com";
  token = "AgNGgB5xE.sdkVy1O";
  ignoreNets = [ "10.*" "192.168.*" ];
  ignorePairs = [ "admin@192.168.1.10" ];
};

token — прямым текстом, проще всего для быстрой настройки, но попадает в /nix/store и в конфиг в открытом виде, читаемые любым пользователем системы. Если это важно — используй tokenFile вместо него:

services.sshNotify = {
  # ...
  tokenFile = config.sops.secrets.gotify-ssh-token.path;
};

Передавай путь строкой (sops-nix, agenix), а не literal-путь вида ./token: второй Nix скопировал бы в /nix/store точно так же. Если заданы оба, побеждает tokenFile.

После enable = true и пересборки в системе появляется команда ssh-notify — упрощённый аналог установщика для диагностики (настройки на NixOS правятся не конфигом, а модулем, поэтому install, uninstall и update ей не нужны — обновление модуля идёт через nixos-rebuild):

ssh-notify           # без аргументов — команды, версия, статус, кто заходил сегодня и кто сейчас в системе
ssh-notify status    # PAM подключён? UsePAM? сколько записей в логе?
ssh-notify test      # отправить тестовые сообщения о входе и выходе
ssh-notify logs      # история входов и выходов, те же опции, что ниже

Настройки

Правятся в /etc/ssh-notify.conf — скрипт перечитывает файл на каждый вход, перезапускать ничего не нужно. На NixOS это одноимённые опции services.sshNotify в camelCase.

Ключ По умолчанию Что делает
GOTIFY_URL — Базовый URL Gotify, завершающий слэш убирается сам
GOTIFY_TOKEN — Application-токен
GOTIFY_TOKEN_FILE — Путь к файлу с токеном вместо самого токена
GEO_LOOKUP 1 Гео через ipinfo.io
REQUIRE_TTY 0 1 — только интерактивные сессии, без sftp/scp/rsync
NOTIFY_CLOSE 1 0 — молчать про выход
IGNORE_USERS пусто Не уведомлять об этих пользователях
IGNORE_NETS пусто Не уведомлять об этих адресах
IGNORE_PAIRS пусто Не уведомлять об этих парах пользователь@адрес
PRIORITY_OPEN 7 Приоритет уведомления о входе
PRIORITY_CLOSE 3 Приоритет уведомления о выходе
HTTP_TIMEOUT 10 Таймаут запроса к Gotify, секунды
GEO_TIMEOUT 5 Таймаут запроса к ipinfo.io, секунды
LOG_FILE /var/log/ssh-notify.log Файл истории, пусто — не вести

Приоритет 8 и выше пробивает «не беспокоить» на Android.

Списки исключений

IGNORE_USERS и IGNORE_NETS — glob-шаблоны через пробел, не CIDR:

IGNORE_USERS="backup rsync-*"
IGNORE_NETS="10.* 192.168.* 203.0.113.5"

Без них резервные копии и мониторинг будут звонить в телефон круглые сутки, а алерты, которые звонят слишком часто, перестают работать.

IGNORE_USERS/IGNORE_NETS глушат пользователя или адрес целиком. Если нужно молчать только про конкретную комбинацию — например, о своих собственных заходах из дома, но не о заходах того же аккаунта откуда-то ещё — используй IGNORE_PAIRS: список пользователь@адрес через пробел, где обе части снова glob-шаблоны:

IGNORE_PAIRS="admin@192.168.1.10 admin@10.0.0.*"

Тот же admin, зашедший не из этих сетей, уведомление всё равно получит.

Диагностика

Скрипт пишет в syslog под тегом ssh-notify — и успехи, и любой сбой доставки:

journalctl -t ssh-notify -n 20

Проверить отправку, не заходя по SSH:

sudo ssh-notify test

Если вход по SSH сломался, из уже открытой сессии:

sudo sed -i '/ssh-notify-wrap/d' /etc/pam.d/sshd

Как это устроено

pam_exec вызывается синхронно, поэтому недоступный Gotify задержал бы логин на весь таймаут curl. Между PAM и основным скриптом стоит обёртка: она запускает работу через setsid в фоне и возвращает управление за миллисекунду. Правило PAM — optional, так что сломанный скрипт не может закрыть тебе вход.

Раскладка файлов:

Путь Что это
/usr/local/bin/ssh-notify управляющая команда (этот же установщик)
/usr/local/bin/ssh-notify-worker рабочий скрипт, шлёт уведомление
/usr/local/bin/ssh-notify-wrap обёртка для pam_exec
/etc/ssh-notify.conf настройки и токен, права 600
/var/log/ssh-notify.log история входов и выходов, права 640 root:adm
/etc/logrotate.d/ssh-notify правило ротации лога
/run/ssh-notify/ время начала сессий, чтобы считать длительность

Длительность считается через файл в /run/ssh-notify, имя файла — PID процесса sshd, обслуживающего сессию. Если открытие и закрытие почему-то обработали разные процессы, в уведомлении будет «Длительность: неизвестно» — на сам вход это не влияет. Осиротевшие файлы старше недели подчищаются автоматически.

ssh-notify.sh в репозитории — единственный источник правды. ssh-notify.nix и install-ssh-notify.sh носят встроенные копии, чтобы каждый из них ставился одним файлом — .nix без соседнего .sh, установщик без ничего, кроме себя самого для curl. После правки скрипта:

./tools/sync-embedded.sh

Обновление с версии до 2.1

До 2.1 управляющая команда называлась ssh-notify-ctl, а /usr/local/bin/ssh-notify был рабочим скриптом. С 2.1 наоборот: короткое имя ssh-notify — у команды управления, воркер переехал в ssh-notify-worker. Проще всего обновиться так — из уже установленной 2.0:

sudo ssh-notify-ctl update

если update ещё нет (совсем старая версия) — обычным способом, сразу следом за установщиком:

sudo curl -fsSL https://raw.githubusercontent.com/Mimic890/ssh-gotify-notify/main/install-ssh-notify.sh -o /usr/local/bin/ssh-notify && sudo chmod +x /usr/local/bin/ssh-notify && sudo ssh-notify install

Между скачиванием и install — окно в пару секунд, когда уведомления не приходят (curl перетирает старый воркер новой командой управления, пока install не пересоздаст воркер под новым именем и не перепишет обёртку). На сам вход это не влияет: правило PAM optional. install подтянет текущий /etc/ssh-notify.conf без переспроса и уберёт ssh-notify-ctl как устаревший файл.

Ограничения

  • Уведомления приходят только на успешные входы. Для неудачных попыток нужен fail2ban или CrowdSec с действием в Gotify — иначе торчащий в интернет 22-й порт зальёт телефон.
  • Гео определяется только для публичных адресов. ipinfo.io без токена отдаёт 1000 запросов в месяц.
  • Заведи для SSH отдельное приложение в Gotify, чтобы эти уведомления можно было отключить отдельно от остальных.
  • Записи дописываются в лог в момент обработки события, а вход ещё ждёт геозапрос. У совсем короткой сессии выход может оказаться в файле выше входа — ориентируйся на штамп времени в записи, он снимается до всех сетевых запросов.
  • Тексты уведомлений русские и зашиты в ssh-notify.sh.