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

1117 lines
36 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
# 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 alternatives=0
while IFS='|' read -r alt_mount_point alt_source alt_type alt_size alt_used alt_mount_source; do
if [[ -n "$alt_mount_point" && "$alt_mount_point" != "$mount_point" ]]; then
if are_same_resource "$mount_point" "$source" "$type" "$alt_mount_point" "$alt_source" "$alt_type"; then
alternatives=$((alternatives + 1))
fi
fi
done <<< "$all_network_shares"
local alt_text=""
[[ $alternatives -gt 0 ]] && alt_text=" (+$alternatives alternative$([ $alternatives -gt 1 ] && echo 's'))"
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$alt_text")
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
2025-09-10 12:16:25 +02:00
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
2025-09-10 11:10:02 +02:00
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
2025-09-10 12:16:25 +02:00
msg_ok "$(translate 'No mount points found in any container')"
2025-09-10 11:10:02 +02:00
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
2025-09-10 12:16:25 +02:00
msg_ok "$(translate "Users added to group") $container_group: $users_added" >&2
2025-09-10 11:10:02 +02:00
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