#!/bin/bash # ========================================================== # ProxMenux - Local Disk Manager for Proxmox Host # ========================================================== # Author : MacRimi # Copyright : (c) 2024 MacRimi # License : GPL-3.0 # https://github.com/MacRimi/ProxMenux/blob/main/LICENSE # Version : 1.0 # ========================================================== # Description: # Prepares a local SCSI / SATA / NVMe disk on the Proxmox host # and registers it as Proxmox storage — either as a directory # (pvesm add dir) or as a ZFS pool (pvesm add zfspool). # # Features: # - Safety filter hides root / swap / mounted / in-use disks # and disks already referenced by any VM/CT config. # - Format path: wipe + GPT + mkfs (ext4 / xfs / btrfs / zfs). # - Reuse path: mount an existing filesystem without touching # the data. # - UUID-based /etc/fstab entries with defaults,nofail. # - Content-type presets (VM Storage / Standard NAS / All / Custom). # - View, remove (with fstab cleanup) and list-disks helpers. # ========================================================== 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="$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 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/disk_ops_helpers.sh" ]]; then source "$LOCAL_SCRIPTS_LOCAL/global/disk_ops_helpers.sh" elif [[ -f "$LOCAL_SCRIPTS_DEFAULT/global/disk_ops_helpers.sh" ]]; then source "$LOCAL_SCRIPTS_DEFAULT/global/disk_ops_helpers.sh" fi load_language initialize_cache if ! command -v pveversion >/dev/null 2>&1; then dialog --backtitle "ProxMenux" --title "$(translate "Error")" \ --msgbox "$(translate "This script must be run on a Proxmox host.")" 8 60 exit 1 fi # ========================================================== # SYSTEM STORAGE DETECTION # ========================================================== # Returns the name of the ZFS pool containing the root filesystem, if any. _get_system_zfs_pool() { local root_fs root_fs=$(df / 2>/dev/null | awk 'NR==2 {print $1}') if [[ "$root_fs" != /dev/* && "$root_fs" == */* ]]; then echo "${root_fs%%/*}" fi } # Returns 0 if the given pvesm storage is a user-created disk storage # that should appear in add/remove menus. Returns 1 for system storages. _is_user_disk_storage() { local storage_id="$1" local storage_type="$2" local sys_pool local cfg_path pool cfg_path=$(get_storage_config "$storage_id" | awk '$1 == "path" {print $2}') pool=$(get_storage_config "$storage_id" | awk '$1 == "pool" {print $2}') case "$storage_type" in dir) # User-created dir storages are always mounted under /mnt/ [[ "$cfg_path" == /mnt/* ]] && return 0 return 1 ;; zfspool) # User-created ZFS pool storages are NOT on the root pool or its datasets sys_pool=$(_get_system_zfs_pool) if [[ -n "$sys_pool" ]]; then # Skip if pool is the root pool or a dataset within it (e.g. rpool/data) [[ "$pool" == "$sys_pool" || "$pool" == "$sys_pool/"* ]] && return 1 fi return 0 ;; *) return 1 ;; esac } # ========================================================== # STORAGE CONFIG READER # ========================================================== get_storage_config() { local storage_id="$1" awk -v id="$storage_id" ' /^[a-z]+: / { found = ($0 ~ ": "id"$"); next } found && /^[^ \t]/ { exit } found { print } ' /etc/pve/storage.cfg } # ========================================================== # DISK DETECTION # ========================================================== disk_referenced_in_guest_configs() { local disk="$1" if declare -F _disk_used_in_guest_configs >/dev/null 2>&1; then _disk_used_in_guest_configs "$disk" return $? fi local real_path config_data link real_path=$(readlink -f "$disk" 2>/dev/null) config_data=$(grep -vE '^\s*#' /etc/pve/qemu-server/*.conf /etc/pve/lxc/*.conf 2>/dev/null) [[ -z "$config_data" ]] && return 1 if [[ -n "$real_path" ]] && grep -Fq "$real_path" <<< "$config_data"; then return 0 fi for link in /dev/disk/by-id/*; do [[ -e "$link" ]] || continue [[ "$(readlink -f "$link" 2>/dev/null)" == "$real_path" ]] || continue if grep -Fq "$link" <<< "$config_data"; then return 0 fi done return 1 } disk_used_by_host_storage() { local disk="$1" if declare -F _disk_is_host_system_used >/dev/null 2>&1; then _disk_is_host_system_used "$disk" return $? fi local mounted_disks swap_disks lvm_devices zfs_disks real_path path base_disk local part fstype part_path 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>/dev/null | xargs -r -n1 readlink -f | sort -u) zfs_disks="" while read -r part fstype; do [[ -z "$part" ]] && continue part_path="/dev/$part" if grep -qFx "$part_path" <<< "$mounted_disks"; then return 0 fi if grep -qFx "$part_path" <<< "$swap_disks"; then return 0 fi case "$fstype" in zfs_member|linux_raid_member|LVM2_member) return 0 ;; esac done < <(lsblk -ln -o NAME,FSTYPE "$disk" 2>/dev/null) while read -r entry; do [[ -z "$entry" ]] && continue 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 < <(zpool list -v -H 2>/dev/null | awk '{print $1}' | grep -v '^NAME$' | grep -v '^-' | grep -v '^mirror') real_path=$(readlink -f "$disk" 2>/dev/null) if [[ -n "$real_path" && -n "$lvm_devices" ]] && grep -qFx "$real_path" <<< "$lvm_devices"; then return 0 fi if [[ -n "$zfs_disks" ]] && grep -qFx "$disk" <<< "$(echo "$zfs_disks" | sort -u)"; then return 0 fi return 1 } get_disk_info() { local disk="$1" local model size model=$(lsblk -dn -o MODEL "$disk" 2>/dev/null | xargs) size=$(lsblk -dn -o SIZE "$disk" 2>/dev/null | xargs) [[ -z "$model" ]] && model="$(translate "Unknown model")" [[ -z "$size" ]] && size="$(translate "Unknown size")" printf '%s\t%s\n' "$model" "$size" } get_available_disks() { if declare -F _refresh_host_storage_cache >/dev/null 2>&1; then _refresh_host_storage_cache fi while read -r disk ro type; do [[ -z "$disk" ]] && continue [[ "$type" != "disk" ]] && continue [[ "$ro" == "1" ]] && continue [[ "$disk" =~ ^/dev/zd ]] && continue if disk_used_by_host_storage "$disk"; then continue fi if disk_referenced_in_guest_configs "$disk"; then continue fi local model size IFS=$'\t' read -r model size < <(get_disk_info "$disk") [[ -z "$model" || "$model" == " " ]] && model="-" echo "$disk|$size — $model" done < <(lsblk -dn -e 7,11 -o PATH,RO,TYPE 2>/dev/null) } select_disk() { local disk_list disk_list=$(get_available_disks) if [[ -z "$disk_list" ]]; then dialog --backtitle "ProxMenux" --title "$(translate "No Disks Found")" \ --msgbox "\n$(translate "No available disks found.")\n\n$(translate "All disks may already be in use or mounted.")" 10 60 return 1 fi local options=() while IFS='|' read -r device info; do [[ -n "$device" ]] && options+=("$device" "$info") done <<< "$disk_list" if [[ ${#options[@]} -eq 0 ]]; then dialog --backtitle "ProxMenux" --title "$(translate "No Disks Found")" \ --msgbox "\n$(translate "No suitable disks found.")" 8 60 return 1 fi SELECTED_DISK=$(dialog --backtitle "ProxMenux" --title "$(translate "Select Disk")" \ --menu "\n$(translate "Select the disk to add as Proxmox storage:")\n$(translate "WARNING: All data on selected disk will be ERASED if formatted.")" \ 20 84 10 "${options[@]}" 3>&1 1>&2 2>&3) [[ -z "$SELECTED_DISK" ]] && return 1 return 0 } inspect_disk() { local disk="$1" # Check existing partitions/filesystem local partition_info partition_info=$(lsblk -no NAME,SIZE,FSTYPE,MOUNTPOINT "$disk" 2>/dev/null | tail -n +2) local existing_fs existing_node existing_fs=$(blkid -s TYPE -o value "$disk" 2>/dev/null || true) existing_node="$disk" if [[ -z "$existing_fs" ]]; then while read -r node fstype mountpoint; do [[ -z "$node" || -z "$fstype" ]] && continue [[ -n "$mountpoint" ]] && continue existing_fs="$fstype" existing_node="$node" break done < <(lsblk -lnpo NAME,FSTYPE,MOUNTPOINT "$disk" 2>/dev/null | awk 'NR>1 {print $1, $2, $3}') fi DISK_HAS_DATA=false DISK_EXISTING_FS="" DISK_EXISTING_NODE="" if [[ -n "$partition_info" || -n "$existing_fs" ]]; then DISK_HAS_DATA=true DISK_EXISTING_FS="$existing_fs" DISK_EXISTING_NODE="$existing_node" fi return 0 } select_partition_action() { local disk="$1" inspect_disk "$disk" local disk_size disk_size=$(lsblk -ndo SIZE "$disk" 2>/dev/null) local menu_items=() menu_items+=("format" "$(translate "Format disk (ERASE all data)")") [[ -n "$DISK_EXISTING_FS" ]] && menu_items+=("use_existing" "$(translate "Use existing filesystem")") menu_items+=("cancel" "$(translate "Cancel")") local menu_text if [[ "$DISK_HAS_DATA" == "true" ]]; then menu_text="$(translate "Disk:"): $disk ($disk_size)\n" [[ -n "$DISK_EXISTING_FS" ]] && menu_text+="$(translate "Existing filesystem:"): $DISK_EXISTING_FS\n" menu_text+="\n$(translate "Options:")\n" menu_text+="• $(translate "Format: ERASE all data and create new filesystem")\n" [[ -n "$DISK_EXISTING_FS" ]] && menu_text+="• $(translate "Use existing: mount without formatting")\n" menu_text+="\n$(translate "Continue?")" else menu_text="$(translate "Disk:"): $disk ($disk_size)\n\n$(translate "Disk appears empty. It will be formatted.")" fi DISK_ACTION=$(dialog --backtitle "ProxMenux" --title "$(translate "Disk Setup")" \ --menu "$menu_text" 20 84 8 \ "${menu_items[@]}" 3>&1 1>&2 2>&3) [[ -z "$DISK_ACTION" || "$DISK_ACTION" == "cancel" ]] && return 1 return 0 } select_filesystem() { FILESYSTEM=$(dialog --backtitle "ProxMenux" --title "$(translate "Select Filesystem")" \ --menu "\n$(translate "Choose filesystem for the disk:")" 16 72 5 \ "ext4" "$(translate "ext4 — Proxmox dir storage (recommended)")" \ "xfs" "$(translate "xfs — Proxmox dir storage (large files and VMs)")" \ "btrfs" "$(translate "btrfs — Proxmox dir storage (snapshots, compression)")" \ "zfs" "$(translate "zfs — Proxmox ZFS pool storage")" \ 3>&1 1>&2 2>&3) [[ -z "$FILESYSTEM" ]] && return 1 return 0 } # Aligns disk_host with nfs_host.sh / samba_host.sh: the user can pick # whether the disk shows up as a Proxmox storage (visible in Datacenter # → Storage and usable for VM disks, backups, ISOs…) or whether it's # only mounted on the host so LXCs can bind-mount it — or both at once. # Without this chooser the script always registered as Proxmox storage, # which forced users who only wanted host-side LXC bind-mounts to walk # through the pvesm flow and then manually remove the storage entry. select_mount_method() { MODE_PVESM=0 MODE_FSTAB=0 # ZFS only makes sense as a pvesm zfspool storage — there is no # "fstab only" equivalent for a ZFS pool (it's imported, not # fstab-mounted), so don't bother asking. if [[ "${FILESYSTEM:-}" == "zfs" ]]; then MODE_PVESM=1 return 0 fi local result result=$(dialog --backtitle "ProxMenux" \ --title "$(translate "Mount Method")" \ --checklist "\n$(translate "Choose how to make the disk available on this host. Mark one or both options:")\n\n$(translate "• Proxmox storage (pvesm): visible in Datacenter > Storage, usable for VMs/backups/ISOs")\n$(translate "• Host fstab: mounted at host path only, for LXC bind-mounts (NOT visible as Proxmox storage)")\n$(translate "• Both: registers as Proxmox storage AND keeps the mount available for LXC bind-mounts")" 20 84 2 \ "pvesm" "$(translate "As Proxmox storage")" off \ "fstab" "$(translate "As host fstab mount only")" on \ 3>&1 1>&2 2>&3) [[ $? -ne 0 ]] && return 1 if echo "$result" | grep -qw "pvesm"; then MODE_PVESM=1; fi if echo "$result" | grep -qw "fstab"; then MODE_FSTAB=1; fi if [[ "$MODE_PVESM" -eq 0 && "$MODE_FSTAB" -eq 0 ]]; then dialog --backtitle "ProxMenux" --title "$(translate "No Method Selected")" \ --msgbox "$(translate "You must select at least one mount method to continue.")" 8 60 return 1 fi return 0 } # ========================================================== # STORAGE CONFIGURATION # ========================================================== configure_disk_storage() { local disk_name disk_name=$(basename "$SELECTED_DISK") # The first prompt collects an identifier used both as the Proxmox # storage ID (when MODE_PVESM=1) and as the /mnt/<…> directory name # in either mode. The wording is adjusted so a user in fstab-only # mode isn't asked for a "Storage ID" they'll never see in Proxmox. local id_title id_prompt if [[ "$MODE_PVESM" -eq 1 ]]; then id_title="$(translate "Storage ID")" id_prompt="$(translate "Enter storage ID for Proxmox:")" else id_title="$(translate "Mount Name")" id_prompt="$(translate "Enter a name for the mount point (used as /mnt/):")" fi STORAGE_ID=$(dialog --backtitle "ProxMenux" --title "$id_title" \ --inputbox "$id_prompt" \ 10 70 "disk-${disk_name}" 3>&1 1>&2 2>&3) [[ $? -ne 0 ]] && return 1 [[ -z "$STORAGE_ID" ]] && STORAGE_ID="disk-${disk_name}" if [[ ! "$STORAGE_ID" =~ ^[a-zA-Z0-9][a-zA-Z0-9_-]*$ ]]; then dialog --backtitle "ProxMenux" --title "$(translate "Invalid ID")" \ --msgbox "$(translate "Invalid name. Use only letters, numbers, hyphens and underscores.")" 8 74 return 1 fi if [[ "${FILESYSTEM:-}" == "zfs" && ! "$STORAGE_ID" =~ ^[a-zA-Z][a-zA-Z0-9_.:-]*$ ]]; then dialog --backtitle "ProxMenux" --title "$(translate "Invalid ID")" \ --msgbox "$(translate "For ZFS, storage ID must start with a letter and use only letters, numbers, dot, dash, underscore or colon.")" 9 86 return 1 fi MOUNT_PATH="/mnt/${STORAGE_ID}" MOUNT_PATH=$(dialog --backtitle "ProxMenux" --title "$(translate "Mount Path")" \ --inputbox "$(translate "Enter mount path on host:")" \ 10 60 "$MOUNT_PATH" 3>&1 1>&2 2>&3) [[ $? -ne 0 || -z "$MOUNT_PATH" ]] && return 1 # Content types are a Proxmox storage concept — skip the prompt # entirely in fstab-only mode and leave MOUNT_CONTENT empty so # add_proxmox_dir_storage is never invoked downstream. if [[ "$MODE_PVESM" -ne 1 ]]; then MOUNT_CONTENT="" return 0 fi CONTENT_TYPE=$(dialog --backtitle "ProxMenux" --title "$(translate "Content Types")" \ --menu "$(translate "Select content types for this storage:")" 16 70 5 \ "1" "$(translate "VM Storage (images, backup)")" \ "2" "$(translate "Standard NAS (backup, iso, vztmpl)")" \ "3" "$(translate "All types (images, backup, iso, vztmpl, snippets)")" \ "4" "$(translate "Custom")" \ 3>&1 1>&2 2>&3) [[ $? -ne 0 ]] && return 1 case "$CONTENT_TYPE" in 1) MOUNT_CONTENT="images,backup" ;; 2) MOUNT_CONTENT="backup,iso,vztmpl" ;; 3) MOUNT_CONTENT="images,backup,iso,vztmpl,snippets" ;; 4) MOUNT_CONTENT=$(dialog --backtitle "ProxMenux" --title "$(translate "Custom Content")" \ --inputbox "$(translate "Enter content types (comma-separated):")" \ 10 70 "images,backup" 3>&1 1>&2 2>&3) [[ $? -ne 0 || -z "$MOUNT_CONTENT" ]] && MOUNT_CONTENT="images,backup" ;; *) return 1 ;; esac return 0 } # ========================================================== # DISK SETUP AND MOUNT # ========================================================== format_and_mount_disk() { local disk="$1" local mount_path="$2" local filesystem="$3" # Final confirmation before any destructive operation local disk_size disk_size=$(lsblk -ndo SIZE "$disk" 2>/dev/null) if ! dialog --backtitle "ProxMenux" --title "$(translate "CONFIRM FORMAT")" --yesno \ "$(translate "FINAL CONFIRMATION — DATA WILL BE ERASED")\n\n$(translate "Disk:"): $disk ($disk_size)\n$(translate "Filesystem:"): $filesystem\n$(translate "Mount path:"): $mount_path\n\n$(translate "ALL DATA ON") $disk $(translate "WILL BE PERMANENTLY ERASED.")\n\n$(translate "Are you absolutely sure?")" \ 14 80; then return 1 fi show_proxmenux_logo if [[ "$MODE_PVESM" -eq 1 && "$MODE_FSTAB" -eq 1 ]]; then msg_title "$(translate "Add Local Disk (Proxmox storage + host mount)")" elif [[ "$MODE_PVESM" -eq 1 ]]; then msg_title "$(translate "Add Local Disk as Proxmox Storage")" else msg_title "$(translate "Add Local Disk as Host Mount (for LXC bind-mounts)")" fi local _disk_model _disk_size _disk_label IFS=$'\t' read -r _disk_model _disk_size < <(get_disk_info "$disk") _disk_label="$disk" [[ -n "$_disk_size" && -n "$_disk_model" ]] && _disk_label="$disk — $_disk_size — $_disk_model" msg_ok "$(translate "Disk:") ${BL}${_disk_label}${CL}" msg_ok "$(translate "Action:") $DISK_ACTION" [[ "$DISK_ACTION" == "format" ]] && msg_ok "$(translate "Filesystem:") $FILESYSTEM" msg_ok "$(translate "Mount path:") $MOUNT_PATH" if [[ "$MODE_PVESM" -eq 1 ]]; then msg_ok "$(translate "Storage ID:") $STORAGE_ID" msg_ok "$(translate "Content:") $MOUNT_CONTENT" else msg_ok "$(translate "Mount Name:") $STORAGE_ID" msg_ok "$(translate "Method:") $(translate "host fstab only (not registered as Proxmox storage)")" fi msg_info "$(translate "Wiping existing partition table...")" doh_wipe_disk "$disk" msg_ok "$(translate "Partition table wiped")" msg_info "$(translate "Creating partition...")" if ! doh_create_partition "$disk"; then msg_error "$(translate "Failed to create partition table")" [[ -n "$DOH_PARTITION_ERROR_DETAIL" ]] && \ msg_error "$(translate "Details"): $(printf '%s' "$DOH_PARTITION_ERROR_DETAIL" | head -n1)" return 1 fi msg_ok "$(translate "Partition created")" local partition="$DOH_CREATED_PARTITION" # ZFS pre-flight checks (pool existence must be verified before format) if [[ "$filesystem" == "zfs" ]]; then if ! command -v zpool >/dev/null 2>&1; then msg_error "$(translate "zpool command not found. Install zfsutils-linux and retry.")" return 1 fi if zpool list "$STORAGE_ID" >/dev/null 2>&1; then msg_error "$(translate "A ZFS pool with this name already exists:") $STORAGE_ID" return 1 fi fi msg_info "$(translate "Formatting as") $filesystem..." if ! doh_format_partition "$partition" "$filesystem" "$STORAGE_ID" "$STORAGE_ID" "$mount_path"; then msg_error "$(translate "Failed to format disk as") $filesystem" return 1 fi msg_ok "$(translate "Disk formatted as") $filesystem" DISK_PARTITION="$partition" return 0 } mount_disk_permanently() { local partition="$1" local mount_path="$2" local filesystem="$3" if [[ "$filesystem" == "zfs" ]]; then if ! zpool list "$STORAGE_ID" >/dev/null 2>&1; then msg_error "$(translate "ZFS pool is not available after creation:") $STORAGE_ID" return 1 fi msg_ok "$(translate "ZFS pool created and mounted at") $mount_path" return 0 fi msg_info "$(translate "Creating mount point...")" if ! mkdir -p "$mount_path"; then msg_error "$(translate "Failed to create mount point:") $mount_path" return 1 fi msg_ok "$(translate "Mount point created")" msg_info "$(translate "Mounting disk...")" if ! mount -t "$filesystem" "$partition" "$mount_path" 2>/dev/null; then msg_error "$(translate "Failed to mount disk")" return 1 fi msg_ok "$(translate "Disk mounted at") $mount_path" msg_info "$(translate "Adding to /etc/fstab for permanent mounting...")" local disk_uuid disk_uuid=$(blkid -s UUID -o value "$partition" 2>/dev/null) if [[ -n "$disk_uuid" ]]; then # Remove any existing fstab entry for this UUID or mount point sed -i "\|UUID=$disk_uuid|d" /etc/fstab sed -i "\|[[:space:]]${mount_path}[[:space:]]|d" /etc/fstab echo "UUID=$disk_uuid $mount_path $filesystem defaults,nofail 0 2" >> /etc/fstab msg_ok "$(translate "Added to /etc/fstab using UUID")" else sed -i "\|[[:space:]]${mount_path}[[:space:]]|d" /etc/fstab echo "$partition $mount_path $filesystem defaults,nofail 0 2" >> /etc/fstab msg_ok "$(translate "Added to /etc/fstab using device path")" fi _apply_lxc_bind_mount_perms "$mount_path" systemctl daemon-reload 2>/dev/null || true return 0 } # When the user opted into the host-fstab mount method, the whole # point of the mount is to bind-mount it into LXC containers. A fresh # ext4/xfs/btrfs filesystem (or a freshly-created /mnt/ directory) # is owned by root:root with mode 0755 — which an unprivileged LXC # sees as "others" (its root uid 0 maps to host uid 100000) and is # therefore read-only. lxc-mount-manager_minimal.sh offers the same # chmod o+rwx + default-ACL fix when the user adds the bind-mount # through that script, but most users don't realise they need to do # both steps. Apply the fix here so that "fstab only" really does # leave a directory ready to bind-mount from an unprivileged CT. # Privileged containers don't need this (root inside = root on host) # but the change is harmless: existing owners keep their access. _apply_lxc_bind_mount_perms() { local mount_path="$1" [[ "${MODE_FSTAB:-0}" -eq 1 ]] || return 0 [[ -d "$mount_path" ]] || return 0 msg_info "$(translate "Applying host permissions for unprivileged LXC bind-mounts...")" chmod o+rwx "$mount_path" 2>/dev/null || true if command -v setfacl >/dev/null 2>&1; then setfacl -m o::rwx "$mount_path" 2>/dev/null || true setfacl -m d:o::rwx "$mount_path" 2>/dev/null || true fi msg_ok "$(translate "Host permissions applied (o+rwx + default ACL) — unprivileged LXCs can read/write through bind-mounts")" } mount_existing_disk() { local disk="$1" local mount_path="$2" local existing_fs existing_fs=$(blkid -s TYPE -o value "$disk" 2>/dev/null || true) if [[ -z "$existing_fs" ]]; then msg_error "$(translate "Cannot detect filesystem on") $disk" return 1 fi msg_info "$(translate "Creating mount point...")" mkdir -p "$mount_path" msg_ok "$(translate "Mount point created")" msg_info "$(translate "Mounting existing") $existing_fs $(translate "filesystem...")" if ! mount "$disk" "$mount_path" 2>/dev/null; then msg_error "$(translate "Failed to mount disk")" return 1 fi msg_ok "$(translate "Disk mounted at") $mount_path" # Add to fstab local disk_uuid disk_uuid=$(blkid -s UUID -o value "$disk" 2>/dev/null) if [[ -n "$disk_uuid" ]]; then sed -i "\|UUID=$disk_uuid|d" /etc/fstab sed -i "\|[[:space:]]${mount_path}[[:space:]]|d" /etc/fstab echo "UUID=$disk_uuid $mount_path $existing_fs defaults,nofail 0 2" >> /etc/fstab msg_ok "$(translate "Added to /etc/fstab")" fi _apply_lxc_bind_mount_perms "$mount_path" DISK_PARTITION="$disk" systemctl daemon-reload 2>/dev/null || true return 0 } add_proxmox_dir_storage() { local storage_id="$1" local path="$2" local content="$3" local storage_kind="dir" local pool_name="$storage_id" if [[ "${FILESYSTEM:-}" == "zfs" ]]; then storage_kind="zfspool" fi if ! command -v pvesm >/dev/null 2>&1; then msg_error "$(translate "pvesm command not found. This should not happen on Proxmox.")" return 1 fi if pvesm status "$storage_id" >/dev/null 2>&1; then msg_warn "$(translate "Storage ID already exists:") $storage_id" if ! dialog --backtitle "ProxMenux" --title "$(translate "Storage Exists")" --yesno \ "$(translate "Storage ID already exists. Do you want to remove and recreate it?")" \ 8 60; then return 0 fi pvesm remove "$storage_id" 2>/dev/null || true fi msg_info "$(translate "Registering disk as Proxmox storage...")" local pvesm_output local add_ok=false if [[ "$storage_kind" == "zfspool" ]]; then if pvesm_output=$(pvesm add zfspool "$storage_id" \ --pool "$pool_name" \ --content "$content" 2>&1); then add_ok=true fi else if pvesm_output=$(pvesm add dir "$storage_id" \ --path "$path" \ --content "$content" 2>&1); then add_ok=true fi fi if [[ "$add_ok" == "true" ]]; then if [[ "$storage_kind" == "zfspool" ]]; then msg_ok "$(translate "ZFS storage added successfully to Proxmox!")" echo -e "" echo -e "${TAB}${BOLD}$(translate "Storage Added:")${CL}" echo -e "${TAB}${BGN}$(translate "Storage ID:")${CL} ${BL}$storage_id${CL}" echo -e "${TAB}${BGN}$(translate "Type:")${CL} ${BL}zfspool${CL}" echo -e "${TAB}${BGN}$(translate "Pool:")${CL} ${BL}$pool_name${CL}" echo -e "${TAB}${BGN}$(translate "Content Types:")${CL} ${BL}$content${CL}" else msg_ok "$(translate "Directory storage added successfully to Proxmox!")" echo -e "" echo -e "${TAB}${BOLD}$(translate "Storage Added:")${CL}" echo -e "${TAB}${BGN}$(translate "Storage ID:")${CL} ${BL}$storage_id${CL}" echo -e "${TAB}${BGN}$(translate "Path:")${CL} ${BL}$path${CL}" echo -e "${TAB}${BGN}$(translate "Content Types:")${CL} ${BL}$content${CL}" fi echo -e "" msg_ok "$(translate "Storage is now available in Proxmox web interface under Datacenter > Storage")" return 0 else msg_error "$(translate "Failed to add storage to Proxmox.")" echo -e "${TAB}$(translate "Error details:"): $pvesm_output" echo -e "" msg_info2 "$(translate "You can add it manually through:")" if [[ "$storage_kind" == "zfspool" ]]; then echo -e "${TAB}• $(translate "Proxmox web interface: Datacenter > Storage > Add > ZFS")" echo -e "${TAB}• pvesm add zfspool $storage_id --pool $pool_name --content $content" else echo -e "${TAB}• $(translate "Proxmox web interface: Datacenter > Storage > Add > Directory")" echo -e "${TAB}• pvesm add dir $storage_id --path $path --content $content" fi return 1 fi } # ========================================================== # MAIN OPERATIONS # ========================================================== add_disk_to_proxmox() { # Check required tools for tool in parted mkfs.ext4 mkfs.xfs blkid lsblk sgdisk; do if ! command -v "$tool" >/dev/null 2>&1; then show_proxmenux_logo msg_title "$(translate "Add Local Disk as Proxmox Storage")" msg_info "$(translate "Installing required tools...")" apt-get update &>/dev/null apt-get install -y parted e2fsprogs util-linux xfsprogs gdisk btrfs-progs &>/dev/null stop_spinner break fi done # Step 1: Select disk select_disk || return # Step 2: Inspect and choose action select_partition_action "$SELECTED_DISK" || return # Step 3: Filesystem selection (only if formatting) if [[ "$DISK_ACTION" == "format" ]]; then select_filesystem || return if [[ "$FILESYSTEM" == "zfs" ]] && ! command -v zpool >/dev/null 2>&1; then msg_error "$(translate "zpool not found. Install zfsutils-linux and retry.")" echo msg_success "$(translate "Press Enter to continue...")" read -r return 1 fi fi # Step 3.5: Choose how the disk is exposed to Proxmox / the host. # Sets MODE_PVESM and MODE_FSTAB. ZFS is forced to MODE_PVESM=1 # inside the helper because a ZFS pool can't be expressed as a # plain fstab mount. select_mount_method || return # Step 4: Configure storage options configure_disk_storage || return if declare -F _refresh_host_storage_cache >/dev/null 2>&1; then _refresh_host_storage_cache fi if disk_used_by_host_storage "$SELECTED_DISK"; then msg_error "$(translate "Safety check failed: selected disk is now used by host/system.")" echo msg_success "$(translate "Press Enter to continue...")" read -r return 1 fi if disk_referenced_in_guest_configs "$SELECTED_DISK"; then msg_error "$(translate "Safety check failed: selected disk is referenced by a VM/LXC config.")" echo msg_success "$(translate "Press Enter to continue...")" read -r return 1 fi # Step 5: Format/mount case "$DISK_ACTION" in format) format_and_mount_disk "$SELECTED_DISK" "$MOUNT_PATH" "$FILESYSTEM" || { echo "" msg_success "$(translate "Press Enter to continue...")" read -r return 1 } mount_disk_permanently "$DISK_PARTITION" "$MOUNT_PATH" "$FILESYSTEM" || { echo "" msg_success "$(translate "Press Enter to continue...")" read -r return 1 } ;; use_existing) local existing_node existing_node="${DISK_EXISTING_NODE:-$SELECTED_DISK}" mount_existing_disk "$existing_node" "$MOUNT_PATH" || { echo "" msg_success "$(translate "Press Enter to continue...")" read -r return 1 } ;; esac # Step 6: Register in Proxmox (only if the user opted in) if [[ "$MODE_PVESM" -eq 1 ]]; then add_proxmox_dir_storage "$STORAGE_ID" "$MOUNT_PATH" "$MOUNT_CONTENT" else echo "" msg_ok "$(translate "Disk mounted at") $MOUNT_PATH" msg_info2 "$(translate "Not registered as Proxmox storage — use 'LXC Mount Manager' to bind-mount") $MOUNT_PATH $(translate "into an LXC.")" fi echo "" msg_success "$(translate "Press Enter to continue...")" read -r } view_disk_storages() { show_proxmenux_logo msg_title "$(translate "Local Disk Storages")" echo "==================================================" echo "" # ── Source 1: Proxmox-registered storages (pvesm dir / zfspool) ── # Same discovery as before — only user-created ones are surfaced; # `_is_user_disk_storage` keeps `local`, `local-lvm` and the # auto-generated `local-zfs` (rpool/data) out of the list. local pvesm_paths=() local pvesm_lines="" if command -v pvesm >/dev/null 2>&1; then local DIR_STORAGES DIR_STORAGES=$(pvesm status 2>/dev/null | awk '$2 == "dir" || $2 == "zfspool" {print $1, $2, $3}') if [[ -n "$DIR_STORAGES" ]]; then while IFS=" " read -r s_id s_type s_status; do [[ -z "$s_id" ]] && continue _is_user_disk_storage "$s_id" "$s_type" || continue local path path=$(get_storage_config "$s_id" | awk '$1 == "path" {print $2}') [[ -n "$path" ]] && pvesm_paths+=("$path") pvesm_lines+="$s_id $s_type $s_status"$'\n' done <<< "$DIR_STORAGES" fi fi # ── Source 2: fstab /mnt/ entries that are LOCAL DISKS not pvesm-managed ── # The new fstab-only mount method introduced for disk_host (matching # nfs_host / samba_host) doesn't go through pvesm, so a user who # picked "fstab only" needs to see the disk here too. Discovery # uses the same criterion remove_disk_storage already applies: # an /etc/fstab line whose mountpoint lives under /mnt/ and isn't # one of the paths a registered pvesm storage owns. # # Network filesystems (cifs/smbfs/nfs/sshfs) belong to the # samba_host / nfs_host scripts — listing them here would let the # user accidentally remove a Samba/NFS mount from the "disk" menu. # Pseudo filesystems are also skipped, and a final block-device # check excludes anything whose backing source isn't a real disk. local fstab_lines="" while IFS= read -r line; do [[ "$line" =~ ^# || -z "$line" ]] && continue local fs mp fstype rest read -r fs mp fstype rest <<< "$line" [[ "$mp" == /mnt/* ]] || continue case "$fstype" in proc|sysfs|tmpfs|devtmpfs|none) continue ;; cifs|smbfs|nfs|nfs4|nfsv4) continue ;; fuse.sshfs|sshfs|fuse) continue ;; esac local resolved="$fs" [[ "$fs" == UUID=* ]] && resolved=$(blkid -U "${fs#UUID=}" 2>/dev/null || echo "$fs") [[ "$fs" == LABEL=* ]] && resolved=$(blkid -L "${fs#LABEL=}" 2>/dev/null || echo "$fs") # Skip anything whose source isn't a block device. That catches # bind-mounts (which use a directory as source) and any oddball # network-style entry that slips past the fstype filter above. [[ -b "$resolved" ]] || continue local covered=false local p for p in "${pvesm_paths[@]}"; do [[ "$p" == "$mp" ]] && covered=true && break done $covered && continue fstab_lines+="$fs|$mp|$fstype"$'\n' done < /etc/fstab # ── Empty-state ── if [[ -z "$pvesm_lines" && -z "$fstab_lines" ]]; then msg_warn "$(translate "No local disk storage configured.")" echo "" msg_info2 "$(translate "Use option 1 to add a local disk (as Proxmox storage and/or as a host mount).")" echo "" msg_success "$(translate "Press Enter to continue...")" read -r return fi # ── Render: pvesm-registered first, then fstab-only ── if [[ -n "$pvesm_lines" ]]; then echo -e "${BOLD}$(translate "Proxmox storages:")${CL}" echo "" while IFS=" " read -r storage_id storage_type storage_status; do [[ -z "$storage_id" ]] && continue local storage_info path content pool storage_info=$(get_storage_config "$storage_id") path=$(echo "$storage_info" | awk '$1 == "path" {print $2}') pool=$(echo "$storage_info" | awk '$1 == "pool" {print $2}') content=$(echo "$storage_info" | awk '$1 == "content" {print $2}') local disk_device="" if [[ -n "$path" ]]; then disk_device=$(findmnt -n -o SOURCE "$path" 2>/dev/null || true) fi local disk_size="" if [[ -n "$disk_device" ]]; then disk_size=$(lsblk -ndo SIZE "$disk_device" 2>/dev/null || true) fi echo -e "${TAB}${BOLD}$storage_id${CL}" echo -e "${TAB} ${BGN}$(translate "Type:")${CL} ${BL}${storage_type:-unknown}${CL}" [[ -n "$path" ]] && echo -e "${TAB} ${BGN}$(translate "Path:")${CL} ${BL}$path${CL}" [[ -n "$pool" ]] && echo -e "${TAB} ${BGN}$(translate "Pool:")${CL} ${BL}$pool${CL}" [[ -n "$disk_device" ]] && echo -e "${TAB} ${BGN}$(translate "Device:")${CL} ${BL}$disk_device${CL}" [[ -n "$disk_size" ]] && echo -e "${TAB} ${BGN}$(translate "Size:")${CL} ${BL}$disk_size${CL}" echo -e "${TAB} ${BGN}$(translate "Content:")${CL} ${BL}$content${CL}" if [[ "$storage_status" == "active" ]]; then echo -e "${TAB} ${BGN}$(translate "Status:")${CL} ${GN}$(translate "Active")${CL}" else echo -e "${TAB} ${BGN}$(translate "Status:")${CL} ${RD}$storage_status${CL}" fi echo "" done <<< "$pvesm_lines" fi if [[ -n "$fstab_lines" ]]; then echo -e "${BOLD}$(translate "Host fstab mounts (not registered as Proxmox storage):")${CL}" echo "" while IFS="|" read -r fs mp fstype; do [[ -z "$mp" ]] && continue local device="$fs" [[ "$fs" == UUID=* ]] && device=$(blkid -U "${fs#UUID=}" 2>/dev/null || echo "$fs") local disk_size="" [[ -b "$device" ]] && disk_size=$(lsblk -ndo SIZE "$device" 2>/dev/null || true) local mount_status if findmnt -n "$mp" >/dev/null 2>&1; then mount_status="${GN}$(translate "Mounted")${CL}" else mount_status="${RD}$(translate "Not mounted")${CL}" fi echo -e "${TAB}${BOLD}$mp${CL}" echo -e "${TAB} ${BGN}$(translate "Type:")${CL} ${BL}${fstype:-unknown} (fstab only)${CL}" echo -e "${TAB} ${BGN}$(translate "Device:")${CL} ${BL}$device${CL}" [[ -n "$disk_size" ]] && echo -e "${TAB} ${BGN}$(translate "Size:")${CL} ${BL}$disk_size${CL}" echo -e "${TAB} ${BGN}$(translate "Status:")${CL} $mount_status" echo -e "${TAB} ${BGN}$(translate "Use:")${CL} ${BL}$(translate "available for LXC bind-mounts via 'LXC Mount Manager'")${CL}" echo "" done <<< "$fstab_lines" fi msg_success "$(translate "Press Enter to continue...")" read -r } _remove_pvesm_storage() { local storage_id="$1" local path pool content stype path=$(get_storage_config "$storage_id" | awk '$1 == "path" {print $2}') pool=$(get_storage_config "$storage_id" | awk '$1 == "pool" {print $2}') content=$(get_storage_config "$storage_id" | awk '$1 == "content" {print $2}') stype=$(pvesm status 2>/dev/null | awk -v id="$storage_id" '$1==id {print $2}') local msg msg="$(translate "WARNING: You are about to remove this Proxmox storage:")\n\n" msg+=" $(translate "Storage ID:") $storage_id\n" msg+=" $(translate "Type:") ${stype:-unknown}\n" [[ -n "$path" ]] && msg+=" $(translate "Mount path:") $path\n" [[ -n "$pool" ]] && msg+=" $(translate "ZFS pool:") $pool\n" [[ -n "$content" ]] && msg+=" $(translate "Content:") $content\n" msg+="\n$(translate "⚠ Disk data will NOT be erased.")\n" [[ -n "$path" ]] && msg+="$(translate "⚠ Disk will be unmounted and removed from /etc/fstab.")\n" [[ -n "$pool" ]] && msg+="$(translate "⚠ ZFS pool stays active — run 'zpool export $pool' to detach.")\n" msg+="\n$(translate "Continue?")" if ! dialog --backtitle "ProxMenux" --title "$(translate "Confirm Remove")" --yesno "$msg" 22 84; then return fi show_proxmenux_logo msg_title "$(translate "Remove Disk Storage")" # Step 1: Remove from Proxmox msg_info "$(translate "Removing storage from Proxmox...")" if ! pvesm remove "$storage_id" 2>/dev/null; then msg_error "$(translate "Failed to remove storage from Proxmox.")" echo "" msg_success "$(translate "Press Enter to continue...")" read -r return fi msg_ok "$(translate "Storage") $storage_id $(translate "removed from Proxmox")" # Step 2: Unmount if mounted (dir-backed storages only) if [[ -n "$path" ]] && mountpoint -q "$path" 2>/dev/null; then msg_info "$(translate "Unmounting disk...")" if umount "$path" 2>/dev/null; then msg_ok "$(translate "Disk unmounted from") $path" else msg_warn "$(translate "Could not unmount") $path $(translate "— disk may be busy. Skipping fstab removal.")" echo "" msg_success "$(translate "Press Enter to continue...")" read -r return fi fi # Step 3: Remove /etc/fstab entry if [[ -n "$path" ]] && grep -q "[[:space:]]${path}[[:space:]]" /etc/fstab 2>/dev/null; then msg_info "$(translate "Removing from /etc/fstab...")" local tmp tmp=$(mktemp) awk -v mp="$path" '$2 != mp' /etc/fstab > "$tmp" && mv "$tmp" /etc/fstab systemctl daemon-reload 2>/dev/null || true msg_ok "$(translate "Removed from /etc/fstab")" fi # Step 3b: Export ZFS pool if applicable if [[ -n "$pool" ]] && zpool list "$pool" >/dev/null 2>&1; then msg_info "$(translate "Exporting ZFS pool...") $pool" if zpool export "$pool" 2>/dev/null; then msg_ok "$(translate "ZFS pool exported:") $pool" else msg_warn "$(translate "Could not export ZFS pool") $pool $(translate "— pool may be busy. Run manually: zpool export $pool")" fi fi # Step 4: Reboot prompt echo "" if whiptail --title "$(translate "Reboot Required")" --yesno \ "\n$(translate "The storage has been removed and the disk unmounted.")\n\n$(translate "A server reboot is recommended for all changes to take full effect.")\n\n$(translate "Reboot now?")" \ 14 72; then msg_success "$(translate "Press Enter to continue...")" read -r echo "" msg_warn "$(translate "Rebooting the system...")" reboot else echo "" msg_info2 "$(translate "Reboot pending — changes will take full effect after the next restart.")" fi echo "" msg_success "$(translate "Press Enter to continue...")" read -r } _remove_fstab_entry() { local mount_point="$1" local fs fstype while IFS= read -r line; do [[ "$line" =~ ^# || -z "$line" ]] && continue local f mp ft read -r f mp ft _ <<< "$line" if [[ "$mp" == "$mount_point" ]]; then fs="$f"; fstype="$ft"; break fi done < /etc/fstab local device="$fs" if [[ "$fs" == UUID=* ]]; then device=$(blkid -U "${fs#UUID=}" 2>/dev/null || echo "$fs") fi local size="" [[ -b "$device" ]] && size=$(lsblk -ndo SIZE "$device" 2>/dev/null) local mounted=false findmnt -n "$mount_point" >/dev/null 2>&1 && mounted=true local msg msg="$(translate "WARNING: You are about to remove this disk mount:")\n\n" msg+=" $(translate "Mount point:") $mount_point\n" [[ -n "$device" && "$device" != "$fs" ]] && msg+=" $(translate "Device:") $device\n" msg+=" $(translate "Filesystem:") $fstype\n" [[ -n "$size" ]] && msg+=" $(translate "Size:") $size\n" local mounted_label; $mounted && mounted_label="$(translate "Yes")" || mounted_label="$(translate "No")" msg+=" $(translate "Currently mounted:") $mounted_label\n" msg+="\n$(translate "⚠ The disk will be unmounted.")\n" msg+="$(translate "⚠ The /etc/fstab entry will be removed.")\n" msg+="$(translate "⚠ Disk data will NOT be erased.")\n" msg+="\n$(translate "Continue?")" if dialog --backtitle "ProxMenux" --title "$(translate "Confirm Remove")" --yesno "$msg" 20 80; then show_proxmenux_logo msg_title "$(translate "Remove Disk from fstab")" if $mounted; then msg_info "$(translate "Unmounting") $mount_point..." if umount "$mount_point" 2>/dev/null; then msg_ok "$(translate "Unmounted successfully")" else msg_warn "$(translate "Could not unmount — disk may be busy. Removing fstab entry anyway.")" fi fi msg_info "$(translate "Removing from /etc/fstab...")" local tmp tmp=$(mktemp) awk -v mp="$mount_point" '$2 != mp' /etc/fstab > "$tmp" mv "$tmp" /etc/fstab systemctl daemon-reload 2>/dev/null || true msg_ok "$(translate "Removed from /etc/fstab")" echo "" msg_success "$(translate "Press Enter to continue...")" read -r fi } remove_disk_storage() { if ! command -v pvesm >/dev/null 2>&1; then dialog --backtitle "ProxMenux" --title "$(translate "Error")" \ --msgbox "\n$(translate "pvesm not found.")" 8 60 return fi local OPTIONS=() local pvesm_paths=() # --- Source 1: pvesm user-created storages --- local ALL_STORAGES ALL_STORAGES=$(pvesm status 2>/dev/null | awk '$2 == "dir" || $2 == "zfspool" {print $1, $2}') while IFS= read -r line; do local storage_id storage_type storage_id=$(echo "$line" | awk '{print $1}') storage_type=$(echo "$line" | awk '{print $2}') [[ -z "$storage_id" ]] && continue _is_user_disk_storage "$storage_id" "$storage_type" || continue local path pool path=$(get_storage_config "$storage_id" | awk '$1 == "path" {print $2}') pool=$(get_storage_config "$storage_id" | awk '$1 == "pool" {print $2}') [[ -n "$path" ]] && pvesm_paths+=("$path") local label="[pvesm] ${storage_type}" [[ -n "$path" ]] && label+=" — $path" [[ -z "$path" && -n "$pool" ]] && label+=" — pool: $pool" OPTIONS+=("pvesm:$storage_id" "$label") done <<< "$ALL_STORAGES" # --- Source 2: fstab /mnt/ entries that are LOCAL DISKS not in pvesm --- # Network filesystems (cifs/nfs/sshfs) are handled by the samba_host # and nfs_host scripts — surfacing them in this disk-only remove # menu would let a user wipe a CIFS/NFS mount expecting it to # belong to a local disk. Pseudo filesystems are skipped, and a # block-device check on the resolved source filters out bind-mounts # and anything else whose backing source isn't a real disk. while IFS= read -r line; do [[ "$line" =~ ^# || -z "$line" ]] && continue local fs mp fstype read -r fs mp fstype _ <<< "$line" [[ "$mp" == /mnt/* ]] || continue case "$fstype" in proc|sysfs|tmpfs|devtmpfs|none) continue ;; cifs|smbfs|nfs|nfs4|nfsv4) continue ;; fuse.sshfs|sshfs|fuse) continue ;; esac local covered=false for p in "${pvesm_paths[@]}"; do [[ "$p" == "$mp" ]] && covered=true && break; done $covered && continue local device="$fs" [[ "$fs" == UUID=* ]] && device=$(blkid -U "${fs#UUID=}" 2>/dev/null || echo "$fs") [[ "$fs" == LABEL=* ]] && device=$(blkid -L "${fs#LABEL=}" 2>/dev/null || echo "$fs") [[ -b "$device" ]] || continue local size size=$(lsblk -ndo SIZE "$device" 2>/dev/null) local label="[fstab] $mp ($fstype)" [[ -n "$size" ]] && label+=" [$size]" findmnt -n "$mp" >/dev/null 2>&1 && label+=" ✓" || label+=" (not mounted)" OPTIONS+=("fstab:$mp" "$label") done < /etc/fstab if [[ ${#OPTIONS[@]} -eq 0 ]]; then dialog --backtitle "ProxMenux" --title "$(translate "No Disk Storage")" \ --msgbox "\n$(translate "No user-created disk storage or fstab mount found.")" 8 68 return fi local SELECTED SELECTED=$(dialog --backtitle "ProxMenux" --title "$(translate "Remove Disk Storage")" \ --menu "$(translate "Select storage to remove:")" 20 88 12 \ "${OPTIONS[@]}" 3>&1 1>&2 2>&3) [[ -z "$SELECTED" ]] && return case "${SELECTED%%:*}" in pvesm) _remove_pvesm_storage "${SELECTED#pvesm:}" ;; fstab) _remove_fstab_entry "${SELECTED#fstab:}" ;; esac } list_available_disks() { show_proxmenux_logo msg_title "$(translate "Available Disks on Host")" echo "==================================================" echo "" echo -e "${BOLD}$(translate "All block devices:")${CL}" echo "" lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,MODEL 2>/dev/null echo "" echo -e "${BOLD}$(translate "Proxmox local storages:")${CL}" if command -v pvesm >/dev/null 2>&1; then pvesm status 2>/dev/null | awk '$2 == "dir" || $2 == "zfspool" {print " " $1, $2, $3}' || echo " $(translate "None")" fi echo "" msg_success "$(translate "Press Enter to continue...")" read -r } # ========================================================== # MAIN MENU # ========================================================== while true; do CHOICE=$(dialog --backtitle "ProxMenux" \ --title "$(translate "Local Disk Manager - Proxmox Host")" \ --menu "$(translate "Choose an option:")" 18 70 6 \ "1" "$(translate "Add Local Disk as Proxmox Storage")" \ "2" "$(translate "View Disk Storages")" \ "3" "$(translate "Remove Disk Storage")" \ "4" "$(translate "List Available Disks")" \ "5" "$(translate "Exit")" \ 3>&1 1>&2 2>&3) RETVAL=$? if [[ $RETVAL -ne 0 ]]; then exit 0 fi case $CHOICE in 1) add_disk_to_proxmox ;; 2) view_disk_storages ;; 3) remove_disk_storage ;; 4) list_available_disks ;; 5) exit 0 ;; *) exit 0 ;; esac done