2025-04-05 11:16:20 +02:00
#!/bin/bash
# ==========================================================
2025-04-05 16:50:31 +02:00
# ProxMenu - A menu-driven script for Proxmox VE management
2025-04-05 11:16:20 +02:00
# ==========================================================
2025-04-05 16:50:31 +02:00
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 28/01/2025
2025-04-05 11:16:20 +02:00
# ==========================================================
# Description:
# This script allows users to assign physical disks to existing
# Proxmox containers (CTs) through an interactive menu.
# - Detects the system disk and excludes it from selection.
# - Lists all available CTs for the user to choose from.
# - Identifies and displays unassigned physical disks.
# - Allows the user to select multiple disks and attach them to a CT.
# - Configures the selected disks for the CT and verifies the assignment.
# ==========================================================
# 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"
if [ [ -f " $UTILS_FILE " ] ] ; then
source " $UTILS_FILE "
fi
load_language
initialize_cache
# ==========================================================
get_disk_info( ) {
local disk = $1
MODEL = $( lsblk -dn -o MODEL " $disk " | xargs)
SIZE = $( lsblk -dn -o SIZE " $disk " | xargs)
echo " $MODEL " " $SIZE "
}
CT_LIST = $( pct list | awk 'NR>1 {print $1, $3}' )
if [ -z " $CT_LIST " ] ; then
whiptail --title " $( translate "Error" ) " --msgbox " $( translate "No CTs available in the system." ) " 8 40
exit 1
fi
CTID = $( whiptail --title " $( translate "Select CT" ) " --menu " $( translate "Select the CT to which you want to add disks:" ) " 15 60 8 $CT_LIST 3>& 1 1>& 2 2>& 3)
if [ -z " $CTID " ] ; then
whiptail --title " $( translate "Error" ) " --msgbox " $( translate "No CT was selected." ) " 8 40
exit 1
fi
CTID = $( echo " $CTID " | tr -d '"' )
msg_ok " $( translate "CT selected successfully." ) "
2025-04-07 18:52:36 +02:00
2025-04-05 11:16:20 +02:00
CT_STATUS = $( pct status " $CTID " | awk '{print $2}' )
2025-04-07 18:52:36 +02:00
if [ " $CT_STATUS " != "running" ] ; then
msg_info " $( translate "Starting CT" ) $CTID ... "
pct start " $CTID "
sleep 2
if [ " $( pct status " $CTID " | awk '{print $2}' ) " != "running" ] ; then
msg_error " $( translate "Failed to start the CT." ) "
exit 1
fi
msg_ok " $( translate "CT started successfully." ) "
fi
CONF_FILE = " /etc/pve/lxc/ $CTID .conf "
if grep -q '^unprivileged: 1' " $CONF_FILE " ; then
if whiptail --title " $( translate "Privileged Container" ) " \
--yesno " $( translate "The selected container is unprivileged. Direct device passthrough may not work. Do you want to convert it to a privileged container?" ) " 12 70; then
CT_STATUS = $( pct status " $CTID " | awk '{print $2}' )
if [ " $CT_STATUS " = = "running" ] ; then
whiptail --title " $( translate "Warning" ) " \
--msgbox " $( translate "The container must be stopped before converting it to privileged. Please shut it down and try again." ) " 10 60
exit 1
fi
cp " $CONF_FILE " " $CONF_FILE .bak "
sed -i '/^unprivileged: 1/d' " $CONF_FILE "
echo "unprivileged: 0" >> " $CONF_FILE "
msg_ok " $( translate "Container successfully converted to privileged." ) "
else
whiptail --title " $( translate "Aborted" ) " \
--msgbox " $( translate "Operation cancelled. Cannot continue with unprivileged container." ) " 10 60
exit 1
fi
2025-04-05 11:16:20 +02:00
fi
2025-04-07 18:52:36 +02:00
2025-04-05 11:16:20 +02:00
##########################################
2025-04-07 18:52:36 +02:00
2025-04-05 11:16:20 +02:00
msg_info " $( translate "Detecting available 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)
is_disk_in_use( ) {
local disk = " $1 "
while read -r part fstype; do
case " $fstype " in
zfs_member| linux_raid_member)
return 0 ; ;
esac
if echo " $MOUNTED_DISKS " | grep -q " /dev/ $part " ; then
return 0
fi
done < <( lsblk -ln -o NAME,FSTYPE " $disk " | tail -n +2)
if echo " $USED_DISKS " | grep -q " $disk " || echo " $ZFS_DISKS " | grep -q " $disk " ; then
return 0
fi
return 1
}
FREE_DISKS = ( )
2025-04-05 18:28:18 +02:00
LVM_DEVICES = $( pvs --noheadings -o pv_name 2> >( grep -v 'File descriptor .* leaked' ) | xargs -n1 readlink -f | sort -u)
2025-04-05 11:16:20 +02:00
RAID_ACTIVE = $( grep -Po 'md\d+\s*:\s*active\s+raid[0-9]+' /proc/mdstat | awk '{print $1}' | sort -u)
while read -r DISK; do
[ [ " $DISK " = ~ /dev/zd ] ] && continue
INFO = ( $( get_disk_info " $DISK " ) )
MODEL = " ${ INFO [@] : : ${# INFO [@] } -1 } "
SIZE = " ${ INFO [-1] } "
LABEL = ""
SHOW_DISK = true
IS_MOUNTED = false
IS_RAID = false
IS_ZFS = false
IS_LVM = false
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
if $IS_RAID && grep -q " $DISK " <<< " $( cat /proc/mdstat) " ; then
if grep -q "active raid" /proc/mdstat; then
SHOW_DISK = false
fi
fi
if $IS_ZFS ; then
SHOW_DISK = false
fi
if $IS_MOUNTED ; then
SHOW_DISK = false
fi
if pct config " $CTID " | grep -q " $DISK " ; then
SHOW_DISK = false
fi
if $SHOW_DISK ; then
[ [ " $IS_RAID " = = true ] ] && LABEL += " ⚠ with partitions"
[ [ " $IS_LVM " = = true ] ] && LABEL += " ⚠ LVM"
[ [ " $IS_ZFS " = = true ] ] && LABEL += " ⚠ ZFS"
DESCRIPTION = $( printf "%-30s %10s%s" " $MODEL " " $SIZE " " $LABEL " )
FREE_DISKS += ( " $DISK " " $DESCRIPTION " "OFF" )
fi
done < <( lsblk -dn -e 7,11 -o PATH)
if [ " ${# FREE_DISKS [@] } " -eq 0 ] ; then
cleanup
whiptail --title " $( translate "Error" ) " --msgbox " $( translate "No disks available for this CT." ) " 8 40
clear
exit 1
fi
msg_ok " $( translate "Available disks detected." ) "
2025-04-07 18:52:36 +02:00
2025-04-05 11:16:20 +02:00
######################################################
2025-04-07 18:52:36 +02:00
2025-04-05 11:16:20 +02:00
MAX_WIDTH = $( printf "%s\n" " ${ FREE_DISKS [@] } " | awk '{print length}' | sort -nr | head -n1)
TOTAL_WIDTH = $(( MAX_WIDTH + 20 ))
if [ $TOTAL_WIDTH -lt 70 ] ; then
TOTAL_WIDTH = 70
fi
SELECTED = $( whiptail --title " $( translate "Select Disks" ) " --radiolist \
" $( translate "Select the disks you want to add:" ) " 20 $TOTAL_WIDTH 10 " ${ FREE_DISKS [@] } " 3>& 1 1>& 2 2>& 3)
if [ -z " $SELECTED " ] ; then
whiptail --title " $( translate "Error" ) " --msgbox " $( translate "No disks were selected." ) " 10 $TOTAL_WIDTH
clear
exit 1
fi
msg_ok " $( translate "Disks selected successfully." ) "
DISKS_ADDED = 0
ERROR_MESSAGES = ""
SUCCESS_MESSAGES = ""
msg_info " $( translate "Processing selected disks..." ) "
for DISK in $SELECTED ; do
DISK = $( echo " $DISK " | tr -d '"' )
DISK_INFO = $( get_disk_info " $DISK " )
ASSIGNED_TO = ""
RUNNING_CTS = ""
while read -r CT_ID CT_NAME; do
if [ [ " $CT_ID " = ~ ^[ 0-9] +$ ] ] && pct config " $CT_ID " | grep -q " $DISK " ; then
ASSIGNED_TO += " $CT_ID $CT_NAME \n "
CT_STATUS = $( pct status " $CT_ID " | awk '{print $2}' )
if [ " $CT_STATUS " = = "running" ] ; then
RUNNING_CTS += " $CT_ID $CT_NAME \n "
fi
fi
done < <( pct list | awk 'NR>1 {print $1, $3}' )
if [ -n " $RUNNING_CTS " ] ; then
ERROR_MESSAGES += " $( translate "The disk" ) $DISK_INFO $( translate "is in use by the following running CT(s):" ) \\n $RUNNING_CTS \\n\\n "
continue
fi
if [ -n " $ASSIGNED_TO " ] ; then
cleanup
whiptail --title " $( translate "Disk Already Assigned" ) " --yesno " $( translate "The disk" ) $DISK_INFO $( translate "is already assigned to the following CT(s):" ) \\n $ASSIGNED_TO \\n\\n $( translate "Do you want to continue anyway?" ) " 15 70
if [ $? -ne 0 ] ; then
sleep 1
exec " $0 "
fi
fi
cleanup
2025-04-07 18:52:36 +02:00
if lsblk " $DISK " | grep -q "raid" || grep -q " ${ DISK ##*/ } " /proc/mdstat; then
whiptail --title " $( translate "RAID Detected" ) " --msgbox " $( translate "The disk" ) $DISK_INFO $( translate "appears to be part of a" ) RAID. $( translate "For safety reasons, the system will not format it automatically." ) \\n\\n $( translate "If you are sure you want to use this disk, please remove the" ) RAID metadata $( translate "or format it manually using external tools." ) \\n\\n $( translate "After that, run this script again to add the disk." ) " 18 70
exit
fi
2025-04-05 11:16:20 +02:00
MOUNT_POINT = $( whiptail --title " $( translate "Mount Point" ) " --inputbox " $( translate "Enter the mount point for the disk (e.g., /mnt/disk_passthrough):" ) " 10 60 "/mnt/disk_passthrough" 3>& 1 1>& 2 2>& 3)
if [ -z " $MOUNT_POINT " ] ; then
whiptail --title " $( translate "Error" ) " --msgbox " $( translate "No mount point was specified." ) " 8 40
continue
fi
msg_ok " $( translate " Mount point specified: $MOUNT_POINT " ) "
2025-04-07 18:52:36 +02:00
PARTITION = $( lsblk -rno NAME " $DISK " | awk -v disk = " $( basename " $DISK " ) " '$1 != disk {print $1; exit}' )
SKIP_FORMAT = false
if [ -n " $PARTITION " ] ; then
PARTITION = " /dev/ $PARTITION "
CURRENT_FS = $( lsblk -no FSTYPE " $PARTITION " | xargs)
2025-04-05 11:16:20 +02:00
2025-04-07 18:52:36 +02:00
if [ [ " $CURRENT_FS " = = "ext4" || " $CURRENT_FS " = = "xfs" || " $CURRENT_FS " = = "btrfs" ] ] ; then
SKIP_FORMAT = true
msg_ok " $( translate "Detected existing filesystem" ) $CURRENT_FS $( translate "on" ) $PARTITION . $( translate "Adding directly without formatting." ) "
else
whiptail --title " $( translate "Unsupported Filesystem" ) " --yesno " $( translate "The partition" ) $PARTITION $( translate " has an unsupported filesystem ( $CURRENT_FS ).\\nDo you want to format it? " ) " 10 70
2025-04-05 11:16:20 +02:00
if [ $? -ne 0 ] ; then
2025-04-07 18:52:36 +02:00
continue
2025-04-05 11:16:20 +02:00
fi
fi
2025-04-07 18:52:36 +02:00
else
CURRENT_FS = $( lsblk -no FSTYPE " $DISK " | xargs)
if [ [ " $CURRENT_FS " = = "ext4" || " $CURRENT_FS " = = "xfs" || " $CURRENT_FS " = = "btrfs" ] ] ; then
SKIP_FORMAT = true
PARTITION = " $DISK "
msg_ok " $( translate "Detected filesystem" ) $CURRENT_FS $( translate "directly on disk" ) $DISK . $( translate "Proceeding without partition." ) "
else
whiptail --title " $( translate "No Valid Partitions" ) " --yesno " $( translate "The disk has no partitions and no valid filesystem. Do you want to create a new partition and format it?" ) " 10 70
if [ $? -ne 0 ] ; then
continue
fi
2025-04-05 11:16:20 +02:00
2025-04-07 18:52:36 +02:00
echo -e " $( translate "Creating partition table and partition..." ) "
parted -s " $DISK " mklabel gpt
parted -s " $DISK " mkpart primary 0% 100%
sleep 2
partprobe " $DISK "
sleep 2
PARTITION = $( lsblk -rno NAME " $DISK " | awk -v disk = " $( basename " $DISK " ) " '$1 != disk {print $1; exit}' )
if [ -n " $PARTITION " ] ; then
PARTITION = " /dev/ $PARTITION "
2025-04-05 11:16:20 +02:00
else
2025-04-07 18:52:36 +02:00
whiptail --title " $( translate "Partition Error" ) " --msgbox " $( translate "Failed to create partition on disk" ) $DISK_INFO . " 8 70
continue
fi
fi
fi
if [ " $SKIP_FORMAT " != true ] ; then
CURRENT_FS = $( lsblk -no FSTYPE " $PARTITION " | xargs)
if [ [ " $CURRENT_FS " = = "ext4" || " $CURRENT_FS " = = "xfs" || " $CURRENT_FS " = = "btrfs" ] ] ; then
SKIP_FORMAT = true
msg_ok " $( translate "Detected existing filesystem" ) $CURRENT_FS $( translate "on" ) $PARTITION . $( translate "Skipping format." ) "
else
FORMAT_TYPE = $( whiptail --title " $( translate "Select Format Type" ) " --menu " $( translate "Select the filesystem type for" ) $DISK_INFO : " 15 60 6 \
"ext4" " $( translate "Extended Filesystem 4 (recommended)" ) " \
"xfs" " $( translate "XFS Filesystem" ) " \
"btrfs" " $( translate "Btrfs Filesystem" ) " 3>& 1 1>& 2 2>& 3)
if [ -z " $FORMAT_TYPE " ] ; then
whiptail --title " $( translate "Format Cancelled" ) " --msgbox " $( translate "Format operation cancelled. The disk will not be added." ) " 8 60
continue
2025-04-05 11:16:20 +02:00
fi
2025-04-07 18:52:36 +02:00
whiptail --title " $( translate "WARNING" ) " --yesno " $( translate "WARNING: This operation will FORMAT the disk" ) $DISK_INFO $( translate "with" ) $FORMAT_TYPE .\\n\\n $( translate "ALL DATA ON THIS DISK WILL BE PERMANENTLY LOST!" ) \\n\\n $( translate "Are you sure you want to continue" ) " 15 70
2025-04-05 11:16:20 +02:00
if [ $? -ne 0 ] ; then
2025-04-07 18:52:36 +02:00
whiptail --title " $( translate "Format Cancelled" ) " --msgbox " $( translate "Format operation cancelled. The disk will not be added." ) " 8 60
continue
2025-04-05 11:16:20 +02:00
fi
fi
fi
2025-04-07 18:52:36 +02:00
if [ " $SKIP_FORMAT " != true ] ; then
echo -e " $( translate "Formatting partition" ) $PARTITION $( translate "with" ) $FORMAT_TYPE ... "
case " $FORMAT_TYPE " in
"ext4" ) mkfs.ext4 -F " $PARTITION " ; ;
"xfs" ) mkfs.xfs -f " $PARTITION " ; ;
"btrfs" ) mkfs.btrfs -f " $PARTITION " ; ;
esac
if [ $? -ne 0 ] ; then
whiptail --title " $( translate "Format Failed" ) " --msgbox " $( translate "Failed to format partition" ) $PARTITION $( translate "with" ) $FORMAT_TYPE .\\n\\n $( translate "The disk may be in use by the system or have hardware issues." ) " 12 70
continue
else
msg_ok " $( translate "Partition" ) $PARTITION $( translate "successfully formatted with" ) $FORMAT_TYPE . "
partprobe " $DISK "
sleep 2
fi
2025-04-05 11:16:20 +02:00
fi
2025-04-07 18:52:36 +02:00
2025-04-05 11:16:20 +02:00
INDEX = 0
while pct config " $CTID " | grep -q " mp ${ INDEX } : " ; do
( ( INDEX++) )
done
2025-04-07 18:52:36 +02:00
##############################################################################
RESULT = $( pct set " $CTID " -mp${ INDEX } " $PARTITION ,mp= $MOUNT_POINT ,backup=0,ro=0,acl=1 " 2>& 1)
pct exec " $CTID " -- chmod -R 775 " $MOUNT_POINT "
##############################################################################
2025-04-05 11:16:20 +02:00
if [ $? -eq 0 ] ; then
MESSAGE = " $( translate "The disk" ) $DISK_INFO $( translate "has been successfully added to CT" ) $CTID $( translate "as a mount point at" ) $MOUNT_POINT . "
if [ -n " $ASSIGNED_TO " ] ; then
MESSAGE += " \\n\\n $( translate "WARNING: This disk is also assigned to the following CT(s):" ) \\n $ASSIGNED_TO "
MESSAGE += " \\n $( translate "Make sure not to start CTs that share this disk at the same time to avoid data corruption." ) "
fi
SUCCESS_MESSAGES += " $MESSAGE \\n\\n "
( ( DISKS_ADDED++) )
else
ERROR_MESSAGES += " $( translate "Could not add disk" ) $DISK_INFO $( translate "to CT" ) $CTID .\\n $( translate "Error:" ) $RESULT \\n\\n "
fi
done
2025-04-07 18:52:36 +02:00
2025-04-05 11:16:20 +02:00
msg_ok " $( translate "Disk processing completed." ) "
if [ -n " $SUCCESS_MESSAGES " ] ; then
MSG_LINES = $( echo " $SUCCESS_MESSAGES " | wc -l)
whiptail --title " $( translate "Successful Operations" ) " --msgbox " $SUCCESS_MESSAGES " 16 70
fi
if [ -n " $ERROR_MESSAGES " ] ; then
whiptail --title " $( translate "Warnings and Errors" ) " --msgbox " $ERROR_MESSAGES " 16 70
fi
exit 0