вторник, 23 декабря 2025 г.

Debian: Настройка VMware VM Customization Specifications для ОС Debian GNU/Linux

К сожалению, при использовании "VM Customization Specifications" при развертывании ОС Debian скрипты "open-vm-toos" не могут корректно сконфигурировать некоторые параметры для данной системы.
Чтобы решить эту проблему, применим скрипт, который возьмет на себя функционал, не корректно работающий в самих тулзах.

Выполним следующие шаги для настройки "VM Customization Specifications":

1. Подготавливаем шаблон ОС Debian с установкой необходимого ПО, которое уже должно быть в комплекте с новыми хостами.
2. Обязательно устанавливаем "open-vm-tools" и проверяем, что служба "vmtoolsd" находится в автозапуске:
sudo apt-get install open-vm-tools
sudo systemctl enable vmtoolsd && sudo systemctl start vmtoolsd


3. Включаем поддержку скриптов для кастомизации:
sudo vmware-toolbox-cmd config set deployPkg enable-custom-scripts true

Для проверки текущего состояния параметра выполните:
sudo vmware-toolbox-cmd config get deployPkg enable-custom-scripts

4. Далее настраиваем "VM Customization Specifications" на vCenter Server. При настройке указываем: тип ОС - Linux, правило формирования имени хоста, часовой пояс, TCP/IP настройки и добавляем следующий скрипт:

#!/bin/bash

# Функция для преобразования маски подсети в префикс CIDR
mask2cidr() {
    local mask=$1
    local n=0
    IFS=.

    for byte in $mask; do
        case $byte in
            255) n=$((n+8));;
            254) n=$((n+7));;
            252) n=$((n+6));;
            248) n=$((n+5));;
            240) n=$((n+4));;
            224) n=$((n+3));;
            192) n=$((n+2));;
            128) n=$((n+1));;
            0);;
            *) echo 24; return 1;;
        esac
    done

    echo $n
}

