#!/usr/bin/env bash
#
# add-ip.sh — Автоматическое добавление IP-адресов на Linux-сервер
#
# Поддерживает: Ubuntu (netplan), Debian/Astra (/etc/network/interfaces),
#               CentOS/AlmaLinux (NetworkManager/nmcli), Arch Linux (systemd-networkd)
#
# Использование:
#   sudo bash add-ip.sh
#
# Скрипт запросит нужные данные и применит конфигурацию автоматически.

set -euo pipefail

# ─── Цвета ───────────────────────────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'

info()  { echo -e "${CYAN}[INFO]${NC} $*"; }
ok()    { echo -e "${GREEN}[OK]${NC} $*"; }
warn()  { echo -e "${YELLOW}[WARN]${NC} $*"; }
err()   { echo -e "${RED}[ERROR]${NC} $*" >&2; }
die()   { err "$@"; exit 1; }

# ─── Проверка root ───────────────────────────────────────────────────────────
if [[ $EUID -ne 0 ]]; then
    die "Скрипт нужно запускать от root: sudo bash $0"
fi

# ─── Определение дистрибутива ────────────────────────────────────────────────
detect_distro() {
    if [[ -f /etc/os-release ]]; then
        . /etc/os-release
        DISTRO_ID="${ID:-unknown}"
        DISTRO_LIKE="${ID_LIKE:-}"
    else
        die "Не удалось определить дистрибутив (/etc/os-release не найден)"
    fi

    case "$DISTRO_ID" in
        ubuntu)           DISTRO_FAMILY="ubuntu" ;;
        debian)           DISTRO_FAMILY="debian" ;;
        astra)            DISTRO_FAMILY="debian" ;;
        centos|almalinux|rocky|rhel|fedora)
                          DISTRO_FAMILY="centos" ;;
        arch|manjaro|endeavouros)
                          DISTRO_FAMILY="arch" ;;
        *)
            # Fallback: проверяем по ID_LIKE
            if [[ "$DISTRO_LIKE" == *"debian"* ]]; then
                DISTRO_FAMILY="debian"
            elif [[ "$DISTRO_LIKE" == *"rhel"* || "$DISTRO_LIKE" == *"centos"* ]]; then
                DISTRO_FAMILY="centos"
            else
                die "Неподдерживаемый дистрибутив: $DISTRO_ID ($DISTRO_LIKE)"
            fi
            ;;
    esac
    info "Дистрибутив: $DISTRO_ID (семейство: $DISTRO_FAMILY)"
}

# ─── Определение интерфейса ─────────────────────────────────────────────────
detect_interface() {
    # Берём первый нелупбэк интерфейс с IPv4 или IPv6 адресом (кроме виртуальных)
    IFACE=$(ip -o link show up | awk -F': ' '{print $2}' | grep -v '^lo$' | grep -v '^docker' | grep -v '^br-' | grep -v '^veth' | head -n1)
    if [[ -z "$IFACE" ]]; then
        die "Не удалось определить сетевой интерфейс. Укажите вручную."
    fi
    info "Сетевой интерфейс: $IFACE"
}

