mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2025-10-02 08:06:17 +00:00
463 lines
14 KiB
Bash
463 lines
14 KiB
Bash
![]() |
#!/bin/bash
|
||
|
# ==========================================================
|
||
|
# ProxMenux - LXC Mount Manager (Standalone - CLEAN v1)
|
||
|
# ==========================================================
|
||
|
# Author : MacRimi
|
||
|
# Copyright : (c) 2024 MacRimi
|
||
|
# License : MIT
|
||
|
# Version : 3.0-clean
|
||
|
# Last Updated: $(date +%d/%m/%Y)
|
||
|
# ==========================================================
|
||
|
|
||
|
BASE_DIR="/usr/local/share/proxmenux"
|
||
|
source "$BASE_DIR/utils.sh"
|
||
|
|
||
|
|
||
|
SHARE_COMMON_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main/scripts/global/share-common.func"
|
||
|
if ! source <(curl -s "$SHARE_COMMON_URL" 2>/dev/null); then
|
||
|
SHARE_COMMON_LOADED=false
|
||
|
else
|
||
|
SHARE_COMMON_LOADED=true
|
||
|
fi
|
||
|
|
||
|
load_language
|
||
|
initialize_cache
|
||
|
|
||
|
# ==========================================================
|
||
|
# CORE FUNCTIONS
|
||
|
# ==========================================================
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
ensure_host_group() {
|
||
|
local group_name="$1"
|
||
|
if ! getent group "$group_name" >/dev/null 2>&1; then
|
||
|
if groupadd "$group_name" >/dev/null 2>&1; then
|
||
|
msg_ok "$(translate "Group created:") $group_name"
|
||
|
else
|
||
|
msg_error "$(translate "Failed to create group:") $group_name"
|
||
|
return 1
|
||
|
fi
|
||
|
fi
|
||
|
local gid
|
||
|
gid="$(getent group "$group_name" | cut -d: -f3)"
|
||
|
echo "$gid"
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
|
||
|
get_container_uid_shift() {
|
||
|
local ctid="$1"
|
||
|
local conf="/etc/pve/lxc/${ctid}.conf"
|
||
|
local uid_shift
|
||
|
|
||
|
if [[ ! -f "$conf" ]]; then
|
||
|
echo "100000"
|
||
|
return 0
|
||
|
fi
|
||
|
|
||
|
|
||
|
local unpriv
|
||
|
unpriv=$(grep "^unprivileged:" "$conf" | awk '{print $2}')
|
||
|
|
||
|
if [[ "$unpriv" == "1" ]]; then
|
||
|
|
||
|
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_container_access() {
|
||
|
local ctid="$1" group_name="$2" host_gid="$3" host_dir="$4"
|
||
|
local uid_shift mapped_gid
|
||
|
|
||
|
if [[ ! "$ctid" =~ ^[0-9]+$ ]]; then
|
||
|
msg_error "$(translate 'Invalid container ID format:') $ctid"
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
uid_shift=$(get_container_uid_shift "$ctid")
|
||
|
|
||
|
if [[ "$uid_shift" -eq 0 ]]; then
|
||
|
mapped_gid="$host_gid"
|
||
|
msg_ok "$(translate "Privileged container - using same GID as host:") $host_gid"
|
||
|
else
|
||
|
mapped_gid=$((uid_shift + host_gid))
|
||
|
msg_ok "$(translate "Unprivileged container - using mapped GID:") $mapped_gid (host GID: $host_gid, shift: $uid_shift)"
|
||
|
fi
|
||
|
|
||
|
|
||
|
pct exec "$ctid" -- sh -c "
|
||
|
if ! getent group $group_name >/dev/null 2>&1; then
|
||
|
groupadd -g $mapped_gid $group_name 2>/dev/null || true
|
||
|
else
|
||
|
ct_gid=\$(getent group $group_name | cut -d: -f3)
|
||
|
if [ \"\$ct_gid\" != \"$mapped_gid\" ]; then
|
||
|
groupdel $group_name 2>/dev/null || true
|
||
|
groupadd -g $mapped_gid $group_name 2>/dev/null || true
|
||
|
fi
|
||
|
fi
|
||
|
|
||
|
|
||
|
getent passwd | while IFS=: read -r username _ uid gid _ home _; do
|
||
|
if [ \"\$uid\" -eq 0 ] || [ \"\$uid\" -ge 1000 ] || [ \"\$username\" = \"nobody\" ] || [ \"\$username\" = \"www-data\" ]; then
|
||
|
usermod -aG $group_name \"\$username\" 2>/dev/null || true
|
||
|
fi
|
||
|
done
|
||
|
" 2>/dev/null
|
||
|
|
||
|
|
||
|
if command -v setfacl >/dev/null 2>&1; then
|
||
|
pct exec "$ctid" -- getent passwd | awk -F: '{print $1, $3}' | while read user uid; do
|
||
|
[[ "$uid" -eq 0 ]] && continue
|
||
|
host_uid=$((uid_shift + uid))
|
||
|
setfacl -m u:$host_uid:rwx "$host_dir" 2>/dev/null || true
|
||
|
done
|
||
|
msg_ok "$(translate "ACL permissions applied for container users")"
|
||
|
fi
|
||
|
|
||
|
|
||
|
chmod 2775 "$host_dir" 2>/dev/null || true
|
||
|
chgrp "$group_name" "$host_dir" 2>/dev/null || true
|
||
|
|
||
|
msg_ok "$(translate "Group mapping ensured:") host=$host_gid → ct=$mapped_gid"
|
||
|
msg_ok "$(translate "Multi-approach access configuration completed")"
|
||
|
|
||
|
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
|
||
|
msg_error "$(translate 'Invalid container ID format:') $ctid"
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
if [[ -z "$ctid" || -z "$host_path" || -z "$ct_path" ]]; then
|
||
|
msg_error "$(translate "Missing arguments")"
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
|
||
|
if pct config "$ctid" | grep -q "$host_path"; then
|
||
|
echo -e
|
||
|
msg_warn "$(translate "Directory already mounted in container configuration.")"
|
||
|
echo -e ""
|
||
|
msg_success "$(translate 'Press Enter to return to menu...')"
|
||
|
read -r
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
mpidx=$(get_next_mp_index "$ctid")
|
||
|
|
||
|
result=$(pct set "$ctid" -mp${mpidx} "$host_path,mp=$ct_path,backup=0,ro=0,acl=1" 2>&1)
|
||
|
|
||
|
if [[ $? -eq 0 ]]; then
|
||
|
msg_ok "$(translate "Successfully mounted:") $host_path → $ct_path"
|
||
|
return 0
|
||
|
else
|
||
|
msg_error "$(translate "Error mounting folder:") $result"
|
||
|
return 1
|
||
|
fi
|
||
|
}
|
||
|
|
||
|
|
||
|
# ==========================================================
|
||
|
|
||
|
|
||
|
select_lxc_container() {
|
||
|
local ct_list ctid ct_status
|
||
|
|
||
|
ct_list=$(pct list | awk 'NR>1 {print $1, $2, $3}')
|
||
|
if [[ -z "$ct_list" ]]; then
|
||
|
dialog --title "$(translate "Error")" \
|
||
|
--msgbox "$(translate "No LXC containers available")" 8 50
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
local options=()
|
||
|
while read -r id name status; do
|
||
|
if [[ -n "$id" ]]; then
|
||
|
options+=("$id" "$name ($status)")
|
||
|
fi
|
||
|
done <<< "$ct_list"
|
||
|
|
||
|
ctid=$(dialog --title "$(translate "Select LXC Container")" \
|
||
|
--menu "$(translate "Select container:")" 20 70 12 \
|
||
|
"${options[@]}" 3>&1 1>&2 2>&3)
|
||
|
|
||
|
if [[ -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 existing_dirs options
|
||
|
|
||
|
while true; do
|
||
|
choice=$(whiptail --title "$(translate "Container Mount Point")" \
|
||
|
--menu "$(translate "Where to mount inside container?")" 18 70 5 \
|
||
|
"1" "$(translate "Create new directory in /mnt")" \
|
||
|
"2" "$(translate "Use existing directory in /mnt")" \
|
||
|
"3" "$(translate "Enter path manually")" \
|
||
|
"4" "$(translate "Cancel")" 3>&1 1>&2 2>&3) || return 1
|
||
|
|
||
|
case "$choice" in
|
||
|
1)
|
||
|
mount_point=$(whiptail --inputbox "$(translate "Enter folder name for /mnt:")" 10 60 "shared" 3>&1 1>&2 2>&3) || continue
|
||
|
[[ -z "$mount_point" ]] && continue
|
||
|
mount_point="/mnt/$mount_point"
|
||
|
pct exec "$ctid" -- mkdir -p "$mount_point" 2>/dev/null
|
||
|
;;
|
||
|
|
||
|
2)
|
||
|
existing_dirs=$(pct exec "$ctid" -- ls -1 /mnt 2>/dev/null | awk '{print "/mnt/"$1" "$1}')
|
||
|
if [[ -z "$existing_dirs" ]]; then
|
||
|
whiptail --msgbox "$(translate "No existing directories found in /mnt")" 8 60
|
||
|
continue
|
||
|
fi
|
||
|
mount_point=$(whiptail --title "$(translate "Select Existing Folder")" \
|
||
|
--menu "$(translate "Choose a folder from /mnt:")" 20 70 10 \
|
||
|
$existing_dirs 3>&1 1>&2 2>&3) || continue
|
||
|
;;
|
||
|
|
||
|
3)
|
||
|
mount_point=$(whiptail --inputbox "$(translate "Enter full path:")" 10 70 "/mnt/shared" 3>&1 1>&2 2>&3) || continue
|
||
|
[[ -z "$mount_point" ]] && continue
|
||
|
pct exec "$ctid" -- mkdir -p "$mount_point" 2>/dev/null
|
||
|
;;
|
||
|
|
||
|
4)
|
||
|
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_host_directory_to_lxc() {
|
||
|
|
||
|
# Step 1: Select container
|
||
|
local container_id
|
||
|
container_id=$(select_lxc_container)
|
||
|
if [[ $? -ne 0 || -z "$container_id" ]]; then
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
show_proxmenux_logo
|
||
|
msg_title "$(translate 'Mount Host Directory to LXC Container')"
|
||
|
|
||
|
# Step 1.1: Ensure running
|
||
|
ct_status=$(pct status "$container_id" | awk '{print $2}')
|
||
|
if [[ "$ct_status" != "running" ]]; then
|
||
|
msg_info "$(translate "Starting container") $container_id..."
|
||
|
if pct start "$container_id"; then
|
||
|
sleep 3
|
||
|
msg_ok "$(translate "Container started")"
|
||
|
else
|
||
|
msg_error "$(translate "Failed to start container")"
|
||
|
return 1
|
||
|
fi
|
||
|
fi
|
||
|
msg_ok "$(translate 'Select LXC container')"
|
||
|
|
||
|
|
||
|
|
||
|
# Step 2: Select host directory
|
||
|
local host_dir
|
||
|
host_dir=$(select_host_directory)
|
||
|
if [[ -z "$host_dir" ]]; then
|
||
|
return 1
|
||
|
fi
|
||
|
msg_ok "$(translate 'Select Host directory')"
|
||
|
|
||
|
# Step 3: Setup group
|
||
|
local group_name="sharedfiles"
|
||
|
local group_gid
|
||
|
group_gid=$(ensure_host_group "$group_name")
|
||
|
if [[ -z "$group_gid" ]]; then
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
# Set basic permissions
|
||
|
chown -R root:"$group_name" "$host_dir" 2>/dev/null || true
|
||
|
chmod -R 2775 "$host_dir" 2>/dev/null || true
|
||
|
|
||
|
msg_ok "$(translate 'Select container mount point')"
|
||
|
|
||
|
# Step 4: Select container mount point
|
||
|
local ct_mount_point
|
||
|
ct_mount_point=$(select_container_mount_point "$container_id" "$host_dir")
|
||
|
if [[ -z "$ct_mount_point" ]]; then
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
# Step 5: Confirmation
|
||
|
local uid_shift container_type
|
||
|
uid_shift=$(get_container_uid_shift "$container_id")
|
||
|
if [[ "$uid_shift" -eq 0 ]]; then
|
||
|
container_type="$(translate 'Privileged')"
|
||
|
else
|
||
|
container_type="$(translate 'Unprivileged')"
|
||
|
fi
|
||
|
|
||
|
local confirm_msg="$(translate "Mount Configuration:")
|
||
|
|
||
|
$(translate "Container ID:"): $container_id ($container_type)
|
||
|
$(translate "Host Directory:"): $host_dir
|
||
|
$(translate "Container Mount Point:"): $ct_mount_point
|
||
|
$(translate "Shared Group:"): $group_name (GID: $group_gid)
|
||
|
|
||
|
$(translate "Proceed?")"
|
||
|
|
||
|
if ! whiptail --title "$(translate "Confirm Mount")" --yesno "$confirm_msg" 16 70; then
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
# Step 6: Add mount
|
||
|
if ! add_bind_mount "$container_id" "$host_dir" "$ct_mount_point"; then
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
# Step 7: Setup access ======================================================
|
||
|
|
||
|
setup_container_access "$container_id" "$group_name" "$group_gid" "$host_dir"
|
||
|
|
||
|
# ===========================================================================
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
# Step 8: Final setup
|
||
|
pct exec "$container_id" -- chgrp "$group_name" "$ct_mount_point" 2>/dev/null || true
|
||
|
pct exec "$container_id" -- chmod 2775 "$ct_mount_point" 2>/dev/null || true
|
||
|
|
||
|
# Step 9: Summary
|
||
|
echo -e ""
|
||
|
echo -e "${TAB}${BOLD}$(translate 'Mount Added Successfully:')${CL}"
|
||
|
echo -e "${TAB}${BGN}$(translate 'Container:')${CL} ${BL}$container_id ($container_type)${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 'Group:')${CL} ${BL}$group_name (GID: $group_gid)${CL}"
|
||
|
|
||
|
|
||
|
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 ncp >/dev/null 2>&1 && echo ncp || echo www-data")
|
||
|
|
||
|
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 "⚠ Access test failed - check permissions (user: $test_user)")"
|
||
|
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 "$(translate 'Choose an option:')" 16 60 4 \
|
||
|
"1" "$(translate 'Mount 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_to_lxc
|
||
|
;;
|
||
|
2)
|
||
|
msg_info2 "$(translate 'Feature coming soon...')"
|
||
|
read -p "$(translate 'Press Enter to continue...')"
|
||
|
;;
|
||
|
3)
|
||
|
msg_info2 "$(translate 'Feature coming soon...')"
|
||
|
read -p "$(translate 'Press Enter to continue...')"
|
||
|
;;
|
||
|
4|"")
|
||
|
exit 0
|
||
|
;;
|
||
|
esac
|
||
|
done
|
||
|
}
|
||
|
|
||
|
# ==========================================================
|
||
|
# MAIN EXECUTION
|
||
|
# ==========================================================
|
||
|
|
||
|
if ! command -v pct >/dev/null 2>&1; then
|
||
|
echo "Error: This script must be run on a Proxmox host with LXC support."
|
||
|
exit 1
|
||
|
fi
|
||
|
|
||
|
main_menu
|