Search
Write a publication
Pull to refresh

От консоли к веб-интерфейсу: создание автоматического веб-инсталлятора для ALD Pro на Flask и Python

Level of difficultyEasy
Reading time11 min
Views1.6K

Что такое ALD Pro и зачем его едят можно почитать в других статья на Хабре:

  1. https://habr.com/ru/companies/astralinux/news/857124/

  2. https://habr.com/ru/companies/astralinux/articles/734788/

  3. https://habr.com/ru/companies/astralinux/news/857892/

А я расскажу о своем пути в установке данного комплекса, а также какие проблемы были у меня, чтобы их не было у Вас:)

Ну и конечно же, как и любая моя статья, эта также будет связана со скриптами, автоматизацией и удобствами для конечного пользователя.

Глава 1: Зачем нужна автоматизация для установки ALD Pro?

ALD Pro — это мощное решение для управления доменами и аутентификации в инфраструктуре Astra Linux. Однако его установка и настройка — это многоэтапный процесс, требующий выполнения множества команд в терминале, редактирования конфигурационных файлов и перезагрузки системы. Вручную это не только отнимает много времени, но и чревато ошибками, особенно для администраторов, которые не часто сталкиваются с подобными задачами.

Типичные проблемы ручной установки:

  1. Сложность: Необходимость точного ввода команд, настройки сети, DNS, репозиториев и других параметров.

  2. Время: Процесс может занять несколько часов, особенно если требуется обновление системы или повторение шагов из-за ошибок.

  3. Человеческий фактор: Опечатки, пропущенные шаги или неверные параметры могут привести к неработоспособности системы.

  4. Документация: Официальные инструкции часто объемны и требуют адаптации под конкретную инфраструктуру.

Почему автоматизация?

Автоматизация установки ALD Pro решает эти проблемы, предоставляя следующие преимущества:

  1. Скорость: Скрипт выполняет все шаги за считанные минуты, минимизируя время простоя.

  2. Точность: Исключаются ошибки, связанные с ручным вводом. Каждый шаг проверяется, а параметры валидируются.

  3. Удобство: Администратору не нужно запоминать последовательность действий — достаточно запустить скрипт и следовать подсказкам.

  4. Масштабируемость: Один и тот же скрипт можно использовать для развертывания ALD Pro на множестве серверов, обеспечивая идентичную конфигурацию.

  5. Поддержка разных сценариев: Возможность выбора версии ALD Pro, настройки глобального каталога или модуля синхронизации через интерактивный интерфейс.

Эволюция инструментов

Изначально я использовал bash-скрипты с zenity для создания простого графического интерфейса. Это работало, но имело ограничения:

  • Сложность поддержки: добавление новых функций усложняло код.

  • Ограниченный UI: zenity предоставляет базовые диалоговые окна, но не подходит для сложных сценариев.

  • Нет возможности удаленного управления.

Переход на Python и Flask позволил:

  1. Создать полноценный веб-интерфейс с интуитивной навигацией.

  2. Реализовать прогресс-бар, логирование и обработку ошибок.

  3. Обеспечить доступ к установке с любого устройства в сети.

  4. Легко расширять функционал (например, добавление поддержки новых версий ALD Pro).

Автоматизация установки ALD Pro — это не просто удобство, а необходимость для современных ИТ-инфраструктур. Она экономит время, снижает риски ошибок и позволяет сосредоточиться на более важных задачах. Дальше я расскажу, как прошел путь от простых bash-скриптов до веб-интерфейса на Flask, и какие подводные камни встретил на этом пути.


Глава 2: Bash + Zenity — Первая версия автоматизации

Украду, свою же картинку с другой статьи. Bash навсегда <3
Украду, свою же картинку с другой статьи. Bash навсегда <3

1. Почему начали с Bash?

  • Простота: Bash — это стандартный инструмент администрирования Linux, не требующий дополнительных зависимостей.

  • Скорость разработки: для простых задач не нужны сложные языки.

  • Доступность: все команды установки ALD Pro уже выполнялись вручную, оставалось лишь объединить их в скрипт.

2. Структура bash-скрипта

