731c50b0bc
Restore ssh-monitor from HEAD when missing on disk; try git ls-files and find fallback; log clone root listing on failure.
932 lines
38 KiB
Bash
Executable File
932 lines
38 KiB
Bash
Executable File
#!/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
|
||
}
|
||
|
||
# Всегда в лог; в stdout — если не --quiet (удобно при ручном запуске и cron с mail)
|
||
log_action() {
|
||
log_message "INFO" "$1"
|
||
if [ "$QUIET" -eq 0 ]; then
|
||
printf '[update_ssh_monitor] %s\n' "$1"
|
||
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)"
|
||
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
|