Now everyone won't be sharing the same serial! On fresh install, It will prompt if you want to generate a new serial. If you have an install already, and you select "205 - Customize OpenCore config.plist" you will have an option to generate a new serial. I created a CLI for GenSMBIOS, which generated all the serial numbers and stuff so that iMessage should work. For now it is pulling from my branch. That is until the pull request is accepted by corpnewt. The Serial generation prompts for System Product Name, and has a default. The user can change it if they want. IE for MacOS 26, they may want to use MacPro7,1. I have not tested that though.
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 |