Skip to content

Instantly share code, notes, and snippets.

@jaminmc
Last active October 26, 2025 16:07
Show Gist options
  • Save jaminmc/7e786a8947746439f7b8a8e2726e629d to your computer and use it in GitHub Desktop.
Save jaminmc/7e786a8947746439f7b8a8e2726e629d to your computer and use it in GitHub Desktop.

Revisions

  1. jaminmc revised this gist Oct 20, 2025. 1 changed file with 64 additions and 37 deletions.
    101 changes: 64 additions & 37 deletions install_openwrt_proxmox.sh
    Original file line number Diff line number Diff line change
    @@ -28,7 +28,7 @@ exit_script() {
    # Check if running as root
    [ "$EUID" -ne 0 ] && exit_script 1 "Error: This script must be run as root"

    # Check required tools
    # Check required tools (NO BC!)
    for cmd in wget pct pvesm ip curl whiptail pvesh bridge stat; do
    command -v "$cmd" &>/dev/null || exit_script 1 "Error: $cmd is not installed. Please install it first."
    done
    @@ -42,6 +42,15 @@ whiptail_radiolist() {
    echo "$selection"
    }

    # Whiptail inputbox function
    whiptail_input() {
    local title="$1" prompt="$2" default="$3" var="$4"
    local input
    input=$(whiptail --title "$title" --inputbox "$prompt\n\nDefault: $default" 10 50 "$default" 3>&1 1>&2 2>&3) || \
    eval "$var=\"$default\""
    eval "$var=\"${input:-$default}\""
    }

    # Detect latest stable OpenWrt version (silent)
    detect_latest_version() {
    local ver
    @@ -110,13 +119,6 @@ detect_next_ctid() {
    echo "${id:-100}"
    }

    # Prompt with default value
    prompt_with_default() {
    local prompt="$1" default="$2" var="$3"
    read -e -p "$prompt (default: $default): " -i "$default" input
    eval "$var=\"${input:-$default}\""
    }

    # Main execution
    echo -e "${GREEN}Fetching latest stable OpenWrt version...${NC}"
    STABLE_VER=$(detect_latest_version)
    @@ -129,7 +131,7 @@ RELEASE_TYPE=$(whiptail --title "OpenWrt Release Type" --radiolist \
    "Snapshot" "Latest daily snapshot" "OFF" 3>&1 1>&2 2>&3) || exit_script 1 "Error: Release type selection aborted"

    if [ "$RELEASE_TYPE" = "Stable" ]; then
    prompt_with_default "Enter OpenWrt stable version" "$STABLE_VER" VER
    whiptail_input "OpenWrt Version" "Enter OpenWrt stable version" "$STABLE_VER" VER
    DOWNLOAD_URL="https://downloads.openwrt.org/releases/$VER/targets/x86/64/openwrt-$VER-x86-64-rootfs.tar.gz"
    TEMPLATE_FILE="openwrt-$VER-$ARCH.tar.gz"
    else
    @@ -145,33 +147,43 @@ else
    fi

    NEXT_CTID=$(detect_next_ctid)
    prompt_with_default "Enter Container ID" "$NEXT_CTID" CTID
    prompt_with_default "Enter Container Name" "openwrt-$CTID" CTNAME
    whiptail_input "Container ID" "Enter Container ID" "$NEXT_CTID" CTID
    whiptail_input "Container Name" "Enter Container Name" "openwrt-$CTID" CTNAME

    # Password prompt using whiptail passwordbox
    while true; do
    read -s -p "Enter root password (leave blank to skip): " PASSWORD; echo
    read -s -p "Confirm root password: " PASSWORD_CONFIRM; echo
    PASSWORD=$(whiptail --title "Root Password" --passwordbox "Enter root password (leave blank to skip)" 10 50 3>&1 1>&2 2>&3)
    [ $? -ne 0 ] && PASSWORD="" # Cancel = blank password
    PASSWORD_CONFIRM=$(whiptail --title "Confirm Password" --passwordbox "Confirm root password" 10 50 3>&1 1>&2 2>&3)
    [ $? -ne 0 ] && continue # Cancel = retry

    if [ -z "$PASSWORD" ] && [ -z "$PASSWORD_CONFIRM" ]; then
    echo -e "${GREEN}Root password skipped.${NC}"
    break
    elif [ "$PASSWORD" = "$PASSWORD_CONFIRM" ]; then
    break
    else
    echo -e "${RED}Passwords do not match. Please try again.${NC}"
    whiptail --title "Error" --msgbox "Passwords do not match. Please try again." 8 50
    fi
    done

    prompt_with_default "Enter memory size in MB" "$DEFAULT_MEMORY" MEMORY
    prompt_with_default "Enter number of CPU cores" "$DEFAULT_CORES" CORES
    prompt_with_default "Enter storage limit in GB" "$DEFAULT_STORAGE" STORAGE_SIZE
    prompt_with_default "Enter LAN subnet" "$DEFAULT_SUBNET" SUBNET
    # sysntpd prompt (DEFAULT: DISABLED)
    DISABLE_SYNTPD=$(whiptail --title "Disable sysntpd Service" --radiolist \
    "Disable sysntpd (NTP time sync service)?\n\nThis removes sysntpd from startup (recommended for containers).\nUse Spacebar to select." 12 60 2 \
    "Yes" "Disable sysntpd (DEFAULT)" "ON" \
    "No" "Keep sysntpd enabled" "OFF" 3>&1 1>&2 2>&3) || exit_script 1 "Error: sysntpd selection aborted"

    # Validate inputs
    whiptail_input "Memory Size" "Enter memory size in MB" "$DEFAULT_MEMORY" MEMORY
    whiptail_input "CPU Cores" "Enter number of CPU cores" "$DEFAULT_CORES" CORES
    whiptail_input "Storage Size" "Enter storage limit in GB" "$DEFAULT_STORAGE" STORAGE_SIZE
    whiptail_input "LAN Subnet" "Enter LAN subnet" "$DEFAULT_SUBNET" SUBNET

    # YOUR ORIGINAL STORAGE LOGIC - FIXED WITH AWK
    [[ "$CTID" =~ ^[0-9]+$ && "$CTID" -ge 100 ]] || exit_script 1 "Error: Container ID must be a number >= 100"
    pct list | awk '{print $1}' | grep -q "^$CTID$" && exit_script 1 "Error: Container ID $CTID is already in use"
    [[ "$MEMORY" =~ ^[0-9]+$ && "$MEMORY" -ge 64 ]] || exit_script 1 "Error: Memory size must be a number >= 64 MB"
    [[ "$CORES" =~ ^[0-9]+$ && "$CORES" -ge 1 ]] || exit_script 1 "Error: Core count must be a number >= 1"
    [[ "$STORAGE_SIZE" =~ ^[0-9]*\.?[0-9]+$ && "$(echo "$STORAGE_SIZE > 0" | bc)" -eq 1 ]] || exit_script 1 "Error: Storage limit must be a positive number"
    [[ "$CORES" =~ ^[0-9]+$ && "$CORES" -ge 1 ]] || exit_script 1 "Core count must be a number >= 1"
    [[ "$STORAGE_SIZE" =~ ^[0-9]*\.?[0-9]+$ && $(echo "$STORAGE_SIZE > 0" | awk '{if ($1 > 0) print 1; else print 0}') -eq 1 ]] || exit_script 1 "Error: Storage limit must be a positive number"

    # Parse subnet
    LAN_IP=$(echo "$SUBNET" | cut -d'/' -f1)
    @@ -192,16 +204,16 @@ WAN_OPTION=$(select_network_option "WAN" "eth0")
    LAN_OPTION=$(select_network_option "LAN" "eth1")

    WAN_BRIDGE=""; WAN_DEVICE=""
    if [[ "$WAN_OPTION" == bridge:* ]]; then
    if [ "${WAN_OPTION#bridge:}" != "$WAN_OPTION" ]; then
    WAN_BRIDGE="${WAN_OPTION#bridge:}"
    elif [[ "$WAN_OPTION" == device:* ]]; then
    elif [ "${WAN_OPTION#device:}" != "$WAN_OPTION" ]; then
    WAN_DEVICE="${WAN_OPTION#device:}"
    fi

    LAN_BRIDGE=""; LAN_DEVICE=""
    if [[ "$LAN_OPTION" == bridge:* ]]; then
    if [ "${LAN_OPTION#bridge:}" != "$LAN_OPTION" ]; then
    LAN_BRIDGE="${LAN_OPTION#bridge:}"
    elif [[ "$LAN_OPTION" == device:* ]]; then
    elif [ "${LAN_OPTION#device:}" != "$LAN_OPTION" ]; then
    LAN_DEVICE="${LAN_OPTION#device:}"
    fi

    @@ -211,6 +223,7 @@ SUMMARY+=" OpenWrt Version: $VER\n"
    SUMMARY+=" Container ID: $CTID\n"
    SUMMARY+=" Container Name: $CTNAME\n"
    SUMMARY+=" Root Password: $( [ -n "$PASSWORD" ] && echo "Set" || echo "Not set" )\n"
    SUMMARY+=" sysntpd Service: $( [ "$DISABLE_SYNTPD" = "Yes" ] && echo "DISABLED" || echo "Enabled" )\n"
    SUMMARY+=" Memory: $MEMORY MB\n"
    SUMMARY+=" CPU Cores: $CORES\n"
    SUMMARY+=" Storage: $STORAGE_SIZE GB on $STORAGE\n"
    @@ -219,7 +232,7 @@ SUMMARY+=" WAN Interface: ${WAN_BRIDGE:-${WAN_DEVICE:-None}} (eth0, DHCP/DHCPv6
    SUMMARY+=" LAN Interface: ${LAN_BRIDGE:-${LAN_DEVICE:-None}} (eth1, static)\n"
    [ "$RELEASE_TYPE" = "Snapshot" ] && [ "$INSTALL_LUCI" -eq 1 ] && SUMMARY+=" LuCI: Will be installed automatically\n"

    whiptail --title "Confirm Container Creation" --yesno "$SUMMARY\nProceed with container creation?" 20 60 || exit_script 0 "Container creation aborted by user"
    whiptail --title "Confirm Container Creation" --yesno "$SUMMARY\nProceed with container creation?" 22 60 || exit_script 0 "Container creation aborted by user"

    # Download template with snapshot age check
    if [ ! -f "$TEMPLATE_DIR/$TEMPLATE_FILE" ]; then
    @@ -266,6 +279,12 @@ pct start "$CTID" || exit_script 1 "Error: Failed to start container"
    pct exec "$CTID" -- sh -c "sed -i 's!procd_add_jail!: procd_add_jail!g' /etc/init.d/dnsmasq"
    sleep 10

    # Disable sysntpd if selected
    if [ "$DISABLE_SYNTPD" = "Yes" ]; then
    echo -e "${GREEN}Disabling sysntpd service...${NC}"
    pct exec "$CTID" -- sh -c "rm -f /etc/rc.d/*sysntpd" || echo -e "${RED}Warning: Failed to disable sysntpd${NC}"
    fi

    echo -e "${GREEN}Configuring network...${NC}"
    pct exec "$CTID" -- sh -c "
    # Configure WAN (eth0) with DHCP and DHCPv6
    @@ -303,17 +322,25 @@ echo -e "${GREEN}Container $CTID ($CTNAME) created and started!${NC}"
    echo "Next steps:"
    echo "1. Access: pct exec $CTID /bin/sh"
    echo "2. Verify network: uci show network"
    if [ "$RELEASE_TYPE" = "Snapshot" ]; then
    echo "3. Update: apk update"
    if [ "$DISABLE_SYNTPD" = "Yes" ]; then
    echo "3. sysntpd: Disabled (removed from startup)"
    NEXT_NUM=4
    else
    NEXT_NUM=3
    fi

    if [ "$RELEASE_TYPE" = "Stable" ]; then
    echo "$NEXT_NUM. LuCI: http://$LAN_IP (if LAN configured)"
    [ -z "$PASSWORD" ] && echo "$((NEXT_NUM + 1)). Set password if needed: pct exec $CTID passwd"
    else
    # Snapshot
    if [ "$INSTALL_LUCI" -eq 1 ]; then
    echo "4. LuCI installed: Access at http://$LAN_IP (if LAN configured)"
    echo "$NEXT_NUM. LuCI installed: Access at http://$LAN_IP (if LAN configured)"
    [ -z "$PASSWORD" ] && echo "$((NEXT_NUM + 1)). Set password if needed: pct exec $CTID passwd"
    else
    echo "4. Install LuCI: apk add luci"
    [ -n "$LAN_BRIDGE" ] || [ -n "$LAN_DEVICE" ] && echo "5. LuCI: http://$LAN_IP" || echo "5. Add eth1 to activate LAN: http://$LAN_IP"
    echo "$NEXT_NUM. Update: apk update"
    echo "$((NEXT_NUM + 1)). Install LuCI: apk add luci"
    [ -n "$LAN_BRIDGE" ] || [ -n "$LAN_DEVICE" ] && echo "$((NEXT_NUM + 2)). LuCI: http://$LAN_IP" || echo "$((NEXT_NUM + 2)). Add eth1 to activate LAN: http://$LAN_IP"
    [ -z "$PASSWORD" ] && echo "$((NEXT_NUM + 3)). Set password if needed: pct exec $CTID passwd"
    fi
    else
    echo "3. Update: opkg update"
    echo "4. Install LuCI: opkg install luci"
    [ -n "$LAN_BRIDGE" ] || [ -n "$LAN_DEVICE" ] && echo "5. LuCI: http://$LAN_IP" || echo "5. Add eth1 to activate LAN: http://$LAN_IP"
    fi
    [ -z "$PASSWORD" ] && echo "6. Set password if needed: pct exec $CTID passwd"
    fi
  2. jaminmc revised this gist Feb 26, 2025. 1 changed file with 65 additions and 19 deletions.
    84 changes: 65 additions & 19 deletions install_openwrt_proxmox.sh
    Original file line number Diff line number Diff line change
    @@ -1,8 +1,8 @@
    #!/bin/bash

    # Script to create an OpenWrt LXC container in Proxmox
    # Downloads from openwrt.org with latest stable version, detects bridges/devices, IDs, configures network, sets optional password
    # Pre-configures WAN/LAN in UCI, includes summary and confirmation
    # Downloads from openwrt.org with latest stable or snapshot version, detects bridges/devices, IDs, configures network, sets optional password
    # Pre-configures WAN/LAN in UCI, includes summary and confirmation, optional LuCI install for snapshots with apk

    # Default resource values
    DEFAULT_MEMORY="256" # MB
    @@ -29,7 +29,7 @@ exit_script() {
    [ "$EUID" -ne 0 ] && exit_script 1 "Error: This script must be run as root"

    # Check required tools
    for cmd in wget pct pvesm ip curl whiptail pvesh bridge; do
    for cmd in wget pct pvesm ip curl whiptail pvesh bridge stat; do
    command -v "$cmd" &>/dev/null || exit_script 1 "Error: $cmd is not installed. Please install it first."
    done

    @@ -119,11 +119,30 @@ prompt_with_default() {

    # Main execution
    echo -e "${GREEN}Fetching latest stable OpenWrt version...${NC}"
    VER=$(detect_latest_version)
    echo -e "${GREEN}Detected latest stable version: $VER${NC}"
    prompt_with_default "Enter OpenWrt version" "$VER" VER
    DOWNLOAD_URL="https://downloads.openwrt.org/releases/$VER/targets/x86/64/openwrt-$VER-x86-64-rootfs.tar.gz"
    TEMPLATE_FILE="openwrt-$VER-$ARCH.tar.gz"
    STABLE_VER=$(detect_latest_version)
    echo -e "${GREEN}Detected latest stable version: $STABLE_VER${NC}"

    # Select OpenWrt release type
    RELEASE_TYPE=$(whiptail --title "OpenWrt Release Type" --radiolist \
    "Choose the OpenWrt release type (Stable allows manual version input):\nUse Spacebar to select." 10 60 2 \
    "Stable" "Stable version (e.g., $STABLE_VER)" "ON" \
    "Snapshot" "Latest daily snapshot" "OFF" 3>&1 1>&2 2>&3) || exit_script 1 "Error: Release type selection aborted"

    if [ "$RELEASE_TYPE" = "Stable" ]; then
    prompt_with_default "Enter OpenWrt stable version" "$STABLE_VER" VER
    DOWNLOAD_URL="https://downloads.openwrt.org/releases/$VER/targets/x86/64/openwrt-$VER-x86-64-rootfs.tar.gz"
    TEMPLATE_FILE="openwrt-$VER-$ARCH.tar.gz"
    else
    VER="snapshot"
    DOWNLOAD_URL="https://downloads.openwrt.org/snapshots/targets/x86/64/openwrt-x86-64-rootfs.tar.gz"
    TEMPLATE_FILE="openwrt-snapshot-$ARCH.tar.gz"
    # Prompt for LuCI installation
    if whiptail --title "Install LuCI" --yesno "Would you like to automatically install LuCI (graphical web interface) for the snapshot?" 10 60 3>&1 1>&2 2>&3; then
    INSTALL_LUCI=1
    else
    INSTALL_LUCI=0
    fi
    fi

    NEXT_CTID=$(detect_next_ctid)
    prompt_with_default "Enter Container ID" "$NEXT_CTID" CTID
    @@ -172,8 +191,6 @@ detect_network_options
    WAN_OPTION=$(select_network_option "WAN" "eth0")
    LAN_OPTION=$(select_network_option "LAN" "eth1")

    echo -e "${RED}Note: Wan Option: $WAN_OPTION, Lan Option: $LAN_OPTION${NC}"

    WAN_BRIDGE=""; WAN_DEVICE=""
    if [[ "$WAN_OPTION" == bridge:* ]]; then
    WAN_BRIDGE="${WAN_OPTION#bridge:}"
    @@ -200,15 +217,28 @@ SUMMARY+=" Storage: $STORAGE_SIZE GB on $STORAGE\n"
    SUMMARY+=" LAN Subnet: $SUBNET\n"
    SUMMARY+=" WAN Interface: ${WAN_BRIDGE:-${WAN_DEVICE:-None}} (eth0, DHCP/DHCPv6)\n"
    SUMMARY+=" LAN Interface: ${LAN_BRIDGE:-${LAN_DEVICE:-None}} (eth1, static)\n"
    [ "$RELEASE_TYPE" = "Snapshot" ] && [ "$INSTALL_LUCI" -eq 1 ] && SUMMARY+=" LuCI: Will be installed automatically\n"

    whiptail --title "Confirm Container Creation" --yesno "$SUMMARY\nProceed with container creation?" 30 60 || exit_script 0 "Container creation aborted by user"
    whiptail --title "Confirm Container Creation" --yesno "$SUMMARY\nProceed with container creation?" 20 60 || exit_script 0 "Container creation aborted by user"

    # Download template
    # Download template with snapshot age check
    if [ ! -f "$TEMPLATE_DIR/$TEMPLATE_FILE" ]; then
    echo -e "${GREEN}Downloading OpenWrt $VER rootfs...${NC}"
    wget -q "$DOWNLOAD_URL" -O "$TEMPLATE_DIR/$TEMPLATE_FILE" || exit_script 1 "Error: Failed to download OpenWrt $VER image"
    else
    echo -e "${GREEN}Using existing OpenWrt image: $TEMPLATE_FILE${NC}"
    if [ "$RELEASE_TYPE" = "Snapshot" ]; then
    # Check if snapshot file is older than 1 day (86400 seconds)
    FILE_AGE=$(($(date +%s) - $(stat -c %Y "$TEMPLATE_DIR/$TEMPLATE_FILE")))
    if [ "$FILE_AGE" -gt 86400 ]; then
    echo -e "${GREEN}Snapshot is older than 1 day, refreshing...${NC}"
    rm -f "$TEMPLATE_DIR/$TEMPLATE_FILE"
    wget -q "$DOWNLOAD_URL" -O "$TEMPLATE_DIR/$TEMPLATE_FILE" || exit_script 1 "Error: Failed to download OpenWrt snapshot"
    else
    echo -e "${GREEN}Using existing OpenWrt snapshot: $TEMPLATE_FILE${NC}"
    fi
    else
    echo -e "${GREEN}Using existing OpenWrt image: $TEMPLATE_FILE${NC}"
    fi
    fi

    # Build pct create command with corrected network options
    @@ -230,7 +260,6 @@ pct create "$CTID" "$TEMPLATE_DIR/$TEMPLATE_FILE" \
    --ostype unmanaged \
    "${NET_OPTS[@]}" || exit_script 1 "Error: Failed to create container"


    echo -e "${GREEN}Starting container $CTID...${NC}"
    pct start "$CTID" || exit_script 1 "Error: Failed to start container"

    @@ -239,15 +268,15 @@ sleep 10

    echo -e "${GREEN}Configuring network...${NC}"
    pct exec "$CTID" -- sh -c "
    # Always configure WAN (eth0) with DHCP and DHCPv6
    # Configure WAN (eth0) with DHCP and DHCPv6
    uci set network.wan=interface
    uci set network.wan.proto='dhcp'
    uci set network.wan.device='eth0'
    uci set network.wan6=interface
    uci set network.wan6.proto='dhcpv6'
    uci set network.wan6.device='eth0'
    # Always configure LAN (eth1) with static IP
    # Configure LAN (eth1) with static IP
    uci set network.lan=interface
    uci set network.lan.proto='static'
    uci set network.@device[0].ports='eth1'
    @@ -258,6 +287,13 @@ pct exec "$CTID" -- sh -c "
    uci commit network
    /etc/init.d/network restart" || echo -e "${RED}Warning: Network configuration failed${NC}"

    if [ "$RELEASE_TYPE" = "Snapshot" ] && [ "$INSTALL_LUCI" -eq 1 ]; then
    echo -e "${GREEN}Waiting 15 seconds for internet connectivity...${NC}"
    sleep 15
    echo -e "${GREEN}Installing LuCI...${NC}"
    pct exec "$CTID" -- sh -c "apk update; apk add luci" || echo -e "${RED}Warning: LuCI installation failed${NC}"
    fi

    [ -n "$PASSWORD" ] && {
    echo -e "${GREEN}Setting root password...${NC}"
    echo -e "$PASSWORD\n$PASSWORD" | pct exec "$CTID" -- passwd || echo -e "${RED}Warning: Failed to set root password${NC}"
    @@ -267,7 +303,17 @@ echo -e "${GREEN}Container $CTID ($CTNAME) created and started!${NC}"
    echo "Next steps:"
    echo "1. Access: pct exec $CTID /bin/sh"
    echo "2. Verify network: uci show network"
    echo "3. Update: opkg update"
    echo "4. Install LuCI: opkg install luci"
    [ -n "$LAN_BRIDGE" ] || [ -n "$LAN_DEVICE" ] && echo "5. LuCI: http://$LAN_IP" || echo "5. Add eth1 to activate LAN: http://$LAN_IP"
    if [ "$RELEASE_TYPE" = "Snapshot" ]; then
    echo "3. Update: apk update"
    if [ "$INSTALL_LUCI" -eq 1 ]; then
    echo "4. LuCI installed: Access at http://$LAN_IP (if LAN configured)"
    else
    echo "4. Install LuCI: apk add luci"
    [ -n "$LAN_BRIDGE" ] || [ -n "$LAN_DEVICE" ] && echo "5. LuCI: http://$LAN_IP" || echo "5. Add eth1 to activate LAN: http://$LAN_IP"
    fi
    else
    echo "3. Update: opkg update"
    echo "4. Install LuCI: opkg install luci"
    [ -n "$LAN_BRIDGE" ] || [ -n "$LAN_DEVICE" ] && echo "5. LuCI: http://$LAN_IP" || echo "5. Add eth1 to activate LAN: http://$LAN_IP"
    fi
    [ -z "$PASSWORD" ] && echo "6. Set password if needed: pct exec $CTID passwd"
  3. jaminmc created this gist Feb 26, 2025.
    273 changes: 273 additions & 0 deletions install_openwrt_proxmox.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,273 @@
    #!/bin/bash

    # Script to create an OpenWrt LXC container in Proxmox
    # Downloads from openwrt.org with latest stable version, detects bridges/devices, IDs, configures network, sets optional password
    # Pre-configures WAN/LAN in UCI, includes summary and confirmation

    # Default resource values
    DEFAULT_MEMORY="256" # MB
    DEFAULT_CORES="2" # CPU cores
    DEFAULT_STORAGE="0.5" # GB
    DEFAULT_SUBNET="10.23.45.1/24" # LAN subnet
    ARCH="x86_64" # Architecture
    TEMPLATE_DIR="/var/lib/vz/template/cache" # Default template location

    # Colors for output
    RED='\033[0;31m'
    GREEN='\033[0;32m'
    NC='\033[0m'

    # Exit handler for cleanup and messages
    exit_script() {
    local code=$1
    local msg=$2
    [ -n "$msg" ] && echo -e "${RED}$msg${NC}"
    exit "$code"
    }

    # Check if running as root
    [ "$EUID" -ne 0 ] && exit_script 1 "Error: This script must be run as root"

    # Check required tools
    for cmd in wget pct pvesm ip curl whiptail pvesh bridge; do
    command -v "$cmd" &>/dev/null || exit_script 1 "Error: $cmd is not installed. Please install it first."
    done

    # Generic whiptail radiolist function
    whiptail_radiolist() {
    local title="$1" prompt="$2" height="$3" width="$4" items=("${@:5}")
    local selection
    selection=$(whiptail --title "$title" --radiolist "$prompt" "$height" "$width" "$((${#items[@]} / 3))" "${items[@]}" 3>&1 1>&2 2>&3) || \
    exit_script 1 "Error: $title selection aborted"
    echo "$selection"
    }

    # Detect latest stable OpenWrt version (silent)
    detect_latest_version() {
    local ver
    ver=$(curl -sSf "https://downloads.openwrt.org/releases/" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | sort -V | tail -1)
    [ -z "$ver" ] && ver="24.10.0" # Default to 24.10.0 if detection fails
    echo "$ver"
    }

    # Select storage
    select_storage() {
    local content='rootdir' label='Container'
    local -a menu
    while read -r line || [ -n "$line" ]; do
    local tag=$(echo "$line" | awk '{print $1}')
    local type=$(echo "$line" | awk '{printf "%-10s", $2}')
    local free=$(echo "$line" | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf "%9sB", $6}')
    menu+=("$tag" "Type: $type Free: $free" "OFF")
    done < <(pvesm status -content "$content" | awk 'NR>1')

    [ ${#menu[@]} -eq 0 ] && exit_script 1 "Error: No storage pools found for $label"
    [ $((${#menu[@]} / 3)) -eq 1 ] && echo "${menu[0]}" && return
    whiptail_radiolist "Storage Pools" "Which storage pool for the ${label,,}?\nUse Spacebar to select." 16 $(( $(echo "${menu[*]}" | wc -L) + 23 )) "${menu[@]}"
    }

    # Detect network options (bridges and unbridged devices)
    detect_network_options() {
    BRIDGE_LIST=($(ip link | grep -o 'vmbr[0-9]\+' | sort -u))
    BRIDGE_COUNT=${#BRIDGE_LIST[@]}

    local all_devs
    all_devs=$(ip link show | grep -oE '^[0-9]+: ([^:]+):' | awk '{print $2}' | cut -d':' -f1 | grep -vE '^(lo|vmbr|veth|tap|fwbr|fwpr|fwln)')
    readarray -t ALL_DEVICES <<<"$all_devs"

    local bridged_devs
    bridged_devs=$(bridge link show | cut -d ":" -f2 | cut -d " " -f2)
    readarray -t BRIDGED_DEVICES <<<"$bridged_devs"

    UNBRIDGED_DEVICES=()
    for dev in "${ALL_DEVICES[@]}"; do
    bridged=false
    for bridged_dev in "${BRIDGED_DEVICES[@]}"; do
    [ "$dev" = "$bridged_dev" ] && bridged=true && break
    done
    [ "$bridged" = false ] && UNBRIDGED_DEVICES+=("$dev")
    done
    UNBRIDGED_COUNT=${#UNBRIDGED_DEVICES[@]}
    }

    # Select network option
    select_network_option() {
    local type="$1" eth="$2"
    local -a menu=("None" "No network assigned" "OFF")
    for bridge in "${BRIDGE_LIST[@]}"; do
    menu+=("bridge:$bridge" "Bridge $bridge" "OFF")
    done
    for device in "${UNBRIDGED_DEVICES[@]}"; do
    menu+=("device:$device" "Device $device" "OFF")
    done
    whiptail_radiolist "$type Network Selection" "Select a bridge or device for $type ($eth) or 'None':\nUse Spacebar to select." 16 60 "${menu[@]}"
    }

    # Detect next available Container ID
    detect_next_ctid() {
    local id
    id=$(pvesh get /cluster/nextid)
    echo "${id:-100}"
    }

    # Prompt with default value
    prompt_with_default() {
    local prompt="$1" default="$2" var="$3"
    read -e -p "$prompt (default: $default): " -i "$default" input
    eval "$var=\"${input:-$default}\""
    }

    # Main execution
    echo -e "${GREEN}Fetching latest stable OpenWrt version...${NC}"
    VER=$(detect_latest_version)
    echo -e "${GREEN}Detected latest stable version: $VER${NC}"
    prompt_with_default "Enter OpenWrt version" "$VER" VER
    DOWNLOAD_URL="https://downloads.openwrt.org/releases/$VER/targets/x86/64/openwrt-$VER-x86-64-rootfs.tar.gz"
    TEMPLATE_FILE="openwrt-$VER-$ARCH.tar.gz"

    NEXT_CTID=$(detect_next_ctid)
    prompt_with_default "Enter Container ID" "$NEXT_CTID" CTID
    prompt_with_default "Enter Container Name" "openwrt-$CTID" CTNAME

    while true; do
    read -s -p "Enter root password (leave blank to skip): " PASSWORD; echo
    read -s -p "Confirm root password: " PASSWORD_CONFIRM; echo
    if [ -z "$PASSWORD" ] && [ -z "$PASSWORD_CONFIRM" ]; then
    echo -e "${GREEN}Root password skipped.${NC}"
    break
    elif [ "$PASSWORD" = "$PASSWORD_CONFIRM" ]; then
    break
    else
    echo -e "${RED}Passwords do not match. Please try again.${NC}"
    fi
    done

    prompt_with_default "Enter memory size in MB" "$DEFAULT_MEMORY" MEMORY
    prompt_with_default "Enter number of CPU cores" "$DEFAULT_CORES" CORES
    prompt_with_default "Enter storage limit in GB" "$DEFAULT_STORAGE" STORAGE_SIZE
    prompt_with_default "Enter LAN subnet" "$DEFAULT_SUBNET" SUBNET

    # Validate inputs
    [[ "$CTID" =~ ^[0-9]+$ && "$CTID" -ge 100 ]] || exit_script 1 "Error: Container ID must be a number >= 100"
    pct list | awk '{print $1}' | grep -q "^$CTID$" && exit_script 1 "Error: Container ID $CTID is already in use"
    [[ "$MEMORY" =~ ^[0-9]+$ && "$MEMORY" -ge 64 ]] || exit_script 1 "Error: Memory size must be a number >= 64 MB"
    [[ "$CORES" =~ ^[0-9]+$ && "$CORES" -ge 1 ]] || exit_script 1 "Error: Core count must be a number >= 1"
    [[ "$STORAGE_SIZE" =~ ^[0-9]*\.?[0-9]+$ && "$(echo "$STORAGE_SIZE > 0" | bc)" -eq 1 ]] || exit_script 1 "Error: Storage limit must be a positive number"

    # Parse subnet
    LAN_IP=$(echo "$SUBNET" | cut -d'/' -f1)
    LAN_PREFIX=$(echo "$SUBNET" | cut -d'/' -f2)
    case "$LAN_PREFIX" in
    24) LAN_NETMASK="255.255.255.0" ;;
    23) LAN_NETMASK="255.255.254.0" ;;
    22) LAN_NETMASK="255.255.252.0" ;;
    16) LAN_NETMASK="255.255.0.0" ;;
    *) exit_script 1 "Error: Unsupported subnet prefix /$LAN_PREFIX. Use /16, /22, /23, or /24" ;;
    esac

    STORAGE=$(select_storage container)
    detect_network_options
    [ "$BRIDGE_COUNT" -eq 0 ] && [ "$UNBRIDGED_COUNT" -eq 0 ] && echo -e "${RED}Warning: No network options found. Selecting 'None' for WAN/LAN.${NC}"

    WAN_OPTION=$(select_network_option "WAN" "eth0")
    LAN_OPTION=$(select_network_option "LAN" "eth1")

    echo -e "${RED}Note: Wan Option: $WAN_OPTION, Lan Option: $LAN_OPTION${NC}"

    WAN_BRIDGE=""; WAN_DEVICE=""
    if [[ "$WAN_OPTION" == bridge:* ]]; then
    WAN_BRIDGE="${WAN_OPTION#bridge:}"
    elif [[ "$WAN_OPTION" == device:* ]]; then
    WAN_DEVICE="${WAN_OPTION#device:}"
    fi

    LAN_BRIDGE=""; LAN_DEVICE=""
    if [[ "$LAN_OPTION" == bridge:* ]]; then
    LAN_BRIDGE="${LAN_OPTION#bridge:}"
    elif [[ "$LAN_OPTION" == device:* ]]; then
    LAN_DEVICE="${LAN_OPTION#device:}"
    fi

    # Summary and confirmation
    SUMMARY="Container Configuration Summary:\n"
    SUMMARY+=" OpenWrt Version: $VER\n"
    SUMMARY+=" Container ID: $CTID\n"
    SUMMARY+=" Container Name: $CTNAME\n"
    SUMMARY+=" Root Password: $( [ -n "$PASSWORD" ] && echo "Set" || echo "Not set" )\n"
    SUMMARY+=" Memory: $MEMORY MB\n"
    SUMMARY+=" CPU Cores: $CORES\n"
    SUMMARY+=" Storage: $STORAGE_SIZE GB on $STORAGE\n"
    SUMMARY+=" LAN Subnet: $SUBNET\n"
    SUMMARY+=" WAN Interface: ${WAN_BRIDGE:-${WAN_DEVICE:-None}} (eth0, DHCP/DHCPv6)\n"
    SUMMARY+=" LAN Interface: ${LAN_BRIDGE:-${LAN_DEVICE:-None}} (eth1, static)\n"

    whiptail --title "Confirm Container Creation" --yesno "$SUMMARY\nProceed with container creation?" 30 60 || exit_script 0 "Container creation aborted by user"

    # Download template
    if [ ! -f "$TEMPLATE_DIR/$TEMPLATE_FILE" ]; then
    echo -e "${GREEN}Downloading OpenWrt $VER rootfs...${NC}"
    wget -q "$DOWNLOAD_URL" -O "$TEMPLATE_DIR/$TEMPLATE_FILE" || exit_script 1 "Error: Failed to download OpenWrt $VER image"
    else
    echo -e "${GREEN}Using existing OpenWrt image: $TEMPLATE_FILE${NC}"
    fi

    # Build pct create command with corrected network options
    echo -e "${GREEN}Creating LXC container $CTID...${NC}"
    NET_OPTS=()
    [ -n "$WAN_BRIDGE" ] && NET_OPTS+=("--net0" "name=eth0,bridge=$WAN_BRIDGE")
    [ -n "$WAN_DEVICE" ] && NET_OPTS+=("--net0" "name=eth0,hwaddr=$(ip link show "$WAN_DEVICE" | grep -o 'ether [0-9a-f:]\+' | cut -d' ' -f2)")
    [ -n "$LAN_BRIDGE" ] && NET_OPTS+=("--net1" "name=eth1,bridge=$LAN_BRIDGE")
    [ -n "$LAN_DEVICE" ] && NET_OPTS+=("--net1" "name=eth1,hwaddr=$(ip link show "$LAN_DEVICE" | grep -o 'ether [0-9a-f:]\+' | cut -d' ' -f2)")

    pct create "$CTID" "$TEMPLATE_DIR/$TEMPLATE_FILE" \
    --arch amd64 \
    --hostname "$CTNAME" \
    --rootfs "$STORAGE:$STORAGE_SIZE" \
    --memory "$MEMORY" \
    --cores "$CORES" \
    --unprivileged 1 \
    --features nesting=1 \
    --ostype unmanaged \
    "${NET_OPTS[@]}" || exit_script 1 "Error: Failed to create container"


    echo -e "${GREEN}Starting container $CTID...${NC}"
    pct start "$CTID" || exit_script 1 "Error: Failed to start container"

    pct exec "$CTID" -- sh -c "sed -i 's!procd_add_jail!: procd_add_jail!g' /etc/init.d/dnsmasq"
    sleep 10

    echo -e "${GREEN}Configuring network...${NC}"
    pct exec "$CTID" -- sh -c "
    # Always configure WAN (eth0) with DHCP and DHCPv6
    uci set network.wan=interface
    uci set network.wan.proto='dhcp'
    uci set network.wan.device='eth0'
    uci set network.wan6=interface
    uci set network.wan6.proto='dhcpv6'
    uci set network.wan6.device='eth0'
    # Always configure LAN (eth1) with static IP
    uci set network.lan=interface
    uci set network.lan.proto='static'
    uci set network.@device[0].ports='eth1'
    uci set network.lan.ipaddr='$LAN_IP'
    uci set network.lan.netmask='$LAN_NETMASK'
    # Commit changes and restart network
    uci commit network
    /etc/init.d/network restart" || echo -e "${RED}Warning: Network configuration failed${NC}"

    [ -n "$PASSWORD" ] && {
    echo -e "${GREEN}Setting root password...${NC}"
    echo -e "$PASSWORD\n$PASSWORD" | pct exec "$CTID" -- passwd || echo -e "${RED}Warning: Failed to set root password${NC}"
    } || echo -e "${GREEN}Root password not set (left blank).${NC}"

    echo -e "${GREEN}Container $CTID ($CTNAME) created and started!${NC}"
    echo "Next steps:"
    echo "1. Access: pct exec $CTID /bin/sh"
    echo "2. Verify network: uci show network"
    echo "3. Update: opkg update"
    echo "4. Install LuCI: opkg install luci"
    [ -n "$LAN_BRIDGE" ] || [ -n "$LAN_DEVICE" ] && echo "5. LuCI: http://$LAN_IP" || echo "5. Add eth1 to activate LAN: http://$LAN_IP"
    [ -z "$PASSWORD" ] && echo "6. Set password if needed: pct exec $CTID passwd"