Files
ProxMenux/scripts/share/disk_host.sh
T
2026-06-02 18:29:29 +02:00

1276 lines
50 KiB
Bash

#!/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/<name>):")"
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/<id> 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
show_proxmenux_logo
msg_title "$(translate "Add Local Disk as Proxmox Storage")"
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