# ─── Сбор данных от пользователя ────────────────────────────────────────────
collect_data() {
    echo ""
    echo -e "${CYAN}══════════════════════════════════════════════════════════════${NC}"
    echo -e "${CYAN}  Добавление IP-адресов на сервер${NC}"
    echo -e "${CYAN}══════════════════════════════════════════════════════════════${NC}"
    echo ""

    # Текущие адреса
    info "Текущие IP-адреса на интерфейсе $IFACE:"
    ip addr show "$IFACE" | grep -E 'inet ' | sed 's/^/    /'
    echo ""

    # IPv4
    read -rp "Добавить IPv4-адрес? (например 192.0.2.70/24) [пусто = пропустить]: " IPV4_ADDR
    if [[ -n "$IPV4_ADDR" ]]; then
        # Извлекаем префикс если не указан
        if [[ "$IPV4_ADDR" != *"/"* ]]; then
            IPV4_ADDR="${IPV4_ADDR}/24"
            warn "Маска не указана, используем /24"
        fi
        read -rp "IPv4 шлюз (первый адрес в сети, например 192.0.2.1): " IPV4_GW
        [[ -z "$IPV4_GW" ]] && die "Шлюз обязателен для IPv4"
        HAS_IPV4=true
    else
        HAS_IPV4=false
    fi

    # IPv6
    read -rp "Добавить IPv6-адрес? (например 2001:db8:a:a::a/128) [пусто = пропустить]: " IPV6_ADDR
    if [[ -n "$IPV6_ADDR" ]]; then
        if [[ "$IPV6_ADDR" != *"/"* ]]; then
            IPV6_ADDR="${IPV6_ADDR}/128"
            warn "Маска не указана, используем /128"
        fi
        read -rp "IPv6 шлюз (например 2001:db8:a:a::1): " IPV6_GW
        [[ -z "$IPV6_GW" ]] && die "Шлюз обязателен для IPv6"
        HAS_IPV6=true
    else
        HAS_IPV6=false
    fi

    if ! $HAS_IPV4 && ! $HAS_IPV6; then
        die "Ни один адрес не указан. Выход."
    fi

    # DNS
    read -rp "DNS-серверы через запятую (по умолчанию 1.1.1.1,1.0.0.1): " DNS_INPUT
    if [[ -z "$DNS_INPUT" ]]; then
        DNS_V4=("1.1.1.1" "1.0.0.1")
        DNS_V6=("2606:4700:4700::1111" "2606:4700:4700::1001")
    else
        IFS=',' read -ra DNS_V4 <<< "$DNS_INPUT"
        DNS_V6=("${DNS_V4[@]}")
    fi

    # Подтверждение
    echo ""
    echo -e "${YELLOW}══════════════════════════════════════════════════════════════${NC}"
    echo -e "${YELLOW}  Итого:${NC}"
    echo -e "${YELLOW}  Интерфейс: ${NC}$IFACE"
    $HAS_IPV4 && echo -e "${YELLOW}  IPv4:      ${NC}$IPV4_ADDR  шлюз: $IPV4_GW"
    $HAS_IPV6 && echo -e "${YELLOW}  IPv6:      ${NC}$IPV6_ADDR  шлюз: $IPV6_GW"
    echo -e "${YELLOW}  DNS v4:    ${NC}${DNS_V4[*]}"
    echo -e "${YELLOW}  DNS v6:    ${NC}${DNS_V6[*]}"
    echo -e "${YELLOW}══════════════════════════════════════════════════════════════${NC}"
    echo ""
    read -rp "Всё верно? Применить? (y/N): " CONFIRM
    [[ "$CONFIRM" =~ ^[YyДд]$ ]] || die "Отмена пользователем."
}

# ─── Общие шаги: sysctl + cloud-init ────────────────────────────────────────
setup_sysctl_nonlocal_bind() {
    info "Настройка ip_nonlocal_bind..."
    local SYSCTL_FILE="/etc/sysctl.d/99-nonlocal-bind.conf"
    cat > "$SYSCTL_FILE" <<'EOF'
net.ipv4.ip_nonlocal_bind = 1
net.ipv6.ip_nonlocal_bind = 1
EOF
    sysctl --system >/dev/null 2>&1
    ok "ip_nonlocal_bind включён"
}

disable_cloud_init_network() {
    local CLOUD_CFG="/etc/cloud/cloud.cfg.d/99-disable-network-config.cfg"
    if [[ ! -f "$CLOUD_CFG" ]]; then
        info "Отключение cloud-init network config..."
        mkdir -p /etc/cloud/cloud.cfg.d/
        echo 'network: {config: disabled}' > "$CLOUD_CFG"
        ok "cloud-init network config отключён"
    else
        ok "cloud-init network config уже отключён"
    fi
}

