2ff60280ef
Always sync kalinamall remote from REPO_URL; support GIT_BRANCH env for SAC-driven updates.
1252 lines
50 KiB
Bash
Executable File
1252 lines
50 KiB
Bash
Executable File
#!/bin/bash
|
||
|
||
# === Конфигурация скрипта ===
|
||
UPDATE_DIR="/opt/scripts/update"
|
||
SCRIPT_NAME="ssh-monitor"
|
||
REPO_URL="${REPO_URL:-https://git.kalinamall.ru/PapaTramp/ssh-monitor.git}"
|
||
GIT_BRANCH="${GIT_BRANCH:-main}"
|
||
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" "GIT_BRANCH" "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
|
||
}
|
||
|
||
config_key_exists() {
|
||
local key="$1" file="$2"
|
||
grep -qE "^[[:space:]]*${key}=" "$file" 2>/dev/null
|
||
}
|
||
|
||
append_heartbeat_interval_block() {
|
||
local file="$1"
|
||
{
|
||
printf '\n# --- SAC agent.heartbeat (интервал в минутах; %s = 4 ч) ---\n' "$EXPECTED_HEARTBEAT_INTERVAL_MINUTES"
|
||
printf 'HEARTBEAT_INTERVAL_MINUTES="%s"\n' "$EXPECTED_HEARTBEAT_INTERVAL_MINUTES"
|
||
printf '# Порог watchdog: интервал + 1 ч запас (%s с)\n' "$EXPECTED_WATCHDOG_MAX_HEARTBEAT_AGE"
|
||
printf 'WATCHDOG_MAX_HEARTBEAT_AGE="%s"\n' "$EXPECTED_WATCHDOG_MAX_HEARTBEAT_AGE"
|
||
} >>"$file"
|
||
}
|
||
|
||
sync_heartbeat_interval_config() {
|
||
local file="$CONFIG_FILE"
|
||
local current_interval current_watchdog
|
||
local changed=0
|
||
local backed_up=0
|
||
|
||
[ -f "$file" ] || return 1
|
||
|
||
backup_config_once() {
|
||
if [ "$backed_up" -eq 0 ]; then
|
||
cp "$file" "${file}.bak.$(date +%Y%m%d_%H%M%S)" 2>/dev/null || true
|
||
backed_up=1
|
||
fi
|
||
}
|
||
|
||
if ! config_key_exists "HEARTBEAT_INTERVAL_MINUTES" "$file"; then
|
||
backup_config_once
|
||
if ! config_key_exists "WATCHDOG_MAX_HEARTBEAT_AGE" "$file"; then
|
||
append_heartbeat_interval_block "$file"
|
||
log_message "INFO" "Config $file: добавлены HEARTBEAT_INTERVAL_MINUTES=$EXPECTED_HEARTBEAT_INTERVAL_MINUTES и WATCHDOG_MAX_HEARTBEAT_AGE=$EXPECTED_WATCHDOG_MAX_HEARTBEAT_AGE (отсутствовали в конфиге)."
|
||
else
|
||
set_config_kv "HEARTBEAT_INTERVAL_MINUTES" "$EXPECTED_HEARTBEAT_INTERVAL_MINUTES" "$file"
|
||
log_message "INFO" "Config $file: добавлен HEARTBEAT_INTERVAL_MINUTES=$EXPECTED_HEARTBEAT_INTERVAL_MINUTES (отсутствовал в конфиге)."
|
||
fi
|
||
changed=1
|
||
else
|
||
current_interval=$(config_conf_value "HEARTBEAT_INTERVAL_MINUTES" "$file" 2>/dev/null || true)
|
||
if [ "$current_interval" != "$EXPECTED_HEARTBEAT_INTERVAL_MINUTES" ]; then
|
||
backup_config_once
|
||
set_config_kv "HEARTBEAT_INTERVAL_MINUTES" "$EXPECTED_HEARTBEAT_INTERVAL_MINUTES" "$file"
|
||
log_message "INFO" "Config $file: HEARTBEAT_INTERVAL_MINUTES ${current_interval:-?} → $EXPECTED_HEARTBEAT_INTERVAL_MINUTES (deploy policy)."
|
||
changed=1
|
||
fi
|
||
fi
|
||
|
||
if ! config_key_exists "WATCHDOG_MAX_HEARTBEAT_AGE" "$file"; then
|
||
backup_config_once
|
||
set_config_kv "WATCHDOG_MAX_HEARTBEAT_AGE" "$EXPECTED_WATCHDOG_MAX_HEARTBEAT_AGE" "$file"
|
||
log_message "INFO" "Config $file: добавлен WATCHDOG_MAX_HEARTBEAT_AGE=$EXPECTED_WATCHDOG_MAX_HEARTBEAT_AGE (отсутствовал в конфиге)."
|
||
changed=1
|
||
else
|
||
current_watchdog=$(config_conf_value "WATCHDOG_MAX_HEARTBEAT_AGE" "$file" 2>/dev/null || true)
|
||
if [ "$current_watchdog" != "$EXPECTED_WATCHDOG_MAX_HEARTBEAT_AGE" ]; then
|
||
backup_config_once
|
||
set_config_kv "WATCHDOG_MAX_HEARTBEAT_AGE" "$EXPECTED_WATCHDOG_MAX_HEARTBEAT_AGE" "$file"
|
||
log_message "INFO" "Config $file: WATCHDOG_MAX_HEARTBEAT_AGE ${current_watchdog:-?} → $EXPECTED_WATCHDOG_MAX_HEARTBEAT_AGE (deploy policy)."
|
||
changed=1
|
||
fi
|
||
fi
|
||
|
||
if [ "$changed" -eq 0 ]; then
|
||
return 1
|
||
fi
|
||
|
||
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 для fetch/pull: всегда kalinamall с URL из REPO_URL (SAC передаёт REPO_URL при SSH-обновлении).
|
||
resolve_git_remote_name() {
|
||
local repo="$1"
|
||
local name=kalinamall
|
||
if git -C "$repo" remote get-url "$name" &>/dev/null; then
|
||
git -C "$repo" remote set-url "$name" "$REPO_URL" >> "$LOG_FILE" 2>&1
|
||
else
|
||
git -C "$repo" remote add "$name" "$REPO_URL" >> "$LOG_FILE" 2>&1 \
|
||
|| git -C "$repo" remote set-url "$name" "$REPO_URL" >> "$LOG_FILE" 2>&1
|
||
fi
|
||
printf '%s' "$name"
|
||
}
|
||
|
||
# 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" "$GIT_BRANCH"; 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 -b "$GIT_BRANCH" "$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
|
||
fi
|
||
# Выравнивать интервал heartbeat при каждом deploy, даже если бинарник не менялся.
|
||
sync_heartbeat_interval_config || true
|
||
|
||
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
|