mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-04-05 20:03:48 +00:00
update pci_passthrough_helpers.sh
This commit is contained in:
52
scripts/global/pci_passthrough_helpers.sh
Normal file
52
scripts/global/pci_passthrough_helpers.sh
Normal file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [[ -n "${__PROXMENUX_PCI_PASSTHROUGH_HELPERS__}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
__PROXMENUX_PCI_PASSTHROUGH_HELPERS__=1
|
||||
|
||||
function _pci_is_iommu_active() {
|
||||
grep -qE 'intel_iommu=on|amd_iommu=on' /proc/cmdline 2>/dev/null || return 1
|
||||
[[ -d /sys/kernel/iommu_groups ]] || return 1
|
||||
find /sys/kernel/iommu_groups -mindepth 1 -maxdepth 1 -type d -print -quit 2>/dev/null | grep -q .
|
||||
}
|
||||
|
||||
function _pci_next_hostpci_index() {
|
||||
local vmid="$1"
|
||||
local idx=0
|
||||
local hostpci_existing
|
||||
|
||||
hostpci_existing=$(qm config "$vmid" 2>/dev/null) || return 1
|
||||
while grep -q "^hostpci${idx}:" <<< "$hostpci_existing"; do
|
||||
idx=$((idx + 1))
|
||||
done
|
||||
echo "$idx"
|
||||
}
|
||||
|
||||
function _pci_slot_assigned_to_vm() {
|
||||
local pci_full="$1"
|
||||
local vmid="$2"
|
||||
local slot_base
|
||||
slot_base="${pci_full#0000:}"
|
||||
slot_base="${slot_base%.*}"
|
||||
|
||||
qm config "$vmid" 2>/dev/null \
|
||||
| grep -qE "^hostpci[0-9]+:.*(0000:)?${slot_base}(\\.[0-7])?([,[:space:]]|$)"
|
||||
}
|
||||
|
||||
function _pci_function_assigned_to_vm() {
|
||||
local pci_full="$1"
|
||||
local vmid="$2"
|
||||
local bdf slot func pattern
|
||||
bdf="${pci_full#0000:}"
|
||||
slot="${bdf%.*}"
|
||||
func="${bdf##*.}"
|
||||
|
||||
if [[ "$func" == "0" ]]; then
|
||||
pattern="^hostpci[0-9]+:.*(0000:)?(${bdf}|${slot})([,:[:space:]]|$)"
|
||||
else
|
||||
pattern="^hostpci[0-9]+:.*(0000:)?${bdf}([,[:space:]]|$)"
|
||||
fi
|
||||
|
||||
qm config "$vmid" 2>/dev/null | grep -qE "$pattern"
|
||||
}
|
||||
138
scripts/global/vm_storage_helpers.sh
Normal file
138
scripts/global/vm_storage_helpers.sh
Normal file
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [[ -n "${__PROXMENUX_VM_STORAGE_HELPERS__}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
__PROXMENUX_VM_STORAGE_HELPERS__=1
|
||||
|
||||
function _array_contains() {
|
||||
local needle="$1"
|
||||
shift
|
||||
local item
|
||||
for item in "$@"; do
|
||||
[[ "$item" == "$needle" ]] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
function _refresh_host_storage_cache() {
|
||||
MOUNTED_DISKS=$(lsblk -ln -o NAME,MOUNTPOINT | awk '$2!="" {print "/dev/" $1}')
|
||||
SWAP_DISKS=$(swapon --noheadings --raw --show=NAME 2>/dev/null)
|
||||
LVM_DEVICES=$(pvs --noheadings -o pv_name 2> >(grep -v 'File descriptor .* leaked') | xargs -r -n1 readlink -f | sort -u)
|
||||
CONFIG_DATA=$(grep -vE '^\s*#' /etc/pve/qemu-server/*.conf /etc/pve/lxc/*.conf 2>/dev/null)
|
||||
|
||||
ZFS_DISKS=""
|
||||
local zfs_raw entry path base_disk
|
||||
zfs_raw=$(zpool list -v -H 2>/dev/null | awk '{print $1}' | grep -v '^NAME$' | grep -v '^-' | grep -v '^mirror')
|
||||
for entry in $zfs_raw; do
|
||||
path=""
|
||||
if [[ "$entry" == wwn-* || "$entry" == ata-* ]]; then
|
||||
[[ -e "/dev/disk/by-id/$entry" ]] && path=$(readlink -f "/dev/disk/by-id/$entry")
|
||||
elif [[ "$entry" == /dev/* ]]; then
|
||||
path="$entry"
|
||||
fi
|
||||
if [[ -n "$path" ]]; then
|
||||
base_disk=$(lsblk -no PKNAME "$path" 2>/dev/null)
|
||||
[[ -n "$base_disk" ]] && ZFS_DISKS+="/dev/$base_disk"$'\n'
|
||||
fi
|
||||
done
|
||||
ZFS_DISKS=$(echo "$ZFS_DISKS" | sort -u)
|
||||
}
|
||||
|
||||
function _disk_is_host_system_used() {
|
||||
local disk="$1"
|
||||
local disk_real part fstype part_path
|
||||
DISK_USAGE_REASON=""
|
||||
|
||||
while read -r part fstype; do
|
||||
[[ -z "$part" ]] && continue
|
||||
part_path="/dev/$part"
|
||||
|
||||
if grep -qFx "$part_path" <<< "$MOUNTED_DISKS"; then
|
||||
DISK_USAGE_REASON="$(translate "Mounted filesystem detected") ($part_path)"
|
||||
return 0
|
||||
fi
|
||||
if grep -qFx "$part_path" <<< "$SWAP_DISKS"; then
|
||||
DISK_USAGE_REASON="$(translate "Swap partition detected") ($part_path)"
|
||||
return 0
|
||||
fi
|
||||
case "$fstype" in
|
||||
zfs_member)
|
||||
DISK_USAGE_REASON="$(translate "ZFS member detected") ($part_path)"
|
||||
return 0
|
||||
;;
|
||||
linux_raid_member)
|
||||
DISK_USAGE_REASON="$(translate "RAID member detected") ($part_path)"
|
||||
return 0
|
||||
;;
|
||||
LVM2_member)
|
||||
DISK_USAGE_REASON="$(translate "LVM physical volume detected") ($part_path)"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
done < <(lsblk -ln -o NAME,FSTYPE "$disk" 2>/dev/null)
|
||||
|
||||
disk_real=$(readlink -f "$disk" 2>/dev/null)
|
||||
if [[ -n "$disk_real" && -n "$LVM_DEVICES" ]] && grep -qFx "$disk_real" <<< "$LVM_DEVICES"; then
|
||||
DISK_USAGE_REASON="$(translate "Disk is part of host LVM")"
|
||||
return 0
|
||||
fi
|
||||
if [[ -n "$ZFS_DISKS" && "$ZFS_DISKS" == *"$disk"* ]]; then
|
||||
DISK_USAGE_REASON="$(translate "Disk is part of a host ZFS pool")"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
function _disk_used_in_guest_configs() {
|
||||
local disk="$1"
|
||||
local real_path
|
||||
real_path=$(readlink -f "$disk" 2>/dev/null)
|
||||
|
||||
if [[ -n "$real_path" ]] && grep -Fq "$real_path" <<< "$CONFIG_DATA"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local symlink
|
||||
for symlink in /dev/disk/by-id/*; do
|
||||
[[ -e "$symlink" ]] || continue
|
||||
if [[ "$(readlink -f "$symlink")" == "$real_path" ]] && grep -Fq "$symlink" <<< "$CONFIG_DATA"; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
function _controller_block_devices() {
|
||||
local pci_full="$1"
|
||||
local pci_root="/sys/bus/pci/devices/$pci_full"
|
||||
[[ -d "$pci_root" ]] || return 0
|
||||
|
||||
local sys_block dev_name cur base
|
||||
# Walk /sys/block and resolve each block device back to its ancestor PCI device.
|
||||
# This avoids unbounded recursive scans while still handling NVMe/SATA paths.
|
||||
for sys_block in /sys/block/*; do
|
||||
[[ -e "$sys_block/device" ]] || continue
|
||||
dev_name=$(basename "$sys_block")
|
||||
[[ -b "/dev/$dev_name" ]] || continue
|
||||
|
||||
cur=$(readlink -f "$sys_block/device" 2>/dev/null)
|
||||
[[ -n "$cur" ]] || continue
|
||||
|
||||
while [[ "$cur" != "/" ]]; do
|
||||
base=$(basename "$cur")
|
||||
if [[ "$base" == "$pci_full" ]]; then
|
||||
echo "/dev/$dev_name"
|
||||
break
|
||||
fi
|
||||
cur=$(dirname "$cur")
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
function _vm_is_q35() {
|
||||
local vmid="$1"
|
||||
local machine_line
|
||||
machine_line=$(qm config "$vmid" 2>/dev/null | awk -F': ' '/^machine:/ {print $2}')
|
||||
[[ "$machine_line" == *q35* ]]
|
||||
}
|
||||
@@ -23,15 +23,29 @@
|
||||
# - VM config: hostpci entries, NVIDIA KVM hiding
|
||||
# ==========================================================
|
||||
|
||||
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
LOCAL_SCRIPTS_LOCAL="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
LOCAL_SCRIPTS_DEFAULT="/usr/local/share/proxmenux/scripts"
|
||||
LOCAL_SCRIPTS="$LOCAL_SCRIPTS_DEFAULT"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
UTILS_FILE="$LOCAL_SCRIPTS/utils.sh"
|
||||
if [[ -f "$LOCAL_SCRIPTS_LOCAL/utils.sh" ]]; then
|
||||
LOCAL_SCRIPTS="$LOCAL_SCRIPTS_LOCAL"
|
||||
UTILS_FILE="$LOCAL_SCRIPTS/utils.sh"
|
||||
elif [[ ! -f "$UTILS_FILE" ]]; then
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
fi
|
||||
LOG_FILE="/tmp/add_gpu_vm.log"
|
||||
screen_capture="/tmp/proxmenux_add_gpu_vm_screen_$$.txt"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
if [[ -f "$LOCAL_SCRIPTS_LOCAL/global/pci_passthrough_helpers.sh" ]]; then
|
||||
source "$LOCAL_SCRIPTS_LOCAL/global/pci_passthrough_helpers.sh"
|
||||
elif [[ -f "$LOCAL_SCRIPTS_DEFAULT/global/pci_passthrough_helpers.sh" ]]; then
|
||||
source "$LOCAL_SCRIPTS_DEFAULT/global/pci_passthrough_helpers.sh"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
@@ -50,6 +64,7 @@ SELECTED_GPU_NAME=""
|
||||
|
||||
declare -a IOMMU_DEVICES=() # all PCI addrs in IOMMU group (endpoint devices)
|
||||
declare -a IOMMU_VFIO_IDS=() # vendor:device for vfio-pci ids=
|
||||
declare -a EXTRA_AUDIO_DEVICES=() # sibling audio function(s), typically *.1
|
||||
IOMMU_GROUP=""
|
||||
|
||||
SELECTED_VMID=""
|
||||
@@ -63,11 +78,17 @@ SWITCH_LXC_LIST=""
|
||||
SWITCH_FROM_VM=false
|
||||
SWITCH_VM_SRC=""
|
||||
TARGET_VM_ALREADY_HAS_GPU=false
|
||||
VM_SWITCH_ALREADY_VFIO=false
|
||||
PREFLIGHT_HOST_REBOOT_REQUIRED=true
|
||||
|
||||
AMD_ROM_FILE=""
|
||||
|
||||
HOST_CONFIG_CHANGED=false # set to true whenever host VFIO config is actually written
|
||||
|
||||
PRESELECT_VMID=""
|
||||
WIZARD_CALL=false
|
||||
GPU_WIZARD_RESULT_FILE=""
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Helpers
|
||||
@@ -92,19 +113,173 @@ _add_line_if_missing() {
|
||||
fi
|
||||
}
|
||||
|
||||
_append_unique() {
|
||||
local needle="$1"
|
||||
shift
|
||||
local item
|
||||
for item in "$@"; do
|
||||
[[ "$item" == "$needle" ]] && return 1
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
_vm_is_running() {
|
||||
local vmid="$1"
|
||||
qm status "$vmid" 2>/dev/null | grep -q "status: running"
|
||||
}
|
||||
|
||||
_vm_onboot_enabled() {
|
||||
local vmid="$1"
|
||||
qm config "$vmid" 2>/dev/null | grep -qE "^onboot:\s*1"
|
||||
}
|
||||
|
||||
_set_wizard_result() {
|
||||
local result="$1"
|
||||
[[ -z "${GPU_WIZARD_RESULT_FILE:-}" ]] && return 0
|
||||
printf '%s\n' "$result" >"$GPU_WIZARD_RESULT_FILE" 2>/dev/null || true
|
||||
}
|
||||
|
||||
_file_has_exact_line() {
|
||||
local line="$1"
|
||||
local file="$2"
|
||||
[[ -f "$file" ]] || return 1
|
||||
grep -qFx "$line" "$file"
|
||||
}
|
||||
|
||||
evaluate_host_reboot_requirement() {
|
||||
# Fast path for VM-to-VM reassignment where GPU is already bound to vfio
|
||||
if [[ "$VM_SWITCH_ALREADY_VFIO" == "true" ]]; then
|
||||
PREFLIGHT_HOST_REBOOT_REQUIRED=false
|
||||
return 0
|
||||
fi
|
||||
|
||||
local needs_change=false
|
||||
local current_driver
|
||||
current_driver=$(_get_pci_driver "$SELECTED_GPU_PCI")
|
||||
[[ "$current_driver" != "vfio-pci" ]] && needs_change=true
|
||||
|
||||
# /etc/modules expected lines
|
||||
local modules_file="/etc/modules"
|
||||
local modules=("vfio" "vfio_iommu_type1" "vfio_pci")
|
||||
local kernel_major kernel_minor
|
||||
kernel_major=$(uname -r | cut -d. -f1)
|
||||
kernel_minor=$(uname -r | cut -d. -f2)
|
||||
if (( kernel_major < 6 || ( kernel_major == 6 && kernel_minor < 2 ) )); then
|
||||
modules+=("vfio_virqfd")
|
||||
fi
|
||||
local mod
|
||||
for mod in "${modules[@]}"; do
|
||||
_file_has_exact_line "$mod" "$modules_file" || needs_change=true
|
||||
done
|
||||
|
||||
# vfio-pci ids
|
||||
local vfio_conf="/etc/modprobe.d/vfio.conf"
|
||||
local ids_line ids_part
|
||||
ids_line=$(grep "^options vfio-pci ids=" "$vfio_conf" 2>/dev/null | head -1)
|
||||
if [[ -z "$ids_line" ]]; then
|
||||
needs_change=true
|
||||
else
|
||||
[[ "$ids_line" == *"disable_vga=1"* ]] || needs_change=true
|
||||
ids_part=$(echo "$ids_line" | grep -oE 'ids=[^[:space:]]+' | sed 's/ids=//')
|
||||
local existing_ids=()
|
||||
IFS=',' read -ra existing_ids <<< "$ids_part"
|
||||
local required found existing
|
||||
for required in "${IOMMU_VFIO_IDS[@]}"; do
|
||||
found=false
|
||||
for existing in "${existing_ids[@]}"; do
|
||||
[[ "$existing" == "$required" ]] && found=true && break
|
||||
done
|
||||
$found || needs_change=true
|
||||
done
|
||||
fi
|
||||
|
||||
# modprobe options files
|
||||
_file_has_exact_line "options vfio_iommu_type1 allow_unsafe_interrupts=1" \
|
||||
/etc/modprobe.d/iommu_unsafe_interrupts.conf || needs_change=true
|
||||
_file_has_exact_line "options kvm ignore_msrs=1" \
|
||||
/etc/modprobe.d/kvm.conf || needs_change=true
|
||||
|
||||
# AMD softdep
|
||||
if [[ "$SELECTED_GPU" == "amd" ]]; then
|
||||
_file_has_exact_line "softdep radeon pre: vfio-pci" "$vfio_conf" || needs_change=true
|
||||
_file_has_exact_line "softdep amdgpu pre: vfio-pci" "$vfio_conf" || needs_change=true
|
||||
_file_has_exact_line "softdep snd_hda_intel pre: vfio-pci" "$vfio_conf" || needs_change=true
|
||||
fi
|
||||
|
||||
# host driver blacklist
|
||||
local blacklist_file="/etc/modprobe.d/blacklist.conf"
|
||||
case "$SELECTED_GPU" in
|
||||
nvidia)
|
||||
_file_has_exact_line "blacklist nouveau" "$blacklist_file" || needs_change=true
|
||||
_file_has_exact_line "blacklist nvidia" "$blacklist_file" || needs_change=true
|
||||
_file_has_exact_line "blacklist nvidiafb" "$blacklist_file" || needs_change=true
|
||||
_file_has_exact_line "blacklist lbm-nouveau" "$blacklist_file" || needs_change=true
|
||||
_file_has_exact_line "options nouveau modeset=0" "$blacklist_file" || needs_change=true
|
||||
;;
|
||||
amd)
|
||||
_file_has_exact_line "blacklist radeon" "$blacklist_file" || needs_change=true
|
||||
_file_has_exact_line "blacklist amdgpu" "$blacklist_file" || needs_change=true
|
||||
;;
|
||||
intel)
|
||||
_file_has_exact_line "blacklist i915" "$blacklist_file" || needs_change=true
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ "$needs_change" == "true" ]]; then
|
||||
PREFLIGHT_HOST_REBOOT_REQUIRED=true
|
||||
else
|
||||
PREFLIGHT_HOST_REBOOT_REQUIRED=false
|
||||
fi
|
||||
}
|
||||
|
||||
parse_cli_args() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--vmid)
|
||||
if [[ -n "${2:-}" ]]; then
|
||||
PRESELECT_VMID="$2"
|
||||
shift 2
|
||||
else
|
||||
shift
|
||||
fi
|
||||
;;
|
||||
--wizard)
|
||||
WIZARD_CALL=true
|
||||
shift
|
||||
;;
|
||||
--result-file)
|
||||
if [[ -n "${2:-}" ]]; then
|
||||
GPU_WIZARD_RESULT_FILE="$2"
|
||||
shift 2
|
||||
else
|
||||
shift
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
_get_vm_run_title() {
|
||||
if [[ "$SWITCH_FROM_LXC" == "true" && "$SWITCH_FROM_VM" == "true" ]]; then
|
||||
echo "GPU Switch Mode (LXC/VM → VM)"
|
||||
echo "$(translate 'GPU Passthrough to VM (reassign from LXC and another VM)')"
|
||||
elif [[ "$SWITCH_FROM_LXC" == "true" ]]; then
|
||||
echo "GPU Switch Mode (LXC → VM)"
|
||||
echo "$(translate 'GPU Passthrough to VM (from LXC)')"
|
||||
elif [[ "$SWITCH_FROM_VM" == "true" ]]; then
|
||||
echo "GPU Switch Mode (VM → VM)"
|
||||
echo "$(translate 'GPU Passthrough to VM (reassign from another VM)')"
|
||||
else
|
||||
echo "$(translate 'GPU Passthrough to VM')"
|
||||
fi
|
||||
}
|
||||
|
||||
_is_pci_slot_assigned_to_vm() {
|
||||
if declare -F _pci_slot_assigned_to_vm >/dev/null 2>&1; then
|
||||
_pci_slot_assigned_to_vm "$1" "$2"
|
||||
return $?
|
||||
fi
|
||||
|
||||
local pci_full="$1"
|
||||
local vmid="$2"
|
||||
local slot_base
|
||||
@@ -118,6 +293,11 @@ _is_pci_slot_assigned_to_vm() {
|
||||
# Match a specific PCI function when possible.
|
||||
# For function .0, also accept slot-only entries (e.g. 01:00) as equivalent.
|
||||
_is_pci_function_assigned_to_vm() {
|
||||
if declare -F _pci_function_assigned_to_vm >/dev/null 2>&1; then
|
||||
_pci_function_assigned_to_vm "$1" "$2"
|
||||
return $?
|
||||
fi
|
||||
|
||||
local pci_full="$1"
|
||||
local vmid="$2"
|
||||
local bdf slot func pattern
|
||||
@@ -234,6 +414,7 @@ detect_host_gpus() {
|
||||
GPU_COUNT=${#ALL_GPU_PCIS[@]}
|
||||
|
||||
if [[ $GPU_COUNT -eq 0 ]]; then
|
||||
_set_wizard_result "no_gpu"
|
||||
dialog --backtitle "ProxMenux" \
|
||||
--title "$(translate 'No GPU Detected')" \
|
||||
--msgbox "\n$(translate 'No compatible GPU was detected on this host.')" 8 60
|
||||
@@ -248,6 +429,10 @@ detect_host_gpus() {
|
||||
# Phase 1 — Step 2: Check IOMMU, offer to enable it
|
||||
# ==========================================================
|
||||
check_iommu_enabled() {
|
||||
if declare -F _pci_is_iommu_active >/dev/null 2>&1 && _pci_is_iommu_active; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if grep -qE 'intel_iommu=on|amd_iommu=on' /proc/cmdline 2>/dev/null && \
|
||||
[[ -d /sys/kernel/iommu_groups ]] && \
|
||||
[[ -n "$(ls /sys/kernel/iommu_groups/ 2>/dev/null)" ]]; then
|
||||
@@ -266,10 +451,10 @@ check_iommu_enabled() {
|
||||
--yesno "$msg" 15 72
|
||||
|
||||
local response=$?
|
||||
clear
|
||||
[[ "$WIZARD_CALL" != "true" ]] && clear
|
||||
|
||||
if [[ $response -eq 0 ]]; then
|
||||
show_proxmenux_logo
|
||||
[[ "$WIZARD_CALL" != "true" ]] && show_proxmenux_logo
|
||||
msg_title "$(translate 'Enabling IOMMU')"
|
||||
_enable_iommu_cmdline
|
||||
echo
|
||||
@@ -709,11 +894,59 @@ analyze_iommu_group() {
|
||||
--msgbox "\n${msg}" 22 82
|
||||
}
|
||||
|
||||
detect_optional_gpu_audio() {
|
||||
EXTRA_AUDIO_DEVICES=()
|
||||
|
||||
local sibling_audio="${SELECTED_GPU_PCI%.*}.1"
|
||||
local dev_path="/sys/bus/pci/devices/${sibling_audio}"
|
||||
[[ -d "$dev_path" ]] || return 0
|
||||
|
||||
local class_hex
|
||||
class_hex=$(cat "${dev_path}/class" 2>/dev/null | sed 's/^0x//')
|
||||
[[ "${class_hex:0:2}" == "04" ]] || return 0
|
||||
|
||||
local already_in_group=false dev
|
||||
for dev in "${IOMMU_DEVICES[@]}"; do
|
||||
if [[ "$dev" == "$sibling_audio" ]]; then
|
||||
already_in_group=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$already_in_group" == "true" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
EXTRA_AUDIO_DEVICES+=("$sibling_audio")
|
||||
|
||||
local vid did new_id
|
||||
vid=$(cat "${dev_path}/vendor" 2>/dev/null | sed 's/0x//')
|
||||
did=$(cat "${dev_path}/device" 2>/dev/null | sed 's/0x//')
|
||||
if [[ -n "$vid" && -n "$did" ]]; then
|
||||
new_id="${vid}:${did}"
|
||||
if _append_unique "$new_id" "${IOMMU_VFIO_IDS[@]}"; then
|
||||
IOMMU_VFIO_IDS+=("$new_id")
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Phase 1 — Step 6: VM selection
|
||||
# ==========================================================
|
||||
select_vm() {
|
||||
if [[ -n "$PRESELECT_VMID" ]]; then
|
||||
if qm config "$PRESELECT_VMID" >/dev/null 2>&1; then
|
||||
SELECTED_VMID="$PRESELECT_VMID"
|
||||
VM_NAME=$(qm config "$SELECTED_VMID" 2>/dev/null | grep "^name:" | awk '{print $2}')
|
||||
return 0
|
||||
fi
|
||||
dialog --backtitle "ProxMenux" \
|
||||
--title "$(translate 'Invalid VMID')" \
|
||||
--msgbox "\n$(translate 'The preselected VMID does not exist on this host:') ${PRESELECT_VMID}" 9 72
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local menu_items=()
|
||||
|
||||
while IFS= read -r line; do
|
||||
@@ -805,6 +1038,7 @@ check_switch_mode() {
|
||||
done
|
||||
msg+="\n$(translate 'VM passthrough requires exclusive VFIO binding of the GPU.')\n"
|
||||
msg+="$(translate 'GPU device access will be removed from those LXC containers.')\n\n"
|
||||
msg+="\Z3$(translate 'After this LXC → VM switch, reboot the host so the new binding state is applied cleanly.')\Zn\n\n"
|
||||
msg+="$(translate 'Do you want to continue?')"
|
||||
|
||||
dialog --backtitle "ProxMenux" \
|
||||
@@ -828,19 +1062,57 @@ check_switch_mode() {
|
||||
done
|
||||
|
||||
if [[ -n "$vm_src_id" ]]; then
|
||||
local src_running=false
|
||||
_vm_is_running "$vm_src_id" && src_running=true
|
||||
|
||||
if [[ "$src_running" == "true" ]]; then
|
||||
local msg
|
||||
msg="\n$(translate 'The selected GPU is already assigned to another VM that is currently running:')\n\n"
|
||||
msg+=" VM ${vm_src_id} (${vm_src_name:-VM-${vm_src_id}})\n\n"
|
||||
msg+="$(translate 'The same GPU cannot be used by two VMs at the same time.')\n\n"
|
||||
msg+="$(translate 'Next step: stop that VM first, then run')\n"
|
||||
msg+=" Hardware Graphics → Add GPU to VM\n"
|
||||
msg+="$(translate 'to move the GPU safely.')"
|
||||
|
||||
dialog --backtitle "ProxMenux" \
|
||||
--title "$(translate 'GPU Busy in Running VM')" \
|
||||
--msgbox "$msg" 16 78
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SWITCH_FROM_VM=true
|
||||
SWITCH_VM_SRC="$vm_src_id"
|
||||
local selected_driver
|
||||
selected_driver=$(_get_pci_driver "$SELECTED_GPU_PCI")
|
||||
if [[ "$selected_driver" == "vfio-pci" && "$SWITCH_FROM_LXC" != "true" ]]; then
|
||||
VM_SWITCH_ALREADY_VFIO=true
|
||||
fi
|
||||
|
||||
local src_onboot target_onboot
|
||||
src_onboot="0"
|
||||
target_onboot="0"
|
||||
_vm_onboot_enabled "$vm_src_id" && src_onboot="1"
|
||||
_vm_onboot_enabled "$SELECTED_VMID" && target_onboot="1"
|
||||
|
||||
local msg
|
||||
msg="\n$(translate 'The selected GPU is already configured for passthrough to:')\n\n"
|
||||
msg+=" VM ${vm_src_id} (${vm_src_name:-VM-${vm_src_id}})\n\n"
|
||||
msg+="$(translate 'That VM is currently stopped, so the GPU can be reassigned now.')\n"
|
||||
msg+="\Z3$(translate 'Important: both VMs cannot be running at the same time with the same GPU.')\Zn\n\n"
|
||||
msg+="$(translate 'The existing hostpci entry will be removed from that VM and configured on'): "
|
||||
msg+="VM ${SELECTED_VMID} (${VM_NAME:-VM-${SELECTED_VMID}})\n\n"
|
||||
if [[ "$src_onboot" == "1" && "$target_onboot" == "1" ]]; then
|
||||
msg+="\Z3$(translate 'Warning: both VMs have autostart enabled (onboot=1).')\Zn\n"
|
||||
msg+="\Z3$(translate 'Disable autostart on one VM to avoid startup conflicts.')\Zn\n\n"
|
||||
fi
|
||||
if [[ "$VM_SWITCH_ALREADY_VFIO" == "true" ]]; then
|
||||
msg+="$(translate 'Host GPU is already bound to vfio-pci. Host reconfiguration/reboot should not be required for this VM-to-VM reassignment.')\n\n"
|
||||
fi
|
||||
msg+="$(translate 'Do you want to continue?')"
|
||||
|
||||
dialog --backtitle "ProxMenux" \
|
||||
dialog --backtitle "ProxMenux" --colors \
|
||||
--title "$(translate 'GPU Already Assigned to Another VM')" \
|
||||
--yesno "$msg" 14 76
|
||||
--yesno "$msg" 24 88
|
||||
[[ $? -ne 0 ]] && exit 0
|
||||
fi
|
||||
}
|
||||
@@ -859,20 +1131,28 @@ confirm_summary() {
|
||||
msg+=" $(translate 'Target VM') : ${VM_NAME:-VM-${SELECTED_VMID}} (${SELECTED_VMID})\n"
|
||||
msg+=" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
|
||||
msg+=" \Zb$(translate 'Host'):\Zn\n"
|
||||
msg+=" • $(translate 'VFIO modules in /etc/modules')\n"
|
||||
msg+=" • $(translate 'vfio-pci IDs in /etc/modprobe.d/vfio.conf')\n"
|
||||
[[ "$SELECTED_GPU" == "amd" ]] && \
|
||||
msg+=" • $(translate 'AMD softdep configured')\n"
|
||||
[[ "$SELECTED_GPU" == "amd" ]] && \
|
||||
msg+=" • $(translate 'GPU ROM dump to /usr/share/kvm/')\n"
|
||||
msg+=" • $(translate 'GPU driver blacklisted')\n"
|
||||
msg+=" • $(translate 'initramfs updated')\n"
|
||||
msg+=" • \Zb$(translate 'System reboot required')\Zn\n\n"
|
||||
if [[ "$PREFLIGHT_HOST_REBOOT_REQUIRED" != "true" ]]; then
|
||||
msg+=" • $(translate 'Host VFIO configuration already up to date')\n"
|
||||
msg+=" • $(translate 'No host VFIO reconfiguration expected')\n"
|
||||
msg+=" • $(translate 'No host reboot expected')\n\n"
|
||||
else
|
||||
msg+=" • $(translate 'VFIO modules in /etc/modules')\n"
|
||||
msg+=" • $(translate 'vfio-pci IDs in /etc/modprobe.d/vfio.conf')\n"
|
||||
[[ "$SELECTED_GPU" == "amd" ]] && \
|
||||
msg+=" • $(translate 'AMD softdep configured')\n"
|
||||
[[ "$SELECTED_GPU" == "amd" ]] && \
|
||||
msg+=" • $(translate 'GPU ROM dump to /usr/share/kvm/')\n"
|
||||
msg+=" • $(translate 'GPU driver blacklisted')\n"
|
||||
msg+=" • $(translate 'initramfs updated')\n"
|
||||
msg+=" • \Zb$(translate 'System reboot required')\Zn\n\n"
|
||||
fi
|
||||
msg+=" \Zb$(translate 'VM') ${SELECTED_VMID}:\Zn\n"
|
||||
[[ "$TARGET_VM_ALREADY_HAS_GPU" == "true" ]] && \
|
||||
msg+=" • $(translate 'Existing hostpci entries detected — they will be reused')\n"
|
||||
msg+=" • $(translate 'Virtual display normalized to vga: std (compatibility)')\n"
|
||||
msg+=" • $(translate 'hostpci entries for all IOMMU group devices')\n"
|
||||
[[ ${#EXTRA_AUDIO_DEVICES[@]} -gt 0 ]] && \
|
||||
msg+=" • $(translate 'Additional GPU audio function will be added'): ${EXTRA_AUDIO_DEVICES[*]}\n"
|
||||
[[ "$SELECTED_GPU" == "nvidia" ]] && \
|
||||
msg+=" • $(translate 'NVIDIA KVM hiding (cpu hidden=1)')\n"
|
||||
[[ "$SWITCH_FROM_LXC" == "true" ]] && \
|
||||
@@ -1144,9 +1424,13 @@ configure_vm() {
|
||||
|
||||
# Find next free hostpciN index
|
||||
local idx=0
|
||||
while qm config "$SELECTED_VMID" 2>/dev/null | grep -q "^hostpci${idx}:"; do
|
||||
idx=$((idx + 1))
|
||||
done
|
||||
if declare -F _pci_next_hostpci_index >/dev/null 2>&1; then
|
||||
idx=$(_pci_next_hostpci_index "$SELECTED_VMID" 2>/dev/null || echo 0)
|
||||
else
|
||||
while qm config "$SELECTED_VMID" 2>/dev/null | grep -q "^hostpci${idx}:"; do
|
||||
idx=$((idx + 1))
|
||||
done
|
||||
fi
|
||||
|
||||
# Primary GPU: pcie=1, x-vga=1 only for NVIDIA/AMD (not Intel iGPU), romfile if AMD
|
||||
local gpu_opts="pcie=1"
|
||||
@@ -1173,6 +1457,17 @@ configure_vm() {
|
||||
idx=$((idx + 1))
|
||||
done
|
||||
|
||||
# Optional sibling GPU audio function (typically *.1) when split from IOMMU group
|
||||
for dev in "${EXTRA_AUDIO_DEVICES[@]}"; do
|
||||
if _is_pci_function_assigned_to_vm "$dev" "$SELECTED_VMID"; then
|
||||
msg_ok "$(translate 'GPU audio already present in target VM — existing hostpci entry reused'): ${dev}" | tee -a "$screen_capture"
|
||||
continue
|
||||
fi
|
||||
qm set "$SELECTED_VMID" --hostpci${idx} "${dev},pcie=1" >>"$LOG_FILE" 2>&1
|
||||
msg_ok "$(translate 'GPU audio added'): hostpci${idx}: ${dev},pcie=1" | tee -a "$screen_capture"
|
||||
idx=$((idx + 1))
|
||||
done
|
||||
|
||||
# NVIDIA: hide KVM hypervisor from guest
|
||||
[[ "$SELECTED_GPU" == "nvidia" ]] && _configure_nvidia_kvm_hide
|
||||
}
|
||||
@@ -1221,8 +1516,11 @@ update_initramfs_host() {
|
||||
# Main
|
||||
# ==========================================================
|
||||
main() {
|
||||
parse_cli_args "$@"
|
||||
|
||||
: >"$LOG_FILE"
|
||||
: >"$screen_capture"
|
||||
[[ "$WIZARD_CALL" == "true" ]] && _set_wizard_result "cancelled"
|
||||
|
||||
# ── Phase 1: all dialogs (no terminal output) ─────────
|
||||
detect_host_gpus
|
||||
@@ -1233,23 +1531,33 @@ main() {
|
||||
ensure_selected_gpu_not_already_in_target_vm
|
||||
check_gpu_vm_compatibility
|
||||
analyze_iommu_group
|
||||
detect_optional_gpu_audio
|
||||
check_vm_machine_type
|
||||
check_switch_mode
|
||||
evaluate_host_reboot_requirement
|
||||
confirm_summary
|
||||
|
||||
# ── Phase 2: processing ───────────────────────────────
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
local run_title
|
||||
run_title=$(_get_vm_run_title)
|
||||
msg_title "${run_title}"
|
||||
if [[ "$WIZARD_CALL" == "true" ]]; then
|
||||
echo
|
||||
else
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
msg_title "${run_title}"
|
||||
fi
|
||||
|
||||
add_vfio_modules
|
||||
configure_vfio_pci_ids
|
||||
configure_iommu_options
|
||||
[[ "$SELECTED_GPU" == "amd" ]] && add_softdep_amd
|
||||
blacklist_gpu_drivers
|
||||
[[ "$SELECTED_GPU" == "amd" ]] && dump_amd_rom
|
||||
if [[ "$VM_SWITCH_ALREADY_VFIO" == "true" ]]; then
|
||||
msg_ok "$(translate 'Host already in VFIO mode — skipping host reconfiguration for VM reassignment')" | tee -a "$screen_capture"
|
||||
else
|
||||
add_vfio_modules
|
||||
configure_vfio_pci_ids
|
||||
configure_iommu_options
|
||||
[[ "$SELECTED_GPU" == "amd" ]] && add_softdep_amd
|
||||
blacklist_gpu_drivers
|
||||
[[ "$SELECTED_GPU" == "amd" ]] && dump_amd_rom
|
||||
fi
|
||||
cleanup_lxc_configs
|
||||
cleanup_vm_config
|
||||
ensure_vm_display_std
|
||||
@@ -1257,43 +1565,50 @@ main() {
|
||||
[[ "$HOST_CONFIG_CHANGED" == "true" ]] && update_initramfs_host
|
||||
|
||||
# ── Phase 3: summary ─────────────────────────────────
|
||||
show_proxmenux_logo
|
||||
msg_title "${run_title}"
|
||||
cat "$screen_capture"
|
||||
|
||||
echo
|
||||
echo -e "${TAB}${BL}📄 Log: ${LOG_FILE}${CL}"
|
||||
echo
|
||||
|
||||
if [[ "$HOST_CONFIG_CHANGED" == "true" ]]; then
|
||||
msg_info2 "$(translate 'After rebooting, verify VFIO binding with:')"
|
||||
echo " lspci -nnk | grep -A2 vfio-pci"
|
||||
if [[ "$WIZARD_CALL" == "true" ]]; then
|
||||
echo
|
||||
msg_info2 "$(translate 'Next steps after reboot:')"
|
||||
echo " 1. $(translate 'Start the VM')"
|
||||
else
|
||||
msg_info2 "$(translate 'Host VFIO config was already up to date — no reboot needed.')"
|
||||
msg_info2 "$(translate 'Next steps:')"
|
||||
echo " 1. $(translate 'Start the VM')"
|
||||
show_proxmenux_logo
|
||||
msg_title "${run_title}"
|
||||
cat "$screen_capture"
|
||||
echo
|
||||
fi
|
||||
|
||||
if [[ "$WIZARD_CALL" == "true" ]]; then
|
||||
_set_wizard_result "applied"
|
||||
rm -f "$screen_capture"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo -e "${TAB}${BL}📄 Log: ${LOG_FILE}${CL}"
|
||||
if [[ "$HOST_CONFIG_CHANGED" == "true" ]]; then
|
||||
echo -e "${TAB}${DGN}- $(translate 'Host VFIO configuration changed — reboot required before starting the VM.')${CL}"
|
||||
else
|
||||
echo -e "${TAB}${DGN}- $(translate 'Host VFIO config was already up to date — no reboot needed.')${CL}"
|
||||
fi
|
||||
|
||||
case "$SELECTED_GPU" in
|
||||
nvidia)
|
||||
echo " 2. $(translate 'Install NVIDIA drivers from nvidia.com inside the guest')"
|
||||
echo " 3. $(translate 'If Code 43 error: KVM hiding is already configured')"
|
||||
echo -e "${TAB}${DGN}- $(translate 'Install NVIDIA drivers from nvidia.com inside the guest.')${CL}"
|
||||
echo -e "${TAB}${DGN}- $(translate 'If Code 43 error appears, KVM hiding is already configured.')${CL}"
|
||||
;;
|
||||
amd)
|
||||
echo " 2. $(translate 'Install AMD GPU drivers inside the guest')"
|
||||
echo " 3. $(translate 'If passthrough fails on Windows: install RadeonResetBugFix')"
|
||||
echo -e "${TAB}${DGN}- $(translate 'Install AMD GPU drivers inside the guest.')${CL}"
|
||||
echo -e "${TAB}${DGN}- $(translate 'If passthrough fails on Windows: install RadeonResetBugFix.')${CL}"
|
||||
[[ -n "$AMD_ROM_FILE" ]] && \
|
||||
echo " $(translate 'ROM file used'): /usr/share/kvm/${AMD_ROM_FILE}"
|
||||
echo -e "${TAB}${DGN}- $(translate 'ROM file used'): /usr/share/kvm/${AMD_ROM_FILE}${CL}"
|
||||
;;
|
||||
intel)
|
||||
echo " 2. $(translate 'Install Intel Graphics Driver inside the guest')"
|
||||
echo " 3. $(translate 'Enable Remote Desktop (RDP) before disabling the virtual display')"
|
||||
echo -e "${TAB}${DGN}- $(translate 'Install Intel Graphics Driver inside the guest.')${CL}"
|
||||
echo -e "${TAB}${DGN}- $(translate 'Enable Remote Desktop (RDP) before disabling the virtual display.')${CL}"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo
|
||||
msg_info2 "$(translate 'If you want to use a physical monitor on the passthrough GPU:')"
|
||||
echo " • $(translate 'First install the GPU drivers inside the guest and verify remote access (RDP/SSH).')"
|
||||
echo " • $(translate 'Then change the VM display to none (vga: none) when the guest is stable.')"
|
||||
|
||||
echo
|
||||
msg_success "$(translate 'GPU passthrough configured for VM') ${SELECTED_VMID} (${VM_NAME})."
|
||||
echo
|
||||
@@ -1317,4 +1632,4 @@ main() {
|
||||
fi
|
||||
}
|
||||
|
||||
main
|
||||
main "$@"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# configurations, streamlining the deployment of Linux, Windows, and other systems.
|
||||
#
|
||||
# Key features:
|
||||
# - Supports both virtual disk creation and physical disk passthrough.
|
||||
# - Supports virtual disks, import disks, and Controller + NVMe passthrough.
|
||||
# - Automates CPU, RAM, BIOS, network and storage configuration.
|
||||
# - Provides a user-friendly menu to select OS type, ISO image and disk interface.
|
||||
# - Automatically generates a detailed and styled HTML description for each VM.
|
||||
@@ -24,14 +24,22 @@
|
||||
# consistent and maintainable way, using ProxMenux standards.
|
||||
# ==========================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
LOCAL_SCRIPTS_DEFAULT="/usr/local/share/proxmenux/scripts"
|
||||
LOCAL_SCRIPTS_LOCAL="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
if [[ -f "$LOCAL_SCRIPTS_LOCAL/vm/disk_selector.sh" ]]; then
|
||||
LOCAL_SCRIPTS="$LOCAL_SCRIPTS_LOCAL"
|
||||
else
|
||||
LOCAL_SCRIPTS="$LOCAL_SCRIPTS_DEFAULT"
|
||||
fi
|
||||
|
||||
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
|
||||
VM_REPO="$LOCAL_SCRIPTS/vm"
|
||||
ISO_REPO="$LOCAL_SCRIPTS/vm"
|
||||
MENU_REPO="$LOCAL_SCRIPTS/menus"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
UTILS_FILE="$LOCAL_SCRIPTS/utils.sh"
|
||||
[[ ! -f "$UTILS_FILE" ]] && UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
# Source utilities and required scripts
|
||||
@@ -55,12 +63,39 @@ initialize_cache
|
||||
function header_info() {
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
echo -e "${BL}╔═══════════════════════════════════════════════╗${CL}"
|
||||
echo -e "${BL}║ ║${CL}"
|
||||
echo -e "${BL}║${YWB} ProxMenux VM Creator ${BL}║${CL}"
|
||||
echo -e "${BL}║ ║${CL}"
|
||||
echo -e "${BL}╚═══════════════════════════════════════════════╝${CL}"
|
||||
echo -e
|
||||
msg_title "ProxMenux VM Creator"
|
||||
}
|
||||
|
||||
VM_WIZARD_CAPTURE_FILE=""
|
||||
VM_WIZARD_CAPTURE_ACTIVE=0
|
||||
|
||||
function start_vm_wizard_capture() {
|
||||
[[ "${VM_WIZARD_CAPTURE_ACTIVE:-0}" -eq 1 ]] && return 0
|
||||
VM_WIZARD_CAPTURE_FILE="/tmp/proxmenux_vm_wizard_screen_capture_$$.txt"
|
||||
: >"$VM_WIZARD_CAPTURE_FILE"
|
||||
exec 8>&1
|
||||
exec > >(tee -a "$VM_WIZARD_CAPTURE_FILE")
|
||||
VM_WIZARD_CAPTURE_ACTIVE=1
|
||||
}
|
||||
|
||||
function stop_vm_wizard_capture() {
|
||||
if [[ "${VM_WIZARD_CAPTURE_ACTIVE:-0}" -eq 1 ]]; then
|
||||
exec 1>&8
|
||||
exec 8>&-
|
||||
VM_WIZARD_CAPTURE_ACTIVE=0
|
||||
fi
|
||||
if [[ -n "${VM_WIZARD_CAPTURE_FILE:-}" && -f "$VM_WIZARD_CAPTURE_FILE" ]]; then
|
||||
rm -f "$VM_WIZARD_CAPTURE_FILE"
|
||||
fi
|
||||
VM_WIZARD_CAPTURE_FILE=""
|
||||
}
|
||||
|
||||
function has_usable_gpu_for_vm_passthrough() {
|
||||
lspci -nn 2>/dev/null \
|
||||
| grep -iE "VGA compatible controller|3D controller|Display controller" \
|
||||
| grep -ivE "Ethernet|Network|Audio" \
|
||||
| grep -ivE "ASPEED|AST[0-9]{3,4}|Matrox|G200e|BMC" \
|
||||
| grep -q .
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
@@ -77,14 +112,15 @@ function header_info() {
|
||||
function start_vm_configuration() {
|
||||
|
||||
if (whiptail --title "ProxMenux" --yesno "$(translate "Use Default Settings?")" --no-button "$(translate "Advanced")" 10 60); then
|
||||
header_info
|
||||
load_default_vm_config "$OS_TYPE"
|
||||
#header_info
|
||||
#load_default_vm_config "$OS_TYPE"
|
||||
|
||||
if [[ -z "$HN" ]]; then
|
||||
HN=$(whiptail --inputbox "$(translate "Enter a name for the new virtual machine:")" 10 60 --title "VM Hostname" 3>&1 1>&2 2>&3)
|
||||
[[ -z "$HN" ]] && HN="custom-vm"
|
||||
fi
|
||||
|
||||
header_info
|
||||
load_default_vm_config "$OS_TYPE"
|
||||
apply_default_vm_config
|
||||
else
|
||||
header_info
|
||||
@@ -133,19 +169,45 @@ while true; do
|
||||
esac
|
||||
|
||||
if ! confirm_vm_creation; then
|
||||
stop_vm_wizard_capture
|
||||
continue
|
||||
fi
|
||||
|
||||
start_vm_wizard_capture
|
||||
|
||||
start_vm_configuration || continue
|
||||
if ! start_vm_configuration; then
|
||||
stop_vm_wizard_capture
|
||||
continue
|
||||
fi
|
||||
|
||||
|
||||
select_disk_type
|
||||
if [[ -z "$DISK_TYPE" ]]; then
|
||||
msg_error "$(translate "Disk type selection failed or cancelled")"
|
||||
unset DISK_TYPE
|
||||
if ! select_disk_type; then
|
||||
stop_vm_wizard_capture
|
||||
msg_error "$(translate "Storage plan selection failed or cancelled")"
|
||||
continue
|
||||
fi
|
||||
|
||||
create_vm
|
||||
WIZARD_ADD_GPU="no"
|
||||
if has_usable_gpu_for_vm_passthrough; then
|
||||
if whiptail --backtitle "ProxMenux" --title "$(translate "Optional GPU Passthrough")" \
|
||||
--yesno "$(translate "Do you want to configure GPU passthrough for this VM now?")\n\n$(translate "This will launch the GPU assistant after VM creation and may require a host reboot.")" 12 78 --defaultno; then
|
||||
WIZARD_ADD_GPU="yes"
|
||||
fi
|
||||
else
|
||||
msg_warn "$(translate "No compatible GPU detected for VM passthrough. Skipping GPU wizard option.")"
|
||||
fi
|
||||
export WIZARD_ADD_GPU
|
||||
|
||||
if [[ "$WIZARD_ADD_GPU" != "yes" ]]; then
|
||||
stop_vm_wizard_capture
|
||||
fi
|
||||
|
||||
if ! create_vm; then
|
||||
stop_vm_wizard_capture
|
||||
msg_error "$(translate "VM creation failed or was cancelled during storage setup.")"
|
||||
continue
|
||||
fi
|
||||
stop_vm_wizard_capture
|
||||
break
|
||||
done
|
||||
|
||||
@@ -1,480 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ==========================================================
|
||||
# ProxMenux - LXC Mount Manager
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT
|
||||
# Version : 3.1-enhanced
|
||||
# Last Updated: $(date +%d/%m/%Y)
|
||||
# ==========================================================
|
||||
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
source "$BASE_DIR/utils.sh"
|
||||
|
||||
SHARE_COMMON_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main/scripts/global/share-common.func"
|
||||
if ! source <(curl -s "$SHARE_COMMON_URL" 2>/dev/null); then
|
||||
SHARE_COMMON_LOADED=false
|
||||
else
|
||||
SHARE_COMMON_LOADED=true
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
# ==========================================================
|
||||
|
||||
get_container_uid_shift() {
|
||||
local ctid="$1"
|
||||
local conf="/etc/pve/lxc/${ctid}.conf"
|
||||
local uid_shift
|
||||
|
||||
if [[ ! -f "$conf" ]]; then
|
||||
echo "100000"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local unpriv
|
||||
unpriv=$(grep "^unprivileged:" "$conf" | awk '{print $2}')
|
||||
|
||||
if [[ "$unpriv" == "1" ]]; then
|
||||
uid_shift=$(grep "^lxc.idmap" "$conf" | grep 'u 0' | awk '{print $5}' | head -1)
|
||||
echo "${uid_shift:-100000}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "0"
|
||||
return 0
|
||||
}
|
||||
|
||||
setup_container_access() {
|
||||
local ctid="$1" group_name="$2" host_gid="$3" host_dir="$4"
|
||||
local uid_shift mapped_gid
|
||||
|
||||
if [[ ! "$ctid" =~ ^[0-9]+$ ]]; then
|
||||
msg_error "$(translate 'Invalid container ID format:') $ctid"
|
||||
return 1
|
||||
fi
|
||||
|
||||
uid_shift=$(get_container_uid_shift "$ctid")
|
||||
|
||||
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# CONTAINER TYPE DETECTION AND STRATEGY
|
||||
# ===================================================================
|
||||
|
||||
if [[ "$uid_shift" -eq 0 ]]; then
|
||||
msg_ok "$(translate "PRIVILEGED container detected - using direct UID/GID mapping")"
|
||||
mapped_gid="$host_gid"
|
||||
container_type="privileged"
|
||||
else
|
||||
msg_ok "$(translate "UNPRIVILEGED container detected - using mapped UID/GID")"
|
||||
mapped_gid=$((uid_shift + host_gid))
|
||||
container_type="unprivileged"
|
||||
msg_ok "UID shift: $uid_shift, Host GID: $host_gid → Container GID: $mapped_gid"
|
||||
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# STEP 1: ACL TOOLS (only for unprivileged containers)
|
||||
# ===================================================================
|
||||
|
||||
if [[ "$container_type" == "unprivileged" ]]; then
|
||||
if ! command -v setfacl >/dev/null 2>&1; then
|
||||
msg_info "$(translate "Installing ACL tools (REQUIRED for unprivileged containers)...")"
|
||||
apt-get update >/dev/null 2>&1
|
||||
apt-get install -y acl >/dev/null 2>&1
|
||||
if command -v setfacl >/dev/null 2>&1; then
|
||||
msg_ok "$(translate "ACL tools installed successfully")"
|
||||
else
|
||||
msg_error "$(translate "Failed to install ACL tools - permissions may not work correctly")"
|
||||
fi
|
||||
else
|
||||
msg_ok "$(translate "ACL tools already available")"
|
||||
fi
|
||||
else
|
||||
msg_ok "$(translate "Privileged container - ACL tools not required (using POSIX permissions)")"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# STEP 2: CONTAINER GROUP CONFIGURATION
|
||||
# ===================================================================
|
||||
|
||||
msg_info "$(translate "Configuring container group with") $container_type $(translate "strategy...")"
|
||||
|
||||
pct exec "$ctid" -- sh -c "
|
||||
# Remove existing group if GID is wrong
|
||||
if getent group $group_name >/dev/null 2>&1; then
|
||||
current_gid=\$(getent group $group_name | cut -d: -f3)
|
||||
if [ \"\$current_gid\" != \"$mapped_gid\" ]; then
|
||||
groupdel $group_name 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create group with correct GID
|
||||
groupadd -g $mapped_gid $group_name 2>/dev/null || true
|
||||
" 2>/dev/null
|
||||
|
||||
msg_ok "$(translate "Container group configured:") $group_name (GID: $mapped_gid)"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# STEP 3: USER PROCESSING (different strategies)
|
||||
# ===================================================================
|
||||
|
||||
local container_users
|
||||
container_users=$(pct exec "$ctid" -- getent passwd | awk -F: '{print $1 ":" $3}' 2>/dev/null)
|
||||
|
||||
local users_added=0
|
||||
local acls_applied=0
|
||||
|
||||
if [[ "$container_type" == "privileged" ]]; then
|
||||
|
||||
|
||||
msg_ok "$(translate "Privileged container:") $users_added $(translate "users added to group (no ACLs needed)")"
|
||||
|
||||
else
|
||||
|
||||
msg_info "$(translate "Using UNPRIVILEGED strategy: mapped UIDs + ACL permissions")"
|
||||
|
||||
while IFS=: read -r username ct_uid; do
|
||||
if [[ -n "$username" && "$ct_uid" =~ ^[0-9]+$ ]]; then
|
||||
local host_uid=$((uid_shift + ct_uid))
|
||||
|
||||
if pct exec "$ctid" -- usermod -aG "$group_name" "$username" 2>/dev/null; then
|
||||
users_added=$((users_added + 1))
|
||||
|
||||
if command -v setfacl >/dev/null 2>&1; then
|
||||
setfacl -m u:$host_uid:rwx "$host_dir" 2>/dev/null
|
||||
setfacl -m d:u:$host_uid:rwx "$host_dir" 2>/dev/null
|
||||
acls_applied=$((acls_applied + 1))
|
||||
fi
|
||||
|
||||
case "$username" in
|
||||
root|www-data|ncp|nobody|ubuntu|debian)
|
||||
msg_ok "$(translate "Configured user:") $username (CT_UID:$ct_uid → HOST_UID:$host_uid)"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
done <<< "$container_users"
|
||||
|
||||
msg_ok "$(translate "Unprivileged container:") $users_added $(translate "users added,") $acls_applied $(translate "ACL entries applied")"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# STEP 4: DIRECTORY PERMISSIONS
|
||||
# ===================================================================
|
||||
msg_info "$(translate "Setting optimal directory permissions...")"
|
||||
|
||||
chmod 2775 "$host_dir" 2>/dev/null || true
|
||||
chgrp "$group_name" "$host_dir" 2>/dev/null || true
|
||||
|
||||
msg_ok "$(translate "Host directory permissions:") 2775 root:$group_name"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# STEP 5: VERIFICATION
|
||||
# ===================================================================
|
||||
msg_info "$(translate "Verifying configuration...")"
|
||||
|
||||
if [[ "$container_type" == "unprivileged" ]] && command -v getfacl >/dev/null 2>&1; then
|
||||
local acl_count=$(getfacl "$host_dir" 2>/dev/null | grep "^user:" | grep -v "^user::" | wc -l)
|
||||
msg_ok "$(translate "ACL entries configured:") $acl_count"
|
||||
|
||||
# Show sample ACL entries
|
||||
if [[ $acl_count -gt 0 ]]; then
|
||||
echo -e "${TAB}${BGN}$(translate " ACL entries:")${CL}"
|
||||
getfacl "$host_dir" 2>/dev/null | grep "^user:" | grep -v "^user::" | head -3 | while read acl_line; do
|
||||
echo -e "${TAB} ${BL}$acl_line${CL}"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
local test_users=("www-data" "root" "ncp" "nobody")
|
||||
local successful_tests=0
|
||||
|
||||
for test_user in "${test_users[@]}"; do
|
||||
if pct exec "$ctid" -- id "$test_user" >/dev/null 2>&1; then
|
||||
if pct exec "$ctid" -- su -s /bin/bash "$test_user" -c "ls '$4' >/dev/null 2>&1" 2>/dev/null; then
|
||||
successful_tests=$((successful_tests + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $successful_tests -gt 0 ]]; then
|
||||
msg_ok "$(translate "Access verification:") $successful_tests $(translate "users can access mount point")"
|
||||
fi
|
||||
|
||||
|
||||
if [[ "$container_type" == "privileged" ]]; then
|
||||
msg_ok "$(translate "PRIVILEGED container configuration completed - using direct POSIX permissions")"
|
||||
else
|
||||
msg_ok "$(translate "UNPRIVILEGED container configuration completed - using ACL permissions")"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
get_next_mp_index() {
|
||||
local ctid="$1"
|
||||
local conf="/etc/pve/lxc/${ctid}.conf"
|
||||
|
||||
if [[ ! "$ctid" =~ ^[0-9]+$ ]] || [[ ! -f "$conf" ]]; then
|
||||
echo "0"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local used idx next=0
|
||||
used=$(awk -F: '/^mp[0-9]+:/ {print $1}' "$conf" | sed 's/mp//' | sort -n)
|
||||
for idx in $used; do
|
||||
[[ "$idx" -ge "$next" ]] && next=$((idx+1))
|
||||
done
|
||||
echo "$next"
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
add_bind_mount() {
|
||||
local ctid="$1" host_path="$2" ct_path="$3"
|
||||
local mpidx result
|
||||
|
||||
if [[ ! "$ctid" =~ ^[0-9]+$ ]]; then
|
||||
msg_error "$(translate 'Invalid container ID format:') $ctid"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -z "$ctid" || -z "$host_path" || -z "$ct_path" ]]; then
|
||||
msg_error "$(translate "Missing arguments")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if pct config "$ctid" | grep -q "$host_path"; then
|
||||
echo -e
|
||||
msg_warn "$(translate "Directory already mounted in container configuration.")"
|
||||
echo -e ""
|
||||
msg_success "$(translate 'Press Enter to return to menu...')"
|
||||
read -r
|
||||
return 1
|
||||
fi
|
||||
|
||||
mpidx=$(get_next_mp_index "$ctid")
|
||||
|
||||
result=$(pct set "$ctid" -mp${mpidx} "$host_path,mp=$ct_path,shared=1,backup=0,acl=1" 2>&1)
|
||||
|
||||
if [[ $? -eq 0 ]]; then
|
||||
msg_ok "$(translate "Successfully mounted:") $host_path → $ct_path"
|
||||
return 0
|
||||
else
|
||||
msg_error "$(translate "Error mounting folder:") $result"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
mount_host_directory_to_lxc() {
|
||||
|
||||
# Step 1: Select container
|
||||
local container_id
|
||||
container_id=$(select_lxc_container)
|
||||
if [[ $? -ne 0 || -z "$container_id" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
show_proxmenux_logo
|
||||
msg_title "$(translate 'Mount Host Directory to LXC Container')"
|
||||
|
||||
# Step 1.1: Ensure running
|
||||
ct_status=$(pct status "$container_id" | awk '{print $2}')
|
||||
if [[ "$ct_status" != "running" ]]; then
|
||||
|
||||
msg_info "$(translate "Starting container") $container_id..."
|
||||
if pct start "$container_id"; then
|
||||
sleep 3
|
||||
msg_ok "$(translate "Container started")"
|
||||
else
|
||||
msg_error "$(translate "Failed to start container")"
|
||||
echo -e ""
|
||||
msg_success "$(translate 'Press Enter to continue...')"
|
||||
read -r
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
msg_ok "$(translate 'Container selected and running')"
|
||||
sleep 2
|
||||
|
||||
# Step 2: Select host directory
|
||||
local host_dir
|
||||
host_dir=$(select_host_directory)
|
||||
if [[ -z "$host_dir" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
msg_ok "$(translate 'Host directory selected')"
|
||||
|
||||
# Step 3: Setup group
|
||||
local group_name="sharedfiles"
|
||||
local group_gid
|
||||
group_gid=$(pmx_ensure_host_group "$group_name")
|
||||
if [[ -z "$group_gid" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Set basic permissions
|
||||
chown -R root:"$group_name" "$host_dir" 2>/dev/null || true
|
||||
chmod -R 2775 "$host_dir" 2>/dev/null || true
|
||||
|
||||
msg_ok "$(translate 'Host group configured')"
|
||||
|
||||
# Step 4: Select container mount point
|
||||
|
||||
local ct_mount_point
|
||||
ct_mount_point=$(select_container_mount_point "$container_id" "$host_dir")
|
||||
if [[ -z "$ct_mount_point" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# Step 5: Confirmation
|
||||
local uid_shift container_type
|
||||
uid_shift=$(get_container_uid_shift "$container_id")
|
||||
if [[ "$uid_shift" -eq 0 ]]; then
|
||||
container_type="$(translate 'Privileged')"
|
||||
else
|
||||
container_type="$(translate 'Unprivileged')"
|
||||
fi
|
||||
|
||||
local confirm_msg="$(translate "Mount Configuration:")
|
||||
|
||||
$(translate "Container ID:"): $container_id ($container_type)
|
||||
$(translate "Host Directory:"): $host_dir
|
||||
$(translate "Container Mount Point:"): $ct_mount_point
|
||||
$(translate "Shared Group:"): $group_name (GID: $group_gid)
|
||||
|
||||
$(translate "Proceed?")"
|
||||
|
||||
if ! whiptail --title "$(translate "Confirm Mount")" --yesno "$confirm_msg" 16 70; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# Step 6: Add mount
|
||||
if ! add_bind_mount "$container_id" "$host_dir" "$ct_mount_point"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Step 7: Setup access (handles both privileged and unprivileged)
|
||||
setup_container_access "$container_id" "$group_name" "$group_gid" "$host_dir"
|
||||
|
||||
# Step 8: Final setup
|
||||
pct exec "$container_id" -- chgrp "$group_name" "$ct_mount_point" 2>/dev/null || true
|
||||
pct exec "$container_id" -- chmod 2775 "$ct_mount_point" 2>/dev/null || true
|
||||
|
||||
# Step 9: Summary
|
||||
echo -e ""
|
||||
echo -e "${TAB}${BOLD}$(translate 'Mount Added Successfully:')${CL}"
|
||||
echo -e "${TAB}${BGN}$(translate 'Container:')${CL} ${BL}$container_id ($container_type)${CL}"
|
||||
echo -e "${TAB}${BGN}$(translate 'Host Directory:')${CL} ${BL}$host_dir${CL}"
|
||||
echo -e "${TAB}${BGN}$(translate 'Mount Point:')${CL} ${BL}$ct_mount_point${CL}"
|
||||
echo -e "${TAB}${BGN}$(translate 'Group:')${CL} ${BL}$group_name (GID: $group_gid)${CL}"
|
||||
|
||||
if [[ "$uid_shift" -eq 0 ]]; then
|
||||
echo -e "${TAB}${BGN}$(translate 'Permission Strategy:')${CL} ${BL}POSIX (direct mapping)${CL}"
|
||||
else
|
||||
echo -e "${TAB}${BGN}$(translate 'Permission Strategy:')${CL} ${BL}ACL (mapped UIDs)${CL}"
|
||||
fi
|
||||
|
||||
echo -e ""
|
||||
if whiptail --yesno "$(translate "Restart container to activate mount?")" 8 60; then
|
||||
msg_info "$(translate 'Restarting container...')"
|
||||
if pct reboot "$container_id"; then
|
||||
sleep 5
|
||||
msg_ok "$(translate 'Container restarted successfully')"
|
||||
|
||||
echo -e
|
||||
echo -e "${TAB}${BOLD}$(translate 'Testing access and read/write:')${CL}"
|
||||
test_user=$(pct exec "$container_id" -- sh -c "id -u ncp >/dev/null 2>&1 && echo ncp || echo www-data")
|
||||
|
||||
if pct exec "$container_id" -- su -s /bin/bash $test_user -c "touch $ct_mount_point/test_access.txt" 2>/dev/null; then
|
||||
msg_ok "$(translate "Mount access and read/write successful (tested as $test_user)")"
|
||||
rm -f "$host_dir/test_access.txt" 2>/dev/null || true
|
||||
else
|
||||
msg_warn "$(translate "⚠ Access test failed - check permissions (user: $test_user)")"
|
||||
fi
|
||||
|
||||
else
|
||||
msg_warn "$(translate 'Failed to restart - restart manually')"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -e ""
|
||||
msg_success "$(translate 'Press Enter to continue...')"
|
||||
read -r
|
||||
}
|
||||
|
||||
# Main menu
|
||||
main_menu() {
|
||||
while true; do
|
||||
choice=$(dialog --title "$(translate 'LXC Mount Manager')" \
|
||||
--menu "\n$(translate 'Choose an option:')" 25 80 15 \
|
||||
"1" "$(translate 'Mount Host Directory to LXC')" \
|
||||
"2" "$(translate 'View Mount Points')" \
|
||||
"3" "$(translate 'Remove Mount Point')" \
|
||||
"4" "$(translate 'Exit')" 3>&1 1>&2 2>&3)
|
||||
|
||||
case $choice in
|
||||
1)
|
||||
mount_host_directory_to_lxc
|
||||
;;
|
||||
2)
|
||||
msg_info2 "$(translate 'Feature coming soon...')"
|
||||
read -p "$(translate 'Press Enter to continue...')"
|
||||
;;
|
||||
3)
|
||||
msg_info2 "$(translate 'Feature coming soon...')"
|
||||
read -p "$(translate 'Press Enter to continue...')"
|
||||
;;
|
||||
4|"")
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
#main_menu
|
||||
mount_host_directory_to_lxc
|
||||
@@ -1,175 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenuX - Virtual Machine Creator Script
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 07/05/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script is part of the central ProxMenux VM creation module. It allows users
|
||||
# to create virtual machines (VMs) in Proxmox VE using either default or advanced
|
||||
# configurations, streamlining the deployment of Linux, Windows, and other systems.
|
||||
#
|
||||
# Key features:
|
||||
# - Supports both virtual disk creation and physical disk passthrough.
|
||||
# - Automates CPU, RAM, BIOS, network and storage configuration.
|
||||
# - Provides a user-friendly menu to select OS type, ISO image and disk interface.
|
||||
# - Automatically generates a detailed and styled HTML description for each VM.
|
||||
#
|
||||
# All operations are designed to simplify and accelerate VM creation in a
|
||||
# consistent and maintainable way, using ProxMenux standards.
|
||||
# ==========================================================
|
||||
|
||||
|
||||
|
||||
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
|
||||
VM_REPO="$LOCAL_SCRIPTS/vm"
|
||||
ISO_REPO="$LOCAL_SCRIPTS/vm"
|
||||
MENU_REPO="$LOCAL_SCRIPTS/menus"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
[[ -f "$UTILS_FILE" ]] && source "$UTILS_FILE"
|
||||
|
||||
|
||||
source "$VM_REPO/vm_configurator.sh"
|
||||
source "$VM_REPO/disk_selector.sh"
|
||||
source "$VM_REPO/vm_creator.sh"
|
||||
|
||||
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
|
||||
|
||||
function header_info() {
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
echo -e "${BL}╔═══════════════════════════════════════════════╗${CL}"
|
||||
echo -e "${BL}║ ║${CL}"
|
||||
echo -e "${BL}║${YWB} ProxMenux VM Creator ${BL}║${CL}"
|
||||
echo -e "${BL}║ ║${CL}"
|
||||
echo -e "${BL}╚═══════════════════════════════════════════════╝${CL}"
|
||||
echo -e
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
# MAIN EXECUTION
|
||||
# ==========================================================
|
||||
|
||||
header_info
|
||||
echo -e "\n Loading..."
|
||||
sleep 1
|
||||
|
||||
|
||||
|
||||
|
||||
function start_vm_configuration() {
|
||||
|
||||
if (whiptail --title "ProxMenux" --yesno "$(translate "Use Default Settings?")" --no-button "$(translate "Advanced")" 10 60); then
|
||||
header_info
|
||||
load_default_vm_config "$OS_TYPE"
|
||||
|
||||
if [[ -z "$HN" ]]; then
|
||||
HN=$(whiptail --inputbox "$(translate "Enter a name for the new virtual machine:")" 10 60 --title "VM Hostname" 3>&1 1>&2 2>&3)
|
||||
[[ -z "$HN" ]] && HN="custom-vm"
|
||||
fi
|
||||
|
||||
apply_default_vm_config
|
||||
else
|
||||
header_info
|
||||
echo -e "${CUS}$(translate "Using advanced configuration")${CL}"
|
||||
configure_vm_advanced "$OS_TYPE"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
while true; do
|
||||
OS_TYPE=$(dialog --backtitle "ProxMenux" \
|
||||
--title "$(translate "Select System Type")" \
|
||||
--menu "\n$(translate "Choose the type of virtual system to install:")" 18 70 10 \
|
||||
1 "$(translate "Create") VM System NAS" \
|
||||
2 "$(translate "Create") VM System Windows" \
|
||||
3 "$(translate "Create") VM System Linux" \
|
||||
4 "$(translate "Create") VM System macOS (OSX-PROXMOX)" \
|
||||
5 "$(translate "Create") VM System Others (based Linux)" \
|
||||
6 "$(translate "Return to Main Menu")" \
|
||||
3>&1 1>&2 2>&3)
|
||||
|
||||
|
||||
[[ $? -ne 0 || "$OS_TYPE" == "5" ]] && exec bash "$MENU_REPO/main_menu.sh"
|
||||
|
||||
case "$OS_TYPE" in
|
||||
1)
|
||||
source "$ISO_REPO/select_nas_iso.sh" && select_nas_iso || continue
|
||||
;;
|
||||
2)
|
||||
source "$ISO_REPO/select_windows_iso.sh" && select_windows_iso || continue
|
||||
;;
|
||||
3)
|
||||
source "$ISO_REPO/select_linux_iso.sh" && select_linux_iso || continue
|
||||
;;
|
||||
4)
|
||||
whiptail --title "OSX-PROXMOX" --yesno "$(translate "This is an external script that creates a macOS VM in Proxmox VE in just a few steps, whether you are using AMD or Intel hardware.")\n\n$(translate "The script clones the osx-proxmox.com repository and once the setup is complete, the server will automatically reboot.")\n\n$(translate "Make sure there are no critical services running as they will be interrupted. Ensure your server can be safely rebooted.")\n\n$(translate "Visit https://osx-proxmox.com for more information.")\n\n$(translate "Do you want to run the script now?")" 20 70
|
||||
if [[ $? -eq 0 ]]; then
|
||||
bash -c "$(curl -fsSL https://install.osx-proxmox.com)"
|
||||
fi
|
||||
continue
|
||||
;;
|
||||
5)
|
||||
source "$ISO_REPO/select_linux_iso.sh" && select_linux_other_scripts || continue
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
if ! confirm_vm_creation; then
|
||||
continue
|
||||
fi
|
||||
|
||||
|
||||
start_vm_configuration || continue
|
||||
|
||||
|
||||
select_disk_type
|
||||
if [[ -z "$DISK_TYPE" ]]; then
|
||||
msg_error "$(translate "Disk type selection failed or cancelled")"
|
||||
continue
|
||||
fi
|
||||
|
||||
create_vm
|
||||
break
|
||||
done
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function start_vm_configuration() {
|
||||
|
||||
if (whiptail --title "ProxMenux" --yesno "$(translate "Use Default Settings?")" --no-button "$(translate "Advanced")" 10 60); then
|
||||
header_info
|
||||
load_default_vm_config "$OS_TYPE"
|
||||
|
||||
if [[ -z "$HN" ]]; then
|
||||
HN=$(whiptail --inputbox "$(translate "Enter a name for the new virtual machine:")" 10 60 --title "VM Hostname" 3>&1 1>&2 2>&3)
|
||||
[[ -z "$HN" ]] && HN="custom-vm"
|
||||
fi
|
||||
|
||||
apply_default_vm_config
|
||||
else
|
||||
header_info
|
||||
echo -e "${CUS}$(translate "Using advanced configuration")${CL}"
|
||||
configure_vm_advanced "$OS_TYPE"
|
||||
fi
|
||||
}
|
||||
@@ -24,132 +24,169 @@
|
||||
# consistent and maintainable way, using ProxMenux standards.
|
||||
# ==========================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
LOCAL_SCRIPTS_LOCAL="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
LOCAL_SCRIPTS_DEFAULT="/usr/local/share/proxmenux/scripts"
|
||||
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
LOCAL_SCRIPTS="$LOCAL_SCRIPTS_DEFAULT"
|
||||
UTILS_FILE="$LOCAL_SCRIPTS/utils.sh"
|
||||
if [[ -f "$LOCAL_SCRIPTS_LOCAL/utils.sh" ]]; then
|
||||
LOCAL_SCRIPTS="$LOCAL_SCRIPTS_LOCAL"
|
||||
UTILS_FILE="$LOCAL_SCRIPTS/utils.sh"
|
||||
elif [[ ! -f "$UTILS_FILE" ]]; then
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
fi
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
if [[ -f "$LOCAL_SCRIPTS_LOCAL/global/vm_storage_helpers.sh" ]]; then
|
||||
source "$LOCAL_SCRIPTS_LOCAL/global/vm_storage_helpers.sh"
|
||||
elif [[ -f "$LOCAL_SCRIPTS_DEFAULT/global/vm_storage_helpers.sh" ]]; then
|
||||
source "$LOCAL_SCRIPTS_DEFAULT/global/vm_storage_helpers.sh"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
VIRTUAL_DISKS=()
|
||||
IMPORT_DISKS=()
|
||||
CONTROLLER_NVME_PCIS=()
|
||||
PASSTHROUGH_DISKS=()
|
||||
|
||||
function _build_storage_plan_summary() {
|
||||
local virtual_count="${#VIRTUAL_DISKS[@]}"
|
||||
local import_count="${#IMPORT_DISKS[@]}"
|
||||
local controller_count="${#CONTROLLER_NVME_PCIS[@]}"
|
||||
local separator
|
||||
local summary
|
||||
separator="$(printf '%*s' 70 '' | tr ' ' '-')"
|
||||
summary="$(translate "Current selection:")\n"
|
||||
summary+=" - $(translate "Virtual disks"): $virtual_count\n"
|
||||
summary+=" - $(translate "Import disks"): $import_count\n"
|
||||
summary+=" - $(translate "Controllers + NVMe"): $controller_count\n"
|
||||
summary+="${separator}\n\n"
|
||||
echo -e "$summary"
|
||||
}
|
||||
|
||||
function select_disk_type() {
|
||||
DISK_TYPE=$(whiptail --backtitle "ProxMenux" --title "DISK TYPE" --menu "$(translate "Choose disk type:")" 12 58 2 \
|
||||
"virtual" "$(translate "Create virtual disk")" \
|
||||
"passthrough" "$(translate "Use physical disk passthrough")" \
|
||||
--ok-button "Select" --cancel-button "Cancel" 3>&1 1>&2 2>&3)
|
||||
VIRTUAL_DISKS=()
|
||||
IMPORT_DISKS=()
|
||||
CONTROLLER_NVME_PCIS=()
|
||||
|
||||
[[ -z "$DISK_TYPE" ]] && return 1
|
||||
while true; do
|
||||
local choice
|
||||
choice=$(whiptail --backtitle "ProxMenux" --title "STORAGE PLAN" --menu "$(_build_storage_plan_summary)" 18 78 5 \
|
||||
"1" "$(translate "Add virtual disk")" \
|
||||
"2" "$(translate "Add import disk")" \
|
||||
"3" "$(translate "Add Controller or NVMe (PCI passthrough)")" \
|
||||
"r" "$(translate "Reset current storage selection")" \
|
||||
"d" "$(translate "[ Finish and continue ]")" \
|
||||
--ok-button "Select" --cancel-button "Cancel" 3>&1 1>&2 2>&3) || return 1
|
||||
|
||||
if [[ "$DISK_TYPE" == "virtual" ]]; then
|
||||
select_virtual_disk
|
||||
else
|
||||
select_passthrough_disk
|
||||
fi
|
||||
case "$choice" in
|
||||
1)
|
||||
select_virtual_disk
|
||||
;;
|
||||
2)
|
||||
select_import_disk
|
||||
;;
|
||||
3)
|
||||
select_controller_nvme
|
||||
;;
|
||||
r)
|
||||
VIRTUAL_DISKS=()
|
||||
IMPORT_DISKS=()
|
||||
CONTROLLER_NVME_PCIS=()
|
||||
;;
|
||||
d|done)
|
||||
if [[ ${#VIRTUAL_DISKS[@]} -eq 0 && ${#IMPORT_DISKS[@]} -eq 0 && ${#CONTROLLER_NVME_PCIS[@]} -eq 0 ]]; then
|
||||
continue
|
||||
fi
|
||||
if [[ ${#VIRTUAL_DISKS[@]} -gt 0 ]]; then
|
||||
msg_ok "$(translate "Virtual Disks Created:")"
|
||||
for i in "${!VIRTUAL_DISKS[@]}"; do
|
||||
echo -e "${TAB}${BL}- $(translate "Disk") $((i+1)): ${VIRTUAL_DISKS[$i]}GB${CL}"
|
||||
done
|
||||
fi
|
||||
if [[ ${#IMPORT_DISKS[@]} -gt 0 ]]; then
|
||||
msg_ok "$(translate "Import Disks Selected:")"
|
||||
for i in "${!IMPORT_DISKS[@]}"; do
|
||||
local disk_info
|
||||
disk_info=$(lsblk -ndo MODEL,SIZE "${IMPORT_DISKS[$i]}" 2>/dev/null | xargs)
|
||||
echo -e "${TAB}${BL}- $(translate "Disk") $((i+1)): ${IMPORT_DISKS[$i]}${disk_info:+ ($disk_info)}${CL}"
|
||||
done
|
||||
fi
|
||||
if [[ ${#CONTROLLER_NVME_PCIS[@]} -gt 0 ]]; then
|
||||
msg_ok "$(translate "Controllers + NVMe Selected:")"
|
||||
for i in "${!CONTROLLER_NVME_PCIS[@]}"; do
|
||||
local pci_info
|
||||
pci_info=$(lspci -nn -s "${CONTROLLER_NVME_PCIS[$i]#0000:}" 2>/dev/null | sed 's/^[^ ]* //')
|
||||
echo -e "${TAB}${BL}- $(translate "Controller") $((i+1)): ${CONTROLLER_NVME_PCIS[$i]}${pci_info:+ ($pci_info)}${CL}"
|
||||
done
|
||||
fi
|
||||
PASSTHROUGH_DISKS=("${IMPORT_DISKS[@]}")
|
||||
DISK_TYPE="mixed"
|
||||
export DISK_TYPE VIRTUAL_DISKS IMPORT_DISKS CONTROLLER_NVME_PCIS PASSTHROUGH_DISKS
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
# Select Virtual Disks
|
||||
# ==========================================================
|
||||
function select_virtual_disk() {
|
||||
|
||||
VIRTUAL_DISKS=()
|
||||
|
||||
|
||||
local add_more_disks=true
|
||||
while $add_more_disks; do
|
||||
|
||||
msg_info "Detecting available storage volumes..."
|
||||
|
||||
local STORAGE_MENU=()
|
||||
local TAG TYPE FREE ITEM
|
||||
while read -r line; do
|
||||
TAG=$(echo $line | awk '{print $1}')
|
||||
TYPE=$(echo $line | awk '{print $2}')
|
||||
FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format "%.2f" | awk '{printf( "%9sB", $6)}')
|
||||
ITEM=$(printf "%-15s %-10s %-15s" "$TAG" "$TYPE" "$FREE")
|
||||
STORAGE_MENU+=("$TAG" "$ITEM" "OFF")
|
||||
done < <(pvesm status -content images | awk 'NR>1')
|
||||
|
||||
STORAGE_MENU=()
|
||||
while read -r line; do
|
||||
TAG=$(echo $line | awk '{print $1}')
|
||||
TYPE=$(echo $line | awk '{print $2}')
|
||||
FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format "%.2f" | awk '{printf( "%9sB", $6)}')
|
||||
ITEM=$(printf "%-15s %-10s %-15s" "$TAG" "$TYPE" "$FREE")
|
||||
STORAGE_MENU+=("$TAG" "$ITEM" "OFF")
|
||||
done < <(pvesm status -content images | awk 'NR>1')
|
||||
|
||||
|
||||
VALID=$(pvesm status -content images | awk 'NR>1')
|
||||
if [ -z "$VALID" ]; then
|
||||
msg_error "Unable to detect a valid storage location."
|
||||
sleep 2
|
||||
select_disk_type
|
||||
fi
|
||||
|
||||
|
||||
|
||||
if [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then
|
||||
STORAGE=${STORAGE_MENU[0]}
|
||||
msg_ok "Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location."
|
||||
else
|
||||
|
||||
kill $SPINNER_PID > /dev/null
|
||||
STORAGE=$(whiptail --backtitle "ProxMenuX" --title "$(translate "Select Storage Volume")" --radiolist \
|
||||
"$(translate "Choose the storage volume for the virtual disk:\n")" 20 78 10 \
|
||||
"${STORAGE_MENU[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ $? -ne 0 ] || [ -z "$STORAGE" ]; then
|
||||
if [ ${#VIRTUAL_DISKS[@]} -eq 0 ]; then
|
||||
msg_error "No storage selected. At least one disk is required."
|
||||
select_disk_type
|
||||
else
|
||||
add_more_disks=false
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
fi
|
||||
|
||||
|
||||
DISK_SIZE=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "System Disk Size (GB)")" 8 58 32 --title "VIRTUAL DISK" --cancel-button Cancel 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
if [ ${#VIRTUAL_DISKS[@]} -eq 0 ]; then
|
||||
msg_error "Disk size not specified. At least one disk is required."
|
||||
sleep 2
|
||||
select_disk_type
|
||||
|
||||
else
|
||||
add_more_disks=false
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$DISK_SIZE" ]; then
|
||||
DISK_SIZE="32"
|
||||
fi
|
||||
|
||||
|
||||
VIRTUAL_DISKS+=("${STORAGE}:${DISK_SIZE}")
|
||||
|
||||
|
||||
|
||||
if ! whiptail --backtitle "ProxMenuX" --title "$(translate "Add Another Disk")" \
|
||||
--yesno "$(translate "Do you want to add another virtual disk?")" 8 58; then
|
||||
add_more_disks=false
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
if [ ${#VIRTUAL_DISKS[@]} -gt 0 ]; then
|
||||
|
||||
msg_ok "Virtual Disks Created:"
|
||||
for i in "${!VIRTUAL_DISKS[@]}"; do
|
||||
echo -e "${TAB}${BL}- Disk $((i+1)): ${VIRTUAL_DISKS[$i]}GB${CL}"
|
||||
done
|
||||
local VALID
|
||||
VALID=$(pvesm status -content images | awk 'NR>1')
|
||||
if [ -z "$VALID" ]; then
|
||||
msg_error "Unable to detect a valid storage location."
|
||||
return 1
|
||||
fi
|
||||
|
||||
local STORAGE
|
||||
if [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then
|
||||
STORAGE=${STORAGE_MENU[0]}
|
||||
else
|
||||
[[ -n "${SPINNER_PID:-}" ]] && kill "$SPINNER_PID" >/dev/null 2>&1
|
||||
STORAGE=$(whiptail --backtitle "ProxMenuX" --title "$(translate "Select Storage Volume")" --radiolist \
|
||||
"$(translate "Choose the storage volume for the virtual disk:\n")" 20 78 10 \
|
||||
"${STORAGE_MENU[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ $? -ne 0 ] || [ -z "$STORAGE" ]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
local DISK_SIZE
|
||||
cleanup
|
||||
DISK_SIZE=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "System Disk Size (GB)")" 8 58 32 --title "VIRTUAL DISK" --cancel-button Cancel 3>&1 1>&2 2>&3)
|
||||
if [ $? -ne 0 ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -z "$DISK_SIZE" ]; then
|
||||
DISK_SIZE="32"
|
||||
fi
|
||||
VIRTUAL_DISKS+=("${STORAGE}:${DISK_SIZE}")
|
||||
|
||||
export VIRTUAL_DISKS
|
||||
|
||||
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
@@ -160,138 +197,164 @@ function select_virtual_disk() {
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Select Physical Disks
|
||||
# Select Import Disks
|
||||
# ==========================================================
|
||||
function select_passthrough_disk() {
|
||||
|
||||
function select_import_disk() {
|
||||
msg_info "$(translate "Detecting available disks...")"
|
||||
|
||||
FREE_DISKS=()
|
||||
|
||||
USED_DISKS=$(lsblk -n -o PKNAME,TYPE | grep 'lvm' | awk '{print "/dev/" $1}')
|
||||
MOUNTED_DISKS=$(lsblk -ln -o NAME,MOUNTPOINT | awk '$2!="" {print "/dev/" $1}')
|
||||
|
||||
ZFS_DISKS=""
|
||||
ZFS_RAW=$(zpool list -v -H 2>/dev/null | awk '{print $1}' | grep -v '^NAME$' | grep -v '^-' | grep -v '^mirror')
|
||||
|
||||
for entry in $ZFS_RAW; do
|
||||
path=""
|
||||
if [[ "$entry" == wwn-* || "$entry" == ata-* ]]; then
|
||||
if [ -e "/dev/disk/by-id/$entry" ]; then
|
||||
path=$(readlink -f "/dev/disk/by-id/$entry")
|
||||
fi
|
||||
elif [[ "$entry" == /dev/* ]]; then
|
||||
path="$entry"
|
||||
fi
|
||||
|
||||
if [ -n "$path" ]; then
|
||||
base_disk=$(lsblk -no PKNAME "$path" 2>/dev/null)
|
||||
if [ -n "$base_disk" ]; then
|
||||
ZFS_DISKS+="/dev/$base_disk"$'\n'
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
ZFS_DISKS=$(echo "$ZFS_DISKS" | sort -u)
|
||||
LVM_DEVICES=$(pvs --noheadings -o pv_name 2> >(grep -v 'File descriptor .* leaked') | xargs -n1 readlink -f | sort -u)
|
||||
|
||||
RAID_ACTIVE=$(grep -Po 'md\d+\s*:\s*active\s+raid[0-9]+' /proc/mdstat | awk '{print $1}' | sort -u)
|
||||
|
||||
_refresh_host_storage_cache
|
||||
local FREE_DISKS=()
|
||||
local DISK INFO MODEL SIZE LABEL DESCRIPTION
|
||||
while read -r DISK; do
|
||||
[[ "$DISK" =~ /dev/zd ]] && continue
|
||||
[[ "$DISK" =~ /dev/zd ]] && continue
|
||||
if _disk_is_host_system_used "$DISK"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
INFO=($(lsblk -dn -o MODEL,SIZE "$DISK"))
|
||||
MODEL="${INFO[@]::${#INFO[@]}-1}"
|
||||
SIZE="${INFO[-1]}"
|
||||
LABEL=""
|
||||
SHOW_DISK=true
|
||||
INFO=($(lsblk -dn -o MODEL,SIZE "$DISK"))
|
||||
MODEL="${INFO[@]::${#INFO[@]}-1}"
|
||||
SIZE="${INFO[-1]}"
|
||||
LABEL=""
|
||||
|
||||
IS_MOUNTED=false
|
||||
IS_RAID=false
|
||||
IS_ZFS=false
|
||||
IS_LVM=false
|
||||
if _disk_used_in_guest_configs "$DISK"; then
|
||||
LABEL+=" [⚠ $(translate "In use by VM/LXC config")]"
|
||||
fi
|
||||
|
||||
while read -r part fstype; do
|
||||
[[ "$fstype" == "zfs_member" ]] && IS_ZFS=true
|
||||
[[ "$fstype" == "linux_raid_member" ]] && IS_RAID=true
|
||||
[[ "$fstype" == "LVM2_member" ]] && IS_LVM=true
|
||||
if grep -q "/dev/$part" <<< "$MOUNTED_DISKS"; then
|
||||
IS_MOUNTED=true
|
||||
fi
|
||||
done < <(lsblk -ln -o NAME,FSTYPE "$DISK" | tail -n +2)
|
||||
|
||||
REAL_PATH=$(readlink -f "$DISK")
|
||||
if echo "$LVM_DEVICES" | grep -qFx "$REAL_PATH"; then
|
||||
IS_MOUNTED=true
|
||||
fi
|
||||
|
||||
USED_BY=""
|
||||
REAL_PATH=$(readlink -f "$DISK")
|
||||
CONFIG_DATA=$(cat /etc/pve/qemu-server/*.conf /etc/pve/lxc/*.conf 2>/dev/null)
|
||||
|
||||
if grep -Fq "$REAL_PATH" <<< "$CONFIG_DATA"; then
|
||||
USED_BY="⚠ $(translate "In use")"
|
||||
else
|
||||
for SYMLINK in /dev/disk/by-id/*; do
|
||||
if [[ "$(readlink -f "$SYMLINK")" == "$REAL_PATH" ]]; then
|
||||
if grep -Fq "$SYMLINK" <<< "$CONFIG_DATA"; then
|
||||
USED_BY="⚠ $(translate "In use")"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if $IS_RAID && grep -q "$DISK" <<< "$(cat /proc/mdstat)" && grep -q "active raid" /proc/mdstat; then
|
||||
SHOW_DISK=false
|
||||
fi
|
||||
|
||||
if $IS_ZFS || $IS_MOUNTED || [[ "$ZFS_DISKS" == *"$DISK"* ]]; then
|
||||
SHOW_DISK=false
|
||||
fi
|
||||
|
||||
if $SHOW_DISK; then
|
||||
[[ -n "$USED_BY" ]] && LABEL+=" [$USED_BY]"
|
||||
[[ "$IS_RAID" == true ]] && LABEL+=" ⚠ RAID"
|
||||
[[ "$IS_LVM" == true ]] && LABEL+=" ⚠ LVM"
|
||||
[[ "$IS_ZFS" == true ]] && LABEL+=" ⚠ ZFS"
|
||||
DESCRIPTION=$(printf "%-30s %10s%s" "$MODEL" "$SIZE" "$LABEL")
|
||||
FREE_DISKS+=("$DISK" "$DESCRIPTION" "OFF")
|
||||
fi
|
||||
DESCRIPTION=$(printf "%-30s %10s%s" "$MODEL" "$SIZE" "$LABEL")
|
||||
if _array_contains "$DISK" "${IMPORT_DISKS[@]}"; then
|
||||
FREE_DISKS+=("$DISK" "$DESCRIPTION" "ON")
|
||||
else
|
||||
FREE_DISKS+=("$DISK" "$DESCRIPTION" "OFF")
|
||||
fi
|
||||
done < <(lsblk -dn -e 7,11 -o PATH)
|
||||
|
||||
|
||||
if [ "${#FREE_DISKS[@]}" -eq 0 ]; then
|
||||
if [[ "${#FREE_DISKS[@]}" -eq 0 ]]; then
|
||||
cleanup
|
||||
whiptail --title "Error" --msgbox "$(translate "No disks available for this VM.")" 8 40
|
||||
select_disk_type
|
||||
return
|
||||
whiptail --title "Error" --msgbox "$(translate "No importable disks available. System disks and protected disks are hidden.")" 9 70
|
||||
return 1
|
||||
fi
|
||||
|
||||
local MAX_WIDTH TOTAL_WIDTH SELECTED_DISKS
|
||||
MAX_WIDTH=$(printf "%s\n" "${FREE_DISKS[@]}" | awk '{print length}' | sort -nr | head -n1)
|
||||
TOTAL_WIDTH=$((MAX_WIDTH + 20))
|
||||
[ $TOTAL_WIDTH -lt 50 ] && TOTAL_WIDTH=50
|
||||
TOTAL_WIDTH=$((MAX_WIDTH + 20))
|
||||
[[ $TOTAL_WIDTH -lt 50 ]] && TOTAL_WIDTH=50
|
||||
cleanup
|
||||
SELECTED_DISKS=$(whiptail --title "Select Disks" --checklist \
|
||||
"$(translate "Select the disks you want to use (use spacebar to select):")" 20 $TOTAL_WIDTH 10 \
|
||||
SELECTED_DISKS=$(whiptail --title "Select Import Disks" --checklist \
|
||||
"$(translate "Select the disks you want to import (use spacebar to toggle):")" 20 $TOTAL_WIDTH 10 \
|
||||
"${FREE_DISKS[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$SELECTED_DISKS" ]; then
|
||||
msg_error "Disk not specified. At least one disk is required."
|
||||
sleep 2
|
||||
select_disk_type
|
||||
return
|
||||
fi
|
||||
[[ $? -ne 0 ]] && return 1
|
||||
|
||||
|
||||
msg_ok "Disk passthrough selected:"
|
||||
PASSTHROUGH_DISKS=()
|
||||
IMPORT_DISKS=()
|
||||
local DISK_INFO
|
||||
for DISK in $(echo "$SELECTED_DISKS" | tr -d '"'); do
|
||||
DISK_INFO=$(lsblk -ndo MODEL,SIZE "$DISK" | xargs)
|
||||
echo -e "${TAB}${CL}${BL}- $DISK $DISK_INFO${GN}${CL}"
|
||||
PASSTHROUGH_DISKS+=("$DISK")
|
||||
_array_contains "$DISK" "${IMPORT_DISKS[@]}" || IMPORT_DISKS+=("$DISK")
|
||||
done
|
||||
|
||||
|
||||
if [[ ${#IMPORT_DISKS[@]} -eq 0 ]]; then
|
||||
msg_warn "$(translate "No import disks selected for now.")"
|
||||
return 0
|
||||
fi
|
||||
|
||||
export IMPORT_DISKS
|
||||
return 0
|
||||
}
|
||||
# ==========================================================
|
||||
|
||||
function select_passthrough_disk() {
|
||||
select_import_disk
|
||||
}
|
||||
|
||||
function select_controller_nvme() {
|
||||
msg_info "$(translate "Detecting PCI storage controllers and NVMe devices...")"
|
||||
|
||||
_refresh_host_storage_cache
|
||||
|
||||
local menu_items=()
|
||||
local blocked_report=""
|
||||
local pci_path pci_full class_hex name controller_disks controller_desc disk reason safe_count blocked_count state
|
||||
safe_count=0
|
||||
blocked_count=0
|
||||
|
||||
while IFS= read -r pci_path; do
|
||||
pci_full=$(basename "$pci_path")
|
||||
class_hex=$(cat "$pci_path/class" 2>/dev/null | sed 's/^0x//')
|
||||
[[ -z "$class_hex" ]] && continue
|
||||
[[ "${class_hex:0:2}" != "01" ]] && continue
|
||||
|
||||
name=$(lspci -nn -s "${pci_full#0000:}" 2>/dev/null | sed 's/^[^ ]* //')
|
||||
[[ -z "$name" ]] && name="$(translate "Unknown storage controller")"
|
||||
|
||||
controller_disks=()
|
||||
while IFS= read -r disk; do
|
||||
[[ -z "$disk" ]] && continue
|
||||
_array_contains "$disk" "${controller_disks[@]}" || controller_disks+=("$disk")
|
||||
done < <(_controller_block_devices "$pci_full")
|
||||
|
||||
reason=""
|
||||
for disk in "${controller_disks[@]}"; do
|
||||
if _disk_is_host_system_used "$disk"; then
|
||||
reason+="${disk} (${DISK_USAGE_REASON}); "
|
||||
elif _disk_used_in_guest_configs "$disk"; then
|
||||
reason+="${disk} ($(translate "In use by VM/LXC config")); "
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -n "$reason" ]]; then
|
||||
blocked_count=$((blocked_count + 1))
|
||||
blocked_report+=" • ${pci_full} — ${name}\n $(translate "Blocked because protected/in-use disks are attached"): ${reason}\n"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ ${#controller_disks[@]} -gt 0 ]]; then
|
||||
controller_desc="$(printf "%-50s [%s]" "$name" "$(IFS=,; echo "${controller_disks[*]}")")"
|
||||
else
|
||||
controller_desc="$(printf "%-50s [%s]" "$name" "$(translate "No attached disks detected")")"
|
||||
fi
|
||||
|
||||
if _array_contains "$pci_full" "${CONTROLLER_NVME_PCIS[@]}"; then
|
||||
state="ON"
|
||||
else
|
||||
state="OFF"
|
||||
fi
|
||||
|
||||
menu_items+=("$pci_full" "$controller_desc" "$state")
|
||||
safe_count=$((safe_count + 1))
|
||||
done < <(ls -d /sys/bus/pci/devices/* 2>/dev/null | sort)
|
||||
|
||||
stop_spinner
|
||||
if [[ $safe_count -eq 0 ]]; then
|
||||
local msg
|
||||
msg="$(translate "No safe controllers/NVMe devices are available for passthrough.")\n\n"
|
||||
if [[ $blocked_count -gt 0 ]]; then
|
||||
msg+="$(translate "Detected controllers blocked for safety:")\n\n${blocked_report}"
|
||||
fi
|
||||
whiptail --title "Controller + NVMe" --msgbox "$msg" 20 90
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ $blocked_count -gt 0 ]]; then
|
||||
whiptail --title "Controller + NVMe" --msgbox "$(translate "Some controllers were hidden because they have host system disks attached.")\n\n${blocked_report}" 20 90
|
||||
fi
|
||||
|
||||
local selected
|
||||
selected=$(whiptail --title "Controller + NVMe" --checklist \
|
||||
"$(translate "Select controllers/NVMe to passthrough (safe devices only):")" 20 90 10 \
|
||||
"${menu_items[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
[[ $? -ne 0 ]] && return 1
|
||||
|
||||
CONTROLLER_NVME_PCIS=()
|
||||
local pci
|
||||
for pci in $(echo "$selected" | tr -d '"'); do
|
||||
_array_contains "$pci" "${CONTROLLER_NVME_PCIS[@]}" || CONTROLLER_NVME_PCIS+=("$pci")
|
||||
done
|
||||
|
||||
if [[ ${#CONTROLLER_NVME_PCIS[@]} -eq 0 ]]; then
|
||||
msg_warn "$(translate "No Controller/NVMe selected for now.")"
|
||||
return 0
|
||||
fi
|
||||
|
||||
export CONTROLLER_NVME_PCIS
|
||||
return 0
|
||||
}
|
||||
# ==========================================================
|
||||
|
||||
@@ -101,27 +101,27 @@ function select_linux_iso() {
|
||||
|
||||
function select_linux_iso_official() {
|
||||
DISTROS=(
|
||||
"Ubuntu 25.04|Desktop|ProxMenux|https://releases.ubuntu.com/25.04/ubuntu-25.04-desktop-amd64.iso"
|
||||
"Ubuntu 24.04|Desktop|ProxMenux|https://releases.ubuntu.com/24.04/ubuntu-24.04.2-desktop-amd64.iso"
|
||||
"Ubuntu 25.10|Desktop|ProxMenux|https://releases.ubuntu.com/25.10/ubuntu-25.10-desktop-amd64.iso"
|
||||
"Ubuntu 24.04|Desktop|ProxMenux|https://releases.ubuntu.com/24.04/ubuntu-24.04.4-desktop-amd64.iso"
|
||||
"Ubuntu 22.04|Desktop|ProxMenux|https://releases.ubuntu.com/22.04/ubuntu-22.04.5-desktop-amd64.iso"
|
||||
"Ubuntu 20.04|Desktop|ProxMenux|https://releases.ubuntu.com/20.04/ubuntu-20.04.6-desktop-amd64.iso"
|
||||
"Ubuntu 25.04 Server|CLI|ProxMenux|https://releases.ubuntu.com/25.04/ubuntu-25.04-live-server-amd64.iso"
|
||||
"Ubuntu 24.04 Server|CLI|ProxMenux|https://releases.ubuntu.com/24.04/ubuntu-24.04.2-live-server-amd64.iso"
|
||||
"Ubuntu 25.10 Server|CLI|ProxMenux|https://releases.ubuntu.com/25.10/ubuntu-25.10-live-server-amd64.iso"
|
||||
"Ubuntu 24.04 Server|CLI|ProxMenux|https://releases.ubuntu.com/24.04/ubuntu-24.04.4-live-server-amd64.iso"
|
||||
"Ubuntu 22.04 Server|CLI|ProxMenux|https://releases.ubuntu.com/22.04/ubuntu-22.04.5-live-server-amd64.iso"
|
||||
"Ubuntu 20.04 Server|CLI|ProxMenux|https://releases.ubuntu.com/20.04/ubuntu-20.04.6-live-server-amd64.iso"
|
||||
"Debian 13|Desktop|ProxMenux|https://cdimage.debian.org/debian-cd/current/amd64/iso-dvd/debian-13.0.0-amd64-DVD-1.iso"
|
||||
"Debian 12|Desktop|ProxMenux|https://cdimage.debian.org/debian-cd/current/amd64/iso-dvd/debian-12.10.0-amd64-DVD-1.iso"
|
||||
"Debian 13|Desktop|ProxMenux|https://cdimage.debian.org/debian-cd/current/amd64/iso-dvd/debian-13.4.0-amd64-DVD-1.iso"
|
||||
"Debian 12|Desktop|ProxMenux|https://cdimage.debian.org/cdimage/archive/12.13.0/amd64/iso-dvd/debian-12.13.0-amd64-DVD-1.iso"
|
||||
"Debian 11|Desktop|ProxMenux|https://cdimage.debian.org/cdimage/archive/11.11.0/amd64/iso-dvd/debian-11.11.0-amd64-DVD-1.iso"
|
||||
"Debian 13 Netinst|CLI|ProxMenux|https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-13.0.0-amd64-netinst.iso"
|
||||
"Debian 12 Netinst|CLI|ProxMenux|https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-12.10.0-amd64-netinst.iso"
|
||||
"Debian 13 Netinst|CLI|ProxMenux|https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-13.4.0-amd64-netinst.iso"
|
||||
"Debian 12 Netinst|CLI|ProxMenux|https://cdimage.debian.org/cdimage/archive/12.13.0/amd64/iso-cd/debian-12.13.0-amd64-netinst.iso"
|
||||
"Debian 11 Netinst|CLI|ProxMenux|https://cdimage.debian.org/cdimage/archive/11.11.0/amd64/iso-cd/debian-11.11.0-amd64-netinst.iso"
|
||||
"Fedora Workstation 42|Desktop|ProxMenux|https://download.fedoraproject.org/pub/fedora/linux/releases/42/Workstation/x86_64/iso/Fedora-Workstation-Live-42-1.1.x86_64.iso"
|
||||
"Arch Linux|CLI|ProxMenux|https://geo.mirror.pkgbuild.com/iso/2025.07.01/archlinux-2025.07.01-x86_64.iso"
|
||||
"Rocky Linux 9.5|Desktop|ProxMenux|https://download.rockylinux.org/pub/rocky/9/isos/x86_64/Rocky-9.5-x86_64-dvd.iso"
|
||||
"Arch Linux|CLI|ProxMenux|https://geo.mirror.pkgbuild.com/iso/latest/archlinux-x86_64.iso"
|
||||
"Rocky Linux 9|Desktop|ProxMenux|https://download.rockylinux.org/pub/rocky/9/isos/x86_64/Rocky-9-latest-x86_64-dvd.iso"
|
||||
"Linux Mint 22.1|Desktop|ProxMenux|https://mirrors.edge.kernel.org/linuxmint/stable/22.1/linuxmint-22.1-cinnamon-64bit.iso"
|
||||
"openSUSE Leap 15.6|Desktop|ProxMenux|https://download.opensuse.org/distribution/leap/15.6/iso/openSUSE-Leap-15.6-DVD-x86_64-Media.iso"
|
||||
"Alpine Linux 3.21|Desktop|ProxMenux|https://dl-cdn.alpinelinux.org/alpine/v3.21/releases/x86_64/alpine-virt-3.21.3-x86_64.iso"
|
||||
"Kali Linux 2025.1|Desktop|ProxMenux|https://cdimage.kali.org/kali-2025.1c/kali-linux-2025.1c-installer-amd64.iso"
|
||||
"Kali Linux 2026.1|Desktop|ProxMenux|https://cdimage.kali.org/kali-2026.1/kali-linux-2026.1-installer-amd64.iso"
|
||||
"Manjaro 25.0|Desktop|ProxMenux|https://download.manjaro.org/gnome/25.0.0/manjaro-gnome-25.0.0-250414-linux612.iso"
|
||||
)
|
||||
|
||||
@@ -219,7 +219,7 @@ function select_linux_cloudinit() {
|
||||
whiptail --title "Proxmox VE Helper-Scripts" \
|
||||
--msgbox "$(translate "Visit the website to discover more scripts, stay updated with the latest updates, and support the project:\n\nhttps://community-scripts.github.io/ProxmoxVE")" 15 70
|
||||
|
||||
exec bash "$LOCAL_SCRIPTS/vm/create_vm.sh"
|
||||
exec bash "$LOCAL_SCRIPTS/menus/create_vm_menu.sh"
|
||||
}
|
||||
|
||||
|
||||
@@ -311,5 +311,3 @@ return 1
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 07/05/2025
|
||||
# Last Updated: 04/04/2026
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script is part of the central ProxMenux VM creation module. It allows users
|
||||
@@ -24,11 +24,20 @@
|
||||
# consistent and maintainable way, using ProxMenux standards.
|
||||
# ==========================================================
|
||||
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
LOCAL_SCRIPTS_LOCAL="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
LOCAL_SCRIPTS_DEFAULT="/usr/local/share/proxmenux/scripts"
|
||||
LOCAL_SCRIPTS="$LOCAL_SCRIPTS_DEFAULT"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
UTILS_FILE="$LOCAL_SCRIPTS/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
|
||||
|
||||
if [[ -f "$LOCAL_SCRIPTS_LOCAL/utils.sh" ]]; then
|
||||
LOCAL_SCRIPTS="$LOCAL_SCRIPTS_LOCAL"
|
||||
UTILS_FILE="$LOCAL_SCRIPTS/utils.sh"
|
||||
elif [[ ! -f "$UTILS_FILE" ]]; then
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
fi
|
||||
|
||||
[[ -f "$UTILS_FILE" ]] && source "$UTILS_FILE"
|
||||
load_language
|
||||
@@ -37,6 +46,138 @@ initialize_cache
|
||||
ISO_DIR="/var/lib/vz/template/iso"
|
||||
mkdir -p "$ISO_DIR"
|
||||
|
||||
function _has_curl() {
|
||||
command -v curl >/dev/null 2>&1
|
||||
}
|
||||
|
||||
function _latest_version_from_lines() {
|
||||
awk 'NF' | sort -V | tail -n 1
|
||||
}
|
||||
|
||||
function resolve_truenas_scale_iso() {
|
||||
local default_ver="25.10.2.1"
|
||||
local base_url="https://download.sys.truenas.net/TrueNAS-SCALE-Goldeye"
|
||||
local detected_ver=""
|
||||
|
||||
if _has_curl; then
|
||||
detected_ver=$(
|
||||
curl -fsSL "${base_url}/" 2>/dev/null \
|
||||
| grep -Eo '>[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?/' \
|
||||
| tr -d '>/' \
|
||||
| _latest_version_from_lines
|
||||
)
|
||||
fi
|
||||
|
||||
[[ -z "$detected_ver" ]] && detected_ver="$default_ver"
|
||||
|
||||
ISO_NAME="TrueNAS SCALE ${detected_ver} (Goldeye)"
|
||||
ISO_FILE="TrueNAS-SCALE-${detected_ver}.iso"
|
||||
ISO_URL="${base_url}/${detected_ver}/${ISO_FILE}"
|
||||
ISO_PATH="$ISO_DIR/$ISO_FILE"
|
||||
HN="TrueNAS-Scale"
|
||||
}
|
||||
|
||||
function resolve_truenas_core_iso() {
|
||||
local default_file="TrueNAS-13.3-U1.2.iso"
|
||||
local detected_file=""
|
||||
local base_url="https://download.freenas.org/13.3/STABLE/latest/x64"
|
||||
|
||||
if _has_curl; then
|
||||
detected_file=$(
|
||||
curl -fsSL "${base_url}/" 2>/dev/null \
|
||||
| grep -Eo 'TrueNAS-13\.3-[^"]+\.iso' \
|
||||
| head -n 1
|
||||
)
|
||||
fi
|
||||
|
||||
[[ -z "$detected_file" ]] && detected_file="$default_file"
|
||||
|
||||
ISO_NAME="TrueNAS CORE 13.3"
|
||||
ISO_FILE="$detected_file"
|
||||
ISO_URL="${base_url}/${ISO_FILE}"
|
||||
ISO_PATH="$ISO_DIR/$ISO_FILE"
|
||||
HN="TrueNAS-Core"
|
||||
}
|
||||
|
||||
function resolve_omv_iso() {
|
||||
local default_ver="8.1.1"
|
||||
local detected_ver=""
|
||||
|
||||
if _has_curl; then
|
||||
detected_ver=$(
|
||||
curl -fsSL "https://sourceforge.net/projects/openmediavault/files/iso/" 2>/dev/null \
|
||||
| grep -Eo '/projects/openmediavault/files/iso/[0-9]+\.[0-9]+\.[0-9]+/' \
|
||||
| sed -E 's|.*/iso/([0-9]+\.[0-9]+\.[0-9]+)/$|\1|' \
|
||||
| _latest_version_from_lines
|
||||
)
|
||||
fi
|
||||
|
||||
[[ -z "$detected_ver" ]] && detected_ver="$default_ver"
|
||||
|
||||
ISO_NAME="OpenMediaVault ${detected_ver}"
|
||||
ISO_FILE="openmediavault_${detected_ver}-amd64.iso"
|
||||
ISO_URL="https://sourceforge.net/projects/openmediavault/files/iso/${detected_ver}/${ISO_FILE}/download"
|
||||
ISO_PATH="$ISO_DIR/$ISO_FILE"
|
||||
HN="OpenMediaVault"
|
||||
}
|
||||
|
||||
function resolve_xigmanas_iso() {
|
||||
local default_train="14.3.0.5"
|
||||
local default_build="14.3.0.5.10566"
|
||||
local detected_train=""
|
||||
local detected_build=""
|
||||
|
||||
if _has_curl; then
|
||||
detected_train=$(
|
||||
curl -fsSL "https://sourceforge.net/projects/xigmanas/files/" 2>/dev/null \
|
||||
| grep -Eo '/projects/xigmanas/files/XigmaNAS-[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/' \
|
||||
| sed -E 's|.*/XigmaNAS-([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/$|\1|' \
|
||||
| _latest_version_from_lines
|
||||
)
|
||||
fi
|
||||
|
||||
[[ -z "$detected_train" ]] && detected_train="$default_train"
|
||||
|
||||
if _has_curl; then
|
||||
detected_build=$(
|
||||
curl -fsSL "https://sourceforge.net/projects/xigmanas/files/XigmaNAS-${detected_train}/" 2>/dev/null \
|
||||
| grep -Eo "/projects/xigmanas/files/XigmaNAS-${detected_train}/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/" \
|
||||
| sed -E "s|.*/XigmaNAS-${detected_train}/([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/$|\\1|" \
|
||||
| _latest_version_from_lines
|
||||
)
|
||||
fi
|
||||
|
||||
[[ -z "$detected_build" ]] && detected_build="$default_build"
|
||||
|
||||
ISO_NAME="XigmaNAS-${detected_train}"
|
||||
ISO_FILE="XigmaNAS-x64-LiveCD-${detected_build}.iso"
|
||||
ISO_URL="https://sourceforge.net/projects/xigmanas/files/XigmaNAS-${detected_train}/${detected_build}/${ISO_FILE}/download"
|
||||
ISO_PATH="$ISO_DIR/$ISO_FILE"
|
||||
HN="XigmaNAS"
|
||||
}
|
||||
|
||||
function resolve_rockstor_iso() {
|
||||
local default_file="Rockstor-Leap15.6-generic.x86_64-5.0.15-0.install.iso"
|
||||
local detected_file=""
|
||||
local base_url="https://rockstor.com/downloads/installer/leap/15.6/x86_64"
|
||||
|
||||
if _has_curl; then
|
||||
detected_file=$(
|
||||
curl -fsSL "${base_url}/" 2>/dev/null \
|
||||
| grep -Eo 'Rockstor-Leap15\.6-generic\.x86_64-[0-9]+\.[0-9]+\.[0-9]+-[0-9]+\.install\.iso' \
|
||||
| _latest_version_from_lines
|
||||
)
|
||||
fi
|
||||
|
||||
[[ -z "$detected_file" ]] && detected_file="$default_file"
|
||||
|
||||
ISO_NAME="Rockstor"
|
||||
ISO_FILE="$detected_file"
|
||||
ISO_URL="${base_url}/${ISO_FILE}"
|
||||
ISO_PATH="$ISO_DIR/$ISO_FILE"
|
||||
HN="Rockstor"
|
||||
}
|
||||
|
||||
function select_nas_iso() {
|
||||
|
||||
local NAS_OPTIONS=(
|
||||
@@ -68,39 +209,19 @@ function select_nas_iso() {
|
||||
return 1
|
||||
;;
|
||||
2)
|
||||
ISO_NAME="TrueNAS SCALE 25 (Goldeye)"
|
||||
ISO_URL="https://download.sys.truenas.net/TrueNAS-SCALE-Goldeye/25.10.0.1/TrueNAS-SCALE-25.10.0.1.iso"
|
||||
ISO_FILE="TrueNAS-SCALE-25.10.0.1.iso"
|
||||
ISO_PATH="$ISO_DIR/$ISO_FILE"
|
||||
HN="TrueNAS-Scale"
|
||||
resolve_truenas_scale_iso
|
||||
;;
|
||||
3)
|
||||
ISO_NAME="TrueNAS CORE 13.3"
|
||||
ISO_URL="https://download.freenas.org/13.3/STABLE/latest/x64/TrueNAS-13.3-U1.2.iso"
|
||||
ISO_FILE="TrueNAS-13.3-U1.2.iso"
|
||||
ISO_PATH="$ISO_DIR/$ISO_FILE"
|
||||
HN="TrueNAS-Core"
|
||||
resolve_truenas_core_iso
|
||||
;;
|
||||
4)
|
||||
ISO_NAME="OpenMediaVault 7.4.17"
|
||||
ISO_URL="https://sourceforge.net/projects/openmediavault/files/iso/7.4.17/openmediavault_7.4.17-amd64.iso/download"
|
||||
ISO_FILE="openmediavault_7.4.17-amd64.iso"
|
||||
ISO_PATH="$ISO_DIR/$ISO_FILE"
|
||||
HN="OpenMediaVault"
|
||||
resolve_omv_iso
|
||||
;;
|
||||
5)
|
||||
ISO_NAME="XigmaNAS-14.3.0.5"
|
||||
ISO_URL="https://sourceforge.net/projects/xigmanas/files/XigmaNAS-14.3.0.5/14.3.0.5.10566/XigmaNAS-x64-LiveCD-14.3.0.5.10566.iso/download"
|
||||
ISO_FILE="XigmaNAS-x64-LiveCD-14.3.0.5.10566.iso"
|
||||
ISO_PATH="$ISO_DIR/$ISO_FILE"
|
||||
HN="XigmaNAS"
|
||||
resolve_xigmanas_iso
|
||||
;;
|
||||
6)
|
||||
ISO_NAME="Rockstor"
|
||||
ISO_URL="https://rockstor.com/downloads/installer/leap/15.6/x86_64/Rockstor-Leap15.6-generic.x86_64-5.0.15-0.install.iso"
|
||||
ISO_FILE="Rockstor-Leap15.6-generic.x86_64-5.0.15-0.install.iso"
|
||||
ISO_PATH="$ISO_DIR/$ISO_FILE"
|
||||
HN="Rockstor"
|
||||
resolve_rockstor_iso
|
||||
;;
|
||||
7)
|
||||
bash "$LOCAL_SCRIPTS/vm/zimaos.sh"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,15 +24,35 @@
|
||||
# consistent and maintainable way, using ProxMenux standards.
|
||||
# ==========================================================
|
||||
|
||||
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
LOCAL_SCRIPTS_LOCAL="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
LOCAL_SCRIPTS_DEFAULT="/usr/local/share/proxmenux/scripts"
|
||||
LOCAL_SCRIPTS="$LOCAL_SCRIPTS_DEFAULT"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
UTILS_FILE="$LOCAL_SCRIPTS/utils.sh"
|
||||
if [[ -f "$LOCAL_SCRIPTS_LOCAL/utils.sh" ]]; then
|
||||
LOCAL_SCRIPTS="$LOCAL_SCRIPTS_LOCAL"
|
||||
UTILS_FILE="$LOCAL_SCRIPTS/utils.sh"
|
||||
elif [[ ! -f "$UTILS_FILE" ]]; then
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
fi
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
if [[ -f "$LOCAL_SCRIPTS_LOCAL/global/vm_storage_helpers.sh" ]]; then
|
||||
source "$LOCAL_SCRIPTS_LOCAL/global/vm_storage_helpers.sh"
|
||||
elif [[ -f "$LOCAL_SCRIPTS_DEFAULT/global/vm_storage_helpers.sh" ]]; then
|
||||
source "$LOCAL_SCRIPTS_DEFAULT/global/vm_storage_helpers.sh"
|
||||
fi
|
||||
if [[ -f "$LOCAL_SCRIPTS_LOCAL/global/pci_passthrough_helpers.sh" ]]; then
|
||||
source "$LOCAL_SCRIPTS_LOCAL/global/pci_passthrough_helpers.sh"
|
||||
elif [[ -f "$LOCAL_SCRIPTS_DEFAULT/global/pci_passthrough_helpers.sh" ]]; then
|
||||
source "$LOCAL_SCRIPTS_DEFAULT/global/pci_passthrough_helpers.sh"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
@@ -67,7 +87,10 @@ function select_interface_type() {
|
||||
"sata" "$(translate "SATA (standard - high compatibility)")" OFF \
|
||||
"virtio" "$(translate "VirtIO (advanced - high performance)")" OFF \
|
||||
"ide" "IDE (legacy)" OFF \
|
||||
3>&1 1>&2 2>&3) || exit 1
|
||||
3>&1 1>&2 2>&3) || {
|
||||
msg_warn "$(translate "Disk interface selection cancelled.")" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
case "$INTERFACE_TYPE" in
|
||||
"scsi"|"sata")
|
||||
@@ -102,17 +125,21 @@ function select_storage_target() {
|
||||
done < <(pvesm status -content images | awk 'NR>1')
|
||||
|
||||
if [[ ${#STORAGE_MENU[@]} -eq 0 ]]; then
|
||||
msg_error "$(translate "Unable to detect a valid storage location for $PURPOSE disk.")"
|
||||
exit 1
|
||||
msg_error "$(translate "Unable to detect a valid storage location for $PURPOSE disk.")" >&2
|
||||
return 1
|
||||
elif [[ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]]; then
|
||||
STORAGE="${STORAGE_MENU[0]}"
|
||||
else
|
||||
kill $SPINNER_PID > /dev/null
|
||||
[[ -n "${SPINNER_PID:-}" ]] && kill "$SPINNER_PID" >/dev/null 2>&1
|
||||
STORAGE=$(whiptail --backtitle "ProxMenux" --title "$(translate "$PURPOSE Disk Storage")" --radiolist \
|
||||
"$(translate "Choose the storage volume for the $PURPOSE disk (4MB):\n\nUse Spacebar to select.")" 16 70 6 \
|
||||
"${STORAGE_MENU[@]}" 3>&1 1>&2 2>&3) || exit 1
|
||||
"${STORAGE_MENU[@]}" 3>&1 1>&2 2>&3) || {
|
||||
msg_warn "$(translate "$PURPOSE disk storage selection cancelled.")" >&2
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
|
||||
[[ -z "$STORAGE" ]] && return 1
|
||||
echo "$STORAGE"
|
||||
}
|
||||
|
||||
@@ -141,6 +168,35 @@ function configure_guest_agent() {
|
||||
|
||||
}
|
||||
|
||||
function run_gpu_passthrough_wizard() {
|
||||
[[ "${WIZARD_ADD_GPU:-no}" != "yes" ]] && return 0
|
||||
|
||||
local gpu_script="$LOCAL_SCRIPTS/gpu_tpu/add_gpu_vm.sh"
|
||||
local wizard_result_file=""
|
||||
if [[ ! -f "$gpu_script" ]]; then
|
||||
local local_gpu_script
|
||||
local_gpu_script="$(cd "$(dirname "${BASH_SOURCE[0]}")"/.. && pwd)/gpu_tpu/add_gpu_vm.sh"
|
||||
[[ -f "$local_gpu_script" ]] && gpu_script="$local_gpu_script"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$gpu_script" ]]; then
|
||||
msg_warn "$(translate "GPU passthrough assistant not found. You can run it later from Hardware Graphics.")"
|
||||
return 0
|
||||
fi
|
||||
|
||||
msg_info2 "$(translate "Launching GPU passthrough assistant for VM") ${VMID}..."
|
||||
wizard_result_file="/tmp/proxmenux_gpu_wizard_result_${VMID}_$$.txt"
|
||||
: >"$wizard_result_file"
|
||||
bash "$gpu_script" --vmid "$VMID" --wizard --result-file "$wizard_result_file"
|
||||
|
||||
if [[ -s "$wizard_result_file" ]]; then
|
||||
WIZARD_GPU_RESULT=$(head -n1 "$wizard_result_file" | tr -d '\r\n')
|
||||
else
|
||||
WIZARD_GPU_RESULT="cancelled"
|
||||
fi
|
||||
rm -f "$wizard_result_file"
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -154,7 +210,6 @@ function create_vm() {
|
||||
local ISO_DIR="/var/lib/vz/template/iso"
|
||||
|
||||
|
||||
|
||||
if [[ -n "$ISO_PATH" && -n "$ISO_URL" && ! -f "$ISO_PATH" ]]; then
|
||||
|
||||
if [[ "$ISO_URL" == *"sourceforge.net"* ]]; then
|
||||
@@ -170,7 +225,7 @@ function create_vm() {
|
||||
msg_ok "$(translate "ISO image downloaded")"
|
||||
else
|
||||
msg_error "$(translate "Failed to download ISO image")"
|
||||
return
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -180,6 +235,13 @@ function create_vm() {
|
||||
GUEST_OS_TYPE="l26"
|
||||
fi
|
||||
|
||||
local VM_TAGS="proxmenux"
|
||||
case "${OS_TYPE:-}" in
|
||||
1) VM_TAGS="proxmenux,nas" ;;
|
||||
2) VM_TAGS="proxmenux,windows" ;;
|
||||
*) VM_TAGS="proxmenux,linux" ;;
|
||||
esac
|
||||
|
||||
|
||||
|
||||
# qm create "$VMID" -agent 1${MACHINE} -tablet 0 -localtime 1${BIOS_TYPE}${CPU_TYPE} \
|
||||
@@ -195,7 +257,7 @@ qm create "$VMID" \
|
||||
-cores "$CORE_COUNT" \
|
||||
-memory "$RAM_SIZE" \
|
||||
-name "$HN" \
|
||||
-tags proxmenux \
|
||||
-tags "$VM_TAGS" \
|
||||
-net0 "virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU" \
|
||||
-ostype "$GUEST_OS_TYPE" \
|
||||
-scsihw virtio-scsi-pci \
|
||||
@@ -213,7 +275,10 @@ fi
|
||||
|
||||
if [[ "$BIOS_TYPE" == *"ovmf"* ]]; then
|
||||
msg_info "$(translate "Configuring EFI disk")"
|
||||
EFI_STORAGE=$(select_storage_target "EFI" "$VMID")
|
||||
if ! EFI_STORAGE=$(select_storage_target "EFI" "$VMID"); then
|
||||
msg_error "$(translate "EFI storage selection failed or was cancelled. VM creation aborted.")"
|
||||
return 1
|
||||
fi
|
||||
STORAGE_TYPE=$(pvesm status -storage "$EFI_STORAGE" | awk 'NR>1 {print $2}')
|
||||
EFI_DISK_ID="efidisk0"
|
||||
EFI_KEYS="0"
|
||||
@@ -249,7 +314,10 @@ fi
|
||||
|
||||
if [[ "$OS_TYPE" == "2" ]]; then
|
||||
msg_info "$(translate "Configuring TPM device")"
|
||||
TPM_STORAGE=$(select_storage_target "TPM" "$VMID")
|
||||
if ! TPM_STORAGE=$(select_storage_target "TPM" "$VMID"); then
|
||||
msg_error "$(translate "TPM storage selection failed or was cancelled. VM creation aborted.")"
|
||||
return 1
|
||||
fi
|
||||
STORAGE_TYPE=$(pvesm status -storage "$TPM_STORAGE" | awk 'NR>1 {print $2}')
|
||||
TPM_ID="tpmstate0"
|
||||
|
||||
@@ -282,18 +350,31 @@ fi
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Create Diks
|
||||
# Create Disks / Import Disks / Controller + NVMe
|
||||
# ==========================================================
|
||||
|
||||
local -a EFFECTIVE_IMPORT_DISKS=()
|
||||
if [[ ${#IMPORT_DISKS[@]} -gt 0 ]]; then
|
||||
EFFECTIVE_IMPORT_DISKS=("${IMPORT_DISKS[@]}")
|
||||
elif [[ ${#PASSTHROUGH_DISKS[@]} -gt 0 ]]; then
|
||||
EFFECTIVE_IMPORT_DISKS=("${PASSTHROUGH_DISKS[@]}")
|
||||
fi
|
||||
|
||||
select_interface_type
|
||||
if [[ ${#VIRTUAL_DISKS[@]} -gt 0 || ${#EFFECTIVE_IMPORT_DISKS[@]} -gt 0 ]]; then
|
||||
if ! select_interface_type; then
|
||||
msg_error "$(translate "Disk interface is required to continue VM creation.")"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$DISK_TYPE" == "virtual" && ${#VIRTUAL_DISKS[@]} -gt 0 ]]; then
|
||||
local NEXT_DISK_SLOT=0
|
||||
|
||||
if [[ ${#VIRTUAL_DISKS[@]} -gt 0 ]]; then
|
||||
for i in "${!VIRTUAL_DISKS[@]}"; do
|
||||
DISK_INDEX=$((i+1))
|
||||
DISK_INDEX=$((NEXT_DISK_SLOT+1))
|
||||
IFS=':' read -r STORAGE SIZE <<< "${VIRTUAL_DISKS[$i]}"
|
||||
DISK_NAME="vm-${VMID}-disk-${DISK_INDEX}"
|
||||
SLOT_NAME="${INTERFACE_TYPE}${i}"
|
||||
SLOT_NAME="${INTERFACE_TYPE}${NEXT_DISK_SLOT}"
|
||||
|
||||
STORAGE_TYPE=$(pvesm status -storage "$STORAGE" | awk 'NR>1 {print $2}')
|
||||
case "$STORAGE_TYPE" in
|
||||
@@ -313,6 +394,7 @@ select_interface_type
|
||||
msg_ok "$(translate "Virtual disk") $DISK_INDEX ${SIZE}GB - $STORAGE ($SLOT_NAME)"
|
||||
DISK_INFO+="<p>Virtual Disk $DISK_INDEX: ${SIZE}GB ($STORAGE / $SLOT_NAME)</p>"
|
||||
[[ -z "$BOOT_ORDER" ]] && BOOT_ORDER="$SLOT_NAME"
|
||||
NEXT_DISK_SLOT=$((NEXT_DISK_SLOT + 1))
|
||||
else
|
||||
msg_error "$(translate "Failed to assign virtual disk") $DISK_INDEX"
|
||||
fi
|
||||
@@ -325,6 +407,7 @@ select_interface_type
|
||||
msg_ok "$(translate "Virtual disk") $DISK_INDEX ${SIZE}GB - $STORAGE ($SLOT_NAME)"
|
||||
DISK_INFO+="<p>Virtual Disk $DISK_INDEX: ${SIZE}GB ($STORAGE / $SLOT_NAME)</p>"
|
||||
[[ -z "$BOOT_ORDER" ]] && BOOT_ORDER="$SLOT_NAME"
|
||||
NEXT_DISK_SLOT=$((NEXT_DISK_SLOT + 1))
|
||||
else
|
||||
msg_error "$(translate "Failed to create disk") $DISK_INDEX"
|
||||
fi
|
||||
@@ -334,19 +417,59 @@ select_interface_type
|
||||
|
||||
|
||||
|
||||
if [[ "$DISK_TYPE" == "passthrough" && ${#PASSTHROUGH_DISKS[@]} -gt 0 ]]; then
|
||||
for i in "${!PASSTHROUGH_DISKS[@]}"; do
|
||||
SLOT_NAME="${INTERFACE_TYPE}${i}"
|
||||
DISK="${PASSTHROUGH_DISKS[$i]}"
|
||||
if [[ ${#EFFECTIVE_IMPORT_DISKS[@]} -gt 0 ]]; then
|
||||
for i in "${!EFFECTIVE_IMPORT_DISKS[@]}"; do
|
||||
SLOT_NAME="${INTERFACE_TYPE}${NEXT_DISK_SLOT}"
|
||||
DISK="${EFFECTIVE_IMPORT_DISKS[$i]}"
|
||||
MODEL=$(lsblk -ndo MODEL "$DISK")
|
||||
SIZE=$(lsblk -ndo SIZE "$DISK")
|
||||
qm set "$VMID" -$SLOT_NAME "$DISK${DISCARD_OPTS}" >/dev/null 2>&1
|
||||
msg_ok "$(translate "Passthrough disk assigned") ($DISK → $SLOT_NAME)"
|
||||
DISK_INFO+="<p>Passthrough Disk $((i+1)): $DISK ($MODEL $SIZE)</p>"
|
||||
msg_ok "$(translate "Import disk assigned") ($DISK → $SLOT_NAME)"
|
||||
DISK_INFO+="<p>Import Disk $((NEXT_DISK_SLOT+1)): $DISK ($MODEL $SIZE)</p>"
|
||||
[[ -z "$BOOT_ORDER" ]] && BOOT_ORDER="$SLOT_NAME"
|
||||
NEXT_DISK_SLOT=$((NEXT_DISK_SLOT + 1))
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ ${#CONTROLLER_NVME_PCIS[@]} -gt 0 ]]; then
|
||||
if ! _vm_is_q35 "$VMID"; then
|
||||
msg_error "$(translate "Controller + NVMe passthrough requires machine type q35. Skipping controller assignment.")"
|
||||
else
|
||||
local hostpci_idx=0
|
||||
if declare -F _pci_next_hostpci_index >/dev/null 2>&1; then
|
||||
hostpci_idx=$(_pci_next_hostpci_index "$VMID" 2>/dev/null || echo 0)
|
||||
else
|
||||
local hostpci_existing
|
||||
hostpci_existing=$(qm config "$VMID" 2>/dev/null)
|
||||
while grep -q "^hostpci${hostpci_idx}:" <<< "$hostpci_existing"; do
|
||||
hostpci_idx=$((hostpci_idx + 1))
|
||||
done
|
||||
fi
|
||||
|
||||
local pci bdf
|
||||
for pci in "${CONTROLLER_NVME_PCIS[@]}"; do
|
||||
bdf="${pci#0000:}"
|
||||
if declare -F _pci_function_assigned_to_vm >/dev/null 2>&1; then
|
||||
if _pci_function_assigned_to_vm "$pci" "$VMID"; then
|
||||
msg_warn "$(translate "Controller/NVMe already present in VM config") ($pci)"
|
||||
continue
|
||||
fi
|
||||
elif qm config "$VMID" 2>/dev/null | grep -qE "^hostpci[0-9]+:.*(0000:)?${bdf}([,[:space:]]|$)"; then
|
||||
msg_warn "$(translate "Controller/NVMe already present in VM config") ($pci)"
|
||||
continue
|
||||
fi
|
||||
|
||||
if qm set "$VMID" --hostpci${hostpci_idx} "${pci},pcie=1" >/dev/null 2>&1; then
|
||||
msg_ok "$(translate "Controller/NVMe assigned") (hostpci${hostpci_idx} → ${pci})"
|
||||
DISK_INFO+="<p>Controller/NVMe: ${pci}</p>"
|
||||
hostpci_idx=$((hostpci_idx + 1))
|
||||
else
|
||||
msg_error "$(translate "Failed to assign Controller/NVMe") (${pci})"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -427,10 +550,17 @@ select_interface_type
|
||||
fi
|
||||
|
||||
|
||||
local BOOT_FINAL="$BOOT_ORDER"
|
||||
[[ -f "$ISO_PATH" ]] && BOOT_FINAL="$BOOT_ORDER;ide2"
|
||||
qm set "$VMID" -boot order="$BOOT_FINAL" >/dev/null
|
||||
msg_ok "$(translate "Boot order set to") $BOOT_FINAL"
|
||||
local BOOT_FINAL=""
|
||||
if [[ -n "$BOOT_ORDER" ]]; then
|
||||
BOOT_FINAL="$BOOT_ORDER"
|
||||
fi
|
||||
if [[ -f "$ISO_PATH" ]]; then
|
||||
BOOT_FINAL="${BOOT_FINAL:+$BOOT_FINAL;}ide2"
|
||||
fi
|
||||
if [[ -n "$BOOT_FINAL" ]]; then
|
||||
qm set "$VMID" -boot order="$BOOT_FINAL" >/dev/null
|
||||
msg_ok "$(translate "Boot order set to") $BOOT_FINAL"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
@@ -450,7 +580,7 @@ select_interface_type
|
||||
|
||||
<p>
|
||||
<a href='https://macrimi.github.io/ProxMenux/docs/create-vm' target='_blank'><img src='https://img.shields.io/badge/📚_Docs-blue' alt='Docs'></a>
|
||||
<a href='https://github.com/MacRimi/ProxMenux/blob/main/scripts/vm/create_vm.sh' target='_blank'><img src='https://img.shields.io/badge/💻_Code-green' alt='Code'></a>
|
||||
<a href='https://github.com/MacRimi/ProxMenux/blob/main/scripts/menus/create_vm_menu.sh' target='_blank'><img src='https://img.shields.io/badge/💻_Code-green' alt='Code'></a>
|
||||
<a href='https://ko-fi.com/macrimi' target='_blank'><img src='https://img.shields.io/badge/☕_Ko--fi-red' alt='Ko-fi'></a>
|
||||
</p>
|
||||
|
||||
@@ -466,16 +596,75 @@ else
|
||||
msg_ok "$(translate "VM description configured")"
|
||||
fi
|
||||
|
||||
if [[ "${WIZARD_ADD_GPU:-no}" == "yes" && "$START_VM" == "yes" ]]; then
|
||||
msg_warn "$(translate "Auto-start was skipped because GPU passthrough setup was requested.")"
|
||||
msg_warn "$(translate "After completing GPU setup, start the VM manually when the host is ready.")"
|
||||
START_VM="no"
|
||||
fi
|
||||
|
||||
|
||||
if [[ "$START_VM" == "yes" ]]; then
|
||||
qm start "$VMID"
|
||||
msg_ok "$(translate "VM started")"
|
||||
fi
|
||||
configure_guest_agent
|
||||
msg_success "$(translate "VM creation completed")"
|
||||
|
||||
if [[ "${WIZARD_ADD_GPU:-no}" == "yes" ]]; then
|
||||
WIZARD_GPU_RESULT="cancelled"
|
||||
run_gpu_passthrough_wizard
|
||||
if [[ "${VM_WIZARD_CAPTURE_ACTIVE:-0}" -eq 1 ]]; then
|
||||
stop_spinner
|
||||
exec 1>&8
|
||||
exec 8>&-
|
||||
VM_WIZARD_CAPTURE_ACTIVE=0
|
||||
show_proxmenux_logo
|
||||
cat "$VM_WIZARD_CAPTURE_FILE"
|
||||
rm -f "$VM_WIZARD_CAPTURE_FILE"
|
||||
VM_WIZARD_CAPTURE_FILE=""
|
||||
fi
|
||||
if [[ "$WIZARD_GPU_RESULT" == "applied" ]]; then
|
||||
msg_success "$(translate "VM creation completed with GPU passthrough configured.")"
|
||||
elif [[ "$WIZARD_GPU_RESULT" == "no_gpu" ]]; then
|
||||
msg_success "$(translate "VM creation completed. GPU passthrough was skipped (no compatible GPU detected).")"
|
||||
else
|
||||
msg_success "$(translate "VM creation completed. GPU passthrough was not applied.")"
|
||||
fi
|
||||
if [[ "$OS_TYPE" == "2" ]]; then
|
||||
echo -e "${TAB}$(translate "Next Steps:")"
|
||||
echo -e "${TAB}1. $(translate "Start the VM to begin Windows installation from the mounted ISO.")"
|
||||
echo -e "${TAB}2. $(translate "When asked to select a disk, click Load Driver and load the VirtIO drivers.")"
|
||||
echo -e "${TAB} $(translate "Required if using a VirtIO or SCSI disk.")"
|
||||
echo -e "${TAB}3. $(translate "Also install the VirtIO network driver during setup to enable network access.")"
|
||||
echo -e "${TAB}4. $(translate "Continue the Windows installation as usual.")"
|
||||
echo -e "${TAB}5. $(translate "Once installed, open the VirtIO ISO and run the installer to complete driver setup.")"
|
||||
echo -e "${TAB}6. $(translate "Reboot the VM to complete the driver installation.")"
|
||||
if [[ "$WIZARD_GPU_RESULT" == "applied" ]]; then
|
||||
echo -e "${TAB}- $(translate "If you want to use a physical monitor on the passthrough GPU:")"
|
||||
echo -e "${TAB}• $(translate "First install the GPU drivers inside the guest and verify remote access (RDP/SSH).")"
|
||||
echo -e "${TAB}• $(translate "Then change the VM display to none (vga: none) when the guest is stable.")"
|
||||
echo -e "${TAB}• $(translate "If passthrough fails on Windows: install RadeonResetBugFix.")"
|
||||
fi
|
||||
echo -e
|
||||
elif [[ "$OS_TYPE" == "3" ]]; then
|
||||
echo -e "${TAB}${GN}$(translate "Recommended: Install the QEMU Guest Agent in the VM")${CL}"
|
||||
echo -e "${TAB}$(translate "Run the following inside the VM:")"
|
||||
echo -e "${TAB}apt install qemu-guest-agent -y && systemctl enable --now qemu-guest-agent"
|
||||
if [[ "$WIZARD_GPU_RESULT" == "applied" ]]; then
|
||||
echo -e "${TAB}- $(translate "If you want to use a physical monitor on the passthrough GPU:")"
|
||||
echo -e "${TAB}• $(translate "First install the GPU drivers inside the guest and verify remote access (RDP/SSH).")"
|
||||
echo -e "${TAB}• $(translate "Then change the VM display to none (vga: none) when the guest is stable.")"
|
||||
fi
|
||||
echo -e
|
||||
fi
|
||||
msg_success "$(translate "Press Enter to return to the main menu...")"
|
||||
read -r
|
||||
bash "$LOCAL_SCRIPTS/menus/create_vm_menu.sh"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
msg_success "$(translate "VM creation completed")"
|
||||
if [[ "$OS_TYPE" == "2" ]]; then
|
||||
echo -e "${TAB}${GN}$(translate "Next Steps:")${CL}"
|
||||
echo -e "${TAB}$(translate "Next Steps:")"
|
||||
echo -e "${TAB}1. $(translate "Start the VM to begin Windows installation from the mounted ISO.")"
|
||||
echo -e "${TAB}2. $(translate "When asked to select a disk, click Load Driver and load the VirtIO drivers.")"
|
||||
echo -e "${TAB} $(translate "Required if using a VirtIO or SCSI disk.")"
|
||||
@@ -487,11 +676,10 @@ if [[ "$OS_TYPE" == "2" ]]; then
|
||||
elif [[ "$OS_TYPE" == "3" ]]; then
|
||||
echo -e "${TAB}${GN}$(translate "Recommended: Install the QEMU Guest Agent in the VM")${CL}"
|
||||
echo -e "${TAB}$(translate "Run the following inside the VM:")"
|
||||
echo -e "${TAB}${CY}apt install qemu-guest-agent -y && systemctl enable --now qemu-guest-agent${CL}"
|
||||
echo -e "${TAB}apt install qemu-guest-agent -y && systemctl enable --now qemu-guest-agent"
|
||||
echo -e
|
||||
fi
|
||||
|
||||
|
||||
msg_success "$(translate "Press Enter to return to the main menu...")"
|
||||
read -r
|
||||
bash "$LOCAL_SCRIPTS/menus/create_vm_menu.sh"
|
||||
|
||||
@@ -1,466 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenuX - Virtual Machine Creator Script
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 07/05/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script is part of the central ProxMenux VM creation module. It allows users
|
||||
# to create virtual machines (VMs) in Proxmox VE using either default or advanced
|
||||
# configurations, streamlining the deployment of Linux, Windows, and other systems.
|
||||
#
|
||||
# Key features:
|
||||
# - Supports both virtual disk creation and physical disk passthrough.
|
||||
# - Automates CPU, RAM, BIOS, network and storage configuration.
|
||||
# - Provides a user-friendly menu to select OS type, ISO image and disk interface.
|
||||
# - Automatically generates a detailed and styled HTML description for each VM.
|
||||
#
|
||||
# All operations are designed to simplify and accelerate VM creation in a
|
||||
# consistent and maintainable way, using ProxMenux standards.
|
||||
# ==========================================================
|
||||
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
# ==========================================================
|
||||
# Mont ISOs
|
||||
# ==========================================================
|
||||
function mount_iso_to_vm() {
|
||||
local vmid="$1"
|
||||
local iso_path="$2"
|
||||
local device="$3"
|
||||
|
||||
if [[ -f "$iso_path" ]]; then
|
||||
local iso_basename
|
||||
iso_basename=$(basename "$iso_path")
|
||||
qm set "$vmid" -$device "local:iso/$iso_basename,media=cdrom" >/dev/null 2>&1
|
||||
msg_ok "$(translate "Mounted ISO on device") $device → $iso_basename"
|
||||
else
|
||||
msg_warn "$(translate "ISO not found to mount on device") $device"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Select Interface Type
|
||||
# ==========================================================
|
||||
function select_interface_type() {
|
||||
INTERFACE_TYPE=$(whiptail --backtitle "ProxMenux" --title "$(translate "Select Disk Interface")" --radiolist \
|
||||
"$(translate "Select the bus type for the disks:")" 15 70 4 \
|
||||
"scsi" "$(translate "SCSI (recommended for Linux and Windows)")" ON \
|
||||
"sata" "$(translate "SATA (standard - high compatibility)")" OFF \
|
||||
"virtio" "$(translate "VirtIO (advanced - high performance)")" OFF \
|
||||
"ide" "IDE (legacy)" OFF \
|
||||
3>&1 1>&2 2>&3) || exit 1
|
||||
|
||||
case "$INTERFACE_TYPE" in
|
||||
"scsi"|"sata")
|
||||
DISCARD_OPTS=",discard=on,ssd=on"
|
||||
;;
|
||||
"virtio")
|
||||
DISCARD_OPTS=",discard=on"
|
||||
;;
|
||||
"ide")
|
||||
DISCARD_OPTS=""
|
||||
;;
|
||||
esac
|
||||
|
||||
msg_ok "$(translate "Disk interface selected:") $INTERFACE_TYPE"
|
||||
}
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# EFI/TPM
|
||||
# ==========================================================
|
||||
function select_storage_target() {
|
||||
local PURPOSE="$1"
|
||||
local vmid="$2"
|
||||
local STORAGE=""
|
||||
local STORAGE_MENU=()
|
||||
|
||||
while read -r line; do
|
||||
TAG=$(echo "$line" | awk '{print $1}')
|
||||
TYPE=$(echo "$line" | awk '{printf "%-10s", $2}')
|
||||
FREE=$(echo "$line" | numfmt --field 4-6 --from-unit=K --to=iec --format "%.2f" | awk '{printf("%9sB", $6)}')
|
||||
STORAGE_MENU+=("$TAG" "$(translate "Type:") $TYPE $(translate "Free:") $FREE" "OFF")
|
||||
done < <(pvesm status -content images | awk 'NR>1')
|
||||
|
||||
if [[ ${#STORAGE_MENU[@]} -eq 0 ]]; then
|
||||
msg_error "$(translate "Unable to detect a valid storage location for $PURPOSE disk.")"
|
||||
exit 1
|
||||
elif [[ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]]; then
|
||||
STORAGE="${STORAGE_MENU[0]}"
|
||||
else
|
||||
kill $SPINNER_PID > /dev/null
|
||||
STORAGE=$(whiptail --backtitle "ProxMenux" --title "$(translate "$PURPOSE Disk Storage")" --radiolist \
|
||||
"$(translate "Choose the storage volume for the $PURPOSE disk (4MB):\n\nUse Spacebar to select.")" 16 70 6 \
|
||||
"${STORAGE_MENU[@]}" 3>&1 1>&2 2>&3) || exit 1
|
||||
fi
|
||||
|
||||
echo "$STORAGE"
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Guest Agent Configurator
|
||||
# ==========================================================
|
||||
function configure_guest_agent() {
|
||||
if [[ -z "$VMID" ]]; then
|
||||
msg_error "$(translate "No VMID defined. Cannot apply guest agent config.")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
msg_info "$(translate "Adding QEMU Guest Agent support...")"
|
||||
|
||||
# Habilitar el agente en la VM
|
||||
qm set "$VMID" -agent enabled=1 >/dev/null 2>&1
|
||||
|
||||
# Añadir canal de comunicación virtio
|
||||
qm set "$VMID" -chardev socket,id=qga0,path=/var/run/qemu-server/$VMID.qga,server=on,wait=off >/dev/null 2>&1
|
||||
qm set "$VMID" -device virtio-serial-pci -device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0 >/dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "Guest Agent configuration applied")"
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Create VM
|
||||
# ==========================================================
|
||||
function create_vm() {
|
||||
local BOOT_ORDER=""
|
||||
local DISK_INFO=""
|
||||
local DISK_INDEX=0
|
||||
local ISO_DIR="/var/lib/vz/template/iso"
|
||||
|
||||
|
||||
|
||||
if [[ -n "$ISO_PATH" && -n "$ISO_URL" && ! -f "$ISO_PATH" ]]; then
|
||||
|
||||
if [[ "$ISO_URL" == *"sourceforge.net"* ]]; then
|
||||
|
||||
wget --content-disposition --show-progress -O "$ISO_PATH" "$ISO_URL"
|
||||
else
|
||||
|
||||
wget --no-verbose --show-progress -O "$ISO_PATH" "$ISO_URL"
|
||||
fi
|
||||
|
||||
|
||||
if [[ -f "$ISO_PATH" ]]; then
|
||||
msg_ok "$(translate "ISO image downloaded")"
|
||||
else
|
||||
msg_error "$(translate "Failed to download ISO image")"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$OS_TYPE" == "2" ]]; then
|
||||
GUEST_OS_TYPE="win10"
|
||||
else
|
||||
GUEST_OS_TYPE="l26"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
qm create "$VMID" -agent 1${MACHINE} -tablet 0 -localtime 1${BIOS_TYPE}${CPU_TYPE} \
|
||||
-cores "$CORE_COUNT" -memory "$RAM_SIZE" -name "$HN" -tags proxmenux \
|
||||
-net0 "virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU" -ostype "$GUEST_OS_TYPE" \
|
||||
-scsihw virtio-scsi-pci \
|
||||
$( [[ -n "$SERIAL_PORT" ]] && echo "-serial0 $SERIAL_PORT" ) >/dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "Base VM created with ID") $VMID"
|
||||
|
||||
|
||||
|
||||
if [[ "$BIOS_TYPE" == *"ovmf"* ]]; then
|
||||
msg_info "$(translate "Configuring EFI disk")"
|
||||
EFI_STORAGE=$(select_storage_target "EFI" "$VMID")
|
||||
EFI_DISK_NAME="vm-${VMID}-disk-efivars"
|
||||
|
||||
STORAGE_TYPE=$(pvesm status -storage "$EFI_STORAGE" | awk 'NR>1 {print $2}')
|
||||
case "$STORAGE_TYPE" in
|
||||
nfs | dir)
|
||||
EFI_DISK_EXT=".raw"
|
||||
EFI_DISK_REF="$VMID/"
|
||||
;;
|
||||
*)
|
||||
EFI_DISK_EXT=""
|
||||
EFI_DISK_REF=""
|
||||
;;
|
||||
esac
|
||||
|
||||
EFI_KEYS="0"
|
||||
[[ "$OS_TYPE" == "2" ]] && EFI_KEYS="1"
|
||||
|
||||
if pvesm alloc "$EFI_STORAGE" "$VMID" "$EFI_DISK_NAME$EFI_DISK_EXT" 4M >/dev/null 2>&1; then
|
||||
if qm set "$VMID" -efidisk0 "$EFI_STORAGE:${EFI_DISK_REF}$EFI_DISK_NAME$EFI_DISK_EXT,pre-enrolled-keys=$EFI_KEYS" >/dev/null 2>&1; then
|
||||
msg_ok "$(translate "EFI disk created and configured on") $EFI_STORAGE"
|
||||
else
|
||||
msg_error "$(translate "Failed to configure EFI disk")"
|
||||
fi
|
||||
else
|
||||
msg_error "$(translate "Failed to create EFI disk")"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if [[ "$OS_TYPE" == "2" ]]; then
|
||||
msg_info "$(translate "Configuring TPM device")"
|
||||
TPM_STORAGE=$(select_storage_target "TPM" "$VMID")
|
||||
TPM_NAME="vm-${VMID}-tpmstate"
|
||||
|
||||
STORAGE_TYPE=$(pvesm status -storage "$TPM_STORAGE" | awk 'NR>1 {print $2}')
|
||||
case "$STORAGE_TYPE" in
|
||||
nfs | dir)
|
||||
TPM_EXT=".raw"
|
||||
TPM_REF="$VMID/"
|
||||
;;
|
||||
*)
|
||||
TPM_EXT=""
|
||||
TPM_REF=""
|
||||
;;
|
||||
esac
|
||||
|
||||
TPM_FULL_NAME="${TPM_NAME}${TPM_EXT}"
|
||||
|
||||
if pvesm alloc "$TPM_STORAGE" "$VMID" "$TPM_FULL_NAME" 4M >/dev/null 2>&1; then
|
||||
TPM_PATH="$TPM_STORAGE:${TPM_REF}${TPM_FULL_NAME},size=4M,version=v2.0"
|
||||
if qm set "$VMID" -tpmstate0 "$TPM_PATH" >/dev/null 2>&1; then
|
||||
msg_ok "$(translate "TPM device added to VM")"
|
||||
else
|
||||
msg_error "$(translate "Failed to configure TPM device in VM") → $TPM_PATH"
|
||||
fi
|
||||
else
|
||||
msg_error "$(translate "Failed to create TPM state disk")"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Create Diks
|
||||
# ==========================================================
|
||||
|
||||
|
||||
select_interface_type
|
||||
|
||||
if [[ "$DISK_TYPE" == "virtual" && ${#VIRTUAL_DISKS[@]} -gt 0 ]]; then
|
||||
for i in "${!VIRTUAL_DISKS[@]}"; do
|
||||
DISK_INDEX=$((i+1))
|
||||
IFS=':' read -r STORAGE SIZE <<< "${VIRTUAL_DISKS[$i]}"
|
||||
DISK_NAME="vm-${VMID}-disk-${DISK_INDEX}"
|
||||
SLOT_NAME="${INTERFACE_TYPE}${i}"
|
||||
|
||||
STORAGE_TYPE=$(pvesm status -storage "$STORAGE" | awk 'NR>1 {print $2}')
|
||||
case "$STORAGE_TYPE" in
|
||||
dir|nfs)
|
||||
DISK_EXT=".raw"
|
||||
DISK_REF="$VMID/"
|
||||
;;
|
||||
*)
|
||||
DISK_EXT=""
|
||||
DISK_REF=""
|
||||
;;
|
||||
esac
|
||||
|
||||
if pvesm alloc "$STORAGE" "$VMID" "$DISK_NAME$DISK_EXT" "$SIZE"G >/dev/null 2>&1; then
|
||||
qm set "$VMID" -$SLOT_NAME "$STORAGE:${DISK_REF}${DISK_NAME}${DISK_EXT}${DISCARD_OPTS}" >/dev/null
|
||||
msg_ok "$(translate "Virtual disk") $DISK_INDEX ${SIZE}GB - $STORAGE ($SLOT_NAME)"
|
||||
DISK_INFO+="<p>Virtual Disk $DISK_INDEX: ${SIZE}GB ($STORAGE / $SLOT_NAME)</p>"
|
||||
[[ -z "$BOOT_ORDER" ]] && BOOT_ORDER="$SLOT_NAME"
|
||||
else
|
||||
msg_error "$(translate "Failed to create disk") $DISK_INDEX"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ "$DISK_TYPE" == "passthrough" && ${#PASSTHROUGH_DISKS[@]} -gt 0 ]]; then
|
||||
for i in "${!PASSTHROUGH_DISKS[@]}"; do
|
||||
SLOT_NAME="${INTERFACE_TYPE}${i}"
|
||||
DISK="${PASSTHROUGH_DISKS[$i]}"
|
||||
MODEL=$(lsblk -ndo MODEL "$DISK")
|
||||
SIZE=$(lsblk -ndo SIZE "$DISK")
|
||||
qm set "$VMID" -$SLOT_NAME "$DISK${DISCARD_OPTS}" >/dev/null 2>&1
|
||||
msg_ok "$(translate "Passthrough disk assigned") ($DISK → $SLOT_NAME)"
|
||||
DISK_INFO+="<p>Passthrough Disk $((i+1)): $DISK ($MODEL $SIZE)</p>"
|
||||
[[ -z "$BOOT_ORDER" ]] && BOOT_ORDER="$SLOT_NAME"
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if [[ -f "$ISO_PATH" ]]; then
|
||||
mount_iso_to_vm "$VMID" "$ISO_PATH" "ide2"
|
||||
fi
|
||||
|
||||
|
||||
if [[ "$OS_TYPE" == "2" ]]; then
|
||||
local VIRTIO_DIR="/var/lib/vz/template/iso"
|
||||
local VIRTIO_SELECTED=""
|
||||
local VIRTIO_DOWNLOAD_URL="https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso"
|
||||
|
||||
while true; do
|
||||
VIRTIO_OPTION=$(whiptail --title "ProxMenux - VirtIO Drivers" --menu "$(translate "Select how to provide VirtIO drivers")" 15 70 2 \
|
||||
"1" "$(translate "Download latest VirtIO ISO automatically")" \
|
||||
"2" "$(translate "Use existing VirtIO ISO from storage")" 3>&1 1>&2 2>&3)
|
||||
|
||||
[[ $? -ne 0 ]] && msg_warn "$(translate "VirtIO ISO selection cancelled.")" && break
|
||||
|
||||
case "$VIRTIO_OPTION" in
|
||||
1)
|
||||
|
||||
if [[ -f "$VIRTIO_DIR/virtio-win.iso" ]]; then
|
||||
if whiptail --title "ProxMenux" --yesno "$(translate "A VirtIO ISO already exists. Do you want to overwrite it?")" 10 60; then
|
||||
wget -q --show-progress -O "$VIRTIO_DIR/virtio-win.iso" "$VIRTIO_DOWNLOAD_URL"
|
||||
if [[ -f "$VIRTIO_DIR/virtio-win.iso" ]]; then
|
||||
msg_ok "$(translate "VirtIO driver ISO downloaded successfully.")"
|
||||
else
|
||||
msg_error "$(translate "Failed to download VirtIO driver ISO.")"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
wget -q --show-progress -O "$VIRTIO_DIR/virtio-win.iso" "$VIRTIO_DOWNLOAD_URL"
|
||||
if [[ -f "$VIRTIO_DIR/virtio-win.iso" ]]; then
|
||||
msg_ok "$(translate "VirtIO driver ISO downloaded successfully.")"
|
||||
else
|
||||
msg_error "$(translate "Failed to download VirtIO driver ISO.")"
|
||||
fi
|
||||
fi
|
||||
|
||||
VIRTIO_SELECTED="$VIRTIO_DIR/virtio-win.iso"
|
||||
;;
|
||||
2)
|
||||
|
||||
VIRTIO_LIST=()
|
||||
while read -r line; do
|
||||
FILENAME=$(basename "$line")
|
||||
SIZE=$(du -h "$line" | cut -f1)
|
||||
VIRTIO_LIST+=("$FILENAME" "$SIZE")
|
||||
done < <(find "$VIRTIO_DIR" -type f -iname "virtio*.iso" | sort)
|
||||
|
||||
if [[ ${#VIRTIO_LIST[@]} -eq 0 ]]; then
|
||||
msg_warn "$(translate "No VirtIO ISO found. Please download one.")"
|
||||
continue
|
||||
fi
|
||||
|
||||
VIRTIO_FILE=$(whiptail --title "ProxMenux - VirtIO ISOs" --menu "$(translate "Select a VirtIO ISO to use:")" 20 70 10 "${VIRTIO_LIST[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [[ -n "$VIRTIO_FILE" ]]; then
|
||||
VIRTIO_SELECTED="$VIRTIO_DIR/$VIRTIO_FILE"
|
||||
else
|
||||
msg_warn "$(translate "No VirtIO ISO selected. Please choose again.")"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ -n "$VIRTIO_SELECTED" && -f "$VIRTIO_SELECTED" ]]; then
|
||||
mount_iso_to_vm "$VMID" "$VIRTIO_SELECTED" "ide3"
|
||||
else
|
||||
msg_warn "$(translate "VirtIO ISO not found after selection.")"
|
||||
fi
|
||||
|
||||
break
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
local BOOT_FINAL="$BOOT_ORDER"
|
||||
[[ -f "$ISO_PATH" ]] && BOOT_FINAL="$BOOT_ORDER;ide2"
|
||||
qm set "$VMID" -boot order="$BOOT_FINAL" >/dev/null
|
||||
msg_ok "$(translate "Boot order set to") $BOOT_FINAL"
|
||||
|
||||
|
||||
|
||||
|
||||
HTML_DESC="<div align='center'>
|
||||
<table style='width: 100%; border-collapse: collapse;'>
|
||||
<tr>
|
||||
<td style='width: 100px; vertical-align: middle;'>
|
||||
<img src='https://raw.githubusercontent.com/MacRimi/ProxMenux/main/images/logo_desc.png' alt='ProxMenux Logo' style='height: 100px;'>
|
||||
</td>
|
||||
<td style='vertical-align: middle;'>
|
||||
<h1 style='margin: 0;'>$HN VM</h1>
|
||||
<p style='margin: 0;'>Created with ProxMenux</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
<a href='https://macrimi.github.io/ProxMenux/docs/create-vm/synology' target='_blank'><img src='https://img.shields.io/badge/📚_Docs-blue' alt='Docs'></a>
|
||||
<a href='https://github.com/MacRimi/ProxMenux/blob/main/scripts/vm/create_vm.sh' target='_blank'><img src='https://img.shields.io/badge/💻_Code-green' alt='Code'></a>
|
||||
<a href='https://ko-fi.com/macrimi' target='_blank'><img src='https://img.shields.io/badge/☕_Ko--fi-red' alt='Ko-fi'></a>
|
||||
</p>
|
||||
|
||||
<div>
|
||||
${DISK_INFO}
|
||||
</div>
|
||||
</div>"
|
||||
|
||||
msg_info "$(translate "Setting VM description")"
|
||||
if ! qm set "$VMID" -description "$HTML_DESC" >/dev/null 2>&1; then
|
||||
msg_error "$(translate "Failed to set VM description")"
|
||||
else
|
||||
msg_ok "$(translate "VM description configured")"
|
||||
fi
|
||||
|
||||
|
||||
if [[ "$START_VM" == "yes" ]]; then
|
||||
qm start "$VMID"
|
||||
msg_ok "$(translate "VM started")"
|
||||
fi
|
||||
configure_guest_agent
|
||||
msg_success "$(translate "VM creation completed")"
|
||||
|
||||
if [[ "$OS_TYPE" == "2" ]]; then
|
||||
echo -e "${TAB}${GN}$(translate "Next Steps:")${CL}"
|
||||
echo -e "${TAB}1. $(translate "Start the VM to begin Windows installation from the mounted ISO.")"
|
||||
echo -e "${TAB}2. $(translate "When asked to select a disk, click Load Driver and load the VirtIO drivers.")"
|
||||
echo -e "${TAB} $(translate "Required if using a VirtIO or SCSI disk.")"
|
||||
echo -e "${TAB}3. $(translate "Also install the VirtIO network driver during setup to enable network access.")"
|
||||
echo -e "${TAB}4. $(translate "Continue the Windows installation as usual.")"
|
||||
echo -e "${TAB}5. $(translate "Once installed, open the VirtIO ISO and run the installer to complete driver setup.")"
|
||||
echo -e "${TAB}6. $(translate "Reboot the VM to complete the driver installation.")"
|
||||
echo -e
|
||||
elif [[ "$OS_TYPE" == "3" ]]; then
|
||||
echo -e "${TAB}${GN}$(translate "Recommended: Install the QEMU Guest Agent in the VM")${CL}"
|
||||
echo -e "${TAB}$(translate "Run the following inside the VM:")"
|
||||
echo -e "${TAB}${CY}apt install qemu-guest-agent -y && systemctl enable --now qemu-guest-agent${CL}"
|
||||
echo -e
|
||||
fi
|
||||
|
||||
|
||||
msg_success "$(translate "Press Enter to return to the main menu...")"
|
||||
read -r
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user