#!/bin/bash
#
# Script: setup
#
# Author: Gabriel Luchina
# https://luchina.com.br
#
# COPYRIGHT - 2021 -> 2026
#
# All rights reserved - You may not copy, reproduce, distribute, publish, display, perform, modify,
# create derivative works, transmit, or in any way exploit any such content, nor may you distribute
# any part of this content over any network, including a local area network, sell or offer it for
# sale, or use such content to construct any kind of database.
#
# You may not alter or remove any copyright or other notice from copies of the content on any scripts
# in the solution of 'OSX-PROXMOX Solution - GABRIEL LUCHINA'.
#
# Copying or storing any content except as provided above is expressly prohibited without prior
# written permission of copyright holder identified in the individual content’s copyright notice.
#
# For permission to use the content 'OSX-PROXMOX Solution - GABRIEL LUCHINA',
# please contact legal@luchina.com.br
#
# FOR DEV/STUDENT ONLY PURPOSES - NOT COMERCIAL
#
# Credits:
# https://github.com/acidanthera/OpenCorePkg
# https://github.com/corpnewt/MountEFI

################################################################################################################################################################################################

# Exit on any error
set -e

# Constants
SCRIPT_DIR="/root/OSX-PROXMOX"
LOGDIR="${SCRIPT_DIR}/logs"
MAIN_LOG="${LOGDIR}/main.log"
TMPDIR="${SCRIPT_DIR}/tmp"
HACKPXVERSION="2026.09.08"
OCVERSION="1.0.8"
DEFAULT_VM_PREFIX="HACK-"
BASE_RAM_SIZE=4096
RAM_PER_CORE=512
BASE_DISK_SIZE=128
MAX_CORES=32
DHCP_CONF_DIR="/etc/dhcp/dhcpd.d"
NETWORK_INTERFACES_FILE="/etc/network/interfaces"
DHCP_USER="dhcpd"
OPENCORE_ISO="opencore-osx-proxmox-vm.iso"

# macOS version configuration
declare -A MACOS_CONFIG=(
  ["1"]="High Sierra|10.13|Mac-BE088AF8C5EB4FA2|00000000000J80300|800M|sata0"
  ["2"]="Mojave|10.14|Mac-7BA5B2DFE22DDD8C|00000000000KXPG00|800M|sata0"
  ["3"]="Catalina|10.15|Mac-00BE6ED71E35EB86|00000000000000000|800M|virtio0"
  ["4"]="Big Sur|11|Mac-42FD25EABCABB274|00000000000000000|1024M|virtio0"
  ["5"]="Monterey|12|Mac-E43C1C25D4880AD6|00000000000000000|1024M|virtio0"
  ["6"]="Ventura|13|Mac-B4831CEBD52A0C4C|00000000000000000|1024M|virtio0"
  ["7"]="Sonoma|14|Mac-827FAC58A8FDFA22|00000000000000000|1450M|virtio0"
  ["8"]="Sequoia|15|Mac-7BA5B2D9E42DDD94|00000000000000000|1450M|virtio0"
  ["9"]="Tahoe|26|Mac-27AD2F918AE68F61|00000000000000000|1450M|virtio0"
)

# Display and log function
display_and_log() {
  local message="$1"
  local specific_logfile="$2"
  echo "$message"
  echo "$(date '+%Y-%m-%d %H:%M:%S') - $message" >> "$MAIN_LOG"
  if [[ -n "$specific_logfile" ]]; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $message" >> "$specific_logfile"
  fi
}

