mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2025-10-02 08:06:17 +00:00
1104 lines
35 KiB
Bash
1104 lines
35 KiB
Bash
#!/bin/bash
|
|
# ==========================================================
|
|
# ProxMenux - LXC Mount Manager
|
|
# ==========================================================
|
|
# Author : MacRimi
|
|
# Copyright : (c) 2024 MacRimi
|
|
# License : MIT
|
|
# Version : 5.0-minimal
|
|
# Last Updated: $(date +%d/%m/%Y)
|
|
# ==========================================================
|
|
|
|
BASE_DIR="/usr/local/share/proxmenux"
|
|
source "$BASE_DIR/utils.sh"
|
|
|
|
load_language
|
|
initialize_cache
|
|
|
|
|
|
|
|
detect_mounted_shares() {
|
|
local mounted_shares=()
|
|
|
|
while IFS= read -r line; do
|
|
local device mount_point fs_type options dump pass
|
|
read -r device mount_point fs_type options dump pass <<< "$line"
|
|
|
|
local is_network=false
|
|
local type=""
|
|
|
|
case "$fs_type" in
|
|
nfs|nfs4)
|
|
is_network=true
|
|
type="NFS"
|
|
;;
|
|
cifs)
|
|
is_network=true
|
|
type="CIFS/SMB"
|
|
;;
|
|
esac
|
|
|
|
|
|
if [[ "$is_network" == true ]]; then
|
|
|
|
local exclude_internal=false
|
|
local internal_mounts=(
|
|
"/mnt/pve/local"
|
|
"/mnt/pve/local-lvm"
|
|
"/mnt/pve/local-zfs"
|
|
"/mnt/pve/backup"
|
|
"/mnt/pve/snippets"
|
|
"/mnt/pve/dump"
|
|
"/mnt/pve/images"
|
|
"/mnt/pve/template"
|
|
"/mnt/pve/private"
|
|
"/mnt/pve/vztmpl"
|
|
)
|
|
|
|
for internal_mount in "${internal_mounts[@]}"; do
|
|
if [[ "$mount_point" == "$internal_mount" || "$mount_point" =~ ^${internal_mount}/ ]]; then
|
|
exclude_internal=true
|
|
break
|
|
fi
|
|
done
|
|
|
|
|
|
if [[ "$exclude_internal" == false ]]; then
|
|
|
|
local size used
|
|
local 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"
|
|
fi
|
|
|
|
|
|
local mount_source="Manual"
|
|
if [[ "$mount_point" =~ ^/mnt/pve/ ]]; then
|
|
mount_source="Proxmox-GUI"
|
|
fi
|
|
|
|
mounted_shares+=("$mount_point|$device|$type|$size|$used|$mount_source")
|
|
fi
|
|
fi
|
|
done < /proc/mounts
|
|
|
|
printf '%s\n' "${mounted_shares[@]}"
|
|
}
|
|
|
|
detect_fstab_network_mounts() {
|
|
local fstab_mounts=()
|
|
|
|
|
|
while IFS= read -r line; do
|
|
|
|
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
|
[[ -z "${line// }" ]] && continue
|
|
|
|
|
|
local source mount_point fs_type options dump pass
|
|
read -r source mount_point fs_type options dump pass <<< "$line"
|
|
|
|
|
|
local is_network=false
|
|
local type=""
|
|
|
|
case "$fs_type" in
|
|
nfs|nfs4)
|
|
is_network=true
|
|
type="NFS"
|
|
;;
|
|
cifs)
|
|
is_network=true
|
|
type="CIFS/SMB"
|
|
;;
|
|
esac
|
|
|
|
if [[ "$is_network" == true && -d "$mount_point" ]]; then
|
|
|
|
local is_mounted=false
|
|
while IFS= read -r proc_line; do
|
|
local proc_device proc_mount_point proc_fs_type
|
|
read -r proc_device proc_mount_point proc_fs_type _ <<< "$proc_line"
|
|
if [[ "$proc_mount_point" == "$mount_point" && ("$proc_fs_type" == "nfs" || "$proc_fs_type" == "nfs4" || "$proc_fs_type" == "cifs") ]]; then
|
|
is_mounted=true
|
|
break
|
|
fi
|
|
done < /proc/mounts
|
|
|
|
|
|
if [[ "$is_mounted" == false ]]; then
|
|
fstab_mounts+=("$mount_point|$source|$type|0|0|fstab-inactive")
|
|
fi
|
|
fi
|
|
done < /etc/fstab
|
|
|
|
printf '%s\n' "${fstab_mounts[@]}"
|
|
}
|
|
|
|
|
|
detect_local_directories() {
|
|
local local_dirs=()
|
|
local network_mounts=()
|
|
|
|
|
|
local all_network_mounts
|
|
all_network_mounts=$(detect_mounted_shares)
|
|
local fstab_network_mounts
|
|
fstab_network_mounts=$(detect_fstab_network_mounts)
|
|
|
|
|
|
local combined_network_mounts="$all_network_mounts"$'\n'"$fstab_network_mounts"
|
|
|
|
while IFS='|' read -r mount_point source type size used mount_source; do
|
|
[[ -n "$mount_point" ]] && network_mounts+=("$mount_point")
|
|
done <<< "$combined_network_mounts"
|
|
|
|
if [[ -d "/mnt" ]]; then
|
|
for dir in /mnt/*/; do
|
|
if [[ -d "$dir" && "$(basename "$dir")" != "pve" ]]; then
|
|
local dir_path="${dir%/}"
|
|
local dir_name=$(basename "$dir_path")
|
|
|
|
|
|
local is_network_mount=false
|
|
|
|
for network_mount in "${network_mounts[@]}"; do
|
|
if [[ "$dir_path" == "$network_mount" ]]; then
|
|
is_network_mount=true
|
|
break
|
|
fi
|
|
done
|
|
|
|
|
|
if [[ "$is_network_mount" == false ]]; then
|
|
local dir_size=$(du -sh "$dir_path" 2>/dev/null | awk '{print $1}')
|
|
local_dirs+=("$dir_path|Local|Directory|$dir_size|-|Manual")
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
|
|
printf '%s\n' "${local_dirs[@]}"
|
|
}
|
|
|
|
|
|
are_same_resource() {
|
|
local path1="$1" source1="$2" type1="$3"
|
|
local path2="$4" source2="$5" type2="$6"
|
|
|
|
|
|
[[ "$type1" != "$type2" ]] && return 1
|
|
|
|
|
|
local server1 share1 server2 share2
|
|
|
|
if [[ "$type1" == "NFS" ]]; then
|
|
|
|
server1=$(echo "$source1" | cut -d: -f1)
|
|
share1=$(echo "$source1" | cut -d: -f2)
|
|
server2=$(echo "$source2" | cut -d: -f1)
|
|
share2=$(echo "$source2" | cut -d: -f2)
|
|
elif [[ "$type1" == "CIFS/SMB" ]]; then
|
|
|
|
server1=$(echo "$source1" | cut -d/ -f3)
|
|
share1=$(echo "$source1" | cut -d/ -f4-)
|
|
server2=$(echo "$source2" | cut -d/ -f3)
|
|
share2=$(echo "$source2" | cut -d/ -f4-)
|
|
else
|
|
return 1
|
|
fi
|
|
|
|
|
|
if [[ "$server1" == "$server2" && "$share1" == "$share2" ]]; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
|
|
|
|
detect_problematic_storage() {
|
|
local mount_point="$1"
|
|
local mount_source="$2"
|
|
local type="$3"
|
|
|
|
|
|
if [[ "$mount_source" == "Proxmox-GUI" && "$type" == "CIFS/SMB" ]]; then
|
|
|
|
local permissions=$(stat -c '%a' "$mount_point" 2>/dev/null)
|
|
local owner=$(stat -c '%U' "$mount_point" 2>/dev/null)
|
|
local group=$(stat -c '%G' "$mount_point" 2>/dev/null)
|
|
|
|
|
|
if [[ "$owner" == "root" && "$group" == "root" && "$permissions" =~ ^75[0-5]$ ]]; then
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
|
|
|
|
|
|
select_host_directory_unified() {
|
|
local mounted_shares local_dirs options=() fstab_mounts
|
|
|
|
|
|
mounted_shares=$(detect_mounted_shares)
|
|
fstab_mounts=$(detect_fstab_network_mounts)
|
|
local_dirs=$(detect_local_directories)
|
|
|
|
|
|
local all_network_shares="$mounted_shares"
|
|
if [[ -n "$fstab_mounts" ]]; then
|
|
all_network_shares="$all_network_shares"$'\n'"$fstab_mounts"
|
|
fi
|
|
|
|
|
|
local has_local_dirs=false
|
|
local has_network_shares=false
|
|
|
|
[[ -n "$local_dirs" ]] && has_local_dirs=true
|
|
[[ -n "$all_network_shares" ]] && has_network_shares=true
|
|
|
|
if [[ "$has_local_dirs" == false && "$has_network_shares" == false ]]; then
|
|
whiptail --title "$(translate "No Directories Found")" \
|
|
--msgbox "$(translate "No directories found in /mnt and no mounted network shares detected.")\n\n$(translate "Please:")\n• Mount shares using Proxmox GUI\n• Create directories in /mnt\n• Use manual path entry" 12 70
|
|
|
|
|
|
local manual_path
|
|
manual_path=$(whiptail --title "$(translate "Manual Path Entry")" \
|
|
--inputbox "$(translate "Enter the full path to the host directory:")" 10 70 "/mnt/" 3>&1 1>&2 2>&3)
|
|
|
|
if [[ -n "$manual_path" && -d "$manual_path" ]]; then
|
|
echo "$manual_path"
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
local processed_resources=()
|
|
local final_shares=()
|
|
|
|
|
|
while IFS='|' read -r mount_point source type size used mount_source; do
|
|
if [[ -n "$mount_point" ]]; then
|
|
|
|
local is_duplicate=false
|
|
local duplicate_index=-1
|
|
|
|
for i in "${!processed_resources[@]}"; do
|
|
IFS='|' read -r proc_path proc_source proc_type proc_size proc_used proc_mount_source <<< "${processed_resources[$i]}"
|
|
if are_same_resource "$mount_point" "$source" "$type" "$proc_path" "$proc_source" "$proc_type"; then
|
|
|
|
if [[ ("$mount_source" == "Manual" || "$proc_mount_source" =~ ^fstab) && "$proc_mount_source" == "Proxmox-GUI" ]]; then
|
|
|
|
is_duplicate=true
|
|
duplicate_index=$i
|
|
break
|
|
elif [[ "$mount_source" == "Proxmox-GUI" && ("$proc_mount_source" == "Manual" || "$proc_mount_source" =~ ^fstab) ]]; then
|
|
|
|
is_duplicate=true
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
|
|
if [[ "$is_duplicate" == true && "$duplicate_index" -ge 0 ]]; then
|
|
|
|
processed_resources[$duplicate_index]="$mount_point|$source|$type|$size|$used|$mount_source"
|
|
elif [[ "$is_duplicate" == false ]]; then
|
|
|
|
processed_resources+=("$mount_point|$source|$type|$size|$used|$mount_source")
|
|
fi
|
|
fi
|
|
done <<< "$all_network_shares"
|
|
|
|
if [[ "$has_local_dirs" == true ]]; then
|
|
options+=("" "\Z4──────────────── LOCAL DIRECTORIES ────────────────\Zn")
|
|
|
|
while IFS='|' read -r dir_path source type size used mount_source; do
|
|
if [[ -n "$dir_path" && "$type" == "Directory" ]]; then
|
|
local dir_name=$(basename "$dir_path")
|
|
local permissions=$(stat -c '%a' "$dir_path" 2>/dev/null)
|
|
local owner_group=$(stat -c '%U:%G' "$dir_path" 2>/dev/null)
|
|
options+=("$dir_path" "$dir_name ($size)")
|
|
fi
|
|
done <<< "$local_dirs"
|
|
fi
|
|
|
|
if [[ ${#processed_resources[@]} -gt 0 ]]; then
|
|
|
|
[[ "$has_local_dirs" == true ]] && options+=("" "")
|
|
options+=("" "\Z4────────────────── NETWORK SHARES──────────────────\Zn")
|
|
|
|
for resource in "${processed_resources[@]}"; do
|
|
IFS='|' read -r mount_point source type size used mount_source <<< "$resource"
|
|
|
|
local share_name=$(basename "$source")
|
|
local mount_name=$(basename "$mount_point")
|
|
local permissions=$(stat -c '%a' "$mount_point" 2>/dev/null)
|
|
local owner_group=$(stat -c '%U:%G' "$mount_point" 2>/dev/null)
|
|
|
|
|
|
local warning=""
|
|
if detect_problematic_storage "$mount_point" "$mount_source" "$type"; then
|
|
warning=" [READ-ONLY]"
|
|
fi
|
|
|
|
local prefix=""
|
|
case "$mount_source" in
|
|
"Proxmox-GUI")
|
|
prefix="GUI-"
|
|
;;
|
|
"fstab-active")
|
|
prefix="fstab-"
|
|
;;
|
|
"fstab-inactive")
|
|
prefix="fstab(off)-"
|
|
;;
|
|
*)
|
|
prefix=""
|
|
;;
|
|
esac
|
|
|
|
options+=("$mount_point" "$prefix$type: $share_name → $mount_name ($size)$warning")
|
|
|
|
done
|
|
fi
|
|
|
|
|
|
options+=("" "")
|
|
options+=("" "\Z4────────────────────── OTHER ──────────────────────\Zn")
|
|
options+=("MANUAL" "$(translate "Enter path manually")")
|
|
|
|
if [[ ${#options[@]} -eq 0 ]]; then
|
|
dialog --title "$(translate "No Valid Options")" \
|
|
--msgbox "$(translate "No valid directories or shares found.")" 8 50
|
|
return 1
|
|
fi
|
|
|
|
|
|
local result
|
|
result=$(dialog --clear --colors --title "$(translate "Select Host Directory")" \
|
|
--menu "\n$(translate "Select the directory to bind to container:")" 25 85 15 \
|
|
"${options[@]}" 3>&1 1>&2 2>&3)
|
|
|
|
|
|
|
|
local dialog_result=$?
|
|
if [[ $dialog_result -ne 0 ]]; then
|
|
return 1
|
|
fi
|
|
|
|
|
|
if [[ -z "$result" || "$result" =~ ^━ ]]; then
|
|
return 1
|
|
fi
|
|
|
|
|
|
if [[ "$result" == "MANUAL" ]]; then
|
|
result=$(whiptail --title "$(translate "Manual Path Entry")" \
|
|
--inputbox "$(translate "Enter the full path to the host directory:")" 10 70 "/mnt/" 3>&1 1>&2 2>&3)
|
|
if [[ $? -ne 0 ]]; then
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
if [[ -z "$result" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
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
|
|
|
|
|
|
if detect_problematic_storage "$result" "Proxmox-GUI" "CIFS/SMB"; then
|
|
dialog --clear --title "$(translate "CIFS Storage Notice")" --yesno "\
|
|
$(translate "\nThis directory is a CIFS storage configured from the Proxmox web interface.")\n\n\
|
|
$(translate "When CIFS storage is configured through the Proxmox GUI, it applies restrictive permissions.")\n\
|
|
$(translate "As a result, LXC containers can usually READ files but may NOT be able to WRITE.")\n\n\
|
|
$(translate "If you need WRITE access, cancel this operation and instead use the option:")\n\
|
|
$(translate "Configure Samba shared on Host")\n\n\
|
|
|
|
$(translate "Do you want to continue anyway?")" 18 80 3>&1 1>&2 2>&3
|
|
|
|
dialog_result=$?
|
|
|
|
case $dialog_result in
|
|
0)
|
|
;;
|
|
1|255)
|
|
return 1
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
echo "$result"
|
|
return 0
|
|
}
|
|
|
|
|
|
|
|
|
|
select_lxc_container() {
|
|
local ct_list ctid ct_status
|
|
|
|
ct_list=$(pct list 2>/dev/null | awk 'NR>1 {print $1, $2, $3}')
|
|
if [[ -z "$ct_list" ]]; then
|
|
whiptail --title "Error" \
|
|
--msgbox "No LXC containers available" 8 50
|
|
return 1
|
|
fi
|
|
|
|
local options=()
|
|
while read -r id name status; do
|
|
if [[ -n "$id" && "$id" =~ ^[0-9]+$ ]]; then
|
|
name=${name:-"unnamed"}
|
|
status=${status:-"unknown"}
|
|
options+=("$id" "$name ($status)")
|
|
fi
|
|
done <<< "$ct_list"
|
|
|
|
if [[ ${#options[@]} -eq 0 ]]; then
|
|
dialog --title "Error" \
|
|
--msgbox "No valid containers found" 8 50
|
|
return 1
|
|
fi
|
|
|
|
ctid=$(dialog --title "Select LXC Container" \
|
|
--menu "Select container:" 25 85 15 \
|
|
"${options[@]}" 3>&1 1>&2 2>&3)
|
|
|
|
local result=$?
|
|
if [[ $result -ne 0 || -z "$ctid" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
echo "$ctid"
|
|
return 0
|
|
}
|
|
|
|
|
|
select_container_mount_point() {
|
|
local ctid="$1"
|
|
local host_dir="$2"
|
|
local choice mount_point base_name
|
|
|
|
base_name=$(basename "$host_dir")
|
|
|
|
while true; do
|
|
choice=$(dialog --clear --title "$(translate "Configure Mount Point inside LXC")" \
|
|
--menu "\n$(translate "Where to mount inside container?")" 18 70 5 \
|
|
"1" "$(translate "Create new directory in /mnt")" \
|
|
"2" "$(translate "Enter path manually")" \
|
|
"3" "$(translate "Cancel")" 3>&1 1>&2 2>&3)
|
|
|
|
local dialog_result=$?
|
|
if [[ $dialog_result -ne 0 ]]; then
|
|
return 1
|
|
fi
|
|
|
|
case "$choice" in
|
|
1)
|
|
mount_point=$(whiptail --inputbox "$(translate "Enter folder name for /mnt:")" \
|
|
10 60 "$base_name" 3>&1 1>&2 2>&3)
|
|
if [[ $? -ne 0 ]]; then
|
|
continue
|
|
fi
|
|
[[ -z "$mount_point" ]] && continue
|
|
mount_point="/mnt/$mount_point"
|
|
pct exec "$ctid" -- mkdir -p "$mount_point" 2>/dev/null
|
|
;;
|
|
|
|
2)
|
|
mount_point=$(whiptail --inputbox "$(translate "Enter full path:")" \
|
|
10 70 "/mnt/$base_name" 3>&1 1>&2 2>&3)
|
|
if [[ $? -ne 0 ]]; then
|
|
continue
|
|
fi
|
|
[[ -z "$mount_point" ]] && continue
|
|
pct exec "$ctid" -- mkdir -p "$mount_point" 2>/dev/null
|
|
;;
|
|
|
|
3)
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
if pct exec "$ctid" -- test -d "$mount_point" 2>/dev/null; then
|
|
echo "$mount_point"
|
|
return 0
|
|
else
|
|
whiptail --msgbox "$(translate "Could not create or access directory:") $mount_point" 8 70
|
|
continue
|
|
fi
|
|
done
|
|
}
|
|
|
|
# ==========================================================
|
|
# MOUNT MANAGEMENT FUNCTIONS
|
|
# ==========================================================
|
|
|
|
|
|
view_mount_points() {
|
|
show_proxmenux_logo
|
|
msg_title "$(translate 'Current LXC Mount Points')"
|
|
|
|
local ct_list
|
|
ct_list=$(pct list 2>/dev/null | awk 'NR>1 {print $1, $2, $3}')
|
|
|
|
if [[ -z "$ct_list" ]]; then
|
|
msg_warn "$(translate 'No LXC containers found')"
|
|
echo -e ""
|
|
msg_success "$(translate 'Press Enter to continue...')"
|
|
read -r
|
|
return 1
|
|
fi
|
|
|
|
local found_mounts=false
|
|
|
|
while read -r id name status; do
|
|
if [[ -n "$id" && "$id" =~ ^[0-9]+$ ]]; then
|
|
local conf="/etc/pve/lxc/${id}.conf"
|
|
if [[ -f "$conf" ]]; then
|
|
local mounts
|
|
mounts=$(grep "^mp[0-9]*:" "$conf" 2>/dev/null)
|
|
|
|
if [[ -n "$mounts" ]]; then
|
|
if [[ "$found_mounts" == false ]]; then
|
|
found_mounts=true
|
|
fi
|
|
|
|
echo -e "${TAB}${BOLD}$(translate 'Container') $id: $name ($status)${CL}"
|
|
|
|
while IFS= read -r mount_line; do
|
|
if [[ -n "$mount_line" ]]; then
|
|
local mp_id=$(echo "$mount_line" | cut -d: -f1)
|
|
local mount_info=$(echo "$mount_line" | cut -d: -f2-)
|
|
local host_path=$(echo "$mount_info" | cut -d, -f1)
|
|
local container_path=$(echo "$mount_info" | grep -o 'mp=[^,]*' | cut -d= -f2)
|
|
local 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: $options${CL}"
|
|
fi
|
|
done <<< "$mounts"
|
|
echo ""
|
|
fi
|
|
fi
|
|
fi
|
|
done <<< "$ct_list"
|
|
|
|
if [[ "$found_mounts" == false ]]; then
|
|
msg_ok "$(translate 'No mount points found in any container')"
|
|
fi
|
|
|
|
echo -e ""
|
|
msg_success "$(translate 'Press Enter to continue...')"
|
|
read -r
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
remove_mount_point() {
|
|
show_proxmenux_logo
|
|
msg_title "$(translate 'Remove LXC Mount Point')"
|
|
|
|
|
|
local container_id
|
|
container_id=$(select_lxc_container)
|
|
if [[ $? -ne 0 || -z "$container_id" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
local conf="/etc/pve/lxc/${container_id}.conf"
|
|
if [[ ! -f "$conf" ]]; then
|
|
msg_error "$(translate 'Container configuration not found')"
|
|
echo -e ""
|
|
msg_success "$(translate 'Press Enter to continue...')"
|
|
read -r
|
|
return 1
|
|
fi
|
|
|
|
|
|
local mounts
|
|
mounts=$(grep "^mp[0-9]*:" "$conf" 2>/dev/null)
|
|
|
|
if [[ -z "$mounts" ]]; then
|
|
show_proxmenux_logo
|
|
msg_title "$(translate 'Remove LXC Mount Point')"
|
|
msg_warn "$(translate 'No mount points found in container') $container_id"
|
|
echo -e ""
|
|
msg_success "$(translate 'Press Enter to continue...')"
|
|
read -r
|
|
return 1
|
|
fi
|
|
|
|
|
|
local options=()
|
|
while IFS= read -r mount_line; do
|
|
if [[ -n "$mount_line" ]]; then
|
|
local mp_id=$(echo "$mount_line" | cut -d: -f1)
|
|
local mount_info=$(echo "$mount_line" | cut -d: -f2-)
|
|
local host_path=$(echo "$mount_info" | cut -d, -f1)
|
|
local container_path=$(echo "$mount_info" | grep -o 'mp=[^,]*' | cut -d= -f2)
|
|
|
|
options+=("$mp_id" "$host_path → $container_path")
|
|
fi
|
|
done <<< "$mounts"
|
|
|
|
if [[ ${#options[@]} -eq 0 ]]; then
|
|
show_proxmenux_logo
|
|
msg_title "$(translate 'Remove LXC Mount Point')"
|
|
msg_warn "$(translate 'No valid mount points found')"
|
|
echo -e ""
|
|
msg_success "$(translate 'Press Enter to continue...')"
|
|
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)
|
|
|
|
if [[ $? -ne 0 || -z "$selected_mp" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
|
|
local selected_mount_line
|
|
selected_mount_line=$(grep "^${selected_mp}:" "$conf")
|
|
local mount_info=$(echo "$selected_mount_line" | cut -d: -f2-)
|
|
local host_path=$(echo "$mount_info" | cut -d, -f1)
|
|
local container_path=$(echo "$mount_info" | grep -o 'mp=[^,]*' | cut -d= -f2)
|
|
|
|
local confirm_msg="$(translate "Remove Mount Point Confirmation:")
|
|
|
|
$(translate "Container ID"): $container_id
|
|
$(translate "Mount Point ID"): $selected_mp
|
|
$(translate "Host Path"): $host_path
|
|
$(translate "Container Path"): $container_path
|
|
|
|
$(translate "WARNING"): $(translate "This will remove the mount point from the container configuration.")
|
|
$(translate "The host directory and its contents will remain unchanged.")
|
|
|
|
$(translate "Proceed with removal")?"
|
|
|
|
if ! dialog --clear --title "$(translate "Confirm Mount Point Removal")" --yesno "$confirm_msg" 18 80; then
|
|
return 1
|
|
fi
|
|
|
|
show_proxmenux_logo
|
|
msg_title "$(translate 'Remove LXC Mount Point')"
|
|
|
|
msg_info "$(translate 'Removing mount point') $selected_mp $(translate 'from container') $container_id..."
|
|
|
|
|
|
if pct set "$container_id" --delete "$selected_mp" 2>/dev/null; then
|
|
msg_ok "$(translate 'Mount point removed successfully')"
|
|
|
|
|
|
local ct_status
|
|
ct_status=$(pct status "$container_id" | awk '{print $2}')
|
|
|
|
if [[ "$ct_status" == "running" ]]; then
|
|
echo -e ""
|
|
if whiptail --yesno "$(translate "Container is running. Restart to apply changes?")" 8 60; then
|
|
msg_info "$(translate 'Restarting container...')"
|
|
if pct reboot "$container_id"; then
|
|
sleep 3
|
|
msg_ok "$(translate 'Container restarted successfully')"
|
|
else
|
|
msg_warn "$(translate 'Failed to restart container - restart manually')"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
echo -e ""
|
|
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_dir (preserved)${CL}"
|
|
echo -e "${TAB}${BGN}$(translate 'Container Path:')${CL} ${BL}$container_path (unmounted)${CL}"
|
|
|
|
else
|
|
msg_error "$(translate 'Failed to remove mount point')"
|
|
fi
|
|
|
|
echo -e ""
|
|
msg_success "$(translate 'Press Enter to continue...')"
|
|
read -r
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
# MINIMAL CONTAINER SETUP (NO HOST MODIFICATIONS)
|
|
# ==========================================================
|
|
|
|
get_container_uid_shift() {
|
|
local ctid="$1"
|
|
local conf="/etc/pve/lxc/${ctid}.conf"
|
|
|
|
if [[ ! -f "$conf" ]]; then
|
|
echo "100000"
|
|
return 0
|
|
fi
|
|
|
|
local unpriv
|
|
unpriv=$(grep "^unprivileged:" "$conf" | awk '{print $2}')
|
|
|
|
if [[ "$unpriv" == "1" ]]; then
|
|
local uid_shift=$(grep "^lxc.idmap" "$conf" | grep 'u 0' | awk '{print $5}' | head -1)
|
|
echo "${uid_shift:-100000}"
|
|
return 0
|
|
fi
|
|
|
|
echo "0"
|
|
return 0
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setup_minimal_container_access() {
|
|
local ctid="$1" host_dir="$2" ct_mount_point="$3"
|
|
local uid_shift container_type host_gid host_group mapped_gid
|
|
|
|
|
|
host_gid=$(stat -c '%g' "$host_dir" 2>/dev/null)
|
|
host_group=$(getent group "$host_gid" | cut -d: -f1 2>/dev/null)
|
|
host_group=${host_group:-"root"}
|
|
|
|
msg_ok "$(translate "Host directory info detected - preserving existing configuration")" >&2
|
|
|
|
uid_shift=$(get_container_uid_shift "$ctid")
|
|
|
|
if [[ "$uid_shift" -eq 0 ]]; then
|
|
msg_ok "$(translate "PRIVILEGED container detected - using direct UID/GID mapping")" >&2
|
|
mapped_gid="$host_gid"
|
|
container_type="privileged"
|
|
else
|
|
msg_ok "$(translate "UNPRIVILEGED container detected - using mapped UID/GID")" >&2
|
|
mapped_gid=$((uid_shift + host_gid))
|
|
container_type="unprivileged"
|
|
msg_ok "$(translate "UID shift:") $uid_shift, $(translate "Host GID:") $host_gid → $(translate "Container GID:") $mapped_gid" >&2
|
|
fi
|
|
|
|
msg_info "$(translate "Creating compatible group in container...")" >&2
|
|
|
|
local container_group="shared_${host_gid}"
|
|
|
|
pct exec "$ctid" -- groupadd -g "$mapped_gid" "$container_group" 2>/dev/null || true
|
|
|
|
|
|
local users_added=0
|
|
local user_list=""
|
|
|
|
local temp_file="/tmp/users_$$.txt"
|
|
pct exec "$ctid" -- awk -F: '$3 >= 25 && $3 < 65534 {print $1}' /etc/passwd > "$temp_file"
|
|
|
|
if [[ -s "$temp_file" ]]; then
|
|
while IFS= read -r username; do
|
|
if [[ -n "$username" ]]; then
|
|
if pct exec "$ctid" -- usermod -aG "$container_group" "$username" 2>/dev/null; then
|
|
users_added=$((users_added + 1))
|
|
user_list="$user_list $username"
|
|
fi
|
|
fi
|
|
done < "$temp_file"
|
|
|
|
if [[ $users_added -gt 0 ]]; then
|
|
msg_ok "$(translate "Users added to group") $container_group: $users_added" >&2
|
|
fi
|
|
fi
|
|
|
|
rm -f "$temp_file"
|
|
|
|
if [[ "$container_type" == "unprivileged" ]]; then
|
|
|
|
if ! command -v setfacl >/dev/null 2>&1; then
|
|
apt-get update >/dev/null 2>&1
|
|
apt-get install -y acl >/dev/null 2>&1
|
|
msg_ok "$(translate "ACL tools installed")" >&2
|
|
fi
|
|
|
|
local acls_applied=0
|
|
local acl_users=()
|
|
|
|
|
|
while IFS=: read -r username _ ct_uid _; do
|
|
if [[ $ct_uid -ge 25 && $ct_uid -lt 65534 ]]; then
|
|
local host_uid=$((uid_shift + ct_uid))
|
|
|
|
if setfacl -m u:$host_uid:rwx "$host_dir" 2>/dev/null && \
|
|
setfacl -m d:u:$host_uid:rwx "$host_dir" 2>/dev/null; then
|
|
acls_applied=$((acls_applied + 1))
|
|
acl_users+=("$username")
|
|
fi
|
|
fi
|
|
done < <(pct exec "$ctid" -- cat /etc/passwd)
|
|
|
|
if [[ $acls_applied -gt 0 ]]; then
|
|
msg_ok "$(translate "ACL entries applied for") $acls_applied $(translate "users:") ${acl_users[*]}" >&2
|
|
fi
|
|
fi
|
|
|
|
msg_info "$(translate "Configuring container mount point with setgid...")" >&2
|
|
|
|
pct exec "$ctid" -- chgrp "$container_group" "$ct_mount_point" 2>/dev/null || true
|
|
pct exec "$ctid" -- chmod 2775 "$ct_mount_point" 2>/dev/null || true
|
|
|
|
msg_ok "$(translate "Container mount point configured with setgid")" >&2
|
|
|
|
echo "$container_type|$host_group|$host_gid|$container_group|$mapped_gid"
|
|
return 0
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 used idx next=0
|
|
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" host_path="$2" ct_path="$3"
|
|
local mpidx result
|
|
|
|
if [[ ! "$ctid" =~ ^[0-9]+$ ]]; then
|
|
return 1
|
|
fi
|
|
|
|
if [[ -z "$ctid" || -z "$host_path" || -z "$ct_path" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
if pct config "$ctid" | grep -q "$host_path"; then
|
|
msg_warn "$(translate "Mount already exists for this path")"
|
|
return 1
|
|
fi
|
|
|
|
mpidx=$(get_next_mp_index "$ctid")
|
|
|
|
result=$(pct set "$ctid" -mp${mpidx} "$host_path,mp=$ct_path,shared=1,backup=0,acl=1" 2>&1)
|
|
|
|
if [[ $? -eq 0 ]]; then
|
|
msg_ok "$(translate "Successfully mounted:") $host_path → $ct_path"
|
|
return 0
|
|
|
|
else
|
|
msg_error "$(translate "Failed to add bind mount:") $result"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# ==========================================================
|
|
# MAIN FUNCTION
|
|
# ==========================================================
|
|
|
|
mount_host_directory_minimal() {
|
|
# Step 1: Select container
|
|
local container_id
|
|
container_id=$(select_lxc_container)
|
|
if [[ $? -ne 0 || -z "$container_id" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
# Step 1.1: Ensure running
|
|
ct_status=$(pct status "$container_id" | awk '{print $2}')
|
|
if [[ "$ct_status" != "running" ]]; then
|
|
show_proxmenux_logo
|
|
msg_title "$(translate 'Mount Host Directory to LXC')"
|
|
msg_info "$(translate "Starting container") $container_id..."
|
|
if pct start "$container_id"; then
|
|
sleep 3
|
|
cleanup
|
|
else
|
|
msg_error "$(translate "Failed to start container")"
|
|
echo -e ""
|
|
msg_success "$(translate 'Press Enter to continue...')"
|
|
read -r
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
|
|
# Step 2: Select host directory (unified menu)
|
|
local host_dir
|
|
host_dir=$(select_host_directory_unified)
|
|
if [[ $? -ne 0 || -z "$host_dir" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
|
|
# Step 3: Select container mount point
|
|
local ct_mount_point
|
|
ct_mount_point=$(select_container_mount_point "$container_id" "$host_dir")
|
|
if [[ $? -ne 0 || -z "$ct_mount_point" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
|
|
# Step 4: Get container info for confirmation
|
|
local uid_shift container_type_display
|
|
uid_shift=$(get_container_uid_shift "$container_id")
|
|
if [[ "$uid_shift" -eq 0 ]]; then
|
|
container_type_display="$(translate 'Privileged')"
|
|
else
|
|
container_type_display="$(translate 'Unprivileged')"
|
|
fi
|
|
|
|
|
|
# Step 4.1: Confirmation
|
|
local confirm_msg="$(translate "Mount Configuration Summary:")
|
|
|
|
$(translate "Container ID"): $container_id ($container_type_display)
|
|
$(translate "Host Directory"): $host_dir
|
|
$(translate "Container Mount Point"): $ct_mount_point
|
|
|
|
$(translate "Notes:")
|
|
- $(translate "The host directory will remain unchanged")
|
|
- $(translate "Basic permissions will be set inside the container")
|
|
- $(translate "ACL and setgid will be applied for group consistency")
|
|
|
|
$(translate "Proceed")?"
|
|
|
|
if ! dialog --clear --title "$(translate "Confirm Mount point")" --yesno "$confirm_msg" 18 80; then
|
|
return 1
|
|
fi
|
|
|
|
show_proxmenux_logo
|
|
msg_title "$(translate 'Mount Host Directory to LXC')"
|
|
|
|
msg_ok "$(translate 'Container selected:') $container_id"
|
|
msg_ok "$(translate 'Container is running')"
|
|
msg_ok "$(translate 'Host directory selected:') $host_dir"
|
|
msg_ok "$(translate 'Container mount point selected:') $ct_mount_point"
|
|
|
|
|
|
# Step 5: Add mount
|
|
if ! add_bind_mount "$container_id" "$host_dir" "$ct_mount_point"; then
|
|
echo -e ""
|
|
msg_success "$(translate 'Press Enter to continue...')"
|
|
read -r
|
|
return 1
|
|
fi
|
|
|
|
# Step 6: Container setup
|
|
local setup_info
|
|
setup_info=$(setup_minimal_container_access "$container_id" "$host_dir" "$ct_mount_point")
|
|
|
|
# Parse setup info
|
|
IFS='|' read -r container_type host_group host_gid container_group mapped_gid fix_type <<< "$setup_info"
|
|
|
|
msg_ok "$(translate "container configuration completed")"
|
|
|
|
# Step 7: Summary
|
|
echo -e ""
|
|
echo -e "${TAB}${BOLD}$(translate 'Mount Added Successfully:')${CL}"
|
|
echo -e "${TAB}${BGN}$(translate 'Container:')${CL} ${BL}$container_id ($container_type_display)${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}"
|
|
echo -e "${TAB}${BGN}$(translate 'Action Taken:')${CL} ${BL}PRESERVE existing permissions${CL}"
|
|
|
|
if [[ "$fix_type" == "cifs-fixed" ]]; then
|
|
echo -e "${TAB}${BGN}$(translate 'Permission Strategy:')${CL} ${BL}CIFS compatibility fixes applied${CL}"
|
|
echo -e "${TAB}${YW}$(translate 'WARNING:')${CL} ${BL}Storage CIFS de Proxmox puede ser solo LECTURA${CL}"
|
|
else
|
|
echo -e "${TAB}${BGN}$(translate 'Permission Strategy:')${CL} ${BL}$(if [[ "$container_type" == "unprivileged" ]]; then echo "ACL (mapped UIDs)"; else echo "Direct mapping"; fi)${CL}"
|
|
fi
|
|
|
|
# Step 8: Restart
|
|
echo -e ""
|
|
if whiptail --yesno "$(translate "Restart container to activate mount?")" 8 60; then
|
|
msg_info "$(translate 'Restarting container...')"
|
|
if pct reboot "$container_id"; then
|
|
sleep 5
|
|
msg_ok "$(translate 'Container restarted successfully')"
|
|
|
|
echo -e ""
|
|
echo -e "${TAB}${BOLD}$(translate 'Testing access and read/write:')${CL}"
|
|
test_user=$(pct exec "$container_id" -- sh -c "id -u www-data >/dev/null 2>&1 && echo www-data || echo root")
|
|
|
|
if pct exec "$container_id" -- su -s /bin/bash $test_user -c "touch $ct_mount_point/test_access.txt" 2>/dev/null; then
|
|
msg_ok "$(translate "Mount access and read/write successful (tested as $test_user)")"
|
|
rm -f "$host_dir/test_access.txt" 2>/dev/null || true
|
|
else
|
|
msg_warn "$(translate "⚠ Test read/write failed - may need additional configuration")"
|
|
|
|
fi
|
|
|
|
else
|
|
msg_warn "$(translate 'Failed to restart - restart manually')"
|
|
fi
|
|
fi
|
|
|
|
echo -e ""
|
|
msg_success "$(translate 'Press Enter to continue...')"
|
|
read -r
|
|
}
|
|
|
|
# Main menu
|
|
main_menu() {
|
|
while true; do
|
|
choice=$(dialog --title "$(translate 'LXC Mount Manager')" \
|
|
--menu "\n$(translate 'Choose an option:')" 25 85 15 \
|
|
"1" "$(translate 'Mount point: Host Directory to LXC')" \
|
|
"2" "$(translate 'View Mount Points')" \
|
|
"3" "$(translate 'Remove Mount Point')" \
|
|
"4" "$(translate 'Exit')" 3>&1 1>&2 2>&3)
|
|
|
|
case $choice in
|
|
1)
|
|
mount_host_directory_minimal
|
|
;;
|
|
2)
|
|
view_mount_points
|
|
;;
|
|
3)
|
|
remove_mount_point
|
|
;;
|
|
4|"")
|
|
exit 0
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
|
|
main_menu
|