Files
ProxMenux/scripts/share/lxc-mount-manager_minimal.sh

895 lines
32 KiB
Bash
Raw Normal View History

2025-09-10 11:10:02 +02:00
#!/bin/bash
# ==========================================================
# ProxMenux - LXC Mount Manager
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT
2026-04-01 23:09:51 +02:00
# ==========================================================
# Description:
# Adds bind mounts from Proxmox host directories into LXC
# containers using pct set -mpX (Proxmox native).
#
# SAFE DESIGN: This script NEVER modifies permissions, ownership,
# or ACLs on the host or inside the container. All existing
# configurations are preserved as-is.
2025-09-10 11:10:02 +02:00
# ==========================================================
BASE_DIR="/usr/local/share/proxmenux"
source "$BASE_DIR/utils.sh"
load_language
initialize_cache
2026-04-01 23:09:51 +02:00
# ==========================================================
# DIRECTORY DETECTION
# ==========================================================
2025-09-10 11:10:02 +02:00
detect_mounted_shares() {
local mounted_shares=()
2026-04-01 23:09:51 +02:00
2025-09-10 11:10:02 +02:00
while IFS= read -r line; do
2026-04-01 23:09:51 +02:00
local device mount_point fs_type
read -r device mount_point fs_type _ <<< "$line"
2025-09-10 11:10:02 +02:00
local type=""
case "$fs_type" in
2026-04-01 23:09:51 +02:00
nfs|nfs4) type="NFS" ;;
cifs) type="CIFS/SMB" ;;
*) continue ;;
2025-09-10 11:10:02 +02:00
esac
2026-04-01 23:09:51 +02:00
# Skip internal Proxmox mounts
local skip=false
for internal in /mnt/pve/local /mnt/pve/local-lvm /mnt/pve/local-zfs \
/mnt/pve/backup /mnt/pve/dump /mnt/pve/images \
/mnt/pve/template /mnt/pve/snippets /mnt/pve/vztmpl; do
if [[ "$mount_point" == "$internal" || "$mount_point" =~ ^${internal}/ ]]; then
skip=true
break
2025-09-10 11:10:02 +02:00
fi
2026-04-01 23:09:51 +02:00
done
[[ "$skip" == true ]] && continue
local size used
local df_info
df_info=$(df -h "$mount_point" 2>/dev/null | tail -n1)
if [[ -n "$df_info" ]]; then
size=$(echo "$df_info" | awk '{print $2}')
used=$(echo "$df_info" | awk '{print $3}')
else
size="N/A"
used="N/A"
2025-09-10 11:10:02 +02:00
fi
2026-04-01 23:09:51 +02:00
local source="Manual"
[[ "$mount_point" =~ ^/mnt/pve/ ]] && source="Proxmox-Storage"
mounted_shares+=("$mount_point|$device|$type|$size|$used|$source")
2025-09-10 11:10:02 +02:00
done < /proc/mounts
2026-04-01 23:09:51 +02:00
2025-09-10 11:10:02 +02:00
printf '%s\n' "${mounted_shares[@]}"
}
detect_fstab_network_mounts() {
local fstab_mounts=()
while IFS= read -r line; do
[[ "$line" =~ ^[[:space:]]*# ]] && continue
[[ -z "${line// }" ]] && continue
2026-04-01 23:09:51 +02:00
local source mount_point fs_type
read -r source mount_point fs_type _ <<< "$line"
2025-09-10 11:10:02 +02:00
local type=""
case "$fs_type" in
2026-04-01 23:09:51 +02:00
nfs|nfs4) type="NFS" ;;
cifs) type="CIFS/SMB" ;;
*) continue ;;
2025-09-10 11:10:02 +02:00
esac
2026-04-01 23:09:51 +02:00
[[ ! -d "$mount_point" ]] && continue
# Skip if already mounted (already captured by detect_mounted_shares)
local is_mounted=false
while IFS= read -r proc_line; do
local proc_mp proc_fs
read -r _ proc_mp proc_fs _ <<< "$proc_line"
if [[ "$proc_mp" == "$mount_point" && ("$proc_fs" == "nfs" || "$proc_fs" == "nfs4" || "$proc_fs" == "cifs") ]]; then
is_mounted=true
break
2025-09-10 11:10:02 +02:00
fi
2026-04-01 23:09:51 +02:00
done < /proc/mounts
[[ "$is_mounted" == false ]] && fstab_mounts+=("$mount_point|$source|$type|0|0|fstab-inactive")
2025-09-10 11:10:02 +02:00
done < /etc/fstab
2026-04-01 23:09:51 +02:00
2025-09-10 11:10:02 +02:00
printf '%s\n' "${fstab_mounts[@]}"
}
detect_local_directories() {
local local_dirs=()
2026-04-01 23:09:51 +02:00
local network_mps=()
2025-09-10 11:10:02 +02:00
2026-04-01 23:09:51 +02:00
# Collect network mount points to exclude
while IFS='|' read -r mp _ _ _ _ _; do
[[ -n "$mp" ]] && network_mps+=("$mp")
done < <({ detect_mounted_shares; detect_fstab_network_mounts; })
2025-09-10 11:10:02 +02:00
if [[ -d "/mnt" ]]; then
for dir in /mnt/*/; do
2026-04-01 23:09:51 +02:00
[[ ! -d "$dir" ]] && continue
local dir_path="${dir%/}"
[[ "$(basename "$dir_path")" == "pve" ]] && continue
2025-09-10 11:10:02 +02:00
2026-04-01 23:09:51 +02:00
local is_network=false
for nmp in "${network_mps[@]}"; do
[[ "$dir_path" == "$nmp" ]] && is_network=true && break
done
[[ "$is_network" == true ]] && continue
2025-09-10 11:10:02 +02:00
2026-04-01 23:09:51 +02:00
local dir_size
dir_size=$(du -sh "$dir_path" 2>/dev/null | awk '{print $1}')
local_dirs+=("$dir_path|Local|Directory|$dir_size|-|Manual")
done
2025-09-10 11:10:02 +02:00
fi
2026-04-01 23:09:51 +02:00
printf '%s\n' "${local_dirs[@]}"
2025-09-10 11:10:02 +02:00
}
2026-04-01 23:09:51 +02:00
# ==========================================================
# HOST DIRECTORY SELECTION
# ==========================================================
2025-09-10 11:10:02 +02:00
detect_problematic_storage() {
2026-04-01 23:09:51 +02:00
local dir="$1"
local check_source="$2"
local check_type="$3"
2025-09-10 11:10:02 +02:00
2026-04-01 23:09:51 +02:00
while IFS='|' read -r mp _ type _ _ source; do
if [[ "$mp" == "$dir" && "$source" == "$check_source" && "$type" == "$check_type" ]]; then
return 0
2025-09-10 11:10:02 +02:00
fi
2026-04-01 23:09:51 +02:00
done < <(detect_mounted_shares)
return 1
2025-09-10 11:10:02 +02:00
}
select_host_directory_unified() {
2026-04-01 23:09:51 +02:00
local mounted_shares fstab_mounts local_dirs
2025-09-10 11:10:02 +02:00
mounted_shares=$(detect_mounted_shares)
fstab_mounts=$(detect_fstab_network_mounts)
local_dirs=$(detect_local_directories)
2026-04-01 23:09:51 +02:00
# Deduplicate and build option list
local all_entries=()
declare -A seen_paths
# Process network shares (mounted + fstab)
while IFS='|' read -r mp device type size used source; do
[[ -z "$mp" ]] && continue
[[ -n "${seen_paths[$mp]}" ]] && continue
seen_paths["$mp"]=1
local prefix=""
case "$source" in
"Proxmox-Storage") prefix="PVE-" ;;
"fstab-inactive") prefix="fstab(off)-" ;;
*) prefix="" ;;
esac
2025-09-10 11:10:02 +02:00
2026-04-01 23:09:51 +02:00
local info="${prefix}${type}"
[[ "$size" != "N/A" && "$size" != "0" ]] && info="${info} [${used}/${size}]"
all_entries+=("$mp" "$info")
done < <(echo "$mounted_shares"; echo "$fstab_mounts")
# Process local directories
while IFS='|' read -r mp _ type size _ _; do
[[ -z "$mp" ]] && continue
[[ -n "${seen_paths[$mp]}" ]] && continue
seen_paths["$mp"]=1
local info="Local"
[[ -n "$size" && "$size" != "0" ]] && info="Local [${size}]"
all_entries+=("$mp" "$info")
done < <(echo "$local_dirs")
# Add Proxmox storage paths (/mnt/pve/*)
if [[ -d "/mnt/pve" ]]; then
for dir in /mnt/pve/*/; do
[[ ! -d "$dir" ]] && continue
local dir_path="${dir%/}"
[[ -n "${seen_paths[$dir_path]}" ]] && continue
seen_paths["$dir_path"]=1
all_entries+=("$dir_path" "Proxmox-Storage")
2025-09-10 11:10:02 +02:00
done
fi
2026-04-01 23:09:51 +02:00
all_entries+=("MANUAL" "$(translate "Enter path manually")")
2025-09-10 11:10:02 +02:00
local result
result=$(dialog --clear --colors --title "$(translate "Select Host Directory")" \
--menu "\n$(translate "Select the directory to bind to container:")" 25 85 15 \
2026-04-01 23:09:51 +02:00
"${all_entries[@]}" 3>&1 1>&2 2>&3)
2025-09-10 11:10:02 +02:00
2026-04-01 23:09:51 +02:00
local dialog_exit=$?
[[ $dialog_exit -ne 0 ]] && return 1
[[ -z "$result" || "$result" =~ ^━ ]] && return 1
2025-09-10 11:10:02 +02:00
if [[ "$result" == "MANUAL" ]]; then
result=$(whiptail --title "$(translate "Manual Path Entry")" \
2026-04-01 23:09:51 +02:00
--inputbox "$(translate "Enter the full path to the host directory:")" \
10 70 "/mnt/" 3>&1 1>&2 2>&3)
[[ $? -ne 0 ]] && return 1
2025-09-10 11:10:02 +02:00
fi
2026-04-01 23:09:51 +02:00
[[ -z "$result" ]] && return 1
2025-09-10 11:10:02 +02:00
if [[ ! -d "$result" ]]; then
whiptail --title "$(translate "Invalid Path")" \
--msgbox "$(translate "The selected path is not a valid directory:") $result" 8 70
return 1
fi
2026-04-12 20:32:34 +02:00
# Store the storage type as a global so the main flow can act on it later.
# We don't block the user here — the active fix happens after we know the container type.
LMM_HOST_DIR_TYPE="local"
2026-04-01 23:09:51 +02:00
if detect_problematic_storage "$result" "Proxmox-Storage" "CIFS/SMB"; then
2026-04-12 20:32:34 +02:00
LMM_HOST_DIR_TYPE="cifs"
elif detect_problematic_storage "$result" "Proxmox-Storage" "NFS"; then
LMM_HOST_DIR_TYPE="nfs"
2025-09-10 11:10:02 +02:00
fi
echo "$result"
return 0
}
2026-04-01 23:09:51 +02:00
# ==========================================================
# CONTAINER SELECTION
# ==========================================================
2025-09-10 11:10:02 +02:00
select_lxc_container() {
2026-04-01 23:09:51 +02:00
local ct_list
2025-09-10 11:10:02 +02:00
ct_list=$(pct list 2>/dev/null | awk 'NR>1 {print $1, $2, $3}')
if [[ -z "$ct_list" ]]; then
2026-04-01 23:09:51 +02:00
whiptail --title "Error" --msgbox "$(translate "No LXC containers available")" 8 50
2025-09-10 11:10:02 +02:00
return 1
fi
local options=()
while read -r id name status; do
2026-04-01 23:09:51 +02:00
[[ -n "$id" && "$id" =~ ^[0-9]+$ ]] && options+=("$id" "${name:-unnamed} ($status)")
2025-09-10 11:10:02 +02:00
done <<< "$ct_list"
2026-04-01 23:09:51 +02:00
2025-09-10 11:10:02 +02:00
if [[ ${#options[@]} -eq 0 ]]; then
2026-04-01 23:09:51 +02:00
dialog --title "Error" --msgbox "$(translate "No valid containers found")" 8 50
2025-09-10 11:10:02 +02:00
return 1
fi
2026-04-01 23:09:51 +02:00
local ctid
ctid=$(dialog --title "$(translate "Select LXC Container")" \
--menu "$(translate "Select container:")" 25 85 15 \
2025-09-10 11:10:02 +02:00
"${options[@]}" 3>&1 1>&2 2>&3)
2026-04-01 23:09:51 +02:00
[[ $? -ne 0 || -z "$ctid" ]] && return 1
2025-09-10 11:10:02 +02:00
echo "$ctid"
return 0
}
select_container_mount_point() {
local ctid="$1"
local host_dir="$2"
2026-04-01 23:09:51 +02:00
local base_name
2025-09-10 11:10:02 +02:00
base_name=$(basename "$host_dir")
while true; do
2026-04-01 23:09:51 +02:00
local choice
2025-09-10 11:10:02 +02:00
choice=$(dialog --clear --title "$(translate "Configure Mount Point inside LXC")" \
2026-04-01 23:09:51 +02:00
--menu "\n$(translate "Where to mount inside container?")" 16 70 3 \
2025-09-10 11:10:02 +02:00
"1" "$(translate "Create new directory in /mnt")" \
"2" "$(translate "Enter path manually")" \
"3" "$(translate "Cancel")" 3>&1 1>&2 2>&3)
2026-04-01 23:09:51 +02:00
[[ $? -ne 0 ]] && return 1
2025-09-10 11:10:02 +02:00
2026-04-01 23:09:51 +02:00
local mount_point
2025-09-10 11:10:02 +02:00
case "$choice" in
1)
mount_point=$(whiptail --inputbox "$(translate "Enter folder name for /mnt:")" \
10 60 "$base_name" 3>&1 1>&2 2>&3)
2026-04-01 23:09:51 +02:00
[[ $? -ne 0 || -z "$mount_point" ]] && continue
2025-09-10 11:10:02 +02:00
mount_point="/mnt/$mount_point"
;;
2)
mount_point=$(whiptail --inputbox "$(translate "Enter full path:")" \
10 70 "/mnt/$base_name" 3>&1 1>&2 2>&3)
2026-04-01 23:09:51 +02:00
[[ $? -ne 0 || -z "$mount_point" ]] && continue
2025-09-10 11:10:02 +02:00
;;
2026-04-01 23:09:51 +02:00
3) return 1 ;;
2025-09-10 11:10:02 +02:00
esac
2026-04-01 23:09:51 +02:00
# Validate path format
if [[ ! "$mount_point" =~ ^/ ]]; then
whiptail --msgbox "$(translate "Path must be absolute (start with /)")" 8 60
2025-09-10 11:10:02 +02:00
continue
fi
2026-04-01 23:09:51 +02:00
# Check if path is already used as a mount point in this CT
2026-04-12 20:32:34 +02:00
if pct config "$ctid" 2>/dev/null | grep -qE "mp=${mount_point}(,|$)"; then
2026-04-01 23:09:51 +02:00
whiptail --msgbox "$(translate "This path is already used as a mount point in this container.")" 8 70
continue
fi
# Create directory inside CT (only if CT is running)
local ct_status
ct_status=$(pct status "$ctid" 2>/dev/null | awk '{print $2}')
if [[ "$ct_status" == "running" ]]; then
pct exec "$ctid" -- mkdir -p "$mount_point" 2>/dev/null
fi
echo "$mount_point"
return 0
2025-09-10 11:10:02 +02:00
done
}
# ==========================================================
2026-04-01 23:09:51 +02:00
# MOUNT MANAGEMENT
2025-09-10 11:10:02 +02:00
# ==========================================================
2026-04-01 23:09:51 +02:00
get_next_mp_index() {
local ctid="$1"
local conf="/etc/pve/lxc/${ctid}.conf"
if [[ ! "$ctid" =~ ^[0-9]+$ ]] || [[ ! -f "$conf" ]]; then
echo "0"
return 0
fi
local next=0
local used
used=$(awk -F: '/^mp[0-9]+:/ {print $1}' "$conf" | sed 's/mp//' | sort -n)
for idx in $used; do
[[ "$idx" -ge "$next" ]] && next=$((idx + 1))
done
echo "$next"
}
add_bind_mount() {
local ctid="$1"
local host_path="$2"
local ct_path="$3"
if [[ ! "$ctid" =~ ^[0-9]+$ || -z "$host_path" || -z "$ct_path" ]]; then
msg_error "$(translate "Invalid parameters for bind mount")"
return 1
fi
# Check if this host path is already mounted in this CT
2026-04-12 20:32:34 +02:00
if pct config "$ctid" 2>/dev/null | grep -qF " ${host_path},"; then
2026-04-01 23:09:51 +02:00
msg_warn "$(translate "Mount already exists for this path in container") $ctid"
return 1
fi
local mpidx
mpidx=$(get_next_mp_index "$ctid")
local result
result=$(pct set "$ctid" -mp${mpidx} "$host_path,mp=$ct_path,shared=1,backup=0" 2>&1)
if [[ $? -eq 0 ]]; then
msg_ok "$(translate "Bind mount added:") $host_path$ct_path (mp${mpidx})"
return 0
else
msg_error "$(translate "Failed to add bind mount:") $result"
return 1
fi
}
# ==========================================================
# VIEW / REMOVE
# ==========================================================
2025-09-10 11:10:02 +02:00
view_mount_points() {
show_proxmenux_logo
2026-04-01 23:09:51 +02:00
msg_title "$(translate "Current LXC Mount Points")"
2025-09-10 11:10:02 +02:00
local ct_list
ct_list=$(pct list 2>/dev/null | awk 'NR>1 {print $1, $2, $3}')
if [[ -z "$ct_list" ]]; then
2026-04-01 23:09:51 +02:00
msg_warn "$(translate "No LXC containers found")"
echo ""
msg_success "$(translate "Press Enter to continue...")"
2025-09-10 11:10:02 +02:00
read -r
return 1
fi
2026-04-01 23:09:51 +02:00
2025-09-10 11:10:02 +02:00
local found_mounts=false
2026-04-01 23:09:51 +02:00
2025-09-10 11:10:02 +02:00
while read -r id name status; do
2026-04-01 23:09:51 +02:00
[[ -z "$id" || ! "$id" =~ ^[0-9]+$ ]] && continue
local conf="/etc/pve/lxc/${id}.conf"
[[ ! -f "$conf" ]] && continue
local mounts
mounts=$(grep "^mp[0-9]*:" "$conf" 2>/dev/null)
[[ -z "$mounts" ]] && continue
found_mounts=true
echo -e "${TAB}${BOLD}$(translate "Container") $id: $name ($status)${CL}"
while IFS= read -r mount_line; do
[[ -z "$mount_line" ]] && continue
local mp_id mount_info host_path container_path options
mp_id=$(echo "$mount_line" | cut -d: -f1)
mount_info=$(echo "$mount_line" | cut -d: -f2-)
host_path=$(echo "$mount_info" | cut -d, -f1)
container_path=$(echo "$mount_info" | grep -o 'mp=[^,]*' | cut -d= -f2)
options=$(echo "$mount_info" | sed 's/^[^,]*,mp=[^,]*,*//')
echo -e "${TAB} ${BGN}$mp_id:${CL} ${BL}$host_path${CL}${BL}$container_path${CL}"
[[ -n "$options" ]] && echo -e "${TAB} ${DGN}$options${CL}"
done <<< "$mounts"
echo ""
2025-09-10 11:10:02 +02:00
done <<< "$ct_list"
2026-04-01 23:09:51 +02:00
2025-09-10 11:10:02 +02:00
if [[ "$found_mounts" == false ]]; then
2026-04-01 23:09:51 +02:00
msg_ok "$(translate "No mount points found in any container")"
2025-09-10 11:10:02 +02:00
fi
2026-04-01 23:09:51 +02:00
echo ""
msg_success "$(translate "Press Enter to continue...")"
2025-09-10 11:10:02 +02:00
read -r
}
remove_mount_point() {
show_proxmenux_logo
2026-04-01 23:09:51 +02:00
msg_title "$(translate "Remove LXC Mount Point")"
2025-09-10 11:10:02 +02:00
local container_id
container_id=$(select_lxc_container)
2026-04-01 23:09:51 +02:00
[[ $? -ne 0 || -z "$container_id" ]] && return 1
2025-09-10 11:10:02 +02:00
local conf="/etc/pve/lxc/${container_id}.conf"
if [[ ! -f "$conf" ]]; then
2026-04-01 23:09:51 +02:00
msg_error "$(translate "Container configuration not found")"
echo ""
msg_success "$(translate "Press Enter to continue...")"
2025-09-10 11:10:02 +02:00
read -r
return 1
fi
local mounts
mounts=$(grep "^mp[0-9]*:" "$conf" 2>/dev/null)
if [[ -z "$mounts" ]]; then
show_proxmenux_logo
2026-04-01 23:09:51 +02:00
msg_title "$(translate "Remove LXC Mount Point")"
msg_warn "$(translate "No mount points found in container") $container_id"
echo ""
msg_success "$(translate "Press Enter to continue...")"
2025-09-10 11:10:02 +02:00
read -r
return 1
fi
local options=()
while IFS= read -r mount_line; do
2026-04-01 23:09:51 +02:00
[[ -z "$mount_line" ]] && continue
local mp_id mount_info host_path container_path
mp_id=$(echo "$mount_line" | cut -d: -f1)
mount_info=$(echo "$mount_line" | cut -d: -f2-)
host_path=$(echo "$mount_info" | cut -d, -f1)
container_path=$(echo "$mount_info" | grep -o 'mp=[^,]*' | cut -d= -f2)
options+=("$mp_id" "$host_path$container_path")
2025-09-10 11:10:02 +02:00
done <<< "$mounts"
2026-04-01 23:09:51 +02:00
2025-09-10 11:10:02 +02:00
if [[ ${#options[@]} -eq 0 ]]; then
show_proxmenux_logo
2026-04-01 23:09:51 +02:00
msg_title "$(translate "Remove LXC Mount Point")"
msg_warn "$(translate "No valid mount points found")"
echo ""
msg_success "$(translate "Press Enter to continue...")"
2025-09-10 11:10:02 +02:00
read -r
return 1
fi
local selected_mp
selected_mp=$(dialog --clear --title "$(translate "Select Mount Point to Remove")" \
--menu "\n$(translate "Select mount point to remove from container") $container_id:" 20 80 10 \
"${options[@]}" 3>&1 1>&2 2>&3)
2026-04-01 23:09:51 +02:00
[[ $? -ne 0 || -z "$selected_mp" ]] && return 1
2025-09-10 11:10:02 +02:00
2026-04-01 23:09:51 +02:00
local selected_mount_line mount_info host_path container_path
2025-09-10 11:10:02 +02:00
selected_mount_line=$(grep "^${selected_mp}:" "$conf")
2026-04-01 23:09:51 +02:00
mount_info=$(echo "$selected_mount_line" | cut -d: -f2-)
host_path=$(echo "$mount_info" | cut -d, -f1)
container_path=$(echo "$mount_info" | grep -o 'mp=[^,]*' | cut -d= -f2)
2025-09-10 11:10:02 +02:00
2026-04-01 23:09:51 +02:00
local confirm_msg
confirm_msg="$(translate "Remove Mount Point Confirmation:")
2025-09-10 11:10:02 +02:00
$(translate "Container ID"): $container_id
$(translate "Mount Point ID"): $selected_mp
$(translate "Host Path"): $host_path
$(translate "Container Path"): $container_path
2026-04-01 23:09:51 +02:00
$(translate "NOTE: The host directory and its contents will remain unchanged.")
2025-09-10 11:10:02 +02:00
$(translate "Proceed with removal")?"
if ! dialog --clear --title "$(translate "Confirm Mount Point Removal")" --yesno "$confirm_msg" 18 80; then
return 1
fi
2026-04-01 23:09:51 +02:00
2025-09-10 11:10:02 +02:00
show_proxmenux_logo
2026-04-01 23:09:51 +02:00
msg_title "$(translate "Remove LXC Mount Point")"
msg_info "$(translate "Removing mount point") $selected_mp $(translate "from container") $container_id..."
2025-09-10 11:10:02 +02:00
if pct set "$container_id" --delete "$selected_mp" 2>/dev/null; then
2026-04-01 23:09:51 +02:00
msg_ok "$(translate "Mount point removed successfully")"
2025-09-10 11:10:02 +02:00
local ct_status
ct_status=$(pct status "$container_id" | awk '{print $2}')
if [[ "$ct_status" == "running" ]]; then
2026-04-01 23:09:51 +02:00
echo ""
2025-09-10 11:10:02 +02:00
if whiptail --yesno "$(translate "Container is running. Restart to apply changes?")" 8 60; then
2026-04-01 23:09:51 +02:00
msg_info "$(translate "Restarting container...")"
2025-09-10 11:10:02 +02:00
if pct reboot "$container_id"; then
sleep 3
2026-04-01 23:09:51 +02:00
msg_ok "$(translate "Container restarted successfully")"
2025-09-10 11:10:02 +02:00
else
2026-04-01 23:09:51 +02:00
msg_warn "$(translate "Failed to restart container — restart manually")"
2025-09-10 11:10:02 +02:00
fi
fi
fi
2026-04-01 23:09:51 +02:00
echo ""
echo -e "${TAB}${BOLD}$(translate "Mount Point Removal Summary:")${CL}"
echo -e "${TAB}${BGN}$(translate "Container:")${CL} ${BL}$container_id${CL}"
echo -e "${TAB}${BGN}$(translate "Removed Mount:")${CL} ${BL}$selected_mp${CL}"
echo -e "${TAB}${BGN}$(translate "Host Path:")${CL} ${BL}$host_path (preserved)${CL}"
echo -e "${TAB}${BGN}$(translate "Container Path:")${CL} ${BL}$container_path (unmounted)${CL}"
2025-09-10 11:10:02 +02:00
else
2026-04-01 23:09:51 +02:00
msg_error "$(translate "Failed to remove mount point")"
2025-09-10 11:10:02 +02:00
fi
2026-04-01 23:09:51 +02:00
echo ""
msg_success "$(translate "Press Enter to continue...")"
read -r
2025-09-10 11:10:02 +02:00
}
2026-04-12 20:32:34 +02:00
# ==========================================================
# ACTIVE FIXES FOR NETWORK STORAGE (CIFS / NFS)
# These functions act on problems instead of just warning about them.
# ==========================================================
lmm_fix_cifs_access() {
local host_dir="$1"
local is_unprivileged="$2"
# CIFS mounted by Proxmox GUI uses uid=0/gid=0 by default (root only).
# The fix: remount with uid/gid that the LXC can access.
# We detect the current mount options and propose a corrected remount.
local mount_src mount_opts
mount_src=$(findmnt -n -o SOURCE --target "$host_dir" 2>/dev/null)
mount_opts=$(findmnt -n -o OPTIONS --target "$host_dir" 2>/dev/null)
if [[ -z "$mount_src" ]]; then
dialog --backtitle "ProxMenux" \
--title "$(translate "CIFS Mount Not Found")" \
--msgbox "$(translate "Could not detect the CIFS mount for this directory. Try accessing it manually.")" 8 70
return 0
fi
# Determine which uid/gid to use
local target_uid target_gid
if [[ "$is_unprivileged" == "1" ]]; then
# Unprivileged LXC: container root (UID 0) maps to host UID 100000.
# Use file_mode/dir_mode 0777 + uid=0/gid=0 — CIFS maps them to everyone.
target_uid=0
target_gid=0
else
target_uid=0
target_gid=0
fi
# Build new options: strip existing uid/gid/file_mode/dir_mode, add ours
local new_opts
new_opts=$(echo "$mount_opts" | sed -E \
's/(^|,)(uid|gid|file_mode|dir_mode)=[^,]*//g' | \
sed 's/^,//')
new_opts="${new_opts},uid=${target_uid},gid=${target_gid},file_mode=0777,dir_mode=0777"
new_opts="${new_opts/#,/}"
if dialog --backtitle "ProxMenux" \
--title "$(translate "Fix CIFS Permissions")" \
--yesno \
"$(translate "This CIFS share is mounted with restrictive permissions.")\n\n\
$(translate "ProxMenux can remount it with open permissions so any LXC can read and write.")\n\n\
$(translate "Current mount options:")\n${mount_opts}\n\n\
$(translate "New mount options to apply:")\n${new_opts}\n\n\
$(translate "Apply fix now? (The share will be briefly remounted)")" \
18 84 3>&1 1>&2 2>&3; then
msg_info "$(translate "Remounting CIFS share with open permissions...")"
if umount "$host_dir" 2>/dev/null && \
mount -t cifs "$mount_src" "$host_dir" -o "$new_opts" 2>/dev/null; then
msg_ok "$(translate "CIFS share remounted — LXC containers can now read and write")"
# Update fstab if the mount is there
if grep -qF "$host_dir" /etc/fstab 2>/dev/null; then
sed -i "s|^\(${mount_src}[[:space:]].*${host_dir}.*cifs[[:space:]]\).*|\1${new_opts} 0 0|" /etc/fstab 2>/dev/null || true
msg_ok "$(translate "/etc/fstab updated — permissions will persist after reboot")"
fi
else
msg_warn "$(translate "Could not remount automatically. Try manually or check credentials.")"
fi
fi
}
lmm_fix_nfs_access() {
local host_dir="$1"
local is_unprivileged="$2"
local uid_shift="${3:-100000}"
# NFS: the host cannot override server-side permissions.
# BUT: if the server exports with root_squash (default), we can check
# if no_root_squash or all_squash is possible, and guide the user.
# What we CAN do on the host: apply a sticky+open directory as a cache layer
# if the NFS mount allows it.
local mount_src mount_opts
mount_src=$(findmnt -n -o SOURCE --target "$host_dir" 2>/dev/null)
mount_opts=$(findmnt -n -o OPTIONS --target "$host_dir" 2>/dev/null)
# Try to detect if we can write to the NFS share as root
local can_write=false
local testfile="${host_dir}/.proxmenux_write_test_$$"
if touch "$testfile" 2>/dev/null; then
rm -f "$testfile" 2>/dev/null
can_write=true
fi
local server_hint=""
if [[ -n "$mount_src" ]]; then
server_hint="${mount_src%%:*}"
fi
if [[ "$can_write" == "true" && "$is_unprivileged" == "1" ]]; then
# Root on host CAN write to NFS, but unprivileged LXC UIDs (100000+)
# will be squashed by the NFS server. We can set a world-writable sticky
# dir on the share itself so the container can write to it.
if dialog --backtitle "ProxMenux" \
--title "$(translate "Fix NFS Access for Unprivileged LXC")" \
--yesno \
"$(translate "NFS server export is writable from the host, but unprivileged LXC containers use mapped UIDs (${uid_shift}+) which the NFS server will squash.")\n\n\
$(translate "ProxMenux can apply open permissions on this NFS directory from the host so the container can read and write:")\n\n\
$(translate " chmod 1777 + setfacl o::rwx (applied on the NFS share from this host)")\n\n\
$(translate "Note: this only works if the NFS server does NOT use 'all_squash' for root.")\n\
$(translate "If it still fails, the NFS server export options must be changed on the server.")\n\n\
$(translate "Apply fix now?")" \
18 84 3>&1 1>&2 2>&3; then
if chmod 1777 "$host_dir" 2>/dev/null; then
msg_ok "$(translate "NFS directory permissions set — containers should now be able to write")"
else
msg_warn "$(translate "chmod failed — NFS server may be restricting changes from root")"
fi
if command -v setfacl >/dev/null 2>&1; then
setfacl -m o::rwx "$host_dir" 2>/dev/null || true
setfacl -m d:o::rwx "$host_dir" 2>/dev/null || true
fi
fi
elif [[ "$can_write" == "false" ]]; then
# Even root cannot write — NFS server is fully restrictive
local server_msg=""
[[ -n "$server_hint" ]] && server_msg="\n$(translate "NFS server:"): ${server_hint}"
dialog --backtitle "ProxMenux" \
--title "$(translate "NFS Access Restricted")" \
--msgbox \
"$(translate "This NFS share is fully restricted — even the host root cannot write to it.")\n\
${server_msg}\n\n\
$(translate "ProxMenux cannot override NFS server-side permissions from the host.")\n\n\
$(translate "To allow LXC write access, change the NFS export on the server to include:")\n\n\
$(translate " no_root_squash") $(translate "(if only privileged LXCs need write access)")\n\
$(translate " all_squash,anonuid=65534,anongid=65534") $(translate "(for unprivileged LXCs)")\n\n\
$(translate "You can still mount this share for READ-ONLY access.")" \
20 84 3>&1 1>&2 2>&3
fi
}
# ==========================================================
# HOST PERMISSION CHECK (host-side only, never touches the container)
# ==========================================================
lmm_offer_host_permissions() {
local host_dir="$1"
local is_unprivileged="$2"
# Privileged containers: UID 0 inside = UID 0 on host — always accessible
[[ "$is_unprivileged" != "1" ]] && return 0
# Check if 'others' already have r+x (minimum to traverse and read)
local stat_perms others_bits
stat_perms=$(stat -c "%a" "$host_dir" 2>/dev/null) || return 0
others_bits=$(( 8#${stat_perms} & 7 ))
# Check ACLs first if available (takes precedence over mode bits)
if command -v getfacl >/dev/null 2>&1; then
if getfacl -p "$host_dir" 2>/dev/null | grep -q "^other::.*r.*x"; then
return 0 # ACL already grants others r+x or better
fi
fi
# 5 = r-x (bits: r=4, x=1). If already r+x or rwx we're fine.
(( (others_bits & 5) == 5 )) && return 0
# Permissions are insufficient — offer to fix HOST directory only
local current_perms
current_perms=$(stat -c "%A" "$host_dir" 2>/dev/null)
if dialog --backtitle "ProxMenux" \
--title "$(translate "Unprivileged Container Access")" \
--yesno \
"$(translate "The host directory may not be accessible from an unprivileged container.")\n\n\
$(translate "Unprivileged containers map their UIDs to high host UIDs (e.g. 100000+), which appear as 'others' on the host filesystem.")\n\n\
$(translate "Current permissions:"): ${current_perms}\n\n\
$(translate "Apply read+write access for 'others' on the host directory?")\n\n\
$(translate "(Only the host directory is modified. Nothing inside the container is changed.")" \
16 80 3>&1 1>&2 2>&3; then
chmod o+rwx "$host_dir" 2>/dev/null || true
if command -v setfacl >/dev/null 2>&1; then
setfacl -m o::rwx "$host_dir" 2>/dev/null || true
setfacl -m d:o::rwx "$host_dir" 2>/dev/null || true
fi
msg_ok "$(translate "Host directory permissions updated — unprivileged containers can now access it")"
fi
}
2025-09-10 11:10:02 +02:00
# ==========================================================
2026-04-01 23:09:51 +02:00
# MAIN FUNCTION — ADD MOUNT
2025-09-10 11:10:02 +02:00
# ==========================================================
mount_host_directory_minimal() {
# Step 1: Select container
local container_id
container_id=$(select_lxc_container)
2026-04-01 23:09:51 +02:00
[[ $? -ne 0 || -z "$container_id" ]] && return 1
2025-09-10 11:10:02 +02:00
2026-04-01 23:09:51 +02:00
# Step 2: Select host directory
2025-09-10 11:10:02 +02:00
local host_dir
host_dir=$(select_host_directory_unified)
2026-04-01 23:09:51 +02:00
[[ $? -ne 0 || -z "$host_dir" ]] && return 1
2025-09-10 11:10:02 +02:00
# Step 3: Select container mount point
local ct_mount_point
ct_mount_point=$(select_container_mount_point "$container_id" "$host_dir")
2026-04-01 23:09:51 +02:00
[[ $? -ne 0 || -z "$ct_mount_point" ]] && return 1
2025-09-10 11:10:02 +02:00
2026-04-01 23:09:51 +02:00
# Step 4: Get container type info (for display only)
2025-09-10 11:10:02 +02:00
local uid_shift container_type_display
2026-04-12 20:32:34 +02:00
uid_shift=$(awk '/^lxc.idmap.*u 0/ {print $5}' "/etc/pve/lxc/${container_id}.conf" 2>/dev/null | head -1)
2026-04-01 23:09:51 +02:00
local is_unprivileged
is_unprivileged=$(grep "^unprivileged:" "/etc/pve/lxc/${container_id}.conf" 2>/dev/null | awk '{print $2}')
if [[ "$is_unprivileged" == "1" ]]; then
container_type_display="$(translate "Unprivileged")"
uid_shift="${uid_shift:-100000}"
2025-09-10 11:10:02 +02:00
else
2026-04-01 23:09:51 +02:00
container_type_display="$(translate "Privileged")"
uid_shift="0"
2025-09-10 11:10:02 +02:00
fi
2026-04-12 20:32:34 +02:00
# Step 5: Active fix for network storage (before confirmation, while we know container type)
case "${LMM_HOST_DIR_TYPE:-local}" in
cifs) lmm_fix_cifs_access "$host_dir" "$is_unprivileged" ;;
nfs) lmm_fix_nfs_access "$host_dir" "$is_unprivileged" "$uid_shift" ;;
esac
# Step 6: Confirmation
2026-04-01 23:09:51 +02:00
local confirm_msg
confirm_msg="$(translate "Mount Configuration Summary:")
2025-09-10 11:10:02 +02:00
$(translate "Container ID"): $container_id ($container_type_display)
$(translate "Host Directory"): $host_dir
$(translate "Container Mount Point"): $ct_mount_point
2026-04-01 23:09:51 +02:00
$(translate "IMPORTANT NOTES:")
2026-04-12 20:32:34 +02:00
- $(translate "Nothing inside the container is modified")
- $(if [[ "$is_unprivileged" == "1" ]]; then
translate "Host directory access for unprivileged containers has been prepared above"
else
translate "Privileged container — host root maps directly, no permission changes needed"
fi)
2025-09-10 11:10:02 +02:00
$(translate "Proceed")?"
2026-04-01 23:09:51 +02:00
if ! dialog --clear --title "$(translate "Confirm Mount")" --yesno "$confirm_msg" 22 80; then
2025-09-10 11:10:02 +02:00
return 1
fi
2026-04-01 23:09:51 +02:00
show_proxmenux_logo
msg_title "$(translate "Mount Host Directory to LXC")"
msg_ok "$(translate "Container:") $container_id ($container_type_display)"
msg_ok "$(translate "Host directory:") $host_dir"
msg_ok "$(translate "Container mount point:") $ct_mount_point"
2025-09-10 11:10:02 +02:00
2026-04-12 20:32:34 +02:00
# Step 7: Add bind mount
2025-09-10 11:10:02 +02:00
if ! add_bind_mount "$container_id" "$host_dir" "$ct_mount_point"; then
2026-04-01 23:09:51 +02:00
echo ""
msg_success "$(translate "Press Enter to continue...")"
2025-09-10 11:10:02 +02:00
read -r
return 1
fi
2026-04-01 23:09:51 +02:00
2026-04-12 20:32:34 +02:00
# Step 8: Host permission check for local dirs (only if not already handled above for CIFS/NFS)
if [[ "${LMM_HOST_DIR_TYPE:-local}" == "local" ]]; then
lmm_offer_host_permissions "$host_dir" "$is_unprivileged"
fi
# Step 9: Summary
2026-04-01 23:09:51 +02:00
echo ""
echo -e "${TAB}${BOLD}$(translate "Mount Added Successfully:")${CL}"
echo -e "${TAB}${BGN}$(translate "Container:")${CL} ${BL}$container_id${CL}"
echo -e "${TAB}${BGN}$(translate "Host Directory:")${CL} ${BL}$host_dir${CL}"
echo -e "${TAB}${BGN}$(translate "Mount Point:")${CL} ${BL}$ct_mount_point${CL}"
if [[ "$is_unprivileged" == "1" ]]; then
2026-04-12 20:32:34 +02:00
echo -e "${TAB}${YW}$(translate "Unprivileged container — UID offset:") ${uid_shift}${CL}"
2025-09-10 11:10:02 +02:00
else
2026-04-12 20:32:34 +02:00
echo -e "${TAB}${DGN}$(translate "Privileged container — direct root access")${CL}"
2025-09-10 11:10:02 +02:00
fi
2026-04-12 20:32:34 +02:00
echo ""
2025-09-10 11:10:02 +02:00
2026-04-12 20:32:34 +02:00
# Step 10: Offer restart
2026-04-01 23:09:51 +02:00
echo ""
2025-09-10 11:10:02 +02:00
if whiptail --yesno "$(translate "Restart container to activate mount?")" 8 60; then
2026-04-01 23:09:51 +02:00
msg_info "$(translate "Restarting container...")"
2025-09-10 11:10:02 +02:00
if pct reboot "$container_id"; then
sleep 5
2026-04-01 23:09:51 +02:00
msg_ok "$(translate "Container restarted successfully")"
# Quick access test (read-only, no files written)
local ct_status
ct_status=$(pct status "$container_id" 2>/dev/null | awk '{print $2}')
if [[ "$ct_status" == "running" ]]; then
echo ""
if pct exec "$container_id" -- test -d "$ct_mount_point" 2>/dev/null; then
msg_ok "$(translate "Mount point is accessible inside container")"
else
msg_warn "$(translate "Mount point not yet accessible — may need manual permission adjustment")"
fi
2025-09-10 11:10:02 +02:00
fi
else
2026-04-01 23:09:51 +02:00
msg_warn "$(translate "Failed to restart — restart manually to activate mount")"
2025-09-10 11:10:02 +02:00
fi
fi
2026-04-01 23:09:51 +02:00
echo ""
msg_success "$(translate "Press Enter to continue...")"
2025-09-10 11:10:02 +02:00
read -r
}
2026-04-01 23:09:51 +02:00
# ==========================================================
# MAIN MENU
# ==========================================================
2025-09-10 11:10:02 +02:00
main_menu() {
while true; do
2026-04-01 23:09:51 +02:00
local choice
choice=$(dialog --title "$(translate "LXC Mount Manager")" \
--menu "\n$(translate "Choose an option:")" 18 80 5 \
"1" "$(translate "Add: Mount Host Directory into LXC")" \
"2" "$(translate "View Mount Points")" \
"3" "$(translate "Remove Mount Point")" \
"4" "$(translate "Exit")" 3>&1 1>&2 2>&3)
2025-09-10 11:10:02 +02:00
case $choice in
2026-04-01 23:09:51 +02:00
1) mount_host_directory_minimal ;;
2) view_mount_points ;;
3) remove_mount_point ;;
4|"") exit 0 ;;
2025-09-10 11:10:02 +02:00
esac
done
}
main_menu