# Cleanup function for mounts and temp files
cleanup() {
  local logfile="${LOGDIR}/cleanup.log"
  if mountpoint -q /mnt/APPLE 2>/dev/null; then
    umount /mnt/APPLE >>"$logfile" 2>&1 || display_and_log "Failed to unmount /mnt/APPLE" "$logfile"
    rmdir /mnt/APPLE 2>/dev/null
  fi
  if mountpoint -q /mnt/opencore 2>/dev/null; then
    umount /mnt/opencore >>"$logfile" 2>&1 || display_and_log "Failed to unmount /mnt/opencore" "$logfile"
    rmdir /mnt/opencore 2>/dev/null
  fi
  losetup -a | grep -q "$TMPDIR" && losetup -d $(losetup -j "$TMPDIR"/* | awk -F: '{print $1}') >>"$logfile" 2>&1
  rm -rf "${TMPDIR:?}"/* 2>/dev/null
}
trap cleanup EXIT

# Function to check if a number is a power of 2
is_power_of_2() {
  local n=$1
  ((n > 0 && (n & (n - 1)) == 0))
}

# Function to get the next power of 2
next_power_of_2() {
  local n=$1
  local p=1
  while ((p < n)); do
    p=$((p * 2))
  done
  echo $p
}

# Function to log errors and exit
log_and_exit() {
  local message=$1
  local logfile=$2
  display_and_log "$message" "$logfile"
  exit 1
}

# Function to validate VM name
validate_vm_name() {
  local vm_name=$1
  [[ "$vm_name" =~ ^[a-zA-Z0-9][a-zA-Z0-9_.-]*[a-zA-Z0-9]$ && ! "$vm_name" =~ [[:space:]] ]]
}

# Function to compare version numbers
version_compare() {
  local v1=$1 v2=$2
  local IFS='.'
  local v1_parts=($v1) v2_parts=($v2)
  local max_len=$(( ${#v1_parts[@]} > ${#v2_parts[@]} ? ${#v1_parts[@]} : ${#v2_parts[@]} ))

  for ((i=0; i<max_len; i++)); do
    local v1_part=${v1_parts[i]:-0}
    local v2_part=${v2_parts[i]:-0}
    if (( v1_part > v2_part )); then
      return 0
    elif (( v1_part < v2_part )); then
      return 1
    fi
  done
  return 0
}

# Function to get available storages for VMs
get_available_storages() {
  local logfile="${LOGDIR}/storage-detection.log"
  local storages=()
  local max_space=0
  local default_storage=""

  local storage_list
  storage_list=$(pvesm status --content images 2>>"$logfile") || log_and_exit "Failed to retrieve storage list" "$logfile"
  while IFS= read -r line; do
    [[ "$line" =~ ^Name.* ]] && continue
    read -r storage_name type status total used avail percent <<< "$line"
    [[ "$status" != "active" || ! "$avail" =~ ^[0-9]+$ || "$avail" -eq 0 ]] && continue
    local avail_space_gb=$(echo "scale=2; $avail / 1024 / 1024" | bc 2>/dev/null)
    storages+=("$storage_name|$avail|$avail_space_gb")
    if [[ $(echo "$avail > $max_space" | bc -l) -eq 1 ]]; then
      max_space=$avail
      default_storage="$storage_name"
    fi
  done <<< "$storage_list"

  [[ ${#storages[@]} -eq 0 || -z "$default_storage" ]] && log_and_exit "No active storages found" "$logfile"
  for storage in "${storages[@]}"; do echo "$storage"; done
  echo "$default_storage"
}

# Function to get available storages for ISOs
get_available_iso_storages() {
  local logfile="${LOGDIR}/iso-storage-detection.log"
  local storages=()
  local max_space=0
  local default_storage=""

  local storage_list
  storage_list=$(pvesm status --content iso 2>>"$logfile") || log_and_exit "Failed to retrieve ISO storage list" "$logfile"
  while IFS= read -r line; do
    [[ "$line" =~ ^Name.* ]] && continue
    read -r storage_name type status total used avail percent <<< "$line"
    [[ "$status" != "active" || ! "$avail" =~ ^[0-9]+$ || "$avail" -eq 0 ]] && continue
    local avail_space_gb=$(echo "scale=2; $avail / 1024 / 1024" | bc 2>/dev/null)
    storages+=("$storage_name|$avail|$avail_space_gb")
    if [[ $(echo "$avail > $max_space" | bc -l) -eq 1 ]]; then
      max_space=$avail
      default_storage="$storage_name"
    fi
  done <<< "$storage_list"

  [[ ${#storages[@]} -eq 0 || -z "$default_storage" ]] && log_and_exit "No active ISO storages found" "$logfile"
  for storage in "${storages[@]}"; do echo "$storage"; done
  echo "$default_storage"
}

# Function to ensure jq is installed
ensure_jq_dependency() {
  local logfile="${LOGDIR}/jq-dependency.log"
  if ! command -v jq >/dev/null 2>&1; then
    display_and_log "Installing jq..." "$logfile"
    apt-get update >>"$logfile" 2>&1 || log_and_exit "Failed to update apt" "$logfile"
    apt-get install -y jq >>"$logfile" 2>&1 || log_and_exit "Failed to install jq" "$logfile"
  fi
}

# Function to ensure xmlstarlet is installed
ensure_xmlstarlet_dependency() {
  local logfile="${LOGDIR}/xmlstarlet-dependency.log"
  if ! command -v xmlstarlet >/dev/null 2>&1; then
    display_and_log "Installing xmlstarlet..." "$logfile"
    apt-get update >>"$logfile" 2>&1 || log_and_exit "Failed to update apt" "$logfile"
    apt-get install -y xmlstarlet >>"$logfile" 2>&1 || log_and_exit "Failed to install xmlstarlet" "$logfile"
  fi
}

# Function to ensure base64 and xxd are available
ensure_base64_xxd_dependency() {
  local logfile="${LOGDIR}/base64-xxd-dependency.log"
  if ! command -v base64 >/dev/null || ! command -v xxd >/dev/null; then
    display_and_log "Installing base64 and xxd..." "$logfile"
    apt-get update >>"$logfile" 2>&1 || log_and_exit "Failed to update apt" "$logfile"
    apt-get install -y coreutils xxd vim-common >>"$logfile" 2>&1 || display_and_log "Failed to install base64 and xxd. Editing ROM in base64 format." "$logfile"
  fi
}

# Function to ensure wget is installed
ensure_wget_dependency() {
  local logfile="${LOGDIR}/wget-dependency.log"
  if ! command -v wget >/dev/null 2>&1; then
    display_and_log "Installing wget..." "$logfile"
    apt-get update >>"$logfile" 2>&1 || log_and_exit "Failed to update apt" "$logfile"
    apt-get install -y wget >>"$logfile" 2>&1 || log_and_exit "Failed to install wget" "$logfile"
  fi
}

# Function to set ISODIR based on selected ISO storage
set_isodir() {
  local logfile="${LOGDIR}/iso-storage-detection.log"
  ensure_jq_dependency
  local storage_output=$(get_available_iso_storages) || { display_and_log "Failed to retrieve ISO storages"; read -n 1 -s; return 1; }
  local storages=() default_storage=""
  while IFS= read -r line; do
    [[ -z "$line" ]] && continue
    [[ -z "$default_storage" && ! "$line" =~ \| ]] && default_storage="$line" || storages+=("$line")
  done <<< "$storage_output"

  if ((${#storages[@]} == 0)); then
    log_and_exit "No ISO storages found" "$logfile"
  fi

  if ((${#storages[@]} == 1)); then
    storage_iso="${storages[0]%%|*}"
    display_and_log "Using ISO storage: $storage_iso" "$logfile"
  else
    while true; do
      display_and_log "Available ISO storages:" "$logfile"
      for s in "${storages[@]}"; do
        storage_name="${s%%|*}"
        avail_space="${s##*|}"
        display_and_log "  - $storage_name ($avail_space GB)" "$logfile"
      done
      read -rp "ISO Storage [${default_storage}]: " storage_iso
      storage_iso=${storage_iso:-$default_storage}
      local valid=false
      for s in "${storages[@]}"; do
        if [[ "$storage_iso" == "${s%%|*}" ]]; then
          valid=true
          break
        fi
      done
      if $valid; then
        display_and_log "Selected ISO storage: $storage_iso" "$logfile"
        break
      else
        display_and_log "Invalid ISO storage. Please try again." "$logfile"
      fi
    done
  fi

  local storage_iso_path
  storage_iso_path=$(pvesh get /storage/"${storage_iso}" --output-format json | jq -r '.path') || log_and_exit "Failed to retrieve path for storage $storage_iso" "$logfile"
  [[ -z "$storage_iso_path" ]] && log_and_exit "Storage path for $storage_iso is empty" "$logfile"
  ISODIR="${storage_iso_path}/template/iso/"
  mkdir -p "$ISODIR" || log_and_exit "Failed to create ISODIR: $ISODIR" "$logfile"
  display_and_log "ISODIR set to: $ISODIR" "$logfile"
}

# Function to get available bridges
get_available_bridges() {
  local bridges=()
  local default_bridge="vmbr0"

  local bridge_lines=$(grep -E '^iface vmbr[0-9]+' "$NETWORK_INTERFACES_FILE")
  while IFS= read -r line; do
    [[ -z "$line" ]] && continue
    if [[ "$line" =~ ^iface\ (vmbr[0-9]+) ]]; then
      local bridge_name="${BASH_REMATCH[1]}"
      [[ ! -d "/sys/class/net/$bridge_name" ]] && continue
      local address=$(awk "/^iface $bridge_name/{p=1} p&&/^[[:space:]]*address/{print \$2; exit}" "$NETWORK_INTERFACES_FILE" | sed 's|/.*||' | tr -d '\r')
      if [[ -n "$address" && "$address" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
        bridges+=("$bridge_name|$address")
      else
        bridges+=("$bridge_name|unknown")
      fi
    fi
  done <<< "$bridge_lines"

  [[ ${#bridges[@]} -eq 0 ]] && bridges+=("$default_bridge|unknown")
  printf '%s\n' "${bridges[@]}"
  echo "$default_bridge"
}

# Function to initialize directories
init_dirs() {
  mkdir -p "$LOGDIR" "$TMPDIR" || log_and_exit "Failed to create directories" "${LOGDIR}/init-dirs.log"
  touch "$MAIN_LOG"  # Ensure main log exists
}

# Function to check Proxmox version
check_proxmox_version() {
  local log_file="${LOGDIR}/proxmox-version.log"

  # Check supported Proxmox versions
  local version=$(pveversion | grep -oE "pve-manager/[0-9.]+")
  if [[ "$version" != pve-manager/[7-9].* ]]; then
    log_and_exit "Unsupported Proxmox version. Use 7.x, 8.x, or 9.x" "$log_file"
  fi

  # Warn about preliminary Proxmox 9 support
  # if [[ "$version" == pve-manager/9.* ]]; then
  #  display_and_log "Proxmox 9 is in preliminary testing. Use at your own risk." "$log_file"
  #  sleep 5
  # fi
}

# Function to detect CPU platform
detect_cpu_platform() {
  lscpu | grep -qi "Vendor ID.*AMD" && echo "AMD" || echo "INTEL"
}

# Function to detect the Intel microarchitecture (CPUID family/model)
detect_intel_uarch() {
  local family model
  family=$(awk -F': ' '/^cpu family/ {print $2; exit}' /proc/cpuinfo)
  model=$(awk -F': ' '/^model[[:space:]]*:/ {print $2; exit}' /proc/cpuinfo)
  if [[ "$family" == "6" ]]; then
    case "$model" in
      60|69|70) echo "HASWELL" ;;         # Core i5/i7 4xxx Desktop/Mobile (Haswell DT, ULT, GT3e)
      63) echo "HASWELL_HEDT" ;;          # Xeon E5 v3 / Core i7 HEDT (Haswell-E/EP)
      79|86) echo "BROADWELL_HEDT" ;;     # Xeon E5 v4 / Core i7 HEDT (Broadwell-E/EP), Xeon D (Broadwell-DE)
      *) echo "GENERIC" ;;
    esac
  else
    echo "GENERIC"
  fi
}

# Function to setup prerequisites
setup_prerequisites() {
  local logfile="${LOGDIR}/prerequisites-setup.log"
  # The OpenCore ISO is already provisioned in ISODIR by update_opencore_iso at startup
  printf "alias osx-setup='%s/setup'\n" "$SCRIPT_DIR" >> /root/.bashrc
  printf "LANG=en_US.UTF-8\nLC_ALL=en_US.UTF-8\n" > /etc/environment
  printf "set mouse-=a\n" > ~/.vimrc
  rm -f /etc/apt/sources.list.d/pve-enterprise.list
  apt-get update >>"$logfile" 2>&1 || {
    local country=$(curl -s https://ipinfo.io/country | tr '[:upper:]' '[:lower:]')
    sed -i "s/ftp.$country.debian.org/ftp.debian.org/g" /etc/apt/sources.list
    apt-get update >>"$logfile" 2>&1 || log_and_exit "Failed to update apt" "$logfile"
  }
  apt-get install -y vim unzip zip sysstat parted wget iptraf git htop ipcalc coreutils vim-common xmlstarlet >>"$logfile" 2>&1 || log_and_exit "Failed to install packages" "$logfile"
  sed -i 's/GRUB_TIMEOUT=5/GRUB_TIMEOUT=0/g' /etc/default/grub
  local grub_cmd="quiet"
  if [[ $OSX_PLATFORM == "AMD" ]]; then
    grub_cmd="quiet amd_iommu=on iommu=pt video=vesafb:off video=efifb:off"
    printf "options kvm-amd nested=1\n" > /etc/modprobe.d/kvm-amd.conf
  else
    grub_cmd="quiet intel_iommu=on iommu=pt video=vesafb:off video=efifb:off"
    printf "options kvm-intel nested=Y\n" > /etc/modprobe.d/kvm-intel.conf
  fi
  pveversion | grep -qE "pve-manager/(7.[2-4]|8.[0-4]|9)" && grub_cmd="$grub_cmd initcall_blacklist=sysfb_init"
  sed -i "s/GRUB_CMDLINE_LINUX_DEFAULT=\"quiet\"/GRUB_CMDLINE_LINUX_DEFAULT=\"$grub_cmd\"/g" /etc/default/grub
  printf "vfio\nvfio_iommu_type1\nvfio_pci\nvfio_virqfd\n" >> /etc/modules
  printf "blacklist nouveau\nblacklist nvidia\nblacklist snd_hda_codec_hdmi\nblacklist snd_hda_intel\nblacklist snd_hda_codec\nblacklist snd_hda_core\nblacklist radeon\nblacklist amdgpu\n" >> /etc/modprobe.d/pve-blacklist.conf
  printf "options kvm ignore_msrs=Y report_ignored_msrs=0\n" > /etc/modprobe.d/kvm.conf
  printf "options vfio_iommu_type1 allow_unsafe_interrupts=1\n" > /etc/modprobe.d/iommu_unsafe_interrupts.conf
  [ -f /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js ] && sed -i.backup -z "s/res === null || res === undefined || \!res || res\n\t\t\t.data.status.toLowerCase() \!== 'active'/false/g" /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js
  touch /etc/pve/qemu-server/.osx-proxmox
  update-grub >>"$logfile" 2>&1 || log_and_exit "Failed to update GRUB" "$logfile"
  display_and_log "Prerequisites setup complete. A reboot is necessary to apply the required changes." "$logfile"
  display_and_log "Press enter to reboot or Ctrl+C if you intend to reboot later." "$logfile"
  read noop
  display_and_log "Enter pressed. Rebooting in 15 seconds..." "$logfile"
  sleep 15 && reboot
}

# Function to download recovery image
download_recovery_image() {
  local version_name=$1 board_id=$2 model_id=$3 iso_size=$4
  local logfile="${LOGDIR}/crt-recovery-${version_name,,}.log"
  local iso_path="${ISODIR}/recovery-${version_name,,}.iso"

  [[ -e "$iso_path" ]] && { display_and_log "Recovery image for $version_name exists" "$logfile"; return; }
  display_and_log "Creating recovery image for $version_name..." "$logfile"
  fallocate -x -l "$iso_size" "${TMPDIR}/recovery-${version_name,,}.iso" >>"$logfile" 2>&1 || log_and_exit "Failed to allocate image" "$logfile"
  mkfs.msdos -F 32 "${TMPDIR}/recovery-${version_name,,}.iso" -n "${version_name^^}" >>"$logfile" 2>&1 || log_and_exit "Failed to format image" "$logfile"
  local loopdev=$(losetup -f --show "${TMPDIR}/recovery-${version_name,,}.iso") || log_and_exit "Failed to set up loop device" "$logfile"
  mkdir -p /mnt/APPLE >>"$logfile" 2>&1 || log_and_exit "Failed to create mount point" "$logfile"
  mount "$loopdev" /mnt/APPLE >>"$logfile" 2>&1 || log_and_exit "Failed to mount image" "$logfile"
  cd /mnt/APPLE
  local recovery_args="-b $board_id -m $model_id download"
  [[ "$version_name" == "Tahoe" ]] && recovery_args="$recovery_args -os latest"
  python3 "${SCRIPT_DIR}/tools/macrecovery/macrecovery.py" $recovery_args >>"$logfile" 2>&1 || log_and_exit "Failed to download recovery" "$logfile"
  cd "$SCRIPT_DIR"
  umount /mnt/APPLE >>"$logfile" 2>&1 || log_and_exit "Failed to unmount image" "$logfile"
  losetup -d "$loopdev" >>"$logfile" 2>&1 || log_and_exit "Failed to detach loop device" "$logfile"
  mv "${TMPDIR}/recovery-${version_name,,}.iso" "$iso_path" >>"$logfile" 2>&1 || log_and_exit "Failed to move image" "$logfile"
  display_and_log "Recovery image created successfully" "$logfile"
}

# Function to create VM
create_vm() {
  local iso_file version_name=$1 vm_id=$2 vm_name=$3 disk_size=$4 storage=$5 core_count=$6 ram_size=$7 iso_size=$8 disk_type=$9 bridge=${10}
  local logfile="${LOGDIR}/crt-vm-${OSX_PLATFORM,,}-${version_name,,}.log"
  iso_file="${OPENCORE_ISO}"
  if [ ! -f "${ISODIR}/$iso_file" ]; then
    update_opencore_iso
  fi
  [[ ! -d "/sys/class/net/$bridge" ]] && log_and_exit "Bridge $bridge does not exist" "$logfile"

  local cpu_args device_args='-device isa-applesmc,osk="ourhardworkbythesewordsguardedpleasedontsteal(c)AppleComputerInc" -smbios type=2'
  if [[ "$version_name" =~ ^(Sonoma|Sequoia|Tahoe)$ ]]; then
    device_args="$device_args -device qemu-xhci -device usb-kbd -device usb-tablet -global nec-usb-xhci.msi=off"
  else
    device_args="$device_args -device usb-kbd,bus=ehci.0,port=2 -device usb-mouse,bus=ehci.0,port=3"
  fi
  if [[ "$OSX_PLATFORM" == "AMD" ]]; then
    if [[ "$version_name" =~ ^(Ventura|Sonoma|Sequoia|Tahoe)$ ]]; then
      cpu_args="-cpu Skylake-Client-v4,vendor=GenuineIntel,+invtsc,+hypervisor,-pcid,-hle,-rtm,-avx512f,-avx512dq,-avx512cd,-avx512bw,-avx512vl,-avx512vnni,-spec-ctrl,kvm=on,vmware-cpuid-freq=on"
    else
      cpu_args="-cpu Penryn,kvm=on,vendor=GenuineIntel,+kvm_pv_unhalt,+kvm_pv_eoi,+hypervisor,+invtsc,+ssse3,+sse4.2,+popcnt,+avx,+avx2,+aes,+fma,+bmi1,+bmi2,+xsave,+xsaveopt,check"
    fi
  else
    case "$OSX_INTEL_UARCH" in
      HASWELL)
        cpu_args="-cpu Haswell-noTSX,stepping=3,kvm=on,vendor=GenuineIntel,+kvm_pv_unhalt,+kvm_pv_eoi,+hypervisor,+invtsc,+vmx,vmware-cpuid-freq=on"
        ;;
      HASWELL_HEDT)
        cpu_args="-cpu Haswell-noTSX,model=158,kvm=on,vendor=GenuineIntel,+kvm_pv_unhalt,+kvm_pv_eoi,+hypervisor,+invtsc,+vmx,vmware-cpuid-freq=on"
        ;;
      BROADWELL_HEDT)
        cpu_args="-cpu Broadwell-noTSX,model=158,kvm=on,vendor=GenuineIntel,+kvm_pv_unhalt,+kvm_pv_eoi,+hypervisor,+invtsc,+vmx,vmware-cpuid-freq=on"
        ;;
      *)
        cpu_args="-cpu Skylake-Client-v4,kvm=on,vendor=GenuineIntel,+kvm_pv_unhalt,+kvm_pv_eoi,+hypervisor,+invtsc,+vmx,vmware-cpuid-freq=on"
        ;;
    esac
  fi

  # Check QEMU version and append hotplug fix if 6.1 or newer
  local qemu_version=$(qemu-system-x86_64 --version | awk '/version/ {print $4}' | cut -d'(' -f1)
  version_compare "$qemu_version" "6.1" && device_args="$device_args -global ICH9-LPC.acpi-pci-hotplug-with-bridge-support=off"

  # CD-ROM bus: High Sierra (10.13) and Mojave (10.14) use sata0 as the system
  # disk, so their CD-ROMs stay on IDE to avoid colliding with it.
  local cdrom_boot cdrom_recovery
  if [[ "$version_name" =~ ^(High\ Sierra|Mojave)$ ]]; then
    cdrom_boot="ide0"; cdrom_recovery="ide2"
  else
    cdrom_boot="sata0"; cdrom_recovery="sata2"
  fi

  qm create "$vm_id" \
    --agent 1 --args "$device_args $cpu_args" --autostart 0 \
    --balloon 0 --bios ovmf --boot "order=${cdrom_boot};$disk_type" \
    --cores "$core_count" --description "Hackintosh VM - $version_name" \
    --efidisk0 "${storage}:4" --machine q35 --memory "$ram_size" \
    --name "$vm_name" --net0 "vmxnet3,bridge=$bridge" --numa 0 \
    --onboot 0 --ostype other --sockets 1 --start 0 --tablet 1 \
    --vga vmware --vmgenid 1 --scsihw virtio-scsi-pci \
    --"$disk_type" "${storage}:${disk_size},cache=none,discard=on" \
    --"$cdrom_boot" "${storage_iso}:iso/${iso_file},media=cdrom,cache=unsafe,size=96M" \
    --"$cdrom_recovery" "${storage_iso}:iso/recovery-${version_name,,}.iso,media=cdrom,cache=unsafe,size=${iso_size}" >>"$logfile" 2>&1 || log_and_exit "Failed to create VM" "$logfile"
  sed -i 's/media=cdrom/media=disk/' "/etc/pve/qemu-server/$vm_id.conf" >>"$logfile" 2>&1 || log_and_exit "Failed to update VM config" "$logfile"

  display_and_log "VM ($vm_name) created successfully" "$logfile"
  local bridge_ip=$(ip -4 addr show "$bridge" | awk '/inet/ {print $2}' | cut -d'/' -f1 || echo "unknown")
  if [[ "$version_name" =~ "High Sierra" ]]; then
    display_and_log "NOTE: High Sierra has a 'The Recovery Server Could Not Be Contacted' Error!" "$logfile"
    display_and_log "Goto https://github.com/luchina-gabriel/OSX-PROXMOX#-high-sierra--below---recovery-server-could-not-be-contacted" "$logfile"
    display_and_log "and do the Fix." "$logfile"
  fi
  display_and_log "Access Proxmox Web Panel: https://$bridge_ip:8006" "$logfile"
}

# Function to add Proxmox VE no-subscription repository
add_no_subscription_repo() {
  local logfile="${LOGDIR}/add-repo-pve-no-subscription.log"
  if pveversion | grep -q "pve-manager/[7]"; then
    printf "deb http://download.proxmox.com/debian/pve bullseye pve-no-subscription\n" > /etc/apt/sources.list.d/pve-no-sub.list
  elif pveversion | grep -q "pve-manager/[8]"; then
    printf "deb http://download.proxmox.com/debian/pve bookworm pve-no-subscription\n" > /etc/apt/sources.list.d/pve-no-sub.list
  elif pveversion | grep -q "pve-manager/[9]"; then
    printf "Types: deb\nURIs: http://download.proxmox.com/debian/pve\nSuites: trixie\nComponents: pve-no-subscription\nSigned-By: /usr/share/keyrings/proxmox-archive-keyring.gpg\n" > /etc/apt/sources.list.d/pve-no-sub.sources
  else
    log_and_exit "Unsupported Proxmox version" "$logfile"
  fi
  apt update -y >>"$logfile" 2>&1 || log_and_exit "Failed to update apt" "$logfile"
  display_and_log "Repository added successfully" "$logfile"
  read -n 1 -sp "Press any key to return to menu..."
}

# Cleaned function to update the SMBIOS in the OpenCore ISO
update_opencore_smbios() {
  local iso_path=$1 oc_json_path=$2
  local logfile="${LOGDIR}/update-opencore-smbios.log"
  ensure_xmlstarlet_dependency
  ensure_base64_xxd_dependency "$logfile"
  ensure_jq_dependency

  if [[ ! -f "$oc_json_path" ]]; then
    read -rp "No Serial number generated. Do you want to generate a unique serial number? [Y/n]: " GENSMBIOS
    if [[ "${GENSMBIOS:-Y}" =~ ^[Yy]$ ]]; then
      SystemProductName="iMacPro1,1"
      read -e -p "Enter System Product Name (press Enter to keep current): " -i "$SystemProductName" new_value
        if [ -n "$new_value" ] && [ "$new_value" != "$SystemProductName" ]; then
          SystemProductName="$new_value"
        fi
      python3 "$SCRIPT_DIR"/tools/GenSMBIOS/GenSMBIOS.py --install
      python3 "$SCRIPT_DIR"/tools/GenSMBIOS/GenSMBIOS.py --generate "$SystemProductName" -j "$oc_json_path" >>"$logfile" 2>&1 || log_and_exit "Failed to generate SMBIOS" "$logfile"
    else
      display_and_log "Skipping SMBIOS generation" "$logfile"
      return
    fi
  fi

  local loopdev=$(losetup -f --show -P "$iso_path") || log_and_exit "Failed to set up loop device" "$logfile"
  mkdir -p /mnt/opencore >>"$logfile" 2>&1 || log_and_exit "Failed to create mount point" "$logfile"
  mount "${loopdev}p1" /mnt/opencore >>"$logfile" 2>&1 || log_and_exit "Failed to mount ISO" "$logfile"
  local config="/mnt/opencore/EFI/OC/config.plist"
  [[ ! -e "$config.backup" ]] && cp "$config" "$config.backup" >>"$logfile" 2>&1

  # Temporary file
  local TEMP_FILE="${config}.tmp"
  cp "$config" "$TEMP_FILE"

  # Define base XPaths
  local nvram_xpath="//key[text()='NVRAM']/following-sibling::dict/key[text()='Add']/following-sibling::dict/key[text()='7C436110-AB2A-4BBB-A880-FE41995C9F82']/following-sibling::dict"
  local platform_generic_xpath="//key[text()='PlatformInfo']/following-sibling::dict/key[text()='Generic']/following-sibling::dict"

  # Function to read a value from plist
  read_plist_value() {
    xmlstarlet sel -t -v "${2}/key[text()='${3}']/following-sibling::*[1]" "$1" 2>/dev/null
  }

  # Function to update a value in plist
  update_plist_value() {
    xmlstarlet ed -L -u "${2}/key[text()='${3}']/following-sibling::*[1]" -v "$4" "$1"
  }

  # Load and apply JSON values
  declare -A json_values
  json_values["SystemProductName"]=$(jq -r '.Type // empty' "$oc_json_path")
  json_values["SystemSerialNumber"]=$(jq -r '.Serial // empty' "$oc_json_path")
  json_values["MLB"]=$(jq -r '."Board Serial" // empty' "$oc_json_path")
  json_values["SystemUUID"]=$(jq -r '.SmUUID // empty' "$oc_json_path")
  json_values["ROM"]=$(jq -r '.ROM // empty' "$oc_json_path")  # HEX

  updated=false
  for key in SystemProductName SystemSerialNumber MLB SystemUUID ROM; do
    if [ -n "${json_values[$key]}" ]; then
      if [ "$key" == "ROM" ]; then
        if command -v base64 >/dev/null 2>&1 && command -v xxd >/dev/null 2>&1; then
          modified_value=$(echo -n "${json_values[$key]}" | xxd -r -p | base64)
        else
          display_and_log "Warning: base64 or xxd not available. Skipping ROM update." "$logfile"
          continue
        fi
      else
        modified_value="${json_values[$key]}"
      fi
      update_plist_value "$TEMP_FILE" "$platform_generic_xpath" "$key" "$modified_value"
      display_and_log "Updated $key from JSON." "$logfile"
      updated=true
    fi
  done

  # Automatic boot-args adjustment
  system_product_name=$(read_plist_value "$TEMP_FILE" "$platform_generic_xpath" "SystemProductName")
  boot_args=$(read_plist_value "$TEMP_FILE" "$nvram_xpath" "boot-args")
  flag=" -nehalem_error_disable"

  if [ "$system_product_name" = "MacPro5,1" ]; then
    if [[ ! "$boot_args" =~ $flag ]]; then
      new_boot_args="$boot_args$flag"
      update_plist_value "$TEMP_FILE" "$nvram_xpath" "boot-args" "$new_boot_args"
      display_and_log "Automatically added '$flag' to boot-args." "$logfile"
      updated=true
    fi
  else
    if [[ "$boot_args" =~ $flag ]]; then
      new_boot_args="${boot_args//$flag/}"
      update_plist_value "$TEMP_FILE" "$nvram_xpath" "boot-args" "$new_boot_args"
      display_and_log "Automatically removed '$flag' from boot-args." "$logfile"
      updated=true
    fi
  fi

  if $updated; then
    display_and_log "Differences between original and modified file (unified format):" "$logfile"
    xmlstarlet fo "$config" > "$config.fmt" || log_and_exit "Failed to format original file" "$logfile"
    diff -u "$config.fmt" "$TEMP_FILE" || true
    rm "$config.fmt"

    read -p "Do you want to apply these changes to the original file? (y/n): " confirm
    if [[ "$confirm" =~ ^[Yy]$ ]]; then
      mv "$TEMP_FILE" "$config"
      display_and_log "Changes applied to $config" "$logfile"
    else
      display_and_log "Changes discarded." "$logfile"
      rm "$TEMP_FILE"
    fi
  else
    display_and_log "No updates needed from JSON." "$logfile"
    rm "$TEMP_FILE"
  fi

  # Cleanup
  umount /mnt/opencore >>"$logfile" 2>&1 || log_and_exit "Failed to unmount ISO" "$logfile"
  losetup -d "$loopdev" >>"$logfile" 2>&1 || log_and_exit "Failed to detach loop device" "$logfile"
}

# Cleaned function to customize OpenCore config.plist
customize_opencore_config() {
  local oc_json_path iso logfile="${LOGDIR}/custom-oc-config.plist.log"
  iso="${ISODIR}/${OPENCORE_ISO}"
  oc_json_path="${ISODIR}/.smbios.json"
  ensure_xmlstarlet_dependency
  ensure_base64_xxd_dependency "$logfile"
  ensure_jq_dependency

  local loopdev=$(losetup -f --show -P "$iso") || log_and_exit "Failed to set up loop device" "$logfile"
  mkdir -p /mnt/opencore >>"$logfile" 2>&1 || log_and_exit "Failed to create mount point" "$logfile"
  mount "${loopdev}p1" /mnt/opencore >>"$logfile" 2>&1 || log_and_exit "Failed to mount ISO" "$logfile"
  local config="/mnt/opencore/EFI/OC/config.plist"
  [[ ! -e "$config.backup" ]] && cp "$config" "$config.backup" >>"$logfile" 2>&1

  # Temporary file
  local TEMP_FILE="${config}.tmp"
  cp "$config" "$TEMP_FILE"

  # Define base XPaths
  local nvram_xpath="//key[text()='NVRAM']/following-sibling::dict/key[text()='Add']/following-sibling::dict/key[text()='7C436110-AB2A-4BBB-A880-FE41995C9F82']/following-sibling::dict"
  local misc_boot_xpath="//key[text()='Misc']/following-sibling::dict/key[text()='Boot']/following-sibling::dict"
  local platform_generic_xpath="//key[text()='PlatformInfo']/following-sibling::dict/key[text()='Generic']/following-sibling::dict"

  # Define keys and sections
  declare -A key_sections=( ["boot-args"]="nvram" ["csr-active-config"]="nvram" ["prev-lang:kbd"]="nvram" ["Timeout"]="misc_boot" ["MLB"]="platform_generic" ["SystemProductName"]="platform_generic" ["SystemSerialNumber"]="platform_generic" ["SystemUUID"]="platform_generic" ["ROM"]="platform_generic" )
  keys=("boot-args" "csr-active-config" "prev-lang:kbd" "Timeout" "MLB" "SystemProductName" "SystemSerialNumber" "SystemUUID" "ROM")

  # Function to read a value from plist
  read_plist_value() {
    xmlstarlet sel -t -v "${2}/key[text()='${3}']/following-sibling::*[1]" "$1" 2>/dev/null
  }

  # Function to update a value in plist
  update_plist_value() {
    xmlstarlet ed -L -u "${2}/key[text()='${3}']/following-sibling::*[1]" -v "$4" "$1"
  }

  # Function to remove a key from plist
  remove_plist_key() {
    local key_xpath="${2}/key[text()='${3}']"
    local value_xpath="${2}/key[text()='${3}']/following-sibling::*[1]"
    xmlstarlet ed -L -d "$key_xpath" -d "$value_xpath" "$1" || log_and_exit "Failed to remove $3" "$logfile"
  }

  # Integrate JSON logic
  generate_new=false
  if [ -f "$oc_json_path" ]; then
    read -rp "Existing SMBIOS JSON found. Do you want to generate a new serial number? [y/N]: " GENSMBIOS
    if [[ "${GENSMBIOS}" =~ ^[Yy]$ ]]; then
      generate_new=true
    else
      # Apply existing JSON
      declare -A json_values
      json_values["SystemProductName"]=$(jq -r '.Type // empty' "$oc_json_path")
      json_values["SystemSerialNumber"]=$(jq -r '.Serial // empty' "$oc_json_path")
      json_values["MLB"]=$(jq -r '."Board Serial" // empty' "$oc_json_path")
      json_values["SystemUUID"]=$(jq -r '.SmUUID // empty' "$oc_json_path")
      json_values["ROM"]=$(jq -r '.ROM // empty' "$oc_json_path")
      for key in SystemProductName SystemSerialNumber MLB SystemUUID ROM; do
        if [ -n "${json_values[$key]}" ]; then
          if [ "$key" == "ROM" ]; then
            if command -v base64 >/dev/null 2>&1 && command -v xxd >/dev/null 2>&1; then
              modified_value=$(echo -n "${json_values[$key]}" | xxd -r -p | base64)
            else
              display_and_log "Warning: base64 or xxd not available. Skipping ROM update." "$logfile"
              continue
            fi
          else
            modified_value="${json_values[$key]}"
          fi
          update_plist_value "$TEMP_FILE" "$platform_generic_xpath" "$key" "$modified_value"
          display_and_log "Applied $key from existing JSON to temp file." "$logfile"
        fi
      done
    fi
  else
    read -rp "No Serial number generated. Do you want to generate a unique serial number? [Y/n]: " GENSMBIOS
    if [[ "${GENSMBIOS:-Y}" =~ ^[Yy]$ ]]; then
      generate_new=true
    fi
  fi

  # Generate new SMBIOS if requested
  if $generate_new; then
    SystemProductName="$(read_plist_value "$TEMP_FILE" "$platform_generic_xpath" "SystemProductName")"
    read -e -p "Enter System Product Name (press Enter to keep current): " -i "$SystemProductName" new_value
      if [ -n "$new_value" ] && [ "$new_value" != "$SystemProductName" ]; then
        SystemProductName="$new_value"
      fi
    python3 "$SCRIPT_DIR"/tools/GenSMBIOS/GenSMBIOS.py --install
    python3 "$SCRIPT_DIR"/tools/GenSMBIOS/GenSMBIOS.py --generate "$SystemProductName" -j "$oc_json_path" >>"$logfile" 2>&1 || log_and_exit "Failed to generate SMBIOS" "$logfile"
    # Apply new JSON
    declare -A json_values
    json_values["SystemProductName"]=$(jq -r '.Type // empty' "$oc_json_path")
    json_values["SystemSerialNumber"]=$(jq -r '.Serial // empty' "$oc_json_path")
    json_values["MLB"]=$(jq -r '."Board Serial" // empty' "$oc_json_path")
    json_values["SystemUUID"]=$(jq -r '.SmUUID // empty' "$oc_json_path")
    json_values["ROM"]=$(jq -r '.ROM // empty' "$oc_json_path")
    for key in SystemProductName SystemSerialNumber MLB SystemUUID ROM; do
      if [ -n "${json_values[$key]}" ]; then
        if [ "$key" == "ROM" ]; then
          if command -v base64 >/dev/null 2>&1 && command -v xxd >/dev/null 2>&1; then
            modified_value=$(echo -n "${json_values[$key]}" | xxd -r -p | base64)
          else
            display_and_log "Warning: base64 or xxd not available. Skipping ROM update." "$logfile"
            continue
          fi
        else
          modified_value="${json_values[$key]}"
        fi
        update_plist_value "$TEMP_FILE" "$platform_generic_xpath" "$key" "$modified_value"
        display_and_log "Applied new $key from generated JSON to temp file." "$logfile"
      fi
    done
  fi

  # Prompt for edits
  declare -A modified
  for key in "${keys[@]}"; do
    local section=${key_sections[$key]}
    local base_xpath
    case $section in
      nvram) base_xpath="$nvram_xpath" ;;
      misc_boot) base_xpath="$misc_boot_xpath" ;;
      platform_generic) base_xpath="$platform_generic_xpath" ;;
    esac
    value=$(read_plist_value "$TEMP_FILE" "$base_xpath" "$key")
    if [ -z "$value" ]; then
      display_and_log "Warning: Could not read value for $key" "$logfile"
      continue
    fi

    if [ "$key" == "csr-active-config" ]; then
      current_value="$value"
      echo "Current value for $key: $current_value"
      read -rp "Remove csr-active-config (unlock SIP)? [Y/N] [N]: " RM_CSR_LOCK
      if [[ "${RM_CSR_LOCK:-N}" =~ ^[Yy]$ ]]; then
        modified[$key]="remove"
        display_and_log "SIP unlocked. Use 'csrutil disable' in Recovery OS" "$logfile"
        continue
      fi
      read -e -p "Edit value (press Enter to keep current): " -i "$current_value" new_value
      if [ -n "$new_value" ] && [ "$new_value" != "$current_value" ]; then
        modified[$key]="$new_value"
      fi
    elif [ "$key" == "ROM" ]; then
      rom_convert=false
      if command -v base64 >/dev/null && command -v xxd >/dev/null; then
        rom_convert=true
        current_value=$(echo -n "$value" | base64 -d | xxd -p -c 999 | tr -d '\n' | tr 'a-f' 'A-F')
      else
        current_value="$value"
      fi
      echo "Current value for $key (${rom_convert:+HEX}base64 if not): $current_value"
      read -e -p "Edit value (press Enter to keep current): " -i "$current_value" new_value
      if [ -n "$new_value" ] && [ "$new_value" != "$current_value" ]; then
        if $rom_convert; then
          modified[$key]=$(echo -n "$new_value" | xxd -r -p | base64)
        else
          modified[$key]="$new_value"
        fi
      fi
    else
      current_value="$value"
      echo "Current value for $key: $current_value"
      read -e -p "Edit value (press Enter to keep current): " -i "$current_value" new_value
      if [ -n "$new_value" ] && [ "$new_value" != "$current_value" ]; then
        modified[$key]="$new_value"
      fi
    fi
  done

  # Apply user modifications
  if [ ${#modified[@]} -gt 0 ]; then
    display_and_log "Applying user changes to temporary file..." "$logfile"
    for key in "${!modified[@]}"; do
      local section=${key_sections[$key]}
      local base_xpath
      case $section in
        nvram) base_xpath="$nvram_xpath" ;;
        misc_boot) base_xpath="$misc_boot_xpath" ;;
        platform_generic) base_xpath="$platform_generic_xpath" ;;
      esac
      if [ "${modified[$key]}" == "remove" ]; then
        remove_plist_key "$TEMP_FILE" "$base_xpath" "$key"
      else
        update_plist_value "$TEMP_FILE" "$base_xpath" "$key" "${modified[$key]}"
      fi
    done
  fi

  # Automatic boot-args adjustment
  system_product_name=$(read_plist_value "$TEMP_FILE" "$platform_generic_xpath" "SystemProductName")
  boot_args=$(read_plist_value "$TEMP_FILE" "$nvram_xpath" "boot-args")
  flag=" -nehalem_error_disable"

  if [ "$system_product_name" = "MacPro5,1" ]; then
    if [[ ! "$boot_args" =~ $flag ]]; then
      new_boot_args="$boot_args$flag"
      update_plist_value "$TEMP_FILE" "$nvram_xpath" "boot-args" "$new_boot_args"
      display_and_log "Automatically added '$flag' to boot-args." "$logfile"
    fi
  else
    if [[ "$boot_args" =~ $flag ]]; then
      new_boot_args="${boot_args//$flag/}"
      update_plist_value "$TEMP_FILE" "$nvram_xpath" "boot-args" "$new_boot_args"
      display_and_log "Automatically removed '$flag' from boot-args." "$logfile"
    fi
  fi

  # Show diff if changes
  xmlstarlet fo "$config" > "$config.fmt"
  local diff_output=$(diff -u "$config.fmt" "$TEMP_FILE")
  rm "$config.fmt"
  if [ -n "$diff_output" ]; then
    display_and_log "Differences between original and modified file (unified format):" "$logfile"
    echo "$diff_output"

    read -rp "Do you want to apply these changes to the original file? (y/n): " confirm
    if [[ "$confirm" =~ ^[Yy]$ ]]; then
      mv "$TEMP_FILE" "$config"
      display_and_log "Changes applied to $config" "$logfile"

      # Extract and save to JSON
      declare -A extracted
      for key in SystemProductName SystemSerialNumber MLB SystemUUID ROM; do
        val=$(read_plist_value "$config" "$platform_generic_xpath" "$key")
        if [ -z "$val" ]; then
          display_and_log "Warning: Could not read $key from plist." "$logfile"
          continue
        fi
        if [ "$key" == "ROM" ]; then
          if command -v base64 >/dev/null 2>&1 && command -v xxd >/dev/null 2>&1; then
            val=$(echo -n "$val" | base64 -d | xxd -p -c 999 | tr -d '\n' | tr 'a-f' 'A-F')
          else
            display_and_log "Warning: base64 or xxd not available. Skipping ROM extraction." "$logfile"
            continue
          fi
        fi
        extracted[$key]="$val"
      done

      jq -n \
        --arg Type "${extracted[SystemProductName]}" \
        --arg Serial "${extracted[SystemSerialNumber]}" \
        --arg board_serial "${extracted[MLB]}" \
        --arg SmUUID "${extracted[SystemUUID]}" \
        --arg ROM "${extracted[ROM]}" \
        '{Type: $Type, Serial: $Serial, "Board Serial": $board_serial, SmUUID: $SmUUID, ROM: $ROM}' > "$oc_json_path"
      display_and_log "Updated/Created SMBIOS JSON at $oc_json_path" "$logfile"
    else
      display_and_log "Changes discarded." "$logfile"
      rm "$TEMP_FILE"
    fi
  else
    display_and_log "No changes were made." "$logfile"
    rm "$TEMP_FILE"
  fi

  # Cleanup
  umount /mnt/opencore >>"$logfile" 2>&1 || log_and_exit "Failed to unmount ISO" "$logfile"
  losetup -d "$loopdev" >>"$logfile" 2>&1 || log_and_exit "Failed to detach loop device" "$logfile"
  display_and_log "OpenCore config customized" "$logfile"
  read -n 1 -sp "Press any key to return to menu..."
}

# Function to update OpenCore ISO
update_opencore_iso() {
  local iso_path
  local logfile="${LOGDIR}/update-opencore-iso.log"
  local iso_url="https://github.com/luchina-gabriel/OSX-PROXMOX/raw/main/EFI/opencore-osx-proxmox-vm.iso"
  iso_path="${ISODIR}/${OPENCORE_ISO}"
  oc_json_path="${ISODIR}/.smbios.json"

  ensure_wget_dependency
  rm -f "$iso_path" >>"$logfile" 2>&1
  if ! wget -q -O "$iso_path" "$iso_url" >>"$logfile" 2>&1; then
    log_and_exit "Failed to download OpenCore ISO" "$logfile"
  fi
  ensure_base64_xxd_dependency
  update_opencore_smbios "$iso_path" "$oc_json_path"

  display_and_log "OpenCore ISO updated" "$logfile"
  sleep 5
}

# Function to clear recovery images
clear_recovery_images() {
  find "$ISODIR" -type f -name "recovery-*.iso" -delete
  find "$LOGDIR" -type f -name "crt-recovery-*.log" -delete
  display_and_log "All recovery images cleared"
  read -n 1 -sp "Press any key to return to menu..."
}

# Function to remove subscription notice
remove_subscription_notice() {
  echo "DPkg::Post-Invoke { \"if [ -s /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js ] && ! grep -q -F 'NoMoreNagging' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js; then echo 'Removing subscription nag from UI...'; sed -i '/data\.status/{s/\!//;s/active/NoMoreNagging/}' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js; fi\" };" >/etc/apt/apt.conf.d/no-nag-script
  apt --reinstall install proxmox-widget-toolkit &>/dev/null
  display_and_log "Subscription notice removed"
  read -n 1 -sp "Press any key to return to menu..."
}

# Function to append parameters to the end of the kernel command line, covering
# both bootloaders used by Proxmox VE: GRUB (/etc/default/grub) and systemd-boot
# (/etc/kernel/cmdline, used when the root filesystem is ZFS)
apply_kernel_cmdline_params() {
  local logname="$1"; shift
  local params=("$@")
  local logfile="${LOGDIR}/${logname}.log"
  local stamp=$(date '+%Y%m%d%H%M%S')
  local handled=0 changed=0 failed=0 boot_mode="Legacy BIOS"
  local p current append ok
  [[ -d /sys/firmware/efi ]] && boot_mode="UEFI"
  display_and_log "Applying '${params[*]}' to the kernel command line (firmware: ${boot_mode})" "$logfile"

  # GRUB - the parameters are appended to the end of GRUB_CMDLINE_LINUX_DEFAULT
  if [[ -f /etc/default/grub ]] && command -v update-grub >/dev/null 2>&1; then
    handled=1
    if ! grep -Eq '^[[:space:]]*GRUB_CMDLINE_LINUX_DEFAULT=' /etc/default/grub; then
      display_and_log "GRUB: GRUB_CMDLINE_LINUX_DEFAULT not found in /etc/default/grub, skipping" "$logfile"
    else
      current=$(grep -E '^[[:space:]]*GRUB_CMDLINE_LINUX_DEFAULT=' /etc/default/grub | head -n 1)
      append=""
      for p in "${params[@]}"; do
        if grep -Eq "(^|[[:space:]])${p}([[:space:]\"]|\$)" <<< "$current"; then
          display_and_log "GRUB: ${p} is already set in /etc/default/grub, skipping it" "$logfile"
        else
          append="${append} ${p}"
        fi
      done
      if [[ -z "$append" ]]; then
        display_and_log "GRUB: nothing to add to GRUB_CMDLINE_LINUX_DEFAULT" "$logfile"
      else
        cp /etc/default/grub "/etc/default/grub.bak-${stamp}" || log_and_exit "GRUB: failed to back up /etc/default/grub" "$logfile"
        sed -i -E \
          -e "s/^([[:space:]]*GRUB_CMDLINE_LINUX_DEFAULT=\")(.*)(\"[[:space:]]*)\$/\1\2${append}\3/" \
          -e "s/^([[:space:]]*GRUB_CMDLINE_LINUX_DEFAULT=\")[[:space:]]+/\1/" \
          /etc/default/grub
        ok=1
        for p in "${params[@]}"; do
          grep -Eq "^[[:space:]]*GRUB_CMDLINE_LINUX_DEFAULT=.*${p}" /etc/default/grub || ok=0
        done
        if ((ok == 1)); then
          display_and_log "GRUB: $(grep -E '^[[:space:]]*GRUB_CMDLINE_LINUX_DEFAULT=' /etc/default/grub | head -n 1)" "$logfile"
          if update-grub >>"$logfile" 2>&1; then
            changed=1
            display_and_log "GRUB: /etc/default/grub updated and update-grub completed" "$logfile"
          else
            failed=1
            cp "/etc/default/grub.bak-${stamp}" /etc/default/grub
            display_and_log "GRUB: update-grub failed, /etc/default/grub restored from backup" "$logfile"
          fi
        else
          failed=1
          cp "/etc/default/grub.bak-${stamp}" /etc/default/grub
          display_and_log "GRUB: unable to edit GRUB_CMDLINE_LINUX_DEFAULT (unexpected quoting), file restored from backup" "$logfile"
        fi
      fi
    fi
  fi

  # systemd-boot - hosts booting with proxmox-boot-tool read the cmdline from /etc/kernel/cmdline
  if [[ -f /etc/kernel/cmdline ]] && command -v proxmox-boot-tool >/dev/null 2>&1; then
    handled=1
    current=$(head -n 1 /etc/kernel/cmdline)
    append=""
    for p in "${params[@]}"; do
      if grep -Eq "(^|[[:space:]])${p}([[:space:]]|\$)" <<< "$current"; then
        display_and_log "systemd-boot: ${p} is already set in /etc/kernel/cmdline, skipping it" "$logfile"
      else
        append="${append} ${p}"
      fi
    done
    if [[ -z "$append" ]]; then
      display_and_log "systemd-boot: nothing to add to /etc/kernel/cmdline" "$logfile"
    else
      cp /etc/kernel/cmdline "/etc/kernel/cmdline.bak-${stamp}" || log_and_exit "systemd-boot: failed to back up /etc/kernel/cmdline" "$logfile"
      sed -i -E "1s/[[:space:]]*\$/${append}/" /etc/kernel/cmdline
      ok=1
      for p in "${params[@]}"; do
        grep -Eq "${p}" /etc/kernel/cmdline || ok=0
      done
      if ((ok == 1)); then
        display_and_log "systemd-boot: $(head -n 1 /etc/kernel/cmdline)" "$logfile"
        if proxmox-boot-tool refresh >>"$logfile" 2>&1; then
          changed=1
          display_and_log "systemd-boot: /etc/kernel/cmdline updated and proxmox-boot-tool refresh completed" "$logfile"
        else
          failed=1
          cp "/etc/kernel/cmdline.bak-${stamp}" /etc/kernel/cmdline
          display_and_log "systemd-boot: proxmox-boot-tool refresh failed, /etc/kernel/cmdline restored from backup" "$logfile"
        fi
      else
        failed=1
        cp "/etc/kernel/cmdline.bak-${stamp}" /etc/kernel/cmdline
        display_and_log "systemd-boot: unable to edit /etc/kernel/cmdline, file restored from backup" "$logfile"
      fi
    fi
  fi

  if ((handled == 0)); then
    display_and_log "No supported bootloader configuration found (/etc/default/grub or /etc/kernel/cmdline). Nothing was changed." "$logfile"
  fi
  if ((changed == 1)); then
    display_and_log "Parameters applied. A reboot is required for them to take effect." "$logfile"
  fi
  if ((failed == 1)); then
    display_and_log "One or more steps failed, check ${logfile}" "$logfile"
  fi
  return 0
}

# Function to apply the pcie_port_pm=off fix to the kernel command line
apply_pcie_port_pm_off() {
  apply_kernel_cmdline_params "apply-pcie-port-pm-off" "pcie_port_pm=off"
  read -n 1 -sp "Press any key to return to menu..."
}

# Function to apply the PCIe ACS override fix to the kernel command line
apply_pcie_acs_override() {
  apply_kernel_cmdline_params "apply-pcie-acs-override" "pcie_acs_override=downstream,multifunction" "pci=nommconf"
  read -n 1 -sp "Press any key to return to menu..."
}

# Function to configure network bridge
configure_network_bridge() {
  local logfile="${LOGDIR}/configure-network-bridge.log"

  # Logging functions
  die() {
    display_and_log "ERROR: $*" "$logfile"
    exit 1
  }

  warn() {
    display_and_log "WARNING: $*" "$logfile"
  }

  info() {
    display_and_log "INFO: $*" "$logfile"
  }

  # Restore backup function
  restore_backup() {
    local backup_file="$1"
    info "Restoring network configuration from backup..."
    if [[ -f "$backup_file" ]]; then
      if ! cp "$backup_file" "$NETWORK_INTERFACES_FILE"; then
        die "CRITICAL: Failed to restore network configuration from backup! System may be in unstable state."
      fi
      info "Network configuration successfully restored from backup"
      return 0
    else
      die "CRITICAL: Backup file not found! Network configuration may be corrupted."
    fi
  }

  # Check/create DHCP user group
  ensure_dhcp_group() {
    if ! getent group "$DHCP_USER" >/dev/null; then
      info "Creating DHCP server group '$DHCP_USER'..."
      groupadd "$DHCP_USER" || die "Failed to create group '$DHCP_USER'"
    fi
  }

  # Dependency check
  ensure_dependencies() {
    local deps=("ipcalc")
    local missing=()

    # Check for isc-dhcp-server
    if ! dpkg -l isc-dhcp-server &>/dev/null; then
      deps+=("isc-dhcp-server")
    fi

    for dep in "${deps[@]}"; do
      if ! command -v "$dep" &>/dev/null && ! dpkg -l "$dep" &>/dev/null; then
        missing+=("$dep")
      fi
    done

    if (( ${#missing[@]} > 0 )); then
      info "Installing missing dependencies: ${missing[*]}"
      apt-get update && apt-get install -y "${missing[@]}" >>"$logfile" 2>&1 || die "Failed to install dependencies"
    fi

    # Ensure DHCP config directory exists
    mkdir -p "$DHCP_CONF_DIR"
    chown root:root "$DHCP_CONF_DIR"
    chmod 755 "$DHCP_CONF_DIR"
  }

  # Network calculations
  calculate_network() {
    local subnet=$1
    declare -gA network_info

    # Validate subnet format
    if [[ ! "$subnet" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$ ]]; then
      warn "Invalid subnet format: $subnet"
      return 1
    fi

    # Get ipcalc output
    if ! ipcalc_output=$(ipcalc -nb "$subnet"); then
      warn "ipcalc failed to process subnet: $subnet"
      return 1
    fi

    # Parse network information
    network_info["network"]=$(echo "$ipcalc_output" | awk '/^Network:/ {print $2}' | cut -d'/' -f1)
    network_info["netmask"]=$(echo "$ipcalc_output" | awk '/^Netmask:/ {print $2}')
    network_info["broadcast"]=$(echo "$ipcalc_output" | awk '/^Broadcast:/ {print $2}')
    network_info["hostmin"]=$(echo "$ipcalc_output" | awk '/^HostMin:/ {print $2}')
    network_info["hostmax"]=$(echo "$ipcalc_output" | awk '/^HostMax:/ {print $2}')

    # Calculate DHCP range (skip first 50 IPs)
    IFS='.' read -r i1 i2 i3 i4 <<< "${network_info[hostmin]}"
    network_info["range_start"]="$i1.$i2.$i3.$((i4 + 50))"
    network_info["range_end"]="${network_info[hostmax]}"
    network_info["gateway"]="${network_info[network]%.*}.1"

    # Validate all calculations
    local required=("network" "netmask" "broadcast" "range_start" "range_end" "gateway")
    for key in "${required[@]}"; do
      if [[ -z "${network_info[$key]}" ]]; then
        warn "Failed to calculate network $key for subnet $subnet"
        return 1
      fi
    done
  }

  # Bridge validation
  validate_bridge() {
    local bridge_num=$1
    [[ "$bridge_num" =~ ^[0-9]+$ ]] || { warn "Bridge number must be a positive integer"; return 1; }

    if [[ -d "/sys/class/net/vmbr$bridge_num" || \
          -n $(grep -h "^iface vmbr$bridge_num" "$NETWORK_INTERFACES_FILE" 2>/dev/null) ]]; then
      return 1  # Bridge exists
    fi
    return 0  # Bridge doesn't exist
  }

  # Find next available bridge
  find_next_bridge() {
    local bridge_num=0
    while ! validate_bridge "$bridge_num"; do
      ((bridge_num++))
    done
    echo "$bridge_num"
  }

  # Subnet validation
  validate_subnet() {
    local subnet=$1
    [[ "$subnet" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$ ]] || { warn "Invalid CIDR format"; return 1; }

    IFS='./' read -r ip1 ip2 ip3 ip4 mask <<< "$subnet"
    (( ip1 <= 255 && ip2 <= 255 && ip3 <= 255 && ip4 <= 255 && mask <= 32 )) || { warn "Invalid IP/Netmask"; return 1; }

    # Check for conflicts
    while read -r existing; do
      if [[ -n "$existing" ]]; then
        if ipcalc -n "$subnet" | grep -q "$(ipcalc -n "$existing" | awk -F= '/NETWORK/ {print $2}')"; then
          warn "Subnet conflict detected with $existing"
          return 1
        fi
      fi
    done < <(get_existing_subnets)

    return 0
  }

  get_existing_subnets() {
    grep -h '^iface' "$NETWORK_INTERFACES_FILE" 2>/dev/null | \
    grep -v '^iface lo' | while read -r line; do
      if [[ $line =~ address[[:space:]]+([0-9.]+) ]]; then
        address=${BASH_REMATCH[1]}
        netmask_line=$(grep -A5 "^$line" "$NETWORK_INTERFACES_FILE" 2>/dev/null | grep -m1 'netmask')
        [[ $netmask_line =~ netmask[[:space:]]+([0-9.]+) ]] || continue
        netmask=${BASH_REMATCH[1]}
        cidr=$(ipcalc -p "$address" "$netmask" | awk -F= '/PREFIX/ {print $2}')
        echo "${address}/${cidr}"
      fi
    done
  }

  # Regenerate main dhcpd.conf
  regenerate_dhcpd_conf() {
    # Start with base configuration
    printf "# DHCP Server Configuration\n# Global DHCP options\noption domain-name \"local\";\noption domain-name-servers 8.8.8.8, 8.8.4.4;\n\ndefault-lease-time 604800;\nmax-lease-time 1209600;\n\nauthoritative;\nlog-facility local7;\n" > /etc/dhcp/dhcpd.conf

    # Add includes for all bridge configs
    printf "\n# Bridge configurations\n" >> /etc/dhcp/dhcpd.conf
    for conf in "$DHCP_CONF_DIR"/*.conf; do
      [[ -f "$conf" ]] && printf "include \"%s\";\n" "$conf" >> /etc/dhcp/dhcpd.conf
    done
  }

  # Update DHCP interfaces list
  update_dhcp_interfaces() {
    # Collect all bridge interfaces with DHCP configs
    local interfaces=()
    for conf in "$DHCP_CONF_DIR"/*.conf; do
      [[ -f "$conf" ]] && interfaces+=("$(basename "${conf%.conf}")")
    done

    # Update interfaces list
    printf "INTERFACESv4=\"%s\"\n" "${interfaces[*]}" > /etc/default/isc-dhcp-server
  }

  # DHCP configuration
  configure_dhcp() {
    local bridge_name=$1
    local subnet=$2

    if ! calculate_network "$subnet"; then
      warn "Failed to calculate network parameters for $subnet"
      return 1
    fi

    # Create bridge-specific config
    printf "subnet %s netmask %s {\n" "${network_info[network]}" "${network_info[netmask]}" > "$DHCP_CONF_DIR/$bridge_name.conf"
    printf "    range %s %s;\n" "${network_info[range_start]}" "${network_info[range_end]}" >> "$DHCP_CONF_DIR/$bridge_name.conf"
    printf "    option routers %s;\n" "${network_info[gateway]}" >> "$DHCP_CONF_DIR/$bridge_name.conf"
    printf "    option broadcast-address %s;\n" "${network_info[broadcast]}" >> "$DHCP_CONF_DIR/$bridge_name.conf"
    printf "    option subnet-mask %s;\n" "${network_info[netmask]}" >> "$DHCP_CONF_DIR/$bridge_name.conf"
    printf "    default-lease-time 604800;\n" >> "$DHCP_CONF_DIR/$bridge_name.conf"
    printf "    max-lease-time 1209600;\n" >> "$DHCP_CONF_DIR/$bridge_name.conf"
    printf "}\n" >> "$DHCP_CONF_DIR/$bridge_name.conf"

    # Set permissions
    chown root:root "$DHCP_CONF_DIR/$bridge_name.conf"
    chmod 644 "$DHCP_CONF_DIR/$bridge_name.conf"

    # Regenerate main config
    regenerate_dhcpd_conf

    # Update interfaces list
    update_dhcp_interfaces

    # Validate config
    if ! dhcpd -t -cf /etc/dhcp/dhcpd.conf >>"$logfile" 2>&1; then
      warn "DHCP configuration validation failed"
      return 1
    fi

    # Restart service
    systemctl restart isc-dhcp-server >>"$logfile" 2>&1 || warn "Failed to restart isc-dhcp-server"
    systemctl enable isc-dhcp-server >>"$logfile" 2>&1
  }

  # Network configuration with rollback support
  configure_network() {
    local bridge_num=$1
    local subnet=$2

    info "Calculating network parameters for $subnet..."
    if ! calculate_network "$subnet"; then
      die "Failed to calculate network parameters for $subnet"
    fi

    local gw_iface=$(ip route | awk '/^default/ {print $5}')
    [[ -z "$gw_iface" ]] && die "No default gateway found"

    # Create backup of interfaces file
    local backup_file="${NETWORK_INTERFACES_FILE}.bak-$(date +%Y%m%d-%H%M%S)"
    info "Creating backup of network interfaces: $backup_file"
    cp "$NETWORK_INTERFACES_FILE" "$backup_file" || die "Failed to create backup of $NETWORK_INTERFACES_FILE"

    # Add bridge configuration
    printf "\n" >> "$NETWORK_INTERFACES_FILE"
    printf "auto vmbr%s\n" "$bridge_num" >> "$NETWORK_INTERFACES_FILE"
    printf "iface vmbr%s inet static\n" "$bridge_num" >> "$NETWORK_INTERFACES_FILE"
    printf "\t# Subnet %s using %s for gateway\n" "$subnet" "$gw_iface" >> "$NETWORK_INTERFACES_FILE"
    printf "\taddress %s\n" "${network_info[gateway]}" >> "$NETWORK_INTERFACES_FILE"
    printf "\tnetmask %s\n" "${network_info[netmask]}" >> "$NETWORK_INTERFACES_FILE"
    printf "\tbridge_ports none\n" >> "$NETWORK_INTERFACES_FILE"
    printf "\tbridge_stp off\n" >> "$NETWORK_INTERFACES_FILE"
    printf "\tbridge_fd 0\n" >> "$NETWORK_INTERFACES_FILE"
    printf "\tpost-up echo 1 > /proc/sys/net/ipv4/ip_forward\n" >> "$NETWORK_INTERFACES_FILE"
    printf "\tpost-up iptables -t nat -A POSTROUTING -s '%s' -o %s -j MASQUERADE\n" "$subnet" "$gw_iface" >> "$NETWORK_INTERFACES_FILE"
    printf "\tpost-down iptables -t nat -D POSTROUTING -s '%s' -o %s -j MASQUERADE\n" "$subnet" "$gw_iface" >> "$NETWORK_INTERFACES_FILE"

    # Verify the config was added correctly
    if ! grep -q "iface vmbr$bridge_num inet static" "$NETWORK_INTERFACES_FILE"; then
      warn "Failed to add bridge configuration"
      restore_backup "$backup_file"
      die "Network configuration failed"
    fi

    # Bring up bridge with rollback on failure
    info "Bringing up bridge vmbr$bridge_num..."
    if ! ifup "vmbr$bridge_num" >>"$logfile" 2>&1; then
      warn "Failed to activate bridge"
      restore_backup "$backup_file"
      die "Bridge activation failed - configuration rolled back"
    fi

    # Clean up backup if successful
    rm -f "$backup_file"
  }

  # Prompt with validation
  prompt_with_validation() {
    local prompt=$1
    local default=$2
    local validation_func=$3
    local value

    while true; do
      read -rp "$prompt [$default]: " value
      value=${value:-$default}
      if $validation_func "$value"; then
        echo "$value"
        return
      fi
      display_and_log "Press any key to return to the main menu..."
      read -n 1 -s
      return 1
    done
  }

  # Main execution
  info "Configuring network bridge for macOS in Cloud..."

  # Check root
  (( EUID == 0 )) || die "This function must be run as root"

  ensure_dependencies
  ensure_dhcp_group

  # Get bridge number
  local next_bridge=$(find_next_bridge)
  info "Next available bridge: vmbr$next_bridge"
  local bridge_num
  bridge_num=$(prompt_with_validation "Enter bridge number" "$next_bridge" validate_bridge) || return

  # Get subnet
  local default_subnet="10.27.$bridge_num.0/24"
  local subnet
  subnet=$(prompt_with_validation "Enter subnet for VM bridge in CIDR notation" "$default_subnet" validate_subnet) || return

  # Configure network
  info "Configuring network..."
  configure_network "$bridge_num" "$subnet"

  # Configure DHCP
  read -rp "Configure DHCP server for vmbr$bridge_num? [Y/n]: " answer
  if [[ "${answer,,}" =~ ^(y|)$ ]]; then
    info "Configuring DHCP server..."
    configure_dhcp "vmbr$bridge_num" "$subnet" || {
      warn "DHCP configuration failed. Network bridge configured, but DHCP not enabled."
    }
  fi

  info "Configuration completed:"
  info "Bridge: vmbr$bridge_num"
  info "Subnet: $subnet"
  info "Gateway: ${network_info[gateway]}"
  [[ "${answer,,}" =~ ^(y|)$ ]] && info "DHCP Range: ${network_info[range_start]} - ${network_info[range_end]}"
  info "Network config: $NETWORK_INTERFACES_FILE"
  [[ "${answer,,}" =~ ^(y|)$ ]] && info "DHCP config: $DHCP_CONF_DIR/vmbr$bridge_num.conf"
  display_and_log "Press any key to return to the main menu..."
  read -n 1 -s
}

# Function to configure macOS VM
configure_macos_vm() {
  local macopt=$1
  local nextid=$2
  local version_name version board_id model_id iso_size disk_type
  IFS='|' read -r version_name version board_id model_id iso_size disk_type <<< "$macopt"
  local default_vm_name="${DEFAULT_VM_PREFIX}$(echo "$version_name" | tr -s ' ' | sed 's/^[ ]*//;s/[ ]*$//;s/[ ]/-/g' | tr '[:lower:]' '[:upper:]' | sed 's/-*$//')"
  validate_vm_name "$default_vm_name" || log_and_exit "Invalid default VM name: $default_vm_name" "${LOGDIR}/main-menu.log"
  clear
  display_and_log "macOS $version_name"

  # VM ID
  while true; do
    read -rp "VM ID [${nextid}]: " VM_ID
    VM_ID=${VM_ID:-$nextid}
    if [[ "$VM_ID" =~ ^[0-9]+$ && ! -e "/etc/pve/qemu-server/$VM_ID.conf" ]]; then
      break
    else
      display_and_log "Invalid or existing VM ID. Please try again."
    fi
  done

  # VM Name
  while true; do
    read -rp "VM Name [${default_vm_name}]: " VM_NAME
    VM_NAME=${VM_NAME:-$default_vm_name}
    if validate_vm_name "$VM_NAME"; then
      break
    else
      display_and_log "Invalid VM name. Please use alphanumeric characters, -, _, .; no spaces."
    fi
  done

  # Disk Size
  local default_disk_size=$BASE_DISK_SIZE
  while true; do
    read -rp "Disk size (GB) [default: $default_disk_size]: " SIZEDISK
    SIZEDISK=${SIZEDISK:-$default_disk_size}
    if [[ "$SIZEDISK" =~ ^[0-9]+$ ]]; then
      break
    else
      display_and_log "Disk size must be an integer. Please try again."
    fi
  done

  # Storage Selection
  local storage_output=$(get_available_storages) || { display_and_log "Failed to retrieve storages"; read -n 1 -s; return 1; }
  local storages=() default_storage=""
  while IFS= read -r line; do
    [[ -z "$line" ]] && continue
    [[ -z "$default_storage" && ! "$line" =~ \| ]] && default_storage="$line" || storages+=("$line")
  done <<< "$storage_output"
  if ((${#storages[@]} == 0)); then
    display_and_log "No storages found"; read -n 1 -s; return 1
  fi
  if ((${#storages[@]} == 1)); then
    STORAGECRTVM="${storages[0]%%|*}"
    display_and_log "Using storage: $STORAGECRTVM"
  else
    while true; do
      display_and_log "Available storages:"
      for s in "${storages[@]}"; do
        storage_name="${s%%|*}"
        avail_space="${s##*|}"
        display_and_log "  - $storage_name ($avail_space GB)"
      done
      read -rp "Storage [${default_storage}]: " STORAGECRTVM
      STORAGECRTVM=${STORAGECRTVM:-$default_storage}
      local valid=false
      for s in "${storages[@]}"; do
        if [[ "$STORAGECRTVM" == "${s%%|*}" ]]; then
          valid=true
          break
        fi
      done
      if $valid; then
        display_and_log "Selected storage: $STORAGECRTVM"
        break
      else
        display_and_log "Invalid storage. Please try again."
      fi
    done
  fi

  # Bridge Selection
  local bridge_output=$(get_available_bridges) || { display_and_log "Failed to retrieve bridges"; read -n 1 -s; return 1; }
  local bridges=() default_bridge=""
  while IFS= read -r line; do
    line=$(echo "$line" | tr -d '\r')
    [[ -z "$line" ]] && continue
    if [[ ! "$line" =~ \| ]]; then
      default_bridge="$line"
    else
      bridges+=("$line")
    fi
  done <<< "$bridge_output"
  if ((${#bridges[@]} == 0)); then
    display_and_log "No bridges found"; read -n 1 -s; return 1
  fi

  declare -A bridge_info
  for b in "${bridges[@]}"; do
    IFS='|' read -r bridge_name ip_addr <<< "$b"
    bridge_info["$bridge_name"]="IP address: ${ip_addr:-unknown}"
  done

  mapfile -t sorted_names < <(printf '%s\n' "${!bridge_info[@]}" | sort -V)

  local default_bridge_num=${default_bridge#vmbr}
  if ((${#bridges[@]} == 1)); then
    name="${sorted_names[0]}"
    ip_info="${bridge_info[$name]}"
    BRIDGECRTVM="$name"
    display_and_log "Using bridge: $BRIDGECRTVM ($ip_info)"
  else
    while true; do
      display_and_log "Available bridges:"
      for name in "${sorted_names[@]}"; do
        bridge_num=${name#vmbr}
        ip_info="${bridge_info[$name]}"
        display_and_log "  - $bridge_num ($name, $ip_info)"
      done
      read -rp "Bridge number [${default_bridge_num}]: " BRIDGE_NUM
      BRIDGE_NUM=${BRIDGE_NUM:-$default_bridge_num}
      if [[ "$BRIDGE_NUM" =~ ^[0-9]+$ ]]; then
        BRIDGECRTVM="vmbr$BRIDGE_NUM"
        if [[ -v bridge_info[$BRIDGECRTVM] ]]; then
          display_and_log "Selected bridge: $BRIDGECRTVM"
          break
        else
          display_and_log "Invalid bridge number. Please try again."
        fi
      else
        display_and_log "Bridge number must be an integer. Please try again."
      fi
    done
  fi

  # CPU Cores
  while true; do
    read -rp "CPU cores (power of 2) [4]: " PROC_COUNT
    PROC_COUNT=${PROC_COUNT:-4}
    if [[ "$PROC_COUNT" =~ ^[0-9]+$ ]]; then
      if ! is_power_of_2 "$PROC_COUNT"; then
        PROC_COUNT=$(next_power_of_2 "$PROC_COUNT")
        display_and_log "Adjusted to next power of 2: $PROC_COUNT"
      fi
      break
    else
      display_and_log "CPU cores must be an integer. Please try again."
    fi
  done
  ((PROC_COUNT > MAX_CORES)) && PROC_COUNT=$MAX_CORES

  # RAM
  while true; do
    default_ram=$((BASE_RAM_SIZE + PROC_COUNT * RAM_PER_CORE))
    read -rp "RAM (MiB) [$default_ram]: " RAM_SIZE
    RAM_SIZE=${RAM_SIZE:-$default_ram}
    if [[ "$RAM_SIZE" =~ ^[0-9]+$ ]]; then
      break
    else
      display_and_log "RAM must be an integer. Please try again."
    fi
  done

  # Recovery Image
  read -rp "Download recovery image? [Y/n]: " CRTRECODISK
  [[ "${CRTRECODISK:-Y}" =~ ^[Yy]$ ]] && download_recovery_image "$version_name" "$board_id" "$model_id" "$iso_size"
  create_vm "$version_name" "$VM_ID" "$VM_NAME" "$SIZEDISK" "$STORAGECRTVM" "$PROC_COUNT" "$RAM_SIZE" "$iso_size" "$disk_type" "$BRIDGECRTVM"
  read -n 1 -sp "Press any key to return to menu..."
}

# Function for main menu loop
main_menu() {
  while true; do
    clear
    NEXTID=$(pvesh get /cluster/nextid)
    echo "#######################################################"
    echo "################ O S X - P R O X M O X ################"
    echo "############### https://osx-proxmox.com ###############"
    echo "############### version: ${HACKPXVERSION} ###################"
    echo "#######################################################"
    echo
    echo " Next VM ID: ${NEXTID}"
    echo " OpenCore version: ${OCVERSION}"
    echo
    echo "Enter macOS version:"
    # Sort MACOS_CONFIG by version number
    for i in $(for key in "${!MACOS_CONFIG[@]}"; do
      IFS='|' read -r _ version _ _ _ _ <<< "${MACOS_CONFIG[$key]}"
      echo "$version|$key"
    done | sort -t'|' -k1,1V | cut -d'|' -f2); do
      IFS='|' read -r name version _ _ _ _ <<< "${MACOS_CONFIG[$i]}"
      [[ "$name" == "Tahoe" ]] && display_name="Tahoe" || display_name="$name"
      echo " $i - $display_name - $version"
    done
    echo
    echo "Additional options:"
    echo " 200 - Add Proxmox VE no-subscription repo"
    echo " 201 - Update OpenCore ISO"
    echo " 202 - Clear all macOS recovery images"
    echo " 203 - Remove Proxmox subscription notice"
    echo " 204 - Add new bridge (macOS in cloud)"
    echo " 205 - Customize OpenCore config.plist"
    echo " 206 - Apply pcie_port_pm=off (kernel command line)"
    echo " 207 - Apply pcie_acs_override + pci=nommconf (kernel command line)"
    echo
    echo " 0 - Quit (or ENTER)"
    echo
    read -rp "Option: " OPT
    [[ -z "$OPT" || "$OPT" -eq 0 ]] && exit

    if [[ ${MACOS_CONFIG[$OPT]} ]]; then
      configure_macos_vm "${MACOS_CONFIG[$OPT]}" "$NEXTID"
    else
      case $OPT in
        200) add_no_subscription_repo ;;
        201) update_opencore_iso ;;
        202) clear_recovery_images ;;
        203) remove_subscription_notice ;;
        204) configure_network_bridge ;;
        205) customize_opencore_config ;;
        206) apply_pcie_port_pm_off ;;
        207) apply_pcie_acs_override ;;
        *) echo "Invalid option"; read -n 1 -s ;;
      esac
    fi
  done
}

# Main script
clear
init_dirs
check_proxmox_version
set_isodir
# Check if OpenCore ISO exists, and install if not in the ISODIR.
if [ ! -f "${ISODIR}/${OPENCORE_ISO}" ]; then
  update_opencore_iso "0"
fi
OSX_PLATFORM=$(detect_cpu_platform)
OSX_INTEL_UARCH=$(detect_intel_uarch)
[[ ! -e /etc/pve/qemu-server/.osx-proxmox ]] && setup_prerequisites
main_menu
