#!/usr/bin/env bash # # deploy-agent.sh — массовая установка/обновление InfoUser Agent. # # Использование: # cd /home/user/moder/info-user/deploy # ./deploy-agent.sh # # Перед запуском: # 1. Положить id_rsa в ./id_rsa # 2. Создать ./ip.txt со списком хостов (user@IP или просто IP) # 3. При необходимости изменить SERVER_URL/AGENT_TOKEN ниже или через env # set -euo pipefail SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" HOSTS_FILE="${SCRIPT_DIR}/ip.txt" KEY_FILE="${SCRIPT_DIR}/id_rsa" PUB_FILE="${SCRIPT_DIR}/id_rsa.pub" OK_FILE="${SCRIPT_DIR}/deploy-ok.txt" DEFER_FILE="${SCRIPT_DIR}/deploy-deferred.txt" LOG_DIR="${SCRIPT_DIR}/logs" PKG_NAME="info-user-agent" AGENT_CONFIG_DIR="/opt/info-user-agent" AGENT_CONFIG_FILE="${AGENT_CONFIG_DIR}/config.json" SERVER_URL="${SERVER_URL:-http://192.168.255.180:3000}" AGENT_TOKEN="${AGENT_TOKEN:-a916c8cd656b3f40acc2920820abf88e}" SSH_PASS="${SSH_PASS:-1q2w3e\$R}" SSH_PORT="${SSH_PORT:-22}" DEFAULT_USER="${DEFAULT_USER:-user}" MAX_RETRIES="${MAX_RETRIES:-3}" RETRY_SLEEP="${RETRY_SLEEP:-3}" # Цвета RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' SSH_KEY_OPTS=( -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=6 -o ConnectionAttempts=1 -o ServerAliveInterval=5 -o ServerAliveCountMax=1 -o PasswordAuthentication=no -p "$SSH_PORT" ) SSH_PASS_OPTS=( -o StrictHostKeyChecking=accept-new -o ConnectTimeout=6 -o ConnectionAttempts=1 -o ServerAliveInterval=5 -o ServerAliveCountMax=1 -p "$SSH_PORT" ) SCP_KEY_OPTS=( -i "$KEY_FILE" -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=6 -o ConnectionAttempts=1 -p "$SSH_PORT" ) SCP_PASS_OPTS=( -o StrictHostKeyChecking=accept-new -o ConnectTimeout=6 -o ConnectionAttempts=1 -p "$SSH_PORT" ) print_header() { echo -e "${BLUE}=== InfoUser Agent deploy ===${NC}" echo "SERVER_URL: $SERVER_URL" echo "AGENT_TOKEN: ${AGENT_TOKEN:0:8}..." echo "HOSTS_FILE: $HOSTS_FILE" echo "KEY_FILE: $KEY_FILE" echo "LOG_DIR: $LOG_DIR" echo "" } check_prerequisites() { local missing=() command -v ssh >/dev/null 2>&1 || missing+=("ssh") command -v scp >/dev/null 2>&1 || missing+=("scp") command -v sshpass >/dev/null 2>&1 || missing+=("sshpass") command -v ping >/dev/null 2>&1 || missing+=("ping") if [[ ${#missing[@]} -gt 0 ]]; then echo -e "${RED}Ошибка: не найдены утилиты: ${missing[*]}${NC}" >&2 exit 1 fi if [[ ! -f "$HOSTS_FILE" ]]; then echo -e "${RED}Ошибка: файл со списком хостов не найден: $HOSTS_FILE${NC}" >&2 echo "Создайте его по примеру ip.txt.example" >&2 exit 1 fi if [[ ! -f "$KEY_FILE" ]]; then echo -e "${RED}Ошибка: SSH-ключ не найден: $KEY_FILE${NC}" >&2 exit 1 fi # Генерируем .pub если нет if [[ ! -f "$PUB_FILE" ]]; then echo "Генерирую публичный ключ: $PUB_FILE" ssh-keygen -y -f "$KEY_FILE" > "$PUB_FILE" fi # Ищем fallback .deb в корне проекта DEB_FILE="" for f in "${PROJECT_DIR}"/${PKG_NAME}_*_all.deb; do [[ -f "$f" ]] && DEB_FILE="$f" done if [[ -z "$DEB_FILE" ]]; then echo -e "${YELLOW}Предупреждение: fallback .deb не найден в $PROJECT_DIR. Будет использован только apt.${NC}" else echo "Fallback .deb: $DEB_FILE" fi } normalize_target() { local line="$1" if [[ "$line" == *"@"* ]]; then echo "$line" else echo "${DEFAULT_USER}@${line}" fi } extract_host() { local target="$1" echo "${target#*@}" } safe_name() { local target="$1" echo "${target//[@.:\/]/_}" } with_retries() { local desc="$1" shift local attempt=1 while true; do if "$@"; then return 0 fi if (( attempt >= MAX_RETRIES )); then return 1 fi echo " ${desc}: повтор ${attempt}/${MAX_RETRIES}..." attempt=$((attempt + 1)) sleep "$RETRY_SLEEP" done } ping_check() { local host="$1" ping -c 1 -W 1 "$host" >/dev/null 2>&1 } ssh_key_check() { local target="$1" ssh -i "$KEY_FILE" -n "${SSH_KEY_OPTS[@]}" "$target" "echo ok" >/dev/null 2>&1 } ssh_pass_check() { local target="$1" sshpass -p "$SSH_PASS" ssh -n "${SSH_PASS_OPTS[@]}" "$target" "echo ok" >/dev/null 2>&1 } detect_auth_method() { local target="$1" if ssh_key_check "$target"; then echo "key" return 0 fi if ssh_pass_check "$target"; then echo "pass" return 0 fi echo "fail" } install_key_on_host() { local target="$1" local log_file="$2" { echo "==> SSH по паролю успешен, добавляю ключ в authorized_keys" sshpass -p "$SSH_PASS" ssh -n "${SSH_PASS_OPTS[@]}" "$target" "mkdir -p ~/.ssh && chmod 700 ~/.ssh" sshpass -p "$SSH_PASS" scp "${SCP_PASS_OPTS[@]}" "$PUB_FILE" "$target:/tmp/info_user_key.pub" sshpass -p "$SSH_PASS" ssh -n "${SSH_PASS_OPTS[@]}" "$target" "cat /tmp/info_user_key.pub >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys && rm -f /tmp/info_user_key.pub" } >>"$log_file" 2>&1 || true } ssh_exec() { local method="$1" local target="$2" local cmd="$3" if [[ "$method" == "key" ]]; then ssh -i "$KEY_FILE" -n "${SSH_KEY_OPTS[@]}" "$target" "$cmd" else sshpass -p "$SSH_PASS" ssh -n "${SSH_PASS_OPTS[@]}" "$target" "$cmd" fi } scp_to_host() { local method="$1" local target="$2" local local_path="$3" local remote_path="$4" if [[ "$method" == "key" ]]; then scp "${SCP_KEY_OPTS[@]}" "$local_path" "$target:$remote_path" else sshpass -p "$SSH_PASS" scp "${SCP_PASS_OPTS[@]}" "$local_path" "$target:$remote_path" fi } configure_agent() { local method="$1" local target="$2" local log_file="$3" local config_json config_json=$(printf '{"server_url":"%s","agent_token":"%s","interval_minutes":60,"send_public_ip":false}' "$SERVER_URL" "$AGENT_TOKEN") local remote_cmd remote_cmd=$(printf 'echo "%s" | sudo -S tee %s >/dev/null' "$config_json" "$AGENT_CONFIG_FILE") { echo "==> Настраиваю $AGENT_CONFIG_FILE" ssh_exec "$method" "$target" "echo '$SSH_PASS' | sudo -S mkdir -p '$AGENT_CONFIG_DIR'" ssh_exec "$method" "$target" "$remote_cmd" echo "==> Запускаю таймер и первый heartbeat" ssh_exec "$method" "$target" "echo '$SSH_PASS' | sudo -S systemctl daemon-reload" ssh_exec "$method" "$target" "echo '$SSH_PASS' | sudo -S systemctl enable info-user-agent.timer" ssh_exec "$method" "$target" "echo '$SSH_PASS' | sudo -S systemctl start info-user-agent.timer" ssh_exec "$method" "$target" "echo '$SSH_PASS' | sudo -S systemctl start info-user-agent" } >>"$log_file" 2>&1 } install_agent() { local method="$1" local target="$2" local log_file="$3" # Сначала пробуем apt { echo "==> Пробую установить через apt" ssh_exec "$method" "$target" "echo '$SSH_PASS' | sudo -S apt-get update -qq" ssh_exec "$method" "$target" "echo '$SSH_PASS' | sudo -S apt-get install -y '$PKG_NAME'" } >>"$log_file" 2>&1 && return 0 # Fallback на dpkg если apt не сработал и есть .deb if [[ -z "$DEB_FILE" ]]; then echo " apt не сработал, fallback .deb отсутствует" >>"$log_file" 2>&1 return 1 fi local deb_name deb_name="$(basename "$DEB_FILE")" local remote_deb="/tmp/${deb_name}" { echo "==> apt не сработал, пробую dpkg -i $deb_name" scp_to_host "$method" "$target" "$DEB_FILE" "$remote_deb" ssh_exec "$method" "$target" "echo '$SSH_PASS' | sudo -S dpkg -i '$remote_deb'" ssh_exec "$method" "$target" "rm -f '$remote_deb'" } >>"$log_file" 2>&1 } process_host() { local raw_line="$1" local target local host_only local safe_host local log_file local auth_method target="$(normalize_target "$raw_line")" host_only="$(extract_host "$target")" safe_host="$(safe_name "$target")" log_file="${LOG_DIR}/${safe_host}.log" echo "" echo -e "${YELLOW}===== $target =====${NC}" # Пинг if ! with_retries "ping" ping_check "$host_only"; then echo -e "${RED}❌ Пинг не проходит: $target -> отложен${NC}" echo "$target" >> "$DEFER_FILE" return 0 fi # Определяем метод авторизации auth_method="$(detect_auth_method "$target")" if [[ "$auth_method" == "fail" ]]; then echo -e "${RED}❌ SSH недоступен (ключ и пароль): $target -> отложен${NC}" echo "$target" >> "$DEFER_FILE" return 0 fi if [[ "$auth_method" == "key" ]]; then echo -e "${GREEN}✅ SSH по ключу: $target${NC}" else echo -e "${GREEN}✅ SSH по паролю: $target (добавляю ключ)${NC}" install_key_on_host "$target" "$log_file" fi # Установка/обновление if install_agent "$auth_method" "$target" "$log_file"; then echo -e "${GREEN}✅ Агент установлен/обновлён: $target${NC}" else echo -e "${RED}❌ Ошибка установки: $target -> отложен (см. $log_file)${NC}" echo "$target" >> "$DEFER_FILE" return 0 fi # Настройка и запуск if configure_agent "$auth_method" "$target" "$log_file"; then echo -e "${GREEN}✅ Настроен и запущен: $target${NC}" echo "$target" >> "$OK_FILE" else echo -e "${RED}❌ Установлен, но не удалось настроить/запустить: $target -> отложен (см. $log_file)${NC}" echo "$target" >> "$DEFER_FILE" fi } print_summary() { local ok_count local defer_count ok_count="$(wc -l < "$OK_FILE" 2>/dev/null || echo 0)" defer_count="$(wc -l < "$DEFER_FILE" 2>/dev/null || echo 0)" echo "" echo -e "${BLUE}=== Итог ===${NC}" echo -e "${GREEN}Успешно: $ok_count${NC}" echo -e "${RED}Отложено: $defer_count${NC}" echo "Логи: $LOG_DIR/" if [[ -s "$DEFER_FILE" ]]; then echo "Повторить для отложенных: ./deploy-agent.sh" fi } main() { print_header check_prerequisites mkdir -p "$LOG_DIR" : > "$OK_FILE" : > "$DEFER_FILE" # Бэкап ip.txt cp -f "$HOSTS_FILE" "${HOSTS_FILE}.bak.$(date +%Y%m%d_%H%M%S)" 2>/dev/null || true local total=0 total="$(grep -vE '^\s*$|^\s*#' "$HOSTS_FILE" | wc -l)" echo -e "${BLUE}Хостов в списке: $total${NC}" while IFS= read -r line || [[ -n "$line" ]]; do # Убираем CR (Windows-концы строк) line="${line//$'\r'/}" # trim line="${line#"${line%%[![:space:]]*}"}" line="${line%"${line##*[![:space:]]}"}" [[ -z "$line" || "$line" =~ ^# ]] && continue process_host "$line" done < "$HOSTS_FILE" print_summary } main "$@"