Пишем надежные скрипты для VPN-серверов
Аудитория: Системные администраторы и DevOps-инженеры, которые начинают писать свои скрипты для автоматизации управления серверами.
Введение: От «Готово» до «А работает ли?»
Автоматизация — это сила. Но без надежности она становится ловушкой.
Представьте: вы запускаете скрипт, он выводит «ГОТОВО», но в этот момент ваш VPN-сервер перестает отвечать. Такие ситуации возникают, когда код работает «в идеале», но не учитывает реальную жизнь — разные версии ПО, нестандартные конфигурации и человеческий фактор.
В этой статье мы разберем, как написать скрипт, который будет работать не только на вашей машине, но и в любой другой среде. Мы будем строить идеальный скрипт пошагово, объясняя, почему каждый шаг важен.
Ключевые принципы надежности
Любой качественный скрипт должен быть построен на четырех китах: строгой изоляции, обнаружения (discovery), безопасности и грамотной обработки ошибок.
Принцип 1: Строгая изоляция и защита (set -euo pipefail)
Первое, что должно быть в любом серьезном скрипте — это строка set -euo pipefail. Она делает три важных вещи:
-e(errexit): Скрипт немедленно останавливается, если любая команда возвращает ненулевой код (ошибку). Это предотвращает «катастрофическое падение», когда одна ошибка ведет за собой десятки других.-u(nounset): Скрипт выдаст ошибку, если вы попытаетесь использовать необъявленную переменную. Это ловит опечатки в именах ($USER_NMAEвместо$USER_NAME).-o pipefail: Позволяет отлавливать ошибки в цепочках команд (конвейерах). Если команда в серединеcmd1 | cmd2 | cmd3упадет, скрипт это заметит.
Почему это важно: Без этой строки скрипт будет пытаться продолжить работу даже после того, как команда mv или systemctl restart провалилась, создавая катастрофическую ситуацию.
Принцип 2: Обнаружение (Service Discovery), а не угадывание
Жестко прописывать имя сервиса (systemctl restart hysteria2.service) — это самое слабое место. Реальная система может называть его hysteria@80.service или просто hysteria.
Решение — динамическое обнаружение.
Вот пример идеальной функции:
safe_restart() {
local service_name="$1"
# Ищем точное имя сервиса, игнорируя возможные различия
local actual_service
actual_service=$(systemctl list-units --type=service --all 2>/dev/null | grep -oP "${service_name}(@\w+)?\.service" | head -n 1 || echo "")
if [[ -n "$actual_service" ]]; then
log_info "Перезапуск $actual_service..."
if systemctl restart "$actual_service"; then
log_success "$actual_service запущен."
else
log_error "Не удалось запустить $actual_service! Проверьте: journalctl -u $actual_service"
fi
else
log_info "Сервис $service_name не найден, пропускаю."
fi
}
Почему это важно: Это делает скрипт адаптивным к любой среде. Он не «угадывает», он «находит». Вы можете запустить этот скрипт на сервере с любыми названиями сервисов, и он сработает.
Принцип 3: Безопасность — Бэкапы, бэкапы и еще раз бэкапы
Изменение системных файлов — операция рискованная. Решение? Обязательное резервное копирование перед каждой операцией.
create_backup() {
local file="$1"
if [[ -f "$file" ]]; then
# Используем формат времени YYYYMMDD_HHMMSS для уникальности
local backup="${file}.bak.$(date +%Y%m%d_%H%M%S)"
cp "$file" "$backup"
log_info "Бэкап создан: $backup"
fi
}
Почему это важно: Бэкап — это ваш «undo» (отмена) в системе. Если jq ошибется и испортит JSON, вы сможете мгновенно вернуть всё как было за долю секунды.
Принцип 4: Обработка ошибок и пользовательский ввод
Ключевая ошибка новичков — полагаться на «ввод» пользователя.
- Проблема: Если скрипт вызывается не в интерактивном режиме (например, через
cron), командаreadприведет к зависанию сервера. - Решение: Использовать параметр по умолчанию и проверку.
USER_NAME="${1:-}" # Используем аргумент или пустую строку
if [[ -z "$USER_NAME" ]]; then
read -p "Введите имя нового пользователя: " USER_NAME
fi
# Проверяем, что пользователь ввел имя
[[ -z "$USER_NAME" ]] && log_error "Имя пользователя обязательно."
Также любой внешний вызов (как curl для определения IP) должен быть ограничен по времени: curl -s --max-time 5 ...
Идеальный скрипт (минимальный рабочий фрагмент)
Вот как выглядит его основа:
#!/bin/bash
set -euo pipefail
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
log_info() { echo -e "${YELLOW}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
log_error() { echo -e "${RED}[ERR]${NC} $1" >&2; exit 1; }
create_backup() {
local file="$1"
if [[ -f "$file" ]]; then
local backup="${file}.bak.$(date +%Y%m%d_%H%M%S)"
cp "$file" "$backup"
log_info "Бэкап создан: $backup"
fi
}
safe_restart() {
local service_name="$1"
local actual_service
actual_service=$(systemctl list-units --type=service --all 2>/dev/null | grep -oP "${service_name}(@\w+)?\.service" | head -n 1 || echo "")
if [[ -n "$actual_service" ]]; then
log_info "Перезапуск $actual_service..."
if systemctl restart "$actual_service"; then
log_success "$actual_service запущен."
else
log_error "Не удалось запустить $actual_service! Проверьте: journalctl -u $actual_service"
fi
else
log_info "Сервис $service_name не найден, пропускаю."
fi
}
# --- Инициализация ---
USER_NAME="${1:-}"
if [[ -z "$USER_NAME" ]]; then
read -p "Введите имя нового пользователя: " USER_NAME
fi
[[ -z "$USER_NAME" ]] && log_error "Имя пользователя обязательно."
Заключение
Следуя этим простым, но мощным принципам, вы превращаете «хакерский» скрипт в надежный инструмент, которым можно доверять. Это основа профессиональной автоматизации.