Разберём ключевые части скрипта:

2.1. Проверки перед установкой

internet_error="У вас проблемы с доступом к сайту dl.astralinux.ru. Проверьте настройку интернет соединения и правильность dns."
if ping -c 1 dl.astralinux.ru &> /dev/null; then
    echo "Доступ к репозиторию есть"
else
    zenity --info --text="$internet_error"
    exit 1
fi
  • Проверка Интернет-соединения перед началом установки.

  • Использование zenity для вывода ошибок в GUI.

2.2. Работа с администратором системы и правами sudo

if id -nG | grep -qw "astra-admin"; then
  echo ok
else 
  zenity --info --text="Пользователь не принадлежит группе astra-admin. Необходимо зайди под пользователем с правами администратора."
  exit 1
fi
passwd=$(zenity --forms --title="Пароль для администратора" \
  --text="Введите пароль администратора" \
  --add-password="Пароль")
echo "$passwd" | sudo -Sv >/dev/null 2>&1
if [ $? -ne 0 ]; then
  #остальной код
else
  zenity --info --text="Неправильный пароль от sudo. Перезапустите скрипт."
  exit 1
fi
  • Интерактивный ввод пароля через zenity.

  • Проверка корректности перед выполнением привилегированных команд.

2.3. Динамические параметры установки

form_data=$(zenity --forms --title="Введите данные" --text="Введите данные:" \
        --add-entry="Введите имя контроллера домена имя типа: dc" \
        --add-entry="Введите имя домена типа: domain.test" \
        --add-entry="Введите имя полное доменное имя типа: dc.domain.test" \
        --add-entry="Введите статический ip-address вашего будущего домена типа: 10.10.10.10" \
        --add-entry="Введите маску подсети вашего будущего домена типа: 255.255.255.0" \
        --add-entry="Введите gateway сети вашего будущего домена типа: 10.10.10.1" \
        --add-entry="Введите ваш dns для доступа в интернет: 10.10.10.9" \
        --add-password="Придумайте пароль для администратора домена:" \
        --add-combo="Версия ALDPro" \
        --combo-values="2.2.1|2.3.0|2.4.0" )
      
# Разбиение строки с данными на отдельные переменные
      small_fqdn=$(echo "$form_data" | awk -F '|' '{print $1}')
      big_fqdn=$(echo "$form_data" | awk -F '|' '{print $2}')
      fqdn=$(echo "$form_data" | awk -F '|' '{print $3}')
      ipaddres=$(echo "$form_data" | awk -F '|' '{print $4}')
      mask=$(echo "$form_data" | awk -F '|' '{print $5}')
      gateway=$(echo "$form_data" | awk -F '|' '{print $6}')
      dns=$(echo "$form_data" | awk -F '|' '{print $7}')
      passwd_dom=$(echo "$form_data" | awk -F '|' '{print $8}')
      version_aldpro=$(echo "$form_data" | awk -F '|' '{print $9}')
  • Графический ввод параметров (FQDN, IP, пароль и т.д.).

  • Демонстрация примеров заполнения, для удобства пользователя.

2.4. Настройка сети и репозиториев

if [ "$version_aldpro" == 2.3.0 ]; then
#репы 2.3.0 и 1.7.5
  echo $passwd | sudo -S bash -c "echo -e 'deb https://download.astralinux.ru/aldpro/frozen/01/2.3.0 1.7_x86-64 main base' > /etc/apt/sources.list.d/aldpro.list"
  echo $passwd | sudo -S bash -c "echo -e 'deb https://dl.astralinux.ru/astra/frozen/1.7_x86-64/1.7.5/uu/1/repository-base/ 1.7_x86-64 main contrib non-free' > /etc/apt/sources.list" 
  echo $passwd | sudo -S bash -c "echo -e 'deb http://download.astralinux.ru/astra/frozen/1.7_x86-64/1.7.5/repository-extended 1.7_x86-64 main contrib non-free' >> /etc/apt/sources.list"                                
