Основы безопасной автоматизации Linux - MSB support

Рубрики


История о том, как скрипт почти уничтожил ваш сервер (и как этого избежать)

Анализ реальной ошибки в системе управления VPN на примере эволюции кода

Аудитория: Системные администраторы, DevOps-инженеры и разработчики скриптов. Понимание этой истории сэкономит вам часы устранения неполадок и предотвратит сбои в работе систем.


Предисловие: Почему «работает» — не значит «надежно»

Скрипт, который выводит «ГОТОВО», и скрипт, который гарантированно не сломает ваш сервер — это две разные вещи. Разница между ними заключается в четырех словах: исходные предположения (assumptions).

Когда вы пишете скрипт, вы делаете предположения:

  • «Служба называется именно так».
  • «Команда всегда вернет то, что я ожидаю».
  • «Файл не повредится при ошибке».

Эти предположения и являются источником катастрофических сбоев. В этой статье мы разберем эволюцию скрипта от «рабочего» до «невзламываемого», на примере его реальных версий.


1. Исходная точка: Рабочий, но «Хрупкий» Скрипт (v1.0)

Это тот код, с которого все начинается: он делает именно то, что задумано. И убийственно рискован.

❌ Ошибки и их цена:

  1. Проблема: Отсутствие set -euo pipefail
    Что происходит: Если команда jq не сможет распарсить JSON из-за синтаксической ошибки, она вернет код ошибки 1. В обычном Bash скрипт проигнорирует это и продолжит выполнять mv, затирая старый, рабочий конфиг файлом с ошибкой.
    Цена: Полная потеря конфигурации и остановка всех VPN-сервисов.
  2. Проблема: Отсутствие резервной копии
    Что происходит: Вся логика работает по схеме jq ... config.json > config.tmp && mv config.tmp config.json. Если между этим mv произойдет отключение питания, вы получите пустой файл config.json.
    Цена: Прямая потеря рабочих данных.
  3. Проблема: Жесткая привязка systemctl restart xray
    Что происходит: На одном сервере служба может называться xray, а на другом — xray@main.service. Если имя не совпадет, systemctl выдаст ошибку, но скрипт продолжит выполнение (если бы не было set -e). Вы увидите «ГОТОВО».
    Цена: Сервер без VPN, а вы не знаете об этом. Это худшая ошибка — тихая.

Код (пример):

#!/bin/bash
# ... поиски и определения переменных ...

if [ -n "$XRAY_CONFIG" ] && [ -f "$XRAY_CONFIG" ]; then
    echo "Обработка Xray..."

    HAS_REALITY=$(jq -e '.inbounds[] | select(.streamSettings.security == "reality")' "$XRAY_CONFIG" 2>/dev/null)

    if [ -n "$HAS_REALITY" ]; then
        # Добавляем в Reality inbound. Здесь может сломаться.
        jq --arg id "$NEW_UUID" ... "$XRAY_CONFIG" > "${XRAY_CONFIG}.tmp" && mv ...

        systemctl restart xray # А если служба называется иначе?
    fi
fi

echo "=== ГОТОВО ===" # А если служба не перезапустилась?

2. Первый шаг к улучшению: Рабочий скрипт с цветами и плюсами (v2.0)

Это уже лучше. Вы добавили set -euo pipefail и цветовую разметку. Код стал «красивее», но не стал безопаснее.

✅ Улучшения:

  1. Улучшение: set -euo pipefail
    Что дает: Теперь скрипт обязательно остановится, если команда вернет ошибку. Вы увидите ошибку в логе и сразу поймете, что что-то пошло не так. Это уже серьезный шаг вперед.
  2. Улучшение: Цветовое логирование
    Что дает: Визуальное разделение вывода на типы сообщений (INFO, OK, ERR). Упрощает поиск проблем в логах.
  3. Проблема: Внутренняя логика не изменилась.
    Что осталось: Отсутствие бэкапов и жесткий перезапуск сервиса. Риск все еще высок, просто теперь вы будете сразу видеть ошибку.

