Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bea39a6ef5 | |||
| b9c5aa3224 | |||
| 7ce9f1d526 | |||
| 21ef703866 | |||
| e48a001d37 | |||
| c9455ed917 | |||
| f7e688d9b0 | |||
| 4a32eca15c | |||
| 56d4b0002e | |||
| 803817cdbb | |||
| 94125757b0 | |||
| 673145a199 | |||
| 1a8352d65c | |||
| d43afe03c6 | |||
| ce6a3d4620 | |||
| 25f85d00fb | |||
| 6a4db2d7ad | |||
| 6552b0225f | |||
| 5e6b0dedb7 | |||
| 0e0333e7e4 | |||
| eee84e5eb7 | |||
| 921fc9c795 | |||
| 57935e292e | |||
| 4975a6d842 | |||
| 80d2508d52 | |||
| 415da821de | |||
| 02fa70722c | |||
| da5d2638d7 | |||
| f52dda23ad | |||
| 17737cbf09 | |||
| a1bfa6a003 | |||
| bdb10e82d8 | |||
| 0f2b26b4b4 | |||
| febe359248 | |||
| ced61c417a | |||
| 3cfdc7b12c |
@@ -8,7 +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`, синхронизация **`sac-client.sh`** рядом с монитором, перезапуск `ssh-monitor.service`. См. [docs/auto-update.ru.md](docs/auto-update.ru.md). |
|
| **`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).
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ Bash-скрипт мониторинга **SSH**, **SUDO** и событий **`
|
|||||||
|
|
||||||
### Автообновление скрипта
|
### Автообновление скрипта
|
||||||
|
|
||||||
Скрипт **`update_ssh_monitor.sh`** по расписанию (cron или systemd timer) выполняет `git pull` / `clone`, сравнивает SHA256 с `/usr/local/bin/ssh-monitor`, **всегда** подтягивает **`/usr/local/bin/sac-client.sh`** и при изменении любого из файлов перезапускает **`ssh-monitor.service`**. Конфиг **`/etc/ssh-monitor.conf`** не трогается.
|
Скрипт **`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).
|
Пошаговая настройка: [docs/auto-update.ru.md](docs/auto-update.ru.md).
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ bash -n ./ssh-monitor
|
|||||||
|
|
||||||
## Релизный архив
|
## Релизный архив
|
||||||
|
|
||||||
Версия задаётся в скрипте переменной **`SSH_MONITOR_VERSION`** (на момент последнего обновления документации — **1.2.2-SAC**; актуальное значение смотрите в файле **`ssh-monitor`**). Сборка tarball из текущего git-дерева:
|
Версия задаётся в скрипте переменной **`SSH_MONITOR_VERSION`** (на момент последнего обновления документации — **1.1.3-server-label**; актуальное значение смотрите в файле **`ssh-monitor`**). Сборка tarball из текущего git-дерева:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make dist
|
make dist
|
||||||
@@ -165,24 +165,12 @@ make dist
|
|||||||
|
|
||||||
## Установка скрипта, конфигурации и systemd
|
## Установка скрипта, конфигурации и systemd
|
||||||
|
|
||||||
### Автоматическая установка (рекомендуется)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo ./first_deploy.sh
|
|
||||||
# или: sudo ./update_ssh_monitor.sh --deploy
|
|
||||||
```
|
|
||||||
|
|
||||||
См. [docs/auto-update.ru.md](docs/auto-update.ru.md) — клон из git, копирование в `/usr/local/bin/`, `sac-client.sh`, пример `/etc/ssh-monitor.conf`, unit systemd.
|
|
||||||
|
|
||||||
### Ручная установка
|
|
||||||
|
|
||||||
1. Скопируйте скрипт в постоянное место:
|
1. Скопируйте скрипт в постоянное место:
|
||||||
- `sudo install -m 750 ./ssh-monitor /usr/local/bin/ssh-monitor`
|
- `sudo install -m 750 ./ssh-monitor /usr/local/bin/ssh-monitor`
|
||||||
- `sudo install -m 644 ./sac-client.sh /usr/local/bin/sac-client.sh`
|
|
||||||
2. Убедитесь, что конфиг создан (см. раздел **«Конфигурация»** выше):
|
2. Убедитесь, что конфиг создан (см. раздел **«Конфигурация»** выше):
|
||||||
- `sudo cp ./ssh-monitor.conf.example /etc/ssh-monitor.conf`
|
- `sudo cp ./ssh-monitor.conf.example /etc/ssh-monitor.conf`
|
||||||
- `sudo chmod 600 /etc/ssh-monitor.conf`
|
- `sudo chmod 600 /etc/ssh-monitor.conf`
|
||||||
3. Создайте unit-файл `/etc/systemd/system/ssh-monitor.service` (или `sudo cp ssh-monitor.service.example ...`):
|
3. Создайте unit-файл `/etc/systemd/system/ssh-monitor.service`:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[Unit]
|
[Unit]
|
||||||
|
|||||||
+9
-55
@@ -13,25 +13,20 @@
|
|||||||
- `$UPDATE_DIR/ssh-monitor/src/branch/main/ssh-monitor` (структура GitLab);
|
- `$UPDATE_DIR/ssh-monitor/src/branch/main/ssh-monitor` (структура GitLab);
|
||||||
- `$UPDATE_DIR/ssh-monitor/ssh-monitor` (плоский репозиторий, как в этом проекте).
|
- `$UPDATE_DIR/ssh-monitor/ssh-monitor` (плоский репозиторий, как в этом проекте).
|
||||||
5. Сравнивает **SHA256** удалённого файла с **`LOCAL_SCRIPT_PATH`** (по умолчанию `/usr/local/bin/ssh-monitor`).
|
5. Сравнивает **SHA256** удалённого файла с **`LOCAL_SCRIPT_PATH`** (по умолчанию `/usr/local/bin/ssh-monitor`).
|
||||||
6. **Всегда** (независимо от шага 5) синхронизирует **`sac-client.sh`** в каталог рядом с монитором (по умолчанию **`/usr/local/bin/sac-client.sh`**) — нужен для **`UseSAC`** и **`--check-sac`**.
|
6. При отличии:
|
||||||
7. При отличии checksum **`ssh-monitor`** и/или **`sac-client.sh`**:
|
- резервная копия `ssh-monitor.backup.YYYYMMDD_HHMMSS`;
|
||||||
- резервная копия `ssh-monitor.backup.YYYYMMDD_HHMMSS` (только для основного скрипта);
|
- `cp` + `chmod +x`;
|
||||||
- `cp` + `chmod +x` для `ssh-monitor`, `chmod 644` для `sac-client.sh`;
|
|
||||||
- **`systemctl restart ssh-monitor.service`** (если есть systemctl).
|
- **`systemctl restart ssh-monitor.service`** (если есть systemctl).
|
||||||
8. Пишет лог в **`LOG_FILE`** (по умолчанию `/var/log/update_script.log`).
|
7. Пишет лог в **`LOG_FILE`** (по умолчанию `/var/log/update_script.log`).
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
A[cron/timer] --> B[update_ssh_monitor.sh]
|
A[cron/timer] --> B[update_ssh_monitor.sh]
|
||||||
B --> C[git pull/clone]
|
B --> C[git pull/clone]
|
||||||
C --> D[sync sac-client.sh]
|
C --> D{SHA256 различается?}
|
||||||
C --> E{SHA256 ssh-monitor?}
|
D -->|да| E[backup + cp]
|
||||||
D --> F{что-то обновилось?}
|
E --> F[restart ssh-monitor.service]
|
||||||
E -->|да| G[backup + cp ssh-monitor]
|
D -->|нет| G[без изменений]
|
||||||
E -->|нет| F
|
|
||||||
G --> F
|
|
||||||
F -->|да| H[restart ssh-monitor.service]
|
|
||||||
F -->|нет| I[без изменений]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Настройка перед первым запуском
|
## Настройка перед первым запуском
|
||||||
@@ -52,29 +47,7 @@ flowchart LR
|
|||||||
- **git** и сетевой доступ к репозиторию (SSH-ключ или HTTPS);
|
- **git** и сетевой доступ к репозиторию (SSH-ключ или HTTPS);
|
||||||
- ветка в скрипте жёстко **`main`** — при другой ветке измените `git pull` в файле.
|
- ветка в скрипте жёстко **`main`** — при другой ветке измените `git pull` в файле.
|
||||||
|
|
||||||
## Первичная установка (--deploy)
|
## Установка
|
||||||
|
|
||||||
Один сценарий для нового сервера (клон git, `ssh-monitor`, `sac-client.sh`, пример конфига, systemd unit):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /path/to/ssh-monitor # или curl/clone репозитория
|
|
||||||
sudo ./first_deploy.sh
|
|
||||||
# то же самое:
|
|
||||||
sudo ./update_ssh_monitor.sh --deploy
|
|
||||||
```
|
|
||||||
|
|
||||||
Скрипт выводит в консоль, **куда что скопировано**, и список переменных для **`/etc/ssh-monitor.conf`**. Существующий конфиг **не перезаписывается**.
|
|
||||||
|
|
||||||
После установки отредактируйте конфиг и проверьте:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo nano /etc/ssh-monitor.conf
|
|
||||||
sudo /usr/local/bin/ssh-monitor --check-config
|
|
||||||
sudo /usr/local/bin/ssh-monitor --check-sac
|
|
||||||
sudo systemctl restart ssh-monitor.service
|
|
||||||
```
|
|
||||||
|
|
||||||
## Установка updater (для последующих обновлений)
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo mkdir -p /opt/scripts
|
sudo mkdir -p /opt/scripts
|
||||||
@@ -146,25 +119,6 @@ sudo cp /usr/local/bin/ssh-monitor.backup.YYYYMMDD_HHMMSS /usr/local/bin/ssh-mon
|
|||||||
sudo systemctl restart ssh-monitor.service
|
sudo systemctl restart ssh-monitor.service
|
||||||
```
|
```
|
||||||
|
|
||||||
## Лог и диагностика «мгновенного» завершения
|
|
||||||
|
|
||||||
Все шаги пишутся в **`/var/log/update_script.log`**. При обычном запуске те же строки дублируются в stdout (кроме **`--quiet`**).
|
|
||||||
|
|
||||||
В конце каждого прогона — блок **«Итог обновления»**: статус `ssh-monitor` / `sac-client` / git / перезапуск и **причина**, если файлы не копировались (checksum совпал, git pull failed, скрипт не найден в клоне).
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo /opt/scripts/update_ssh_monitor.sh
|
|
||||||
sudo tail -40 /var/log/update_script.log
|
|
||||||
```
|
|
||||||
|
|
||||||
**Важно:** после `git pull` в клоне скрипт **сам обновляет** `/opt/scripts/update_ssh_monitor.sh` из репозитория и один раз перезапускает себя (если вызывали именно этот путь). Если в логе старые строки вроде `Checksums match` без блока «Итог обновления» — на сервере ещё старая копия updater; один раз:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo cp /opt/scripts/update/ssh-monitor/update_ssh_monitor.sh /opt/scripts/update_ssh_monitor.sh
|
|
||||||
sudo chmod 750 /opt/scripts/update_ssh_monitor.sh
|
|
||||||
sudo /opt/scripts/update_ssh_monitor.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## Ограничения
|
## Ограничения
|
||||||
|
|
||||||
- Обновляется **только** файл **`ssh-monitor`**, не конфиг и не watchdog.
|
- Обновляется **только** файл **`ssh-monitor`**, не конфиг и не watchdog.
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Первичная установка ssh-monitor — обёртка над update_ssh_monitor.sh --deploy
|
|
||||||
set -euo pipefail
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
exec "$SCRIPT_DIR/update_ssh_monitor.sh" --deploy "$@"
|
|
||||||
+1
-1
@@ -153,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.2.2-SAC**; 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
|
||||||
|
|||||||
-382
@@ -1,382 +0,0 @@
|
|||||||
# SAC client for ssh-monitor (source from ssh-monitor)
|
|
||||||
# shellcheck shell=bash
|
|
||||||
|
|
||||||
sac_normalize_mode() {
|
|
||||||
printf '%s' "${1:-off}" | tr '[:upper:]' '[:lower:]'
|
|
||||||
}
|
|
||||||
|
|
||||||
sac_is_configured() {
|
|
||||||
[ -n "${SAC_URL:-}" ] && [ -n "${SAC_API_KEY:-}" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
sac_base_url() {
|
|
||||||
local url="${SAC_URL%/}"
|
|
||||||
case "$url" in
|
|
||||||
*/api/v1/events) url="${url%/api/v1/events}" ;;
|
|
||||||
esac
|
|
||||||
printf '%s' "${url%/}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# URL ingest (POST): всегда .../api/v1/events, даже если в конфиге указан только хост
|
|
||||||
sac_ingest_url() {
|
|
||||||
local base
|
|
||||||
base="$(sac_base_url)"
|
|
||||||
[ -n "$base" ] || return 1
|
|
||||||
printf '%s/api/v1/events' "$base"
|
|
||||||
}
|
|
||||||
|
|
||||||
sac_agent_instance_id() {
|
|
||||||
local id_file="${SAC_AGENT_ID_FILE:-/var/lib/ssh-monitor/agent_instance_id}"
|
|
||||||
local dir
|
|
||||||
dir="$(dirname "$id_file")"
|
|
||||||
if [ ! -d "$dir" ]; then
|
|
||||||
mkdir -p "$dir" 2>/dev/null || id_file="/tmp/ssh-monitor-agent_instance_id"
|
|
||||||
fi
|
|
||||||
if [ -f "$id_file" ]; then
|
|
||||||
cat "$id_file"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
local new_id
|
|
||||||
new_id="$(python3 -c 'import uuid; print(uuid.uuid4())')"
|
|
||||||
printf '%s\n' "$new_id" >"$id_file" 2>/dev/null || true
|
|
||||||
printf '%s' "$new_id"
|
|
||||||
}
|
|
||||||
|
|
||||||
sac_check_health() {
|
|
||||||
local base code
|
|
||||||
base="$(sac_base_url)"
|
|
||||||
[ -n "$base" ] || return 1
|
|
||||||
if [ "${DRY_RUN:-0}" = "1" ]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
code="$(curl -sS -o /dev/null -w '%{http_code}' \
|
|
||||||
--connect-timeout "${SAC_TIMEOUT_SEC:-12}" \
|
|
||||||
--max-time "${SAC_TIMEOUT_SEC:-12}" \
|
|
||||||
"${base}/health" || echo 000)"
|
|
||||||
[ "$code" = "200" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
sac_spool_write() {
|
|
||||||
local event_id="$1"
|
|
||||||
local body="$2"
|
|
||||||
local dir="${SAC_SPOOL_DIR:-/var/lib/ssh-monitor/sac-spool}"
|
|
||||||
mkdir -p "$dir" 2>/dev/null || return 1
|
|
||||||
printf '%s' "$body" >"${dir}/${event_id}.json"
|
|
||||||
}
|
|
||||||
|
|
||||||
sac_spool_remove() {
|
|
||||||
local event_id="$1"
|
|
||||||
local dir="${SAC_SPOOL_DIR:-/var/lib/ssh-monitor/sac-spool}"
|
|
||||||
rm -f "${dir}/${event_id}.json"
|
|
||||||
}
|
|
||||||
|
|
||||||
sac_fail_count_file() {
|
|
||||||
printf '%s' "${SAC_FAIL_COUNT_FILE:-/var/lib/ssh-monitor/sac-fail.count}"
|
|
||||||
}
|
|
||||||
|
|
||||||
sac_read_fail_count() {
|
|
||||||
local f
|
|
||||||
f="$(sac_fail_count_file)"
|
|
||||||
if [ -f "$f" ]; then
|
|
||||||
cat "$f" 2>/dev/null | tr -dc '0-9'
|
|
||||||
else
|
|
||||||
printf '0'
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
sac_write_fail_count() {
|
|
||||||
local n="$1"
|
|
||||||
local f
|
|
||||||
f="$(sac_fail_count_file)"
|
|
||||||
mkdir -p "$(dirname "$f")" 2>/dev/null || true
|
|
||||||
printf '%s' "$n" >"$f"
|
|
||||||
}
|
|
||||||
|
|
||||||
sac_reset_fail_count() {
|
|
||||||
sac_write_fail_count 0
|
|
||||||
}
|
|
||||||
|
|
||||||
sac_increment_fail_count() {
|
|
||||||
local n max
|
|
||||||
max="${SAC_FALLBACK_FAILURES:-5}"
|
|
||||||
n="$(sac_read_fail_count)"
|
|
||||||
n=$((n + 1))
|
|
||||||
sac_write_fail_count "$n"
|
|
||||||
[ "$n" -ge "$max" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
# В fallback: после N подряд ошибок POST не дергать SAC, пока /health снова не OK.
|
|
||||||
sac_should_attempt_send() {
|
|
||||||
local mode
|
|
||||||
mode="$(sac_normalize_mode "${UseSAC:-off}")"
|
|
||||||
[ "$mode" = "fallback" ] || return 0
|
|
||||||
local max n
|
|
||||||
max="${SAC_FALLBACK_FAILURES:-5}"
|
|
||||||
n="$(sac_read_fail_count)"
|
|
||||||
[ "$n" -lt "$max" ] && return 0
|
|
||||||
if sac_check_health; then
|
|
||||||
sac_reset_fail_count
|
|
||||||
write_log "SAC: /health восстановлен, снова отправляем в SAC (fallback)"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
write_log "WARN: SAC fallback: пропуск POST ($n>=$max сбоев подряд), только локальные каналы"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# POST готового JSON (ingest / повтор из spool). Возврат 0 при HTTP 201, 409 или 202 (legacy).
|
|
||||||
sac_post_payload() {
|
|
||||||
local payload="$1"
|
|
||||||
local event_id http_code tmp resp event_type
|
|
||||||
|
|
||||||
if ! sac_is_configured; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
if [ "${DRY_RUN:-0}" = "1" ]; then
|
|
||||||
printf '[DRY-RUN SAC POST] %s\n' "$(printf '%s' "$payload" | head -c 120)" >&2
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! sac_should_attempt_send; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
event_id="$(printf '%s' "$payload" | python3 -c 'import json,sys; print(json.load(sys.stdin)["event_id"])')" || return 1
|
|
||||||
event_type="$(printf '%s' "$payload" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("type","?"))')" || event_type="?"
|
|
||||||
|
|
||||||
tmp="$(mktemp)"
|
|
||||||
resp="$(mktemp)"
|
|
||||||
printf '%s' "$payload" >"$tmp"
|
|
||||||
http_code="$(curl -sS -o "$resp" -w '%{http_code}' \
|
|
||||||
-X POST "$(sac_ingest_url)" \
|
|
||||||
-H "Authorization: Bearer ${SAC_API_KEY}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-H "Idempotency-Key: ${event_id}" \
|
|
||||||
--connect-timeout "${SAC_TIMEOUT_SEC:-12}" \
|
|
||||||
--max-time "${SAC_TIMEOUT_SEC:-12}" \
|
|
||||||
-d @"$tmp" 2>/dev/null || echo 000)"
|
|
||||||
rm -f "$tmp"
|
|
||||||
|
|
||||||
if [ "$http_code" = "201" ] || [ "$http_code" = "409" ] || [ "$http_code" = "202" ]; then
|
|
||||||
sac_spool_remove "$event_id" 2>/dev/null || true
|
|
||||||
sac_reset_fail_count
|
|
||||||
write_log "SAC: принято event_id=$event_id type=$event_type"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
write_log "WARN: SAC POST HTTP $http_code: $(tr '\n' ' ' <"$resp" | head -c 200)"
|
|
||||||
sac_spool_write "$event_id" "$payload"
|
|
||||||
sac_increment_fail_count || write_log "WARN: SAC fallback: достигнут порог SAC_FALLBACK_FAILURES"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Повторная отправка файлов из SAC_SPOOL_DIR (до max_files за вызов).
|
|
||||||
sac_flush_spool() {
|
|
||||||
local mode max_files="${1:-20}"
|
|
||||||
mode="$(sac_normalize_mode "${UseSAC:-off}")"
|
|
||||||
case "$mode" in
|
|
||||||
off) return 0 ;;
|
|
||||||
esac
|
|
||||||
if ! sac_is_configured; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
local dir="${SAC_SPOOL_DIR:-/var/lib/ssh-monitor/sac-spool}"
|
|
||||||
[ -d "$dir" ] || return 0
|
|
||||||
|
|
||||||
local f count=0
|
|
||||||
shopt -s nullglob
|
|
||||||
for f in "$dir"/*.json; do
|
|
||||||
[ -f "$f" ] || continue
|
|
||||||
count=$((count + 1))
|
|
||||||
[ "$count" -gt "$max_files" ] && break
|
|
||||||
local payload
|
|
||||||
payload="$(cat "$f")"
|
|
||||||
sac_post_payload "$payload" || true
|
|
||||||
done
|
|
||||||
shopt -u nullglob
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# send_sac_event type severity title summary [details_json]
|
|
||||||
sac_send_event() {
|
|
||||||
local event_type="$1"
|
|
||||||
local severity="$2"
|
|
||||||
local title="$3"
|
|
||||||
local summary="$4"
|
|
||||||
local details_json="${5:-}"
|
|
||||||
|
|
||||||
if ! sac_is_configured; then
|
|
||||||
write_log "WARN: SAC не настроен (SAC_URL / SAC_API_KEY)"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "${DRY_RUN:-0}" = "1" ]; then
|
|
||||||
printf '[DRY-RUN SAC] %s | %s | %s\n' "$event_type" "$severity" "$title" >&2
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! sac_should_attempt_send; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local payload http_code event_id
|
|
||||||
payload="$(SAC_EVENT_TYPE="$event_type" \
|
|
||||||
SAC_SEVERITY="$severity" \
|
|
||||||
SAC_TITLE="$title" \
|
|
||||||
SAC_SUMMARY="$summary" \
|
|
||||||
SAC_DETAILS_JSON="$details_json" \
|
|
||||||
SAC_AGENT_ID="$(sac_agent_instance_id)" \
|
|
||||||
SAC_PRODUCT_VERSION="${SSH_MONITOR_VERSION:-unknown}" \
|
|
||||||
python3 <<'PY'
|
|
||||||
import json, os, socket, uuid
|
|
||||||
from datetime import datetime, timezone
|
|
||||||
|
|
||||||
def details():
|
|
||||||
raw = os.environ.get("SAC_DETAILS_JSON", "").strip()
|
|
||||||
if not raw:
|
|
||||||
return None
|
|
||||||
return json.loads(raw)
|
|
||||||
|
|
||||||
host = socket.gethostname()
|
|
||||||
etype = os.environ["SAC_EVENT_TYPE"]
|
|
||||||
if etype.startswith(("ssh.", "auth.", "rdp.")):
|
|
||||||
category = "auth"
|
|
||||||
elif etype.startswith("privilege."):
|
|
||||||
category = "privilege"
|
|
||||||
elif etype.startswith("session."):
|
|
||||||
category = "session"
|
|
||||||
elif etype.startswith("report."):
|
|
||||||
category = "report"
|
|
||||||
elif etype.startswith("rdg."):
|
|
||||||
category = "network"
|
|
||||||
else:
|
|
||||||
category = "agent"
|
|
||||||
payload = {
|
|
||||||
"schema_version": "1.0",
|
|
||||||
"event_id": str(uuid.uuid4()),
|
|
||||||
"occurred_at": datetime.now(timezone.utc).astimezone().isoformat(timespec="seconds"),
|
|
||||||
"source": {
|
|
||||||
"product": "ssh-monitor",
|
|
||||||
"product_version": os.environ.get("SAC_PRODUCT_VERSION", "unknown"),
|
|
||||||
"agent_instance_id": os.environ["SAC_AGENT_ID"],
|
|
||||||
},
|
|
||||||
"host": {
|
|
||||||
"hostname": host,
|
|
||||||
"os_family": "linux",
|
|
||||||
},
|
|
||||||
"category": category,
|
|
||||||
"type": os.environ["SAC_EVENT_TYPE"],
|
|
||||||
"severity": os.environ["SAC_SEVERITY"],
|
|
||||||
"title": os.environ["SAC_TITLE"],
|
|
||||||
"summary": os.environ["SAC_SUMMARY"],
|
|
||||||
}
|
|
||||||
d = details()
|
|
||||||
if d is not None:
|
|
||||||
payload["details"] = d
|
|
||||||
print(json.dumps(payload, ensure_ascii=False))
|
|
||||||
PY
|
|
||||||
)" || return 1
|
|
||||||
|
|
||||||
sac_post_payload "$payload"
|
|
||||||
}
|
|
||||||
|
|
||||||
# JSON details из переменных SAC_D_* (опционально)
|
|
||||||
sac_build_details_json() {
|
|
||||||
python3 <<'PY'
|
|
||||||
import json, os
|
|
||||||
out = {}
|
|
||||||
for k, v in os.environ.items():
|
|
||||||
if k.startswith("SAC_D_") and v:
|
|
||||||
out[k[6:].lower()] = v
|
|
||||||
print(json.dumps(out) if out else "")
|
|
||||||
PY
|
|
||||||
}
|
|
||||||
|
|
||||||
sac_is_heartbeat_only_event() {
|
|
||||||
[ "${1:-}" = "agent.heartbeat" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
# notify_or_sac type severity title summary telegram_message [details_json]
|
|
||||||
notify_or_sac() {
|
|
||||||
local event_type="$1"
|
|
||||||
local severity="$2"
|
|
||||||
local title="$3"
|
|
||||||
local summary="$4"
|
|
||||||
local telegram_message="${5:-$summary}"
|
|
||||||
local details_json="${6:-}"
|
|
||||||
if [ -z "$details_json" ] && [ -n "${SAC_D_USER:-}${SAC_D_IP:-}${SAC_D_COMMAND:-}" ]; then
|
|
||||||
details_json="$(sac_build_details_json)"
|
|
||||||
fi
|
|
||||||
unset SAC_D_USER SAC_D_IP SAC_D_COMMAND SAC_D_ATTEMPT SAC_D_MAX SAC_D_RUN_AS SAC_D_PWD 2>/dev/null || true
|
|
||||||
local mode
|
|
||||||
mode="$(sac_normalize_mode "${UseSAC:-off}")"
|
|
||||||
|
|
||||||
# Периодический heartbeat — только SAC (браузер), без Telegram/email в любом режиме.
|
|
||||||
if sac_is_heartbeat_only_event "$event_type"; then
|
|
||||||
case "$mode" in
|
|
||||||
off)
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
exclusive|dual|fallback)
|
|
||||||
sac_send_event "$event_type" "$severity" "$title" "$summary" "$details_json"
|
|
||||||
return $?
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "$mode" in
|
|
||||||
off)
|
|
||||||
notify_send "$telegram_message"
|
|
||||||
;;
|
|
||||||
exclusive)
|
|
||||||
sac_send_event "$event_type" "$severity" "$title" "$summary" "$details_json"
|
|
||||||
;;
|
|
||||||
dual)
|
|
||||||
sac_send_event "$event_type" "$severity" "$title" "$summary" "$details_json" || true
|
|
||||||
NOTIFY_SKIP_SAC_MIRROR=1 notify_send "$telegram_message"
|
|
||||||
;;
|
|
||||||
fallback)
|
|
||||||
if sac_send_event "$event_type" "$severity" "$title" "$summary" "$details_json"; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
NOTIFY_SKIP_SAC_MIRROR=1 notify_send "$telegram_message"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
write_log "WARN: неизвестный UseSAC=$mode, только Telegram"
|
|
||||||
notify_send "$telegram_message"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
run_check_sac() {
|
|
||||||
printf 'Проверка SAC (ssh-monitor)\n'
|
|
||||||
printf 'UseSAC=%s\n' "${UseSAC:-off}"
|
|
||||||
case "$(sac_normalize_mode "${UseSAC:-off}")" in
|
|
||||||
exclusive) printf 'Режим exclusive: уведомления только в SAC (Telegram/email с агента не отправляются)\n' ;;
|
|
||||||
dual) printf 'Режим dual: SAC + локальные каналы NOTIFY_CHAIN\n' ;;
|
|
||||||
fallback) printf 'Режим fallback: SAC, при сбое — NOTIFY_CHAIN\n' ;;
|
|
||||||
esac
|
|
||||||
printf 'SAC_URL=%s\n' "${SAC_URL:-}"
|
|
||||||
if sac_is_configured; then
|
|
||||||
printf 'SAC ingest URL=%s\n' "$(sac_ingest_url)"
|
|
||||||
fi
|
|
||||||
if ! sac_is_configured; then
|
|
||||||
printf 'SAC: SAC_URL или SAC_API_KEY не заданы\n' >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
if sac_check_health; then
|
|
||||||
printf 'SAC health: OK\n'
|
|
||||||
else
|
|
||||||
printf 'SAC health: FAIL\n' >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
if sac_send_event "agent.test" "info" "SAC test" "ssh-monitor --check-sac"; then
|
|
||||||
printf 'SAC ingest agent.test: OK (ожидается HTTP 201)\n'
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
printf 'SAC ingest agent.test: FAIL\n' >&2
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Проверка готовности хоста к пилоту UseSAC=exclusive (фаза 2.1).
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
CONFIG="${CONFIG_FILE:-/etc/ssh-monitor.conf}"
|
|
||||||
MONITOR="${SSH_MONITOR_BIN:-/usr/local/bin/ssh-monitor}"
|
|
||||||
|
|
||||||
if [ -f "$CONFIG" ]; then
|
|
||||||
# shellcheck disable=SC1090
|
|
||||||
. "$CONFIG"
|
|
||||||
fi
|
|
||||||
|
|
||||||
fail=0
|
|
||||||
ok() { printf '[OK] %s\n' "$1"; }
|
|
||||||
bad() { printf '[FAIL] %s\n' "$1"; fail=1; }
|
|
||||||
|
|
||||||
mode="$(printf '%s' "${UseSAC:-off}" | tr '[:upper:]' '[:lower:]')"
|
|
||||||
[ "$mode" = "exclusive" ] || bad "UseSAC должен быть exclusive (сейчас: ${UseSAC:-off})"
|
|
||||||
[ -n "${SAC_URL:-}" ] && [ -n "${SAC_API_KEY:-}" ] || bad "Задайте SAC_URL и SAC_API_KEY"
|
|
||||||
[ -x "$MONITOR" ] || bad "Не найден $MONITOR"
|
|
||||||
[ -f "${MONITOR%/*}/sac-client.sh" ] || bad "Нет sac-client.sh рядом с ssh-monitor"
|
|
||||||
|
|
||||||
if "$MONITOR" --check-sac; then
|
|
||||||
ok "--check-sac"
|
|
||||||
else
|
|
||||||
bad "--check-sac"
|
|
||||||
fi
|
|
||||||
|
|
||||||
spool="${SAC_SPOOL_DIR:-/var/lib/ssh-monitor/sac-spool}"
|
|
||||||
pending=0
|
|
||||||
if [ -d "$spool" ]; then
|
|
||||||
pending=$(find "$spool" -maxdepth 1 -name '*.json' 2>/dev/null | wc -l)
|
|
||||||
fi
|
|
||||||
printf ' Spool %s: %s файл(ов)\n' "$spool" "$pending"
|
|
||||||
|
|
||||||
if [ "$fail" -eq 0 ]; then
|
|
||||||
printf '\nПилот exclusive: хост готов. Сгенерируйте SSH/sudo событие и проверьте только в SAC UI.\n'
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
printf '\nИсправьте ошибки выше.\n'
|
|
||||||
exit 1
|
|
||||||
+17
-159
@@ -7,19 +7,7 @@ IFS=$'\n\t'
|
|||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
CONFIG_FILE="/etc/ssh-monitor.conf"
|
CONFIG_FILE="/etc/ssh-monitor.conf"
|
||||||
SSH_MONITOR_VERSION="1.2.3-SAC"
|
SSH_MONITOR_VERSION="1.1.3-server-label"
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
if [ -f "$SCRIPT_DIR/sac-client.sh" ]; then
|
|
||||||
# shellcheck source=sac-client.sh
|
|
||||||
source "$SCRIPT_DIR/sac-client.sh"
|
|
||||||
else
|
|
||||||
sac_normalize_mode() { printf '%s' "${1:-off}" | tr '[:upper:]' '[:lower:]'; }
|
|
||||||
sac_is_configured() { return 1; }
|
|
||||||
sac_check_health() { return 1; }
|
|
||||||
notify_or_sac() { notify_send "${5:-$4}"; }
|
|
||||||
run_check_sac() { printf 'sac-client.sh не найден\n' >&2; return 1; }
|
|
||||||
fi
|
|
||||||
|
|
||||||
TELEGRAM_BOT_TOKEN=""
|
TELEGRAM_BOT_TOKEN=""
|
||||||
TELEGRAM_CHAT_ID=""
|
TELEGRAM_CHAT_ID=""
|
||||||
@@ -29,15 +17,6 @@ BACKUP_WEBHOOK_URL=""
|
|||||||
# Пусто = авто: только настроенные каналы, порядок telegram → email.
|
# Пусто = авто: только настроенные каналы, порядок telegram → email.
|
||||||
NOTIFY_ORDER=""
|
NOTIFY_ORDER=""
|
||||||
|
|
||||||
# Security Alert Center: off | exclusive | dual | fallback
|
|
||||||
UseSAC="off"
|
|
||||||
SAC_URL=""
|
|
||||||
SAC_API_KEY=""
|
|
||||||
SAC_SPOOL_DIR="/var/lib/ssh-monitor/sac-spool"
|
|
||||||
SAC_TIMEOUT_SEC="12"
|
|
||||||
SAC_SEND_HEARTBEAT="1"
|
|
||||||
SAC_FALLBACK_FAILURES="5"
|
|
||||||
|
|
||||||
# Почта (SMTP через python3). Нужны MAIL_SMTP_HOST, MAIL_FROM, MAIL_TO.
|
# Почта (SMTP через python3). Нужны MAIL_SMTP_HOST, MAIL_FROM, MAIL_TO.
|
||||||
MAIL_SMTP_HOST=""
|
MAIL_SMTP_HOST=""
|
||||||
MAIL_SMTP_PORT="587"
|
MAIL_SMTP_PORT="587"
|
||||||
@@ -213,11 +192,6 @@ load_config() {
|
|||||||
local cfg_whitelist_ips_csv="${default_whitelist_ips_csv}"
|
local cfg_whitelist_ips_csv="${default_whitelist_ips_csv}"
|
||||||
local cfg_whitelist_subnets_csv="${default_whitelist_subnets_csv}"
|
local cfg_whitelist_subnets_csv="${default_whitelist_subnets_csv}"
|
||||||
local cfg_notify_order="${NOTIFY_ORDER:-}"
|
local cfg_notify_order="${NOTIFY_ORDER:-}"
|
||||||
local cfg_use_sac="${UseSAC:-off}"
|
|
||||||
local cfg_sac_url="${SAC_URL:-}"
|
|
||||||
local cfg_sac_api_key="${SAC_API_KEY:-}"
|
|
||||||
local cfg_sac_spool_dir="${SAC_SPOOL_DIR:-/var/lib/ssh-monitor/sac-spool}"
|
|
||||||
local cfg_sac_timeout_sec="${SAC_TIMEOUT_SEC:-12}"
|
|
||||||
local cfg_mail_smtp_host="${MAIL_SMTP_HOST:-}"
|
local cfg_mail_smtp_host="${MAIL_SMTP_HOST:-}"
|
||||||
local cfg_mail_smtp_port="${MAIL_SMTP_PORT:-587}"
|
local cfg_mail_smtp_port="${MAIL_SMTP_PORT:-587}"
|
||||||
local cfg_mail_smtp_user="${MAIL_SMTP_USER:-}"
|
local cfg_mail_smtp_user="${MAIL_SMTP_USER:-}"
|
||||||
@@ -280,11 +254,6 @@ load_config() {
|
|||||||
csv_to_array "$whitelist_subnets_csv" WHITELIST_SUBNETS
|
csv_to_array "$whitelist_subnets_csv" WHITELIST_SUBNETS
|
||||||
|
|
||||||
NOTIFY_ORDER="$(trim_value "${NOTIFY_ORDER:-$cfg_notify_order}")"
|
NOTIFY_ORDER="$(trim_value "${NOTIFY_ORDER:-$cfg_notify_order}")"
|
||||||
UseSAC="$(trim_value "${UseSAC:-$cfg_use_sac}")"
|
|
||||||
SAC_URL="$(trim_value "${SAC_URL:-$cfg_sac_url}")"
|
|
||||||
SAC_API_KEY="$(trim_value "${SAC_API_KEY:-$cfg_sac_api_key}")"
|
|
||||||
SAC_SPOOL_DIR="$(trim_value "${SAC_SPOOL_DIR:-$cfg_sac_spool_dir}")"
|
|
||||||
SAC_TIMEOUT_SEC="$(trim_value "${SAC_TIMEOUT_SEC:-$cfg_sac_timeout_sec}")"
|
|
||||||
MAIL_SMTP_HOST="$(trim_value "${MAIL_SMTP_HOST:-$cfg_mail_smtp_host}")"
|
MAIL_SMTP_HOST="$(trim_value "${MAIL_SMTP_HOST:-$cfg_mail_smtp_host}")"
|
||||||
MAIL_SMTP_PORT="${MAIL_SMTP_PORT:-$cfg_mail_smtp_port}"
|
MAIL_SMTP_PORT="${MAIL_SMTP_PORT:-$cfg_mail_smtp_port}"
|
||||||
MAIL_SMTP_USER="$(trim_value "${MAIL_SMTP_USER:-$cfg_mail_smtp_user}")"
|
MAIL_SMTP_USER="$(trim_value "${MAIL_SMTP_USER:-$cfg_mail_smtp_user}")"
|
||||||
@@ -388,31 +357,8 @@ validate_config() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
local sac_mode
|
|
||||||
sac_mode="$(sac_normalize_mode "${UseSAC:-off}")"
|
|
||||||
case "$sac_mode" in
|
|
||||||
off) ;;
|
|
||||||
exclusive | dual | fallback)
|
|
||||||
if ! sac_is_configured; then
|
|
||||||
printf '%s\n' "UseSAC=${sac_mode}: задайте SAC_URL и SAC_API_KEY" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
printf '%s\n' "Неверный UseSAC=\"${UseSAC:-}\"" >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
refresh_notify_chain
|
refresh_notify_chain
|
||||||
if [ "$sac_mode" = "exclusive" ]; then
|
if [ "${#NOTIFY_CHAIN[@]}" -eq 0 ]; then
|
||||||
if [ "${CHECK_CONFIG_ONLY:-0}" != "1" ] && [ "${CHECK_SAC_ONLY:-0}" != "1" ] && [ "${DRY_RUN:-0}" != "1" ]; then
|
|
||||||
if ! sac_check_health; then
|
|
||||||
printf '%s\n' 'UseSAC=exclusive: SAC /health недоступен' >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
elif [ "${#NOTIFY_CHAIN[@]}" -eq 0 ]; then
|
|
||||||
printf '%s\n' 'Не настроен ни один канал отправки оповещений' >&2
|
printf '%s\n' 'Не настроен ни один канал отправки оповещений' >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -706,16 +652,6 @@ PY
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
sac_mirror_notify_message() {
|
|
||||||
local message="$1"
|
|
||||||
local mode
|
|
||||||
mode="$(sac_normalize_mode "${UseSAC:-off}")"
|
|
||||||
[ "$mode" = "off" ] && return 0
|
|
||||||
local summary
|
|
||||||
summary="$(printf '%s' "$message" | tr '\n' ' ' | head -c 400)"
|
|
||||||
sac_send_event "agent.notification" "info" "SSH Monitor" "$summary" "" || return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
notify_send() {
|
notify_send() {
|
||||||
local message="$1"
|
local message="$1"
|
||||||
message="$(message_ensure_server_line "$message")"
|
message="$(message_ensure_server_line "$message")"
|
||||||
@@ -724,28 +660,6 @@ notify_send() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local sac_mode
|
|
||||||
sac_mode="$(sac_normalize_mode "${UseSAC:-off}")"
|
|
||||||
if [ "${NOTIFY_SKIP_SAC_MIRROR:-0}" != "1" ]; then
|
|
||||||
case "$sac_mode" in
|
|
||||||
exclusive)
|
|
||||||
if sac_mirror_notify_message "$message"; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
write_log "WARN: UseSAC=exclusive: SAC не принял событие"
|
|
||||||
return 1
|
|
||||||
;;
|
|
||||||
dual)
|
|
||||||
sac_mirror_notify_message "$message" || true
|
|
||||||
;;
|
|
||||||
fallback)
|
|
||||||
if sac_mirror_notify_message "$message"; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
local any_ok=0
|
local any_ok=0
|
||||||
local ch
|
local ch
|
||||||
for ch in "${NOTIFY_CHAIN[@]}"; do
|
for ch in "${NOTIFY_CHAIN[@]}"; do
|
||||||
@@ -991,8 +905,7 @@ block_ip() {
|
|||||||
message="${message}👤 Попыток: ${attempts} (порог ${MAX_ATTEMPTS}; ENABLE_IP_BAN=0)"$'\n'
|
message="${message}👤 Попыток: ${attempts} (порог ${MAX_ATTEMPTS}; ENABLE_IP_BAN=0)"$'\n'
|
||||||
message="${message}ℹ️ ipset/firewall не меняются. При ENABLE_IP_BAN=1 бан был бы ~${ban_time_hours} ч."$'\n'
|
message="${message}ℹ️ ipset/firewall не меняются. При ENABLE_IP_BAN=1 бан был бы ~${ban_time_hours} ч."$'\n'
|
||||||
message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
||||||
SAC_D_IP="$ip" SAC_D_ATTEMPT="$attempts" SAC_D_MAX="$MAX_ATTEMPTS" \
|
notify_send "$message"
|
||||||
notify_or_sac "ssh.ip.bruteforce.threshold" "warning" "Лимит SSH без бана" "${ip}: ${attempts} попыток" "$message"
|
|
||||||
write_log "Порог неудачных SSH для $ip ($attempts попыток), автобан отключён (ENABLE_IP_BAN=0)"
|
write_log "Порог неудачных SSH для $ip ($attempts попыток), автобан отключён (ENABLE_IP_BAN=0)"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@@ -1016,8 +929,7 @@ block_ip() {
|
|||||||
message="${message}📊 Неудачных попыток: ${attempts}"$'\n'
|
message="${message}📊 Неудачных попыток: ${attempts}"$'\n'
|
||||||
message="${message}⏱️ Блокировка на: ${ban_time_hours} час(ов) (ipset timeout)"$'\n'
|
message="${message}⏱️ Блокировка на: ${ban_time_hours} час(ов) (ipset timeout)"$'\n'
|
||||||
message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
||||||
SAC_D_IP="$ip" SAC_D_ATTEMPT="$attempts" \
|
notify_send "$message"
|
||||||
notify_or_sac "ssh.ip.banned" "high" "IP заблокирован" "${ip} (${attempts} попыток)" "$message"
|
|
||||||
write_log "AUTO-BAN: IP $ip в ipset на $ban_time_hours ч. ($attempts попыток)"
|
write_log "AUTO-BAN: IP $ip в ipset на $ban_time_hours ч. ($attempts попыток)"
|
||||||
|
|
||||||
# Один IP — одна строка: после истечения ipset-таймаута is_ip_blocked=0, а в файле
|
# Один IP — одна строка: после истечения ipset-таймаута is_ip_blocked=0, а в файле
|
||||||
@@ -1283,15 +1195,13 @@ monitor_ssh() {
|
|||||||
message="${message}👤 Пользователь: ${user}"$'\n'
|
message="${message}👤 Пользователь: ${user}"$'\n'
|
||||||
message="${message}🌐 IP адрес: ${ip:-local}"$'\n'
|
message="${message}🌐 IP адрес: ${ip:-local}"$'\n'
|
||||||
message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
||||||
SAC_D_USER="$user" SAC_D_IP="${ip:-}" \
|
notify_send "$message"
|
||||||
notify_or_sac "ssh.login.success" "info" "Успешное SSH" "${user} с ${ip:-local}" "$message"
|
|
||||||
write_log "SSH успешный вход: $user с IP ${ip:-local}"
|
write_log "SSH успешный вход: $user с IP ${ip:-local}"
|
||||||
if [ "$user" = "root" ]; then
|
if [ "$user" = "root" ]; then
|
||||||
local rmsg=" 🔑 SSH ВХОД ПОД ROOT "$'\n'
|
local rmsg=" 🔑 SSH ВХОД ПОД ROOT "$'\n'
|
||||||
rmsg="${rmsg}🌐 IP: ${ip:-local}"$'\n'
|
rmsg="${rmsg}🌐 IP: ${ip:-local}"$'\n'
|
||||||
rmsg="${rmsg}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
rmsg="${rmsg}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
||||||
SAC_D_USER="root" SAC_D_IP="${ip:-}" \
|
notify_send "$rmsg"
|
||||||
notify_or_sac "ssh.login.success" "warning" "SSH вход под root" "root с ${ip:-local}" "$rmsg"
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
elif echo "$line" | grep -q "Failed password"; then
|
elif echo "$line" | grep -q "Failed password"; then
|
||||||
@@ -1320,8 +1230,7 @@ monitor_ssh() {
|
|||||||
message="${message}🌐 IP адрес: ${ip}"$'\n'
|
message="${message}🌐 IP адрес: ${ip}"$'\n'
|
||||||
message="${message}📊 Попытка ${current_attempts} из ${MAX_ATTEMPTS}"$'\n'
|
message="${message}📊 Попытка ${current_attempts} из ${MAX_ATTEMPTS}"$'\n'
|
||||||
message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
||||||
SAC_D_USER="$user" SAC_D_IP="$ip" SAC_D_ATTEMPT="$current_attempts" SAC_D_MAX="$MAX_ATTEMPTS" \
|
notify_send "$message"
|
||||||
notify_or_sac "ssh.login.failed" "warning" "Неудачная SSH" "${user} с ${ip} (${current_attempts}/${MAX_ATTEMPTS})" "$message"
|
|
||||||
fi
|
fi
|
||||||
write_log "SSH неудачная попытка: $user с IP $ip (попытка $current_attempts из $MAX_ATTEMPTS)"
|
write_log "SSH неудачная попытка: $user с IP $ip (попытка $current_attempts из $MAX_ATTEMPTS)"
|
||||||
fi
|
fi
|
||||||
@@ -1436,9 +1345,7 @@ monitor_sudo() {
|
|||||||
message="${message}💻 Команда: ${sudo_cmd}"$'\n'
|
message="${message}💻 Команда: ${sudo_cmd}"$'\n'
|
||||||
message="${message}📁 Директория: ${pwd:-unknown}"$'\n'
|
message="${message}📁 Директория: ${pwd:-unknown}"$'\n'
|
||||||
message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
||||||
local sac_summary="${user}: ${sudo_cmd}"
|
notify_send "$message"
|
||||||
SAC_D_USER="$user" SAC_D_COMMAND="$sudo_cmd" SAC_D_RUN_AS="${run_as:-}" SAC_D_PWD="${pwd:-}" \
|
|
||||||
notify_or_sac "privilege.sudo.command" "warning" "Использование sudo" "$sac_summary" "$message"
|
|
||||||
write_log "SUDO: $user выполнил $sudo_cmd${run_as:+ (USER=$run_as)}"
|
write_log "SUDO: $user выполнил $sudo_cmd${run_as:+ (USER=$run_as)}"
|
||||||
fi
|
fi
|
||||||
done <<<"$sudo_logs"
|
done <<<"$sudo_logs"
|
||||||
@@ -1479,7 +1386,7 @@ monitor_security_events() {
|
|||||||
local m=" 🔐 ВНИМАНИЕ: изменение SSH host key / MITM? "$'\n'
|
local m=" 🔐 ВНИМАНИЕ: изменение SSH host key / MITM? "$'\n'
|
||||||
m="${m}$(printf '%s\n' "$hk")"
|
m="${m}$(printf '%s\n' "$hk")"
|
||||||
m="${m}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
m="${m}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
||||||
notify_or_sac "agent.notification" "high" "SSH host key changed" "MITM?" "$m"
|
notify_send "$m"
|
||||||
write_log "SECURITY: обнаружено сообщение об изменении host key"
|
write_log "SECURITY: обнаружено сообщение об изменении host key"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -1500,8 +1407,7 @@ monitor_security_events() {
|
|||||||
bm="${bm}🌐 IP: ${ip}"$'\n'
|
bm="${bm}🌐 IP: ${ip}"$'\n'
|
||||||
bm="${bm}📊 Неудачных попыток за ~${BRUTE_WINDOW_SEC}s: ${count}"$'\n'
|
bm="${bm}📊 Неудачных попыток за ~${BRUTE_WINDOW_SEC}s: ${count}"$'\n'
|
||||||
bm="${bm}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
bm="${bm}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
||||||
SAC_D_IP="$ip" SAC_D_ATTEMPT="$count" \
|
notify_send "$bm"
|
||||||
notify_or_sac "ssh.bruteforce.mass" "high" "Массовый брутфорс" "${ip}: ${count} за ${BRUTE_WINDOW_SEC}s" "$bm"
|
|
||||||
write_log "SECURITY: массовый брутфорс? IP=$ip fails=${count} за окно ${BRUTE_WINDOW_SEC}s"
|
write_log "SECURITY: массовый брутфорс? IP=$ip fails=${count} за окно ${BRUTE_WINDOW_SEC}s"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -1612,8 +1518,7 @@ monitor_logind() {
|
|||||||
msg="${msg}👤 Пользователь: ${user:-неизвестно}"$'\n'
|
msg="${msg}👤 Пользователь: ${user:-неизвестно}"$'\n'
|
||||||
msg="${msg}🆔 Сессия: ${sid:-?}"$'\n'
|
msg="${msg}🆔 Сессия: ${sid:-?}"$'\n'
|
||||||
msg="${msg}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
msg="${msg}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
||||||
SAC_D_USER="${user:-}" \
|
notify_send "$msg"
|
||||||
notify_or_sac "session.logind.new" "info" "Новая сессия logind" "${user:-?} sid=${sid:-?}" "$msg"
|
|
||||||
write_log "LOGIND: новая сессия: ${line}"
|
write_log "LOGIND: новая сессия: ${line}"
|
||||||
else
|
else
|
||||||
write_log "LOGIND: новая сессия (разбор user/sid не удался): ${line}"
|
write_log "LOGIND: новая сессия (разбор user/sid не удался): ${line}"
|
||||||
@@ -1630,7 +1535,7 @@ monitor_logind() {
|
|||||||
local msg_r=" 🚪 СЕССИЯ ЗАКРЫТА (systemd-logind) "$'\n'
|
local msg_r=" 🚪 СЕССИЯ ЗАКРЫТА (systemd-logind) "$'\n'
|
||||||
msg_r="${msg_r}🆔 Сессия: ${sess_r:-?}"$'\n'
|
msg_r="${msg_r}🆔 Сессия: ${sess_r:-?}"$'\n'
|
||||||
msg_r="${msg_r}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
msg_r="${msg_r}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
||||||
notify_or_sac "session.logind.removed" "info" "Сессия закрыта" "sid=${sess_r:-?}" "$msg_r"
|
notify_send "$msg_r"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
elif [[ "$low" == *"failed"* ]]; then
|
elif [[ "$low" == *"failed"* ]]; then
|
||||||
@@ -1638,7 +1543,7 @@ monitor_logind() {
|
|||||||
local msg_f=" ❌ СБОЙ (systemd-logind) "$'\n'
|
local msg_f=" ❌ СБОЙ (systemd-logind) "$'\n'
|
||||||
msg_f="${msg_f}📝 ${line}"$'\n'
|
msg_f="${msg_f}📝 ${line}"$'\n'
|
||||||
msg_f="${msg_f}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
msg_f="${msg_f}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
||||||
notify_or_sac "session.logind.failed" "warning" "Сбой logind" "logind failed" "$msg_f"
|
notify_send "$msg_f"
|
||||||
write_log "LOGIND: сбой: $line"
|
write_log "LOGIND: сбой: $line"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -1754,45 +1659,7 @@ send_daily_report() {
|
|||||||
message="${message} Нет активных пользователей"$'\n'
|
message="${message} Нет активных пользователей"$'\n'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local sac_summary sac_details_json
|
notify_send "$message"
|
||||||
sac_summary="SSH 24ч: успех ${successful_logins:-0}, неудач ${failed_logins:-0}, sudo ${sudo_commands:-0}"
|
|
||||||
sac_details_json="$(
|
|
||||||
SAC_REPORT_BODY="$message" \
|
|
||||||
SAC_OK="${successful_logins:-0}" \
|
|
||||||
SAC_FAIL="${failed_logins:-0}" \
|
|
||||||
SAC_SUDO="${sudo_commands:-0}" \
|
|
||||||
SAC_BANS="${active_bans:-0}" \
|
|
||||||
SAC_ACTIVE="${active_count:-0}" \
|
|
||||||
python3 <<'PY'
|
|
||||||
import html
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
|
|
||||||
body = os.environ.get("SAC_REPORT_BODY", "")
|
|
||||||
stats = {
|
|
||||||
"successful_ssh": int(os.environ.get("SAC_OK", "0") or 0),
|
|
||||||
"failed_ssh": int(os.environ.get("SAC_FAIL", "0") or 0),
|
|
||||||
"sudo_commands": int(os.environ.get("SAC_SUDO", "0") or 0),
|
|
||||||
"active_bans": int(os.environ.get("SAC_BANS", "0") or 0),
|
|
||||||
"active_sessions": int(os.environ.get("SAC_ACTIVE", "0") or 0),
|
|
||||||
}
|
|
||||||
escaped = html.escape(body)
|
|
||||||
report_html = '<div class="agent-report">' + escaped.replace("\n", "<br>\n") + "</div>"
|
|
||||||
print(
|
|
||||||
json.dumps(
|
|
||||||
{
|
|
||||||
"stats": stats,
|
|
||||||
"report_body": body,
|
|
||||||
"report_format": "plain",
|
|
||||||
"report_html": report_html,
|
|
||||||
},
|
|
||||||
ensure_ascii=False,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
PY
|
|
||||||
)" || sac_details_json=""
|
|
||||||
|
|
||||||
notify_or_sac "report.daily.ssh" "info" "Ежедневный отчёт SSH" "$sac_summary" "$message" "$sac_details_json"
|
|
||||||
write_log "Ежедневный отчет отправлен"
|
write_log "Ежедневный отчет отправлен"
|
||||||
|
|
||||||
echo "$(report_date '+%F')" >"$LAST_REPORT_FILE"
|
echo "$(report_date '+%F')" >"$LAST_REPORT_FILE"
|
||||||
@@ -1850,7 +1717,7 @@ send_heartbeat() {
|
|||||||
local message=" ❤️ Heartbeat - скрипт мониторинга работает "$'\n'
|
local message=" ❤️ Heartbeat - скрипт мониторинга работает "$'\n'
|
||||||
message="${message}🕐 Время: ${timestamp_human}"
|
message="${message}🕐 Время: ${timestamp_human}"
|
||||||
|
|
||||||
notify_or_sac "agent.heartbeat" "info" "Heartbeat" "ssh-monitor alive" "$message"
|
notify_send "$message"
|
||||||
echo "$timestamp" >"$LAST_HEARTBEAT_FILE"
|
echo "$timestamp" >"$LAST_HEARTBEAT_FILE"
|
||||||
write_log "Heartbeat отправлен"
|
write_log "Heartbeat отправлен"
|
||||||
}
|
}
|
||||||
@@ -1917,7 +1784,7 @@ send_startup_notification() {
|
|||||||
message="${message}❤️ Heartbeat будет отправляться каждые 12 часов"$'\n'
|
message="${message}❤️ Heartbeat будет отправляться каждые 12 часов"$'\n'
|
||||||
message="${message}📢 Каналы уведомлений (все по списку): $(notify_chain_human)"
|
message="${message}📢 Каналы уведомлений (все по списку): $(notify_chain_human)"
|
||||||
|
|
||||||
notify_or_sac "agent.lifecycle" "info" "Мониторинг запущен" "ssh-monitor started" "$message"
|
notify_send "$message"
|
||||||
write_log "Скрипт мониторинга запущен (белый список: $whitelist_count записей)"
|
write_log "Скрипт мониторинга запущен (белый список: $whitelist_count записей)"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1930,7 +1797,7 @@ send_shutdown_notification() {
|
|||||||
local message=" ⚠️ СКРИПТ МОНИТОРИНГА ОСТАНОВЛЕН "$'\n'
|
local message=" ⚠️ СКРИПТ МОНИТОРИНГА ОСТАНОВЛЕН "$'\n'
|
||||||
message="${message}🕐 Время остановки: ${timestamp}"
|
message="${message}🕐 Время остановки: ${timestamp}"
|
||||||
|
|
||||||
notify_or_sac "agent.lifecycle" "warning" "Мониторинг остановлен" "ssh-monitor stopped" "$message"
|
notify_send "$message"
|
||||||
write_log "Скрипт мониторинга остановлен"
|
write_log "Скрипт мониторинга остановлен"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1959,7 +1826,6 @@ main() {
|
|||||||
monitor_logind
|
monitor_logind
|
||||||
check_daily_report
|
check_daily_report
|
||||||
check_heartbeat
|
check_heartbeat
|
||||||
sac_flush_spool 20 2>/dev/null || true
|
|
||||||
write_health_and_metrics
|
write_health_and_metrics
|
||||||
|
|
||||||
local current_time
|
local current_time
|
||||||
@@ -2002,8 +1868,6 @@ run_check_config() {
|
|||||||
"${MAIL_SMTP_STARTTLS:-}" "${MAIL_SMTP_SSL:-}"
|
"${MAIL_SMTP_STARTTLS:-}" "${MAIL_SMTP_SSL:-}"
|
||||||
printf 'MAIL_SMTP_USER: %s\n' "$([ -n "${MAIL_SMTP_USER:-}" ] && echo задан || echo не задан)"
|
printf 'MAIL_SMTP_USER: %s\n' "$([ -n "${MAIL_SMTP_USER:-}" ] && echo задан || echo не задан)"
|
||||||
printf 'MAIL_SMTP_PASSWORD: %s\n' "$([ -n "${MAIL_SMTP_PASSWORD:-}" ] && echo задан || echo не задан)"
|
printf 'MAIL_SMTP_PASSWORD: %s\n' "$([ -n "${MAIL_SMTP_PASSWORD:-}" ] && echo задан || echo не задан)"
|
||||||
printf 'UseSAC=%s SAC_URL=%s SAC_API_KEY=%s\n' "${UseSAC:-off}" "${SAC_URL:-}" \
|
|
||||||
"$([ -n "${SAC_API_KEY:-}" ] && echo задан || echo не задан)"
|
|
||||||
if bash -n "$0" 2>/dev/null; then
|
if bash -n "$0" 2>/dev/null; then
|
||||||
printf 'bash -n: OK\n'
|
printf 'bash -n: OK\n'
|
||||||
else
|
else
|
||||||
@@ -2017,7 +1881,6 @@ parse_args() {
|
|||||||
for a in "$@"; do
|
for a in "$@"; do
|
||||||
case "$a" in
|
case "$a" in
|
||||||
--check-config) CHECK_CONFIG_ONLY=1 ;;
|
--check-config) CHECK_CONFIG_ONLY=1 ;;
|
||||||
--check-sac) CHECK_SAC_ONLY=1 ;;
|
|
||||||
--dry-run) DRY_RUN=1 ;;
|
--dry-run) DRY_RUN=1 ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
@@ -2037,11 +1900,6 @@ if [ "$CHECK_CONFIG_ONLY" = "1" ]; then
|
|||||||
exit $?
|
exit $?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${CHECK_SAC_ONLY:-0}" = "1" ]; then
|
|
||||||
run_check_sac
|
|
||||||
exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
trap send_shutdown_notification EXIT INT TERM
|
trap send_shutdown_notification EXIT INT TERM
|
||||||
|
|
||||||
mkdir -p "$(dirname "$LOG_FILE")"
|
mkdir -p "$(dirname "$LOG_FILE")"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
# Репозиторий git.kalinamall.ru — доверенный; значения по умолчанию для пилота SAC.
|
TELEGRAM_BOT_TOKEN="replace_with_telegram_bot_token"
|
||||||
TELEGRAM_BOT_TOKEN="8239219522:AAEyOZX3cwNfgGOMDkf-mgjTIuoaOh5gF7I"
|
TELEGRAM_CHAT_ID="replace_with_telegram_chat_id"
|
||||||
TELEGRAM_CHAT_ID="2843230"
|
|
||||||
|
|
||||||
# Резервный webhook, если ВСЕ каналы из NOTIFY_CHAIN не смогли доставить сообщение (JSON {"text":"..."}).
|
# Резервный webhook, если ВСЕ каналы из NOTIFY_CHAIN не смогли доставить сообщение (JSON {"text":"..."}).
|
||||||
# ВАЖНО: BACKUP_WEBHOOK_URL НЕ заменяет основной канал — без хотя бы одного из telegram/email
|
# ВАЖНО: BACKUP_WEBHOOK_URL НЕ заменяет основной канал — без хотя бы одного из telegram/email
|
||||||
@@ -26,17 +25,6 @@ BACKUP_WEBHOOK_URL=""
|
|||||||
# NOTIFY_ORDER="email"
|
# NOTIFY_ORDER="email"
|
||||||
NOTIFY_ORDER=""
|
NOTIFY_ORDER=""
|
||||||
|
|
||||||
# --- Security Alert Center (SAC) ---
|
|
||||||
# off | exclusive | dual | fallback — см. docs в security-alert-center (agent-integration.md)
|
|
||||||
UseSAC="dual"
|
|
||||||
# Допустимо и корень хоста (ingest всё равно пойдёт на /api/v1/events):
|
|
||||||
SAC_URL="https://sac.kalinamall.ru"
|
|
||||||
SAC_API_KEY="sac_UkOsAT3UWiQS54KK5OJPBDCSucysQDrKFju28wmYiz8"
|
|
||||||
SAC_SPOOL_DIR="/var/lib/ssh-monitor/sac-spool"
|
|
||||||
SAC_FAIL_COUNT_FILE="/var/lib/ssh-monitor/sac-fail.count"
|
|
||||||
SAC_FALLBACK_FAILURES="5"
|
|
||||||
SAC_TIMEOUT_SEC="12"
|
|
||||||
|
|
||||||
# SMTP (python3). Для авто-режима почты нужны MAIL_SMTP_HOST, MAIL_FROM, MAIL_TO.
|
# SMTP (python3). Для авто-режима почты нужны MAIL_SMTP_HOST, MAIL_FROM, MAIL_TO.
|
||||||
MAIL_SMTP_HOST=""
|
MAIL_SMTP_HOST=""
|
||||||
MAIL_SMTP_PORT="587"
|
MAIL_SMTP_PORT="587"
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=SSH Monitor with Telegram alerts and SAC integration
|
|
||||||
After=network-online.target
|
|
||||||
Wants=network-online.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
ExecStart=/usr/local/bin/ssh-monitor
|
|
||||||
Restart=always
|
|
||||||
RestartSec=5
|
|
||||||
User=root
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
Executable → Regular
+102
-670
@@ -9,191 +9,12 @@ LOG_FILE="/var/log/update_script.log"
|
|||||||
|
|
||||||
# Режим отладки: 1 = включён, 0 = выключен
|
# Режим отладки: 1 = включён, 0 = выключен
|
||||||
DEBUG=0
|
DEBUG=0
|
||||||
DEPLOY_MODE=0
|
|
||||||
QUIET=0
|
|
||||||
|
|
||||||
# Итог последнего прогона (для лога и консоли)
|
|
||||||
SUMMARY_SCRIPT="—"
|
|
||||||
SUMMARY_SCRIPT_REASON="—"
|
|
||||||
SUMMARY_SAC_CLIENT="—"
|
|
||||||
SUMMARY_SAC_REASON="—"
|
|
||||||
SUMMARY_CONFIG="—"
|
|
||||||
SUMMARY_GIT="—"
|
|
||||||
SUMMARY_SERVICE_RESTART="—"
|
|
||||||
SUMMARY_UPDATER="—"
|
|
||||||
|
|
||||||
CONFIG_FILE="/etc/ssh-monitor.conf"
|
|
||||||
DEPLOY_INSTALL_PATH="/opt/scripts/update_ssh_monitor.sh"
|
|
||||||
SAC_SPOOL_DIR_DEFAULT="/var/lib/ssh-monitor/sac-spool"
|
|
||||||
SYSTEMD_UNIT_PATH="/etc/systemd/system/ssh-monitor.service"
|
|
||||||
|
|
||||||
# === Список переменных, объявленных в скрипте (для логирования при DEBUG=1) ===
|
# === Список переменных, объявленных в скрипте (для логирования при DEBUG=1) ===
|
||||||
SCRIPT_VARS=("UPDATE_DIR" "SCRIPT_NAME" "REPO_URL" "LOCAL_SCRIPT_PATH" "LOG_FILE" "DEBUG" "REMOTE_SCRIPT_PATH" "LOCAL_CHECKSUM" "REMOTE_CHECKSUM")
|
SCRIPT_VARS=("UPDATE_DIR" "SCRIPT_NAME" "REPO_URL" "LOCAL_SCRIPT_PATH" "LOG_FILE" "DEBUG" "REMOTE_SCRIPT_PATH" "LOCAL_CHECKSUM" "REMOTE_CHECKSUM")
|
||||||
|
|
||||||
# === Функции ===
|
# === Функции ===
|
||||||
|
|
||||||
say_step() {
|
|
||||||
printf '\n==> %s\n' "$1"
|
|
||||||
}
|
|
||||||
|
|
||||||
say_ok() {
|
|
||||||
printf '[OK] %s\n' "$1"
|
|
||||||
log_message "INFO" "$1"
|
|
||||||
}
|
|
||||||
|
|
||||||
say_warn() {
|
|
||||||
printf '[WARN] %s\n' "$1"
|
|
||||||
log_message "INFO" "WARNING: $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
say_info() {
|
|
||||||
printf ' %s\n' "$1"
|
|
||||||
}
|
|
||||||
|
|
||||||
show_help() {
|
|
||||||
cat <<'EOF'
|
|
||||||
update_ssh_monitor.sh — обновление или первичная установка ssh-monitor
|
|
||||||
|
|
||||||
Использование:
|
|
||||||
sudo ./update_ssh_monitor.sh обновление (git pull + checksum)
|
|
||||||
sudo ./update_ssh_monitor.sh --deploy первичная установка / полный deploy
|
|
||||||
sudo ./first_deploy.sh то же, что --deploy
|
|
||||||
|
|
||||||
Опции:
|
|
||||||
--deploy установить ssh-monitor, sac-client, пример конфига, systemd unit
|
|
||||||
--debug подробный лог в /var/log/update_script.log
|
|
||||||
--quiet не печатать шаги в stdout (только в $LOG_FILE)
|
|
||||||
-h, --help эта справка
|
|
||||||
|
|
||||||
Лог действий: /var/log/update_script.log (в конце каждого прогона — блок «Итог обновления»).
|
|
||||||
|
|
||||||
При первой установке или апгрейде с UseSAC=off / пустым SAC_API_KEY / без sac-client.sh
|
|
||||||
example из git перезаписывает /etc/ssh-monitor.conf (старый файл — .bak.*).
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
find_ssh_monitor_conf_example() {
|
|
||||||
local example="$UPDATE_DIR/$SCRIPT_NAME/ssh-monitor.conf.example"
|
|
||||||
if [ -f "$example" ]; then
|
|
||||||
printf '%s\n' "$example"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
config_conf_value() {
|
|
||||||
local key="$1" file="$2" line val
|
|
||||||
[ -f "$file" ] || return 1
|
|
||||||
line=$(grep -E "^[[:space:]]*${key}=" "$file" 2>/dev/null | tail -1) || return 1
|
|
||||||
val=${line#*=}
|
|
||||||
val=${val#"${val%%[![:space:]]*}"}
|
|
||||||
val=${val%"${val##*[![:space:]]}"}
|
|
||||||
val=${val#\"}
|
|
||||||
val=${val%\"}
|
|
||||||
val=${val#\'}
|
|
||||||
val=${val%\'}
|
|
||||||
printf '%s\n' "$val"
|
|
||||||
}
|
|
||||||
|
|
||||||
config_needs_sac_bootstrap() {
|
|
||||||
local sac_client use_sac api_key
|
|
||||||
|
|
||||||
sac_client="${LOCAL_SCRIPT_PATH%/*}/sac-client.sh"
|
|
||||||
if [ ! -f "$sac_client" ]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
if [ ! -f "$CONFIG_FILE" ]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
use_sac=$(config_conf_value "UseSAC" "$CONFIG_FILE" 2>/dev/null || true)
|
|
||||||
api_key=$(config_conf_value "SAC_API_KEY" "$CONFIG_FILE" 2>/dev/null || true)
|
|
||||||
|
|
||||||
if [ -n "$use_sac" ] && [ "$use_sac" != "off" ] \
|
|
||||||
&& [ -n "$api_key" ] && [ "${api_key#sac_}" != "$api_key" ]; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_ssh_monitor_config_from_example() {
|
|
||||||
local example
|
|
||||||
CONFIG_CHANGED=0
|
|
||||||
|
|
||||||
example=$(find_ssh_monitor_conf_example) || {
|
|
||||||
log_message "INFO" "WARNING: ssh-monitor.conf.example not found in clone"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ -f "$CONFIG_FILE" ] && ! config_needs_sac_bootstrap; then
|
|
||||||
log_message "INFO" "Config $CONFIG_FILE: SAC already configured, not overwriting."
|
|
||||||
if [ "$DEPLOY_MODE" -eq 1 ]; then
|
|
||||||
say_info "Уже существует: $CONFIG_FILE (SAC настроен, не перезаписываем)"
|
|
||||||
fi
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "$CONFIG_FILE" ]; then
|
|
||||||
cp "$CONFIG_FILE" "${CONFIG_FILE}.bak.$(date +%Y%m%d_%H%M%S)" 2>/dev/null || true
|
|
||||||
log_message "INFO" "Upgrading config without SAC: backup created, applying ssh-monitor.conf.example from git."
|
|
||||||
if [ "$DEPLOY_MODE" -eq 1 ]; then
|
|
||||||
say_info "Апгрейд без SAC: резервная копия, применяем example (Telegram + dual)."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
log_message "INFO" "Creating $CONFIG_FILE from ssh-monitor.conf.example"
|
|
||||||
if [ "$DEPLOY_MODE" -eq 1 ]; then
|
|
||||||
say_ok "Создан $CONFIG_FILE из ssh-monitor.conf.example"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! cp "$example" "$CONFIG_FILE"; then
|
|
||||||
log_message "INFO" "ERROR: Failed to copy ssh-monitor.conf.example to $CONFIG_FILE"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
chmod 600 "$CONFIG_FILE" 2>/dev/null || true
|
|
||||||
CONFIG_CHANGED=1
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
parse_command_line() {
|
|
||||||
DEPLOY_MODE=0
|
|
||||||
while [ $# -gt 0 ]; do
|
|
||||||
case "$1" in
|
|
||||||
--deploy)
|
|
||||||
DEPLOY_MODE=1
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--debug)
|
|
||||||
DEBUG=1
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--quiet)
|
|
||||||
QUIET=1
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
-h | --help)
|
|
||||||
show_help
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
printf 'Неизвестная опция: %s\n' "$1" >&2
|
|
||||||
show_help >&2
|
|
||||||
exit 2
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
deploy_require_root() {
|
|
||||||
if [ "$DEPLOY_MODE" -ne 1 ]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
if [ "$(id -u)" -ne 0 ]; then
|
|
||||||
printf 'ERROR: режим --deploy нужно запускать от root (sudo)\n' >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Основная функция логирования
|
# Основная функция логирования
|
||||||
log_message() {
|
log_message() {
|
||||||
local level="$1"
|
local level="$1"
|
||||||
@@ -216,48 +37,6 @@ log_message() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Всегда в лог; в stderr — если не --quiet (stdout не трогаем: его используют $(find_*))
|
|
||||||
log_action() {
|
|
||||||
log_message "INFO" "$1"
|
|
||||||
if [ "$QUIET" -eq 0 ]; then
|
|
||||||
printf '[update_ssh_monitor] %s\n' "$1" >&2
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
get_git_short_rev() {
|
|
||||||
local repo_dir="$1"
|
|
||||||
git -C "$repo_dir" rev-parse --short HEAD 2>/dev/null || printf 'unknown'
|
|
||||||
}
|
|
||||||
|
|
||||||
read_ssh_monitor_version() {
|
|
||||||
local f="$1"
|
|
||||||
local line
|
|
||||||
[ -f "$f" ] || return 0
|
|
||||||
line=$(grep -E '^[[:space:]]*SSH_MONITOR_VERSION=' "$f" 2>/dev/null | head -1) || return 0
|
|
||||||
line=${line#SSH_MONITOR_VERSION=}
|
|
||||||
line=${line#\"}
|
|
||||||
line=${line%\"}
|
|
||||||
line=${line#\'}
|
|
||||||
line=${line%\'}
|
|
||||||
printf '%s' "$line"
|
|
||||||
}
|
|
||||||
|
|
||||||
file_sha256() {
|
|
||||||
sha256sum "$1" 2>/dev/null | awk '{print $1}' || true
|
|
||||||
}
|
|
||||||
|
|
||||||
log_update_summary() {
|
|
||||||
log_action "===== Итог обновления ====="
|
|
||||||
log_action "ssh-monitor: $SUMMARY_SCRIPT ($SUMMARY_SCRIPT_REASON)"
|
|
||||||
log_action "sac-client.sh: $SUMMARY_SAC_CLIENT ($SUMMARY_SAC_REASON)"
|
|
||||||
log_action "конфиг: $SUMMARY_CONFIG"
|
|
||||||
log_action "git: $SUMMARY_GIT"
|
|
||||||
log_action "updater ($DEPLOY_INSTALL_PATH): $SUMMARY_UPDATER"
|
|
||||||
log_action "перезапуск ssh-monitor.service: $SUMMARY_SERVICE_RESTART"
|
|
||||||
log_action "полный лог: $LOG_FILE"
|
|
||||||
log_action "==========================="
|
|
||||||
}
|
|
||||||
|
|
||||||
# Функция для логирования шагов с комментариями (работает только при DEBUG=1)
|
# Функция для логирования шагов с комментариями (работает только при DEBUG=1)
|
||||||
debug_step() {
|
debug_step() {
|
||||||
if [ "$DEBUG" -eq 1 ]; then
|
if [ "$DEBUG" -eq 1 ]; then
|
||||||
@@ -416,518 +195,171 @@ ensure_directories() {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Путь к sac-client.sh в клоне репозитория (корень git)
|
# Функция для обновления скрипта
|
||||||
find_sac_client_remote() {
|
|
||||||
local candidates=(
|
|
||||||
"$UPDATE_DIR/$SCRIPT_NAME/sac-client.sh"
|
|
||||||
)
|
|
||||||
local p
|
|
||||||
for p in "${candidates[@]}"; do
|
|
||||||
if [ -f "$p" ]; then
|
|
||||||
printf '%s\n' "$p"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Копирует sac-client.sh рядом с LOCAL_SCRIPT_PATH (всегда после git pull, по checksum).
|
|
||||||
# Устанавливает SAC_CLIENT_CHANGED=1 при фактическом обновлении файла.
|
|
||||||
sync_sac_client() {
|
|
||||||
SAC_CLIENT_CHANGED=0
|
|
||||||
local remote local_dst local_sum remote_sum
|
|
||||||
remote=$(find_sac_client_remote) || {
|
|
||||||
SUMMARY_SAC_CLIENT="не найден в клоне"
|
|
||||||
SUMMARY_SAC_REASON="путь $UPDATE_DIR/$SCRIPT_NAME/sac-client.sh"
|
|
||||||
log_action "WARNING: sac-client.sh не найден в клоне git"
|
|
||||||
debug_step "sac-client.sh не найден в клоне репозитория"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
local_dst="${LOCAL_SCRIPT_PATH%/*}/sac-client.sh"
|
|
||||||
log_action "sac-client: remote=$remote local=$local_dst"
|
|
||||||
remote_sum=$(file_sha256 "$remote")
|
|
||||||
if [ -z "$remote_sum" ]; then
|
|
||||||
SUMMARY_SAC_CLIENT="ошибка"
|
|
||||||
SUMMARY_SAC_REASON="не удалось sha256 remote"
|
|
||||||
log_action "ERROR: не удалось посчитать sha256 для $remote"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$DEPLOY_MODE" -ne 1 ] && [ -f "$local_dst" ]; then
|
|
||||||
local_sum=$(file_sha256 "$local_dst")
|
|
||||||
if [ "$local_sum" = "$remote_sum" ]; then
|
|
||||||
SUMMARY_SAC_CLIENT="без изменений"
|
|
||||||
SUMMARY_SAC_REASON="sha256 совпадает (${local_sum:0:16}…)"
|
|
||||||
log_action "sac-client.sh: checksum совпадает, копирование не требуется"
|
|
||||||
debug_step "sac-client.sh без изменений"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
log_action "sac-client.sh: checksum отличается (local ${local_sum:0:16}… != remote ${remote_sum:0:16}…)"
|
|
||||||
elif [ ! -f "$local_dst" ]; then
|
|
||||||
log_action "sac-client.sh: локальный файл отсутствует, будет установлен"
|
|
||||||
debug_step "Локальный sac-client.sh отсутствует, будет установлен"
|
|
||||||
fi
|
|
||||||
|
|
||||||
debug_step "Копируем sac-client.sh в $local_dst"
|
|
||||||
if ! cp "$remote" "$local_dst"; then
|
|
||||||
SUMMARY_SAC_CLIENT="ошибка копирования"
|
|
||||||
SUMMARY_SAC_REASON="cp $remote -> $local_dst"
|
|
||||||
log_action "ERROR: не удалось скопировать sac-client.sh"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
chmod 644 "$local_dst"
|
|
||||||
SAC_CLIENT_CHANGED=1
|
|
||||||
SUMMARY_SAC_CLIENT="обновлён"
|
|
||||||
SUMMARY_SAC_REASON="sha256 ${remote_sum:0:16}…"
|
|
||||||
log_action "sac-client.sh обновлён → $local_dst"
|
|
||||||
if [ "$DEPLOY_MODE" -eq 1 ]; then
|
|
||||||
say_ok "sac-client.sh → $local_dst"
|
|
||||||
fi
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
find_remote_ssh_monitor_path() {
|
|
||||||
local repo="$UPDATE_DIR/$SCRIPT_NAME"
|
|
||||||
local candidates=() git_path="" found="" p
|
|
||||||
|
|
||||||
REMOTE_SCRIPT_PATH="$repo/ssh-monitor"
|
|
||||||
ALT_REMOTE_PATH="$repo/src/branch/main/ssh-monitor"
|
|
||||||
candidates=(
|
|
||||||
"$REMOTE_SCRIPT_PATH"
|
|
||||||
"$ALT_REMOTE_PATH"
|
|
||||||
)
|
|
||||||
|
|
||||||
for p in "${candidates[@]}"; do
|
|
||||||
if [ -f "$p" ]; then
|
|
||||||
log_action "ssh-monitor в клоне: $p"
|
|
||||||
printf '%s\n' "$p"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -d "$repo/.git" ]; then
|
|
||||||
if git -C "$repo" cat-file -e "HEAD:ssh-monitor" 2>/dev/null; then
|
|
||||||
if [ ! -f "$repo/ssh-monitor" ]; then
|
|
||||||
log_action "ssh-monitor есть в git HEAD, но отсутствует на диске — git checkout HEAD -- ssh-monitor"
|
|
||||||
git -C "$repo" checkout HEAD -- ssh-monitor >> "$LOG_FILE" 2>&1 || true
|
|
||||||
fi
|
|
||||||
if [ -f "$repo/ssh-monitor" ]; then
|
|
||||||
log_action "ssh-monitor восстановлен: $repo/ssh-monitor"
|
|
||||||
printf '%s\n' "$repo/ssh-monitor"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
git_path=$(git -C "$repo" ls-files 'ssh-monitor' 2>/dev/null | grep -v '^contrib/' | head -1)
|
|
||||||
if [ -z "$git_path" ]; then
|
|
||||||
git_path=$(git -C "$repo" ls-files 2>/dev/null | grep -E '/ssh-monitor$' | grep -v contrib | grep -v watchdog | head -1)
|
|
||||||
fi
|
|
||||||
if [ -n "$git_path" ] && [ -f "$repo/$git_path" ]; then
|
|
||||||
log_action "ssh-monitor в клоне (git ls-files): $repo/$git_path"
|
|
||||||
printf '%s\n' "$repo/$git_path"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
found=$(find "$repo" -maxdepth 4 -type f -name 'ssh-monitor' ! -path '*/contrib/*' 2>/dev/null | head -1)
|
|
||||||
if [ -n "$found" ] && [ -f "$found" ]; then
|
|
||||||
log_action "ssh-monitor найден поиском: $found"
|
|
||||||
printf '%s\n' "$found"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_action "WARNING: ssh-monitor не найден в $repo"
|
|
||||||
if [ -d "$repo" ]; then
|
|
||||||
log_action "корень клона: $(ls -1 "$repo" 2>/dev/null | tr '\n' ' ')"
|
|
||||||
fi
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
install_ssh_monitor_from() {
|
|
||||||
local actual_script_path="$1"
|
|
||||||
local backup_path=""
|
|
||||||
if [ -f "$LOCAL_SCRIPT_PATH" ]; then
|
|
||||||
backup_path="${LOCAL_SCRIPT_PATH}.backup.$(date +%Y%m%d_%H%M%S)"
|
|
||||||
cp "$LOCAL_SCRIPT_PATH" "$backup_path" 2>/dev/null || true
|
|
||||||
log_action "резервная копия: $backup_path"
|
|
||||||
fi
|
|
||||||
if ! cp "$actual_script_path" "$LOCAL_SCRIPT_PATH"; then
|
|
||||||
log_action "ERROR: не удалось скопировать $actual_script_path → $LOCAL_SCRIPT_PATH"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
chmod +x "$LOCAL_SCRIPT_PATH"
|
|
||||||
log_action "ssh-monitor установлен: $LOCAL_SCRIPT_PATH (версия $(read_ssh_monitor_version "$LOCAL_SCRIPT_PATH"))"
|
|
||||||
if [ "$DEPLOY_MODE" -eq 1 ]; then
|
|
||||||
say_ok "ssh-monitor → $LOCAL_SCRIPT_PATH"
|
|
||||||
fi
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
deploy_prepare_dirs() {
|
|
||||||
say_step "Каталоги"
|
|
||||||
mkdir -p "$UPDATE_DIR" /opt/scripts /var/log "$SAC_SPOOL_DIR_DEFAULT" /var/lib/ssh-monitor 2>/dev/null || true
|
|
||||||
say_ok "UPDATE_DIR=$UPDATE_DIR, spool=$SAC_SPOOL_DIR_DEFAULT"
|
|
||||||
}
|
|
||||||
|
|
||||||
deploy_install_config() {
|
|
||||||
say_step "Конфигурация"
|
|
||||||
ensure_ssh_monitor_config_from_example || return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
deploy_install_systemd() {
|
|
||||||
local example="$UPDATE_DIR/$SCRIPT_NAME/ssh-monitor.service.example"
|
|
||||||
say_step "systemd"
|
|
||||||
if [ -f "$SYSTEMD_UNIT_PATH" ]; then
|
|
||||||
say_info "Уже существует: $SYSTEMD_UNIT_PATH"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
if [ ! -f "$example" ]; then
|
|
||||||
say_warn "Unit example не найден: $example"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
cp "$example" "$SYSTEMD_UNIT_PATH"
|
|
||||||
if command -v systemctl &>/dev/null; then
|
|
||||||
systemctl daemon-reload 2>/dev/null || true
|
|
||||||
systemctl enable ssh-monitor.service 2>/dev/null || true
|
|
||||||
say_ok "Установлен $SYSTEMD_UNIT_PATH (enable ssh-monitor.service)"
|
|
||||||
else
|
|
||||||
say_ok "Скопирован $SYSTEMD_UNIT_PATH (systemctl не найден)"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
deploy_install_self() {
|
|
||||||
local self_path
|
|
||||||
self_path="$(readlink -f "$0" 2>/dev/null || realpath "$0" 2>/dev/null || printf '%s' "$0")"
|
|
||||||
say_step "Скрипт автообновления"
|
|
||||||
if [ "$self_path" = "$DEPLOY_INSTALL_PATH" ]; then
|
|
||||||
say_info "Уже запущен из $DEPLOY_INSTALL_PATH"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
mkdir -p "$(dirname "$DEPLOY_INSTALL_PATH")"
|
|
||||||
cp "$self_path" "$DEPLOY_INSTALL_PATH"
|
|
||||||
chmod 750 "$DEPLOY_INSTALL_PATH"
|
|
||||||
say_ok "update_ssh_monitor.sh → $DEPLOY_INSTALL_PATH"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_config_variables_help() {
|
|
||||||
say_step "Заполните в $CONFIG_FILE"
|
|
||||||
cat <<'EOF'
|
|
||||||
Обязательно хотя бы один канал уведомлений ИЛИ SAC:
|
|
||||||
TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID
|
|
||||||
— или MAIL_SMTP_HOST, MAIL_FROM, MAIL_TO
|
|
||||||
|
|
||||||
Security Alert Center:
|
|
||||||
UseSAC=off | dual | exclusive | fallback
|
|
||||||
Пилот 2.1 (только SAC): UseSAC=exclusive
|
|
||||||
SAC_URL=https://sac.kalinamall.ru
|
|
||||||
SAC_API_KEY=<ключ из SAC sac-api.env / bootstrap>
|
|
||||||
|
|
||||||
Проверка после правок:
|
|
||||||
/usr/local/bin/ssh-monitor --check-config
|
|
||||||
/usr/local/bin/ssh-monitor --check-sac
|
|
||||||
systemctl restart ssh-monitor.service
|
|
||||||
systemctl status ssh-monitor.service
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
print_deploy_summary() {
|
|
||||||
say_step "Итог установки"
|
|
||||||
say_info "Монитор: $LOCAL_SCRIPT_PATH"
|
|
||||||
say_info "SAC client: ${LOCAL_SCRIPT_PATH%/*}/sac-client.sh"
|
|
||||||
say_info "Конфиг: $CONFIG_FILE"
|
|
||||||
say_info "Git-клон: $UPDATE_DIR/$SCRIPT_NAME"
|
|
||||||
say_info "Updater: $DEPLOY_INSTALL_PATH"
|
|
||||||
say_info "Лог updater: $LOG_FILE"
|
|
||||||
}
|
|
||||||
|
|
||||||
deploy_post_install() {
|
|
||||||
deploy_prepare_dirs
|
|
||||||
deploy_install_config
|
|
||||||
deploy_install_systemd
|
|
||||||
deploy_install_self
|
|
||||||
print_deploy_summary
|
|
||||||
print_config_variables_help
|
|
||||||
}
|
|
||||||
|
|
||||||
# Копирует update_ssh_monitor.sh из git-клона в /opt/scripts/ и при необходимости re-exec.
|
|
||||||
sync_installed_updater_from_clone() {
|
|
||||||
local remote="$UPDATE_DIR/$SCRIPT_NAME/update_ssh_monitor.sh"
|
|
||||||
local self installed_path remote_sum installed_sum=""
|
|
||||||
|
|
||||||
if [ ! -f "$remote" ]; then
|
|
||||||
SUMMARY_UPDATER="не найден в клоне"
|
|
||||||
log_action "updater: в клоне нет $remote"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
self="$(readlink -f "$0" 2>/dev/null || realpath "$0" 2>/dev/null || printf '%s' "$0")"
|
|
||||||
installed_path="$(readlink -f "$DEPLOY_INSTALL_PATH" 2>/dev/null || realpath "$DEPLOY_INSTALL_PATH" 2>/dev/null || printf '%s' "$DEPLOY_INSTALL_PATH")"
|
|
||||||
remote_sum=$(file_sha256 "$remote")
|
|
||||||
if [ -f "$DEPLOY_INSTALL_PATH" ]; then
|
|
||||||
installed_sum=$(file_sha256 "$DEPLOY_INSTALL_PATH")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$remote_sum" = "$installed_sum" ]; then
|
|
||||||
SUMMARY_UPDATER="актуален (sha ${remote_sum:0:16}…)"
|
|
||||||
log_action "updater: $DEPLOY_INSTALL_PATH совпадает с git (commit $(get_git_short_rev "$UPDATE_DIR/$SCRIPT_NAME"))"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p "$(dirname "$DEPLOY_INSTALL_PATH")"
|
|
||||||
if ! cp "$remote" "$DEPLOY_INSTALL_PATH"; then
|
|
||||||
SUMMARY_UPDATER="ошибка копирования"
|
|
||||||
log_action "ERROR: не удалось обновить $DEPLOY_INSTALL_PATH из git"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
chmod 750 "$DEPLOY_INSTALL_PATH"
|
|
||||||
SUMMARY_UPDATER="обновлён из git (${installed_sum:0:8}…→${remote_sum:0:8}…)"
|
|
||||||
log_action "updater: записан $DEPLOY_INSTALL_PATH из клона (новый sha ${remote_sum:0:16}…)"
|
|
||||||
|
|
||||||
if [ "$self" = "$installed_path" ] && [ "${UPDATER_REEXEC_GUARD:-0}" != "1" ]; then
|
|
||||||
log_action "updater: перезапуск с новой версией (расширенный лог)…"
|
|
||||||
UPDATER_REEXEC_GUARD=1 exec /bin/bash "$DEPLOY_INSTALL_PATH" "$@"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$self" != "$installed_path" ]; then
|
|
||||||
log_action "updater: сейчас запущен $self — в cron используйте $DEPLOY_INSTALL_PATH"
|
|
||||||
fi
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
restart_ssh_monitor_service() {
|
|
||||||
debug_step "Перезапускаем сервис ssh-monitor.service"
|
|
||||||
if command -v systemctl &>/dev/null; then
|
|
||||||
if systemctl restart ssh-monitor.service 2>/dev/null; then
|
|
||||||
SUMMARY_SERVICE_RESTART="выполнен"
|
|
||||||
log_action "ssh-monitor.service перезапущен"
|
|
||||||
debug_step "Сервис успешно перезапущен"
|
|
||||||
else
|
|
||||||
SUMMARY_SERVICE_RESTART="не удался"
|
|
||||||
log_action "WARNING: systemctl restart ssh-monitor.service не выполнен"
|
|
||||||
debug_step "ПРЕДУПРЕЖДЕНИЕ: Не удалось перезапустить сервис"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
SUMMARY_SERVICE_RESTART="пропущен (нет systemctl)"
|
|
||||||
log_action "systemctl не найден, перезапуск сервиса пропущен"
|
|
||||||
debug_step "systemctl не найден, сервис не перезапущен"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Функция для обновления скрипта (принимает "$@" для re-exec updater)
|
|
||||||
update_script() {
|
update_script() {
|
||||||
local git_repo="$UPDATE_DIR/$SCRIPT_NAME"
|
log_message "INFO" "Starting script update..."
|
||||||
local git_before="" git_after="" pull_ok=1
|
|
||||||
local actual_script_path="" script_changed=0
|
|
||||||
local local_ver="" remote_ver="" local_sum="" remote_sum=""
|
|
||||||
|
|
||||||
SUMMARY_SCRIPT="—"
|
|
||||||
SUMMARY_SCRIPT_REASON="—"
|
|
||||||
SUMMARY_SAC_CLIENT="—"
|
|
||||||
SUMMARY_SAC_REASON="—"
|
|
||||||
SUMMARY_CONFIG="—"
|
|
||||||
SUMMARY_GIT="—"
|
|
||||||
SUMMARY_SERVICE_RESTART="не требовался"
|
|
||||||
|
|
||||||
log_action "Старт обновления (deploy=$DEPLOY_MODE, debug=$DEBUG)"
|
|
||||||
debug_step "Запуск функции обновления скрипта"
|
debug_step "Запуск функции обновления скрипта"
|
||||||
|
|
||||||
|
# Гарантируем наличие директории
|
||||||
debug_step "Гарантируем наличие директории для работы"
|
debug_step "Гарантируем наличие директории для работы"
|
||||||
if ! ensure_directories; then
|
if ! ensure_directories; then
|
||||||
SUMMARY_SCRIPT="ошибка"
|
|
||||||
SUMMARY_SCRIPT_REASON="нет каталога $UPDATE_DIR или нет прав на запись"
|
|
||||||
log_update_summary
|
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Переходим в директорию для клона репозитория
|
||||||
debug_step "Переходим в директорию для клона репозитория: $UPDATE_DIR"
|
debug_step "Переходим в директорию для клона репозитория: $UPDATE_DIR"
|
||||||
if ! cd "$UPDATE_DIR" 2>/dev/null; then
|
if ! cd "$UPDATE_DIR" 2>/dev/null; then
|
||||||
SUMMARY_SCRIPT="ошибка"
|
log_message "INFO" "ERROR: Cannot cd to $UPDATE_DIR"
|
||||||
SUMMARY_SCRIPT_REASON="cannot cd $UPDATE_DIR"
|
|
||||||
log_action "ERROR: cannot cd $UPDATE_DIR"
|
|
||||||
log_update_summary
|
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Клонируем или обновляем репозиторий
|
||||||
debug_step "Проверяем наличие репозитория и обновляем/клонируем"
|
debug_step "Проверяем наличие репозитория и обновляем/клонируем"
|
||||||
if [ -d "$SCRIPT_NAME" ]; then
|
if [ -d "$SCRIPT_NAME" ]; then
|
||||||
git_before="$(get_git_short_rev "$git_repo")"
|
log_message "INFO" "Repository exists, pulling updates..."
|
||||||
log_action "git: pull origin main (было commit $git_before) → $REPO_URL"
|
|
||||||
debug_step "Выполняем git pull origin main"
|
debug_step "Выполняем git pull origin main"
|
||||||
if git -C "$SCRIPT_NAME" pull origin main >> "$LOG_FILE" 2>&1; then
|
if ! git -C "$SCRIPT_NAME" pull origin main >> "$LOG_FILE" 2>&1; then
|
||||||
git_after="$(get_git_short_rev "$git_repo")"
|
log_message "INFO" "WARNING: git pull failed, continuing with existing local copy..."
|
||||||
SUMMARY_GIT="pull ok, $git_before → $git_after"
|
|
||||||
log_action "git pull: успех (commit $git_before → $git_after)"
|
|
||||||
else
|
|
||||||
pull_ok=0
|
|
||||||
git_after="$(get_git_short_rev "$git_repo")"
|
|
||||||
SUMMARY_GIT="pull FAILED, используется локальный клон ($git_after)"
|
|
||||||
log_action "WARNING: git pull не удался — продолжаем со старым клоном (commit $git_after). См. хвост $LOG_FILE"
|
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log_action "git: clone $REPO_URL → $UPDATE_DIR/$SCRIPT_NAME"
|
log_message "INFO" "Cloning repository from $REPO_URL"
|
||||||
debug_step "Выполняем git clone $REPO_URL $SCRIPT_NAME"
|
debug_step "Выполняем git clone $REPO_URL $SCRIPT_NAME"
|
||||||
if ! git clone "$REPO_URL" "$SCRIPT_NAME" >> "$LOG_FILE" 2>&1; then
|
if ! git clone "$REPO_URL" "$SCRIPT_NAME" >> "$LOG_FILE" 2>&1; then
|
||||||
SUMMARY_GIT="clone FAILED"
|
log_message "INFO" "ERROR: git clone failed"
|
||||||
SUMMARY_SCRIPT="ошибка"
|
|
||||||
SUMMARY_SCRIPT_REASON="git clone failed"
|
|
||||||
log_action "ERROR: git clone failed (см. $LOG_FILE)"
|
|
||||||
log_update_summary
|
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
git_after="$(get_git_short_rev "$git_repo")"
|
|
||||||
SUMMARY_GIT="clone ok, commit $git_after"
|
|
||||||
log_action "git clone: успех (commit $git_after)"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sync_installed_updater_from_clone "$@"
|
# Формируем путь к скрипту в репозитории (основной и альтернативный)
|
||||||
|
REMOTE_SCRIPT_PATH="$UPDATE_DIR/$SCRIPT_NAME/src/branch/main/$SCRIPT_NAME"
|
||||||
|
ALT_REMOTE_PATH="$UPDATE_DIR/$SCRIPT_NAME/$SCRIPT_NAME"
|
||||||
|
|
||||||
|
debug_step "Основной путь к скрипту: $REMOTE_SCRIPT_PATH"
|
||||||
|
debug_step "Альтернативный путь к скрипту: $ALT_REMOTE_PATH"
|
||||||
|
|
||||||
actual_script_path="$(find_remote_ssh_monitor_path 2>/dev/null || true)"
|
# Определяем реальный путь к скрипту
|
||||||
actual_script_path="${actual_script_path//$'\n'/}"
|
local actual_script_path=""
|
||||||
actual_script_path="${actual_script_path//$'\r'/}"
|
if [ -f "$REMOTE_SCRIPT_PATH" ]; then
|
||||||
debug_step "Путь к ssh-monitor в клоне: ${actual_script_path:-не найден}"
|
actual_script_path="$REMOTE_SCRIPT_PATH"
|
||||||
|
debug_step "Скрипт найден по основному пути"
|
||||||
SAC_CLIENT_CHANGED=0
|
elif [ -f "$ALT_REMOTE_PATH" ]; then
|
||||||
if ! sync_sac_client; then
|
actual_script_path="$ALT_REMOTE_PATH"
|
||||||
log_update_summary
|
log_message "INFO" "Script found at alternative path"
|
||||||
return 1
|
debug_step "Скрипт найден по альтернативному пути"
|
||||||
fi
|
|
||||||
|
|
||||||
CONFIG_CHANGED=0
|
|
||||||
if ! ensure_ssh_monitor_config_from_example; then
|
|
||||||
log_update_summary
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
if [ "${CONFIG_CHANGED:-0}" -eq 1 ]; then
|
|
||||||
SUMMARY_CONFIG="обновлён из example"
|
|
||||||
else
|
|
||||||
SUMMARY_CONFIG="без изменений"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Проверяем наличие файла скрипта в репозитории
|
||||||
if [ -n "$actual_script_path" ] && [ -f "$actual_script_path" ]; then
|
if [ -n "$actual_script_path" ] && [ -f "$actual_script_path" ]; then
|
||||||
if [ "$DEPLOY_MODE" -eq 1 ]; then
|
log_message "INFO" "Remote script found. Comparing checksums..."
|
||||||
log_action "режим --deploy: установка ssh-monitor → $LOCAL_SCRIPT_PATH"
|
debug_step "Проверяем наличие файла скрипта в репозитории"
|
||||||
if install_ssh_monitor_from "$actual_script_path"; then
|
|
||||||
script_changed=1
|
# Проверяем существование локального скрипта
|
||||||
SUMMARY_SCRIPT="установлен (--deploy)"
|
if [ ! -f "$LOCAL_SCRIPT_PATH" ]; then
|
||||||
SUMMARY_SCRIPT_REASON="$(read_ssh_monitor_version "$LOCAL_SCRIPT_PATH")"
|
log_message "INFO" "Local script not found. Will install new one."
|
||||||
else
|
LOCAL_CHECKSUM=""
|
||||||
SUMMARY_SCRIPT="ошибка установки"
|
debug_step "Локальный скрипт отсутствует, будет установлен новый"
|
||||||
log_update_summary
|
else
|
||||||
|
debug_step "Вычисляем контрольную сумму локального скрипта"
|
||||||
|
LOCAL_CHECKSUM=$(sha256sum "$LOCAL_SCRIPT_PATH" 2>/dev/null | awk '{print $1}')
|
||||||
|
fi
|
||||||
|
|
||||||
|
debug_step "Вычисляем контрольную сумму удалённого скрипта"
|
||||||
|
REMOTE_CHECKSUM=$(sha256sum "$actual_script_path" 2>/dev/null | awk '{print $1}')
|
||||||
|
|
||||||
|
# Сравниваем контрольные суммы
|
||||||
|
debug_step "Сравниваем контрольные суммы"
|
||||||
|
if [ "$LOCAL_CHECKSUM" != "$REMOTE_CHECKSUM" ]; then
|
||||||
|
log_message "INFO" "Checksums differ. Updating $LOCAL_SCRIPT_PATH..."
|
||||||
|
debug_step "Контрольные суммы различаются, начинаем обновление"
|
||||||
|
|
||||||
|
# Создаем бекап текущего скрипта если он существует
|
||||||
|
if [ -f "$LOCAL_SCRIPT_PATH" ]; then
|
||||||
|
debug_step "Создаем резервную копию текущего скрипта"
|
||||||
|
cp "$LOCAL_SCRIPT_PATH" "${LOCAL_SCRIPT_PATH}.backup.$(date +%Y%m%d_%H%M%S)" 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
debug_step "Копируем новый скрипт в $LOCAL_SCRIPT_PATH"
|
||||||
|
if ! cp "$actual_script_path" "$LOCAL_SCRIPT_PATH"; then
|
||||||
|
log_message "INFO" "ERROR: Failed to copy script"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
else
|
|
||||||
if [ ! -f "$LOCAL_SCRIPT_PATH" ]; then
|
debug_step "Делаем скрипт исполняемым"
|
||||||
LOCAL_CHECKSUM=""
|
chmod +x "$LOCAL_SCRIPT_PATH"
|
||||||
local_ver="(нет файла)"
|
|
||||||
else
|
log_message "INFO" "Script updated successfully."
|
||||||
LOCAL_CHECKSUM=$(file_sha256 "$LOCAL_SCRIPT_PATH")
|
debug_step "Скрипт успешно обновлен"
|
||||||
local_ver="$(read_ssh_monitor_version "$LOCAL_SCRIPT_PATH")"
|
|
||||||
fi
|
# Перезапускаем сервис
|
||||||
REMOTE_CHECKSUM=$(file_sha256 "$actual_script_path")
|
debug_step "Перезапускаем сервис ssh-monitor.service"
|
||||||
remote_ver="$(read_ssh_monitor_version "$actual_script_path")"
|
if command -v systemctl &>/dev/null; then
|
||||||
log_action "сравнение: local=$LOCAL_SCRIPT_PATH ver=$local_ver sha=${LOCAL_CHECKSUM:0:16}…"
|
if systemctl restart ssh-monitor.service 2>/dev/null; then
|
||||||
log_action "сравнение: remote=$actual_script_path ver=$remote_ver sha=${REMOTE_CHECKSUM:0:16}…"
|
log_message "INFO" "Service restarted successfully."
|
||||||
if [ "$pull_ok" -eq 0 ]; then
|
debug_step "Сервис успешно перезапущен"
|
||||||
log_action "примечание: git pull не удался — remote может быть устаревшим"
|
|
||||||
fi
|
|
||||||
if [ "$LOCAL_CHECKSUM" != "$REMOTE_CHECKSUM" ]; then
|
|
||||||
log_action "checksum различается — обновляем $LOCAL_SCRIPT_PATH"
|
|
||||||
if install_ssh_monitor_from "$actual_script_path"; then
|
|
||||||
script_changed=1
|
|
||||||
SUMMARY_SCRIPT="обновлён"
|
|
||||||
SUMMARY_SCRIPT_REASON="${local_ver} → ${remote_ver}, sha изменился"
|
|
||||||
else
|
else
|
||||||
SUMMARY_SCRIPT="ошибка копирования"
|
log_message "INFO" "WARNING: Failed to restart service (may not be running)"
|
||||||
log_update_summary
|
debug_step "ПРЕДУПРЕЖДЕНИЕ: Не удалось перезапустить сервис"
|
||||||
return 1
|
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
SUMMARY_SCRIPT="без изменений"
|
log_message "INFO" "systemctl not found, service not restarted"
|
||||||
SUMMARY_SCRIPT_REASON="sha256 совпадает; версия local=$local_ver remote=$remote_ver"
|
debug_step "systemctl не найден, сервис не перезапущен"
|
||||||
log_action "ssh-monitor: файлы идентичны, копирование не требуется"
|
fi
|
||||||
|
else
|
||||||
|
log_message "INFO" "Checksums match. No update needed."
|
||||||
|
debug_step "Контрольные суммы совпадают, обновление не требуется"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_message "INFO" "No remote script found. No update needed."
|
||||||
|
debug_step "Удаленный скрипт не найден ни по одному из путей"
|
||||||
|
|
||||||
|
# Информация для отладки
|
||||||
|
if [ "$DEBUG" -eq 1 ]; then
|
||||||
|
debug_step "Содержимое директории репозитория:"
|
||||||
|
if [ -d "$UPDATE_DIR/$SCRIPT_NAME" ]; then
|
||||||
|
find "$UPDATE_DIR/$SCRIPT_NAME" -type f -name "$SCRIPT_NAME" 2>/dev/null | while read -r found_file; do
|
||||||
|
log_message "DEBUG" "Найден файл: $found_file"
|
||||||
|
done
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
else
|
|
||||||
SUMMARY_SCRIPT="не найден в клоне"
|
|
||||||
SUMMARY_SCRIPT_REASON="проверьте структуру репозитория после git pull"
|
|
||||||
log_action "ERROR: ssh-monitor не найден в клоне $UPDATE_DIR/$SCRIPT_NAME"
|
|
||||||
if [ "$DEPLOY_MODE" -eq 1 ]; then
|
|
||||||
say_warn "ssh-monitor не найден в клоне $UPDATE_DIR/$SCRIPT_NAME"
|
|
||||||
fi
|
|
||||||
if [ "$DEBUG" -eq 1 ] && [ -d "$UPDATE_DIR/$SCRIPT_NAME" ]; then
|
|
||||||
find "$UPDATE_DIR/$SCRIPT_NAME" -type f -name "$SCRIPT_NAME" 2>/dev/null | while read -r found_file; do
|
|
||||||
log_message "DEBUG" "Найден файл: $found_file"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$DEPLOY_MODE" -eq 1 ]; then
|
|
||||||
script_changed=1
|
|
||||||
elif [ "$script_changed" -eq 1 ] || [ "${SAC_CLIENT_CHANGED:-0}" -eq 1 ] || [ "${CONFIG_CHANGED:-0}" -eq 1 ]; then
|
|
||||||
restart_ssh_monitor_service
|
|
||||||
else
|
|
||||||
log_action "перезапуск сервиса не нужен (ни один файл не изменился)"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Логируем значения переменных скрипта при DEBUG=1
|
||||||
log_script_vars
|
log_script_vars
|
||||||
log_update_summary
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# === Основной блок выполнения ===
|
# === Основной блок выполнения ===
|
||||||
|
|
||||||
parse_command_line "$@"
|
log_message "INFO" "=== Script update started ==="
|
||||||
deploy_require_root
|
|
||||||
|
|
||||||
if [ "$DEPLOY_MODE" -eq 1 ]; then
|
|
||||||
printf '\n=== ssh-monitor: первичная установка (--deploy) ===\n'
|
|
||||||
say_step "Репозиторий и файлы"
|
|
||||||
fi
|
|
||||||
|
|
||||||
_updater_self="$(readlink -f "$0" 2>/dev/null || realpath "$0" 2>/dev/null || printf '%s' "$0")"
|
|
||||||
_updater_sha="$(file_sha256 "$_updater_self")"
|
|
||||||
log_message "INFO" "=== Script update started (deploy=$DEPLOY_MODE) ==="
|
|
||||||
log_action "запуск updater: $_updater_self (sha ${_updater_sha:0:16}…)"
|
|
||||||
log_action "хост=$(hostname 2>/dev/null || echo '?') user=$(id -un 2>/dev/null) uid=$(id -u 2>/dev/null)"
|
|
||||||
log_action "пути: LOCAL=$LOCAL_SCRIPT_PATH UPDATE_DIR=$UPDATE_DIR REPO=$REPO_URL"
|
|
||||||
debug_step "Инициализация скрипта обновления"
|
debug_step "Инициализация скрипта обновления"
|
||||||
|
|
||||||
|
# 1. Гарантируем создание необходимых директорий
|
||||||
if ! ensure_directories; then
|
if ! ensure_directories; then
|
||||||
log_message "INFO" "ERROR: Failed to ensure directories. Exiting."
|
log_message "INFO" "ERROR: Failed to ensure directories. Exiting."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# 2. КРИТИЧЕСКИ ВАЖНО: Проверяем и устанавливаем ipset
|
||||||
debug_step "Начинаем проверку и установку ipset (КРИТИЧЕСКИЙ ШАГ)"
|
debug_step "Начинаем проверку и установку ipset (КРИТИЧЕСКИЙ ШАГ)"
|
||||||
if ! ensure_ipset_installed; then
|
if ! ensure_ipset_installed; then
|
||||||
log_message "ERROR" "CRITICAL: ipset installation or verification failed"
|
log_message "ERROR" "CRITICAL: ipset installation or verification failed"
|
||||||
|
log_message "ERROR" "Работа обновленного скрипта невозможна без установленного ipset"
|
||||||
echo "ERROR: Работа обновленного скрипта невозможна без установленного ipset" >&2
|
echo "ERROR: Работа обновленного скрипта невозможна без установленного ipset" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
update_script "$@"
|
# 3. Выполняем функцию обновления
|
||||||
|
update_script
|
||||||
exit_code=$?
|
exit_code=$?
|
||||||
|
|
||||||
if [ "$exit_code" -eq 0 ] && [ "$DEPLOY_MODE" -eq 1 ]; then
|
|
||||||
deploy_post_install
|
|
||||||
restart_ssh_monitor_service
|
|
||||||
if command -v systemctl &>/dev/null; then
|
|
||||||
if systemctl start ssh-monitor.service 2>/dev/null; then
|
|
||||||
say_ok "systemctl start ssh-monitor.service"
|
|
||||||
else
|
|
||||||
say_warn "systemctl start не выполнен (проверьте --check-config и конфиг)"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ $exit_code -eq 0 ]; then
|
if [ $exit_code -eq 0 ]; then
|
||||||
log_message "INFO" "=== Script update completed successfully ==="
|
log_message "INFO" "=== Script update completed successfully ==="
|
||||||
log_action "завершено успешно (код 0). Итог см. выше или: tail -30 $LOG_FILE"
|
debug_step "Скрипт успешно завершил работу"
|
||||||
if [ "$DEPLOY_MODE" -eq 1 ]; then
|
|
||||||
printf '\n=== Установка завершена. Отредактируйте %s и перезапустите сервис. ===\n' "$CONFIG_FILE"
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
log_message "INFO" "=== Script update completed with errors (exit code: $exit_code) ==="
|
log_message "INFO" "=== Script update completed with errors (exit code: $exit_code) ==="
|
||||||
log_action "завершено с ошибкой (код $exit_code). См. $LOG_FILE"
|
debug_step "Скрипт завершил работу с ошибками"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Выход с кодом завершения
|
||||||
exit $exit_code
|
exit $exit_code
|
||||||
|
|||||||
Reference in New Issue
Block a user