elif [ "$version_aldpro" == 2.4.0 ]; then
#репы 2.4.0 и 1.7.6
  echo $passwd | sudo -S bash -c "echo -e 'deb https://dl.astralinux.ru/aldpro/frozen/01/2.4.0/ 1.7_x86-64 main base' > /etc/apt/sources.list.d/aldpro.list"
  echo $passwd | sudo -S bash -c "echo -e 'deb https://dl.astralinux.ru/astra/frozen/1.7_x86-64/1.7.6/repository-base 1.7_x86-64 main contrib non-free' > /etc/apt/sources.list" 
  echo $passwd | sudo -S bash -c "echo -e 'deb https://dl.astralinux.ru/astra/frozen/1.7_x86-64/1.7.6/repository-update 1.7_x86-64 main contrib non-free' >> /etc/apt/sources.list"     
else
#репы 2.2.1 и 1.7.4
  echo $passwd | sudo -S bash -c "echo -e 'deb https://download.astralinux.ru/aldpro/frozen/01/2.2.1 1.7_x86-64 main base' > /etc/apt/sources.list.d/aldpro.list"
  echo $passwd | sudo -S bash -c "echo -e 'deb http://dl.astralinux.ru/astra/frozen/1.7_x86-64/1.7.4/repository-extended 1.7_x86-64 main contrib non-free' > /etc/apt/sources.list" 
  echo $passwd | sudo -S bash -c "echo -e 'deb http://dl.astralinux.ru/astra/frozen/1.7_x86-64/1.7.4/repository-base 1.7_x86-64 main non-free contrib' >> /etc/apt/sources.list"
fi

echo $passwd | sudo -S bash -c  "echo -e 'search $big_fqdn' > /etc/resolv.conf"
echo $passwd | sudo -S bash -c  "echo -e 'nameserver 127.0.0.1' >> /etc/resolv.conf"
  • Замена DNS для работы ALD Pro.

  • Добавление репозитория в зависимости от выбранной версии.

2.5. Запуск установки ALD Pro

echo $passwd | sudo -S aldpro-server-install -d "$big_fqdn" -n "$small_fqdn" -p "$passwd_dom" --ip "$ipaddres" --no-reboot --setup_syncer --setup_gc
  • Автоматический вызов официального инсталлятора с нужными параметрами.

3. Плюсы и минусы решения

Плюсы:

  • Минимальные зависимости (bash, zenity, sudo).

  • Быстрая разработка для базового сценария.

  • Подходит для разовых установок.

Минусы:

  • Сложность поддержки: при добавлении новых функций код становится запутанным.

  • Ограниченный UI: Zenity не поддерживает прогресс-бар в реальном времени или сложные формы.

  • Нет обработки ошибок: если что-то пошло не так, скрипт может «зависнуть» без понятного лога.

Глава 3: Переход на Python + Flask — Современный веб-интерфейс

И такое бывает
И такое бывает

1. Почему Bash перестал устраивать?

  • Масштабируемость: добавление новых функций (например, выбор версии ALD Pro) усложняло код.

  • Удобство: администраторы хотели удалённо запускать установку, а не подключаться к серверу через SSH.

  • Интерактивность: нужен был прогресс-бар, логирование и возможность перезапуска прерванной установки.

2. Архитектура Flask-приложения

Рассмотрим ключевые компоненты:

2.1. REST API для управления установкой

@app.route('/installation_progress')
def get_progress():
    if installation_progress.get('is_complete') and installation_progress.get('access_info'):
        return jsonify({
            'status': 'complete',
            'access_info': installation_progress['access_info'],
            'progress': installation_progress
        })
    return jsonify(installation_progress)

def start_installation(config_data):
    try:
        # New Step 0: Configure ALDPro repository
        ...
  • Запуск установки в отдельном потоке, чтобы не блокировать веб-интерфейс.

2.2. Прогресс в реальном времени

