#!/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 < /etc/netplan/99-ipv6.yaml </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" < "$NETDIR/20-static-ipv6.network" <&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 "$@"