2025-04-09 10:12:04 +02:00
#!/bin/bash
# ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
2025-05-29 21:49:21 +02:00
# Version : 1.1
# Last Updated: 29/05/2025
2025-04-09 10:12:04 +02:00
# ==========================================================
# Description:
# This script automates the process of importing disk images into Proxmox VE virtual machines (VMs),
# making it easy to attach pre-existing disk files without manual configuration.
#
# Before running the script, ensure that disk images are available in /var/lib/vz/template/images/.
2025-05-29 21:49:21 +02:00
# The script scans this directory for compatible formats (.img, .qcow2, .vmdk, .raw) and lists the available files.
2025-04-09 10:12:04 +02:00
#
# Using an interactive menu, you can:
# - Select a VM to attach the imported disk.
# - Choose one or multiple disk images for import.
# - Pick a storage volume in Proxmox for disk placement.
# - Assign a suitable interface (SATA, SCSI, VirtIO, or IDE).
# - Enable optional settings like SSD emulation or bootable disk configuration.
#
# Once completed, the script ensures the selected images are correctly attached and ready to use.
# ==========================================================
# Configuration ============================================
REPO_URL = "https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
BASE_DIR = "/usr/local/share/proxmenux"
UTILS_FILE = " $BASE_DIR /utils.sh "
VENV_PATH = "/opt/googletrans-env"
2025-05-29 21:49:21 +02:00
[ [ -f " $UTILS_FILE " ] ] && source " $UTILS_FILE "
2025-04-09 10:12:04 +02:00
load_language
initialize_cache
2025-05-29 21:49:21 +02:00
# Configuration ============================================
2025-04-09 10:12:04 +02:00
2025-05-30 18:25:10 +02:00
2025-05-29 21:49:21 +02:00
detect_image_dir( ) {
for store in $( pvesm status -content images | awk 'NR>1 {print $1}' ) ; do
path = $( pvesm path " ${ store } :template " 2>/dev/null)
if [ [ -d " $path " ] ] ; then
for ext in raw img qcow2 vmdk; do
if compgen -G " $path /*. $ext " > /dev/null; then
echo " $path "
return 0
fi
done
for sub in images iso; do
dir = " $path / $sub "
if [ [ -d " $dir " ] ] ; then
for ext in raw img qcow2 vmdk; do
if compgen -G " $dir /*. $ext " > /dev/null; then
echo " $dir "
return 0
fi
done
fi
done
fi
done
for fallback in /var/lib/vz/template/images /var/lib/vz/template/iso; do
if [ [ -d " $fallback " ] ] ; then
for ext in raw img qcow2 vmdk; do
if compgen -G " $fallback /*. $ext " > /dev/null; then
echo " $fallback "
return 0
fi
done
fi
done
return 1
}
2025-04-09 10:12:04 +02:00
2025-05-29 21:49:21 +02:00
IMAGES_DIR = $( detect_image_dir)
if [ [ -z " $IMAGES_DIR " ] ] ; then
dialog --title " $( translate 'No Images Found' ) " \
--msgbox " $( translate 'Could not find any directory containing disk images' ) \n\n $( translate 'Make sure there is at least one file with extension .img, .qcow2, .vmdk or .raw' ) " 15 60
exit 1
fi
2025-04-09 10:12:04 +02:00
2025-05-29 21:49:21 +02:00
IMAGES = $( ls -A " $IMAGES_DIR " | grep -E " \.(img|qcow2|vmdk|raw) $" )
if [ -z " $IMAGES " ] ; then
dialog --title " $( translate 'No Disk Images Found' ) " \
--msgbox " $( translate 'No compatible disk images found in:' ) \n\n $IMAGES_DIR \n\n $( translate 'Supported formats: .img, .qcow2, .vmdk, .raw' ) " 15 60
exit 1
fi
2025-04-09 10:12:04 +02:00
2025-05-30 18:25:10 +02:00
# 1. Select VM
2025-04-09 10:12:04 +02:00
msg_info " $( translate 'Getting VM list' ) "
VM_LIST = $( qm list | awk 'NR>1 {print $1" "$2}' )
2025-05-30 18:25:10 +02:00
if [ -z " $VM_LIST " ] ; then
msg_error " $( translate 'No VMs available in the system' ) "
exit 1
fi
2025-04-09 10:12:04 +02:00
msg_ok " $( translate 'VM list obtained' ) "
2025-05-30 18:25:10 +02:00
VMID = $( whiptail --title " $( translate 'Select VM' ) " --menu " $( translate 'Select the VM where you want to import the disk image:' ) " 15 60 8 $VM_LIST 3>& 1 1>& 2 2>& 3)
2025-04-09 10:12:04 +02:00
2025-05-30 18:25:10 +02:00
if [ -z " $VMID " ] ; then
exit 1
fi
2025-04-09 10:12:04 +02:00
2025-05-30 18:25:10 +02:00
# 2. Select storage volume
2025-04-09 10:12:04 +02:00
msg_info " $( translate 'Getting storage volumes' ) "
STORAGE_LIST = $( pvesm status -content images | awk 'NR>1 {print $1}' )
2025-05-30 18:25:10 +02:00
if [ -z " $STORAGE_LIST " ] ; then
msg_error " $( translate 'No storage volumes available' ) "
exit 1
fi
2025-04-09 10:12:04 +02:00
msg_ok " $( translate 'Storage volumes obtained' ) "
2025-05-30 18:25:10 +02:00
2025-04-09 10:12:04 +02:00
STORAGE_OPTIONS = ( )
2025-05-30 18:25:10 +02:00
while read -r storage; do
STORAGE_OPTIONS += ( " $storage " "" )
done <<< " $STORAGE_LIST "
2025-04-09 10:12:04 +02:00
2025-05-30 18:25:10 +02:00
STORAGE = $( whiptail --title " $( translate 'Select Storage' ) " --menu " $( translate 'Select the storage volume for disk import:' ) " 15 60 8 " ${ STORAGE_OPTIONS [@] } " 3>& 1 1>& 2 2>& 3)
2025-04-09 10:12:04 +02:00
2025-05-30 18:25:10 +02:00
if [ -z " $STORAGE " ] ; then
exit 1
fi
# 3. Select disk images
msg_info " $( translate 'Scanning disk images' ) "
if [ -z " $IMAGES " ] ; then
msg_warn " $( translate 'No compatible disk images found in' ) $IMAGES_DIR "
exit 0
fi
msg_ok " $( translate 'Disk images found' ) "
2025-04-09 10:12:04 +02:00
IMAGE_OPTIONS = ( )
2025-05-30 18:25:10 +02:00
while read -r img; do
IMAGE_OPTIONS += ( " $img " "" "OFF" )
done <<< " $IMAGES "
2025-04-09 10:12:04 +02:00
2025-05-30 18:25:10 +02:00
SELECTED_IMAGES = $( whiptail --title " $( translate 'Select Disk Images' ) " --checklist " $( translate 'Select the disk images to import:' ) " 20 60 10 " ${ IMAGE_OPTIONS [@] } " 3>& 1 1>& 2 2>& 3)
if [ -z " $SELECTED_IMAGES " ] ; then
exit 1
fi
2025-04-09 10:12:04 +02:00
2025-05-30 18:25:10 +02:00
# 4. Import each selected image
2025-04-09 10:12:04 +02:00
for IMAGE in $SELECTED_IMAGES ; do
2025-05-30 18:25:10 +02:00
IMAGE = $( echo " $IMAGE " | tr -d '"' )
INTERFACE = $( whiptail --title " $( translate 'Interface Type' ) " --menu " $( translate 'Select the interface type for the image:' ) $IMAGE " 15 40 4 \
"sata" "SATA" \
"scsi" "SCSI" \
"virtio" "VirtIO" \
"ide" "IDE" 3>& 1 1>& 2 2>& 3)
if [ -z " $INTERFACE " ] ; then
msg_error " $( translate 'No interface type selected for' ) $IMAGE "
continue
2025-04-09 10:12:04 +02:00
fi
2025-05-29 21:49:21 +02:00
2025-05-30 18:25:10 +02:00
FULL_PATH = " $IMAGES_DIR / $IMAGE "
2025-05-29 21:49:21 +02:00
2025-05-30 18:25:10 +02:00
msg_info " $( translate 'Importing image:' ) "
TEMP_DISK_FILE = $( mktemp)
qm importdisk " $VMID " " $FULL_PATH " " $STORAGE " 2>& 1 | while read -r line; do
if [ [ " $line " = ~ transferred ] ] ; then
PERCENT = $( echo " $line " | grep -oP "\d+\.\d+(?=%)" )
2025-05-30 14:56:57 +02:00
2025-05-30 18:25:10 +02:00
echo -ne " \r ${ TAB } ${ BL } - $( translate 'Importing image:' ) $IMAGE - ${ CL } ${ PERCENT } % "
elif [ [ " $line " = ~ successfully\ imported\ disk ] ] ; then
echo " $line " | grep -oP "(?<=successfully imported disk ').*(?=')" > " $TEMP_DISK_FILE "
fi
done
echo -ne "\n"
IMPORT_STATUS = ${ PIPESTATUS [0] }
if [ $IMPORT_STATUS -eq 0 ] ; then
msg_ok " $( translate 'Image imported successfully' ) "
IMPORTED_DISK = $( cat " $TEMP_DISK_FILE " )
rm -f " $TEMP_DISK_FILE "
if [ -z " $IMPORTED_DISK " ] ; then
STORAGE_TYPE = $( pvesm status -storage " $STORAGE " | awk 'NR>1 {print $2}' )
if [ [ " $STORAGE_TYPE " = = "btrfs" || " $STORAGE_TYPE " = = "dir" || " $STORAGE_TYPE " = = "nfs" ] ] ; then
UNUSED_LINE = $( qm config " $VMID " | grep -E '^unused[0-9]+:' )
IMPORTED_ID = $( echo " $UNUSED_LINE " | cut -d: -f1)
IMPORTED_DISK = $( echo " $UNUSED_LINE " | cut -d: -f2- | xargs)
else
IMPORTED_DISK = $( qm config " $VMID " | grep -E 'unused[0-9]+' | tail -1 | cut -d: -f2- | xargs)
IMPORTED_ID = $( qm config " $VMID " | grep -E 'unused[0-9]+' | tail -1 | cut -d: -f1)
fi
fi
if [ -n " $IMPORTED_DISK " ] ; then
EXISTING_DISKS = $( qm config " $VMID " | grep -oP " ${ INTERFACE } \d+ " | sort -n)
if [ -z " $EXISTING_DISKS " ] ; then
NEXT_SLOT = 0
else
LAST_SLOT = $( echo " $EXISTING_DISKS " | tail -n1 | sed " s/ ${ INTERFACE } // " )
NEXT_SLOT = $(( LAST_SLOT + 1 ))
fi
if [ " $INTERFACE " != "virtio" ] ; then
if ( whiptail --title " $( translate 'SSD Emulation' ) " --yesno " $( translate 'Do you want to use SSD emulation for this disk?' ) " 10 60) ; then
SSD_OPTION = ",ssd=1"
else
SSD_OPTION = ""
fi
else
SSD_OPTION = ""
fi
msg_info " $( translate 'Configuring disk' ) "
if qm set " $VMID " --${ INTERFACE } ${ NEXT_SLOT } " $IMPORTED_DISK ${ SSD_OPTION } " & >/dev/null; then
msg_ok " $( translate 'Image' ) $IMAGE $( translate 'configured as' ) ${ INTERFACE } ${ NEXT_SLOT } "
if [ [ -n " $IMPORTED_ID " ] ] ; then
qm set " $VMID " -delete " $IMPORTED_ID " >/dev/null 2>& 1
fi
if ( whiptail --title " $( translate 'Make Bootable' ) " --yesno " $( translate 'Do you want to make this disk bootable?' ) " 10 60) ; then
msg_info " $( translate 'Configuring disk as bootable' ) "
if qm set " $VMID " --boot c --bootdisk ${ INTERFACE } ${ NEXT_SLOT } & >/dev/null; then
msg_ok " $( translate 'Disk configured as bootable' ) "
else
msg_error " $( translate 'Could not configure the disk as bootable' ) "
fi
fi
else
msg_error " $( translate 'Could not configure disk' ) ${ INTERFACE } ${ NEXT_SLOT } $( translate 'for VM' ) $VMID "
echo " DEBUG: Tried to configure: -- ${ INTERFACE } ${ NEXT_SLOT } \" $IMPORTED_DISK ${ SSD_OPTION } \" "
echo "DEBUG: VM config after import:"
qm config " $VMID " | grep -E " (unused| ${ INTERFACE } ) "
fi
else
msg_error " $( translate 'Could not find the imported disk' ) "
echo "DEBUG: VM config after import:"
qm config " $VMID "
fi
2025-04-09 10:12:04 +02:00
else
2025-05-30 18:25:10 +02:00
msg_error " $( translate 'Could not import' ) $IMAGE "
2025-04-09 10:12:04 +02:00
fi
done
2025-05-30 18:25:10 +02:00
2025-04-09 10:12:04 +02:00
msg_ok " $( translate 'All selected images have been processed' ) "
2025-05-29 21:49:21 +02:00
msg_success " $( translate "Press Enter to return to menu..." ) "
2025-05-30 14:56:57 +02:00
read -r