Files
ssh-monitor/update_ssh_monitor.sh
T
2026-06-10 17:41:47 +10:00

1209 lines
48 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# === Конфигурация скрипта ===
UPDATE_DIR="/opt/scripts/update"
SCRIPT_NAME="ssh-monitor"
REPO_URL="https://git.kalinamall.ru/PapaTramp/ssh-monitor.git"
LOCAL_SCRIPT_PATH="/usr/local/bin/ssh-monitor"
LOG_FILE="/var/log/update_script.log"
# Режим отладки: 1 = включён, 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="—"
SUMMARY_WATCHDOG="—"
CONFIG_FILE="/etc/ssh-monitor.conf"
DEPLOY_INSTALL_PATH="/opt/scripts/update_ssh_monitor.sh"
EXPECTED_HEARTBEAT_INTERVAL_MINUTES=240
EXPECTED_WATCHDOG_MAX_HEARTBEAT_AGE=$((EXPECTED_HEARTBEAT_INTERVAL_MINUTES * 60 + 3600))
SAC_SPOOL_DIR_DEFAULT="/var/lib/ssh-monitor/sac-spool"
SYSTEMD_UNIT_PATH="/etc/systemd/system/ssh-monitor.service"
LOCAL_WATCHDOG_PATH="/usr/local/bin/ssh-monitor-watchdog"
WATCHDOG_SERVICE_PATH="/etc/systemd/system/ssh-monitor-watchdog.service"
WATCHDOG_TIMER_PATH="/etc/systemd/system/ssh-monitor-watchdog.timer"
# === Список переменных, объявленных в скрипте (для логирования при DEBUG=1) ===
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 + fallback)."
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
}
set_config_kv() {
local key="$1" val="$2" file="$3"
if grep -qE "^[[:space:]]*${key}=" "$file" 2>/dev/null; then
sed -i -E "s/^[[:space:]]*${key}=.*/${key}=\"${val}\"/" "$file"
else
printf '%s="%s"\n' "$key" "$val" >>"$file"
fi
}
sync_heartbeat_interval_config() {
local file="$CONFIG_FILE"
local current_interval current_watchdog
[ -f "$file" ] || return 1
current_interval=$(config_conf_value "HEARTBEAT_INTERVAL_MINUTES" "$file" 2>/dev/null || true)
current_watchdog=$(config_conf_value "WATCHDOG_MAX_HEARTBEAT_AGE" "$file" 2>/dev/null || true)
if [ "$current_interval" = "$EXPECTED_HEARTBEAT_INTERVAL_MINUTES" ] \
&& [ "$current_watchdog" = "$EXPECTED_WATCHDOG_MAX_HEARTBEAT_AGE" ]; then
return 1
fi
cp "$file" "${file}.bak.$(date +%Y%m%d_%H%M%S)" 2>/dev/null || true
set_config_kv "HEARTBEAT_INTERVAL_MINUTES" "$EXPECTED_HEARTBEAT_INTERVAL_MINUTES" "$file"
set_config_kv "WATCHDOG_MAX_HEARTBEAT_AGE" "$EXPECTED_WATCHDOG_MAX_HEARTBEAT_AGE" "$file"
log_message "INFO" "Config $file: HEARTBEAT_INTERVAL_MINUTES=$EXPECTED_HEARTBEAT_INTERVAL_MINUTES, WATCHDOG_MAX_HEARTBEAT_AGE=$EXPECTED_WATCHDOG_MAX_HEARTBEAT_AGE (deploy policy on script upgrade)."
if [ "$DEPLOY_MODE" -eq 1 ]; then
say_info "Heartbeat: ${EXPECTED_HEARTBEAT_INTERVAL_MINUTES} мин, watchdog ${EXPECTED_WATCHDOG_MAX_HEARTBEAT_AGE} с."
fi
CONFIG_CHANGED=1
return 0
}
sync_use_sac_fallback_mode() {
local file="$CONFIG_FILE"
[ -f "$file" ] || return 1
if grep -qE '^[[:space:]]*UseSAC=[[:space:]]*["'"'"']?fallback["'"'"']?[[:space:]]*(#.*)?$' "$file" 2>/dev/null; then
return 1
fi
if ! grep -qE '^[[:space:]]*UseSAC=' "$file" 2>/dev/null; then
return 1
fi
cp "$file" "${file}.bak.$(date +%Y%m%d_%H%M%S)" 2>/dev/null || true
sed -i -E 's/^[[:space:]]*UseSAC=.*/UseSAC="fallback"/' "$file"
log_message "INFO" "Config $file: UseSAC set to fallback (deploy policy on script upgrade)."
if [ "$DEPLOY_MODE" -eq 1 ]; then
say_info "UseSAC=fallback (рабочий режим SAC + локальный fallback)."
fi
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() {
local level="$1"
local message="$2"
local timestamp
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
# Создаём лог-файл и директорию при необходимости
if [ ! -f "$LOG_FILE" ]; then
mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null
touch "$LOG_FILE" 2>/dev/null
fi
if [ "$level" = "INFO" ]; then
echo "$timestamp INFO: $message" >> "$LOG_FILE"
elif [ "$level" = "DEBUG" ] && [ "$DEBUG" -eq 1 ]; then
echo "$timestamp DEBUG: $message" >> "$LOG_FILE"
elif [ "$level" = "ERROR" ]; then
echo "$timestamp ERROR: $message" >> "$LOG_FILE"
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'
}
# Remote kalinamall или origin с URL git.kalinamall.ru (не GitHub).
resolve_git_remote_name() {
local repo="$1" name url
if git -C "$repo" remote get-url kalinamall &>/dev/null; then
printf '%s' kalinamall
return 0
fi
while read -r name; do
[ -n "$name" ] || continue
url=$(git -C "$repo" remote get-url "$name" 2>/dev/null || true)
case "$url" in
*git.kalinamall.ru*) printf '%s' "$name"; return 0 ;;
esac
done < <(git -C "$repo" remote 2>/dev/null)
if git -C "$repo" remote get-url origin &>/dev/null; then
printf '%s' origin
return 0
fi
git -C "$repo" remote add kalinamall "$REPO_URL" >> "$LOG_FILE" 2>&1 \
|| git -C "$repo" remote set-url kalinamall "$REPO_URL" >> "$LOG_FILE" 2>&1
printf '%s' kalinamall
}
# fetch + ff-only pull; при расхождении истории (force-push) — reset --hard.
sync_git_repository() {
local repo="$1" branch="${2:-main}"
local remote upstream git_before git_after
remote="$(resolve_git_remote_name "$repo")"
upstream="${remote}/${branch}"
if [ -f "$repo/.git/MERGE_HEAD" ]; then
log_action "git: незавершённый merge — abort"
git -C "$repo" merge --abort >> "$LOG_FILE" 2>&1 || true
fi
git_before="$(get_git_short_rev "$repo")"
log_action "git: fetch --prune $remote $branch (было $git_before) → $REPO_URL"
if ! git -C "$repo" fetch --prune "$remote" "$branch" >> "$LOG_FILE" 2>&1; then
SUMMARY_GIT="fetch FAILED ($git_before)"
log_action "ERROR: git fetch failed (см. $LOG_FILE)"
return 1
fi
if git -C "$repo" pull --ff-only "$remote" "$branch" >> "$LOG_FILE" 2>&1; then
git_after="$(get_git_short_rev "$repo")"
SUMMARY_GIT="pull ok, $git_before$git_after"
log_action "git: fast-forward OK ($git_before$git_after)"
return 0
fi
log_action "WARN: fast-forward невозможен (часто после force-push) — reset --hard $upstream"
if ! git -C "$repo" reset --hard "$upstream" >> "$LOG_FILE" 2>&1; then
SUMMARY_GIT="reset FAILED ($git_before)"
log_action "ERROR: git reset --hard $upstream failed (см. $LOG_FILE)"
return 1
fi
git_after="$(get_git_short_rev "$repo")"
SUMMARY_GIT="reset ok, $git_before$git_after"
log_action "git reset --hard: $git_before$git_after"
return 0
}
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"
}
# Версия SAC-модуля совпадает с SSH_MONITOR_VERSION основного скрипта в том же каталоге.
read_sac_client_version() {
local sac_file="$1"
local dir="${sac_file%/*}"
read_ssh_monitor_version "$dir/ssh-monitor"
}
warn_version_mismatch() {
local repo_ssh="$1"
local repo_ver vt
repo_ver="$(read_ssh_monitor_version "$repo_ssh")"
[ -f "$repo_ssh" ] || return 0
if [ -f "${repo_ssh%/*}/version.txt" ]; then
vt="$(tr -d '\r\n' < "${repo_ssh%/*}/version.txt")"
if [ -n "$repo_ver" ] && [ -n "$vt" ] && [ "$repo_ver" != "$vt" ]; then
log_action "WARN: SSH_MONITOR_VERSION=$repo_ver != version.txt=$vt"
fi
fi
}
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 "watchdog: $SUMMARY_WATCHDOG"
log_action "перезапуск ssh-monitor.service: $SUMMARY_SERVICE_RESTART"
log_action "полный лог: $LOG_FILE"
log_action "==========================="
}
# Функция для логирования шагов с комментариями (работает только при DEBUG=1)
debug_step() {
if [ "$DEBUG" -eq 1 ]; then
log_message "DEBUG" "# $1"
fi
}
# Функция для логирования только объявленных в скрипте переменных
log_script_vars() {
if [ "$DEBUG" -eq 1 ]; then
log_message "DEBUG" "=== Значения переменных скрипта ==="
for var_name in "${SCRIPT_VARS[@]}"; do
local var_value="${!var_name}"
log_message "DEBUG" "$var_name='$var_value'"
done
log_message "DEBUG" "=== Конец списка переменных ==="
fi
}
# === КРИТИЧЕСКАЯ ФУНКЦИЯ: Проверка и установка ipset ===
ensure_ipset_installed() {
debug_step "Проверяем наличие ipset в системе"
# Проверяем, установлен ли ipset
if ! command -v ipset &>/dev/null; then
log_message "INFO" "ipset not found. Attempting silent installation..."
debug_step "ipset не обнаружен, начинаем установку в silent режиме"
# Определяем пакетный менеджер и устанавливаем ipset
if command -v apt-get &>/dev/null || command -v apt &>/dev/null; then
debug_step "Обнаружен Debian/Ubuntu, используем apt"
log_message "INFO" "Detected apt package manager, installing ipset..."
# Тихая установка через apt
if apt-get update -qq >/dev/null 2>&1 && apt-get install -y -qq ipset >/dev/null 2>&1; then
log_message "INFO" "ipset installed successfully via apt"
debug_step "ipset успешно установлен через apt"
else
log_message "ERROR" "Failed to install ipset via apt"
log_message "ERROR" "Работа обновленного скрипта невозможна без установленного ipset"
debug_step "КРИТИЧЕСКАЯ ОШИБКА: Не удалось установить ipset через apt"
return 1
fi
elif command -v yum &>/dev/null; then
debug_step "Обнаружен RHEL/CentOS, используем yum"
log_message "INFO" "Detected yum package manager, installing ipset..."
if yum install -y -q ipset >/dev/null 2>&1; then
log_message "INFO" "ipset installed successfully via yum"
debug_step "ipset успешно установлен через yum"
else
log_message "ERROR" "Failed to install ipset via yum"
log_message "ERROR" "Работа обновленного скрипта невозможна без установленного ipset"
debug_step "КРИТИЧЕСКАЯ ОШИБКА: Не удалось установить ipset через yum"
return 1
fi
elif command -v dnf &>/dev/null; then
debug_step "Обнаружен Fedora/RHEL 8+, используем dnf"
log_message "INFO" "Detected dnf package manager, installing ipset..."
if dnf install -y -q ipset >/dev/null 2>&1; then
log_message "INFO" "ipset installed successfully via dnf"
debug_step "ipset успешно установлен через dnf"
else
log_message "ERROR" "Failed to install ipset via dnf"
log_message "ERROR" "Работа обновленного скрипта невозможна без установленного ipset"
debug_step "КРИТИЧЕСКАЯ ОШИБКА: Не удалось установить ipset через dnf"
return 1
fi
elif command -v pacman &>/dev/null; then
debug_step "Обнаружен Arch Linux, используем pacman"
log_message "INFO" "Detected pacman package manager, installing ipset..."
if pacman -S --noconfirm --quiet ipset >/dev/null 2>&1; then
log_message "INFO" "ipset installed successfully via pacman"
debug_step "ipset успешно установлен через pacman"
else
log_message "ERROR" "Failed to install ipset via pacman"
log_message "ERROR" "Работа обновленного скрипта невозможна без установленного ipset"
debug_step "КРИТИЧЕСКАЯ ОШИБКА: Не удалось установить ipset через pacman"
return 1
fi
elif command -v zypper &>/dev/null; then
debug_step "Обнаружен openSUSE, используем zypper"
log_message "INFO" "Detected zypper package manager, installing ipset..."
if zypper --quiet --non-interactive install ipset >/dev/null 2>&1; then
log_message "INFO" "ipset installed successfully via zypper"
debug_step "ipset успешно установлен через zypper"
else
log_message "ERROR" "Failed to install ipset via zypper"
log_message "ERROR" "Работа обновленного скрипта невозможна без установленного ipset"
debug_step "КРИТИЧЕСКАЯ ОШИБКА: Не удалось установить ipset через zypper"
return 1
fi
else
log_message "ERROR" "Unsupported package manager. Cannot install ipset"
log_message "ERROR" "Работа обновленного скрипта невозможна без установленного ipset"
debug_step "КРИТИЧЕСКАЯ ОШИБКА: Пакетный менеджер не распознан"
return 1
fi
# Проверяем, что ipset действительно установился
if ! command -v ipset &>/dev/null; then
log_message "ERROR" "ipset installation verification failed"
log_message "ERROR" "Работа обновленного скрипта невозможна без установленного ipset"
debug_step "КРИТИЧЕСКАЯ ОШИБКА: ipset не обнаружен после установки"
return 1
fi
else
debug_step "ipset уже установлен в системе"
log_message "INFO" "ipset is already installed"
# Проверяем версию ipset (информационно)
if [ "$DEBUG" -eq 1 ]; then
local ipset_version=$(ipset --version 2>&1)
log_message "DEBUG" "ipset version: $ipset_version"
fi
fi
log_message "INFO" "ipset check/installation completed successfully"
return 0
}
# === Гарантированное создание директорий ===
ensure_directories() {
debug_step "Проверяем и создаём необходимые директории"
# Создаём UPDATE_DIR если не существует
if [ ! -d "$UPDATE_DIR" ]; then
log_message "INFO" "Creating directory: $UPDATE_DIR"
debug_step "Директория $UPDATE_DIR не существует, создаём"
if mkdir -p "$UPDATE_DIR" 2>/dev/null; then
log_message "INFO" "Directory created successfully"
else
log_message "INFO" "ERROR: Cannot create directory $UPDATE_DIR"
return 1
fi
else
debug_step "Директория $UPDATE_DIR уже существует"
fi
# Проверяем права на запись
if [ ! -w "$UPDATE_DIR" ]; then
log_message "INFO" "WARNING: Directory $UPDATE_DIR is not writable"
debug_step "ПРЕДУПРЕЖДЕНИЕ: Нет прав на запись в $UPDATE_DIR"
return 1
fi
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"
local remote_ver local_ver
remote_ver="$(read_sac_client_version "$remote")"
local_ver="$(read_sac_client_version "$local_dst")"
log_action "sac-client: remote=$remote ver=${remote_ver:-?} local=$local_dst ver=${local_ver:-?}"
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 совпадает; ver=${remote_ver:-?} (${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="ver ${local_ver:-?}${remote_ver:-?}, 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"
elif [ ! -f "$example" ]; then
say_warn "Unit example не найден: $example"
else
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
fi
ensure_watchdog_installed || true
}
find_watchdog_remote() {
local p="$UPDATE_DIR/$SCRIPT_NAME/ssh-monitor-watchdog"
if [ -f "$p" ]; then
printf '%s\n' "$p"
return 0
fi
return 1
}
sync_watchdog_script() {
WATCHDOG_CHANGED=0
local remote local_sum remote_sum
remote=$(find_watchdog_remote) || {
SUMMARY_WATCHDOG="скрипт не найден в клоне"
log_action "WARNING: ssh-monitor-watchdog не найден в клоне git"
return 0
}
remote_sum=$(file_sha256 "$remote")
if [ -z "$remote_sum" ]; then
SUMMARY_WATCHDOG="ошибка sha256 remote"
return 1
fi
if [ -f "$LOCAL_WATCHDOG_PATH" ]; then
local_sum=$(file_sha256 "$LOCAL_WATCHDOG_PATH")
if [ "$local_sum" = "$remote_sum" ]; then
log_action "ssh-monitor-watchdog: checksum совпадает, копирование не требуется"
return 0
fi
log_action "ssh-monitor-watchdog: обновление (sha ${local_sum:0:16}… → ${remote_sum:0:16}…)"
else
log_action "ssh-monitor-watchdog: локальный файл отсутствует, будет установлен"
fi
if ! cp "$remote" "$LOCAL_WATCHDOG_PATH"; then
SUMMARY_WATCHDOG="ошибка копирования"
log_action "ERROR: не удалось скопировать ssh-monitor-watchdog → $LOCAL_WATCHDOG_PATH"
return 1
fi
chmod 750 "$LOCAL_WATCHDOG_PATH"
WATCHDOG_CHANGED=1
SUMMARY_WATCHDOG="скрипт обновлён"
if [ "$DEPLOY_MODE" -eq 1 ]; then
say_ok "ssh-monitor-watchdog → $LOCAL_WATCHDOG_PATH"
fi
return 0
}
install_watchdog_systemd_units() {
local svc_example timer_example units_changed=0
svc_example="$UPDATE_DIR/$SCRIPT_NAME/ssh-monitor-watchdog.service.example"
timer_example="$UPDATE_DIR/$SCRIPT_NAME/ssh-monitor-watchdog.timer.example"
if [ ! -f "$svc_example" ] || [ ! -f "$timer_example" ]; then
log_action "WARNING: watchdog unit examples не найдены в клоне"
return 0
fi
if [ ! -f "$WATCHDOG_SERVICE_PATH" ] \
|| ! cmp -s "$svc_example" "$WATCHDOG_SERVICE_PATH" 2>/dev/null; then
cp "$svc_example" "$WATCHDOG_SERVICE_PATH"
units_changed=1
log_action "watchdog: установлен/обновлён $WATCHDOG_SERVICE_PATH"
fi
if [ ! -f "$WATCHDOG_TIMER_PATH" ] \
|| ! cmp -s "$timer_example" "$WATCHDOG_TIMER_PATH" 2>/dev/null; then
cp "$timer_example" "$WATCHDOG_TIMER_PATH"
units_changed=1
log_action "watchdog: установлен/обновлён $WATCHDOG_TIMER_PATH"
fi
if [ "$units_changed" -eq 1 ]; then
WATCHDOG_CHANGED=1
fi
if ! command -v systemctl &>/dev/null; then
log_action "watchdog: systemctl не найден, units только скопированы"
return 0
fi
systemctl daemon-reload 2>/dev/null || true
if systemctl enable ssh-monitor-watchdog.timer 2>/dev/null; then
log_action "watchdog: enable ssh-monitor-watchdog.timer"
fi
if systemctl start ssh-monitor-watchdog.timer 2>/dev/null; then
log_action "watchdog: start ssh-monitor-watchdog.timer"
if [ -z "$SUMMARY_WATCHDOG" ] || [ "$SUMMARY_WATCHDOG" = "—" ]; then
SUMMARY_WATCHDOG="timer активен"
elif [ "${WATCHDOG_CHANGED:-0}" -eq 1 ]; then
SUMMARY_WATCHDOG="${SUMMARY_WATCHDOG}; timer активен"
fi
else
log_action "WARNING: systemctl start ssh-monitor-watchdog.timer не выполнен"
fi
if [ "$DEPLOY_MODE" -eq 1 ] && [ "${WATCHDOG_CHANGED:-0}" -eq 1 ]; then
say_ok "watchdog: units + timer (ssh-monitor-watchdog.timer)"
fi
return 0
}
ensure_watchdog_installed() {
sync_watchdog_script || return 1
install_watchdog_systemd_units || return 1
if [ -z "$SUMMARY_WATCHDOG" ] || [ "$SUMMARY_WATCHDOG" = "—" ]; then
if [ -f "$LOCAL_WATCHDOG_PATH" ] && systemctl is-enabled ssh-monitor-watchdog.timer &>/dev/null; then
SUMMARY_WATCHDOG="без изменений (timer enabled)"
elif [ -f "$LOCAL_WATCHDOG_PATH" ]; then
SUMMARY_WATCHDOG="скрипт на месте, timer настроен"
else
SUMMARY_WATCHDOG="не установлен"
fi
fi
return 0
}
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
systemctl status ssh-monitor-watchdog.timer
systemctl list-timers | grep ssh-monitor-watchdog
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() {
local git_repo="$UPDATE_DIR/$SCRIPT_NAME"
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 "Гарантируем наличие директории для работы"
if ! ensure_directories; then
SUMMARY_SCRIPT="ошибка"
SUMMARY_SCRIPT_REASON="нет каталога $UPDATE_DIR или нет прав на запись"
log_update_summary
return 1
fi
debug_step "Переходим в директорию для клона репозитория: $UPDATE_DIR"
if ! cd "$UPDATE_DIR" 2>/dev/null; then
SUMMARY_SCRIPT="ошибка"
SUMMARY_SCRIPT_REASON="cannot cd $UPDATE_DIR"
log_action "ERROR: cannot cd $UPDATE_DIR"
log_update_summary
return 1
fi
debug_step "Проверяем наличие репозитория и обновляем/клонируем"
if [ -d "$SCRIPT_NAME" ]; then
if sync_git_repository "$git_repo" main; then
pull_ok=1
else
pull_ok=0
log_action "WARNING: git sync не удался — remote в клоне может быть устаревшим"
fi
else
log_action "git: clone $REPO_URL$UPDATE_DIR/$SCRIPT_NAME"
debug_step "Выполняем git clone $REPO_URL $SCRIPT_NAME"
if ! git clone "$REPO_URL" "$SCRIPT_NAME" >> "$LOG_FILE" 2>&1; then
SUMMARY_GIT="clone FAILED"
SUMMARY_SCRIPT="ошибка"
SUMMARY_SCRIPT_REASON="git clone failed"
log_action "ERROR: git clone failed (см. $LOG_FILE)"
log_update_summary
return 1
fi
git_after="$(get_git_short_rev "$git_repo")"
SUMMARY_GIT="clone ok, commit $git_after"
log_action "git clone: успех (commit $git_after)"
fi
sync_installed_updater_from_clone "$@"
actual_script_path="$(find_remote_ssh_monitor_path 2>/dev/null || true)"
actual_script_path="${actual_script_path//$'\n'/}"
actual_script_path="${actual_script_path//$'\r'/}"
debug_step "Путь к ssh-monitor в клоне: ${actual_script_path:-не найден}"
if [ -n "$actual_script_path" ] && [ -f "$actual_script_path" ]; then
warn_version_mismatch "$actual_script_path"
fi
SAC_CLIENT_CHANGED=0
if ! sync_sac_client; then
log_update_summary
return 1
fi
WATCHDOG_CHANGED=0
if ! ensure_watchdog_installed; then
log_action "WARNING: установка watchdog завершилась с ошибкой"
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
if [ -n "$actual_script_path" ] && [ -f "$actual_script_path" ]; then
if [ "$DEPLOY_MODE" -eq 1 ]; then
log_action "режим --deploy: установка ssh-monitor → $LOCAL_SCRIPT_PATH"
if install_ssh_monitor_from "$actual_script_path"; then
script_changed=1
SUMMARY_SCRIPT="установлен (--deploy)"
SUMMARY_SCRIPT_REASON="$(read_ssh_monitor_version "$LOCAL_SCRIPT_PATH")"
else
SUMMARY_SCRIPT="ошибка установки"
log_update_summary
return 1
fi
else
if [ ! -f "$LOCAL_SCRIPT_PATH" ]; then
LOCAL_CHECKSUM=""
local_ver="(нет файла)"
else
LOCAL_CHECKSUM=$(file_sha256 "$LOCAL_SCRIPT_PATH")
local_ver="$(read_ssh_monitor_version "$LOCAL_SCRIPT_PATH")"
fi
REMOTE_CHECKSUM=$(file_sha256 "$actual_script_path")
remote_ver="$(read_ssh_monitor_version "$actual_script_path")"
log_action "сравнение: local=$LOCAL_SCRIPT_PATH ver=$local_ver sha=${LOCAL_CHECKSUM:0:16}"
log_action "сравнение: remote=$actual_script_path ver=$remote_ver sha=${REMOTE_CHECKSUM:0:16}"
if [ "$pull_ok" -eq 0 ]; then
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
SUMMARY_SCRIPT="ошибка копирования"
log_update_summary
return 1
fi
else
SUMMARY_SCRIPT="без изменений"
SUMMARY_SCRIPT_REASON="sha256 совпадает; версия local=$local_ver remote=$remote_ver"
log_action "ssh-monitor: файлы идентичны, копирование не требуется"
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
fi
if [ "$script_changed" -eq 1 ]; then
sync_use_sac_fallback_mode || true
sync_heartbeat_interval_config || true
fi
if [ "$script_changed" -eq 1 ] || [ "${SAC_CLIENT_CHANGED:-0}" -eq 1 ] || [ "${CONFIG_CHANGED:-0}" -eq 1 ]; then
restart_ssh_monitor_service
else
log_action "перезапуск сервиса не нужен (ни один файл не изменился)"
fi
log_script_vars
log_update_summary
return 0
}
# === Основной блок выполнения ===
parse_command_line "$@"
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 "Инициализация скрипта обновления"
if ! ensure_directories; then
log_message "INFO" "ERROR: Failed to ensure directories. Exiting."
exit 1
fi
debug_step "Начинаем проверку и установку ipset (КРИТИЧЕСКИЙ ШАГ)"
if ! ensure_ipset_installed; then
log_message "ERROR" "CRITICAL: ipset installation or verification failed"
echo "ERROR: Работа обновленного скрипта невозможна без установленного ipset" >&2
exit 1
fi
update_script "$@"
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
log_message "INFO" "=== Script update completed successfully ==="
log_action "завершено успешно (код 0). Итог см. выше или: tail -30 $LOG_FILE"
if [ "$DEPLOY_MODE" -eq 1 ]; then
printf '\n=== Установка завершена. Отредактируйте %s и перезапустите сервис. ===\n' "$CONFIG_FILE"
fi
else
log_message "INFO" "=== Script update completed with errors (exit code: $exit_code) ==="
log_action "завершено с ошибкой (код $exit_code). См. $LOG_FILE"
fi
exit $exit_code