@app.route('/submit', methods=['POST'])  
# Reset progress and SAVE CONFIG DATA
global installation_progress
installation_progress = {
    'current_step': 0,
    'total_steps': 9,
    'steps': [
        {'name': 'Настройка репозитория ALD Pro и имени хоста', 'status': 'pending', 'start_time': None, 'end_time': None},
        {'name': 'Настройка сети и файла hosts', 'status': 'pending', 'start_time': None, 'end_time': None},
        {'name': 'Обновление системы', 'status': 'pending', 'start_time': None, 'end_time': None},
        {'name': 'Установка компонентов ALD Pro', 'status': 'pending', 'start_time': None, 'end_time': None},
        {'name': 'Установка ALD Pro', 'status': 'pending', 'start_time': None, 'end_time': None},
        {'name': 'Отключение DNSSEC', 'status': 'pending', 'start_time': None, 'end_time': None},
        {'name': 'Перезапуск служб', 'status': 'pending', 'start_time': None, 'end_time': None},
        {'name': 'Очистка', 'status': 'pending', 'start_time': None, 'end_time': None},
    ],
    'start_time': None,
    'end_time': None,
    'is_complete': False,
    'error': None,
    'config_data': config_data  # Сохраняем конфигурацию для возможного повтора
}
threading.Thread(target=start_installation, args=(config_data,)).start()
return render_template('progress.html', reboot_info=REBOOT_INFO)
  • Frontend (progress.html) опрашивает этот endpoint и обновляет прогресс.

2.3. Основной процесс установки

@app.route('/installation_progress')
def get_progress():
    if installation_progress.get('is_complete') and installation_progress.get('access_info'):
        return jsonify({
            'status': 'complete',
            'access_info': installation_progress['access_info'],
            'progress': installation_progress
        })
    return jsonify(installation_progress)

def start_installation(config_data):
    try:
        # New Step 0: Configure ALDPro repository
        update_progress(0, 'in_progress')
        time.sleep(1)
        
        version = config_data['version_aldpro']
        repo_content = ""
        
        if version == "2.5.0":
            repo_content = "deb https://dl.astralinux.ru/aldpro/frozen/01/2.5.0/ 1.7_x86-64 main base"
        elif version == "2.4.1":
            repo_content = "deb https://dl.astralinux.ru/aldpro/frozen/01/2.4.1/ 1.7_x86-64 main base"
        elif version == "2.4.0":
            repo_content = "deb https://dl.astralinux.ru/aldpro/frozen/01/2.4.0/ 1.7_x86-64 main base"
        elif version == "2.3.0":
            repo_content = "deb https://dl.astralinux.ru/aldpro/frozen/01/2.3.0 1.7_x86-64 main base"
        elif version == "local":
            repo_content = config_data['local_repo_path']
        else:
            raise Exception(f"Неизвестная версия ALDPro: {version}")
        
        # Write repository file
        with open("/etc/apt/sources.list.d/aldpro.list", "w") as f:
            f.write(repo_content + "\n")  
        # Меняем имя хоста
        fqdn = config_data.get('fqdn', '')
        if fqdn:
            cmd = f"hostnamectl set-hostname {fqdn}"
            success, output = run_command(cmd)
            if not success:
                raise Exception(f"Ошибка смены имени хоста: {output}")                  
        update_progress(0, 'completed')
        time.sleep(1)
        
        # Step 1: Network config
        update_progress(1, 'in_progress')
        time.sleep(1)
            # Определяем активный сетевой интерфейс
        get_active_iface_cmd = "ip route | grep default | awk '{print $5}' | head -n 1"
        success, output = run_command(get_active_iface_cmd)
        if not success or not output.strip():
            raise Exception("Не удалось определить активный сетевой интерфейс")
        
        active_interface = output.strip()
        # Проверяем, активен ли NetworkManager
        nm_active = False
        check_nm_cmd = "systemctl is-active NetworkManager"
        success, output = run_command(check_nm_cmd)
        
        if success and output.strip() == "active":
            nm_active = True
        
        commands = []
        
        # Если NetworkManager активен, добавляем команды для его отключения
        if nm_active:
            commands.extend([
                "systemctl stop NetworkManager",
                "systemctl disable NetworkManager",
                "systemctl mask NetworkManager",
                # Ждем немного, чтобы служба точно остановилась
                "sleep 2"
            ])
        
        # Настраиваем статический IP в /etc/network/interfaces
        interfaces_content = f"""# This file describes the network interfaces available on your system
source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback
auto {active_interface}
iface {active_interface} inet static
address {config_data['ipaddres']}
netmask {config_data['mask']}
gateway {config_data['gateway']}

source /etc/network/interfaces.d/*.cfg"""
        
        # Добавляем остальные команды настройки сети
        commands.extend([
            f"echo '{interfaces_content}' > /etc/network/interfaces",
            f"sed -i 's/dns-nameservers {config_data.get('dns', '')}/dns-nameservers 127.0.0.1/g' /etc/network/interfaces",
            f"echo -e 'search {config_data.get('big_fqdn', '')}' > /etc/resolv.conf",
            f"echo -e 'nameserver {config_data.get('dns', '')}' >> /etc/resolv.conf",
            # Очистка старых записей localhost
            "sed -i '/^127\\.0\\.1\\.1/d' /etc/hosts",
            # Добавление новых записей
            f"echo '{config_data['ipaddres']} {config_data['fqdn']} {config_data['small_fqdn']}' >> /etc/hosts",
            # Перезапускаем сеть для применения изменений
            "systemctl restart networking.service",
            # Ждем применения настроек сети
            "sleep 5"
        ])
        
        for cmd in commands:
            success, output = run_command(cmd)
            if not success:
                # Для systemctl mask ошибка может быть ожидаемой, если служба уже замаскирована
                if not (nm_active and "mask" in cmd and "is already masked" in output):
                    raise Exception(f"Ошибка настройки сети: {output}")
            time.sleep(0.5)

        update_progress(1, 'completed')
        time.sleep(1)
        
