1461 lines
58 KiB
Bash
Executable File
1461 lines
58 KiB
Bash
Executable File
#!/bin/bash
|
||
#
|
||
# Script: setup
|
||
#
|
||
# Author: Gabriel Luchina
|
||
# https://luchina.com.br
|
||
#
|
||
# COPYRIGHT - 2021, 2022
|
||
#
|
||
# 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="2025.07.23"
|
||
OCVERSION="1.0.4"
|
||
DEFAULT_VM_PREFIX="HACK-"
|
||
BASE_RAM_SIZE=4096
|
||
RAM_PER_CORE=512
|
||
BASE_DISK_SIZE=64
|
||
DISK_INCREMENT=8
|
||
MAX_CORES=16
|
||
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"
|
||
)
|
||
|
||
# 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 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 setup prerequisites
|
||
setup_prerequisites() {
|
||
local logfile="${LOGDIR}/prerequisites-setup.log"
|
||
cp "${SCRIPT_DIR}/EFI/"*.iso "$ISODIR" || log_and_exit "Failed to copy EFI files" "$logfile"
|
||
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. 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" == "Sequoia" ]] && 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)$ ]]; 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)$ ]]; then
|
||
cpu_args="-cpu Cascadelake-Server,vendor=GenuineIntel,+invtsc,-pcid,-hle,-rtm,-avx512f,-avx512dq,-avx512cd,-avx512bw,-avx512vl,-avx512vnni,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
|
||
cpu_args="-cpu host,kvm=on,vendor=GenuineIntel,+kvm_pv_unhalt,+kvm_pv_eoi,+hypervisor,+invtsc"
|
||
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"
|
||
|
||
qm create "$vm_id" \
|
||
--agent 1 --args "$device_args $cpu_args" --autostart 0 \
|
||
--balloon 0 --bios ovmf --boot "order=ide0;$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" \
|
||
--ide0 "${storage_iso}:iso/${iso_file},media=cdrom,cache=unsafe,size=96M" \
|
||
--ide2 "${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 "\nNOTE: High Sierra has a 'The Recovery Server Could Not Be Contacted' Error!\n - Goto https://mrmacintosh.com/how-to-fix-the-recovery-server-could-not-be-contacted-error-high-sierra-recovery-is-still-online-but-broken/ and do the Fix #3\n\n" "$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"
|
||
|
||
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 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 opt=$3
|
||
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
|
||
default_disk_size=$((BASE_DISK_SIZE + (opt > 6 ? 2 : opt == 4 ? 1 : 0) * DISK_INCREMENT))
|
||
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" == "Sequoia" ]] && display_name="macOS Sequoia" || 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
|
||
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" "$OPT"
|
||
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 ;;
|
||
*) 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
|
||
sleep 4
|
||
OSX_PLATFORM=$(detect_cpu_platform)
|
||
[[ ! -e /etc/pve/qemu-server/.osx-proxmox ]] && setup_prerequisites
|
||
main_menu |