diff --git a/README.md b/README.md index 836d4a0..a81ec39 100644 --- a/README.md +++ b/README.md @@ -165,12 +165,24 @@ make dist ## Установка скрипта, конфигурации и systemd +### Автоматическая установка (рекомендуется) + +```bash +sudo ./first_deploy.sh +# или: sudo ./update_ssh_monitor.sh --deploy +``` + +См. [docs/auto-update.ru.md](docs/auto-update.ru.md) — клон из git, копирование в `/usr/local/bin/`, `sac-client.sh`, пример `/etc/ssh-monitor.conf`, unit systemd. + +### Ручная установка + 1. Скопируйте скрипт в постоянное место: - `sudo install -m 750 ./ssh-monitor /usr/local/bin/ssh-monitor` + - `sudo install -m 644 ./sac-client.sh /usr/local/bin/sac-client.sh` 2. Убедитесь, что конфиг создан (см. раздел **«Конфигурация»** выше): - `sudo cp ./ssh-monitor.conf.example /etc/ssh-monitor.conf` - `sudo chmod 600 /etc/ssh-monitor.conf` -3. Создайте unit-файл `/etc/systemd/system/ssh-monitor.service`: +3. Создайте unit-файл `/etc/systemd/system/ssh-monitor.service` (или `sudo cp ssh-monitor.service.example ...`): ```ini [Unit] diff --git a/docs/auto-update.ru.md b/docs/auto-update.ru.md index ee1714d..b365e10 100644 --- a/docs/auto-update.ru.md +++ b/docs/auto-update.ru.md @@ -52,7 +52,29 @@ flowchart LR - **git** и сетевой доступ к репозиторию (SSH-ключ или HTTPS); - ветка в скрипте жёстко **`main`** — при другой ветке измените `git pull` в файле. -## Установка +## Первичная установка (--deploy) + +Один сценарий для нового сервера (клон git, `ssh-monitor`, `sac-client.sh`, пример конфига, systemd unit): + +```bash +cd /path/to/ssh-monitor # или curl/clone репозитория +sudo ./first_deploy.sh +# то же самое: +sudo ./update_ssh_monitor.sh --deploy +``` + +Скрипт выводит в консоль, **куда что скопировано**, и список переменных для **`/etc/ssh-monitor.conf`**. Существующий конфиг **не перезаписывается**. + +После установки отредактируйте конфиг и проверьте: + +```bash +sudo nano /etc/ssh-monitor.conf +sudo /usr/local/bin/ssh-monitor --check-config +sudo /usr/local/bin/ssh-monitor --check-sac +sudo systemctl restart ssh-monitor.service +``` + +## Установка updater (для последующих обновлений) ```bash sudo mkdir -p /opt/scripts diff --git a/first_deploy.sh b/first_deploy.sh new file mode 100755 index 0000000..b2671c5 --- /dev/null +++ b/first_deploy.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# Первичная установка ssh-monitor — обёртка над update_ssh_monitor.sh --deploy +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +exec "$SCRIPT_DIR/update_ssh_monitor.sh" --deploy "$@" diff --git a/ssh-monitor.service.example b/ssh-monitor.service.example new file mode 100644 index 0000000..796856b --- /dev/null +++ b/ssh-monitor.service.example @@ -0,0 +1,14 @@ +[Unit] +Description=SSH Monitor with Telegram alerts and SAC integration +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/ssh-monitor +Restart=always +RestartSec=5 +User=root + +[Install] +WantedBy=multi-user.target diff --git a/update_ssh_monitor.sh b/update_ssh_monitor.sh old mode 100644 new mode 100755 index 38f53a8..71899cd --- a/update_ssh_monitor.sh +++ b/update_ssh_monitor.sh @@ -9,12 +9,89 @@ LOG_FILE="/var/log/update_script.log" # Режим отладки: 1 = включён, 0 = выключен DEBUG=0 +DEPLOY_MODE=0 + +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 + -h, --help эта справка + +После --deploy отредактируйте /etc/ssh-monitor.conf (Telegram и/или SAC). +EOF +} + +parse_command_line() { + DEPLOY_MODE=0 + while [ $# -gt 0 ]; do + case "$1" in + --deploy) + DEPLOY_MODE=1 + shift + ;; + --debug) + DEBUG=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" @@ -228,14 +305,14 @@ sync_sac_client() { return 1 fi - if [ -f "$local_dst" ]; then + if [ "$DEPLOY_MODE" -ne 1 ] && [ -f "$local_dst" ]; then local_sum=$(sha256sum "$local_dst" 2>/dev/null | awk '{print $1}') if [ "$local_sum" = "$remote_sum" ]; then log_message "INFO" "sac-client.sh checksums match. No update needed." debug_step "sac-client.sh без изменений" return 0 fi - else + elif [ ! -f "$local_dst" ]; then log_message "INFO" "Local sac-client.sh not found. Will install." debug_step "Локальный sac-client.sh отсутствует, будет установлен" fi @@ -248,9 +325,139 @@ sync_sac_client() { chmod 644 "$local_dst" SAC_CLIENT_CHANGED=1 log_message "INFO" "sac-client.sh updated." + if [ "$DEPLOY_MODE" -eq 1 ]; then + say_ok "sac-client.sh → $local_dst" + fi return 0 } +find_remote_ssh_monitor_path() { + REMOTE_SCRIPT_PATH="$UPDATE_DIR/$SCRIPT_NAME/src/branch/main/$SCRIPT_NAME" + ALT_REMOTE_PATH="$UPDATE_DIR/$SCRIPT_NAME/$SCRIPT_NAME" + if [ -f "$REMOTE_SCRIPT_PATH" ]; then + printf '%s\n' "$REMOTE_SCRIPT_PATH" + return 0 + fi + if [ -f "$ALT_REMOTE_PATH" ]; then + log_message "INFO" "Script found at alternative path" + printf '%s\n' "$ALT_REMOTE_PATH" + return 0 + fi + return 1 +} + +install_ssh_monitor_from() { + local actual_script_path="$1" + if [ -f "$LOCAL_SCRIPT_PATH" ]; then + cp "$LOCAL_SCRIPT_PATH" "${LOCAL_SCRIPT_PATH}.backup.$(date +%Y%m%d_%H%M%S)" 2>/dev/null || true + fi + if ! cp "$actual_script_path" "$LOCAL_SCRIPT_PATH"; then + log_message "INFO" "ERROR: Failed to copy script" + return 1 + fi + chmod +x "$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() { + local example="$UPDATE_DIR/$SCRIPT_NAME/ssh-monitor.conf.example" + say_step "Конфигурация" + if [ -f "$CONFIG_FILE" ]; then + say_info "Уже существует: $CONFIG_FILE (не перезаписываем)" + return 0 + fi + if [ ! -f "$example" ]; then + say_warn "Пример конфига не найден в клоне: $example" + return 0 + fi + cp "$example" "$CONFIG_FILE" + chmod 600 "$CONFIG_FILE" + say_ok "Создан $CONFIG_FILE из ssh-monitor.conf.example" +} + +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 (рекомендуется dual на пилоте): + UseSAC=dual + 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 +} + restart_ssh_monitor_service() { debug_step "Перезапускаем сервис ssh-monitor.service" if command -v systemctl &>/dev/null; then @@ -302,25 +509,10 @@ update_script() { fi fi - # Формируем путь к скрипту в репозитории (основной и альтернативный) - REMOTE_SCRIPT_PATH="$UPDATE_DIR/$SCRIPT_NAME/src/branch/main/$SCRIPT_NAME" - ALT_REMOTE_PATH="$UPDATE_DIR/$SCRIPT_NAME/$SCRIPT_NAME" - - debug_step "Основной путь к скрипту: $REMOTE_SCRIPT_PATH" - debug_step "Альтернативный путь к скрипту: $ALT_REMOTE_PATH" - - # Определяем реальный путь к скрипту local actual_script_path="" - if [ -f "$REMOTE_SCRIPT_PATH" ]; then - actual_script_path="$REMOTE_SCRIPT_PATH" - debug_step "Скрипт найден по основному пути" - elif [ -f "$ALT_REMOTE_PATH" ]; then - actual_script_path="$ALT_REMOTE_PATH" - log_message "INFO" "Script found at alternative path" - debug_step "Скрипт найден по альтернативному пути" - fi + actual_script_path="$(find_remote_ssh_monitor_path 2>/dev/null || true)" + debug_step "Путь к ssh-monitor в клоне: ${actual_script_path:-не найден}" - # sac-client.sh — отдельно от ssh-monitor: синхронизируем всегда (нужен для UseSAC/--check-sac) SAC_CLIENT_CHANGED=0 if ! sync_sac_client; then return 1 @@ -328,68 +520,49 @@ update_script() { local script_changed=0 - # Проверяем наличие файла скрипта в репозитории if [ -n "$actual_script_path" ] && [ -f "$actual_script_path" ]; then - log_message "INFO" "Remote script found. Comparing checksums..." - debug_step "Проверяем наличие файла скрипта в репозитории" - - # Проверяем существование локального скрипта - if [ ! -f "$LOCAL_SCRIPT_PATH" ]; then - log_message "INFO" "Local script not found. Will install new one." - LOCAL_CHECKSUM="" - debug_step "Локальный скрипт отсутствует, будет установлен новый" - else - debug_step "Вычисляем контрольную сумму локального скрипта" - LOCAL_CHECKSUM=$(sha256sum "$LOCAL_SCRIPT_PATH" 2>/dev/null | awk '{print $1}') - fi - - debug_step "Вычисляем контрольную сумму удалённого скрипта" - REMOTE_CHECKSUM=$(sha256sum "$actual_script_path" 2>/dev/null | awk '{print $1}') - - # Сравниваем контрольные суммы - debug_step "Сравниваем контрольные суммы" - if [ "$LOCAL_CHECKSUM" != "$REMOTE_CHECKSUM" ]; then - log_message "INFO" "Checksums differ. Updating $LOCAL_SCRIPT_PATH..." - debug_step "Контрольные суммы различаются, начинаем обновление" - - # Создаем бекап текущего скрипта если он существует - if [ -f "$LOCAL_SCRIPT_PATH" ]; then - debug_step "Создаем резервную копию текущего скрипта" - cp "$LOCAL_SCRIPT_PATH" "${LOCAL_SCRIPT_PATH}.backup.$(date +%Y%m%d_%H%M%S)" 2>/dev/null - fi - - debug_step "Копируем новый скрипт в $LOCAL_SCRIPT_PATH" - if ! cp "$actual_script_path" "$LOCAL_SCRIPT_PATH"; then - log_message "INFO" "ERROR: Failed to copy script" + if [ "$DEPLOY_MODE" -eq 1 ]; then + log_message "INFO" "Deploy: installing ssh-monitor to $LOCAL_SCRIPT_PATH" + if install_ssh_monitor_from "$actual_script_path"; then + script_changed=1 + else return 1 fi - - debug_step "Делаем скрипт исполняемым" - chmod +x "$LOCAL_SCRIPT_PATH" - - script_changed=1 - log_message "INFO" "Script updated successfully." - debug_step "Скрипт успешно обновлен" else - log_message "INFO" "Checksums match. No update needed." - debug_step "Контрольные суммы совпадают, обновление не требуется" + log_message "INFO" "Remote script found. Comparing checksums..." + if [ ! -f "$LOCAL_SCRIPT_PATH" ]; then + LOCAL_CHECKSUM="" + else + LOCAL_CHECKSUM=$(sha256sum "$LOCAL_SCRIPT_PATH" 2>/dev/null | awk '{print $1}') + fi + REMOTE_CHECKSUM=$(sha256sum "$actual_script_path" 2>/dev/null | awk '{print $1}') + if [ "$LOCAL_CHECKSUM" != "$REMOTE_CHECKSUM" ]; then + log_message "INFO" "Checksums differ. Updating $LOCAL_SCRIPT_PATH..." + if install_ssh_monitor_from "$actual_script_path"; then + script_changed=1 + log_message "INFO" "Script updated successfully." + else + return 1 + fi + else + log_message "INFO" "Checksums match. No update needed." + fi fi else log_message "INFO" "No remote script found. No update needed." - debug_step "Удаленный скрипт не найден ни по одному из путей" - - # Информация для отладки - if [ "$DEBUG" -eq 1 ]; then - debug_step "Содержимое директории репозитория:" - if [ -d "$UPDATE_DIR/$SCRIPT_NAME" ]; then - find "$UPDATE_DIR/$SCRIPT_NAME" -type f -name "$SCRIPT_NAME" 2>/dev/null | while read -r found_file; do - log_message "DEBUG" "Найден файл: $found_file" - done - fi + 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 [ "$script_changed" -eq 1 ] || [ "${SAC_CLIENT_CHANGED:-0}" -eq 1 ]; then + if [ "$DEPLOY_MODE" -eq 1 ]; then + script_changed=1 + elif [ "$script_changed" -eq 1 ] || [ "${SAC_CLIENT_CHANGED:-0}" -eq 1 ]; then restart_ssh_monitor_service fi @@ -401,35 +574,51 @@ update_script() { # === Основной блок выполнения === -log_message "INFO" "=== Script update started ===" +parse_command_line "$@" +deploy_require_root + +if [ "$DEPLOY_MODE" -eq 1 ]; then + printf '\n=== ssh-monitor: первичная установка (--deploy) ===\n' + say_step "Репозиторий и файлы" +fi + +log_message "INFO" "=== Script update started (deploy=$DEPLOY_MODE) ===" debug_step "Инициализация скрипта обновления" -# 1. Гарантируем создание необходимых директорий if ! ensure_directories; then log_message "INFO" "ERROR: Failed to ensure directories. Exiting." exit 1 fi -# 2. КРИТИЧЕСКИ ВАЖНО: Проверяем и устанавливаем ipset debug_step "Начинаем проверку и установку ipset (КРИТИЧЕСКИЙ ШАГ)" if ! ensure_ipset_installed; then log_message "ERROR" "CRITICAL: ipset installation or verification failed" - log_message "ERROR" "Работа обновленного скрипта невозможна без установленного ipset" echo "ERROR: Работа обновленного скрипта невозможна без установленного ipset" >&2 exit 1 fi -# 3. Выполняем функцию обновления update_script exit_code=$? -if [ $exit_code -eq 0 ]; then - log_message "INFO" "=== Script update completed successfully ===" - debug_step "Скрипт успешно завершил работу" -else - log_message "INFO" "=== Script update completed with errors (exit code: $exit_code) ===" - debug_step "Скрипт завершил работу с ошибками" +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 ===" + 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) ===" fi -# Выход с кодом завершения exit $exit_code