# ─── Ubuntu (netplan) ───────────────────────────────────────────────────────
configure_ubuntu() {
    info "Конфигурация через netplan..."

    # Бэкап существующих файлов
    for f in /etc/netplan/*.yaml; do
        if [[ -f "$f" && "$f" != *"99-ipv"* ]]; then
            local backup="${f}-backup-$(date +%Y%m%d%H%M%S)"
            mv "$f" "$backup"
            info "Создан бэкап: $backup"
        fi
    done

    # --- IPv4 ---
    if $HAS_IPV4; then
        cat > /etc/netplan/99-ipv4.yaml <<YAML
network:
  version: 2
  renderer: networkd
  ethernets:
    ${IFACE}:
      dhcp4: false
      addresses:
        - "${IPV4_ADDR}"
      routes:
        - to: "0.0.0.0/0"
          via: "${IPV4_GW}"
      nameservers:
        addresses:
$(printf '          - "%s"\n' "${DNS_V4[@]}")
YAML
        chmod 600 /etc/netplan/99-ipv4.yaml
        ok "Создан /etc/netplan/99-ipv4.yaml"
    fi

    # --- IPv6 ---
    if $HAS_IPV6; then
        cat > /etc/netplan/99-ipv6.yaml <<YAML
network:
  version: 2
  renderer: networkd
  ethernets:
    ${IFACE}:
      accept-ra: false
      dhcp6: false
      addresses:
        - "${IPV6_ADDR}"
      routes:
        - to: "::/0"
          via: "${IPV6_GW}"
          on-link: true
      nameservers:
        addresses:
$(printf '          - "%s"\n' "${DNS_V6[@]}")
YAML
        chmod 600 /etc/netplan/99-ipv6.yaml
        ok "Создан /etc/netplan/99-ipv6.yaml"
    fi

    # Отключение dhcpcd6
    if systemctl list-unit-files | grep -q 'dhcpcd6.service'; then
        systemctl disable dhcpcd6.service 2>/dev/null || true
    fi
    if systemctl list-unit-files | grep -q 'dhclient6.service'; then
        systemctl disable dhclient6.service 2>/dev/null || true
    fi

    systemctl daemon-reload 2>/dev/null || true

    setup_sysctl_nonlocal_bind
    disable_cloud_init_network

    info "Применение netplan..."
    netplan --debug apply 2>&1 || warn "netplan apply вернул ошибку — попробуйте перезагрузку"
    ok "Netplan конфигурация применена"
}

# ─── Debian/Astra (/etc/network/interfaces) ──────────────────────────────────
configure_debian() {
    info "Конфигурация через /etc/network/interfaces..."

    local IFACE_FILE="/etc/network/interfaces"
    local BACKUP="${IFACE_FILE}-backup-$(date +%Y%m%d%H%M%S)"
    [[ -f "$IFACE_FILE" ]] && cp "$IFACE_FILE" "$BACKUP" && info "Бэкап: $BACKUP"

    # Генерируем новый конфиг
    {
        echo "auto lo"
        echo "iface lo inet loopback"
        echo ""

        if $HAS_IPV4; then
            echo "auto ${IFACE}"
            echo "allow-hotplug ${IFACE}"
            echo "iface ${IFACE} inet static"
            echo -e "\taddress ${IPV4_ADDR}"
            echo -e "\tgateway ${IPV4_GW}"
            echo -e "\tdns-nameservers ${DNS_V4[*]}"
            echo ""
        fi

        if $HAS_IPV6; then
            echo "auto ${IFACE}"
            echo "allow-hotplug ${IFACE}"
            echo "iface ${IFACE} inet6 static"
            echo -e "\taddress ${IPV6_ADDR}"
            echo -e "\tgateway ${IPV6_GW}"
            echo -e "\tdns-nameservers ${DNS_V6[*]}"
        fi
    } > "$IFACE_FILE"

    ok "Записан $IFACE_FILE"

    # Бэкап cloud-init файла
    local INTERFACES_D="/etc/network/interfaces.d"
    if [[ -d "$INTERFACES_D" ]]; then
        for f in "$INTERFACES_D"/*; do
            [[ -f "$f" ]] && mv "$f" "/root/$(basename "$f")-backup-$(date +%Y%m%d%H%M%S)" 2>/dev/null || true
        done
    fi

    # Отключение dhcpcd6
    if systemctl list-unit-files | grep -q 'dhcpcd6.service'; then
        systemctl disable dhcpcd6.service 2>/dev/null || true
    fi
    if systemctl list-unit-files | grep -q 'dhclient6.service'; then
        systemctl disable dhclient6.service 2>/dev/null || true
    fi

    systemctl daemon-reload 2>/dev/null || true

    setup_sysctl_nonlocal_bind
    disable_cloud_init_network

    info "Перезапуск networking..."
    systemctl restart networking.service 2>&1 || warn "networking restart вернул ошибку — попробуйте перезагрузку"
    ok "Конфигурация применена"
}

# ─── CentOS/AlmaLinux (NetworkManager / nmcli) ──────────────────────────────
configure_centos() {
    info "Конфигурация через NetworkManager (nmcli)..."

    # Определяем имя соединения
    local CONN_NAME
    CONN_NAME=$(nmcli -t -f NAME,DEVICE connection show | grep ":${IFACE}$" | head -n1 | cut -d: -f1)
    if [[ -z "$CONN_NAME" ]]; then
        die "Не удалось найти соединение для интерфейса $IFACE. Проверьте 'nmcli connection show'"
    fi
    info "Имя соединения: $CONN_NAME"

    # Бэкап текущих настроек
    local CONN_DIR="/etc/NetworkManager/system-connections"
    local CONN_FILE=""
    for f in "$CONN_DIR"/*; do
        if [[ -f "$f" ]] && grep -q "interface-name=${IFACE}" "$f" 2>/dev/null; then
            CONN_FILE="$f"
            break
        fi
    done
    if [[ -n "$CONN_FILE" ]]; then
        local BACKUP="${CONN_FILE}-backup-$(date +%Y%m%d%H%M%S)"
        cp "$CONN_FILE" "$BACKUP"
        info "Бэкап: $BACKUP"
    fi

    # IPv4
    if $HAS_IPV4; then
        nmcli connection modify "$CONN_NAME" ipv4.addresses "$IPV4_ADDR"
        nmcli connection modify "$CONN_NAME" ipv4.gateway "$IPV4_GW"
        nmcli connection modify "$CONN_NAME" ipv4.method manual
        nmcli connection modify "$CONN_NAME" ipv4.dns "${DNS_V4[0]}"
        for dns in "${DNS_V4[@]:1}"; do
            nmcli connection modify "$CONN_NAME" +ipv4.dns "$dns"
        done
        ok "IPv4 настроены: $IPV4_ADDR, шлюз $IPV4_GW"
    fi

    # IPv6
    if $HAS_IPV6; then
        nmcli connection modify "$CONN_NAME" ipv6.addresses "$IPV6_ADDR"
        nmcli connection modify "$CONN_NAME" ipv6.gateway "$IPV6_GW"
        nmcli connection modify "$CONN_NAME" ipv6.method manual
        nmcli connection modify "$CONN_NAME" ipv6.dns "${DNS_V6[0]}"
        for dns in "${DNS_V6[@]:1}"; do
            nmcli connection modify "$CONN_NAME" +ipv6.dns "$dns"
        done
        ok "IPv6 настроены: $IPV6_ADDR, шлюз $IPV6_GW"
    fi

    # Бэкап старых ifcfg файлов
    local NETSCRIPTS="/etc/sysconfig/network-scripts"
    if [[ -d "$NETSCRIPTS" ]]; then
        for f in "$NETSCRIPTS"/ifcfg-*; do
            [[ -f "$f" && "$f" != *"backup"* ]] && mv "$f" "/root/$(basename "$f")-backup-$(date +%Y%m%d%H%M%S)" 2>/dev/null || true
        done
    fi

    setup_sysctl_nonlocal_bind
    disable_cloud_init_network

    info "Перезапуск NetworkManager..."
    systemctl restart NetworkManager.service 2>&1 || warn "NetworkManager restart вернул ошибку — попробуйте перезагрузку"
    ok "Конфигурация применена"
}

# ─── Arch Linux (systemd-networkd) ──────────────────────────────────────────
configure_arch() {
    info "Конфигурация через systemd-networkd..."

    local NETDIR="/etc/systemd/network"
    mkdir -p "$NETDIR"

    # Бэкап старых конфигов
    for f in "$NETDIR"/*.network; do
        [[ -f "$f" ]] && mv "$f" "${f}_old" 2>/dev/null || true
    done

    # Включаем нужные сервисы
    systemctl enable --now systemd-networkd.service 2>/dev/null || true
    systemctl enable --now systemd-resolved.service 2>/dev/null || true

    # IPv4
    if $HAS_IPV4; then
        cat > "$NETDIR/10-static-ipv4.network" <<EOF
[Match]
Name=${IFACE}

[Network]
Address=${IPV4_ADDR}
Gateway=${IPV4_GW}
$(printf 'DNS=%s\n' "${DNS_V4[@]}")
DHCP=no
EOF
        ok "Создан $NETDIR/10-static-ipv4.network"
    fi

    # IPv6
    if $HAS_IPV6; then
        cat > "$NETDIR/20-static-ipv6.network" <<EOF
[Match]
Name=${IFACE}

[Network]
Address=${IPV6_ADDR}
$(printf 'DNS=%s\n' "${DNS_V6[@]}")
DHCP=no
IPv6AcceptRA=no

[Route]
Destination=::/0
Gateway=${IPV6_GW}
GatewayOnLink=yes
EOF
        ok "Создан $NETDIR/20-static-ipv6.network"
    fi

    setup_sysctl_nonlocal_bind

    info "Перезапуск systemd-networkd..."
    systemctl restart systemd-networkd 2>&1 || warn "systemd-networkd restart вернул ошибку — попробуйте перезагрузку"
    ok "Конфигурация применена"
}

# ─── Проверка результата ────────────────────────────────────────────────────
verify() {
    echo ""
    info "Проверяем IP-адреса на интерфейсе $IFACE:"
    echo ""
    ip addr show "$IFACE" | grep -E 'inet ' | sed 's/^/    /'
    echo ""
    ok "Готово! Рекомендуется перезагрузить сервер для проверки: sudo reboot"
}

# ─── main ────────────────────────────────────────────────────────────────────
main() {
    detect_distro
    detect_interface
    collect_data

    case "$DISTRO_FAMILY" in
        ubuntu) configure_ubuntu ;;
        debian) configure_debian ;;
        centos) configure_centos ;;
        arch)   configure_arch ;;
    esac

    verify
}

main "$@"
