#!/usr/bin/env bash # ============================================================================= # ES24N IOM Network Configuration Script # TrueNAS ES24N Expansion Shelf — Serial Configuration Tool # Based on ES24N Product Service Guide v.26011 # ============================================================================= # ── Colours & formatting ────────────────────────────────────────────────────── RED='\033[0;31m' GRN='\033[0;32m' YEL='\033[1;33m' CYN='\033[0;36m' BLD='\033[1m' DIM='\033[2m' RST='\033[0m' # ── Helpers ─────────────────────────────────────────────────────────────────── banner() { clear echo -e "${CYN}${BLD}" echo " ╔══════════════════════════════════════════════════════════╗" echo " ║ TrueNAS ES24N IOM Configuration Tool ║" echo " ║ Serial Network Setup • v1.0 ║" echo " ╚══════════════════════════════════════════════════════════╝" echo -e "${RST}" } info() { echo -e " ${CYN}[INFO]${RST} $*"; } ok() { echo -e " ${GRN}[ OK ]${RST} $*"; } warn() { echo -e " ${YEL}[WARN]${RST} $*"; } error() { echo -e " ${RED}[ERR ]${RST} $*"; } step() { echo -e "\n ${BLD}${YEL}──▶ $*${RST}"; } divider() { echo -e " ${DIM}──────────────────────────────────────────────────────────${RST}"; } # ── Input validators ────────────────────────────────────────────────────────── validate_ip() { local ip="$1" local re='^([0-9]{1,3}\.){3}[0-9]{1,3}$' if [[ ! $ip =~ $re ]]; then return 1; fi IFS='.' read -ra oct <<< "$ip" for o in "${oct[@]}"; do (( o > 255 )) && return 1 done return 0 } prompt_ip() { local label="$1" local var_name="$2" local ip while true; do read -rp " ${label}: " ip if validate_ip "$ip"; then eval "$var_name='$ip'" return 0 fi warn "Invalid IP address format. Please try again." done } prompt_password() { local var_name="$1" local pass while true; do read -rsp " Admin password (BMC/chassis serial, e.g. MXE3000043CHA007): " pass echo if [[ -n "$pass" ]]; then eval "$var_name='$pass'" return 0 fi warn "Password cannot be empty." done } # ── Step 1: Connect cable & detect USB device ───────────────────────────────── detect_serial_device() { step "Serial Cable & Device Detection" divider echo echo -e " ${BLD}Action required:${RST}" echo " 1. Connect the serial cable from the ES24N IOM1 port" echo " to the active F-Series controller USB port." echo " 2. Press ${BLD}[Enter]${RST} once the cable is connected." echo read -rp " Press [Enter] when the serial cable is connected... " echo info "Scanning for USB serial devices (/dev/ttyUSB* and /dev/ttyACM*)..." sleep 1 mapfile -t devices < <(ls /dev/ttyUSB* /dev/ttyACM* 2>/dev/null) if [[ ${#devices[@]} -eq 0 ]]; then error "No USB serial device detected." echo echo " Troubleshooting:" echo " • Make sure the serial cable is fully seated." echo " • Try a different USB port on the controller." echo " • Verify the ES24N is powered on." echo read -rp " Retry detection? [y/N]: " retry [[ "${retry,,}" == "y" ]] && detect_serial_device && return return 1 fi if [[ ${#devices[@]} -eq 1 ]]; then SERIAL_DEV="${devices[0]}" ok "Device found: ${BLD}${SERIAL_DEV}${RST}" else echo echo " Multiple devices detected:" local i=1 for dev in "${devices[@]}"; do echo " ${i}) ${dev}" (( i++ )) done echo while true; do read -rp " Select device number [1-${#devices[@]}]: " sel if [[ "$sel" =~ ^[0-9]+$ ]] && (( sel >= 1 && sel <= ${#devices[@]} )); then SERIAL_DEV="${devices[$((sel-1))]}" ok "Selected: ${BLD}${SERIAL_DEV}${RST}" break fi warn "Invalid selection. Please enter a number between 1 and ${#devices[@]}." done fi # Fix permissions so the current user can access the port info "Setting group permission on ${SERIAL_DEV}..." if sudo chown ":wheel" "${SERIAL_DEV}" 2>/dev/null || \ sudo chmod a+rw "${SERIAL_DEV}" 2>/dev/null; then ok "Permissions updated." else warn "Could not update permissions automatically. You may need to run as root." fi echo return 0 } # ── Step 2: Open serial connection ──────────────────────────────────────────── open_serial_connection() { local dev="$1" step "Opening Serial Connection → ${dev}" divider # Check that a terminal tool is available if command -v screen &>/dev/null; then SERIAL_TOOL="screen" elif command -v minicom &>/dev/null; then SERIAL_TOOL="minicom" elif command -v picocom &>/dev/null; then SERIAL_TOOL="picocom" else error "No serial terminal found (screen / minicom / picocom)." error "Install one of them and re-run this script." return 1 fi info "Using ${BLD}${SERIAL_TOOL}${RST} at 115200 8N1" echo echo -e " ${YEL}${BLD}NOTE:${RST} A serial session will now open." echo " • Log into the ES24N IOM shell when the prompt appears." echo " • Press Enter once or twice if you do not see a login prompt." echo " • Exit the terminal session and return here when done:" case "$SERIAL_TOOL" in screen) echo " screen exit: Ctrl-A then K (then 'y' to confirm)" ;; minicom) echo " minicom exit: Ctrl-A then X" ;; picocom) echo " picocom exit: Ctrl-A then Ctrl-X" ;; esac echo read -rp " Press [Enter] to launch the serial terminal... " echo case "$SERIAL_TOOL" in screen) screen "${dev}" 115200 ;; minicom) minicom -D "${dev}" -b 115200 ;; picocom) picocom -b 115200 "${dev}" ;; esac ok "Serial session closed. Returning to configuration menu." echo } # ── Step 3: Collect network config from user ────────────────────────────────── collect_network_config() { step "IOM Network Configuration" divider echo echo " How should the IOMs be configured?" echo " 1) Static IP addresses" echo " 2) DHCP" echo while true; do read -rp " Select option [1-2]: " choice case "$choice" in 1) IP_MODE="static"; break ;; 2) IP_MODE="dhcp"; break ;; *) warn "Please enter 1 or 2." ;; esac done echo prompt_password ADMIN_PASS if [[ "$IP_MODE" == "static" ]]; then echo info "Enter static network details for IOM1:" prompt_ip " IOM1 IP address " IOM1_IP prompt_ip " IOM1 Gateway " IOM1_GW prompt_ip " IOM1 Subnet Mask " IOM1_NM echo info "Enter static network details for IOM2:" prompt_ip " IOM2 IP address " IOM2_IP read -rp " IOM2 Gateway (same as IOM1? [Y/n]): " same_gw if [[ "${same_gw,,}" != "n" ]]; then IOM2_GW="$IOM1_GW"; IOM2_NM="$IOM1_NM" else prompt_ip " IOM2 Gateway " IOM2_GW prompt_ip " IOM2 Subnet Mask " IOM2_NM fi fi } # ── Step 4: Apply configuration via Redfish over the serial loopback ────────── apply_configuration() { step "Applying Configuration" divider echo local BASE="https://127.0.0.1/redfish/v1/Managers" if [[ "$IP_MODE" == "dhcp" ]]; then info "Setting IOM1 to DHCP..." curl -sk -u "Admin:${ADMIN_PASS}" -X PATCH \ "${BASE}/IOM1/EthernetInterfaces/1" \ -H "Content-Type: application/json" \ -d '{"DHCPv4": {"DHCPEnabled": true}}' \ -o /tmp/es24n_iom1.json show_result /tmp/es24n_iom1.json "IOM1" info "Setting IOM2 to DHCP..." curl -sk -u "Admin:${ADMIN_PASS}" -X PATCH \ "${BASE}/IOM2/EthernetInterfaces/1" \ -H "Content-Type: application/json" \ -d '{"DHCPv4": {"DHCPEnabled": true}}' \ -o /tmp/es24n_iom2.json show_result /tmp/es24n_iom2.json "IOM2" else info "Applying static IP to IOM1 (${IOM1_IP})..." curl -sk -u "Admin:${ADMIN_PASS}" -X PATCH \ "${BASE}/IOM1/EthernetInterfaces/1" \ -H "Content-Type: application/json" \ -d "{\"DHCPv4\": {\"DHCPEnabled\": false}, \ \"IPv4StaticAddresses\": [{\"Address\": \"${IOM1_IP}\", \ \"Gateway\": \"${IOM1_GW}\", \"SubnetMask\": \"${IOM1_NM}\"}]}" \ -o /tmp/es24n_iom1.json show_result /tmp/es24n_iom1.json "IOM1" info "Applying static IP to IOM2 (${IOM2_IP})..." curl -sk -u "Admin:${ADMIN_PASS}" -X PATCH \ "${BASE}/IOM2/EthernetInterfaces/1" \ -H "Content-Type: application/json" \ -d "{\"DHCPv4\": {\"DHCPEnabled\": false}, \ \"IPv4StaticAddresses\": [{\"Address\": \"${IOM2_IP}\", \ \"Gateway\": \"${IOM2_GW}\", \"SubnetMask\": \"${IOM2_NM}\"}]}" \ -o /tmp/es24n_iom2.json show_result /tmp/es24n_iom2.json "IOM2" fi echo info "Configuration applied. Verify settings in TrueNAS → System Settings → Enclosure." } # parse curl response and print a friendly summary show_result() { local file="$1" label="$2" if [[ -f "$file" ]]; then local http_err http_err=$(python3 -c "import json,sys; d=json.load(open('$file')); \ print(d.get('error',{}).get('message',''))" 2>/dev/null) if [[ -z "$http_err" ]]; then ok "${label}: Configuration accepted." else error "${label}: ${http_err}" fi else warn "${label}: No response received (curl may have failed)." fi } # ── Step 5: Print a config summary ──────────────────────────────────────────── print_summary() { echo divider echo -e " ${BLD}Configuration Summary${RST}" divider echo " Mode : ${IP_MODE^^}" if [[ "$IP_MODE" == "static" ]]; then echo " IOM1 : ${IOM1_IP} GW ${IOM1_GW} NM ${IOM1_NM}" echo " IOM2 : ${IOM2_IP} GW ${IOM2_GW} NM ${IOM2_NM}" fi echo " Device : ${SERIAL_DEV}" divider echo warn "IMPORTANT: Per the ES24N Service Guide, remove the serial cable ONLY" warn "after verifying each expander is configured in TrueNAS with matching" warn "drives in the expected slots (System Settings → Enclosure)." echo } # ── Step 6: Close serial connection (prompt) ────────────────────────────────── close_serial_connection() { step "Close Serial Connection" echo echo " When you have verified the configuration in TrueNAS, disconnect" echo " the serial cable from the IOM1 port." echo read -rp " Press [Enter] to confirm the serial cable has been disconnected... " ok "Serial connection closed." } # ── Main loop ───────────────────────────────────────────────────────────────── main() { banner echo -e " ${DIM}This tool guides you through ES24N IOM network configuration" echo -e " over a serial connection. Based on TrueNAS ES24N Service Guide v.26011.${RST}" echo while true; do banner step "Main Menu" divider echo echo " 1) Configure a new ES24N shelf" echo " 2) Exit" echo read -rp " Select [1-2]: " main_choice case "$main_choice" in 1) # ── Full configuration flow ────────────────────────────────── banner # 1. Cable & device detect_serial_device || { error "Aborting shelf configuration."; sleep 2; continue; } # 2. Open serial terminal so user can verify/log in open_serial_connection "${SERIAL_DEV}" # 3. Collect network settings collect_network_config # 4. Apply via Redfish (runs locally on the IOM over 127.0.0.1) echo echo -e " ${YEL}${BLD}Ready to apply network configuration via Redfish API.${RST}" echo " Ensure you are still logged into the IOM serial session" echo " or that the Redfish API is reachable over the loopback." echo read -rp " Apply configuration now? [Y/n]: " apply_now if [[ "${apply_now,,}" != "n" ]]; then apply_configuration fi # 5. Summary print_summary # 6. Close serial close_serial_connection # 7. Another shelf? echo divider read -rp " Configure another ES24N shelf? [y/N]: " another [[ "${another,,}" != "y" ]] && break ;; 2) # ── Exit ──────────────────────────────────────────────────── echo ok "Exiting ES24N IOM Configuration Tool. Goodbye." echo exit 0 ;; *) warn "Invalid selection. Please enter 1 or 2." ; sleep 1 ;; esac done echo ok "All shelves configured. Exiting." echo exit 0 } main "$@"