Add CryptoPro license update and service restart scripts
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,366 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -u
|
||||
set -o pipefail
|
||||
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
LOG_FILE="./restart_services.log"
|
||||
|
||||
SSH_USER=""
|
||||
CONFIG_FILE=""
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Logging
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
log() {
|
||||
local level="$1"
|
||||
shift
|
||||
|
||||
printf '[%s] [%s] %s\n' \
|
||||
"$(date '+%Y-%m-%d %H:%M:%S')" \
|
||||
"$level" \
|
||||
"$*" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Usage
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Использование:
|
||||
|
||||
$SCRIPT_NAME -u USER -c CONFIG
|
||||
|
||||
Параметры:
|
||||
|
||||
-u USER SSH пользователь
|
||||
-c CONFIG файл конфигурации (host;service1,service2,...)
|
||||
|
||||
Пример:
|
||||
|
||||
$SCRIPT_NAME -u admin -c ./services.csv
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Arguments
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
while getopts ":u:c:h" opt; do
|
||||
case "$opt" in
|
||||
u)
|
||||
SSH_USER="$OPTARG"
|
||||
;;
|
||||
c)
|
||||
CONFIG_FILE="$OPTARG"
|
||||
;;
|
||||
h)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
\?)
|
||||
echo "Неизвестный параметр: -$OPTARG" >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
:)
|
||||
echo "Для параметра -$OPTARG требуется значение." >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$SSH_USER" || -z "$CONFIG_FILE" ]]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
echo "Ошибка: файл конфигурации не найден: $CONFIG_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Dependencies
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
for cmd in ssh base64 mktemp date tee grep sed; do
|
||||
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||
echo "Ошибка: не найдена команда '$cmd'" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Temp files / cleanup
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$TMP_DIR"
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# SSH
|
||||
#
|
||||
# -tt обязательно: sudo на удалённом хосте запрашивает пароль
|
||||
# через настоящий TTY. Пароль SSH спрашивает сам ssh.
|
||||
#
|
||||
# Локальный stdin остаётся терминалом и используется только
|
||||
# для ввода паролей. Удалённый скрипт передаётся в командной
|
||||
# строке (base64) и не занимает stdin.
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
SSH_OPTS=(
|
||||
-tt
|
||||
-o ConnectTimeout=15
|
||||
-o ServerAliveInterval=30
|
||||
-o ServerAliveCountMax=3
|
||||
)
|
||||
|
||||
# run_remote_script HOST LOCAL_SCRIPT
|
||||
# Выполняет локальный файл скрипта на хосте под "sudo bash".
|
||||
run_remote_script() {
|
||||
local host="$1"
|
||||
local script="$2"
|
||||
local b64
|
||||
|
||||
b64=$(base64 -w0 "$script")
|
||||
|
||||
ssh "${SSH_OPTS[@]}" "$SSH_USER@$host" \
|
||||
"t=\$(mktemp) && printf '%s' '$b64' | base64 -d > \"\$t\" && sudo bash \"\$t\"; rc=\$?; rm -f \"\$t\"; exit \$rc"
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
trim() {
|
||||
local value="$1"
|
||||
|
||||
value="${value#"${value%%[![:space:]]*}"}"
|
||||
value="${value%"${value##*[![:space:]]}"}"
|
||||
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Restart services
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
restart_services() {
|
||||
local host="$1"
|
||||
local services="$2"
|
||||
|
||||
log INFO "[$host] Подготовка к перезапуску служб"
|
||||
|
||||
local remote_script="$TMP_DIR/remote_$$.sh"
|
||||
local host_out="$TMP_DIR/out_$$.log"
|
||||
|
||||
# Список служб встраивается в скрипт (heredoc), stdin не используется
|
||||
{
|
||||
cat <<'REMOTE_SCRIPT'
|
||||
#!/bin/bash
|
||||
|
||||
set -u
|
||||
set -o pipefail
|
||||
|
||||
if [[ "$EUID" -ne 0 ]]; then
|
||||
echo "ERROR: скрипт должен выполняться от root (sudo)"
|
||||
exit 10
|
||||
fi
|
||||
|
||||
SERVICES=$(cat <<'SERVICES_LIST'
|
||||
REMOTE_SCRIPT
|
||||
|
||||
printf '%s\n' "$services"
|
||||
|
||||
cat <<'REMOTE_SCRIPT'
|
||||
SERVICES_LIST
|
||||
)
|
||||
|
||||
FAILED=0
|
||||
|
||||
IFS="," read -ra SERVICE_LIST <<< "$SERVICES"
|
||||
|
||||
for SERVICE in "${SERVICE_LIST[@]}"; do
|
||||
|
||||
# Убираем пробелы
|
||||
SERVICE="${SERVICE#"${SERVICE%%[![:space:]]*}"}"
|
||||
SERVICE="${SERVICE%"${SERVICE##*[![:space:]]}"}"
|
||||
|
||||
if [[ -z "$SERVICE" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "--------------------------------------------------"
|
||||
echo "Service: $SERVICE"
|
||||
|
||||
# Проверяем наличие unit
|
||||
if ! systemctl cat "$SERVICE" >/dev/null 2>&1 </dev/null; then
|
||||
echo "ERROR: systemd unit not found: $SERVICE"
|
||||
FAILED=1
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "Restarting $SERVICE..."
|
||||
|
||||
if ! systemctl restart "$SERVICE" </dev/null; then
|
||||
echo "ERROR: failed to restart $SERVICE"
|
||||
FAILED=1
|
||||
continue
|
||||
fi
|
||||
|
||||
# Небольшая пауза перед проверкой
|
||||
sleep 2
|
||||
|
||||
if systemctl is-active --quiet "$SERVICE"; then
|
||||
echo "SUCCESS: $SERVICE is active"
|
||||
else
|
||||
echo "ERROR: $SERVICE is not active after restart"
|
||||
|
||||
systemctl status "$SERVICE" \
|
||||
--no-pager \
|
||||
--full </dev/null 2>&1 || true
|
||||
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
echo "--------------------------------------------------"
|
||||
|
||||
if [[ "$FAILED" -ne 0 ]]; then
|
||||
echo "RESULT=FAILED"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "RESULT=SUCCESS"
|
||||
exit 0
|
||||
REMOTE_SCRIPT
|
||||
} > "$remote_script"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Выполнение.
|
||||
#
|
||||
# Вывод удалённого скрипта идёт через tee одновременно в лог
|
||||
# и во временный файл (для проверки результата). tee не буферизует
|
||||
# вывод, поэтому приглашение sudo (без перевода строки) видно сразу.
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
: > "$host_out"
|
||||
|
||||
local rc=0
|
||||
run_remote_script "$host" "$remote_script" 2>&1 | tee -a "$LOG_FILE" "$host_out" || rc=$?
|
||||
|
||||
# pty на удалённой стороне добавляет CR
|
||||
sed -i 's/\r$//' "$host_out"
|
||||
|
||||
rm -f "$remote_script"
|
||||
|
||||
if [[ "$rc" -eq 255 ]]; then
|
||||
log ERROR "[$host] Ошибка SSH-соединения"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "$rc" -eq 0 ]] && grep -q '^RESULT=SUCCESS$' "$host_out"; then
|
||||
log INFO "[$host] Все службы успешно перезапущены"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log ERROR "[$host] Не все службы были успешно перезапущены (rc=$rc)"
|
||||
return 1
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Main
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
log INFO "=============================================="
|
||||
log INFO "Запуск $SCRIPT_NAME"
|
||||
log INFO "SSH user: $SSH_USER"
|
||||
log INFO "Config: $CONFIG_FILE"
|
||||
log INFO "=============================================="
|
||||
|
||||
TOTAL=0
|
||||
SUCCESS=0
|
||||
FAILED=0
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Читаем конфигурацию в массивы заранее: во время ssh stdin должен
|
||||
# оставаться терминалом (ввод паролей), а не файлом конфигурации.
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
HOSTS=()
|
||||
HOST_SERVICES=()
|
||||
|
||||
while IFS=';' read -r host services || [[ -n "$host" ]]; do
|
||||
|
||||
# Убираем BOM из первой строки
|
||||
host="${host#$'\xef\xbb\xbf'}"
|
||||
|
||||
host="$(trim "$host")"
|
||||
services="$(trim "$services")"
|
||||
|
||||
# Пустая строка
|
||||
[[ -z "$host" ]] && continue
|
||||
|
||||
# Комментарий
|
||||
[[ "$host" == \#* ]] && continue
|
||||
|
||||
# Проверяем конфигурацию
|
||||
if [[ -z "$services" ]]; then
|
||||
log ERROR "[$host] Не указан список служб"
|
||||
((FAILED++))
|
||||
((TOTAL++))
|
||||
continue
|
||||
fi
|
||||
|
||||
HOSTS+=("$host")
|
||||
HOST_SERVICES+=("$services")
|
||||
((TOTAL++))
|
||||
|
||||
done < "$CONFIG_FILE"
|
||||
|
||||
for i in ${HOSTS[@]+"${!HOSTS[@]}"}; do
|
||||
host="${HOSTS[$i]}"
|
||||
services="${HOST_SERVICES[$i]}"
|
||||
|
||||
log INFO "----------------------------------------------"
|
||||
log INFO "Host: $host"
|
||||
log INFO "Services: $services"
|
||||
|
||||
if restart_services "$host" "$services"; then
|
||||
((SUCCESS++))
|
||||
else
|
||||
((FAILED++))
|
||||
fi
|
||||
done
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Summary
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
log INFO "=============================================="
|
||||
log INFO "Результат выполнения"
|
||||
log INFO "Всего хостов : $TOTAL"
|
||||
log INFO "Успешно : $SUCCESS"
|
||||
log INFO "Ошибок : $FAILED"
|
||||
log INFO "Log : $LOG_FILE"
|
||||
log INFO "=============================================="
|
||||
|
||||
# Убираем CR, попавшие в лог из удалённого TTY
|
||||
sed -i 's/\r$//' "$LOG_FILE"
|
||||
|
||||
if [[ $FAILED -gt 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Reference in New Issue
Block a user