# Функция определения метода управления сетью с приоритетом: NM -> SystemD -> Ifupdown
get_network_manager() {
    local interface=$1

    # 1. Проверяем NetworkManager в первую очередь
    if command -v nmcli >/dev/null 2>&1; then
        # Проверяем, управляется ли интерфейс через NetworkManager
        if nmcli -t -f DEVICE device 2>/dev/null | grep -q "^${interface}$"; then
            echo "NetworkManager"
            return
        fi

        # Проверяем, активен ли NetworkManager как служба
        if systemctl is-active NetworkManager >/dev/null 2>&1; then
            echo "NetworkManager"
            return
        fi

        # Проверяем, есть ли соединение NetworkManager для этого интерфейса
        if nmcli -t -f NAME,DEVICE connection show 2>/dev/null | grep -q ":${interface}$"; then
            echo "NetworkManager"
            return
        fi
    fi

    # 2. Проверяем systemd-networkd
    if systemctl is-active systemd-networkd >/dev/null 2>&1 && \
       systemctl is-active systemd-resolved >/dev/null 2>&1; then
        echo "SystemD"
        return
    fi

    # 3. Проверяем ifupdown (традиционный метод Debian)
    if [ -f "/etc/network/interfaces" ]; then
        # Проверяем, упоминается ли интерфейс в конфигурации
        if grep -q -E "^(auto|allow-hotplug|iface|mapping).*${interface}" /etc/network/interfaces; then
            echo "Ifupdown"
            return
        fi

        # Проверяем наличие конфигов в interfaces.d
        if [ -d "/etc/network/interfaces.d" ]; then
            for config in /etc/network/interfaces.d/*; do
                if [ -f "$config" ] && grep -q "${interface}" "$config"; then
                    echo "Ifupdown"
                    return
                fi
            done
        fi
    fi

    # Проверяем, активна ли служба networking (ifupdown)
    if systemctl is-active networking >/dev/null 2>&1; then
        echo "Ifupdown"
        return
    fi

    # 4. Если ничего не найдено, проверяем по наличию служб
    if systemctl is-enabled NetworkManager >/dev/null 2>&1; then
        echo "NetworkManager"
    elif systemctl is-enabled systemd-networkd >/dev/null 2>&1; then
        echo "SystemD"
    elif systemctl is-enabled networking >/dev/null 2>&1; then
        echo "Ifupdown"
    else
        # Если ничего не определено, используем ifupdown как запасной вариант
        echo "Ifupdown"
    fi
}

# Функция настройки NetworkManager
configure_networkmanager() {
    local interface=$1
    local bootproto=$2
    local ipaddr=$3
    local netmask=$4
    local gateway=$5
    local dns_servers=$6
    local domain=$7

    # Удаляем существующее соединение для этого интерфейса
    nmcli connection delete "$interface" 2>/dev/null || true
    nmcli connection delete "Wired connection 1" 2>/dev/null || true
    nmcli connection delete "System ${interface}" 2>/dev/null || true

    # Создаем новое соединение
    if [ "$bootproto" = "static" ]; then
        local prefix_length=$(mask2cidr "$netmask")
        nmcli con add type ethernet con-name "$interface" ifname "$interface" ipv4.method manual \
            ipv4.addresses "${ipaddr}/${prefix_length}" \
            ipv4.gateway "$gateway" \
            ipv4.dns "$dns_servers" \
            ipv4.dns-search "$domain" \
            autoconnect yes
    else
        nmcli con add type ethernet con-name "$interface" ifname "$interface" \
            ipv4.method auto autoconnect yes
    fi

    # Активируем соединение
    nmcli con up "$interface"
}

# Функция настройки SystemD networkd
configure_systemd() {
    local interface=$1
    local bootproto=$2
    local ipaddr=$3
    local netmask=$4
    local gateway=$5
    local dns_servers=$6
    local domain=$7
    local dns_from_dhcp=$8

    # Убедимся, что служба активна
    systemctl enable systemd-networkd 2>/dev/null || true
    systemctl start systemd-networkd 2>/dev/null || true

    # Очищаем каталог /etc/systemd/network
    rm -rf /etc/systemd/network/*

    # Создаем базовую конфигурацию для loopback-интерфейса
    mkdir -p /etc/systemd/network
    cat > /etc/systemd/network/00-loopback.network <<EOF
[Match]
Name=lo

[Network]
Address=127.0.0.1/8
Address=::1/128
EOF

    # Создаем конфигурацию для основного интерфейса
    CONFIG_FILE="/etc/systemd/network/10-${interface}.network"

    if [ "$bootproto" = "static" ]; then
        if [ -z "$ipaddr" ] || [ -z "$netmask" ]; then
            echo "Ошибка: для статической настройки нужны IPADDR и NETMASK"
            exit 1
        fi

        prefix_length=$(mask2cidr "$netmask")
        if [ -z "$prefix_length" ]; then
            prefix_length=24
        fi

        # Создаем конфиг для статического IP
        cat > "$CONFIG_FILE" <<EOF
[Match]
Name=$interface

[Network]
Address=$ipaddr/$prefix_length
EOF

        if [ -n "$gateway" ]; then
            echo "Gateway=$gateway" >> "$CONFIG_FILE"
        fi

        # Добавляем DNS серверы
        if [ -n "$dns_servers" ]; then
            for dns in $dns_servers; do
                echo "DNS=$dns" >> "$CONFIG_FILE"
            done
        fi
    else
        # Создаем конфиг для DHCP
        cat > "$CONFIG_FILE" <<EOF
[Match]
Name=$interface

[Network]
DHCP=ipv4

[DHCPv4]
UseDomains=true
EOF

        # Настраиваем использование DNS
        if [ "$dns_from_dhcp" = "yes" ]; then
            echo "UseDNS=yes" >> "$CONFIG_FILE"
        else
            echo "UseDNS=no" >> "$CONFIG_FILE"
            if [ -n "$dns_servers" ]; then
                for dns in $dns_servers; do
                    echo "DNS=$dns" >> "$CONFIG_FILE"
                done
            fi
        fi
    fi

    # Настраиваем systemd-resolved если нужно
    if [ -n "$dns_servers" ] || [ -n "$domain" ]; then
        mkdir -p /etc/systemd/resolved.conf.d
        cat > /etc/systemd/resolved.conf.d/10-${interface}.conf <<EOF
[Resolve]
EOF

        if [ -n "$dns_servers" ]; then
            echo "DNS=$dns_servers" >> /etc/systemd/resolved.conf.d/10-${interface}.conf
        fi

        if [ -n "$domain" ]; then
            echo "Domains=$domain" >> /etc/systemd/resolved.conf.d/10-${interface}.conf
        fi

        systemctl enable systemd-resolved 2>/dev/null || true
        systemctl start systemd-resolved 2>/dev/null || true

        # Создаем симлинк для resolv.conf
        ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf 2>/dev/null || true
    fi

    systemctl restart systemd-networkd

    if systemctl is-active systemd-resolved >/dev/null 2>&1; then
        systemctl restart systemd-resolved
    fi
}

# Функция настройки Ifupdown (/etc/network/interfaces)
configure_ifupdown() {
    local interface=$1
    local bootproto=$2
    local ipaddr=$3
    local netmask=$4
    local gateway=$5
    local dns_servers=$6
    local domain=$7

    echo "Configuring interface $interface using ifupdown" >&2

    # Создаем базовый файл interfaces если его нет
    if [ ! -f /etc/network/interfaces ]; then
        mkdir -p /etc/network
        cat > /etc/network/interfaces <<EOF
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback
EOF
    fi

    # Удаляем ВСЮ конфигурацию для этого интерфейса
    # Создаем временный файл без конфигурации текущего интерфейса
    tmpfile=$(mktemp)
    in_block=0

    while IFS= read -r line; do
        # Проверяем, начинается ли новый блок интерфейса
        if [[ "$line" =~ ^(auto|allow-hotplug|iface)[[:space:]]+$interface([[:space:]]|$) ]]; then
            in_block=1
            continue
        fi

        # Если мы внутри блока и встречаем начало другого блока, выходим из блока
        if [[ $in_block -eq 1 && "$line" =~ ^(auto|allow-hotplug|iface)[[:space:]]+ ]]; then
            in_block=0
        fi

        # Если мы не внутри блока удаляемого интерфейса, сохраняем строку
        if [[ $in_block -eq 0 ]]; then
            echo "$line" >> "$tmpfile"
        fi
    done < /etc/network/interfaces

    # Копируем обратно и удаляем временный файл
    mv "$tmpfile" /etc/network/interfaces
    chmod 644 /etc/network/interfaces

    # Удаляем возможные повторяющиеся пустые строки
    sed -i '/^$/N; /^\n$/D' /etc/network/interfaces
    sed -i -e :a -e '/^\n*$/{$d;N;ba' -e '}' /etc/network/interfaces

    # Добавляем новую конфигурацию
    {
        echo ""
        echo "auto $interface"
        if [ "$bootproto" = "static" ]; then
            echo "iface $interface inet static"
            echo "    address $ipaddr"
            echo "    netmask $netmask"
            [ -n "$gateway" ] && echo "    gateway $gateway"
            [ -n "$dns_servers" ] && echo "    dns-nameservers $dns_servers"
            [ -n "$domain" ] && echo "    dns-search $domain"
        else
            echo "iface $interface inet dhcp"
        fi
    } >> /etc/network/interfaces

    # Настраиваем DNS через resolvconf если установлен
    if command -v resolvconf >/dev/null 2>&1; then
        # Создаем временный файл для resolvconf
        TMP_RESOLV="/tmp/resolv.conf.${interface}"
        echo "# Generated by network configuration script" > "$TMP_RESOLV"

        if [ -n "$domain" ]; then
            echo "domain $domain" >> "$TMP_RESOLV"
            echo "search $domain" >> "$TMP_RESOLV"
        fi

        if [ -n "$dns_servers" ]; then
            for dns in $dns_servers; do
                echo "nameserver $dns" >> "$TMP_RESOLV"
            done
        fi

        # Обновляем resolvconf
        resolvconf -a "${interface}" < "$TMP_RESOLV" 2>/dev/null || true
        rm -f "$TMP_RESOLV"
    else
        # Прямая настройка /etc/resolv.conf
        if [ -n "$dns_servers" ] || [ -n "$domain" ]; then
            echo "# Generated by network configuration script" > /etc/resolv.conf

            if [ -n "$domain" ]; then
                echo "domain $domain" >> /etc/resolv.conf
                echo "search $domain" >> /etc/resolv.conf
            fi

            if [ -n "$dns_servers" ]; then
                for dns in $dns_servers; do
                    echo "nameserver $dns" >> /etc/resolv.conf
                done
            fi
        fi
    fi

    # Перезапускаем сетевую службу
    if systemctl is-active networking >/dev/null 2>&1; then
        systemctl restart networking
    else
        # Старый метод перезапуска сети
        ifdown "$interface" 2>/dev/null || true
        ifup "$interface" 2>/dev/null || true
    fi
}

# --- Функция очистки логов и временных файлов ---
clean_system() {
    # Очистка логов (с сохранением структуры каталогов)
    find /var/log -type f -name "*.log" -exec truncate -s 0 {} \; 2>/dev/null || true
    find /var/log -type f -name "*.gz" -delete 2>/dev/null || true
    find /var/log -type f -name "*.old" -delete 2>/dev/null || true
    find /var/log -type f -name "lastlog" -exec rm -f {} \; 2>/dev/null || true

    # Очистка временных файлов
    rm -rf /tmp/* 2>/dev/null || true
    rm -rf /var/tmp/* 2>/dev/null || true

    # Очистка истории команд и пользовательских данных
    for user_home in /home/*; do
        if [[ -d "$user_home" ]]; then
            user=$(basename "$user_home")
            # Очистка bash_history и других историй
            truncate -s 0 "$user_home/.bash_history" 2>/dev/null || true
            # Очистка кэшей приложений
            rm -rf "$user_home/.cache/*" 2>/dev/null || true
        fi
    done

    # Очистка root
    truncate -s 0 /root/.bash_history 2>/dev/null || true
    rm -rf /root/.cache/* 2>/dev/null || true

    # Удаление случайных seed-файлов
    rm -f /var/lib/systemd/random-seed 2>/dev/null || true
}

# --- Функция удаления SSH host keys ---
reset_ssh_keys() {
    rm -f /etc/ssh/ssh_host_* 2>/dev/null || true

    # Регенерация ключей SSH-сервера
    if command -v dpkg-reconfigure >/dev/null 2>&1; then
        dpkg-reconfigure -f noninteractive openssh-server 2>/dev/null || true
    fi

    # Альтернативный способ для Debian
    if [ -x /usr/sbin/sshd ]; then
        /usr/sbin/sshd-keygen -A 2>/dev/null || true
    fi
}

# ============================================================
# Основная логика скрипта
# ============================================================
if [ "$1" = "precustomization" ]; then
    # Выполняем очистку системы
    clean_system

    # Проверяем и создаем /etc/network/interfaces
    if [ ! -f /etc/network/interfaces ]; then
        echo "Creating /etc/network/interfaces file"
        mkdir -p /etc/network
        cat > /etc/network/interfaces <<EOF
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback
EOF
    fi

    # Проверяем и создаем директорию /etc/sysconfig/network-scripts если нужно
    if [ ! -d "/etc/sysconfig/network-scripts" ]; then
        mkdir -p /etc/sysconfig/network-scripts 2>/dev/null || true
    fi

    # Этап предварительной настройки
    VMCUST_DIR=$(ls -d /var/run/.vmware-imgcust* 2>/dev/null | head -n 1)
    if [ -n "$VMCUST_DIR" ]; then
        CUST_CFG_PATH="$VMCUST_DIR/cust.cfg"
        if [ -f "$CUST_CFG_PATH" ]; then
            cp "$CUST_CFG_PATH" "/root/cust.cfg"
        fi
    fi

elif [ "$1" = "postcustomization" ]; then
    # Этап пост-настройки
    reset_ssh_keys

    if [ ! -f "/root/cust.cfg" ]; then
        echo "Ошибка: файл конфигурации /root/cust.cfg не найден"
        exit 1
    fi

    CFG_FILE="/root/cust.cfg"

    # Парсим параметры конфигурации
    BOOTPROTO=$(awk -F' = ' '/^BOOTPROTO/ {line=$2} END{print tolower(line)}' "$CFG_FILE")
    IPADDR=$(awk -F' = ' '/^IPADDR/ {line=$2} END{print line}' "$CFG_FILE")
    NETMASK=$(awk -F' = ' '/^NETMASK/ {line=$2} END{print line}' "$CFG_FILE")
    GATEWAY=$(awk -F' = ' '/^GATEWAY/ {line=$2} END{print line}' "$CFG_FILE")
    HOSTNAME=$(awk -F' = ' '/^HOSTNAME/ {line=$2} END{print line}' "$CFG_FILE")
    DOMAIN=$(awk -F' = ' '/^DOMAINNAME/ {line=$2} END{print line}' "$CFG_FILE")
    MACADDR=$(awk -F' = ' '/^MACADDR/ {line=$2} END{print tolower(line)}' "$CFG_FILE")
    DNS_SERVERS=$(awk -F' = ' '/^NAMESERVER\|[0-9]/ {print $2}' "$CFG_FILE" | tr '\n' ' ' | sed 's/ $//')
    DNS_FROM_DHCP=$(awk -F' = ' '/^DNSFROMDHCP/ {line=$2} END{print tolower(line)}' "$CFG_FILE")
    TIMEZONE=$(awk -F' = ' '/^TIMEZONE/ {line=$2} END{print line}' "$CFG_FILE")
    UTC=$(awk -F' = ' '/^UTC/ {line=$2} END{print tolower(line)}' "$CFG_FILE")

    # Определяем сетевой интерфейс
    INTERFACE=$(ip -o link | awk -v mac="$MACADDR" 'tolower($0) ~ mac {gsub(":", "", $2); print $2}')

    if [ -z "$INTERFACE" ]; then
        INTERFACE=$(ip route | awk '/default/ {print $5; exit}')
    fi

    if [ -z "$INTERFACE" ]; then
        # Последняя попытка: берем первый Ethernet интерфейс
        INTERFACE=$(ip -o link | awk -F': ' '$2 !~ /^lo|virbr|docker|veth/ {print $2; exit}' | sed 's/@.*//')
    fi

    if [ -z "$INTERFACE" ]; then
        echo "Ошибка: не удалось определить сетевой интерфейс"
        exit 1
    fi

    # Определяем метод управления сетью
    NET_MGR=$(get_network_manager "$INTERFACE")
    echo "Определен метод управления сетью: $NET_MGR для интерфейса $INTERFACE"

    case "$NET_MGR" in
        "NetworkManager")
            configure_networkmanager "$INTERFACE" "$BOOTPROTO" "$IPADDR" "$NETMASK" \
                "$GATEWAY" "$DNS_SERVERS" "$DOMAIN"
            ;;
        "SystemD")
            configure_systemd "$INTERFACE" "$BOOTPROTO" "$IPADDR" "$NETMASK" \
                "$GATEWAY" "$DNS_SERVERS" "$DOMAIN" "$DNS_FROM_DHCP"
            ;;
        "Ifupdown")
            configure_ifupdown "$INTERFACE" "$BOOTPROTO" "$IPADDR" "$NETMASK" \
                "$GATEWAY" "$DNS_SERVERS" "$DOMAIN"
            ;;
        *)
            echo "Неизвестный метод управления сетью, используем ifupdown как запасной вариант"
            configure_ifupdown "$INTERFACE" "$BOOTPROTO" "$IPADDR" "$NETMASK" \
                "$GATEWAY" "$DNS_SERVERS" "$DOMAIN"
            ;;
    esac

    # Общие настройки
    hostnamectl set-hostname "$HOSTNAME" 2>/dev/null || echo "$HOSTNAME" > /etc/hostname
    sed -i "/127\.0\.1\.1/d" /etc/hosts
    echo "127.0.1.1 $HOSTNAME.$DOMAIN $HOSTNAME" >> /etc/hosts

    # Настройка часового пояса
    if [ -n "$TIMEZONE" ]; then
        timedatectl set-timezone "$TIMEZONE" 2>/dev/null || true

        # Альтернативный способ для Debian
        if [ -f "/usr/share/zoneinfo/$TIMEZONE" ]; then
            cp "/usr/share/zoneinfo/$TIMEZONE" /etc/localtime 2>/dev/null || true
            echo "$TIMEZONE" > /etc/timezone 2>/dev/null || true
        fi
    fi

    # Настройка формата времени BIOS (UTC или локальное)
    if [ -n "$UTC" ]; then
        if [ "$UTC" = "yes" ]; then
            # Устанавливаем UTC время в BIOS
            timedatectl set-local-rtc 0 2>/dev/null || true
        else
            # Устанавливаем локальное время в BIOS
            timedatectl set-local-rtc 1 2>/dev/null || true
        fi
    fi

    # Сохраняем конфигурацию
    CONFIG_LOG="/root/vm-customization.log"
    {
        echo "=========================================="
        echo "Настройка завершена: $(date)"
        echo "=========================================="
        echo "CONFIG_SOURCE=$CFG_FILE"
        echo "BOOTPROTO=$BOOTPROTO"
        echo "INTERFACE=$INTERFACE"
        echo "NETWORK_MANAGER=$NET_MGR"
        echo "HOSTNAME=$HOSTNAME"
        echo "DOMAIN=$DOMAIN"
        echo "TIMEZONE=$TIMEZONE"
        echo "UTC=$UTC"

        [ -n "$DNS_FROM_DHCP" ] && echo "DNS_FROM_DHCP=$DNS_FROM_DHCP"
        [ -n "$DNS_SERVERS" ] && echo "DNS_SERVERS=\"$DNS_SERVERS\""

        if [ "$BOOTPROTO" = "static" ]; then
            echo "IP_ADDRESS=$IPADDR"
            echo "NETMASK=$NETMASK"
            [ -n "$GATEWAY" ] && echo "GATEWAY=$GATEWAY"
        fi
    } > "$CONFIG_LOG"

    rm -f "/root/cust.cfg"

    # Синхронизируем изменения на диск
    sync

    echo "Настройка завершена успешно. Метод управления сетью: $NET_MGR"

    ##############################################################################################
    # МЕСТО ДЛЯ ДОПОЛНИТЕЛЬНОЙ НАСТРОЙКИ СИСТЕМЫ
    # Здесь можно добавить команды для окончательной настройки машины:
    # - Доустановка необходимого программного обеспечения
    # - Настройка через Ansible или другие системы управления конфигурациями
    # - Выполнение пользовательских скриптов
    # - Настройка мониторинга, логирования и других сервисов

    # Пример: установка базовых пакетов
    # apt-get update && apt-get install -y curl wget net-tools

    ##############################################################################################
fi

Данный скрипт охватывает все возможные варианты настройки сети: IfUpDown, NetworkManager, Systemd-Networkd, как со статическим IP, так и с DHCP.

Комментариев нет:

Отправить комментарий