Что такое 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

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-версии установщика;