Files
ssh-monitor/update_ssh_monitor.sh
T
PapaTramp 31c4265716 fix: log_action to stderr so captured paths stay valid
find_remote_ssh_monitor_path output was polluted by log_action on stdout,
breaking -f checks; mirror user messages to stderr instead.
2026-05-28 08:24:09 +10:00

934 lines
38 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="—"
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) ===
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() {
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'
}
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_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"
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() {
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
git_before="$(get_git_short_rev "$git_repo")"
log_action "git: pull origin main (было commit $git_before) → $REPO_URL"
debug_step "Выполняем git pull origin main"
if git -C "$SCRIPT_NAME" pull origin main >> "$LOG_FILE" 2>&1; then
git_after="$(get_git_short_rev "$git_repo")"
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
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:-не найден}"
SAC_CLIENT_CHANGED=0
if ! sync_sac_client; then
log_update_summary
return 1
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
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
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