# и остальные шаги
  • Каждый этап фиксируется в объекте installation_progress.

  • При ошибке можно перезапустить конкретный шаг.

2.4. Валидация данных

<div class="form-group">
    <label for="passwd_dom">Пароль администратора домена:
        <span class="help-icon">?
            <span class="tooltip">Пароль должен содержать: минимум 8 символов, заглавные и строчные буквы, цифры</span>
        </span>
    </label>
    <input type="password" id="passwd_dom" name="passwd_dom" value="{{ config.passwd_dom }}" 
        pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" 
        title="Пароль должен содержать минимум 8 символов, включая заглавные и строчные буквы, и цифры" 
        required
        oninput="validatePassword()">
    
    <div class="password-requirements">
        <div class="requirement">
            <span id="length-icon" class="requirement-icon">❌</span>
            <span>Минимум 8 символов</span>
        </div>
        <div class="requirement">
            <span id="uppercase-icon" class="requirement-icon">❌</span>
            <span>Хотя бы одна заглавная буква</span>
        </div>
        <div class="requirement">
            <span id="lowercase-icon" class="requirement-icon">❌</span>
            <span>Хотя бы одна строчная буква</span>
        </div>
        <div class="requirement">
            <span id="number-icon" class="requirement-icon">❌</span>
            <span>Хотя бы одна цифра</span>
        </div>
    </div>
</div>
  • Проверка паролей перед запуском.

3. Преимущества перед Bash

Гибкость:

  • Можно добавлять новые функции без переписывания всей логики.

  • Поддержка разных версий ALD Pro через конфигурацию.

Удобство:

  • Веб-интерфейс доступен с любого устройства в сети.

  • Прогресс-бар, логи и возможность повтора неудачного шага.

Надёжность:

  • Обработка ошибок на каждом этапе.

  • Возможность перезапуска прерванной установки.

4. Итог

Переход на Python + Flask превратил простой скрипт в полноценный инструмент администрирования, который:

  • экономит время,

  • уменьшает количество ошибок,

  • даёт больше контроля над процессом.

5. Новый интерфейс

проверка исходной системы
проверка исходной системы
дисклеймер
дисклеймер
форма для заполнения
форма для заполнения
прогресс бар
прогресс бар

Источники

Если вы дочитали до конца, то исходники можно посмотреть:

GitFlick - исходники кода, и собранные deb-пакеты;

YouTube - видео по работе web-версии установщика;

RuTube - видео на отечественной площадке по работе web-версии установщика;

Tags:
Hubs:
+2
Comments0

Articles