#!/usr/bin/env bash # Statyczne IP na dwóch kartach Ethernet — GMKtec K11 (gmktec-k11) # # Skanuje link na eno1 / enp3s0, pokazuje menu CLI i generuje Netplan. # Rollback: scripts/restore-dhcp-network.sh set -euo pipefail NETPLAN_FILE="/etc/netplan/00-installer-config.yaml" NIC_A="eno1" NIC_B="enp3s0" IP80="192.168.100.80/24" IP90="192.168.100.90/24" GATEWAY="192.168.100.1" DNS="192.168.100.1" DRY_RUN=false NON_INTERACTIVE=false PROFILE="" # Po wyborze profilu (ustawiane przez apply_profile) GW_NIC="" ENO1_IPS="" ENP3S0_IPS="" PROFILE_NAME="" usage() { cat <<'EOF' Użycie: sudo ./configure-static-dual-nic.sh [opcje] Opcje: --dry-run Skan + menu (lub profil) + YAML, bez zapisu i apply --non-interactive Pomiń menu (wymaga --profile) --profile Profil konfiguracji (patrz niżej) -h, --help Ta pomoc Profile (--non-interactive): dual-eno1 eno1=.80+brama, enp3s0=.90 dual-enp3s0 enp3s0=.80+brama, eno1=.90 single-eno1-80 tylko eno1: .80+brama single-eno1-both tylko eno1: .80+.90+brama single-enp3s0-90 tylko enp3s0: .90+brama single-enp3s0-80 tylko enp3s0: .80+brama single-enp3s0-both tylko enp3s0: .80+.90+brama Interaktywnie skrypt skanuje kable i pokazuje tylko pasujące opcje. Rollback: sudo ./restore-dhcp-network.sh EOF } while [[ $# -gt 0 ]]; do case "$1" in --dry-run) DRY_RUN=true; shift ;; --non-interactive) NON_INTERACTIVE=true; shift ;; --profile) if [[ $# -lt 2 ]]; then echo "BŁĄD: --profile wymaga nazwy." >&2 exit 1 fi PROFILE="$2" shift 2 ;; -h|--help) usage; exit 0 ;; *) echo "Nieznana opcja: $1" >&2; usage >&2; exit 1 ;; esac done if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then echo "BŁĄD: Uruchom skrypt jako root (sudo)." >&2 exit 1 fi if ! command -v netplan >/dev/null 2>&1; then echo "BŁĄD: netplan nie jest zainstalowany." >&2 exit 1 fi for nic in "$NIC_A" "$NIC_B"; do if ! ip link show "$nic" &>/dev/null; then echo "BŁĄD: Interfejs $nic nie istnieje." >&2 ip -br link || true exit 1 fi done has_link() { local nic="$1" if command -v ethtool >/dev/null 2>&1; then ethtool "$nic" 2>/dev/null | grep -q "Link detected: yes" return $? fi ip link show "$nic" | grep -q "LOWER_UP" } current_ipv4() { local nic="$1" ip -4 -br addr show "$nic" 2>/dev/null | awk '{print $3}' | head -1 } # Zwraca: LINK_ENO1 LINK_ENP3S0 (yes/no) scan_nics() { LINK_ENO1="no" LINK_ENP3S0="no" has_link "$NIC_A" && LINK_ENO1="yes" has_link "$NIC_B" && LINK_ENP3S0="yes" echo "=== Skan interfejsów ===" printf " %-8s %-6s %-8s %s\n" "Interfejs" "Stan" "Link" "IPv4" for nic in "$NIC_A" "$NIC_B"; do local state link_flag ipv4 state=$(ip -br link show "$nic" | awk '{print $2}') if has_link "$nic"; then link_flag="TAK"; else link_flag="NIE"; fi ipv4=$(current_ipv4 "$nic") ipv4="${ipv4:-—}" printf " %-8s %-6s %-8s %s\n" "$nic" "$state" "$link_flag" "$ipv4" done echo "" } ips_has() { local list="$1" local which="$2" [[ " $list " == *" $which "* ]] } apply_profile() { local key="$1" PROFILE_NAME="$key" case "$key" in dual-eno1) GW_NIC="$NIC_A"; ENO1_IPS="80"; ENP3S0_IPS="90" ;; dual-enp3s0) GW_NIC="$NIC_B"; ENO1_IPS="90"; ENP3S0_IPS="80" ;; single-eno1-80) GW_NIC="$NIC_A"; ENO1_IPS="80"; ENP3S0_IPS="" ;; single-eno1-both) GW_NIC="$NIC_A"; ENO1_IPS="80 90"; ENP3S0_IPS="" ;; single-enp3s0-90) GW_NIC="$NIC_B"; ENO1_IPS=""; ENP3S0_IPS="90" ;; single-enp3s0-80) GW_NIC="$NIC_B"; ENO1_IPS=""; ENP3S0_IPS="80" ;; single-enp3s0-both) GW_NIC="$NIC_B"; ENO1_IPS=""; ENP3S0_IPS="80 90" ;; *) echo "BŁĄD: Nieznany profil: $key" >&2 return 1 ;; esac } profile_description() { local key="$1" case "$key" in dual-eno1) echo "eno1=.80+brama, enp3s0=.90" ;; dual-enp3s0) echo "enp3s0=.80+brama, eno1=.90" ;; single-eno1-80) echo "eno1=.80+brama (enp3s0 bez adresu)" ;; single-eno1-both) echo "eno1=.80+.90+brama (oba IP na eno1)" ;; single-enp3s0-90) echo "enp3s0=.90+brama (usługi LAN)" ;; single-enp3s0-80) echo "enp3s0=.80+brama" ;; single-enp3s0-both) echo "enp3s0=.80+.90+brama (oba IP na enp3s0)" ;; *) echo "$key" ;; esac } # MENU_KEYS[], MENU_LABELS[] — numeracja od 1 declare -a MENU_KEYS=() declare -a MENU_LABELS=() build_menu() { MENU_KEYS=() MENU_LABELS=() if [[ "$LINK_ENO1" == "yes" && "$LINK_ENP3S0" == "yes" ]]; then MENU_KEYS+=(dual-eno1 dual-enp3s0) MENU_LABELS+=( "Dual: eno1=.80+brama, enp3s0=.90 (zalecane)" "Dual odwrócony: enp3s0=.80+brama, eno1=.90" ) elif [[ "$LINK_ENO1" == "yes" ]]; then MENU_KEYS+=(single-eno1-80 single-eno1-both) MENU_LABELS+=( "Pojedynczy eno1: .80+brama" "Pojedynczy eno1: .80+.90+brama (awaryjnie, oba IP na jednej karcie)" ) elif [[ "$LINK_ENP3S0" == "yes" ]]; then MENU_KEYS+=(single-enp3s0-90 single-enp3s0-80 single-enp3s0-both) MENU_LABELS+=( "Pojedynczy enp3s0: .90+brama (zalecane — usługi Docker)" "Pojedynczy enp3s0: .80+brama" "Pojedynczy enp3s0: .80+.90+brama (oba IP na jednej karcie)" ) else echo "BŁĄD: Żaden interfejs nie ma aktywnego linku (kabel sieciowy)." >&2 echo " Podłącz kabel do eno1 lub enp3s0 i uruchom skrypt ponownie." >&2 exit 1 fi } show_menu() { echo "=== Wybierz konfigurację ===" local i for i in "${!MENU_KEYS[@]}"; do printf " %d) %s\n" "$((i + 1))" "${MENU_LABELS[$i]}" done printf " 0) Anuluj\n" echo "" } pick_profile_interactive() { build_menu show_menu local choice while true; do read -r -p "Wybór [1-${#MENU_KEYS[@]}, domyślnie 1]: " choice choice="${choice:-1}" if [[ "$choice" == "0" ]]; then echo "Anulowano." exit 0 fi if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#MENU_KEYS[@]} )); then apply_profile "${MENU_KEYS[$((choice - 1))]}" return 0 fi echo "Nieprawidłowy wybór." done } pick_profile() { if [[ -n "$PROFILE" ]]; then apply_profile "$PROFILE" elif [[ "$NON_INTERACTIVE" == true ]]; then echo "BŁĄD: --non-interactive wymaga --profile ." >&2 exit 1 else pick_profile_interactive fi } build_nic_block() { local nic="$1" local ips="$2" local is_gw="$3" local has_cable="$4" local optional="true" [[ "$is_gw" == "yes" ]] && optional="false" echo " ${nic}:" echo " dhcp4: false" echo " dhcp6: false" echo " optional: ${optional}" if [[ -n "$ips" ]]; then echo " ignore-carrier: true" echo " addresses:" ips_has "$ips" "80" && echo " - ${IP80}" ips_has "$ips" "90" && echo " - ${IP90}" fi if [[ "$is_gw" == "yes" ]]; then echo " routes:" echo " - to: default" echo " via: ${GATEWAY}" echo " metric: 100" echo " nameservers:" echo " addresses:" echo " - ${DNS}" fi } write_netplan_file() { local content="$1" local tmp tmp=$(mktemp) || { echo "BŁĄD: mktemp nie powiódł się." >&2; return 1; } chmod 600 "$tmp" printf '%s\n' "$content" > "$tmp" mv -f "$tmp" "$NETPLAN_FILE" chmod 600 "$NETPLAN_FILE" } render_netplan() { local eno1_gw="no" enp3_gw="no" [[ "$GW_NIC" == "$NIC_A" ]] && eno1_gw="yes" [[ "$GW_NIC" == "$NIC_B" ]] && enp3_gw="yes" { echo "network:" echo " version: 2" echo " renderer: networkd" echo " ethernets:" build_nic_block "$NIC_A" "$ENO1_IPS" "$eno1_gw" "$LINK_ENO1" build_nic_block "$NIC_B" "$ENP3S0_IPS" "$enp3_gw" "$LINK_ENP3S0" } } expected_ips_for_nic() { local nic="$1" local ips="" if [[ "$nic" == "$NIC_A" ]]; then ips="$ENO1_IPS"; else ips="$ENP3S0_IPS"; fi if ips_has "$ips" "80"; then echo "192.168.100.80"; fi if ips_has "$ips" "90"; then echo "192.168.100.90"; fi } verify_config() { local ok=true echo "=== Weryfikacja ===" for nic in "$NIC_A" "$NIC_B"; do local ip while read -r ip; do [[ -z "$ip" ]] && continue if ip -4 addr show dev "$nic" | grep -q "inet ${ip}/"; then echo " ${nic}: ${ip} OK" else echo " ${nic}: ${ip} BRAK — próbuję networkctl reconfigure ..." networkctl reconfigure "$nic" 2>/dev/null || true sleep 2 if ip -4 addr show dev "$nic" | grep -q "inet ${ip}/"; then echo " ${nic}: ${ip} OK (po reconfigure)" else echo " ${nic}: ${ip} NADAL BRAK" ok=false fi fi done < <(expected_ips_for_nic "$nic") done ip -br addr show "$NIC_A" "$NIC_B" 2>/dev/null || ip -br addr show "$NIC_A" "$NIC_B" echo "--- trasy ---" ip route show default || echo " (brak trasy domyślnej)" if ping -c 1 -W 3 -I "$GW_NIC" "$GATEWAY" &>/dev/null; then echo " ping -I ${GW_NIC} ${GATEWAY}: OK" else echo " ping -I ${GW_NIC} ${GATEWAY}: NIEUDANY" ok=false fi if [[ "$ok" != true ]]; then echo "" echo "WERYFIKACJA NIEUDANA — sprawdź kable, rezerwację IP na routerze." echo "Rollback: sudo $(dirname "$0")/restore-dhcp-network.sh" return 1 fi return 0 } wait_for_ips() { local attempt for attempt in $(seq 1 15); do local missing=false for nic in "$NIC_A" "$NIC_B"; do local ip while read -r ip; do [[ -z "$ip" ]] && continue ip -4 addr show dev "$nic" | grep -q "inet ${ip}/" || missing=true done < <(expected_ips_for_nic "$nic") done [[ "$missing" == false ]] && return 0 networkctl reconfigure "$NIC_A" 2>/dev/null || true networkctl reconfigure "$NIC_B" 2>/dev/null || true sleep 1 done return 1 } # --- main --- scan_nics pick_profile STATIC_YAML=$(render_netplan) echo "=== Wybrany profil: ${PROFILE_NAME} ===" echo " $(profile_description "$PROFILE_NAME")" echo " Brama domyślna: ${GW_NIC}" echo "" if [[ "$DRY_RUN" == true ]]; then echo "=== DRY-RUN: docelowy Netplan ($NETPLAN_FILE) ===" echo "$STATIC_YAML" echo "" echo "Dry-run: backup, zapis i netplan apply zostały pominięte." exit 0 fi echo "=== Backup Netplan ===" if [[ -f "$NETPLAN_FILE" ]]; then backup="${NETPLAN_FILE}.bak.$(date +%Y%m%d%H%M%S)" cp -a "$NETPLAN_FILE" "$backup" echo " Zapisano: $backup" else echo " Brak istniejącego $NETPLAN_FILE — backup pominięty." fi echo "=== Zapis statycznej konfiguracji ===" write_netplan_file "$STATIC_YAML" echo " Zapisano: $NETPLAN_FILE" echo "=== netplan generate ===" if ! netplan generate 2>&1; then echo "" echo "BŁĄD: netplan generate nie powiódł się. Przywróć backup lub uruchom restore-dhcp-network.sh" exit 1 fi echo "=== netplan apply ===" netplan apply wait_for_ips || true verify_config || exit 1 echo "" echo "=== GOTOWE ===" echo " Profil: ${PROFILE_NAME} — $(profile_description "$PROFILE_NAME")" echo " Brama: ${GW_NIC} -> ${GATEWAY}" [[ -n "$ENO1_IPS" ]] && echo " eno1: ${ENO1_IPS} -> 192.168.100.{${ENO1_IPS// /,}}" [[ -n "$ENP3S0_IPS" ]] && echo " enp3s0: ${ENP3S0_IPS} -> 192.168.100.{${ENP3S0_IPS// /,}}" echo "" echo "SSH: sprawdź ip -br addr (adresy .80 / .90 na aktywnych kartach)" echo "Rollback: sudo $(dirname "$0")/restore-dhcp-network.sh"