feat: подпись сервера во всех уведомлениях и документация
Добавлены message_ensure_server_line и SERVER_DISPLAY_NAME; версия 1.1.3-server-label. Гайды по уведомлениям и автообновлению.
This commit is contained in:
@@ -8,6 +8,7 @@ Bash-скрипт мониторинга **SSH**, **SUDO** и событий **`
|
|||||||
|------|------------|
|
|------|------------|
|
||||||
| **`ssh-monitor`** | Основной цикл: разбор журналов (**journalctl**: sshd, sudo, «тяжёлые» события, **systemd-logind**), цепочка уведомлений (Telegram, SMTP через **python3**, резервный webhook), при **`ENABLE_IP_BAN=1`** — учёт неудачных попыток, бан в **ipset** и согласование с **iptables**/**ip6tables**, ежедневный отчёт, heartbeat, опционально **Prometheus** textfile и JSON health. Режимы: **`--check-config`**, **`--dry-run`**. |
|
| **`ssh-monitor`** | Основной цикл: разбор журналов (**journalctl**: sshd, sudo, «тяжёлые» события, **systemd-logind**), цепочка уведомлений (Telegram, SMTP через **python3**, резервный webhook), при **`ENABLE_IP_BAN=1`** — учёт неудачных попыток, бан в **ipset** и согласование с **iptables**/**ip6tables**, ежедневный отчёт, heartbeat, опционально **Prometheus** textfile и JSON health. Режимы: **`--check-config`**, **`--dry-run`**. |
|
||||||
| **`ssh-monitor-watchdog`** | Отдельный скрипт (обычно по **systemd timer**): проверяет, что сервис монитора активен и файл heartbeat не старше порога; при сбое выполняет **`systemctl restart`** и шлёт уведомление в Telegram или в **`BACKUP_WEBHOOK_URL`**. Читает те же `TELEGRAM_*` / webhook из `/etc/ssh-monitor.conf`. |
|
| **`ssh-monitor-watchdog`** | Отдельный скрипт (обычно по **systemd timer**): проверяет, что сервис монитора активен и файл heartbeat не старше порога; при сбое выполняет **`systemctl restart`** и шлёт уведомление в Telegram или в **`BACKUP_WEBHOOK_URL`**. Читает те же `TELEGRAM_*` / webhook из `/etc/ssh-monitor.conf`. |
|
||||||
|
| **`update_ssh_monitor.sh`** | Опциональное автообновление: `git pull` / `clone`, сравнение SHA256 с `/usr/local/bin/ssh-monitor`, перезапуск `ssh-monitor.service`. См. [docs/auto-update.ru.md](docs/auto-update.ru.md). |
|
||||||
|
|
||||||
**Системные пакеты и зависимости** (что поставить на сервер до запуска): пошагово в [docs/install-prerequisites.ru.md](docs/install-prerequisites.ru.md).
|
**Системные пакеты и зависимости** (что поставить на сервер до запуска): пошагово в [docs/install-prerequisites.ru.md](docs/install-prerequisites.ru.md).
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@ Bash-скрипт мониторинга **SSH**, **SUDO** и событий **`
|
|||||||
- `BACKUP_WEBHOOK_URL` — резервная доставка JSON `{"text":"..."}` (например Slack Incoming Webhook), если **все** каналы из **`NOTIFY_CHAIN`** не смогли доставить сообщение; **не заменяет** обязательность хотя бы одного основного канала (см. шаг 3)
|
- `BACKUP_WEBHOOK_URL` — резервная доставка JSON `{"text":"..."}` (например Slack Incoming Webhook), если **все** каналы из **`NOTIFY_CHAIN`** не смогли доставить сообщение; **не заменяет** обязательность хотя бы одного основного канала (см. шаг 3)
|
||||||
- `LOG_FILE`, `LAST_HEARTBEAT_FILE`, `LAST_REPORT_FILE`, `LAST_SSH_CHECK_FILE`, `LAST_SUDO_CHECK_FILE`, `LAST_SECURITY_EVENTS_FILE`, `LAST_LOGIND_CHECK_FILE`, `BAN_LIST_FILE`
|
- `LOG_FILE`, `LAST_HEARTBEAT_FILE`, `LAST_REPORT_FILE`, `LAST_SSH_CHECK_FILE`, `LAST_SUDO_CHECK_FILE`, `LAST_SECURITY_EVENTS_FILE`, `LAST_LOGIND_CHECK_FILE`, `BAN_LIST_FILE`
|
||||||
- `ENABLE_LOGIND_MONITOR`, `LOGIND_NOTIFY_NEW`, `LOGIND_NOTIFY_REMOVED`, `LOGIND_NOTIFY_FAILED`, `LOGIND_SKIP_REMOTE` — см. раздел «Мониторинг systemd-logind» ниже
|
- `ENABLE_LOGIND_MONITOR`, `LOGIND_NOTIFY_NEW`, `LOGIND_NOTIFY_REMOVED`, `LOGIND_NOTIFY_FAILED`, `LOGIND_SKIP_REMOTE` — см. раздел «Мониторинг systemd-logind» ниже
|
||||||
- `DAILY_REPORT_HOUR` (0..23), `DAILY_REPORT_TZ` (опционально), `NOTIFY_TZ` (опционально), `SSH_ACCEPT_NOTIFY_DEDUP_SEC`, `DAILY_REPORT_TOP_IPS`
|
- `DAILY_REPORT_HOUR` (0..23), `DAILY_REPORT_TZ` (опционально), `NOTIFY_TZ` (опционально), `SERVER_DISPLAY_NAME` (опционально), `SSH_ACCEPT_NOTIFY_DEDUP_SEC`, `DAILY_REPORT_TOP_IPS`
|
||||||
- `BRUTE_WINDOW_SEC`, `BRUTE_MIN_FAILS`, `BRUTE_NOTIFY_COOLDOWN_SEC`
|
- `BRUTE_WINDOW_SEC`, `BRUTE_MIN_FAILS`, `BRUTE_NOTIFY_COOLDOWN_SEC`
|
||||||
- `PROMETHEUS_TEXTFILE_DIR` — каталог для `ssh_monitor.prom` (совместимость с node_exporter textfile collector)
|
- `PROMETHEUS_TEXTFILE_DIR` — каталог для `ssh_monitor.prom` (совместимость с node_exporter textfile collector)
|
||||||
- `HEALTHCHECK_STATUS_FILE` — путь к JSON-файлу с меткой последней итерации цикла
|
- `HEALTHCHECK_STATUS_FILE` — путь к JSON-файлу с меткой последней итерации цикла
|
||||||
@@ -67,6 +68,7 @@ Bash-скрипт мониторинга **SSH**, **SUDO** и событий **`
|
|||||||
- `DAILY_REPORT_HOUR` — час (0..23), после наступления которого в **текущих календарных сутках** (в выбранной ниже зоне) отправляется не более одного ежедневного отчёта.
|
- `DAILY_REPORT_HOUR` — час (0..23), после наступления которого в **текущих календарных сутках** (в выбранной ниже зоне) отправляется не более одного ежедневного отчёта.
|
||||||
- `DAILY_REPORT_TZ` — необязательная **IANA**-зона (`Europe/Moscow`, `Asia/Yekaterinburg`, …). Если **пусто**, для отчёта используется та же зона, что и у команды `date` у процесса монитора (как правило, совпадает с `timedatectl` / `/etc/localtime` на сервере). Если сервис запускается с `TZ=UTC` в unit-файле, без `DAILY_REPORT_TZ` отчёт ориентируется на **UTC** — тогда задайте явную зону в конфиге.
|
- `DAILY_REPORT_TZ` — необязательная **IANA**-зона (`Europe/Moscow`, `Asia/Yekaterinburg`, …). Если **пусто**, для отчёта используется та же зона, что и у команды `date` у процесса монитора (как правило, совпадает с `timedatectl` / `/etc/localtime` на сервере). Если сервис запускается с `TZ=UTC` в unit-файле, без `DAILY_REPORT_TZ` отчёт ориентируется на **UTC** — тогда задайте явную зону в конфиге.
|
||||||
- `NOTIFY_TZ` — **IANA**-зона для строк **«🕐 Время»** в Telegram/email (и аналогичных текстах). **Пусто** — используется **`DAILY_REPORT_TZ`**, если она задана, иначе зона процесса. Частая причина «время в теле сообщения UTC, а в заголовке чата локальное»: в **`ssh-monitor.service`** задано **`Environment=TZ=UTC`** — тогда задайте **`NOTIFY_TZ`** (или **`DAILY_REPORT_TZ`**) на вашу локаль, например `Asia/Vladivostok`.
|
- `NOTIFY_TZ` — **IANA**-зона для строк **«🕐 Время»** в Telegram/email (и аналогичных текстах). **Пусто** — используется **`DAILY_REPORT_TZ`**, если она задана, иначе зона процесса. Частая причина «время в теле сообщения UTC, а в заголовке чата локальное»: в **`ssh-monitor.service`** задано **`Environment=TZ=UTC`** — тогда задайте **`NOTIFY_TZ`** (или **`DAILY_REPORT_TZ`**) на вашу локаль, например `Asia/Vladivostok`.
|
||||||
|
- `SERVER_DISPLAY_NAME` — подпись в строке **«🖥️ Сервер»** во всех уведомлениях. **Пусто** — `hostname` и при возможности основной IPv4 (`hostname (10.x.x.x)`). Задайте явное имя, если несколько хостов с одинаковым hostname шлют в один чат. Подробнее: [docs/notifications.ru.md](docs/notifications.ru.md).
|
||||||
- `SSH_ACCEPT_NOTIFY_DEDUP_SEC` — не чаще одного Telegram по **успешному SSH (`Accepted`)** на одну пару **пользователь + IP** за указанное число **секунд** (по умолчанию **5**). Снимает дубли, когда в journal две строки с **разными портами клиента** за один вход. **`0`** — отключить этот антидубль (останется только дедупликация внутри одного прохода `monitor_ssh` по `user|ip|port`).
|
- `SSH_ACCEPT_NOTIFY_DEDUP_SEC` — не чаще одного Telegram по **успешному SSH (`Accepted`)** на одну пару **пользователь + IP** за указанное число **секунд** (по умолчанию **5**). Снимает дубли, когда в journal две строки с **разными портами клиента** за один вход. **`0`** — отключить этот антидубль (останется только дедупликация внутри одного прохода `monitor_ssh` по `user|ip|port`).
|
||||||
- `DAILY_REPORT_TOP_IPS` — сколько IP показывать в топе неудачных попыток за 24 часа.
|
- `DAILY_REPORT_TOP_IPS` — сколько IP показывать в топе неудачных попыток за 24 часа.
|
||||||
- `BRUTE_WINDOW_SEC` — окно (секунды) для оценки «массового» брутфорса по `journalctl`.
|
- `BRUTE_WINDOW_SEC` — окно (секунды) для оценки «массового» брутфорса по `journalctl`.
|
||||||
@@ -109,6 +111,20 @@ Bash-скрипт мониторинга **SSH**, **SUDO** и событий **`
|
|||||||
|
|
||||||
Ограничения на хостах **без journald** и нюансы **`/var/log/auth.log`**: см. раздел 4 в [docs/install-prerequisites.ru.md](docs/install-prerequisites.ru.md).
|
Ограничения на хостах **без journald** и нюансы **`/var/log/auth.log`**: см. раздел 4 в [docs/install-prerequisites.ru.md](docs/install-prerequisites.ru.md).
|
||||||
|
|
||||||
|
### Идентификация сервера в уведомлениях
|
||||||
|
|
||||||
|
Во **всех** оповещениях (SSH, sudo, бан IP, logind, heartbeat, ежедневный отчёт и т.д.) после заголовка автоматически добавляется строка **«🖥️ Сервер: …»** — формат `hostname (IPv4)` или только `hostname`, если IPv4 определить не удалось. Логика в `notify_send()`; дублирование в тексте сообщения не требуется.
|
||||||
|
|
||||||
|
- Утилиты **`hostname`** и **`ip`** (см. [docs/install-prerequisites.ru.md](docs/install-prerequisites.ru.md)).
|
||||||
|
- Опционально **`SERVER_DISPLAY_NAME`** в конфиге — фиксированная подпись вместо hostname (удобно при нескольких серверах в одном Telegram-чате).
|
||||||
|
- Типы сообщений и примеры: [docs/notifications.ru.md](docs/notifications.ru.md).
|
||||||
|
|
||||||
|
### Автообновление скрипта
|
||||||
|
|
||||||
|
Скрипт **`update_ssh_monitor.sh`** по расписанию (cron или systemd timer) выполняет `git pull` / `clone`, сравнивает SHA256 с `/usr/local/bin/ssh-monitor` и при отличии копирует новую версию и перезапускает **`ssh-monitor.service`**. Конфиг **`/etc/ssh-monitor.conf`** не трогается.
|
||||||
|
|
||||||
|
Пошаговая настройка: [docs/auto-update.ru.md](docs/auto-update.ru.md).
|
||||||
|
|
||||||
## Режимы запуска
|
## Режимы запуска
|
||||||
|
|
||||||
Обычный запуск (root):
|
Обычный запуск (root):
|
||||||
@@ -137,7 +153,7 @@ bash -n ./ssh-monitor
|
|||||||
|
|
||||||
## Релизный архив
|
## Релизный архив
|
||||||
|
|
||||||
Версия задаётся в скрипте переменной **`SSH_MONITOR_VERSION`** (на момент последнего обновления документации — **1.1.2-ipset**; актуальное значение смотрите в файле **`ssh-monitor`**). Сборка tarball из текущего git-дерева:
|
Версия задаётся в скрипте переменной **`SSH_MONITOR_VERSION`** (на момент последнего обновления документации — **1.1.3-server-label**; актуальное значение смотрите в файле **`ssh-monitor`**). Сборка tarball из текущего git-дерева:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make dist
|
make dist
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
# Автообновление ssh-monitor
|
||||||
|
|
||||||
|
Скрипт **`update_ssh_monitor.sh`** в корне репозитория обновляет установленный **`/usr/local/bin/ssh-monitor`** из git-репозитория и перезапускает systemd-сервис. Конфиг **`/etc/ssh-monitor.conf`**, watchdog и таймеры **не изменяются**.
|
||||||
|
|
||||||
|
Установка самого монитора и unit-файлов: [README.md](../README.md), зависимости: [install-prerequisites.ru.md](install-prerequisites.ru.md).
|
||||||
|
|
||||||
|
## Что делает скрипт
|
||||||
|
|
||||||
|
1. Создаёт рабочий каталог **`UPDATE_DIR`** (по умолчанию `/opt/scripts/update`).
|
||||||
|
2. Проверяет наличие **`ipset`**; при отсутствии пытается установить через пакетный менеджер (apt, yum, dnf, pacman, zypper). Без ipset скрипт завершается с ошибкой — новая версия монитора с **`ENABLE_IP_BAN=1`** требует ipset.
|
||||||
|
3. В **`UPDATE_DIR`** выполняет **`git clone`** или **`git pull origin main`** репозитория.
|
||||||
|
4. Ищет файл **`ssh-monitor`** в клоне:
|
||||||
|
- `$UPDATE_DIR/ssh-monitor/src/branch/main/ssh-monitor` (структура GitLab);
|
||||||
|
- `$UPDATE_DIR/ssh-monitor/ssh-monitor` (плоский репозиторий, как в этом проекте).
|
||||||
|
5. Сравнивает **SHA256** удалённого файла с **`LOCAL_SCRIPT_PATH`** (по умолчанию `/usr/local/bin/ssh-monitor`).
|
||||||
|
6. При отличии:
|
||||||
|
- резервная копия `ssh-monitor.backup.YYYYMMDD_HHMMSS`;
|
||||||
|
- `cp` + `chmod +x`;
|
||||||
|
- **`systemctl restart ssh-monitor.service`** (если есть systemctl).
|
||||||
|
7. Пишет лог в **`LOG_FILE`** (по умолчанию `/var/log/update_script.log`).
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A[cron/timer] --> B[update_ssh_monitor.sh]
|
||||||
|
B --> C[git pull/clone]
|
||||||
|
C --> D{SHA256 различается?}
|
||||||
|
D -->|да| E[backup + cp]
|
||||||
|
E --> F[restart ssh-monitor.service]
|
||||||
|
D -->|нет| G[без изменений]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Настройка перед первым запуском
|
||||||
|
|
||||||
|
Отредактируйте переменные в начале **`update_ssh_monitor.sh`**:
|
||||||
|
|
||||||
|
| Переменная | По умолчанию | Назначение |
|
||||||
|
|------------|--------------|------------|
|
||||||
|
| `UPDATE_DIR` | `/opt/scripts/update` | Каталог для git-клона |
|
||||||
|
| `REPO_URL` | `https://GIT/user/ssh-monitor.git` | **Замените** на ваш реальный URL |
|
||||||
|
| `LOCAL_SCRIPT_PATH` | `/usr/local/bin/ssh-monitor` | Куда копировать бинарник |
|
||||||
|
| `LOG_FILE` | `/var/log/update_script.log` | Лог обновлений |
|
||||||
|
| `DEBUG` | `0` | `1` — подробный DEBUG в лог |
|
||||||
|
|
||||||
|
Требования на сервере:
|
||||||
|
|
||||||
|
- **root** (или sudo) для `cp`, `systemctl`, установки ipset;
|
||||||
|
- **git** и сетевой доступ к репозиторию (SSH-ключ или HTTPS);
|
||||||
|
- ветка в скрипте жёстко **`main`** — при другой ветке измените `git pull` в файле.
|
||||||
|
|
||||||
|
## Установка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo mkdir -p /opt/scripts
|
||||||
|
sudo cp update_ssh_monitor.sh /opt/scripts/update_ssh_monitor.sh
|
||||||
|
sudo chmod 750 /opt/scripts/update_ssh_monitor.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверка вручную:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo /opt/scripts/update_ssh_monitor.sh
|
||||||
|
sudo tail -20 /var/log/update_script.log
|
||||||
|
sudo systemctl status ssh-monitor
|
||||||
|
```
|
||||||
|
|
||||||
|
## Запуск по расписанию
|
||||||
|
|
||||||
|
### Cron (пример: каждый день в 04:00)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo crontab -e
|
||||||
|
```
|
||||||
|
|
||||||
|
Добавьте:
|
||||||
|
|
||||||
|
```
|
||||||
|
0 4 * * * /opt/scripts/update_ssh_monitor.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### systemd timer (альтернатива)
|
||||||
|
|
||||||
|
Создайте `/etc/systemd/system/ssh-monitor-update.service`:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Unit]
|
||||||
|
Description=Update ssh-monitor from git
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/opt/scripts/update_ssh_monitor.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
И `/etc/systemd/system/ssh-monitor-update.timer`:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Unit]
|
||||||
|
Description=Daily ssh-monitor update
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnCalendar=*-*-* 04:00:00
|
||||||
|
Persistent=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable --now ssh-monitor-update.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
## Откат после неудачного обновления
|
||||||
|
|
||||||
|
Резервные копии лежат рядом с установленным скриптом:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -la /usr/local/bin/ssh-monitor.backup.*
|
||||||
|
sudo cp /usr/local/bin/ssh-monitor.backup.YYYYMMDD_HHMMSS /usr/local/bin/ssh-monitor
|
||||||
|
sudo systemctl restart ssh-monitor.service
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ограничения
|
||||||
|
|
||||||
|
- Обновляется **только** файл **`ssh-monitor`**, не конфиг и не watchdog.
|
||||||
|
- При недоступном git **`git pull`** пишет предупреждение и продолжает с локальной копией.
|
||||||
|
- Placeholder **`REPO_URL`** в репозитории нужно заменить на боевой URL до использования в продакшене.
|
||||||
@@ -145,3 +145,8 @@ sudo journalctl -u ssh-monitor-watchdog.service -f
|
|||||||
- [ ] (По желанию) настроено сохранение правил после перезагрузки.
|
- [ ] (По желанию) настроено сохранение правил после перезагрузки.
|
||||||
|
|
||||||
Далее: установка самого **`ssh-monitor`**, unit **systemd** и проверка — в [README.md](../README.md).
|
Далее: установка самого **`ssh-monitor`**, unit **systemd** и проверка — в [README.md](../README.md).
|
||||||
|
|
||||||
|
Дополнительно:
|
||||||
|
|
||||||
|
- [notifications.ru.md](notifications.ru.md) — подпись **«🖥️ Сервер»** в уведомлениях, типы алертов, часовые пояса.
|
||||||
|
- [auto-update.ru.md](auto-update.ru.md) — автообновление скрипта через **`update_ssh_monitor.sh`**.
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
# Уведомления ssh-monitor
|
||||||
|
|
||||||
|
Документ описывает, **какие сообщения** отправляет монитор, **какие поля** в них есть и как настроить **подпись сервера** и **часовой пояс**.
|
||||||
|
|
||||||
|
См. также: [README.md](../README.md), пример конфига [ssh-monitor.conf.example](../ssh-monitor.conf.example).
|
||||||
|
|
||||||
|
## Подпись сервера (🖥️ Сервер)
|
||||||
|
|
||||||
|
Начиная с версии **1.1.3-server-label**, каждое уведомление, проходящее через **`notify_send()`**, получает строку **«🖥️ Сервер: …»** сразу **после первой строки** (заголовка алерта).
|
||||||
|
|
||||||
|
Формат значения:
|
||||||
|
|
||||||
|
| Условие | Пример |
|
||||||
|
|---------|--------|
|
||||||
|
| `SERVER_DISPLAY_NAME` пусто, IPv4 найден | `web01 (10.1.20.5)` |
|
||||||
|
| `SERVER_DISPLAY_NAME` пусто, IPv4 нет | `web01` |
|
||||||
|
| Задан `SERVER_DISPLAY_NAME="prod-db-01"` | `prod-db-01 (10.1.20.5)` |
|
||||||
|
|
||||||
|
IPv4 определяется через `ip -4 route get` (исходящий адрес) или `hostname -I`. Нужны утилиты **`hostname`** и **`ip`** — см. [install-prerequisites.ru.md](install-prerequisites.ru.md).
|
||||||
|
|
||||||
|
Если в тексте сообщения уже есть подстрока **`🖥️ Сервер:`**, вторая строка **не добавляется** (защита от дубля при ручной вставке).
|
||||||
|
|
||||||
|
### Пример: успешный SSH
|
||||||
|
|
||||||
|
```
|
||||||
|
✅ УСПЕШНОЕ SSH ПОДКЛЮЧЕНИЕ
|
||||||
|
🖥️ Сервер: myhost (10.1.20.5)
|
||||||
|
👤 Пользователь: alice
|
||||||
|
🌐 IP адрес: 10.1.20.1
|
||||||
|
🕐 Время: 25.05.2026 09:08:22
|
||||||
|
```
|
||||||
|
|
||||||
|
### Пример: sudo
|
||||||
|
|
||||||
|
```
|
||||||
|
⚠️ ИСПОЛЬЗОВАНИЕ SUDO
|
||||||
|
🖥️ Сервер: myhost (10.1.20.5)
|
||||||
|
👤 Пользователь: alice
|
||||||
|
🔑 От имени USER: root
|
||||||
|
💻 Команда: /bin/bash
|
||||||
|
📁 Директория: /home/alice
|
||||||
|
🕐 Время: 24.05.2026 16:54:49
|
||||||
|
```
|
||||||
|
|
||||||
|
## Настройка в `/etc/ssh-monitor.conf`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Пусто = hostname (+ IPv4 при возможности)
|
||||||
|
SERVER_DISPLAY_NAME=""
|
||||||
|
|
||||||
|
# Или фиксированное имя для этого хоста в общем чате:
|
||||||
|
# SERVER_DISPLAY_NAME="dc1-app-03"
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверка эффективной подписи:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ssh-monitor --check-config
|
||||||
|
```
|
||||||
|
|
||||||
|
В выводе будет строка `SERVER_DISPLAY_NAME=... (эффективно: ...)`.
|
||||||
|
|
||||||
|
## Типы уведомлений
|
||||||
|
|
||||||
|
| Событие | Заголовок (первая строка) | Доп. поля |
|
||||||
|
|---------|----------------------------|-----------|
|
||||||
|
| Успешный SSH | ✅ УСПЕШНОЕ SSH ПОДКЛЮЧЕНИЕ | пользователь, IP клиента, время |
|
||||||
|
| SSH под root | 🔑 SSH ВХОД ПОД ROOT | IP, время |
|
||||||
|
| Неудачная попытка SSH | ❌ НЕУДАЧНАЯ ПОПЫТКА SSH | пользователь, IP, счётчик попыток, время |
|
||||||
|
| Sudo | ⚠️ ИСПОЛЬЗОВАНИЕ SUDO | пользователь, USER=, команда, PWD, время |
|
||||||
|
| Бан IP | 🚫 IP ЗАБЛОКИРОВАН АВТОМАТИЧЕСКИ | IP, попытки, длительность, время |
|
||||||
|
| Порог без бана | ⚠️ ЛИМИТ НЕУДАЧНЫХ SSH (автобан отключён…) | IP, попытки, время |
|
||||||
|
| Брутфорс | 🧨 ВОЗМОЖНЫЙ МАССОВЫЙ БРУТФОРС SSH | IP, счётчик за окно, время |
|
||||||
|
| Host key / MITM | 🔐 ВНИМАНИЕ: изменение SSH host key… | фрагмент журнала, время |
|
||||||
|
| logind: новая сессия | 🖥️ НОВАЯ СЕССИЯ (systemd-logind) | пользователь, ID сессии, время |
|
||||||
|
| logind: сбой | ❌ СБОЙ (systemd-logind) | строка журнала, время |
|
||||||
|
| Ежедневный отчёт | 📊 ЕЖЕДНЕВНЫЙ ОТЧЕТ SSH МОНИТОРИНГА | статистика 24 ч, баны, топ IP, сессии |
|
||||||
|
| Heartbeat | ❤️ Heartbeat - скрипт мониторинга работает | время |
|
||||||
|
| Старт / стоп | ✅ СКРИПТ МОНИТОРИНГА ЗАПУЩЕН / ⚠️ ОСТАНОВЛЕН | версия, каналы, время |
|
||||||
|
|
||||||
|
Во всех перечисленных случаях **🖥️ Сервер** добавляется автоматически (кроме сообщений, где вы сами вставили эту строку).
|
||||||
|
|
||||||
|
## Время в сообщениях (🕐)
|
||||||
|
|
||||||
|
Строки **«🕐 Время»** формируются функцией **`notification_date()`**:
|
||||||
|
|
||||||
|
1. Если задан **`NOTIFY_TZ`** (IANA, например `Europe/Moscow`) — используется он.
|
||||||
|
2. Иначе, если задан **`DAILY_REPORT_TZ`** — он.
|
||||||
|
3. Иначе — зона процесса (`date` у systemd-сервиса; часто UTC, если в unit указано `Environment=TZ=UTC`).
|
||||||
|
|
||||||
|
Ежедневный отчёт по календарю и часу **`DAILY_REPORT_HOUR`** использует **`DAILY_REPORT_TZ`** (или зону процесса) — см. README.
|
||||||
|
|
||||||
|
## Каналы доставки
|
||||||
|
|
||||||
|
- **`NOTIFY_CHAIN`**: `telegram`, `email` — при каждом событии попытка **во все** каналы списка.
|
||||||
|
- **`BACKUP_WEBHOOK_URL`**: только если **все** каналы цепочки не доставили сообщение.
|
||||||
|
|
||||||
|
Режим **`--dry-run`**: уведомления не отправляются; в stderr печатается текст **уже с подписью сервера**.
|
||||||
+18
-2
@@ -8,6 +8,7 @@ Bash script that monitors **SSH**, **SUDO**, and **`systemd-logind`** events (lo
|
|||||||
|------|---------|
|
|------|---------|
|
||||||
| **`ssh-monitor`** | Main loop: parses logs (**journalctl**: sshd, sudo, security events, **systemd-logind**), notification chain (Telegram, SMTP via **python3**, backup webhook); with **`ENABLE_IP_BAN=1`** tracks failed attempts, bans via **ipset**, reconciles with **iptables**/**ip6tables**, daily report, heartbeat, optional **Prometheus** textfile and JSON health. Modes: **`--check-config`**, **`--dry-run`**. |
|
| **`ssh-monitor`** | Main loop: parses logs (**journalctl**: sshd, sudo, security events, **systemd-logind**), notification chain (Telegram, SMTP via **python3**, backup webhook); with **`ENABLE_IP_BAN=1`** tracks failed attempts, bans via **ipset**, reconciles with **iptables**/**ip6tables**, daily report, heartbeat, optional **Prometheus** textfile and JSON health. Modes: **`--check-config`**, **`--dry-run`**. |
|
||||||
| **`ssh-monitor-watchdog`** | Standalone script (usually via **systemd timer**): checks that the monitor service is active and the heartbeat file is fresh; on failure runs **`systemctl restart`** and sends Telegram or **`BACKUP_WEBHOOK_URL`**. Reads the same `TELEGRAM_*` / webhook from `/etc/ssh-monitor.conf`. |
|
| **`ssh-monitor-watchdog`** | Standalone script (usually via **systemd timer**): checks that the monitor service is active and the heartbeat file is fresh; on failure runs **`systemctl restart`** and sends Telegram or **`BACKUP_WEBHOOK_URL`**. Reads the same `TELEGRAM_*` / webhook from `/etc/ssh-monitor.conf`. |
|
||||||
|
| **`update_ssh_monitor.sh`** | Optional auto-update: `git pull` / `clone`, SHA256 compare with `/usr/local/bin/ssh-monitor`, restart `ssh-monitor.service`. See [docs/auto-update.ru.md](docs/auto-update.ru.md) (Russian). |
|
||||||
|
|
||||||
**System packages and dependencies** (install on the server before running): step-by-step in [docs/install-prerequisites.ru.md](docs/install-prerequisites.ru.md) (Russian).
|
**System packages and dependencies** (install on the server before running): step-by-step in [docs/install-prerequisites.ru.md](docs/install-prerequisites.ru.md) (Russian).
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@ Supported parameters:
|
|||||||
- `BACKUP_WEBHOOK_URL` — fallback JSON `{"text":"..."}` (e.g. Slack Incoming Webhook) if **all** **`NOTIFY_CHAIN`** channels fail; **does not** replace the need for at least one primary channel (see step 3)
|
- `BACKUP_WEBHOOK_URL` — fallback JSON `{"text":"..."}` (e.g. Slack Incoming Webhook) if **all** **`NOTIFY_CHAIN`** channels fail; **does not** replace the need for at least one primary channel (see step 3)
|
||||||
- `LOG_FILE`, `LAST_HEARTBEAT_FILE`, `LAST_REPORT_FILE`, `LAST_SSH_CHECK_FILE`, `LAST_SUDO_CHECK_FILE`, `LAST_SECURITY_EVENTS_FILE`, `LAST_LOGIND_CHECK_FILE`, `BAN_LIST_FILE`
|
- `LOG_FILE`, `LAST_HEARTBEAT_FILE`, `LAST_REPORT_FILE`, `LAST_SSH_CHECK_FILE`, `LAST_SUDO_CHECK_FILE`, `LAST_SECURITY_EVENTS_FILE`, `LAST_LOGIND_CHECK_FILE`, `BAN_LIST_FILE`
|
||||||
- `ENABLE_LOGIND_MONITOR`, `LOGIND_NOTIFY_NEW`, `LOGIND_NOTIFY_REMOVED`, `LOGIND_NOTIFY_FAILED`, `LOGIND_SKIP_REMOTE` — see **«systemd-logind monitoring»** below
|
- `ENABLE_LOGIND_MONITOR`, `LOGIND_NOTIFY_NEW`, `LOGIND_NOTIFY_REMOVED`, `LOGIND_NOTIFY_FAILED`, `LOGIND_SKIP_REMOTE` — see **«systemd-logind monitoring»** below
|
||||||
- `DAILY_REPORT_HOUR` (0..23), `DAILY_REPORT_TZ` (optional), `NOTIFY_TZ` (optional), `SSH_ACCEPT_NOTIFY_DEDUP_SEC`, `DAILY_REPORT_TOP_IPS`
|
- `DAILY_REPORT_HOUR` (0..23), `DAILY_REPORT_TZ` (optional), `NOTIFY_TZ` (optional), `SERVER_DISPLAY_NAME` (optional), `SSH_ACCEPT_NOTIFY_DEDUP_SEC`, `DAILY_REPORT_TOP_IPS`
|
||||||
- `BRUTE_WINDOW_SEC`, `BRUTE_MIN_FAILS`, `BRUTE_NOTIFY_COOLDOWN_SEC`
|
- `BRUTE_WINDOW_SEC`, `BRUTE_MIN_FAILS`, `BRUTE_NOTIFY_COOLDOWN_SEC`
|
||||||
- `PROMETHEUS_TEXTFILE_DIR` — directory for `ssh_monitor.prom` (node_exporter textfile collector)
|
- `PROMETHEUS_TEXTFILE_DIR` — directory for `ssh_monitor.prom` (node_exporter textfile collector)
|
||||||
- `HEALTHCHECK_STATUS_FILE` — path to JSON with last loop iteration marker
|
- `HEALTHCHECK_STATUS_FILE` — path to JSON with last loop iteration marker
|
||||||
@@ -67,6 +68,7 @@ If `/etc/ssh-monitor.conf` is missing or some values are empty, script defaults
|
|||||||
- `DAILY_REPORT_HOUR` — hour (0..23) after which at most one daily report is sent in the **current calendar day** (in the zone below).
|
- `DAILY_REPORT_HOUR` — hour (0..23) after which at most one daily report is sent in the **current calendar day** (in the zone below).
|
||||||
- `DAILY_REPORT_TZ` — optional **IANA** zone (`Europe/Moscow`, …). If **empty**, report uses the monitor process zone (usually matches `timedatectl` / `/etc/localtime`). If the unit sets `TZ=UTC`, without `DAILY_REPORT_TZ` the report uses **UTC** — set an explicit zone in config.
|
- `DAILY_REPORT_TZ` — optional **IANA** zone (`Europe/Moscow`, …). If **empty**, report uses the monitor process zone (usually matches `timedatectl` / `/etc/localtime`). If the unit sets `TZ=UTC`, without `DAILY_REPORT_TZ` the report uses **UTC** — set an explicit zone in config.
|
||||||
- `NOTIFY_TZ` — **IANA** zone for **«🕐 Время»** lines in Telegram/email. **Empty** — use **`DAILY_REPORT_TZ`** if set, else process zone. Common mismatch: **`ssh-monitor.service`** has **`Environment=TZ=UTC`** — set **`NOTIFY_TZ`** or **`DAILY_REPORT_TZ`** to your locale, e.g. `Asia/Vladivostok`.
|
- `NOTIFY_TZ` — **IANA** zone for **«🕐 Время»** lines in Telegram/email. **Empty** — use **`DAILY_REPORT_TZ`** if set, else process zone. Common mismatch: **`ssh-monitor.service`** has **`Environment=TZ=UTC`** — set **`NOTIFY_TZ`** or **`DAILY_REPORT_TZ`** to your locale, e.g. `Asia/Vladivostok`.
|
||||||
|
- `SERVER_DISPLAY_NAME` — label in **«🖥️ Server»** line on every alert. **Empty** — `hostname` plus primary IPv4 when available. Set a fixed name when many hosts share one Telegram chat. Details: [docs/notifications.ru.md](docs/notifications.ru.md) (Russian).
|
||||||
- `SSH_ACCEPT_NOTIFY_DEDUP_SEC` — at most one Telegram per successful SSH (**Accepted**) per **user + IP** pair within **N** seconds (default **5**). Removes duplicates when journal has two lines with **different client ports** for one login. **`0`** disables this cross-iteration dedup (in-pass dedup by `user|ip|port` remains).
|
- `SSH_ACCEPT_NOTIFY_DEDUP_SEC` — at most one Telegram per successful SSH (**Accepted**) per **user + IP** pair within **N** seconds (default **5**). Removes duplicates when journal has two lines with **different client ports** for one login. **`0`** disables this cross-iteration dedup (in-pass dedup by `user|ip|port` remains).
|
||||||
- `DAILY_REPORT_TOP_IPS` — how many IPs in the failed-attempts top for 24 hours.
|
- `DAILY_REPORT_TOP_IPS` — how many IPs in the failed-attempts top for 24 hours.
|
||||||
- `BRUTE_WINDOW_SEC` — window (seconds) for “mass” brute-force evaluation via `journalctl`.
|
- `BRUTE_WINDOW_SEC` — window (seconds) for “mass” brute-force evaluation via `journalctl`.
|
||||||
@@ -109,6 +111,20 @@ If **`NOTIFY_CHAIN` is empty**, the script exits with **`Не настроен
|
|||||||
|
|
||||||
Hosts **without journald** and **`/var/log/auth.log`** caveats: section 4 in [docs/install-prerequisites.ru.md](docs/install-prerequisites.ru.md).
|
Hosts **without journald** and **`/var/log/auth.log`** caveats: section 4 in [docs/install-prerequisites.ru.md](docs/install-prerequisites.ru.md).
|
||||||
|
|
||||||
|
### Server identification in alerts
|
||||||
|
|
||||||
|
**All** notifications sent via **`notify_send()`** get a **«🖥️ Сервер: …»** line (Russian label in messages) right after the title: `hostname (IPv4)` or hostname only. Implemented centrally in **`message_ensure_server_line()`**; no need to add the line in each alert builder.
|
||||||
|
|
||||||
|
- Requires **`hostname`** and **`ip`** (see prerequisites doc).
|
||||||
|
- Optional **`SERVER_DISPLAY_NAME`** in config for a fixed label.
|
||||||
|
- Message types and examples: [docs/notifications.ru.md](docs/notifications.ru.md) (Russian).
|
||||||
|
|
||||||
|
### Auto-update
|
||||||
|
|
||||||
|
**`update_ssh_monitor.sh`** can run on a schedule to `git pull`, compare SHA256 with `/usr/local/bin/ssh-monitor`, copy on change, and restart **`ssh-monitor.service`**. Does not modify `/etc/ssh-monitor.conf`.
|
||||||
|
|
||||||
|
Setup guide: [docs/auto-update.ru.md](docs/auto-update.ru.md) (Russian).
|
||||||
|
|
||||||
## Run modes
|
## Run modes
|
||||||
|
|
||||||
Normal run (root):
|
Normal run (root):
|
||||||
@@ -137,7 +153,7 @@ bash -n ./ssh-monitor
|
|||||||
|
|
||||||
## Release tarball
|
## Release tarball
|
||||||
|
|
||||||
Version is set in the script as **`SSH_MONITOR_VERSION`** (as of last doc update — **1.1.2-ipset**; see the **`ssh-monitor`** file for the current value). Build from the git tree:
|
Version is set in the script as **`SSH_MONITOR_VERSION`** (as of last doc update — **1.1.3-server-label**; see the **`ssh-monitor`** file for the current value). Build from the git tree:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make dist
|
make dist
|
||||||
|
|||||||
+54
-27
@@ -7,7 +7,7 @@ IFS=$'\n\t'
|
|||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
CONFIG_FILE="/etc/ssh-monitor.conf"
|
CONFIG_FILE="/etc/ssh-monitor.conf"
|
||||||
SSH_MONITOR_VERSION="1.1.2-ipset"
|
SSH_MONITOR_VERSION="1.1.3-server-label"
|
||||||
|
|
||||||
TELEGRAM_BOT_TOKEN=""
|
TELEGRAM_BOT_TOKEN=""
|
||||||
TELEGRAM_CHAT_ID=""
|
TELEGRAM_CHAT_ID=""
|
||||||
@@ -38,6 +38,8 @@ DAILY_REPORT_TOP_IPS=5
|
|||||||
DAILY_REPORT_TZ=""
|
DAILY_REPORT_TZ=""
|
||||||
# IANA-зона для строк «🕐 Время» в Telegram/email. Пусто = тогда DAILY_REPORT_TZ, если она задана, иначе зона процесса (часто UTC у systemd — см. README).
|
# IANA-зона для строк «🕐 Время» в Telegram/email. Пусто = тогда DAILY_REPORT_TZ, если она задана, иначе зона процесса (часто UTC у systemd — см. README).
|
||||||
NOTIFY_TZ=""
|
NOTIFY_TZ=""
|
||||||
|
# Подпись сервера в уведомлениях: пусто = hostname (+ IPv4); иначе фиксированное имя (удобно при одинаковых hostname).
|
||||||
|
SERVER_DISPLAY_NAME=""
|
||||||
# Не слать повтор «Accepted» для той же пары user+IP чаще, чем раз в N секунд (разные port в журнале — один вход).
|
# Не слать повтор «Accepted» для той же пары user+IP чаще, чем раз в N секунд (разные port в журнале — один вход).
|
||||||
SSH_ACCEPT_NOTIFY_DEDUP_SEC=5
|
SSH_ACCEPT_NOTIFY_DEDUP_SEC=5
|
||||||
|
|
||||||
@@ -162,6 +164,7 @@ load_config() {
|
|||||||
local cfg_daily_report_top_ips="${DAILY_REPORT_TOP_IPS}"
|
local cfg_daily_report_top_ips="${DAILY_REPORT_TOP_IPS}"
|
||||||
local cfg_daily_report_tz="${DAILY_REPORT_TZ}"
|
local cfg_daily_report_tz="${DAILY_REPORT_TZ}"
|
||||||
local cfg_notify_tz="${NOTIFY_TZ:-}"
|
local cfg_notify_tz="${NOTIFY_TZ:-}"
|
||||||
|
local cfg_server_display_name="${SERVER_DISPLAY_NAME:-}"
|
||||||
local cfg_ssh_accept_notify_dedup_sec="${SSH_ACCEPT_NOTIFY_DEDUP_SEC:-5}"
|
local cfg_ssh_accept_notify_dedup_sec="${SSH_ACCEPT_NOTIFY_DEDUP_SEC:-5}"
|
||||||
local cfg_last_ssh_check_file="${LAST_SSH_CHECK_FILE}"
|
local cfg_last_ssh_check_file="${LAST_SSH_CHECK_FILE}"
|
||||||
local cfg_last_sudo_check_file="${LAST_SUDO_CHECK_FILE}"
|
local cfg_last_sudo_check_file="${LAST_SUDO_CHECK_FILE}"
|
||||||
@@ -231,6 +234,7 @@ load_config() {
|
|||||||
DAILY_REPORT_TOP_IPS="${DAILY_REPORT_TOP_IPS:-$cfg_daily_report_top_ips}"
|
DAILY_REPORT_TOP_IPS="${DAILY_REPORT_TOP_IPS:-$cfg_daily_report_top_ips}"
|
||||||
DAILY_REPORT_TZ="$(trim_value "${DAILY_REPORT_TZ:-$cfg_daily_report_tz}")"
|
DAILY_REPORT_TZ="$(trim_value "${DAILY_REPORT_TZ:-$cfg_daily_report_tz}")"
|
||||||
NOTIFY_TZ="$(trim_value "${NOTIFY_TZ:-$cfg_notify_tz}")"
|
NOTIFY_TZ="$(trim_value "${NOTIFY_TZ:-$cfg_notify_tz}")"
|
||||||
|
SERVER_DISPLAY_NAME="$(trim_value "${SERVER_DISPLAY_NAME:-$cfg_server_display_name}")"
|
||||||
SSH_ACCEPT_NOTIFY_DEDUP_SEC="$(trim_value "${SSH_ACCEPT_NOTIFY_DEDUP_SEC:-$cfg_ssh_accept_notify_dedup_sec}")"
|
SSH_ACCEPT_NOTIFY_DEDUP_SEC="$(trim_value "${SSH_ACCEPT_NOTIFY_DEDUP_SEC:-$cfg_ssh_accept_notify_dedup_sec}")"
|
||||||
if ! [[ "${SSH_ACCEPT_NOTIFY_DEDUP_SEC:-5}" =~ ^[0-9]+$ ]]; then
|
if ! [[ "${SSH_ACCEPT_NOTIFY_DEDUP_SEC:-5}" =~ ^[0-9]+$ ]]; then
|
||||||
SSH_ACCEPT_NOTIFY_DEDUP_SEC=5
|
SSH_ACCEPT_NOTIFY_DEDUP_SEC=5
|
||||||
@@ -477,6 +481,53 @@ write_log() {
|
|||||||
echo "${timestamp} - ${message}"
|
echo "${timestamp} - ${message}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Подпись сервера в текстах уведомлений
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Для Telegram и отчётов: «hostname (IPv4)» или SERVER_DISPLAY_NAME (+ IPv4); без IPv4 — только имя.
|
||||||
|
server_display_name_with_ip() {
|
||||||
|
local hn ip
|
||||||
|
if [ -n "${SERVER_DISPLAY_NAME:-}" ]; then
|
||||||
|
hn="${SERVER_DISPLAY_NAME}"
|
||||||
|
else
|
||||||
|
hn=$(hostname 2>/dev/null | tr -d '\r\n')
|
||||||
|
[ -z "$hn" ] && hn="(unknown)"
|
||||||
|
fi
|
||||||
|
ip=""
|
||||||
|
if command -v ip >/dev/null 2>&1; then
|
||||||
|
ip=$(ip -4 route get 1.1.1.1 2>/dev/null | awk '/\bsrc\b/ {for (i = 1; i < NF; i++) if ($i == "src") { print $(i + 1); exit }}')
|
||||||
|
[ -z "$ip" ] && ip=$(ip -4 route get 8.8.8.8 2>/dev/null | awk '/\bsrc\b/ {for (i = 1; i < NF; i++) if ($i == "src") { print $(i + 1); exit }}')
|
||||||
|
fi
|
||||||
|
if [ -z "$ip" ] && command -v hostname >/dev/null 2>&1; then
|
||||||
|
if hostname -I >/dev/null 2>&1; then
|
||||||
|
ip=$(hostname -I 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | grep -Ev '^(127\.|0\.0\.0\.0)$' | head -n1)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ -n "$ip" ]; then
|
||||||
|
printf '%s (%s)' "$hn" "$ip"
|
||||||
|
else
|
||||||
|
printf '%s' "$hn"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Вставляет «🖥️ Сервер: …» сразу после первой строки (заголовка), если строки ещё нет.
|
||||||
|
message_ensure_server_line() {
|
||||||
|
local msg="$1"
|
||||||
|
if [[ "$msg" == *'🖥️ Сервер:'* ]]; then
|
||||||
|
printf '%s' "$msg"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local first rest
|
||||||
|
first="${msg%%$'\n'*}"
|
||||||
|
if [ "$first" = "$msg" ]; then
|
||||||
|
printf '%s\n🖥️ Сервер: %s' "$first" "$(server_display_name_with_ip)"
|
||||||
|
else
|
||||||
|
rest="${msg#*$'\n'}"
|
||||||
|
printf '%s\n🖥️ Сервер: %s\n%s' "$first" "$(server_display_name_with_ip)" "$rest"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# УВЕДОМЛЕНИЯ: telegram / email — по NOTIFY_CHAIN во все перечисленные каналы; BACKUP_WEBHOOK только если все неуспешны.
|
# УВЕДОМЛЕНИЯ: telegram / email — по NOTIFY_CHAIN во все перечисленные каналы; BACKUP_WEBHOOK только если все неуспешны.
|
||||||
# ============================================
|
# ============================================
|
||||||
@@ -603,6 +654,7 @@ PY
|
|||||||
|
|
||||||
notify_send() {
|
notify_send() {
|
||||||
local message="$1"
|
local message="$1"
|
||||||
|
message="$(message_ensure_server_line "$message")"
|
||||||
if [ "${DRY_RUN:-0}" = "1" ]; then
|
if [ "${DRY_RUN:-0}" = "1" ]; then
|
||||||
printf '[DRY-RUN notify] %s\n' "$(printf '%s' "$message" | tr '\n' ' ')" >&2
|
printf '[DRY-RUN notify] %s\n' "$(printf '%s' "$message" | tr '\n' ' ')" >&2
|
||||||
return 0
|
return 0
|
||||||
@@ -1501,28 +1553,6 @@ monitor_logind() {
|
|||||||
echo "$current_time" >"$LAST_LOGIND_CHECK_FILE"
|
echo "$current_time" >"$LAST_LOGIND_CHECK_FILE"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Для Telegram и отчётов: «hostname (IPv4)»; без доступного IPv4 — только hostname.
|
|
||||||
server_display_name_with_ip() {
|
|
||||||
local hn ip
|
|
||||||
hn=$(hostname 2>/dev/null | tr -d '\r\n')
|
|
||||||
[ -z "$hn" ] && hn="(unknown)"
|
|
||||||
ip=""
|
|
||||||
if command -v ip >/dev/null 2>&1; then
|
|
||||||
ip=$(ip -4 route get 1.1.1.1 2>/dev/null | awk '/\bsrc\b/ {for (i = 1; i < NF; i++) if ($i == "src") { print $(i + 1); exit }}')
|
|
||||||
[ -z "$ip" ] && ip=$(ip -4 route get 8.8.8.8 2>/dev/null | awk '/\bsrc\b/ {for (i = 1; i < NF; i++) if ($i == "src") { print $(i + 1); exit }}')
|
|
||||||
fi
|
|
||||||
if [ -z "$ip" ] && command -v hostname >/dev/null 2>&1; then
|
|
||||||
if hostname -I >/dev/null 2>&1; then
|
|
||||||
ip=$(hostname -I 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | grep -Ev '^(127\.|0\.0\.0\.0)$' | head -n1)
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
if [ -n "$ip" ]; then
|
|
||||||
printf '%s (%s)' "$hn" "$ip"
|
|
||||||
else
|
|
||||||
printf '%s' "$hn"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# Ежедневный отчёт
|
# Ежедневный отчёт
|
||||||
# ============================================
|
# ============================================
|
||||||
@@ -1606,7 +1636,6 @@ send_daily_report() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local message=" 📊 ЕЖЕДНЕВНЫЙ ОТЧЕТ SSH МОНИТОРИНГА "$'\n'
|
local message=" 📊 ЕЖЕДНЕВНЫЙ ОТЧЕТ SSH МОНИТОРИНГА "$'\n'
|
||||||
message="${message}🖥️ Сервер: $(server_display_name_with_ip)"$'\n'
|
|
||||||
message="${message}🕐 Время отчета: ${current_time}"$'\n'
|
message="${message}🕐 Время отчета: ${current_time}"$'\n'
|
||||||
message="${message}"$'\n'
|
message="${message}"$'\n'
|
||||||
message="${message} 📈 СТАТИСТИКА ЗА ПОСЛЕДНИЕ 24 ЧАСА: "$'\n'
|
message="${message} 📈 СТАТИСТИКА ЗА ПОСЛЕДНИЕ 24 ЧАСА: "$'\n'
|
||||||
@@ -1686,7 +1715,6 @@ send_heartbeat() {
|
|||||||
timestamp_human=$(notification_date '+%d.%m.%Y %H:%M:%S')
|
timestamp_human=$(notification_date '+%d.%m.%Y %H:%M:%S')
|
||||||
|
|
||||||
local message=" ❤️ Heartbeat - скрипт мониторинга работает "$'\n'
|
local message=" ❤️ Heartbeat - скрипт мониторинга работает "$'\n'
|
||||||
message="${message}🖥️ Сервер: $(server_display_name_with_ip)"$'\n'
|
|
||||||
message="${message}🕐 Время: ${timestamp_human}"
|
message="${message}🕐 Время: ${timestamp_human}"
|
||||||
|
|
||||||
notify_send "$message"
|
notify_send "$message"
|
||||||
@@ -1745,7 +1773,6 @@ send_startup_notification() {
|
|||||||
|
|
||||||
local message=" ✅ СКРИПТ МОНИТОРИНГА ЗАПУЩЕН "$'\n'
|
local message=" ✅ СКРИПТ МОНИТОРИНГА ЗАПУЩЕН "$'\n'
|
||||||
message="${message}📌 Версия: ${SSH_MONITOR_VERSION:-?}"$'\n'
|
message="${message}📌 Версия: ${SSH_MONITOR_VERSION:-?}"$'\n'
|
||||||
message="${message}🖥️ Сервер: $(server_display_name_with_ip)"$'\n'
|
|
||||||
message="${message}🕐 Время запуска: ${timestamp}"$'\n'
|
message="${message}🕐 Время запуска: ${timestamp}"$'\n'
|
||||||
message="${message}📊 Мониторинг SSH, SUDO и systemd-logind активирован"$'\n'
|
message="${message}📊 Мониторинг SSH, SUDO и systemd-logind активирован"$'\n'
|
||||||
if [ "${ENABLE_IP_BAN:-1}" = "1" ]; then
|
if [ "${ENABLE_IP_BAN:-1}" = "1" ]; then
|
||||||
@@ -1768,7 +1795,6 @@ send_shutdown_notification() {
|
|||||||
local timestamp
|
local timestamp
|
||||||
timestamp=$(notification_date '+%d.%m.%Y %H:%M:%S')
|
timestamp=$(notification_date '+%d.%m.%Y %H:%M:%S')
|
||||||
local message=" ⚠️ СКРИПТ МОНИТОРИНГА ОСТАНОВЛЕН "$'\n'
|
local message=" ⚠️ СКРИПТ МОНИТОРИНГА ОСТАНОВЛЕН "$'\n'
|
||||||
message="${message}🖥️ Сервер: $(server_display_name_with_ip)"$'\n'
|
|
||||||
message="${message}🕐 Время остановки: ${timestamp}"
|
message="${message}🕐 Время остановки: ${timestamp}"
|
||||||
|
|
||||||
notify_send "$message"
|
notify_send "$message"
|
||||||
@@ -1826,6 +1852,7 @@ run_check_config() {
|
|||||||
printf 'DAILY_REPORT_HOUR=%s\n' "$DAILY_REPORT_HOUR"
|
printf 'DAILY_REPORT_HOUR=%s\n' "$DAILY_REPORT_HOUR"
|
||||||
printf 'DAILY_REPORT_TZ=%s (пусто = зона процесса/date)\n' "${DAILY_REPORT_TZ:-}"
|
printf 'DAILY_REPORT_TZ=%s (пусто = зона процесса/date)\n' "${DAILY_REPORT_TZ:-}"
|
||||||
printf 'NOTIFY_TZ=%s (пусто = DAILY_REPORT_TZ или зона процесса; см. README)\n' "${NOTIFY_TZ:-}"
|
printf 'NOTIFY_TZ=%s (пусто = DAILY_REPORT_TZ или зона процесса; см. README)\n' "${NOTIFY_TZ:-}"
|
||||||
|
printf 'SERVER_DISPLAY_NAME=%s (пусто = hostname; эффективно: %s)\n' "${SERVER_DISPLAY_NAME:-}" "$(server_display_name_with_ip)"
|
||||||
printf 'SSH_ACCEPT_NOTIFY_DEDUP_SEC=%s\n' "${SSH_ACCEPT_NOTIFY_DEDUP_SEC:-5}"
|
printf 'SSH_ACCEPT_NOTIFY_DEDUP_SEC=%s\n' "${SSH_ACCEPT_NOTIFY_DEDUP_SEC:-5}"
|
||||||
printf 'DAILY_REPORT_TOP_IPS=%s\n' "$DAILY_REPORT_TOP_IPS"
|
printf 'DAILY_REPORT_TOP_IPS=%s\n' "$DAILY_REPORT_TOP_IPS"
|
||||||
printf 'BRUTE_WINDOW_SEC=%s BRUTE_MIN_FAILS=%s\n' "$BRUTE_WINDOW_SEC" "$BRUTE_MIN_FAILS"
|
printf 'BRUTE_WINDOW_SEC=%s BRUTE_MIN_FAILS=%s\n' "$BRUTE_WINDOW_SEC" "$BRUTE_MIN_FAILS"
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ DAILY_REPORT_TZ=""
|
|||||||
DAILY_REPORT_TOP_IPS="5"
|
DAILY_REPORT_TOP_IPS="5"
|
||||||
# IANA-зона для «🕐 Время» в Telegram/email. Пусто = как DAILY_REPORT_TZ, иначе зона процесса (часто UTC в systemd — см. README).
|
# IANA-зона для «🕐 Время» в Telegram/email. Пусто = как DAILY_REPORT_TZ, иначе зона процесса (часто UTC в systemd — см. README).
|
||||||
NOTIFY_TZ=""
|
NOTIFY_TZ=""
|
||||||
|
# Подпись «🖥️ Сервер» в уведомлениях: пусто = hostname (+ IPv4); иначе фиксированное имя (например prod-web-01).
|
||||||
|
# SERVER_DISPLAY_NAME="prod-web-01"
|
||||||
# Не чаще 1 успешного SSH (Accepted) в Telegram на пару user+IP за N секунд (разные port в журнале). 0 = только дедуп внутри одного прохода.
|
# Не чаще 1 успешного SSH (Accepted) в Telegram на пару user+IP за N секунд (разные port в журнале). 0 = только дедуп внутри одного прохода.
|
||||||
SSH_ACCEPT_NOTIFY_DEDUP_SEC="5"
|
SSH_ACCEPT_NOTIFY_DEDUP_SEC="5"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user