Код (пример):

#!/bin/bash
set -euo pipefail # --- Добавлено! Критически важно. ---

GREEN='\033[0;32m'
YELLOW='\\033[1;33m'
RED='\033[0;31m'
NC='\\033[0m'

# ... код ...
echo -e "${GREEN}[+] Ссылка Xray VLESS Reality:${NC}"
# ... но если `jq` сломается, конфиг не сохранится, а бэкапа нет.

3. Прорыв: Гибридный, Универсальный и Отказоустойчивый Скрипт (v3.0)

Вы внесли реальные изменения, которые превращают скрипт из «хака» в инструмент. Это ваша финальная версия, и она делает всё правильно.

✅ Улучшения:

  1. Идея 1 (Бэкапы): Функция create_backup()
    Что делает: Перед любой записью в файл она создает копию с временной меткой: config.json.bak.20241231_235959.
    Почему это важно: Это ваш «откат» (undo). Если что-то пойдет не так, вы быстро вернете всё в исходное состояние.
  2. Идея 2 (Service Discovery): Функция safe_restart()
    Что делает: Вместо жесткого имени сервиса, она сначала делает запрос к systemctl list-units, находит точное имя (например, hysteria@80.service) и только потом его перезапускает.
    Почему это важно: Это делает скрипт абсолютно универсальным. Он будет работать на любом сервере, независимо от того, как названа служба.
  3. Идея 3 (Обработка ошибок): Проверка if systemctl restart
    Что делает: Если перезапуск службы не удался, скрипт немедленно выводит ошибку и выходит.
    Почему это важно: Вы получаете обратную связь: скрипт «молчит», только если всё прошло идеально.

Код (пример):

#!/bin/bash
set -euo pipefail

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!"
        fi
    else
        log_info "Сервис $service_name не найден, пропускаю."
    fi
}

# --- В вызове: ---
create_backup "$XRAY_CONFIG"  # Бэкап создан.
jq ...                        # Конфиг изменен.
safe_restart "xray"           # Служба перезапущена с гарантией, что она найдется.

4. Завершающий штрих: Структурирование и чистота кода

Теперь ваш скрипт не просто надежный, он еще и профессиональный. Его можно легко поддерживать, расширять и делиться с коллегами.

✅ Улучшения:

  1. Читаемость: Функции логирования (log_info, log_success) делают вывод скрипта понятным для оператора. Вы сразу видите, что происходит.
  2. Модульность: Логика для каждого протокола вынесена в отдельные функции (add_xray_user, add_v2ray_user, add_tuic_user). Это позволяет легко отлаживать и расширять скрипт, не ломая уже рабочий код.
  3. Глобальные проверки: Весь код структурирован, что минимизирует количество неявных ошибок.

Код (пример):

# --- Цвета и логирование (для удобства чтения) ---
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; }

# --- Логика добавления пользователя для каждого протокола ---
add_xray_user() {
    local config_file="$1"
    log_info "Настройка Xray..."
    create_backup "$config_file"  # Бэкап вызывается внутри функции.
    # ... основная логика ...
}

add_v2ray_user() { /* ... */ }
add_tuic_user() { /* ... */ }

# --- Главная логика скрипта ---
if [[ -n "$XRAY_CONFIG" ]]; then add_xray_user "$XRAY_CONFIG"; fi
# ...

Заключение: От хака к инженерии

Рассмотрев эволюцию от v1.0 до v3.0, мы видим, что путь к надежному скрипту — это путь от простой последовательности команд к системе, где каждая часть имеет резервный выход и проверку.

Ваш гибридный скрипт (v3.0) — это идеальный пример того, как можно и нужно писать автоматизацию. Он не предполагает, он проверяет. Он не угадывает, он ищет. Он не рискует, он бэкапит.

Запомните это правило: если в вашем скрипте нет set -euo pipefail, функции бэкапа и проверки ошибок — он не готов для реального мира.