diff --git a/scripts/menus/share_menu.sh b/scripts/menus/share_menu.sh new file mode 100644 index 0000000..2a9267a --- /dev/null +++ b/scripts/menus/share_menu.sh @@ -0,0 +1,70 @@ +#!/bin/bash +# ========================================================== +# ProxMenux - Network Storage Manager Menu +# ========================================================== +# Author : MacRimi +# Copyright : (c) 2024 MacRimi +# License : MIT +# Version : 1.2 +# Last Updated: $(date +%d/%m/%Y) +# ========================================================== + +# Configuration +REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main" +BASE_DIR="/usr/local/share/proxmenux" +UTILS_FILE="$BASE_DIR/utils.sh" +VENV_PATH="/opt/googletrans-env" + +if [[ -f "$UTILS_FILE" ]]; then + source "$UTILS_FILE" +fi + +load_language +initialize_cache + +# ========================================================== + +while true; do + OPTION=$(dialog --clear --backtitle "ProxMenux" \ + --title "$(translate "Network Storage Manager")" \ + --menu "\n$(translate "Select an option:")" 25 80 15 \ + "1" "$(translate "Add NFS Server on LXC")" \ + "2" "$(translate "Add Samba Server on LXC")" \ + "3" "$(translate "Config NFS Client on LXC")" \ + "4" "$(translate "Config Samba Client on LXC")" \ + "5" "$(translate "Config NFS Host Storage (Proxmox)")" \ + "6" "$(translate "Config Samba Host Storage (Proxmox)")" \ + "7" "$(translate "Help & Info commands")" \ + "8" "$(translate "Return to Main Menu")" \ + 2>&1 >/dev/tty) + + case $OPTION in + 1) + bash <(curl -s "$REPO_URL/scripts/share/nfs.sh") + ;; + 2) + bash <(curl -s "$REPO_URL/scripts/share/samba.sh") + ;; + 3) + bash <(curl -s "$REPO_URL/scripts/share/nfs_client.sh") + ;; + 4) + bash <(curl -s "$REPO_URL/scripts/storage/samba_client.sh") + ;; + 5) + bash <(curl -s "$REPO_URL/scripts/storage/nfs_host.sh") + ;; + 6) + bash <(curl -s "$REPO_URL/scripts/storage/samba_host.sh") + ;; + 7) + bash <(curl -s "$REPO_URL/scripts/storage/commands_share.sh") + ;; + 8) + exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh") + ;; + *) + exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh") + ;; + esac +done diff --git a/scripts/share/commands_share.sh b/scripts/share/commands_share.sh new file mode 100644 index 0000000..dfa9ec9 --- /dev/null +++ b/scripts/share/commands_share.sh @@ -0,0 +1,377 @@ +#!/bin/bash + +# ========================================================== +# ProxMenu - A menu-driven script for Proxmox VE management +# ========================================================== +# Author : MacRimi +# Copyright : (c) 2024 MacRimi +# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE) +# Version : 1.5 +# Last Updated: 04/08/2025 +# ========================================================== + +# Configuration ============================================ +REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main" +BASE_DIR="/usr/local/share/proxmenux" +UTILS_FILE="$BASE_DIR/utils.sh" +VENV_PATH="/opt/googletrans-env" + +if [[ -f "$UTILS_FILE" ]]; then + source "$UTILS_FILE" +fi +load_language +initialize_cache +# ========================================================== + +show_command() { + local step="$1" + local description="$2" + local command="$3" + local note="$4" + local command_extra="$5" + + echo -e "${BGN}${step}.${CL} ${BL}${description}${CL}" + echo "" + echo -e "${TAB}${command}" + echo -e + [[ -n "$note" ]] && echo -e "${TAB}${DARK_GRAY}${note}${CL}" + [[ -n "$command_extra" ]] && echo -e "${TAB}${YW}${command_extra}${CL}" + echo "" +} + +show_how_to_enter_lxc() { + clear + show_proxmenux_logo + msg_title "$(translate "How to Access an LXC Terminal from Proxmox Host")" + + msg_info2 "$(translate "Use these commands on your Proxmox host to access an LXC container's terminal:")" + echo -e + + show_command "1" \ + "$(translate "Get a list of all your containers:")" \ + "pct list" \ + "" \ + "" + + show_command "2" \ + "$(translate "Enter the container's terminal")" \ + "pct enter ${CUS}${CL}" \ + "$(translate "Replace with the actual ID.")"\ + "$(translate "For example: pct enter 101")" + + show_command "3" \ + "$(translate "To exit the container's terminal, press:")" \ + "CTRL + D" \ + "" \ + "" + + echo -e "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + +show_nfs_server_help() { + clear + show_proxmenux_logo + msg_title "$(translate "NFS Server Installation Guide")" + + msg_info2 "$(translate "Manual commands to install NFS server in an LXC. Remember to substitute the highlighted values.")" + echo -e + + show_command "1" \ + "$(translate "Update package list:")" \ + "apt-get update" \ + "" \ + "" + + show_command "2" \ + "$(translate "Install NFS server packages:")" \ + "apt-get install -y nfs-kernel-server nfs-common rpcbind" \ + "" \ + "" + + show_command "3" \ + "$(translate "Create the export directory:")" \ + "mkdir -p ${CUS}/mnt/nfs_export${CL}" \ + "$(translate "You can change /mnt/nfs_export to your preferred path.")" \ + "" + + show_command "4" \ + "$(translate "Set permissions for the directory:")" \ + "chmod 755 ${CUS}/mnt/nfs_export${CL}" \ + "" \ + "" + + show_command "5" \ + "$(translate "Configure exports:")" \ + "echo '${CUS}/mnt/nfs_export${CL} ${CUS}192.168.1.0/24${CL}(rw,sync,no_subtree_check,no_root_squash)' >> /etc/exports" \ + "$(translate "Replace the directory path and the network IP with your own values.")" \ + "" + + show_command "6" \ + "$(translate "Apply export configuration:")" \ + "exportfs -ra" \ + "" \ + "" + + show_command "7" \ + "$(translate "Restart NFS service:")" \ + "systemctl restart nfs-kernel-server" \ + "" \ + "" + + show_command "8" \ + "$(translate "Enable and start services:")" \ + "systemctl enable rpcbind nfs-kernel-server" \ + "$(translate "Ensure services start on boot.")" \ + "" + + show_command "9" \ + "$(translate "Verify installation:")" \ + "showmount -e localhost" \ + "" \ + "" + + show_command "10" \ + "$(translate "Verify service status:")" \ + "systemctl status nfs-kernel-server" \ + "" \ + "" + + echo -e "${BOR}" + echo -e "${BOLD}$(translate "Additional Information:")${CL}" + echo -e "${TAB}${BGN}$(translate "Config file:")${CL} ${BL}/etc/exports${CL}" + echo -e "${TAB}${BGN}$(translate "Service name:")${CL} ${BL}nfs-kernel-server${CL}" + + echo -e "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + +show_samba_server_help() { + clear + show_proxmenux_logo + msg_title "$(translate "Samba Server Installation Guide")" + + msg_info2 "$(translate "Manual commands to install Samba server in an LXC. Remember to substitute the highlighted values.")" + echo -e + + show_command "1" \ + "$(translate "Update package list:")" \ + "apt-get update" \ + "" \ + "" + + show_command "2" \ + "$(translate "Install Samba packages:")" \ + "apt-get install -y samba samba-common-bin acl" \ + "" \ + "" + + show_command "3" \ + "$(translate "Create the share directory:")" \ + "mkdir -p ${CUS}/mnt/samba_share${CL}" \ + "$(translate "You can change /mnt/samba_share to your preferred path.")" \ + "" + + show_command "4" \ + "$(translate "Set permissions for the directory:")" \ + "chmod 755 ${CUS}/mnt/samba_share${CL}" \ + "" \ + "" + + show_command "5" \ + "$(translate "Create a Samba user:")" \ + "adduser --disabled-password --gecos '' ${CUS}sambauser${CL}" \ + "$(translate "You can change 'sambauser' to your preferred username.")" \ + "" + + show_command "6" \ + "$(translate "Set the Samba password for the user:")" \ + "smbpasswd -a ${CUS}sambauser${CL}" \ + "$(translate "You will be prompted to enter and confirm a new password for the account.")" \ + "" + + show_command "7" \ + "$(translate "Configure the Samba share:")" \ + "cat >> /etc/samba/smb.conf << EOF +[shared] + comment = Shared folder + path = ${CUS}/mnt/samba_share${CL} + read only = no + browseable = yes + valid users = ${CUS}sambauser${CL} + create mask = 0664 + directory mask = 0775 +EOF" \ + "$(translate "Replace 'path' and 'valid users' with your chosen values.")" \ + "" + + show_command "8" \ + "$(translate "Restart the Samba service:")" \ + "systemctl restart smbd" \ + "" \ + "" + + show_command "9" \ + "$(translate "Enable the Samba service:")" \ + "systemctl enable smbd" \ + "" \ + "" + + show_command "10" \ + "$(translate "Verify installation:")" \ + "smbclient -L localhost -U ${CUS}sambauser${CL}" \ + "$(translate "Test share listing and service status. You will be prompted for a password.")" \ + "" + + show_command "11" \ + "$(translate "Verify service status:")" \ + "systemctl status smbd" \ + "" \ + "" + + echo -e "${BOR}" + echo -e "${BOLD}$(translate "Connection Examples:")${CL}" + echo -e "${TAB}${BGN}$(translate "Windows:")${CL} ${YW}\\\\\\shared${CL}" + echo -e "${TAB}${BGN}$(translate "Linux:")${CL} ${YW}smbclient //server-ip/shared -U sambauser${CL}" + echo -e "${TAB}${BGN}$(translate "Mount:")${CL} ${YW}mount -t cifs //server-ip/shared /mnt/point -o username=sambauser${CL}" + + echo -e "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + +show_nfs_client_help() { + clear + show_proxmenux_logo + msg_title "$(translate "NFS Client Configuration Guide")" + + msg_info2 "$(translate "Manual commands to configure an NFS client in an LXC. Remember to substitute the highlighted values.")" + echo -e + + show_command "1" \ + "$(translate "Update package list:")" \ + "apt-get update" \ + "" \ + "" + + show_command "2" \ + "$(translate "Install NFS client packages:")" \ + "apt-get install -y nfs-common" \ + "" \ + "" + + show_command "3" \ + "$(translate "Create local mount point:")" \ + "mkdir -p ${CUS}/mnt/nfsmount${CL}" \ + "$(translate "Create the directory where the remote share will be mounted.")" \ + "" + + show_command "4" \ + "$(translate "Mount the NFS share manually:")" \ + "mount ${CUS}192.168.1.100${CL}:${CUS}/mnt/nfs_export${CL} ${CUS}/mnt/nfsmount${CL}" \ + "$(translate "Replace the IP, remote share path, and local mount point.")" \ + "" + + show_command "5" \ + "$(translate "Verify the mount:")" \ + "df -h | grep ${CUS}/mnt/nfsmount${CL}" \ + "" \ + "" + + show_command "6" \ + "$(translate "Add to fstab for automatic mounting on boot:")" \ + "echo '${CUS}192.168.1.100${CL}:${CUS}/mnt/nfs_export${CL} ${CUS}/mnt/nfsmount${CL} nfs defaults 0 0' >> /etc/fstab" \ + "$(translate "Makes the mount persistent. Replace the IP and paths with your own values.")" \ + "" + + echo -e "${BOR}" + echo -e "${BOLD}$(translate "Additional Information:")${CL}" + echo -e "${TAB}${BGN}$(translate "Config file:")${CL} ${BL}/etc/fstab${CL}" + echo -e "${TAB}${BGN}$(translate "Service name:")${CL} ${BL}nfs-common${CL}" + + echo -e "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + +show_samba_client_help() { + clear + show_proxmenux_logo + msg_title "$(translate "Samba Client Configuration Guide")" + + msg_info2 "$(translate "Manual commands to configure a Samba client in an LXC. Remember to substitute the highlighted values.")" + echo -e + + show_command "1" \ + "$(translate "Update package list:")" \ + "apt-get update" \ + "" \ + "" + + show_command "2" \ + "$(translate "Install Samba client packages:")" \ + "apt-get install -y cifs-utils" \ + "" \ + "" + + show_command "3" \ + "$(translate "Create local mount point:")" \ + "mkdir -p ${CUS}/mnt/sambamount${CL}" \ + "$(translate "Create the directory where the remote share will be mounted.")" \ + "" + + show_command "4" \ + "$(translate "Mount the Samba share manually:")" \ + "mount -t cifs //${CUS}192.168.1.100${CL}/${CUS}shared${CL} ${CUS}/mnt/sambamount${CL} -o username=${CUS}sambauser${CL},domain=WORKGROUP" \ + "$(translate "Replace the IP, share name, and username. You will be prompted for a password.")" \ + "" + + show_command "5" \ + "$(translate "Verify the mount:")" \ + "df -h | grep ${CUS}/mnt/sambamount${CL}" \ + "" \ + "" + + show_command "6" \ + "$(translate "Add to fstab for automatic mounting on boot:")" \ + "echo '//${CUS}192.168.1.100${CL}/${CUS}shared${CL} ${CUS}/mnt/sambamount${CL} cifs defaults,username=${CUS}sambauser${CL},uid=1000,gid=1000 0 0' >> /etc/fstab" \ + "$(translate "This makes the mount persistent. Replace the IP, share name, and username.")" \ + "" + + echo -e "${BOR}" + echo -e "${BOLD}$(translate "Additional Information:")${CL}" + echo -e "${TAB}${BGN}$(translate "Config file:")${CL} ${BL}/etc/fstab${CL}" + echo -e "${TAB}${BGN}$(translate "Service name:")${CL} ${BL}cifs-utils${CL}" + + echo -e "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + + +show_help_menu() { + while true; do + CHOICE=$(dialog --title "$(translate "Help & Information")" \ + --menu "$(translate "Select help topic:")" 22 70 12 \ + "0" "$(translate "How to Access an LXC Terminal")" \ + "1" "$(translate "NFS Server Installation")" \ + "2" "$(translate "Samba Server Installation")" \ + "3" "$(translate "NFS Client Configuration")" \ + "4" "$(translate "Samba Client Configuration")" \ + "5" "$(translate "Return to Main Menu")" \ + 3>&1 1>&2 2>&3) + + case $CHOICE in + 0) show_how_to_enter_lxc ;; + 1) show_nfs_server_help ;; + 2) show_samba_server_help ;; + 3) show_nfs_client_help ;; + 4) show_samba_client_help ;; + 5) return ;; + *) return ;; + esac + done +} +show_help_menu \ No newline at end of file diff --git a/scripts/share/nfs.sh b/scripts/share/nfs.sh new file mode 100644 index 0000000..6c27495 --- /dev/null +++ b/scripts/share/nfs.sh @@ -0,0 +1,506 @@ +#!/bin/bash +# ========================================================== +# ProxMenu CT - NFS Manager for Proxmox LXC +# ========================================================== +# Based on ProxMenux by MacRimi +# ========================================================== +# Description: +# This script allows you to manage NFS shares inside Proxmox CTs: +# - Create NFS exports +# - View configured exports +# - Delete existing exports +# - Check NFS service status +# ========================================================== + +# Configuration +REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main" +BASE_DIR="/usr/local/share/proxmenux" +UTILS_FILE="$BASE_DIR/utils.sh" +VENV_PATH="/opt/googletrans-env" + +if [[ -f "$UTILS_FILE" ]]; then + source "$UTILS_FILE" +fi + +load_language +initialize_cache + +# === Select CT === +CT_LIST=$(pct list | awk 'NR>1 {print $1, $3}') +if [ -z "$CT_LIST" ]; then + dialog --title "$(translate "Error")" --msgbox "$(translate "No CTs available in the system.")" 8 50 + exit 1 +fi + +CTID=$(dialog --title "$(translate "Select CT")" --menu "$(translate "Select the CT to manage NFS:")" 20 70 12 $CT_LIST 3>&1 1>&2 2>&3) +if [ -z "$CTID" ]; then + dialog --title "$(translate "Error")" --msgbox "$(translate "No CT was selected.")" 8 50 + exit 1 +fi + + +# === Start CT if not running === +CT_STATUS=$(pct status "$CTID" | awk '{print $2}') +if [ "$CT_STATUS" != "running" ]; then + show_proxmenux_logo + msg_info "$(translate "Starting CT") $CTID..." + pct start "$CTID" + sleep 2 + if [ "$(pct status "$CTID" | awk '{print $2}')" != "running" ]; then + msg_error "$(translate "Failed to start the CT.")" + exit 1 + fi + msg_ok "$(translate "CT started successfully.")" +fi + +select_mount_point() { + while true; do + METHOD=$(dialog --title "$(translate "Select Folder")" \ + --menu "$(translate "How do you want to select the folder to export?")" 15 60 5 \ + "auto" "$(translate "Select from folders inside /mnt")" \ + "manual" "$(translate "Enter path manually")" \ + 3>&1 1>&2 2>&3) + + if [[ $? -ne 0 ]]; then + return 1 + fi + + case "$METHOD" in + auto) + DIRS=$(pct exec "$CTID" -- find /mnt -maxdepth 1 -mindepth 1 -type d 2>/dev/null) + if [[ -z "$DIRS" ]]; then + dialog --title "$(translate "No Folders")" --msgbox "$(translate "No folders found inside /mnt in the CT.")" 8 60 + continue + fi + + OPTIONS=() + while IFS= read -r dir; do + name=$(basename "$dir") + OPTIONS+=("$dir" "$name") + done <<< "$DIRS" + + MOUNT_POINT=$(dialog --title "$(translate "Select Folder")" \ + --menu "$(translate "Choose a folder to export:")" 20 60 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) + if [[ $? -ne 0 ]]; then + return 1 + fi + [[ -n "$MOUNT_POINT" ]] && return 0 + ;; + manual) + CT_NAME=$(pct config "$CTID" | awk -F: '/hostname/ {print $2}' | xargs) + DEFAULT_MOUNT_POINT="/mnt/${CT_NAME}_nfs" + MOUNT_POINT=$(whiptail --title "$(translate "Mount Point")" \ + --inputbox "$(translate "Enter the mount point for the NFS export (e.g., /mnt/mynfs):")" \ + 10 70 "$DEFAULT_MOUNT_POINT" 3>&1 1>&2 2>&3) + if [[ $? -ne 0 ]]; then + return 1 + fi + if [[ -z "$MOUNT_POINT" ]]; then + whiptail --title "$(translate "Error")" --msgbox "$(translate "No mount point was specified.")" 8 50 + continue + else + return 0 + fi + ;; + esac + done +} + +get_network_config() { + clear + NETWORK=$(whiptail --title "$(translate "Network Configuration")" --menu "$(translate "Select network access level:")" 15 70 4 \ + "local" "$(translate "Local network only (192.168.0.0/16)")" \ + "subnet" "$(translate "Specific subnet (enter manually)")" \ + "host" "$(translate "Specific host (enter IP)")" \ + "all" "$(translate "All networks (*) - NOT RECOMMENDED")" 3>&1 1>&2 2>&3) + + case "$NETWORK" in + local) + NETWORK_RANGE="192.168.0.0/16" + ;; + subnet) + NETWORK_RANGE=$(whiptail --inputbox "$(translate "Enter subnet (e.g., 192.168.1.0/24):")" 10 60 "192.168.1.0/24" --title "$(translate "Subnet")" 3>&1 1>&2 2>&3) + [[ -z "$NETWORK_RANGE" ]] && return 1 + ;; + host) + NETWORK_RANGE=$(whiptail --inputbox "$(translate "Enter host IP (e.g., 192.168.1.100):")" 10 60 --title "$(translate "Host IP")" 3>&1 1>&2 2>&3) + [[ -z "$NETWORK_RANGE" ]] && return 1 + ;; + all) + if whiptail --yesno "$(translate "WARNING: This will allow access from ANY network.\nThis is a security risk. Are you sure?")" 10 60 --title "$(translate "Security Warning")"; then + NETWORK_RANGE="*" + else + return 1 + fi + ;; + *) + return 1 + ;; + esac + return 0 +} + + + +create_nfs_export() { + select_mount_point || return + get_network_config || return + + + if ! pct exec "$CTID" -- test -d "$MOUNT_POINT"; then + if whiptail --yesno "$(translate "The directory does not exist in the CT.")\n\n$MOUNT_POINT\n\n$(translate "Do you want to create it?")" 12 70 --title "$(translate "Create Directory")"; then + pct exec "$CTID" -- mkdir -p "$MOUNT_POINT" + msg_ok "$(translate "Directory created successfully.")" + else + msg_error "$(translate "Directory does not exist and was not created.")" + return + fi + fi + show_proxmenux_logo + msg_title "$(translate "Create NFS server service")" + if pct exec "$CTID" -- dpkg -s nfs-kernel-server &>/dev/null; then + NFS_INSTALLED=true + else + NFS_INSTALLED=false + fi + + + if [ "$NFS_INSTALLED" = false ]; then + echo -e "${TAB}$(translate "Installing NFS server packages inside the CT...")" + pct exec "$CTID" -- bash -c "apt-get update && apt-get install -y nfs-kernel-server nfs-common rpcbind" + + + pct exec "$CTID" -- systemctl enable rpcbind + pct exec "$CTID" -- systemctl enable nfs-kernel-server + pct exec "$CTID" -- systemctl start rpcbind + + msg_ok "$(translate "NFS server installed successfully.")" + else + msg_ok "$(translate "NFS server is already installed.")" + fi + + + IS_MOUNTED=$(pct exec "$CTID" -- mount | grep "$MOUNT_POINT" || true) + if [[ -n "$IS_MOUNTED" ]]; then + msg_info "$(translate "Detected a mounted directory from host. Setting up shared group...")" + + SHARE_GID=999 + GROUP_EXISTS=$(pct exec "$CTID" -- getent group nfsshare || true) + GID_IN_USE=$(pct exec "$CTID" -- getent group "$SHARE_GID" | cut -d: -f1 || true) + + if [[ -z "$GROUP_EXISTS" ]]; then + if [[ -z "$GID_IN_USE" ]]; then + pct exec "$CTID" -- groupadd -g "$SHARE_GID" nfsshare + msg_ok "$(translate "Group 'nfsshare' created with GID $SHARE_GID")" + else + pct exec "$CTID" -- groupadd nfsshare + msg_warn "$(translate "GID $SHARE_GID already in use. Group 'nfsshare' created with dynamic GID.")" + fi + else + msg_ok "$(translate "Group 'nfsshare' already exists inside the CT")" + fi + + pct exec "$CTID" -- chown root:nfsshare "$MOUNT_POINT" + pct exec "$CTID" -- chmod 2775 "$MOUNT_POINT" + else + msg_ok "$(translate "No shared mount detected. Applying standard local access.")" + pct exec "$CTID" -- chmod 755 "$MOUNT_POINT" + fi + + + EXPORT_OPTIONS=$(whiptail --title "$(translate "Export Options")" --menu "$(translate "Select export permissions:")" 15 70 3 \ + "rw" "$(translate "Read-Write access")" \ + "ro" "$(translate "Read-Only access")" \ + "custom" "$(translate "Custom options")" 3>&1 1>&2 2>&3) + + + case "$EXPORT_OPTIONS" in + rw) + OPTIONS="rw,sync,no_subtree_check,no_root_squash" + ;; + ro) + OPTIONS="ro,sync,no_subtree_check,root_squash" + ;; + custom) + OPTIONS=$(whiptail --inputbox "$(translate "Enter custom NFS options:")" 10 70 "rw,sync,no_subtree_check,no_root_squash" --title "$(translate "Custom Options")" 3>&1 1>&2 2>&3) + [[ -z "$OPTIONS" ]] && OPTIONS="rw,sync,no_subtree_check,no_root_squash" + ;; + *) + OPTIONS="rw,sync,no_subtree_check,no_root_squash" + ;; + esac + + + EXPORT_LINE="$MOUNT_POINT $NETWORK_RANGE($OPTIONS)" + + + if pct exec "$CTID" -- grep -q "^$MOUNT_POINT " /etc/exports; then + msg_warn "$(translate "Export already exists for:") $MOUNT_POINT" + if whiptail --yesno "$(translate "Do you want to update the existing export?")" 10 60 --title "$(translate "Update Export")"; then + + pct exec "$CTID" -- sed -i "\|^$MOUNT_POINT |d" /etc/exports + pct exec "$CTID" -- bash -c "echo '$EXPORT_LINE' >> /etc/exports" + msg_ok "$(translate "Export updated successfully.")" + else + return + fi + else + msg_ok "$(translate "Adding new export to /etc/exports...")" + pct exec "$CTID" -- bash -c "echo '$EXPORT_LINE' >> /etc/exports" + msg_ok "$(translate "Export added successfully.")" + fi + + pct exec "$CTID" -- systemctl restart nfs-kernel-server + pct exec "$CTID" -- exportfs -ra + + CT_IP=$(pct exec "$CTID" -- hostname -I | awk '{print $1}') + echo -e "" + msg_ok "$(translate "NFS export created successfully!")" + echo -e "" + echo -e "${TAB}${BOLD}$(translate "Connection details:")${CL}" + echo -e "${TAB}${BGN}$(translate "Server IP:")${CL} ${CUS}$CT_IP${CL}" + echo -e "${TAB}${BGN}$(translate "Export path:")${CL} ${CUS}$MOUNT_POINT${CL}" + echo -e + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + + + +view_exports() { + show_proxmenux_logo + msg_title "$(translate "View Current Exports")" + + echo -e "$(translate "Current NFS exports in CT") $CTID:" + echo "==================================" + + if pct exec "$CTID" -- test -f /etc/exports; then + EXPORTS=$(pct exec "$CTID" -- cat /etc/exports | grep -v '^#' | grep -v '^$') + if [[ -n "$EXPORTS" ]]; then + echo "$EXPORTS" + echo "" + echo "$(translate "Active exports:")" + pct exec "$CTID" -- showmount -e localhost 2>/dev/null || echo "$(translate "No active exports or showmount not available")" + + + CT_IP=$(pct exec "$CTID" -- hostname -I | awk '{print $1}') + + echo "" + echo "==================================" + echo -e "${TAB}${BOLD}$(translate "Connection Details:")${CL}" + echo -e "${TAB}${BGN}$(translate "Server IP:")${CL} ${CUS}$CT_IP${CL}" + while IFS= read -r export_line; do + if [[ -n "$export_line" ]]; then + EXPORT_PATH=$(echo "$export_line" | awk '{print $1}') + echo -e "${TAB}${BGN}$(translate "Export path:")${CL} ${CUS}$EXPORT_PATH${CL}" + echo "" + fi + done <<< "$EXPORTS" + + else + echo "$(translate "No exports configured.")" + fi + else + echo "$(translate "/etc/exports file does not exist.")" + fi + + echo "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + + +delete_export() { + + # if ! pct exec "$CTID" -- test -f /etc/exports; then + # whiptail --title "$(translate "Error")" --msgbox "$(translate "No exports file found.")" 8 50 + # return + # fi + + # EXPORTS=$(pct exec "$CTID" -- awk '!/^#|^$/ {print NR, $0}' /etc/exports) + # if [[ -z "$EXPORTS" ]]; then + # whiptail --title "$(translate "No Exports")" --msgbox "$(translate "No exports found in /etc/exports.")" 8 60 + # return + # fi + + + + if ! pct exec "$CTID" -- test -f /etc/exports; then + dialog --title "$(translate "Error")" --msgbox "\n$(translate "No exports file found.")" 8 50 + return + fi + + EXPORTS=$(pct exec "$CTID" -- awk '!/^#|^$/ {print NR, $0}' /etc/exports) + if [[ -z "$EXPORTS" ]]; then + dialog --title "$(translate "No Exports")" --msgbox "$(translate "No exports found in /etc/exports.")" 8 60 + return + fi + + + + +OPTIONS=() +while read -r line; do + [[ -z "$line" ]] && continue + NUM=$(echo "$line" | awk '{print $1}') + EXPORT_LINE=$(echo "$line" | cut -d' ' -f2-) + EXPORT_PATH=$(echo "$EXPORT_LINE" | awk '{print $1}') + EXPORT_CLIENT=$(echo "$EXPORT_LINE" | awk '{print $2}' | cut -d'(' -f1) + [[ -z "$EXPORT_PATH" || -z "$EXPORT_CLIENT" ]] && continue + OPTIONS+=("$NUM" "$EXPORT_PATH $EXPORT_CLIENT") +done <<< "$EXPORTS" + + +# SELECTED_NUM=$(whiptail --title "$(translate "Delete Export")" --menu "$(translate "Select an export to delete:")" 20 70 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) +# [ -z "$SELECTED_NUM" ] && return + + +SELECTED_NUM=$(dialog --title "$(translate "Delete Export")" --menu "$(translate "Select an export to delete:")" 20 70 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) +[ -z "$SELECTED_NUM" ] && return + + + EXPORT_LINE=$(echo "$EXPORTS" | awk -v num="$SELECTED_NUM" '$1 == num {$1=""; print substr($0,2)}') + + if whiptail --yesno "$(translate "Are you sure you want to delete this export?")\n\n$EXPORT_LINE" 10 70 --title "$(translate "Confirm Deletion")"; then + show_proxmenux_logo + msg_title "$(translate "Delete Export")" + pct exec "$CTID" -- sed -i "${SELECTED_NUM}d" /etc/exports + pct exec "$CTID" -- exportfs -ra + pct exec "$CTID" -- systemctl restart nfs-kernel-server + msg_ok "$(translate "Export deleted and NFS service restarted.")" + fi + + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + + + +check_nfs_status() { + show_proxmenux_logo + msg_title "$(translate "Check NFS Status")" + echo -e "$(translate "NFS Service Status in CT") $CTID:" + echo "==================================" + + + if pct exec "$CTID" -- dpkg -s nfs-kernel-server &>/dev/null; then + echo "$(translate "NFS Server: INSTALLED")" + + + if pct exec "$CTID" -- systemctl is-active --quiet nfs-kernel-server; then + echo "$(translate "NFS Service: RUNNING")" + else + echo "$(translate "NFS Service: STOPPED")" + fi + + + if pct exec "$CTID" -- systemctl is-active --quiet rpcbind; then + echo "$(translate "RPC Bind Service: RUNNING")" + else + echo "$(translate "RPC Bind Service: STOPPED")" + fi + + + echo "" + echo "$(translate "Listening ports:")" + pct exec "$CTID" -- ss -tlnp | grep -E ':(111|2049|20048)' || echo "$(translate "No NFS ports found")" + + else + echo "$(translate "NFS Server: NOT INSTALLED")" + fi + + echo "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + + + +uninstall_nfs() { + + if ! pct exec "$CTID" -- dpkg -s nfs-kernel-server &>/dev/null; then + dialog --title "$(translate "NFS Not Installed")" --msgbox "\n$(translate "NFS server is not installed in this CT.")" 8 60 + return + fi + + + if ! whiptail --title "$(translate "Uninstall NFS Server")" \ + --yesno "$(translate "WARNING: This will completely remove NFS server from the CT.")\n\n$(translate "This action will:")\n$(translate "• Stop all NFS services")\n$(translate "• Remove all exports")\n$(translate "• Uninstall NFS packages")\n$(translate "• Remove NFS groups")\n\n$(translate "Are you sure you want to continue?")" \ + 16 70; then + return + fi + + + show_proxmenux_logo + msg_title "$(translate "Uninstall NFS Server")" + + msg_info "$(translate "Stopping NFS services...")" + pct exec "$CTID" -- systemctl stop nfs-kernel-server 2>/dev/null || true + pct exec "$CTID" -- systemctl stop rpcbind 2>/dev/null || true + pct exec "$CTID" -- systemctl disable nfs-kernel-server 2>/dev/null || true + pct exec "$CTID" -- systemctl disable rpcbind 2>/dev/null || true + msg_ok "$(translate "NFS services stopped and disabled.")" + + + if pct exec "$CTID" -- test -f /etc/exports; then + pct exec "$CTID" -- truncate -s 0 /etc/exports + msg_ok "$(translate "Exports cleared.")" + fi + + pct exec "$CTID" -- apt-get remove --purge -y nfs-kernel-server nfs-common 2>/dev/null || true + pct exec "$CTID" -- apt-get autoremove -y 2>/dev/null || true + msg_ok "$(translate "NFS packages removed.")" + + + if pct exec "$CTID" -- getent group nfsshare >/dev/null 2>&1; then + + GROUP_USERS=$(pct exec "$CTID" -- getent group nfsshare | cut -d: -f4) + if [[ -z "$GROUP_USERS" ]]; then + pct exec "$CTID" -- groupdel nfsshare 2>/dev/null || true + msg_ok "$(translate "NFS group removed.")" + else + msg_warn "$(translate "NFS group kept (has users assigned).")" + fi + fi + + + msg_info "$(translate "Cleaning up remaining processes...")" + pct exec "$CTID" -- pkill -f nfs 2>/dev/null || true + pct exec "$CTID" -- pkill -f rpc 2>/dev/null || true + sleep 2 + msg_ok "$(translate "NFS server has been completely uninstalled!")" + echo -e "" + echo -e "${TAB}${BOLD}$(translate "Uninstallation Summary:")${CL}" + echo -e "${TAB}${BGN}$(translate "Services:")${CL} ${BL}$(translate "Stopped and disabled")${CL}" + echo -e "${TAB}${BGN}$(translate "Packages:")${CL} ${BL}$(translate "Removed")${CL}" + echo -e "${TAB}${BGN}$(translate "Exports:")${CL} ${BL}$(translate "Cleared (backup created)")${CL}" + echo -e "${TAB}${BGN}$(translate "Groups:")${CL} ${BL}$(translate "Cleaned up")${CL}" + echo -e + + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + + + + +# === Main Menu === +while true; do + CHOICE=$(dialog --title "$(translate "NFS Manager - CT") $CTID" --menu "$(translate "Choose an option:")" 20 70 12 \ + "1" "$(translate "Create NFS server service")" \ + "2" "$(translate "View Current Exports")" \ + "3" "$(translate "Delete Export")" \ + "4" "$(translate "Check NFS Status")" \ + "5" "$(translate "Uninstall NFS Server")" \ + "6" "$(translate "Exit")" 3>&1 1>&2 2>&3) + + case $CHOICE in + 1) create_nfs_export ;; + 2) view_exports ;; + 3) delete_export ;; + 4) check_nfs_status ;; + 5) uninstall_nfs ;; + 6) exit 0 ;; + *) exit 0 ;; + esac +done \ No newline at end of file diff --git a/scripts/share/nfs_client.sh b/scripts/share/nfs_client.sh new file mode 100644 index 0000000..e7ff36c --- /dev/null +++ b/scripts/share/nfs_client.sh @@ -0,0 +1,722 @@ +#!/bin/bash +# ========================================================== +# ProxMenu CT - NFS Client Manager for Proxmox LXC +# ========================================================== +# Based on ProxMenux by MacRimi +# ========================================================== +# Description: +# This script allows you to manage NFS client mounts inside Proxmox CTs: +# - Mount NFS shares (temporary and permanent) +# - View current mounts +# - Unmount and remove NFS shares +# - Auto-discover NFS servers +# ========================================================== + +# Configuration +REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main" +BASE_DIR="/usr/local/share/proxmenux" +UTILS_FILE="$BASE_DIR/utils.sh" +VENV_PATH="/opt/googletrans-env" + +if [[ -f "$UTILS_FILE" ]]; then + source "$UTILS_FILE" +fi + +load_language +initialize_cache + +# === Select CT === +CT_LIST=$(pct list | awk 'NR>1 {print $1, $3}') +if [ -z "$CT_LIST" ]; then + dialog --backtitle "ProxMenux" --title "$(translate "Error")" --msgbox "$(translate "No CTs available in the system.")" 8 50 + exit 1 +fi + +CTID=$(dialog --backtitle "ProxMenux" --title "$(translate "Select CT")" --menu "$(translate "Select the CT to manage NFS client:")" 20 70 12 $CT_LIST 3>&1 1>&2 2>&3) +if [ -z "$CTID" ]; then + dialog --backtitle "ProxMenux" --title "$(translate "Error")" --msgbox "$(translate "No CT was selected.")" 8 50 + exit 1 +fi + + +# === Start CT if not running === +CT_STATUS=$(pct status "$CTID" | awk '{print $2}') +if [ "$CT_STATUS" != "running" ]; then + msg_info "$(translate "Starting CT") $CTID..." + pct start "$CTID" + sleep 2 + if [ "$(pct status "$CTID" | awk '{print $2}')" != "running" ]; then + msg_error "$(translate "Failed to start the CT.")" + exit 1 + fi + msg_ok "$(translate "CT started successfully.")" +fi + + + +install_nfs_client() { + + if pct exec "$CTID" -- dpkg -s nfs-common &>/dev/null; then + return 0 + fi + + show_proxmenux_logo + msg_title "$(translate "Installing NFS Client")" + + msg_info "$(translate "Installing NFS client packages...")" + if ! pct exec "$CTID" -- apt-get update >/dev/null 2>&1; then + msg_error "$(translate "Failed to update package list.")" + msg_success "$(translate "Press Enter to return to menu...")" + read -r + return 1 + fi + + if ! pct exec "$CTID" -- apt-get install -y nfs-common >/dev/null 2>&1; then + msg_error "$(translate "Failed to install NFS client packages.")" + msg_success "$(translate "Press Enter to return to menu...")" + read -r + return 1 + fi + + if ! pct exec "$CTID" -- which showmount >/dev/null 2>&1; then + msg_error "$(translate "showmount command not found after installation.")" + msg_success "$(translate "Press Enter to return to menu...")" + read -r + return 1 + fi + if ! pct exec "$CTID" -- which mount.nfs >/dev/null 2>&1; then + msg_error "$(translate "mount.nfs command not found after installation.")" + msg_success "$(translate "Press Enter to return to menu...")" + read -r + return 1 + fi + + msg_ok "$(translate "NFS client installed successfully.")" + return 0 +} + + + +discover_nfs_servers() { + show_proxmenux_logo + msg_title "$(translate "NFS Host Manager - Proxmox Host")" + msg_info "$(translate "Scanning network for NFS servers...")" + + + + HOST_IP=$(hostname -I | awk '{print $1}') + NETWORK=$(echo "$HOST_IP" | cut -d. -f1-3).0/24 + + + if ! which nmap >/dev/null 2>&1; then + apt-get install -y nmap &>/dev/null + fi + + + SERVERS=$(nmap -p 2049 --open "$NETWORK" 2>/dev/null | grep -B 4 "2049/tcp open" | grep "Nmap scan report" | awk '{print $5}' | sort -u || true) + + if [[ -z "$SERVERS" ]]; then + cleanup + whiptail --title "$(translate "No Servers Found")" --msgbox "$(translate "No NFS servers found on the network.")\n\n$(translate "You can add servers manually.")" 10 60 + return 1 + fi + + OPTIONS=() + while IFS= read -r server; do + if [[ -n "$server" ]]; then + EXPORTS_COUNT=$(showmount -e "$server" 2>/dev/null | tail -n +2 | wc -l || echo "0") + SERVER_INFO="NFS Server ($EXPORTS_COUNT exports)" + OPTIONS+=("$server" "$SERVER_INFO") + fi + done <<< "$SERVERS" + + if [[ ${#OPTIONS[@]} -eq 0 ]]; then + cleanup + whiptail --title "$(translate "No Valid Servers")" --msgbox "$(translate "No accessible NFS servers found.")" 8 50 + return 1 + fi + msg_ok "$(translate "NFS servers detected")" + NFS_SERVER=$(whiptail --title "$(translate "Select NFS Server")" --menu "$(translate "Choose an NFS server:")" 20 80 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) + [[ -n "$NFS_SERVER" ]] && return 0 || return 1 +} + +select_nfs_server() { + METHOD=$(dialog --backtitle "ProxMenux" --title "$(translate "NFS Server Selection")" --menu "$(translate "How do you want to select the NFS server?")" 15 70 3 \ + "auto" "$(translate "Auto-discover servers on network")" \ + "manual" "$(translate "Enter server IP/hostname manually")" \ + "recent" "$(translate "Select from recent servers")" 3>&1 1>&2 2>&3) + + case "$METHOD" in + auto) + discover_nfs_servers || return 1 + ;; + manual) + clear + NFS_SERVER=$(whiptail --inputbox "$(translate "Enter NFS server IP or hostname:")" 10 60 --title "$(translate "NFS Server")" 3>&1 1>&2 2>&3) + [[ -z "$NFS_SERVER" ]] && return 1 + ;; + recent) + clear + RECENT=$(grep "nfs" /etc/fstab 2>/dev/null | awk '{print $1}' | cut -d: -f1 | sort -u || true) + if [[ -z "$RECENT" ]]; then + dialog --backtitle "ProxMenux" --title "$(translate "No Recent Servers")" --msgbox "\n$(translate "No recent NFS servers found.")" 8 50 + return 1 + fi + + OPTIONS=() + while IFS= read -r server; do + [[ -n "$server" ]] && OPTIONS+=("$server" "$(translate "Recent NFS server")") + done <<< "$RECENT" + + NFS_SERVER=$(whiptail --title "$(translate "Recent NFS Servers")" --menu "$(translate "Choose a recent server:")" 20 70 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) + [[ -n "$NFS_SERVER" ]] && return 0 || return 1 + ;; + *) + return 1 + ;; + esac + return 0 +} + +select_nfs_export() { + clear + if ! pct exec "$CTID" -- which showmount >/dev/null 2>&1; then + whiptail --title "$(translate "NFS Client Error")" \ + --msgbox "$(translate "showmount command is not working properly.")\n\n$(translate "Please check the installation.")" \ + 10 60 + return 1 + fi + + if ! pct exec "$CTID" -- ping -c 1 -W 3 "$NFS_SERVER" >/dev/null 2>&1; then + whiptail --title "$(translate "Connection Error")" \ + --msgbox "$(translate "Cannot reach server") $NFS_SERVER\n\n$(translate "Please check:")\n• $(translate "Server IP/hostname is correct")\n• $(translate "Network connectivity")\n• $(translate "Server is online")" \ + 12 70 + return 1 + fi + + + if ! pct exec "$CTID" -- nc -z -w 3 "$NFS_SERVER" 2049 2>/dev/null; then + whiptail --title "$(translate "NFS Port Error")" \ + --msgbox "$(translate "NFS port (2049) is not accessible on") $NFS_SERVER\n\n$(translate "Please check:")\n• $(translate "NFS server is running")\n• $(translate "Firewall settings")\n• $(translate "NFS service is enabled")" \ + 12 70 + return 1 + fi + + + EXPORTS_OUTPUT=$(pct exec "$CTID" -- showmount -e "$NFS_SERVER" 2>&1) + EXPORTS_RESULT=$? + + if [[ $EXPORTS_RESULT -ne 0 ]]; then + ERROR_MSG=$(echo "$EXPORTS_OUTPUT" | grep -i "error\|failed\|denied" | head -1) + + + if echo "$EXPORTS_OUTPUT" | grep -qi "connection refused\|network unreachable"; then + whiptail --title "$(translate "Network Error")" \ + --msgbox "$(translate "Network connection failed to") $NFS_SERVER\n\n$(translate "Error:"): $ERROR_MSG\n\n$(translate "Please check:")\n• $(translate "Server is running")\n• $(translate "Network connectivity")\n• $(translate "Firewall settings")" \ + 14 80 + else + whiptail --title "$(translate "NFS Error")" \ + --msgbox "$(translate "Failed to connect to") $NFS_SERVER\n\n$(translate "Error:"): $ERROR_MSG" \ + 12 80 + fi + return 1 + fi + + + EXPORTS=$(echo "$EXPORTS_OUTPUT" | tail -n +2 | awk '{print $1}' | grep -v "^$") + + if [[ -z "$EXPORTS" ]]; then + whiptail --title "$(translate "No Exports Found")" \ + --msgbox "$(translate "No exports found on server") $NFS_SERVER\n\n$(translate "Server response:")\n$(echo "$EXPORTS_OUTPUT" | head -10)\n\n$(translate "You can enter the export path manually.")" \ + 16 80 + + NFS_EXPORT=$(whiptail --inputbox "$(translate "Enter NFS export path (e.g., /mnt/shared):")" 10 60 --title "$(translate "Export Path")" 3>&1 1>&2 2>&3) + [[ -z "$NFS_EXPORT" ]] && return 1 + return 0 + fi + + # Build options for whiptail + OPTIONS=() + while IFS= read -r export_line; do + if [[ -n "$export_line" ]]; then + EXPORT_PATH=$(echo "$export_line" | awk '{print $1}') + # Get allowed clients if available + CLIENTS=$(echo "$EXPORTS_OUTPUT" | grep "^$EXPORT_PATH" | awk '{for(i=2;i<=NF;i++) printf "%s ", $i; print ""}' | sed 's/[[:space:]]*$//') + if [[ -n "$CLIENTS" ]]; then + OPTIONS+=("$EXPORT_PATH" "$CLIENTS") + else + OPTIONS+=("$EXPORT_PATH" "$(translate "NFS export")") + fi + fi + done <<< "$EXPORTS" + + if [[ ${#OPTIONS[@]} -eq 0 ]]; then + whiptail --title "$(translate "No Available Exports")" \ + --msgbox "$(translate "No accessible exports found.")\n\n$(translate "You can enter the export path manually.")" \ + 10 70 + + NFS_EXPORT=$(whiptail --inputbox "$(translate "Enter NFS export path (e.g., /mnt/shared):")" 10 60 --title "$(translate "Export Path")" 3>&1 1>&2 2>&3) + [[ -n "$NFS_EXPORT" ]] && return 0 || return 1 + fi + + NFS_EXPORT=$(whiptail --title "$(translate "Select NFS Export")" --menu "$(translate "Choose an export to mount:")" 20 70 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) + [[ -n "$NFS_EXPORT" ]] && return 0 || return 1 +} + +select_mount_point() { + while true; do + METHOD=$(whiptail --title "$(translate "Select Mount Point")" --menu "$(translate "Where do you want to mount the NFS export?")" 15 70 3 \ + "existing" "$(translate "Select from existing folders in /mnt")" \ + "new" "$(translate "Create new folder in /mnt")" \ + "custom" "$(translate "Enter custom path")" 3>&1 1>&2 2>&3) + + case "$METHOD" in + existing) + DIRS=$(pct exec "$CTID" -- find /mnt -maxdepth 1 -mindepth 1 -type d 2>/dev/null) + if [[ -z "$DIRS" ]]; then + whiptail --title "$(translate "No Folders")" --msgbox "$(translate "No folders found in /mnt. Please create a new folder.")" 8 60 + continue + fi + + OPTIONS=() + while IFS= read -r dir; do + if [[ -n "$dir" ]]; then + name=$(basename "$dir") + if pct exec "$CTID" -- [ "$(ls -A "$dir" 2>/dev/null | wc -l)" -eq 0 ]; then + status="$(translate "Empty")" + else + status="$(translate "Contains files")" + fi + OPTIONS+=("$dir" "$name ($status)") + fi + done <<< "$DIRS" + + MOUNT_POINT=$(whiptail --title "$(translate "Select Existing Folder")" --menu "$(translate "Choose a folder to mount the export:")" 20 80 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) + + if [[ -n "$MOUNT_POINT" ]]; then + if pct exec "$CTID" -- [ "$(ls -A "$MOUNT_POINT" 2>/dev/null | wc -l)" -gt 0 ]; then + FILE_COUNT=$(pct exec "$CTID" -- ls -A "$MOUNT_POINT" 2>/dev/null | wc -l) + if ! whiptail --yesno "$(translate "WARNING: The selected directory is not empty!")\n\n$(translate "Directory:"): $MOUNT_POINT\n$(translate "Contains:"): $FILE_COUNT $(translate "files/folders")\n\n$(translate "Mounting here will hide existing files until unmounted.")\n\n$(translate "Do you want to continue?")" 14 70 --title "$(translate "Directory Not Empty")"; then + continue + fi + fi + return 0 + fi + ;; + new) + # Create default name from server and export + EXPORT_NAME=$(basename "$NFS_EXPORT") + DEFAULT_NAME="nfs_${NFS_SERVER}_${EXPORT_NAME}" + FOLDER_NAME=$(whiptail --inputbox "$(translate "Enter new folder name:")" 10 60 "$DEFAULT_NAME" --title "$(translate "New Folder in /mnt")" 3>&1 1>&2 2>&3) + if [[ -n "$FOLDER_NAME" ]]; then + MOUNT_POINT="/mnt/$FOLDER_NAME" + return 0 + fi + ;; + custom) + MOUNT_POINT=$(whiptail --inputbox "$(translate "Enter full path for mount point:")" 10 70 "/mnt/nfs_share" --title "$(translate "Custom Path")" 3>&1 1>&2 2>&3) + if [[ -n "$MOUNT_POINT" ]]; then + return 0 + fi + ;; + *) + return 1 + ;; + esac + done +} + +configure_mount_options() { + MOUNT_TYPE=$(whiptail --title "$(translate "Mount Options")" --menu "$(translate "Select mount configuration:")" 15 70 4 \ + "default" "$(translate "Default options")" \ + "readonly" "$(translate "Read-only mount")" \ + "performance" "$(translate "Performance optimized")" \ + "custom" "$(translate "Custom options")" 3>&1 1>&2 2>&3) + + case "$MOUNT_TYPE" in + default) + MOUNT_OPTIONS="rw,hard,intr,rsize=8192,wsize=8192,timeo=14" + ;; + readonly) + MOUNT_OPTIONS="ro,hard,intr,rsize=8192,timeo=14" + ;; + performance) + MOUNT_OPTIONS="rw,hard,intr,rsize=1048576,wsize=1048576,timeo=14,retrans=2" + ;; + custom) + MOUNT_OPTIONS=$(whiptail --inputbox "$(translate "Enter custom mount options:")" 10 70 "rw,hard,intr" --title "$(translate "Custom Options")" 3>&1 1>&2 2>&3) + [[ -z "$MOUNT_OPTIONS" ]] && MOUNT_OPTIONS="rw,hard,intr" + ;; + *) + MOUNT_OPTIONS="rw,hard,intr,rsize=8192,wsize=8192,timeo=14" + ;; + esac + + if whiptail --yesno "$(translate "Do you want to make this mount permanent?")\n\n$(translate "This will add the mount to /etc/fstab so it persists after reboot.")" 10 70 --title "$(translate "Permanent Mount")"; then + PERMANENT_MOUNT=true + else + PERMANENT_MOUNT=false + fi +} + +validate_export_exists() { + local server="$1" + local export="$2" + + + + VALIDATION_OUTPUT=$(pct exec "$CTID" -- showmount -e "$server" 2>/dev/null | grep "^$export[[:space:]]") + + if [[ -n "$VALIDATION_OUTPUT" ]]; then + + return 0 + else + show_proxmenux_logo + echo -e + msg_error "$(translate "Export not found on server:") $export" + msg_success "$(translate "Press Enter to return to menu...")" + read -r + return 1 + fi +} + +mount_nfs_share() { + # Step 0: Install NFS client first + install_nfs_client || return + + # Step 1: Select server + select_nfs_server || return + + # Step 2: Select export + select_nfs_export || return + + # Step 2.5: Validate export exists + if ! validate_export_exists "$NFS_SERVER" "$NFS_EXPORT"; then + echo -e "" + msg_error "$(translate "Cannot proceed with invalid export path.")" + msg_success "$(translate "Press Enter to return to menu...")" + read -r + return + fi + + # Step 3: Select mount point + select_mount_point || return + + # Step 4: Configure mount options + configure_mount_options || return + + +# =================================================== + + show_proxmenux_logo + msg_title "$(translate "Mount NFS Share on Host")" + +# ===================================================== + + + if ! pct exec "$CTID" -- test -d "$MOUNT_POINT"; then + if pct exec "$CTID" -- mkdir -p "$MOUNT_POINT"; then + msg_ok "$(translate "Mount point created.")" + else + msg_error "$(translate "Failed to create mount point.")" + return 1 + fi + fi + + if pct exec "$CTID" -- mount | grep -q "$MOUNT_POINT"; then + msg_warn "$(translate "Something is already mounted at") $MOUNT_POINT" + if ! whiptail --yesno "$(translate "Do you want to unmount it first?")" 8 60 --title "$(translate "Already Mounted")"; then + return + fi + pct exec "$CTID" -- umount "$MOUNT_POINT" 2>/dev/null || true + fi + + # Build mount command + NFS_PATH="$NFS_SERVER:$NFS_EXPORT" + + msg_info "$(translate "Testing NFS connection...")" + if pct exec "$CTID" -- mount -t nfs -o "$MOUNT_OPTIONS" "$NFS_PATH" "$MOUNT_POINT"; then + msg_ok "$(translate "NFS share mounted successfully!")" + + # Test write access + if pct exec "$CTID" -- touch "$MOUNT_POINT/.test_write" 2>/dev/null; then + pct exec "$CTID" -- rm "$MOUNT_POINT/.test_write" 2>/dev/null + msg_ok "$(translate "Write access confirmed.")" + else + msg_warn "$(translate "Read-only access (or no write permissions).")" + fi + + # Add to fstab if permanent + if [[ "$PERMANENT_MOUNT" == "true" ]]; then + pct exec "$CTID" -- sed -i "\|$MOUNT_POINT|d" /etc/fstab + FSTAB_ENTRY="$NFS_PATH $MOUNT_POINT nfs $MOUNT_OPTIONS 0 0" + pct exec "$CTID" -- bash -c "echo '$FSTAB_ENTRY' >> /etc/fstab" + msg_ok "$(translate "Added to /etc/fstab for permanent mounting.")" + fi + + # Show mount information + echo -e "" + echo -e "${TAB}${BOLD}$(translate "Mount Information:")${CL}" + echo -e "${TAB}${BGN}$(translate "Server:")${CL} ${BL}$NFS_SERVER${CL}" + echo -e "${TAB}${BGN}$(translate "Export:")${CL} ${BL}$NFS_EXPORT${CL}" + echo -e "${TAB}${BGN}$(translate "Mount Point:")${CL} ${BL}$MOUNT_POINT${CL}" + echo -e "${TAB}${BGN}$(translate "Options:")${CL} ${BL}$MOUNT_OPTIONS${CL}" + echo -e "${TAB}${BGN}$(translate "Permanent:")${CL} ${BL}$PERMANENT_MOUNT${CL}" + + else + msg_error "$(translate "Failed to mount NFS share.")" + echo -e "${TAB}$(translate "Please check:")" + echo -e "${TAB}• $(translate "Server is accessible:"): $NFS_SERVER" + echo -e "${TAB}• $(translate "Export exists:"): $NFS_EXPORT" + echo -e "${TAB}• $(translate "Network connectivity")" + echo -e "${TAB}• $(translate "NFS server is running")" + echo -e "${TAB}• $(translate "Export permissions allow access")" + fi + + echo -e "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + +view_nfs_mounts() { + show_proxmenux_logo + msg_title "$(translate "Current NFS Mounts")" + + echo -e "$(translate "NFS mounts in CT") $CTID:" + echo "==================================" + + # Show currently mounted NFS shares - VERSIÓN CORREGIDA + CURRENT_MOUNTS=$(pct exec "$CTID" -- mount | grep -E "type nfs|:.*on.*nfs" 2>/dev/null || true) + if [[ -n "$CURRENT_MOUNTS" ]]; then + echo -e "${BOLD}$(translate "Currently Mounted:")${CL}" + echo "$CURRENT_MOUNTS" + echo "" + else + # Verificar si hay montajes NFS en fstab que estén activos + ACTIVE_NFS_MOUNTS=$(pct exec "$CTID" -- grep "nfs" /etc/fstab 2>/dev/null | grep -v "^#" | while read -r line; do + MOUNT_POINT=$(echo "$line" | awk '{print $2}') + if pct exec "$CTID" -- mount | grep -q "$MOUNT_POINT"; then + echo "$MOUNT_POINT" + fi + done) + + if [[ -n "$ACTIVE_NFS_MOUNTS" ]]; then + echo -e "${BOLD}$(translate "Currently Mounted:")${CL}" + while IFS= read -r mount_point; do + if [[ -n "$mount_point" ]]; then + MOUNT_INFO=$(pct exec "$CTID" -- mount | grep "$mount_point") + echo "$MOUNT_INFO" + fi + done <<< "$ACTIVE_NFS_MOUNTS" + echo "" + else + echo "$(translate "No NFS shares currently mounted.")" + echo "" + fi + fi + + # Show fstab entries + FSTAB_NFS=$(pct exec "$CTID" -- grep "nfs" /etc/fstab 2>/dev/null || true) + if [[ -n "$FSTAB_NFS" ]]; then + echo -e "${BOLD}$(translate "Permanent Mounts (fstab):")${CL}" + echo "$FSTAB_NFS" + echo "" + + echo -e "${TAB}${BOLD}$(translate "Mount Details:")${CL}" + while IFS= read -r fstab_line; do + if [[ -n "$fstab_line" && ! "$fstab_line" =~ ^# ]]; then + NFS_PATH=$(echo "$fstab_line" | awk '{print $1}') + MOUNT_POINT=$(echo "$fstab_line" | awk '{print $2}') + OPTIONS=$(echo "$fstab_line" | awk '{print $4}') + + # Extract server and export from NFS path + SERVER=$(echo "$NFS_PATH" | cut -d: -f1) + EXPORT=$(echo "$NFS_PATH" | cut -d: -f2) + + echo -e "${TAB}${BGN}$(translate "Server:")${CL} ${BL}$SERVER${CL}" + echo -e "${TAB}${BGN}$(translate "Export:")${CL} ${BL}$EXPORT${CL}" + echo -e "${TAB}${BGN}$(translate "Mount Point:")${CL} ${BL}$MOUNT_POINT${CL}" + echo -e "${TAB}${BGN}$(translate "Options:")${CL} ${BL}$OPTIONS${CL}" + + # Check if currently mounted + if pct exec "$CTID" -- mount | grep -q "$MOUNT_POINT"; then + echo -e "${TAB}${BGN}$(translate "Status:")${CL} ${GN}$(translate "Mounted")${CL}" + else + echo -e "${TAB}${BGN}$(translate "Status:")${CL} ${RD}$(translate "Not Mounted")${CL}" + fi + echo "" + fi + done <<< "$FSTAB_NFS" + else + echo "$(translate "No permanent NFS mounts configured.")" + fi + + echo "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + + + +unmount_nfs_share() { + # Get current NFS mounts + MOUNTS=$(pct exec "$CTID" -- mount | grep -E "type nfs|:.*on.*nfs" | awk '{print $3}' | sort -u || true) + FSTAB_MOUNTS=$(pct exec "$CTID" -- grep -E "nfs" /etc/fstab 2>/dev/null | grep -v "^#" | awk '{print $2}' | sort -u || true) + + # Combine and deduplicate + ALL_MOUNTS=$(echo -e "$MOUNTS\n$FSTAB_MOUNTS" | sort -u | grep -v "^$" || true) + + if [[ -z "$ALL_MOUNTS" ]]; then + dialog --backtitle "ProxMenux" --title "$(translate "No Mounts")" --msgbox "\n$(translate "No NFS mounts found.")" 8 50 + return + fi + + OPTIONS=() + while IFS= read -r mount_point; do + [[ -n "$mount_point" ]] && OPTIONS+=("$mount_point" "") + done <<< "$ALL_MOUNTS" + + SELECTED_MOUNT=$(dialog --backtitle "ProxMenux" --title "$(translate "Unmount NFS Share")" --menu "$(translate "Select mount point to unmount:")" 20 80 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) + [[ -z "$SELECTED_MOUNT" ]] && return + + if whiptail --yesno "$(translate "Are you sure you want to unmount this NFS share?")\n\n$(translate "Mount Point:"): $SELECTED_MOUNT\n\n$(translate "This will remove the mount from /etc/fstab.")" 12 80 --title "$(translate "Confirm Unmount")"; then + show_proxmenux_logo + msg_title "$(translate "Unmount NFS Share")" + + # Remove from fstab + pct exec "$CTID" -- sed -i "\|[[:space:]]$SELECTED_MOUNT[[:space:]]|d" /etc/fstab + msg_ok "$(translate "Removed from /etc/fstab.")" + + echo -e "" + msg_ok "$(translate "NFS share unmount successfully. Reboot LXC required to take effect.")" + fi + + echo -e "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + + + + +test_nfs_connectivity() { + show_proxmenux_logo + msg_title "$(translate "Test NFS Connectivity")" + + echo -e "$(translate "NFS Client Status in CT") $CTID:" + echo "==================================" + + # Check if NFS client is installed + if pct exec "$CTID" -- dpkg -s nfs-common &>/dev/null; then + echo "$(translate "NFS Client: INSTALLED")" + + # Check showmount + if pct exec "$CTID" -- which showmount >/dev/null 2>&1; then + echo "$(translate "NFS Client Tools: AVAILABLE")" + else + echo "$(translate "NFS Client Tools: NOT AVAILABLE")" + fi + + # Check rpcbind service + if pct exec "$CTID" -- systemctl is-active --quiet rpcbind 2>/dev/null; then + echo "$(translate "RPC Bind Service: RUNNING")" + else + echo "$(translate "RPC Bind Service: STOPPED")" + msg_warn "$(translate "Starting rpcbind service...")" + pct exec "$CTID" -- systemctl start rpcbind 2>/dev/null || true + fi + + echo "" + echo "$(translate "Current NFS mounts:")" + CURRENT_MOUNTS=$(pct exec "$CTID" -- mount | grep -E "type nfs|:.*on.*nfs" 2>/dev/null || true) + if [[ -n "$CURRENT_MOUNTS" ]]; then + echo "$CURRENT_MOUNTS" + else + # Check for active NFS mounts from fstab + ACTIVE_NFS_MOUNTS=$(pct exec "$CTID" -- grep "nfs" /etc/fstab 2>/dev/null | grep -v "^#" | while read -r line; do + MOUNT_POINT=$(echo "$line" | awk '{print $2}') + if pct exec "$CTID" -- mount | grep -q "$MOUNT_POINT"; then + pct exec "$CTID" -- mount | grep "$MOUNT_POINT" + fi + done) + + if [[ -n "$ACTIVE_NFS_MOUNTS" ]]; then + echo "$ACTIVE_NFS_MOUNTS" + else + echo "$(translate "No NFS mounts active.")" + fi + fi + + echo "" + echo "$(translate "Testing network connectivity...")" + + # Test connectivity to known NFS servers from fstab + FSTAB_SERVERS=$(pct exec "$CTID" -- grep "nfs" /etc/fstab 2>/dev/null | awk '{print $1}' | cut -d: -f1 | sort -u || true) + if [[ -n "$FSTAB_SERVERS" ]]; then + while IFS= read -r server; do + if [[ -n "$server" ]]; then + echo -n "$(translate "Testing") $server: " + if pct exec "$CTID" -- ping -c 1 -W 2 "$server" >/dev/null 2>&1; then + echo -e "\033[1;92m$(translate "Reachable")\033[0m" + + # Test NFS port + echo -n " $(translate "NFS port 2049"): " + if pct exec "$CTID" -- nc -z -w 2 "$server" 2049 2>/dev/null; then + echo -e "\033[1;92m$(translate "Open")\033[0m" + else + echo -e "\033[1;91m$(translate "Closed")\033[0m" + fi + + # Try to list exports + echo -n " $(translate "Export list test"): " + if pct exec "$CTID" -- showmount -e "$server" >/dev/null 2>&1; then + echo -e "\033[1;92m$(translate "Available")\033[0m" + else + echo -e "\033[1;91m$(translate "Failed")\033[0m" + fi + else + echo -e "\033[1;91m$(translate "Unreachable")\033[0m" + fi + fi + done <<< "$FSTAB_SERVERS" + else + echo "$(translate "No NFS servers configured to test.")" + fi + + else + echo "$(translate "NFS Client: NOT INSTALLED")" + echo "" + echo "$(translate "Run 'Mount NFS Share' to install NFS client automatically.")" + fi + + echo "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + + +# === Main Menu === +while true; do + CHOICE=$(dialog --backtitle "ProxMenux" --title "$(translate "NFS Client Manager - CT") $CTID" \ + --menu "$(translate "Choose an option:")" 20 70 12 \ + "1" "$(translate "Mount NFS Share")" \ + "2" "$(translate "View Current Mounts")" \ + "3" "$(translate "Unmount NFS Share")" \ + "4" "$(translate "Test NFS Connectivity")" \ + "5" "$(translate "Exit")" \ + 3>&1 1>&2 2>&3) + + RETVAL=$? + if [[ $RETVAL -ne 0 ]]; then + exit 0 + fi + + case $CHOICE in + 1) mount_nfs_share ;; + 2) view_nfs_mounts ;; + 3) unmount_nfs_share ;; + 4) test_nfs_connectivity ;; + 5) exit 0 ;; + *) exit 0 ;; + esac +done \ No newline at end of file diff --git a/scripts/share/nfs_host.sh b/scripts/share/nfs_host.sh new file mode 100644 index 0000000..cc3f794 --- /dev/null +++ b/scripts/share/nfs_host.sh @@ -0,0 +1,901 @@ +#!/bin/bash +# ========================================================== +# ProxMenu Host - NFS Host Manager for Proxmox Host +# ========================================================== +# Based on ProxMenux by MacRimi +# ========================================================== +# Description: +# This script allows you to manage NFS client mounts on Proxmox Host: +# - Mount external NFS shares on the host +# - Configure permanent mounts +# - Auto-discover NFS servers +# - Integrate with Proxmox storage system +# ========================================================== + +# Configuration +REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main" +BASE_DIR="/usr/local/share/proxmenux" +UTILS_FILE="$BASE_DIR/utils.sh" +VENV_PATH="/opt/googletrans-env" + +if [[ -f "$UTILS_FILE" ]]; then + source "$UTILS_FILE" +fi + +load_language +initialize_cache + + +if ! command -v pveversion >/dev/null 2>&1; then + dialog --backtitle "ProxMenux" --title "$(translate "Error")" --msgbox "$(translate "This script must be run on a Proxmox host.")" 8 60 + exit 1 +fi + + +discover_nfs_servers() { + show_proxmenux_logo + msg_title "$(translate "NFS Host Manager - Proxmox Host")" + msg_info "$(translate "Scanning network for NFS servers...")" + + + + HOST_IP=$(hostname -I | awk '{print $1}') + NETWORK=$(echo "$HOST_IP" | cut -d. -f1-3).0/24 + + + if ! which nmap >/dev/null 2>&1; then + apt-get install -y nmap &>/dev/null + fi + + + SERVERS=$(nmap -p 2049 --open "$NETWORK" 2>/dev/null | grep -B 4 "2049/tcp open" | grep "Nmap scan report" | awk '{print $5}' | sort -u || true) + + if [[ -z "$SERVERS" ]]; then + cleanup + whiptail --title "$(translate "No Servers Found")" --msgbox "$(translate "No NFS servers found on the network.")\n\n$(translate "You can add servers manually.")" 10 60 + return 1 + fi + + OPTIONS=() + while IFS= read -r server; do + if [[ -n "$server" ]]; then + EXPORTS_COUNT=$(showmount -e "$server" 2>/dev/null | tail -n +2 | wc -l || echo "0") + SERVER_INFO="NFS Server ($EXPORTS_COUNT exports)" + OPTIONS+=("$server" "$SERVER_INFO") + fi + done <<< "$SERVERS" + + if [[ ${#OPTIONS[@]} -eq 0 ]]; then + cleanup + whiptail --title "$(translate "No Valid Servers")" --msgbox "$(translate "No accessible NFS servers found.")" 8 50 + return 1 + fi + msg_ok "$(translate "NFS servers detected")" + NFS_SERVER=$(whiptail --title "$(translate "Select NFS Server")" --menu "$(translate "Choose an NFS server:")" 20 80 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) + [[ -n "$NFS_SERVER" ]] && return 0 || return 1 +} + +select_nfs_server() { + METHOD=$(dialog --backtitle "ProxMenux" --title "$(translate "NFS Server Selection")" --menu "$(translate "How do you want to select the NFS server?")" 15 70 3 \ + "auto" "$(translate "Auto-discover servers on network")" \ + "manual" "$(translate "Enter server IP/hostname manually")" \ + "recent" "$(translate "Select from recent servers")" 3>&1 1>&2 2>&3) + + case "$METHOD" in + auto) + discover_nfs_servers || return 1 + ;; + manual) + clear + NFS_SERVER=$(whiptail --inputbox "$(translate "Enter NFS server IP or hostname:")" 10 60 --title "$(translate "NFS Server")" 3>&1 1>&2 2>&3) + [[ -z "$NFS_SERVER" ]] && return 1 + ;; + recent) + clear + RECENT=$(grep "nfs" /etc/fstab 2>/dev/null | awk '{print $1}' | cut -d: -f1 | sort -u || true) + if [[ -z "$RECENT" ]]; then + dialog --title "$(translate "No Recent Servers")" --msgbox "\n$(translate "No recent NFS servers found.")" 8 50 + return 1 + fi + + OPTIONS=() + while IFS= read -r server; do + [[ -n "$server" ]] && OPTIONS+=("$server" "$(translate "Recent NFS server")") + done <<< "$RECENT" + + NFS_SERVER=$(whiptail --title "$(translate "Recent NFS Servers")" --menu "$(translate "Choose a recent server:")" 20 70 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) + [[ -n "$NFS_SERVER" ]] && return 0 || return 1 + ;; + *) + return 1 + ;; + esac + return 0 +} + +select_nfs_export() { + if ! which showmount >/dev/null 2>&1; then + whiptail --title "$(translate "NFS Client Error")" \ + --msgbox "$(translate "showmount command is not working properly.")\n\n$(translate "Please check the installation.")" \ + 10 60 + return 1 + fi + + + if ! ping -c 1 -W 3 "$NFS_SERVER" >/dev/null 2>&1; then + whiptail --title "$(translate "Connection Error")" \ + --msgbox "$(translate "Cannot reach server") $NFS_SERVER\n\n$(translate "Please check:")\n• $(translate "Server IP/hostname is correct")\n• $(translate "Network connectivity")\n• $(translate "Server is online")" \ + 12 70 + return 1 + fi + + + if ! nc -z -w 3 "$NFS_SERVER" 2049 2>/dev/null; then + whiptail --title "$(translate "NFS Port Error")" \ + --msgbox "$(translate "NFS port (2049) is not accessible on") $NFS_SERVER\n\n$(translate "Please check:")\n• $(translate "NFS server is running")\n• $(translate "Firewall settings")\n• $(translate "NFS service is enabled")" \ + 12 70 + return 1 + fi + + + EXPORTS_OUTPUT=$(showmount -e "$NFS_SERVER" 2>&1) + EXPORTS_RESULT=$? + + if [[ $EXPORTS_RESULT -ne 0 ]]; then + ERROR_MSG=$(echo "$EXPORTS_OUTPUT" | grep -i "error\|failed\|denied" | head -1) + + if echo "$EXPORTS_OUTPUT" | grep -qi "connection refused\|network unreachable"; then + whiptail --title "$(translate "Network Error")" \ + --msgbox "$(translate "Network connection failed to") $NFS_SERVER\n\n$(translate "Error:"): $ERROR_MSG\n\n$(translate "Please check:")\n• $(translate "Server is running")\n• $(translate "Network connectivity")\n• $(translate "Firewall settings")" \ + 14 80 + else + whiptail --title "$(translate "NFS Error")" \ + --msgbox "$(translate "Failed to connect to") $NFS_SERVER\n\n$(translate "Error:"): $ERROR_MSG" \ + 12 80 + fi + return 1 + fi + + EXPORTS=$(echo "$EXPORTS_OUTPUT" | tail -n +2 | awk '{print $1}' | grep -v "^$") + + if [[ -z "$EXPORTS" ]]; then + whiptail --title "$(translate "No Exports Found")" \ + --msgbox "$(translate "No exports found on server") $NFS_SERVER\n\n$(translate "Server response:")\n$(echo "$EXPORTS_OUTPUT" | head -10)\n\n$(translate "You can enter the export path manually.")" \ + 16 80 + + NFS_EXPORT=$(whiptail --inputbox "$(translate "Enter NFS export path (e.g., /mnt/shared):")" 10 60 --title "$(translate "Export Path")" 3>&1 1>&2 2>&3) + [[ -z "$NFS_EXPORT" ]] && return 1 + return 0 + fi + + OPTIONS=() + while IFS= read -r export_line; do + if [[ -n "$export_line" ]]; then + EXPORT_PATH=$(echo "$export_line" | awk '{print $1}') + CLIENTS=$(echo "$EXPORTS_OUTPUT" | grep "^$EXPORT_PATH" | awk '{for(i=2;i<=NF;i++) printf "%s ", $i; print ""}' | sed 's/[[:space:]]*$//') + if [[ -n "$CLIENTS" ]]; then + OPTIONS+=("$EXPORT_PATH" "$CLIENTS") + else + OPTIONS+=("$EXPORT_PATH" "$(translate "NFS export")") + fi + fi + done <<< "$EXPORTS" + + if [[ ${#OPTIONS[@]} -eq 0 ]]; then + whiptail --title "$(translate "No Available Exports")" \ + --msgbox "$(translate "No accessible exports found.")\n\n$(translate "You can enter the export path manually.")" \ + 10 70 + + NFS_EXPORT=$(whiptail --inputbox "$(translate "Enter NFS export path (e.g., /mnt/shared):")" 10 60 --title "$(translate "Export Path")" 3>&1 1>&2 2>&3) + [[ -n "$NFS_EXPORT" ]] && return 0 || return 1 + fi + + NFS_EXPORT=$(whiptail --title "$(translate "Select NFS Export")" --menu "$(translate "Choose an export to mount:")" 20 70 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) + [[ -n "$NFS_EXPORT" ]] && return 0 || return 1 +} + +select_host_mount_point() { + while true; do + METHOD=$(whiptail --title "$(translate "Select Mount Point")" --menu "$(translate "Where do you want to mount the NFS export on the host?")" 15 70 4 \ + "mnt" "$(translate "Create folder in /mnt")" \ + "media" "$(translate "Create folder in /media")" \ + "srv" "$(translate "Create folder in /srv")" \ + "custom" "$(translate "Enter custom path")" 3>&1 1>&2 2>&3) + + case "$METHOD" in + mnt) + EXPORT_NAME=$(basename "$NFS_EXPORT") + DEFAULT_NAME="${EXPORT_NAME}" + FOLDER_NAME=$(whiptail --inputbox "$(translate "Enter folder name for /mnt:")" 10 60 "$DEFAULT_NAME" --title "$(translate "Folder in /mnt")" 3>&1 1>&2 2>&3) + if [[ -n "$FOLDER_NAME" ]]; then + MOUNT_POINT="/mnt/$FOLDER_NAME" + return 0 + fi + ;; + media) + EXPORT_NAME=$(basename "$NFS_EXPORT") + DEFAULT_NAME="nfs_${NFS_SERVER}_${EXPORT_NAME}" + FOLDER_NAME=$(whiptail --inputbox "$(translate "Enter folder name for /media:")" 10 60 "$DEFAULT_NAME" --title "$(translate "Folder in /media")" 3>&1 1>&2 2>&3) + if [[ -n "$FOLDER_NAME" ]]; then + MOUNT_POINT="/media/$FOLDER_NAME" + return 0 + fi + ;; + srv) + EXPORT_NAME=$(basename "$NFS_EXPORT") + DEFAULT_NAME="nfs_${NFS_SERVER}_${EXPORT_NAME}" + FOLDER_NAME=$(whiptail --inputbox "$(translate "Enter folder name for /srv:")" 10 60 "$DEFAULT_NAME" --title "$(translate "Folder in /srv")" 3>&1 1>&2 2>&3) + if [[ -n "$FOLDER_NAME" ]]; then + MOUNT_POINT="/srv/$FOLDER_NAME" + return 0 + fi + ;; + custom) + MOUNT_POINT=$(whiptail --inputbox "$(translate "Enter full path for mount point:")" 10 70 "/mnt/nfs_share" --title "$(translate "Custom Path")" 3>&1 1>&2 2>&3) + if [[ -n "$MOUNT_POINT" ]]; then + return 0 + fi + ;; + *) + return 1 + ;; + esac + done +} + +configure_host_mount_options() { + MOUNT_TYPE=$(whiptail --title "$(translate "Mount Options")" --menu "$(translate "Select mount configuration:")" 15 70 4 \ + "default" "$(translate "Default options")" \ + "readonly" "$(translate "Read-only mount")" \ + "performance" "$(translate "Performance optimized")" \ + "custom" "$(translate "Custom options")" 3>&1 1>&2 2>&3) + + [[ $? -ne 0 ]] && return 1 + + case "$MOUNT_TYPE" in + default) + MOUNT_OPTIONS="rw,hard,intr,rsize=8192,wsize=8192,timeo=14" + ;; + readonly) + MOUNT_OPTIONS="ro,hard,intr,rsize=8192,timeo=14" + ;; + performance) + MOUNT_OPTIONS="rw,hard,intr,rsize=1048576,wsize=1048576,timeo=14,retrans=2" + ;; + custom) + MOUNT_OPTIONS=$(whiptail --inputbox "$(translate "Enter custom mount options:")" 10 70 "rw,hard,intr" --title "$(translate "Custom Options")" 3>&1 1>&2 2>&3) + [[ $? -ne 0 ]] && return 1 + [[ -z "$MOUNT_OPTIONS" ]] && MOUNT_OPTIONS="rw,hard,intr" + ;; + *) + MOUNT_OPTIONS="rw,hard,intr,rsize=8192,wsize=8192,timeo=14" + ;; + esac + + + if whiptail --yesno "$(translate "Do you want to make this mount permanent?")\n\n$(translate "This will add the mount to /etc/fstab so it persists after reboot.")" 10 70 --title "$(translate "Permanent Mount")"; then + PERMANENT_MOUNT=true + else + if [[ $? -eq 1 ]]; then + PERMANENT_MOUNT=false + else + return 1 + fi + fi + + + if whiptail --yesno "$(translate "Do you want to add this as Proxmox storage?")\n\n$(translate "This will make the NFS share available as storage in Proxmox web interface.")" 10 70 --title "$(translate "Proxmox Storage")"; then + PROXMOX_STORAGE=true + + + STORAGE_ID=$(whiptail --inputbox "$(translate "Enter storage ID for Proxmox:")" 10 60 "nfs-$(echo $NFS_SERVER | tr '.' '-')" --title "$(translate "Storage ID")" 3>&1 1>&2 2>&3) + STORAGE_ID_RESULT=$? + + if [[ $STORAGE_ID_RESULT -ne 0 ]]; then + + if whiptail --yesno "$(translate "Storage ID input was cancelled.")\n\n$(translate "Do you want to continue without Proxmox storage integration?")" 10 70 --title "$(translate "Continue Without Storage")"; then + PROXMOX_STORAGE=false + else + return 1 + fi + else + + [[ -z "$STORAGE_ID" ]] && STORAGE_ID="nfs-$(echo $NFS_SERVER | tr '.' '-')" + fi + else + DIALOG_RESULT=$? + if [[ $DIALOG_RESULT -eq 1 ]]; then + + PROXMOX_STORAGE=false + else + + return 1 + fi + fi + + return 0 +} + +validate_host_export_exists() { + local server="$1" + local export="$2" + + VALIDATION_OUTPUT=$(showmount -e "$server" 2>/dev/null | grep "^$export[[:space:]]") + + if [[ -n "$VALIDATION_OUTPUT" ]]; then + return 0 + else + show_proxmenux_logo + echo -e + msg_error "$(translate "Export not found on server:") $export" + return 1 + fi +} + +add_proxmox_nfs_storage() { + local storage_id="$1" + local server="$2" + local export="$3" + local mount_point="$4" + + msg_info "$(translate "Starting Proxmox storage integration...")" + + if ! which pvesm >/dev/null 2>&1; then + msg_error "$(translate "pvesm command not found. This should not happen on Proxmox.")" + echo "Press Enter to continue..." + read -r + return 1 + fi + + msg_ok "$(translate "pvesm command found")" + + + if pvesm status "$storage_id" >/dev/null 2>&1; then + msg_warn "$(translate "Storage ID already exists:") $storage_id" + if ! whiptail --yesno "$(translate "Storage ID already exists. Do you want to remove and recreate it?")" 8 60 --title "$(translate "Storage Exists")"; then + return 0 + fi + pvesm remove "$storage_id" 2>/dev/null || true + fi + + msg_ok "$(translate "Storage ID is available")" + + + msg_info "$(translate "Creating NFS storage...")" + CONTENT_LIST="backup,iso,vztmpl" + + +############################################# + + NFS_VERSION="3" + +############################################# + + + PVESM_OUTPUT=$(pvesm add nfs "$storage_id" \ + --server "$server" \ + --export "$export" \ + --content "$CONTENT_LIST" \ + --options "vers=$NFS_VERSION" 2>&1) + PVESM_RESULT=$? + + if [[ $PVESM_RESULT -eq 0 ]]; then + msg_ok "$(translate "NFS storage added successfully to Proxmox!")" + echo -e "" + echo -e "${TAB}${BGN}$(translate "Storage ID:")${CL} ${BL}$storage_id${CL}" + echo -e "${TAB}${BGN}$(translate "Server:")${CL} ${BL}$server${CL}" + echo -e "${TAB}${BGN}$(translate "Export:")${CL} ${BL}$export${CL}" + echo -e "${TAB}${BGN}$(translate "Content Types:")${CL} ${BL}$CONTENT_LIST${CL}" + echo -e "${TAB}${BGN}$(translate "NFS Version:")${CL} ${BL}$NFS_VERSION${CL}" + echo -e "" + msg_ok "$(translate "Storage is now available in Proxmox web interface under Datacenter > Storage")" + return 0 + else + msg_error "$(translate "Failed to add NFS storage to Proxmox.")" + echo "Error details: $PVESM_OUTPUT" + msg_warn "$(translate "The NFS share is still mounted, but not added as Proxmox storage.")" + msg_info2 "$(translate "You can add it manually through:")" + echo -e "${TAB}• $(translate "Proxmox web interface: Datacenter > Storage > Add > NFS")" + echo -e "${TAB}• $(translate "Command line:"): pvesm add nfs $storage_id --server $server --export $export --content backup,iso,vztmpl" + return 1 + fi +} + +mount_host_nfs_share() { + + if ! which showmount >/dev/null 2>&1; then + msg_error "$(translate "NFS client tools not found. Please check Proxmox installation.")" + return 1 + fi + + # Step 1: + select_nfs_server || return + + # Step 2: + select_nfs_export || return + + # Step 2.5: + if ! validate_host_export_exists "$NFS_SERVER" "$NFS_EXPORT"; then + echo -e "" + msg_error "$(translate "Cannot proceed with invalid export path.")" + msg_success "$(translate "Press Enter to return to menu...")" + read -r + return + fi + + # Step 3: + select_host_mount_point || return + + # Step 4: + configure_host_mount_options || return + + + # =================================================== + + show_proxmenux_logo + msg_title "$(translate "Mount NFS Share on Host")" + +# ===================================================== + + + if ! test -d "$MOUNT_POINT"; then + if mkdir -p "$MOUNT_POINT"; then + msg_ok "$(translate "Mount point created on host.")" + else + msg_error "$(translate "Failed to create mount point on host.")" + return 1 + fi + fi + + + if mount | grep -q "$MOUNT_POINT"; then + msg_warn "$(translate "Something is already mounted at") $MOUNT_POINT" + if ! whiptail --yesno "$(translate "Do you want to unmount it first?")" 8 60 --title "$(translate "Already Mounted")"; then + return + fi + umount "$MOUNT_POINT" 2>/dev/null || true + fi + + + NFS_PATH="$NFS_SERVER:$NFS_EXPORT" + + if mount -t nfs -o "$MOUNT_OPTIONS" "$NFS_PATH" "$MOUNT_POINT" > /dev/null 2>&1; then + msg_ok "$(translate "NFS share mounted successfully on host!")" + + + + if touch "$MOUNT_POINT/.test_write" 2>/dev/null; then + rm "$MOUNT_POINT/.test_write" 2>/dev/null + msg_ok "$(translate "Write access confirmed.")" + else + msg_warn "$(translate "Read-only access (or no write permissions).")" + fi + + + if [[ "$PERMANENT_MOUNT" == "true" ]]; then + + sed -i "\|$MOUNT_POINT|d" /etc/fstab + FSTAB_ENTRY="$NFS_PATH $MOUNT_POINT nfs $MOUNT_OPTIONS 0 0" + echo "$FSTAB_ENTRY" >> /etc/fstab + msg_ok "$(translate "Added to /etc/fstab for permanent mounting.")" + + msg_info "$(translate "Reloading systemd configuration...")" + systemctl daemon-reload 2>/dev/null || true + msg_ok "$(translate "Systemd configuration reloaded.")" + fi + + + if [[ "$PROXMOX_STORAGE" == "true" ]]; then + add_proxmox_nfs_storage "$STORAGE_ID" "$NFS_SERVER" "$NFS_EXPORT" "$MOUNT_POINT" + fi + + + echo -e "" + echo -e "${TAB}${BOLD}$(translate "Host Mount Information:")${CL}" + echo -e "${TAB}${BGN}$(translate "Server:")${CL} ${BL}$NFS_SERVER${CL}" + echo -e "${TAB}${BGN}$(translate "Export:")${CL} ${BL}$NFS_EXPORT${CL}" + echo -e "${TAB}${BGN}$(translate "Host Mount Point:")${CL} ${BL}$MOUNT_POINT${CL}" + echo -e "${TAB}${BGN}$(translate "Options:")${CL} ${BL}$MOUNT_OPTIONS${CL}" + echo -e "${TAB}${BGN}$(translate "Permanent:")${CL} ${BL}$PERMANENT_MOUNT${CL}" + if [[ "$PROXMOX_STORAGE" == "true" ]]; then + echo -e "${TAB}${BGN}$(translate "Proxmox Storage ID:")${CL} ${BL}$STORAGE_ID${CL}" + fi + + else + msg_error "$(translate "Failed to mount NFS share on host.")" + echo -e "${TAB}$(translate "Please check:")" + echo -e "${TAB}• $(translate "Server is accessible:"): $NFS_SERVER" + echo -e "${TAB}• $(translate "Export exists:"): $NFS_EXPORT" + echo -e "${TAB}• $(translate "Network connectivity")" + echo -e "${TAB}• $(translate "NFS server is running")" + echo -e "${TAB}• $(translate "Export permissions allow access")" + fi + + echo -e "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + +view_host_nfs_mounts() { + show_proxmenux_logo + msg_title "$(translate "Current NFS Mounts on Host")" + + echo -e "$(translate "NFS mounts on Proxmox host:"):" + echo "==================================" + + CURRENT_MOUNTS=$(mount | grep -E "type nfs|:.*on.*nfs" 2>/dev/null || true) + if [[ -n "$CURRENT_MOUNTS" ]]; then + echo -e "${BOLD}$(translate "Currently Mounted:")${CL}" + echo "$CURRENT_MOUNTS" + echo "" + else + echo "$(translate "No NFS shares currently mounted on host.")" + echo "" + fi + + FSTAB_NFS=$(grep "nfs" /etc/fstab 2>/dev/null || true) + if [[ -n "$FSTAB_NFS" ]]; then + echo -e "${BOLD}$(translate "Permanent Mounts (fstab):")${CL}" + echo "$FSTAB_NFS" + echo "" + + echo -e "${TAB}${BOLD}$(translate "Mount Details:")${CL}" + while IFS= read -r fstab_line; do + if [[ -n "$fstab_line" && ! "$fstab_line" =~ ^# ]]; then + NFS_PATH=$(echo "$fstab_line" | awk '{print $1}') + MOUNT_POINT=$(echo "$fstab_line" | awk '{print $2}') + OPTIONS=$(echo "$fstab_line" | awk '{print $4}') + + SERVER=$(echo "$NFS_PATH" | cut -d: -f1) + EXPORT=$(echo "$NFS_PATH" | cut -d: -f2) + + echo -e "${TAB}${BGN}$(translate "Server:")${CL} ${BL}$SERVER${CL}" + echo -e "${TAB}${BGN}$(translate "Export:")${CL} ${BL}$EXPORT${CL}" + echo -e "${TAB}${BGN}$(translate "Host Mount Point:")${CL} ${BL}$MOUNT_POINT${CL}" + echo -e "${TAB}${BGN}$(translate "Options:")${CL} ${BL}$OPTIONS${CL}" + + if mount | grep -q "$MOUNT_POINT"; then + echo -e "${TAB}${BGN}$(translate "Status:")${CL} ${GN}$(translate "Mounted")${CL}" + else + echo -e "${TAB}${BGN}$(translate "Status:")${CL} ${RD}$(translate "Not Mounted")${CL}" + fi + echo "" + fi + done <<< "$FSTAB_NFS" + else + echo "$(translate "No permanent NFS mounts configured on host.")" + fi + + + echo -e "${BOLD}$(translate "Proxmox NFS Storage:")${CL}" + if which pvesm >/dev/null 2>&1; then + NFS_STORAGES=$(pvesm status 2>/dev/null | grep "nfs" | awk '{print $1}' || true) + if [[ -n "$NFS_STORAGES" ]]; then + while IFS= read -r storage_id; do + if [[ -n "$storage_id" ]]; then + echo -e "${TAB}${BGN}$(translate "Storage ID:")${CL} ${BL}$storage_id${CL}" + + + STORAGE_INFO=$(pvesm config "$storage_id" 2>/dev/null || true) + if [[ -n "$STORAGE_INFO" ]]; then + SERVER=$(echo "$STORAGE_INFO" | grep "server" | awk '{print $2}') + EXPORT=$(echo "$STORAGE_INFO" | grep "export" | awk '{print $2}') + CONTENT=$(echo "$STORAGE_INFO" | grep "content" | awk '{print $2}') + + [[ -n "$SERVER" ]] && echo -e "${TAB} ${BGN}$(translate "Server:")${CL} ${BL}$SERVER${CL}" + [[ -n "$EXPORT" ]] && echo -e "${TAB} ${BGN}$(translate "Export:")${CL} ${BL}$EXPORT${CL}" + [[ -n "$CONTENT" ]] && echo -e "${TAB} ${BGN}$(translate "Content:")${CL} ${BL}$CONTENT${CL}" + fi + echo "" + fi + done <<< "$NFS_STORAGES" + else + echo -e "${TAB}$(translate "No NFS storage configured in Proxmox")" + fi + else + echo -e "${TAB}$(translate "pvesm command not available")" + fi + + echo "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + +unmount_host_nfs_share() { + + MOUNTS=$(mount | grep -E "type nfs|:.*on.*nfs" | awk '{print $3}' | sort -u || true) + FSTAB_MOUNTS=$(grep -E "nfs" /etc/fstab 2>/dev/null | grep -v "^#" | awk '{print $2}' | sort -u || true) + + + ALL_MOUNTS=$(echo -e "$MOUNTS\n$FSTAB_MOUNTS" | sort -u | grep -v "^$" || true) + + if [[ -z "$ALL_MOUNTS" ]]; then + dialog --backtitle "ProxMenux" --title "$(translate "No Mounts")" --msgbox "\n$(translate "No NFS mounts found on host.")" 8 50 + return + fi + + OPTIONS=() + while IFS= read -r mount_point; do + if [[ -n "$mount_point" ]]; then + + NFS_PATH=$(mount | grep "$mount_point" | awk '{print $1}' || grep "$mount_point" /etc/fstab | awk '{print $1}' || echo "Unknown") + SERVER=$(echo "$NFS_PATH" | cut -d: -f1) + EXPORT=$(echo "$NFS_PATH" | cut -d: -f2) + OPTIONS+=("$mount_point" "$SERVER:$EXPORT") + fi + done <<< "$ALL_MOUNTS" + + SELECTED_MOUNT=$(dialog --backtitle "ProxMenux" --title "$(translate "Unmount NFS Share")" --menu "$(translate "Select mount point to unmount:")" 20 80 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) + [[ -z "$SELECTED_MOUNT" ]] && return + + + NFS_PATH=$(mount | grep "$SELECTED_MOUNT" | awk '{print $1}' || grep "$SELECTED_MOUNT" /etc/fstab | awk '{print $1}' || echo "Unknown") + SERVER=$(echo "$NFS_PATH" | cut -d: -f1) + EXPORT=$(echo "$NFS_PATH" | cut -d: -f2) + + + PROXMOX_STORAGE="" + if which pvesm >/dev/null 2>&1; then + + NFS_STORAGES=$(pvesm status 2>/dev/null | grep "nfs" | awk '{print $1}' || true) + while IFS= read -r storage_id; do + if [[ -n "$storage_id" ]]; then + STORAGE_INFO=$(pvesm config "$storage_id" 2>/dev/null || true) + STORAGE_SERVER=$(echo "$STORAGE_INFO" | grep "server" | awk '{print $2}') + STORAGE_EXPORT=$(echo "$STORAGE_INFO" | grep "export" | awk '{print $2}') + if [[ "$STORAGE_SERVER" == "$SERVER" && "$STORAGE_EXPORT" == "$EXPORT" ]]; then + PROXMOX_STORAGE="$storage_id" + break + fi + fi + done <<< "$NFS_STORAGES" + fi + + + CONFIRMATION_MSG="$(translate "Are you sure you want to unmount this NFS share?")\n\n$(translate "Mount Point:"): $SELECTED_MOUNT\n$(translate "Server:"): $SERVER\n$(translate "Export:"): $EXPORT\n\n$(translate "This will:")\n• $(translate "Unmount the NFS share")\n• $(translate "Remove from /etc/fstab")" + + if [[ -n "$PROXMOX_STORAGE" ]]; then + CONFIRMATION_MSG="$CONFIRMATION_MSG\n• $(translate "Remove Proxmox storage:"): $PROXMOX_STORAGE" + fi + + CONFIRMATION_MSG="$CONFIRMATION_MSG\n• $(translate "Remove mount point directory")" + + if whiptail --yesno "$CONFIRMATION_MSG" 16 80 --title "$(translate "Confirm Unmount")"; then + show_proxmenux_logo + msg_title "$(translate "Unmount NFS Share from Host")" + + if [[ -n "$PROXMOX_STORAGE" ]]; then + if pvesm remove "$PROXMOX_STORAGE" 2>/dev/null; then + msg_ok "$(translate "Proxmox storage removed successfully.")" + else + msg_warn "$(translate "Failed to remove Proxmox storage, continuing with unmount...")" + fi + fi + + + if mount | grep -q "$SELECTED_MOUNT"; then + if umount "$SELECTED_MOUNT"; then + msg_ok "$(translate "Successfully unmounted.")" + else + msg_warn "$(translate "Failed to unmount. Trying force unmount...")" + if umount -f "$SELECTED_MOUNT" 2>/dev/null; then + msg_ok "$(translate "Force unmount successful.")" + else + msg_error "$(translate "Failed to unmount. Mount point may be busy.")" + echo -e "${TAB}$(translate "Try closing any applications using the mount point.")" + fi + fi + fi + + + msg_info "$(translate "Removing from /etc/fstab...")" + sed -i "\|[[:space:]]$SELECTED_MOUNT[[:space:]]|d" /etc/fstab + msg_ok "$(translate "Removed from /etc/fstab.")" + + echo -e "" + msg_ok "$(translate "NFS share unmounted successfully from host!")" + + if [[ -n "$PROXMOX_STORAGE" ]]; then + echo -e "${TAB}${BGN}$(translate "Proxmox storage removed:")${CL} ${BL}$PROXMOX_STORAGE${CL}" + fi + echo -e "${TAB}${BGN}$(translate "Mount point unmounted:")${CL} ${BL}$SELECTED_MOUNT${CL}" + echo -e "${TAB}${BGN}$(translate "Removed from fstab:")${CL} ${BL}Yes${CL}" + fi + + echo -e "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + + + + +manage_proxmox_storage() { + if ! command -v pvesm >/dev/null 2>&1; then + dialog --backtitle "ProxMenux" --title "$(translate "Error")" --msgbox "\n$(translate "pvesm command not found. This should not happen on Proxmox.")" 8 60 + return + fi + + NFS_STORAGES=$(pvesm status 2>/dev/null | awk '$2 == "nfs" {print $1}') + if [[ -z "$NFS_STORAGES" ]]; then + dialog --backtitle "ProxMenux" --title "$(translate "No NFS Storage")" --msgbox "\n$(translate "No NFS storage found in Proxmox.")" 8 60 + return + fi + + + OPTIONS=() + while IFS= read -r storage_id; do + if [[ -n "$storage_id" ]]; then + STORAGE_INFO=$(pvesm config "$storage_id" 2>/dev/null || true) + SERVER=$(echo "$STORAGE_INFO" | grep "server" | awk '{print $2}') + EXPORT=$(echo "$STORAGE_INFO" | grep "export" | awk '{print $2}') + + if [[ -n "$SERVER" && -n "$EXPORT" ]]; then + OPTIONS+=("$storage_id" "$SERVER:$EXPORT") + else + OPTIONS+=("$storage_id" "$(translate "NFS Storage")") + fi + fi + done <<< "$NFS_STORAGES" + + SELECTED_STORAGE=$(dialog --backtitle "ProxMenux" --title "$(translate "Manage Proxmox NFS Storage")" --menu "$(translate "Select storage to manage:")" 20 80 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) + [[ -z "$SELECTED_STORAGE" ]] && return + + + STORAGE_INFO=$(pvesm config "$SELECTED_STORAGE" 2>/dev/null || true) + SERVER=$(echo "$STORAGE_INFO" | grep "server" | awk '{print $2}') + EXPORT=$(echo "$STORAGE_INFO" | grep "export" | awk '{print $2}') + CONTENT=$(echo "$STORAGE_INFO" | grep "content" | awk '{print $2}') + + + FSTAB_NFS=$(grep "nfs" /etc/fstab 2>/dev/null || true) + if [[ -n "$FSTAB_NFS" ]]; then + while IFS= read -r fstab_line; do + if [[ -n "$fstab_line" && ! "$fstab_line" =~ ^# ]]; then + NFS_PATH=$(echo "$fstab_line" | awk '{print $1}') + MOUNT_POINT=$(echo "$fstab_line" | awk '{print $2}') + OPTIONS=$(echo "$fstab_line" | awk '{print $4}') + + SERVER=$(echo "$NFS_PATH" | cut -d: -f1) + EXPORT=$(echo "$NFS_PATH" | cut -d: -f2) + fi + done <<< "$FSTAB_NFS" + fi + + + if whiptail --yesno "$(translate "Are you sure you want to REMOVE storage") $SELECTED_STORAGE?\n\n$(translate "Server:"): $SERVER\n$(translate "Export:"): $EXPORT\n\n$(translate "WARNING: This will permanently remove the storage from Proxmox configuration.")\n$(translate "The NFS mount on the host will NOT be affected.")" 14 80 --title "$(translate "Remove Storage")"; then + show_proxmenux_logo + msg_title "$(translate "Remove Storage")" + + if pvesm remove "$SELECTED_STORAGE" 2>/dev/null; then + msg_ok "$(translate "Storage removed successfully from Proxmox.")" + echo -e "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r + else + msg_error "$(translate "Failed to remove storage.")" + fi + fi + + +} + + + +test_host_nfs_connectivity() { + show_proxmenux_logo + msg_title "$(translate "Test NFS Connectivity on Host")" + + echo -e "$(translate "NFS Client Status on Proxmox Host:"):" + echo "==================================" + + + if which showmount >/dev/null 2>&1; then + echo "$(translate "NFS Client Tools: AVAILABLE")" + + + if systemctl is-active --quiet rpcbind 2>/dev/null; then + echo "$(translate "RPC Bind Service: RUNNING")" + else + echo "$(translate "RPC Bind Service: STOPPED")" + msg_warn "$(translate "Starting rpcbind service...")" + systemctl start rpcbind 2>/dev/null || true + fi + + echo "" + echo "$(translate "Current NFS mounts on host:")" + CURRENT_MOUNTS=$(mount | grep -E "type nfs|:.*on.*nfs" 2>/dev/null || true) + if [[ -n "$CURRENT_MOUNTS" ]]; then + echo "$CURRENT_MOUNTS" + else + echo "$(translate "No NFS mounts active on host.")" + fi + + echo "" + echo "$(translate "Testing network connectivity...")" + + + FSTAB_SERVERS=$(grep "nfs" /etc/fstab 2>/dev/null | awk '{print $1}' | cut -d: -f1 | sort -u || true) + if [[ -n "$FSTAB_SERVERS" ]]; then + while IFS= read -r server; do + if [[ -n "$server" ]]; then + echo -n "$(translate "Testing") $server: " + if ping -c 1 -W 2 "$server" >/dev/null 2>&1; then + echo -e "${GN}$(translate "Reachable")${CL}" + + + echo -n " $(translate "NFS port 2049"): " + if nc -z -w 2 "$server" 2049 2>/dev/null; then + echo -e "${GN}$(translate "Open")${CL}" + else + echo -e "${RD}$(translate "Closed")${CL}" + fi + + + echo -n " $(translate "Export list test"): " + if showmount -e "$server" >/dev/null 2>&1; then + echo -e "${GN}$(translate "Available")${CL}" + else + echo -e "${RD}$(translate "Failed")${CL}" + fi + else + echo -e "${RD}$(translate "Unreachable")${CL}" + fi + fi + done <<< "$FSTAB_SERVERS" + else + echo "$(translate "No NFS servers configured to test.")" + fi + + + echo "" + echo "$(translate "Proxmox NFS Storage Status:")" + if which pvesm >/dev/null 2>&1; then + NFS_STORAGES=$(pvesm status 2>/dev/null | grep "nfs" || true) + if [[ -n "$NFS_STORAGES" ]]; then + echo "$NFS_STORAGES" + else + echo "$(translate "No NFS storage configured in Proxmox.")" + fi + else + echo "$(translate "pvesm command not available.")" + fi + + else + echo "$(translate "NFS Client Tools: NOT AVAILABLE")" + echo "" + echo "$(translate "This is unusual for Proxmox. NFS client tools should be installed.")" + fi + + echo "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + +# === Main Menu === +while true; do + CHOICE=$(dialog --backtitle "ProxMenux" --title "$(translate "NFS Host Manager - Proxmox Host")" \ + --menu "$(translate "Choose an option:")" 22 80 14 \ + "1" "$(translate "Mount NFS Share on Host")" \ + "2" "$(translate "View Current Host NFS Mounts")" \ + "3" "$(translate "Unmount NFS Share from Host")" \ + "4" "$(translate "Remove Proxmox NFS Storage")" \ + "5" "$(translate "Test NFS Connectivity")" \ + "6" "$(translate "Exit")" \ + 3>&1 1>&2 2>&3) + + RETVAL=$? + if [[ $RETVAL -ne 0 ]]; then + exit 0 + fi + + case $CHOICE in + 1) mount_host_nfs_share ;; + 2) view_host_nfs_mounts ;; + 3) unmount_host_nfs_share ;; + 4) manage_proxmox_storage ;; + 5) test_host_nfs_connectivity ;; + 6) exit 0 ;; + *) exit 0 ;; + esac +done \ No newline at end of file diff --git a/scripts/share/samba.sh b/scripts/share/samba.sh new file mode 100644 index 0000000..31a0b28 --- /dev/null +++ b/scripts/share/samba.sh @@ -0,0 +1,588 @@ +#!/bin/bash +# ========================================================== +# ProxMenu CT - Samba Manager for Proxmox LXC +# ========================================================== +# Based on ProxMenux by MacRimi +# ========================================================== +# Description: +# This script allows you to manage Samba shares inside Proxmox CTs: +# - Create shared folders +# - View configured shares +# - Delete existing shares +# - Check Samba service status +# ========================================================== + +# Configuration +REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main" +BASE_DIR="/usr/local/share/proxmenux" +UTILS_FILE="$BASE_DIR/utils.sh" +VENV_PATH="/opt/googletrans-env" + +if [[ -f "$UTILS_FILE" ]]; then + source "$UTILS_FILE" +fi + +load_language +initialize_cache + +# === Select CT === +CT_LIST=$(pct list | awk 'NR>1 {print $1, $3}') +if [ -z "$CT_LIST" ]; then + dialog --backtitle "ProxMenux" --title "$(translate "Error")" --msgbox "$(translate "No CTs available in the system.")" 8 50 + exit 1 +fi + +CTID=$(dialog --backtitle "ProxMenux" --title "$(translate "Select CT")" --menu "$(translate "Select the CT to manage Samba:")" 20 70 12 $CT_LIST 3>&1 1>&2 2>&3) +if [ -z "$CTID" ]; then + dialog --backtitle "ProxMenux" --title "$(translate "Error")" --msgbox "$(translate "No CT was selected.")" 8 50 + exit 1 +fi + +# === Start CT if not running === +CT_STATUS=$(pct status "$CTID" | awk '{print $2}') +if [ "$CT_STATUS" != "running" ]; then + show_proxmenux_logo + msg_info "$(translate "Starting CT") $CTID..." + pct start "$CTID" + sleep 2 + if [ "$(pct status "$CTID" | awk '{print $2}')" != "running" ]; then + msg_error "$(translate "Failed to start the CT.")" + exit 1 + fi + msg_ok "$(translate "CT started successfully.")" +fi + + +select_mount_point() { + while true; do + METHOD=$(dialog --backtitle "ProxMenux" --title "$(translate "Select Folder")" \ + --menu "$(translate "How do you want to select the folder to share?")" 15 60 5 \ + "auto" "$(translate "Select from folders inside /mnt")" \ + "manual" "$(translate "Enter path manually")" \ + 3>&1 1>&2 2>&3) + + if [[ $? -ne 0 ]]; then + return 1 + fi + + case "$METHOD" in + auto) + DIRS=$(pct exec "$CTID" -- find /mnt -maxdepth 1 -mindepth 1 -type d 2>/dev/null) + if [[ -z "$DIRS" ]]; then + dialog --title "$(translate "No Folders")" --msgbox "$(translate "No folders found inside /mnt in the CT.")" 8 60 + continue + fi + + OPTIONS=() + while IFS= read -r dir; do + name=$(basename "$dir") + OPTIONS+=("$dir" "$name") + done <<< "$DIRS" + + MOUNT_POINT=$(dialog --title "$(translate "Select Folder")" \ + --menu "$(translate "Choose a folder to share:")" 20 60 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) + if [[ $? -ne 0 ]]; then + return 1 + fi + [[ -n "$MOUNT_POINT" ]] && return 0 + ;; + manual) + CT_NAME=$(pct config "$CTID" | awk -F: '/hostname/ {print $2}' | xargs) + DEFAULT_MOUNT_POINT="/mnt/${CT_NAME}_share" + MOUNT_POINT=$(whiptail --title "$(translate "Mount Point")" \ + --inputbox "$(translate "Enter the mount point for the shared folder (e.g., /mnt/myshare):")" \ + 10 70 "$DEFAULT_MOUNT_POINT" 3>&1 1>&2 2>&3) + if [[ $? -ne 0 ]]; then + return 1 + fi + clear + if [[ -z "$MOUNT_POINT" ]]; then + whiptail --title "$(translate "Error")" --msgbox "\n$(translate "No mount point was specified.")" 8 50 + continue + else + return 0 + fi + ;; + esac + done +} + + + +create_share() { + select_mount_point || return + + + if ! pct exec "$CTID" -- test -d "$MOUNT_POINT"; then + if whiptail --yesno "$(translate "The directory does not exist in the CT.")\n\n$MOUNT_POINT\n\n$(translate "Do you want to create it?")" 12 70 --title "$(translate "Create Directory")"; then + pct exec "$CTID" -- mkdir -p "$MOUNT_POINT" + msg_ok "$(translate "Directory created successfully.")" + else + msg_error "$(translate "Directory does not exist and was not created.")" + return + fi + fi + + show_proxmenux_logo + msg_title "$(translate "Create Samba server service")" + + + if pct exec "$CTID" -- dpkg -s samba &>/dev/null; then + SAMBA_INSTALLED=true + else + SAMBA_INSTALLED=false + fi + + + if [ "$SAMBA_INSTALLED" = false ]; then + echo -e "${TAB}$(translate "Installing Samba server packages inside the CT...")" + pct exec "$CTID" -- bash -c "apt-get update && apt-get install -y samba samba-common-bin acl" + + USERNAME=$(whiptail --inputbox "$(translate "Enter the Samba username:")" 10 60 "proxmenux" --title "$(translate "Samba User")" 3>&1 1>&2 2>&3) + [[ -z "$USERNAME" ]] && msg_error "$(translate "No username provided.")" && return + + while true; do + PASSWORD1=$(whiptail --passwordbox "$(translate "Enter the password for Samba user:")" 10 60 --title "$(translate "Samba Password")" 3>&1 1>&2 2>&3) + [[ -z "$PASSWORD1" ]] && msg_error "$(translate "No password provided.")" && return + PASSWORD2=$(whiptail --passwordbox "$(translate "Confirm the password:")" 10 60 --title "$(translate "Confirm Password")" 3>&1 1>&2 2>&3) + [[ -z "$PASSWORD2" ]] && msg_error "$(translate "Password confirmation is required.")" && return + + if [[ "$PASSWORD1" != "$PASSWORD2" ]]; then + whiptail --title "$(translate "Password Mismatch")" --msgbox "$(translate "The passwords do not match. Please try again.")" 10 60 + else + PASSWORD="$PASSWORD1" + break + fi + done + + if ! pct exec "$CTID" -- id "$USERNAME" &>/dev/null; then + pct exec "$CTID" -- adduser --disabled-password --gecos "" "$USERNAME" + fi + pct exec "$CTID" -- bash -c "echo -e '$PASSWORD\n$PASSWORD' | smbpasswd -a '$USERNAME'" + + msg_ok "$(translate "Samba server installed successfully.")" + else + USERNAME=$(pct exec "$CTID" -- pdbedit -L | awk -F: '{print $1}' | head -n1) + msg_ok "$(translate "Samba server is already installed.")" + echo -e "$(translate "Detected existing Samba user:") $USERNAME" + fi + + + IS_MOUNTED=$(pct exec "$CTID" -- mount | grep "$MOUNT_POINT" || true) + if [[ -n "$IS_MOUNTED" ]]; then + msg_info "$(translate "Detected a mounted directory from host. Setting up shared group...")" + + SHARE_GID=999 + GROUP_EXISTS=$(pct exec "$CTID" -- getent group sharedfiles || true) + GID_IN_USE=$(pct exec "$CTID" -- getent group "$SHARE_GID" | cut -d: -f1 || true) + + if [[ -z "$GROUP_EXISTS" ]]; then + if [[ -z "$GID_IN_USE" ]]; then + pct exec "$CTID" -- groupadd -g "$SHARE_GID" sharedfiles + msg_ok "$(translate "Group 'sharedfiles' created with GID $SHARE_GID")" + else + pct exec "$CTID" -- groupadd sharedfiles + msg_warn "$(translate "GID $SHARE_GID already in use. Group 'sharedfiles' created with dynamic GID.")" + fi + else + msg_ok "$(translate "Group 'sharedfiles' already exists inside the CT")" + fi + + if pct exec "$CTID" -- getent group sharedfiles >/dev/null; then + pct exec "$CTID" -- usermod -aG sharedfiles "$USERNAME" + pct exec "$CTID" -- chown root:sharedfiles "$MOUNT_POINT" + pct exec "$CTID" -- chmod 2775 "$MOUNT_POINT" + else + msg_error "$(translate "Group 'sharedfiles' was not created successfully. Skipping chown/usermod.")" + fi + + HAS_ACCESS=$(pct exec "$CTID" -- su -s /bin/bash -c "test -w '$MOUNT_POINT' && echo yes || echo no" "$USERNAME" 2>/dev/null) + if [ "$HAS_ACCESS" = "no" ]; then + pct exec "$CTID" -- setfacl -R -m "u:$USERNAME:rwx" "$MOUNT_POINT" + msg_warn "$(translate "ACL permissions applied to allow write access for user:") $USERNAME" + else + msg_ok "$(translate "Write access confirmed for user:") $USERNAME" + fi + else + msg_ok "$(translate "No shared mount detected. Applying standard local access.")" + pct exec "$CTID" -- chown -R "$USERNAME:$USERNAME" "$MOUNT_POINT" + pct exec "$CTID" -- chmod -R 755 "$MOUNT_POINT" + + HAS_ACCESS=$(pct exec "$CTID" -- su -s /bin/bash -c "test -w '$MOUNT_POINT' && echo yes || echo no" "$USERNAME" 2>/dev/null) + if [ "$HAS_ACCESS" = "no" ]; then + pct exec "$CTID" -- setfacl -R -m "u:$USERNAME:rwx" "$MOUNT_POINT" + msg_warn "$(translate "ACL permissions applied for local access for user:") $USERNAME" + else + msg_ok "$(translate "Write access confirmed for user:") $USERNAME" + fi + fi + + + SHARE_OPTIONS=$(whiptail --title "$(translate "Share Options")" --menu "$(translate "Select share permissions:")" 15 70 3 \ + "rw" "$(translate "Read-Write access")" \ + "ro" "$(translate "Read-Only access")" \ + "custom" "$(translate "Custom options")" 3>&1 1>&2 2>&3) + + SHARE_NAME=$(basename "$MOUNT_POINT") + + case "$SHARE_OPTIONS" in + rw) + CONFIG=$(cat <&1 1>&2 2>&3) + CONFIG=$(cat <> /etc/samba/smb.conf" + msg_ok "$(translate "Share updated successfully.")" + else + return + fi + else + msg_ok "$(translate "Adding new share to smb.conf...")" + pct exec "$CTID" -- bash -c "echo '$CONFIG' >> /etc/samba/smb.conf" + msg_ok "$(translate "Share added successfully.")" + fi + + + pct exec "$CTID" -- systemctl restart smbd.service + + + CT_IP=$(pct exec "$CTID" -- hostname -I | awk '{print $1}') + + echo -e "" + msg_ok "$(translate "Samba share created successfully!")" + echo -e "" + echo -e "${TAB}${BOLD}$(translate "Connection details:")${CL}" + echo -e "${TAB}${BGN}$(translate "Server IP:")${CL} ${BL}$CT_IP${CL}" + echo -e "${TAB}${BGN}$(translate "Share name:")${CL} ${BL}$SHARE_NAME${CL}" + echo -e "${TAB}${BGN}$(translate "Share path:")${CL} ${BL}$MOUNT_POINT${CL}" + echo -e "${TAB}${BGN}$(translate "Username:")${CL} ${BL}$USERNAME${CL}" + echo -e + + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + + + + +view_shares() { + show_proxmenux_logo + msg_title "$(translate "View Current Shares")" + + echo -e "$(translate "Current Samba shares in CT") $CTID:" + echo "==================================" + + if pct exec "$CTID" -- test -f /etc/samba/smb.conf; then + + SHARES=$(pct exec "$CTID" -- awk '/^\[.*\]/ && !/^\[global\]/ && !/^\[homes\]/ && !/^\[printers\]/ {print $0}' /etc/samba/smb.conf) + if [[ -n "$SHARES" ]]; then + + while IFS= read -r share_line; do + if [[ -n "$share_line" ]]; then + SHARE_NAME=$(echo "$share_line" | sed 's/\[//g' | sed 's/\]//g') + SHARE_PATH=$(pct exec "$CTID" -- awk "/^\[$SHARE_NAME\]/,/^$/ {if(/path =/) print \$3}" /etc/samba/smb.conf) + echo "$share_line -> $SHARE_PATH" + fi + done <<< "$SHARES" + + + CT_IP=$(pct exec "$CTID" -- hostname -I | awk '{print $1}') + USERNAME=$(pct exec "$CTID" -- pdbedit -L | awk -F: '{print $1}' | head -n1) + + echo "" + echo "==================================" + echo -e "${TAB}${BOLD}$(translate "Connection Details:")${CL}" + echo -e "${TAB}${BGN}$(translate "Server IP:")${CL} ${BL}$CT_IP${CL}" + echo -e "${TAB}${BGN}$(translate "Username:")${CL} ${BL}$USERNAME${CL}" + echo "" + + echo -e "${TAB}${BOLD}$(translate "Available Shares:")${CL}" + while IFS= read -r share_line; do + if [[ -n "$share_line" ]]; then + SHARE_NAME=$(echo "$share_line" | sed 's/\[//g' | sed 's/\]//g') + SHARE_PATH=$(pct exec "$CTID" -- awk "/^\[$SHARE_NAME\]/,/^$/ {if(/path =/) print \$3}" /etc/samba/smb.conf) + echo -e "${TAB}${BGN}$(translate "Share name:")${CL} ${BL}$SHARE_NAME${CL}" + echo -e "${TAB}${BGN}$(translate "Share path:")${CL} ${BL}$SHARE_PATH${CL}" + echo -e "${TAB}${BGN}$(translate "Windows path:")${CL} ${YW}\\\\$CT_IP\\$SHARE_NAME${CL}" + echo -e "${TAB}${BGN}$(translate "Linux/Mac path:")${CL} ${YW}smb://$CT_IP/$SHARE_NAME${CL}" + echo "" + fi + done <<< "$SHARES" + + else + echo "$(translate "No shares configured.")" + fi + else + echo "$(translate "/etc/samba/smb.conf file does not exist.")" + fi + + echo "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + + + + +delete_share() { + if ! pct exec "$CTID" -- test -f /etc/samba/smb.conf; then + dialog --backtitle "ProxMenux" --title "$(translate "Error")" --msgbox "\n$(translate "No smb.conf file found.")" 8 50 + return + fi + + + SHARES=$(pct exec "$CTID" -- awk '/^\[.*\]/ && !/^\[global\]/ && !/^\[homes\]/ && !/^\[printers\]/ {gsub(/\[|\]/, ""); print NR, $0}' /etc/samba/smb.conf) + + if [[ -z "$SHARES" ]]; then + dialog --backtitle "ProxMenux" --title "$(translate "No Shares")" --msgbox "\n$(translate "No shares found in smb.conf.")" 8 60 + return + fi + + OPTIONS=() + while read -r line; do + [[ -z "$line" ]] && continue + NUM=$(echo "$line" | awk '{print $1}') + SHARE_NAME=$(echo "$line" | awk '{print $2}') + SHARE_PATH=$(pct exec "$CTID" -- awk "/^\[$SHARE_NAME\]/,/^$/ {if(/path =/) print \$3}" /etc/samba/smb.conf) + [[ -z "$SHARE_NAME" ]] && continue + OPTIONS+=("$SHARE_NAME" "$SHARE_NAME -> $SHARE_PATH") + done <<< "$SHARES" + + SELECTED_SHARE=$(dialog --backtitle "ProxMenux" --title "$(translate "Delete Share")" --menu "$(translate "Select a share to delete:")" 20 70 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) + [ -z "$SELECTED_SHARE" ] && return + + SHARE_PATH=$(pct exec "$CTID" -- awk "/^\[$SELECTED_SHARE\]/,/^$/ {if(/path =/) print \$3}" /etc/samba/smb.conf) + if whiptail --yesno "$(translate "Are you sure you want to delete this share?")\n\n$(translate "Share name:"): $SELECTED_SHARE\n$(translate "Share path:"): $SHARE_PATH" 12 70 --title "$(translate "Confirm Deletion")"; then + show_proxmenux_logo + msg_title "$(translate "Delete Share")" + + + pct exec "$CTID" -- sed -i "/^\[$SELECTED_SHARE\]/,/^$/d" /etc/samba/smb.conf + pct exec "$CTID" -- systemctl restart smbd.service + msg_ok "$(translate "Share deleted and Samba service restarted.")" + fi + + echo -e + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + +check_samba_status() { + show_proxmenux_logo + msg_title "$(translate "Check Samba Status")" + + echo -e "$(translate "Samba Service Status in CT") $CTID:" + echo "==================================" + + + if pct exec "$CTID" -- dpkg -s samba &>/dev/null; then + echo "$(translate "Samba Server: INSTALLED")" + + + if pct exec "$CTID" -- systemctl is-active --quiet smbd; then + echo "$(translate "Samba Service: RUNNING")" + else + echo "$(translate "Samba Service: STOPPED")" + fi + + + if pct exec "$CTID" -- systemctl is-active --quiet nmbd; then + echo "$(translate "NetBIOS Service: RUNNING")" + else + echo "$(translate "NetBIOS Service: STOPPED")" + fi + + + echo "" + echo "$(translate "Listening ports:")" + pct exec "$CTID" -- ss -tlnp | grep -E ':(139|445)' || echo "$(translate "No Samba ports found")" + + + echo "" + echo "$(translate "Samba users:")" + pct exec "$CTID" -- pdbedit -L 2>/dev/null || echo "$(translate "No Samba users found")" + + else + echo "$(translate "Samba Server: NOT INSTALLED")" + fi + + echo "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + + + +uninstall_samba() { + + if ! pct exec "$CTID" -- dpkg -s samba &>/dev/null; then + dialog --backtitle "ProxMenux" --title "$(translate "Samba Not Installed")" --msgbox "\n$(translate "Samba server is not installed in this CT.")" 8 60 + return + fi + + if ! whiptail --title "$(translate "Uninstall Samba Server")" \ + --yesno "$(translate "WARNING: This will completely remove Samba server from the CT.")\n\n$(translate "This action will:")\n$(translate "• Stop all Samba services")\n$(translate "• Remove all shares")\n$(translate "• Remove all Samba users")\n$(translate "• Uninstall Samba packages")\n$(translate "• Remove Samba groups")\n\n$(translate "Are you sure you want to continue?")" \ + 18 70; then + return + fi + + + show_proxmenux_logo + msg_title "$(translate "Uninstall Samba Server")" + + + msg_info "$(translate "Stopping Samba services...")" + pct exec "$CTID" -- systemctl stop smbd 2>/dev/null || true + pct exec "$CTID" -- systemctl stop nmbd 2>/dev/null || true + pct exec "$CTID" -- systemctl disable smbd 2>/dev/null || true + pct exec "$CTID" -- systemctl disable nmbd 2>/dev/null || true + msg_ok "$(translate "Samba services stopped and disabled.")" + + + if pct exec "$CTID" -- test -f /etc/samba/smb.conf; then + pct exec "$CTID" -- cp /etc/samba/smb.conf /etc/samba/smb.conf.backup.$(date +%Y%m%d_%H%M%S) + msg_ok "$(translate "Samba configuration backed up.")" + fi + + + SAMBA_USERS=$(pct exec "$CTID" -- pdbedit -L 2>/dev/null | awk -F: '{print $1}' || true) + if [[ -n "$SAMBA_USERS" ]]; then + while IFS= read -r user; do + if [[ -n "$user" ]]; then + pct exec "$CTID" -- smbpasswd -x "$user" 2>/dev/null || true + fi + done <<< "$SAMBA_USERS" + msg_ok "$(translate "Samba users removed.")" + fi + + + + pct exec "$CTID" -- apt-get remove --purge -y samba samba-common-bin samba-common 2>/dev/null || true + pct exec "$CTID" -- apt-get autoremove -y 2>/dev/null || true + msg_ok "$(translate "Samba packages removed.")" + + + + if pct exec "$CTID" -- getent group sharedfiles >/dev/null 2>&1; then + + GROUP_USERS=$(pct exec "$CTID" -- getent group sharedfiles | cut -d: -f4) + if [[ -z "$GROUP_USERS" ]]; then + pct exec "$CTID" -- groupdel sharedfiles 2>/dev/null || true + msg_ok "$(translate "Samba group removed.")" + else + msg_warn "$(translate "Samba group kept (has users assigned).")" + fi + fi + + msg_info "$(translate "Cleaning up Samba directories...")" + pct exec "$CTID" -- pkill -f smbd 2>/dev/null || true + pct exec "$CTID" -- pkill -f nmbd 2>/dev/null || true + + pct exec "$CTID" -- rm -rf /var/lib/samba 2>/dev/null || true + pct exec "$CTID" -- rm -rf /var/cache/samba 2>/dev/null || true + + sleep 2 + msg_ok "$(translate "Samba server has been completely uninstalled!")" + echo -e "" + echo -e "${TAB}${BOLD}$(translate "Uninstallation Summary:")${CL}" + echo -e "${TAB}${BGN}$(translate "Services:")${CL} ${BL}$(translate "Stopped and disabled")${CL}" + echo -e "${TAB}${BGN}$(translate "Packages:")${CL} ${BL}$(translate "Removed")${CL}" + echo -e "${TAB}${BGN}$(translate "Users:")${CL} ${BL}$(translate "Removed")${CL}" + echo -e "${TAB}${BGN}$(translate "Configuration:")${CL} ${BL}$(translate "Backed up and cleared")${CL}" + echo -e "${TAB}${BGN}$(translate "Groups:")${CL} ${BL}$(translate "Cleaned up")${CL}" + echo -e + + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + + +# === Main Menu === +while true; do + CHOICE=$(dialog --backtitle "ProxMenux" --title "$(translate "Samba Manager - CT") $CTID" --menu "$(translate "Choose an option:")" 20 70 12 \ + "1" "$(translate "Create Samba server service")" \ + "2" "$(translate "View Current Shares")" \ + "3" "$(translate "Delete Share")" \ + "4" "$(translate "Check Samba Status")" \ + "5" "$(translate "Uninstall Samba Server")" \ + "6" "$(translate "Exit")" 3>&1 1>&2 2>&3) + + case $CHOICE in + 1) create_share ;; + 2) view_shares ;; + 3) delete_share ;; + 4) check_samba_status ;; + 5) uninstall_samba ;; + 6) exit 0 ;; + *) exit 0 ;; + esac +done \ No newline at end of file diff --git a/scripts/share/samba_client.sh b/scripts/share/samba_client.sh new file mode 100644 index 0000000..87c4e86 --- /dev/null +++ b/scripts/share/samba_client.sh @@ -0,0 +1,1105 @@ +#!/bin/bash +# ========================================================== +# ProxMenu CT - Samba Client Manager for Proxmox LXC +# ========================================================== +# Based on ProxMenux by MacRimi +# ========================================================== +# Description: +# This script allows you to manage Samba/CIFS client mounts inside Proxmox CTs: +# - Mount Samba/CIFS shares (temporary and permanent) +# - View current mounts +# - Unmount and remove Samba shares +# - Auto-discover Samba servers +# - Manage credentials securely +# ========================================================== + +# Configuration +REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main" +BASE_DIR="/usr/local/share/proxmenux" +UTILS_FILE="$BASE_DIR/utils.sh" +VENV_PATH="/opt/googletrans-env" +CREDENTIALS_DIR="/etc/samba/credentials" + +if [[ -f "$UTILS_FILE" ]]; then + source "$UTILS_FILE" +fi + +load_language +initialize_cache + +# === Select CT === +CT_LIST=$(pct list | awk 'NR>1 {print $1, $3}') +if [ -z "$CT_LIST" ]; then + dialog --title "$(translate "Error")" --msgbox "$(translate "No CTs available in the system.")" 8 50 + exit 1 +fi + +CTID=$(dialog --title "$(translate "Select CT")" --menu "$(translate "Select the CT to manage Samba client:")" 20 70 12 $CT_LIST 3>&1 1>&2 2>&3) +if [ -z "$CTID" ]; then + dialog --title "$(translate "Error")" --msgbox "$(translate "No CT was selected.")" 8 50 + exit 1 +fi + + + +# === Start CT if not running === +CT_STATUS=$(pct status "$CTID" | awk '{print $2}') +if [ "$CT_STATUS" != "running" ]; then + show_proxmenux_logo + msg_info "$(translate "Starting CT") $CTID..." + pct start "$CTID" + sleep 2 + if [ "$(pct status "$CTID" | awk '{print $2}')" != "running" ]; then + msg_error "$(translate "Failed to start the CT.")" + exit 1 + fi + msg_ok "$(translate "CT started successfully.")" +fi + +install_samba_client() { + + if pct exec "$CTID" -- dpkg -s cifs-utils &>/dev/null && pct exec "$CTID" -- dpkg -s smbclient &>/dev/null; then + pct exec "$CTID" -- mkdir -p "$CREDENTIALS_DIR" + pct exec "$CTID" -- chmod 700 "$CREDENTIALS_DIR" + return 0 + fi + + show_proxmenux_logo + msg_title "$(translate "Installing Samba Client")" + msg_info "$(translate "Installing Samba/CIFS client packages...")" + + if ! pct exec "$CTID" -- apt-get update &>/dev/null; then + msg_error "$(translate "Failed to update package list.")" + return 1 + fi + + if ! pct exec "$CTID" -- apt-get install -y cifs-utils smbclient &>/dev/null; then + msg_error "$(translate "Failed to install Samba client packages.")" + return 1 + fi + + if ! pct exec "$CTID" -- which smbclient >/dev/null 2>&1; then + msg_error "$(translate "smbclient command not found after installation.")" + return 1 + fi + if ! pct exec "$CTID" -- which mount.cifs >/dev/null 2>&1; then + msg_error "$(translate "mount.cifs command not found after installation.")" + return 1 + fi + + pct exec "$CTID" -- mkdir -p "$CREDENTIALS_DIR" + pct exec "$CTID" -- chmod 700 "$CREDENTIALS_DIR" + + msg_ok "$(translate "Samba/CIFS client installed successfully.")" + return 0 +} + + +discover_samba_servers() { + show_proxmenux_logo + msg_title "$(translate "Samba LXC Manager")" + msg_info "$(translate "Scanning network for Samba servers...")" + + + HOST_IP=$(hostname -I | awk '{print $1}') + NETWORK=$(echo "$HOST_IP" | cut -d. -f1-3).0/24 + + + for pkg in nmap samba-common-bin; do + if ! which ${pkg%%-*} >/dev/null 2>&1; then + apt-get install -y "$pkg" &>/dev/null + fi + done + + SERVERS=$(nmap -p 139,445 --open "$NETWORK" 2>/dev/null | grep -B 4 -E "(139|445)/tcp open" | grep "Nmap scan report" | awk '{print $5}' | sort -u || true) + if [[ -z "$SERVERS" ]]; then + cleanup + whiptail --title "$(translate "No Servers Found")" --msgbox "$(translate "No Samba servers found on the network.")\n\n$(translate "You can add servers manually.")" 10 60 + return 1 + fi + + SERVER_LINES=() + while IFS= read -r server; do + [[ -z "$server" ]] && continue + + NB_NAME=$(nmblookup -A "$server" 2>/dev/null | awk '/<00> -.*B / {print $1; exit}') + + if [[ -z "$NB_NAME" || "$NB_NAME" == "$server" || "$NB_NAME" == "address" || "$NB_NAME" == "-" ]]; then + NB_NAME="Unknown" + fi + + SERVER_LINES+=("$server|$NB_NAME ($server)") + done <<< "$SERVERS" + + IFS=$'\n' SORTED=($(printf "%s\n" "${SERVER_LINES[@]}" | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n)) + + OPTIONS=() + declare -A SERVER_IPS + i=1 + for entry in "${SORTED[@]}"; do + server="${entry%%|*}" + label="${entry#*|}" + OPTIONS+=("$i" "$label") + SERVER_IPS["$i"]="$server" + ((i++)) + done + + if [[ ${#OPTIONS[@]} -eq 0 ]]; then + cleanup + whiptail --title "$(translate "No Valid Servers")" --msgbox "$(translate "No accessible Samba servers found.")" 8 50 + return 1 + fi + + msg_ok "$(translate "Samba servers detected")" + CHOICE=$(dialog --title "$(translate "Select Samba Server")" \ + --menu "$(translate "Choose a Samba server:")" 20 80 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) + + if [[ -n "$CHOICE" ]]; then + SAMBA_SERVER="${SERVER_IPS[$CHOICE]}" + + return 0 + else + + return 1 + fi + +} + +select_samba_server() { + METHOD=$(dialog --title "$(translate "Samba Server Selection")" --menu "$(translate "How do you want to select the Samba server?")" 15 70 3 \ + "auto" "$(translate "Auto-discover servers on network")" \ + "manual" "$(translate "Enter server IP")" \ + "recent" "$(translate "Select from recent servers")" 3>&1 1>&2 2>&3) + + case "$METHOD" in + auto) + discover_samba_servers || return 1 + ;; + manual) + SAMBA_SERVER=$(whiptail --inputbox "$(translate "Enter Samba server IP:")" 10 60 --title "$(translate "Samba Server")" 3>&1 1>&2 2>&3) + [[ -z "$SAMBA_SERVER" ]] && return 1 + ;; + recent) + + RECENT=$(pct exec "$CTID" -- grep "cifs" /etc/fstab 2>/dev/null | awk '{print $1}' | cut -d/ -f3 | sort -u || true) + if [[ -z "$RECENT" ]]; then + whiptail --title "$(translate "No Recent Servers")" --msgbox "$(translate "No recent Samba servers found.")" 8 50 + return 1 + fi + + OPTIONS=() + while IFS= read -r server; do + [[ -n "$server" ]] && OPTIONS+=("$server" "$(translate "Recent Samba server")") + done <<< "$RECENT" + + SAMBA_SERVER=$(whiptail --title "$(translate "Recent Samba Servers")" --menu "$(translate "Choose a recent server:")" 20 70 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) + [[ -z "$SAMBA_SERVER" ]] && return 1 + ;; + *) + return 1 + ;; + esac + return 0 +} + + + + + + +validate_guest_access() { + local server="$1" + + show_proxmenux_logo + msg_info "$(translate "Testing comprehensive guest access to server") $server..." + + GUEST_LIST_OUTPUT=$(smbclient -L "$server" -N 2>&1) + GUEST_LIST_RESULT=$? + + if [[ $GUEST_LIST_RESULT -ne 0 ]]; then + cleanup + if echo "$GUEST_LIST_OUTPUT" | grep -qi "access denied\|logon failure"; then + whiptail --title "$(translate "Guest Access Denied")" \ + --msgbox "$(translate "Guest access is not allowed on this server.")\n\n$(translate "You need to use username and password authentication.")" \ + 10 70 + else + whiptail --title "$(translate "Guest Access Error")" \ + --msgbox "$(translate "Guest access failed.")\n\n$(translate "Error details:")\n$(echo "$GUEST_LIST_OUTPUT" | head -3)" \ + 12 70 + fi + return 1 + fi + sleep 2 + msg_ok "$(translate "Guest share listing successful")" + + GUEST_SHARES=$(echo "$GUEST_LIST_OUTPUT" | awk '/Disk/ && !/IPC\$/ && !/ADMIN\$/ && !/print\$/ {print $1}' | grep -v "^$") + if [[ -z "$GUEST_SHARES" ]]; then + whiptail --title "$(translate "No Guest Shares")" \ + --msgbox "$(translate "Guest access works for listing, but no shares are available.")\n\n$(translate "The server may require authentication for actual share access.")" \ + 10 70 + return 1 + fi + + msg_ok "$(translate "Found guest-accessible shares:") $(echo "$GUEST_SHARES" | wc -l)" + + msg_info "$(translate "Step 2: Testing actual share access with guest...")" + ACCESSIBLE_SHARES="" + FAILED_SHARES="" + sleep 1 + while IFS= read -r share; do + if [[ -n "$share" ]]; then + + SHARE_TEST_OUTPUT=$(smbclient "//$server/$share" -N -c "ls" 2>&1) + SHARE_TEST_RESULT=$? + + if [[ $SHARE_TEST_RESULT -eq 0 ]]; then + echo -e + msg_ok "$(translate "Guest access confirmed for share:") $share" + echo -e + ACCESSIBLE_SHARES="$ACCESSIBLE_SHARES$share\n" + else + msg_error "$(translate "Guest access denied for share:") $share" + FAILED_SHARES="$FAILED_SHARES$share\n" + + if echo "$SHARE_TEST_OUTPUT" | grep -qi "access denied\|logon failure\|authentication"; then + msg_warn " $(translate "Reason: Authentication required")" + elif echo "$SHARE_TEST_OUTPUT" | grep -qi "permission denied"; then + msg_warn " $(translate "Reason: Permission denied")" + else + msg_warn " $(translate "Reason: Access denied")" + fi + fi + fi + done <<< "$GUEST_SHARES" + + + ACCESSIBLE_COUNT=$(echo -e "$ACCESSIBLE_SHARES" | grep -v "^$" | wc -l) + FAILED_COUNT=$(echo -e "$FAILED_SHARES" | grep -v "^$" | wc -l) + + echo -e "" + msg_info2 "$(translate "Guest Access Validation Results:")" + echo -e "${TAB}${BGN}$(translate "Shares found:")${CL} ${BL}$(echo "$GUEST_SHARES" | wc -l)${CL}" + echo -e "${TAB}${BGN}$(translate "Guest accessible:")${CL} ${GN}$ACCESSIBLE_COUNT${CL}" + echo -e "${TAB}${BGN}$(translate "Authentication required:")${CL} ${YW}$FAILED_COUNT${CL}" + + if [[ $ACCESSIBLE_COUNT -gt 0 ]]; then + msg_ok "$(translate "Guest access validated successfully!")" + echo -e "" + echo -e "${TAB}${BOLD}$(translate "Available shares for guest access:")${CL}" + while IFS= read -r share; do + [[ -n "$share" ]] && echo -e "${TAB}• ${BL}$share${CL}" + done <<< "$(echo -e "$ACCESSIBLE_SHARES" | grep -v "^$")" + echo -e + msg_success "$(translate "Press Enter to continue...")" + read -r + clear + + VALIDATED_GUEST_SHARES="$ACCESSIBLE_SHARES" + return 0 + else + msg_success "$(translate "Press Enter to continue...")" + read -r + whiptail --title "$(translate "Guest Access Failed")" \ + --msgbox "$(translate "While the server allows guest listing, no shares are actually accessible without authentication.")\n\n$(translate "You need to use username and password authentication.")" \ + 12 70 + clear + return 1 + fi +} + + + + + + +get_samba_credentials() { + while true; do + CHOICE=$(whiptail --title "$(translate "Samba Credentials")" \ + --menu "$(translate "Select authentication mode:")" 13 60 2 \ + "1" "$(translate "Configure with username and password")" \ + "2" "$(translate "Configure as guest (no authentication)")" \ + 3>&1 1>&2 2>&3) + + if [[ $? -ne 0 ]]; then + return 1 + fi + + case "$CHOICE" in + 1) + + while true; do + USERNAME=$(whiptail --inputbox "$(translate "Enter username for Samba server:")" 10 60 --title "$(translate "Username")" 3>&1 1>&2 2>&3) + if [[ $? -ne 0 ]]; then + break + fi + if [[ -z "$USERNAME" ]]; then + whiptail --title "$(translate "Error")" --msgbox "$(translate "Username cannot be empty.")" 8 50 + continue + fi + + while true; do + PASSWORD=$(whiptail --passwordbox "$(translate "Enter password for") $USERNAME:" 10 60 --title "$(translate "Password")" 3>&1 1>&2 2>&3) + if [[ $? -ne 0 ]]; then + break + fi + if [[ -z "$PASSWORD" ]]; then + whiptail --title "$(translate "Error")" --msgbox "$(translate "Password cannot be empty.")" 8 50 + continue + fi + + PASSWORD_CONFIRM=$(whiptail --passwordbox "$(translate "Confirm password for") $USERNAME:" 10 60 --title "$(translate "Confirm Password")" 3>&1 1>&2 2>&3) + if [[ $? -ne 0 ]]; then + continue + fi + if [[ -z "$PASSWORD_CONFIRM" ]]; then + whiptail --title "$(translate "Error")" --msgbox "$(translate "Password confirmation cannot be empty.")" 8 50 + continue + fi + + if [[ "$PASSWORD" == "$PASSWORD_CONFIRM" ]]; then + + show_proxmenux_logo + msg_info "$(translate "Validating credentials with server") $SAMBA_SERVER..." + + + TEMP_CRED="/tmp/validate_cred_$$" + cat > "$TEMP_CRED" << EOF +username=$USERNAME +password=$PASSWORD +EOF + chmod 600 "$TEMP_CRED" + + + SHARES_OUTPUT=$(smbclient -L "$SAMBA_SERVER" -A "$TEMP_CRED" 2>&1) + SHARES_RESULT=$? + + if [[ $SHARES_RESULT -eq 0 ]]; then + + FIRST_SHARE=$(echo "$SHARES_OUTPUT" | awk '/Disk/ && !/IPC\$/ && !/ADMIN\$/ && !/print\$/ {print $1; exit}') + + if [[ -n "$FIRST_SHARE" ]]; then + + SHARE_TEST_OUTPUT=$(smbclient "//$SAMBA_SERVER/$FIRST_SHARE" -A "$TEMP_CRED" -c "ls" 2>&1) + SHARE_TEST_RESULT=$? + + rm -f "$TEMP_CRED" + + if [[ $SHARE_TEST_RESULT -eq 0 ]]; then + + cleanup + if echo "$SHARE_TEST_OUTPUT" | grep -qi "guest"; then + whiptail --title "$(translate "Authentication Error")" \ + --msgbox "$(translate "The server connected you as guest instead of the specified user.")\n\n$(translate "This means the credentials are incorrect.")\n\n$(translate "Please check:")\n• $(translate "Username is correct")\n• $(translate "Password is correct")\n• $(translate "User account exists on server")" \ + 14 70 + else + msg_ok "$(translate "Credentials validated successfully")" + USE_GUEST=false + return 0 + fi + else + + cleanup + if echo "$SHARE_TEST_OUTPUT" | grep -qi "access denied\|logon failure\|authentication\|NT_STATUS_LOGON_FAILURE"; then + whiptail --title "$(translate "Authentication Error")" \ + --msgbox "$(translate "Invalid username or password.")\n\n$(translate "Error details:")\n$(echo "$SHARE_TEST_OUTPUT" | head -2)\n\n$(translate "Please check:")\n• $(translate "Username is correct")\n• $(translate "Password is correct")\n• $(translate "User account exists on server")" \ + 16 70 + elif echo "$SHARE_TEST_OUTPUT" | grep -qi "connection refused\|network unreachable"; then + whiptail --title "$(translate "Network Error")" \ + --msgbox "$(translate "Cannot connect to server") $SAMBA_SERVER\n\n$(translate "Please check network connectivity.")" \ + 10 60 + return 1 + else + whiptail --title "$(translate "Share Access Error")" \ + --msgbox "$(translate "Failed to access share with provided credentials.")\n\n$(translate "Error details:")\n$(echo "$SHARE_TEST_OUTPUT" | head -3)" \ + 12 70 + fi + fi + else + + cleanup + whiptail --title "$(translate "No Shares Available")" \ + --msgbox "$(translate "Cannot validate credentials - no shares available for testing.")\n\n$(translate "The server may not have accessible shares.")" \ + 10 70 + fi + else + + rm -f "$TEMP_CRED" + + + if echo "$SHARES_OUTPUT" | grep -qi "access denied\|logon failure\|authentication\|NT_STATUS_LOGON_FAILURE"; then + cleanup + whiptail --title "$(translate "Authentication Error")" \ + --msgbox "$(translate "Invalid username or password.")\n\n$(translate "Please check:")\n• $(translate "Username is correct")\n• $(translate "Password is correct")\n• $(translate "User account exists on server")\n• $(translate "Account is not locked")" \ + 12 70 + elif echo "$SHARES_OUTPUT" | grep -qi "connection refused\|network unreachable"; then + cleanup + whiptail --title "$(translate "Network Error")" \ + --msgbox "$(translate "Cannot connect to server") $SAMBA_SERVER\n\n$(translate "Please check network connectivity.")" \ + 10 60 + return 1 + else + cleanup + whiptail --title "$(translate "Connection Error")" \ + --msgbox "$(translate "Failed to connect to server.")\n\n$(translate "Error details:")\n$(echo "$SHARES_OUTPUT" | head -3)" \ + 12 70 + fi + fi + + break + else + cleanup + whiptail --title "$(translate "Password Mismatch")" \ + --msgbox "$(translate "Passwords do not match. Please try again.")" \ + 8 50 + + fi + done + + if [[ $? -ne 0 ]]; then + break + fi + done + ;; + 2) + + if validate_guest_access "$SAMBA_SERVER"; then + USE_GUEST=true + return 0 + fi + ;; + *) + return 1 + ;; + esac + + + if ! whiptail --yesno "$(translate "Authentication failed.")\n\n$(translate "Do you want to try different credentials or authentication method?")" 10 70 --title "$(translate "Try Again")"; then + return 1 + fi + + done +} + + + + + + +select_samba_share() { + if ! which smbclient >/dev/null 2>&1; then + whiptail --title "$(translate "SMB Client Error")" \ + --msgbox "$(translate "smbclient command is not working properly.")\n\n$(translate "Please check the installation.")" \ + 10 60 + return 1 + fi + + + if [[ "$USE_GUEST" == "true" ]]; then + + if [[ -n "$VALIDATED_GUEST_SHARES" ]]; then + SHARES=$(echo -e "$VALIDATED_GUEST_SHARES" | grep -v "^$") + else + + SHARES_OUTPUT=$(smbclient -L "$SAMBA_SERVER" -N 2>&1) + SHARES_RESULT=$? + if [[ $SHARES_RESULT -eq 0 ]]; then + SHARES=$(echo "$SHARES_OUTPUT" | awk '/Disk/ && !/IPC\$/ && !/ADMIN\$/ && !/print\$/ {print $1}' | grep -v "^$") + else + show_proxmenux_logo + msg_error "$(translate "Failed to get shares")" + echo -e + msg_success "$(translate "Press Enter to continue...")" + read -r + return 1 + fi + fi + else + + TEMP_CRED="/tmp/temp_smb_cred_$$" + cat > "$TEMP_CRED" << EOF +username=$USERNAME +password=$PASSWORD +EOF + chmod 600 "$TEMP_CRED" + + SHARES_OUTPUT=$(smbclient -L "$SAMBA_SERVER" -A "$TEMP_CRED" 2>&1) + SHARES_RESULT=$? + + rm -f "$TEMP_CRED" + + if [[ $SHARES_RESULT -ne 0 ]]; then + whiptail --title "$(translate "SMB Error")" \ + --msgbox "$(translate "Failed to get shares from") $SAMBA_SERVER\n\n$(translate "This is unexpected since credentials were validated.")" \ + 12 80 + return 1 + fi + + SHARES=$(echo "$SHARES_OUTPUT" | awk '/Disk/ && !/IPC\$/ && !/ADMIN\$/ && !/print\$/ {print $1}' | grep -v "^$") + fi + + if [[ -z "$SHARES" ]]; then + whiptail --title "$(translate "No Shares Found")" \ + --msgbox "$(translate "No shares found on server") $SAMBA_SERVER\n\n$(translate "You can enter the share name manually.")" \ + 12 70 + + SAMBA_SHARE=$(whiptail --inputbox "$(translate "Enter Samba share name:")" 10 60 --title "$(translate "Share Name")" 3>&1 1>&2 2>&3) + [[ -z "$SAMBA_SHARE" ]] && return 1 + return 0 + fi + + + OPTIONS=() + while IFS= read -r share; do + if [[ -n "$share" && "$share" != "IPC$" && "$share" != "ADMIN$" && "$share" != "print$" ]]; then + + if [[ "$USE_GUEST" == "true" ]]; then + if echo -e "$VALIDATED_GUEST_SHARES" | grep -q "^$share$"; then + OPTIONS+=("$share" "$(translate "Guest accessible share")") + fi + else + + OPTIONS+=("$share" "$(translate "Samba share")") + fi + fi + done <<< "$SHARES" + + if [[ ${#OPTIONS[@]} -eq 0 ]]; then + whiptail --title "$(translate "No Available Shares")" \ + --msgbox "$(translate "No accessible shares found.")\n\n$(translate "You can enter the share name manually.")" \ + 10 70 + + SAMBA_SHARE=$(whiptail --inputbox "$(translate "Enter Samba share name:")" 10 60 --title "$(translate "Share Name")" 3>&1 1>&2 2>&3) + [[ -z "$SAMBA_SHARE" ]] && return 1 + return 0 + fi + + SAMBA_SHARE=$(whiptail --title "$(translate "Select Samba Share")" --menu "$(translate "Choose a share to mount:")" 20 70 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) + [[ -n "$SAMBA_SHARE" ]] && return 0 || return 1 +} + + + + + + + + + + +select_mount_point() { + while true; do + METHOD=$(whiptail --title "$(translate "Select Mount Point")" --menu "$(translate "Where do you want to mount the Samba share?")" 15 70 3 \ + "existing" "$(translate "Select from existing folders in /mnt")" \ + "new" "$(translate "Create new folder in /mnt")" \ + "custom" "$(translate "Enter custom path")" 3>&1 1>&2 2>&3) + + case "$METHOD" in + existing) + + DIRS=$(pct exec "$CTID" -- find /mnt -maxdepth 1 -mindepth 1 -type d 2>/dev/null) + if [[ -z "$DIRS" ]]; then + whiptail --title "$(translate "No Folders")" --msgbox "$(translate "No folders found in /mnt. Please create a new folder.")" 8 60 + continue + fi + + OPTIONS=() + while IFS= read -r dir; do + if [[ -n "$dir" ]]; then + name=$(basename "$dir") + + if pct exec "$CTID" -- [ "$(ls -A "$dir" 2>/dev/null | wc -l)" -eq 0 ]; then + status="$(translate "Empty")" + else + status="$(translate "Contains files")" + fi + OPTIONS+=("$dir" "$name ($status)") + fi + done <<< "$DIRS" + + MOUNT_POINT=$(whiptail --title "$(translate "Select Existing Folder")" --menu "$(translate "Choose a folder to mount the share:")" 20 80 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) + + if [[ -n "$MOUNT_POINT" ]]; then + + if pct exec "$CTID" -- [ "$(ls -A "$MOUNT_POINT" 2>/dev/null | wc -l)" -gt 0 ]; then + FILE_COUNT=$(pct exec "$CTID" -- ls -A "$MOUNT_POINT" 2>/dev/null | wc -l) + if ! whiptail --yesno "$(translate "WARNING: The selected directory is not empty!")\n\n$(translate "Directory:"): $MOUNT_POINT\n$(translate "Contains:"): $FILE_COUNT $(translate "files/folders")\n\n$(translate "Mounting here will hide existing files until unmounted.")\n\n$(translate "Do you want to continue?")" 14 70 --title "$(translate "Directory Not Empty")"; then + continue + fi + fi + return 0 + fi + ;; + new) + FOLDER_NAME=$(whiptail --inputbox "$(translate "Enter new folder name:")" 10 60 "${SAMBA_SHARE}" --title "$(translate "New Folder in /mnt")" 3>&1 1>&2 2>&3) + if [[ -n "$FOLDER_NAME" ]]; then + MOUNT_POINT="/mnt/$FOLDER_NAME" + return 0 + fi + ;; + custom) + MOUNT_POINT=$(whiptail --inputbox "$(translate "Enter full path for mount point:")" 10 70 "/mnt/samba_share" --title "$(translate "Custom Path")" 3>&1 1>&2 2>&3) + if [[ -n "$MOUNT_POINT" ]]; then + return 0 + fi + ;; + *) + return 1 + ;; + esac + done +} + +configure_mount_options() { + MOUNT_TYPE=$(whiptail --title "$(translate "Mount Options")" --menu "$(translate "Select mount configuration:")" 15 70 4 \ + "default" "$(translate "Default options")" \ + "readonly" "$(translate "Read-only mount")" \ + "performance" "$(translate "Performance optimized")" \ + "custom" "$(translate "Custom options")" 3>&1 1>&2 2>&3) + + case "$MOUNT_TYPE" in + default) + MOUNT_OPTIONS="rw,file_mode=0664,dir_mode=0775,iocharset=utf8" + ;; + readonly) + MOUNT_OPTIONS="ro,file_mode=0444,dir_mode=0555,iocharset=utf8" + ;; + performance) + MOUNT_OPTIONS="rw,file_mode=0664,dir_mode=0775,iocharset=utf8,cache=strict,rsize=1048576,wsize=1048576" + ;; + custom) + MOUNT_OPTIONS=$(whiptail --inputbox "$(translate "Enter custom mount options:")" 10 70 "rw,file_mode=0664,dir_mode=0775" --title "$(translate "Custom Options")" 3>&1 1>&2 2>&3) + [[ -z "$MOUNT_OPTIONS" ]] && MOUNT_OPTIONS="rw,file_mode=0664,dir_mode=0775" + ;; + *) + MOUNT_OPTIONS="rw,file_mode=0664,dir_mode=0775,iocharset=utf8" + ;; + esac + + + if whiptail --yesno "$(translate "Do you want to make this mount permanent?")\n\n$(translate "This will add the mount to /etc/fstab so it persists after reboot.")" 10 70 --title "$(translate "Permanent Mount")"; then + PERMANENT_MOUNT=true + else + PERMANENT_MOUNT=false + fi +} + +create_credentials_file() { + if [[ "$USE_GUEST" == "true" ]]; then + return 0 + fi + + + CRED_FILE="$CREDENTIALS_DIR/${SAMBA_SERVER}_${SAMBA_SHARE}.cred" + + + pct exec "$CTID" -- bash -c "cat > '$CRED_FILE' << EOF +username=$USERNAME +password=$PASSWORD +EOF" + + pct exec "$CTID" -- chmod 600 "$CRED_FILE" + msg_ok "$(translate "Credentials file created securely.")" +} + +validate_share_exists() { + local server="$1" + local share="$2" + local use_guest="$3" + local username="$4" + local password="$5" + + + if [[ "$use_guest" == "true" ]]; then + VALIDATION_OUTPUT=$(pct exec "$CTID" -- smbclient -L "$server" -N 2>/dev/null | grep "^[[:space:]]*$share[[:space:]]") + else + TEMP_CRED="/tmp/validate_cred_$$" + pct exec "$CTID" -- bash -c "cat > $TEMP_CRED << EOF +username=$username +password=$password +EOF" + pct exec "$CTID" -- chmod 600 "$TEMP_CRED" + + VALIDATION_OUTPUT=$(pct exec "$CTID" -- smbclient -L "$server" -A "$TEMP_CRED" 2>/dev/null | grep "^[[:space:]]*$share[[:space:]]") + pct exec "$CTID" -- rm -f "$TEMP_CRED" + fi + + if [[ -n "$VALIDATION_OUTPUT" ]]; then + return 0 + else + show_proxmenux_logo + msg_error "$(translate "Share not found on server:") $share" + msg_success "$(translate "Press Enter to continue...")" + read -r + return 1 + fi +} + +mount_samba_share() { + # Step 0: + install_samba_client || return + + # Step 1: + select_samba_server || return + + # Step 2: + get_samba_credentials || return + + # Step 3: + select_samba_share || return + + if ! validate_share_exists "$SAMBA_SERVER" "$SAMBA_SHARE" "$USE_GUEST" "$USERNAME" "$PASSWORD"; then + echo -e "" + msg_error "$(translate "Cannot proceed with invalid share name.")" + msg_success "$(translate "Press Enter to return to menu...")" + read -r + return + fi + + # Step 4: + select_mount_point || return + + # Step 5: + configure_mount_options || return + + show_proxmenux_logo + msg_title "$(translate "Installing Samba Client")" + + if ! pct exec "$CTID" -- test -d "$MOUNT_POINT"; then + if pct exec "$CTID" -- mkdir -p "$MOUNT_POINT"; then + msg_ok "$(translate "Mount point created.")" + else + msg_error "$(translate "Failed to create mount point.")" + return 1 + fi + fi + + + if pct exec "$CTID" -- mount | grep -q "$MOUNT_POINT"; then + msg_warn "$(translate "Something is already mounted at") $MOUNT_POINT" + if ! whiptail --yesno "$(translate "Do you want to unmount it first?")" 8 60 --title "$(translate "Already Mounted")"; then + return + fi + pct exec "$CTID" -- umount "$MOUNT_POINT" 2>/dev/null || true + fi + + + if [[ "$USE_GUEST" != "true" ]]; then + create_credentials_file + CRED_OPTION="credentials=$CRED_FILE" + else + CRED_OPTION="guest" + fi + + FULL_OPTIONS="$MOUNT_OPTIONS,$CRED_OPTION" + UNC_PATH="//$SAMBA_SERVER/$SAMBA_SHARE" + + + #if pct exec "$CTID" -- mount -t cifs "$UNC_PATH" "$MOUNT_POINT" -o "$FULL_OPTIONS"; then + if pct exec "$CTID" -- mount -t cifs "$UNC_PATH" "$MOUNT_POINT" -o "$FULL_OPTIONS" 2>/dev/null; then + msg_ok "$(translate "Samba share mounted successfully!")" + + if pct exec "$CTID" -- touch "$MOUNT_POINT/.test_write" 2>/dev/null; then + pct exec "$CTID" -- rm "$MOUNT_POINT/.test_write" 2>/dev/null + msg_ok "$(translate "Write access confirmed.")" + else + msg_warn "$(translate "Read-only access (or no write permissions).")" + fi + + + if [[ "$PERMANENT_MOUNT" == "true" ]]; then + + + pct exec "$CTID" -- sed -i "\|$MOUNT_POINT|d" /etc/fstab + + + FSTAB_ENTRY="$UNC_PATH $MOUNT_POINT cifs $FULL_OPTIONS 0 0" + pct exec "$CTID" -- bash -c "echo '$FSTAB_ENTRY' >> /etc/fstab" + msg_ok "$(translate "Added to /etc/fstab for permanent mounting.")" + fi + + + echo -e "" + echo -e "${TAB}${BOLD}$(translate "Mount Information:")${CL}" + echo -e "${TAB}${BGN}$(translate "Server:")${CL} ${BL}$SAMBA_SERVER${CL}" + echo -e "${TAB}${BGN}$(translate "Share:")${CL} ${BL}$SAMBA_SHARE${CL}" + echo -e "${TAB}${BGN}$(translate "Mount Point:")${CL} ${BL}$MOUNT_POINT${CL}" + echo -e "${TAB}${BGN}$(translate "Authentication:")${CL} ${BL}$([ "$USE_GUEST" == "true" ] && echo "Guest" || echo "User: $USERNAME")${CL}" + echo -e "${TAB}${BGN}$(translate "Permanent:")${CL} ${BL}$PERMANENT_MOUNT${CL}" + + else + msg_error "$(translate "Failed to mount Samba share.")" + echo -e "${TAB}$(translate "Please check:")" + echo -e "${TAB}• $(translate "Server is accessible:"): $SAMBA_SERVER" + echo -e "${TAB}• $(translate "Share exists:"): $SAMBA_SHARE" + echo -e "${TAB}• $(translate "Credentials are correct")" + echo -e "${TAB}• $(translate "Network connectivity")" + echo -e "${TAB}• $(translate "Samba server is running")" + + + if [[ "$USE_GUEST" != "true" && -n "$CRED_FILE" ]]; then + pct exec "$CTID" -- rm -f "$CRED_FILE" 2>/dev/null || true + fi + fi + + echo -e "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + + + +view_samba_mounts() { + show_proxmenux_logo + msg_title "$(translate "Current Samba Mounts")" + + echo -e "$(translate "Samba/CIFS mounts in CT") $CTID:" + echo "==================================" + + + CURRENT_MOUNTS=$(pct exec "$CTID" -- mount -t cifs 2>/dev/null || true) + if [[ -n "$CURRENT_MOUNTS" ]]; then + echo -e "${BOLD}$(translate "Currently Mounted:")${CL}" + echo "$CURRENT_MOUNTS" + echo "" + else + echo "$(translate "No Samba shares currently mounted.")" + echo "" + fi + + + FSTAB_CIFS=$(pct exec "$CTID" -- grep "cifs" /etc/fstab 2>/dev/null || true) + if [[ -n "$FSTAB_CIFS" ]]; then + echo -e "${BOLD}$(translate "Permanent Mounts (fstab):")${CL}" + echo "$FSTAB_CIFS" + echo "" + + echo -e "${TAB}${BOLD}$(translate "Mount Details:")${CL}" + while IFS= read -r fstab_line; do + if [[ -n "$fstab_line" && ! "$fstab_line" =~ ^# ]]; then + UNC_PATH=$(echo "$fstab_line" | awk '{print $1}') + MOUNT_POINT=$(echo "$fstab_line" | awk '{print $2}') + OPTIONS=$(echo "$fstab_line" | awk '{print $4}') + + + SERVER=$(echo "$UNC_PATH" | cut -d/ -f3) + SHARE=$(echo "$UNC_PATH" | cut -d/ -f4) + + echo -e "${TAB}${BGN}$(translate "Server:")${CL} ${BL}$SERVER${CL}" + echo -e "${TAB}${BGN}$(translate "Share:")${CL} ${BL}$SHARE${CL}" + echo -e "${TAB}${BGN}$(translate "Mount Point:")${CL} ${BL}$MOUNT_POINT${CL}" + + + if echo "$OPTIONS" | grep -q "guest"; then + echo -e "${TAB}${BGN}$(translate "Authentication:")${CL} ${BL}Guest${CL}" + elif echo "$OPTIONS" | grep -q "credentials="; then + CRED_FILE=$(echo "$OPTIONS" | grep -o "credentials=[^,]*" | cut -d= -f2) + echo -e "${TAB}${BGN}$(translate "Authentication:")${CL} ${BL}Credentials ($CRED_FILE)${CL}" + fi + + + if pct exec "$CTID" -- mount | grep -q "$MOUNT_POINT"; then + echo -e "${TAB}${BGN}$(translate "Status:")${CL} ${GN}$(translate "Mounted")${CL}" + else + echo -e "${TAB}${BGN}$(translate "Status:")${CL} ${RD}$(translate "Not Mounted")${CL}" + fi + echo "" + fi + done <<< "$FSTAB_CIFS" + else + echo "$(translate "No permanent Samba mounts configured.")" + fi + + + CRED_FILES=$(pct exec "$CTID" -- find "$CREDENTIALS_DIR" -name "*.cred" 2>/dev/null || true) + if [[ -n "$CRED_FILES" ]]; then + echo -e "${BOLD}$(translate "Stored Credentials:")${CL}" + while IFS= read -r cred_file; do + if [[ -n "$cred_file" ]]; then + FILENAME=$(basename "$cred_file") + echo -e "${TAB}• $FILENAME" + fi + done <<< "$CRED_FILES" + echo "" + fi + + echo "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + + + + + + +unmount_samba_share() { + + MOUNTS=$(pct exec "$CTID" -- mount -t cifs 2>/dev/null | awk '{print $3}' | sort -u || true) + + FSTAB_MOUNTS=$(pct exec "$CTID" -- grep -E "cifs" /etc/fstab 2>/dev/null | grep -v "^#" | awk '{print $2}' | sort -u || true) + + + ALL_MOUNTS=$(echo -e "$MOUNTS\n$FSTAB_MOUNTS" | sort -u | grep -v "^$" || true) + + if [[ -z "$ALL_MOUNTS" ]]; then + dialog --title "$(translate "No Mounts")" --msgbox "\n$(translate "No Samba mounts found.")" 8 50 + return + fi + + OPTIONS=() + while IFS= read -r mount_point; do + [[ -n "$mount_point" ]] && OPTIONS+=("$mount_point" "") + done <<< "$ALL_MOUNTS" + + SELECTED_MOUNT=$(dialog --title "$(translate "Unmount Samba Share")" --menu "$(translate "Select mount point to unmount:")" 20 80 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) + [[ -z "$SELECTED_MOUNT" ]] && return + + + if whiptail --yesno "$(translate "Are you sure you want to unmount this Samba share?")\n\n$(translate "Mount Point:") $SELECTED_MOUNT\n\n$(translate "This will remove the mount from /etc/fstab and delete credentials if present.")" 14 80 --title "$(translate "Confirm Unmount")"; then + + show_proxmenux_logo + msg_title "$(translate "Unmount Samba Share")" + + CRED_FILE=$(pct exec "$CTID" -- grep -E "\s+$SELECTED_MOUNT\s+" /etc/fstab 2>/dev/null | grep -o "credentials=[^, ]*" | cut -d= -f2 || true) + pct exec "$CTID" -- sed -i "\|[[:space:]]$SELECTED_MOUNT[[:space:]]|d" /etc/fstab + msg_ok "$(translate "Removed from /etc/fstab.")" + + if [[ -n "$CRED_FILE" && "$CRED_FILE" != "guest" ]]; then + if pct exec "$CTID" -- test -f "$CRED_FILE"; then + pct exec "$CTID" -- rm -f "$CRED_FILE" + msg_ok "$(translate "Credentials file removed.")" + fi + fi + + echo -e "" + + + msg_ok "$(translate "Samba share unmount successfully. Reboot LXC required to take effect.")" + echo -e "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r + fi + +} + + + + + + + +test_samba_connectivity() { + show_proxmenux_logo + msg_title "$(translate "Test Samba Connectivity")" + + echo -e "$(translate "Samba/CIFS Client Status in CT") $CTID:" + echo "==================================" + + + if pct exec "$CTID" -- dpkg -s cifs-utils &>/dev/null; then + echo "$(translate "CIFS Client: INSTALLED")" + + + if pct exec "$CTID" -- which smbclient >/dev/null 2>&1; then + echo "$(translate "SMB Client Tools: AVAILABLE")" + else + echo "$(translate "SMB Client Tools: NOT AVAILABLE")" + fi + + echo "" + echo "$(translate "Current CIFS mounts:")" + CURRENT_MOUNTS=$(pct exec "$CTID" -- mount -t cifs 2>/dev/null || true) + if [[ -n "$CURRENT_MOUNTS" ]]; then + echo "$CURRENT_MOUNTS" + else + echo "$(translate "No CIFS mounts active.")" + fi + + echo "" + echo "$(translate "Testing network connectivity...")" + + + FSTAB_SERVERS=$(pct exec "$CTID" -- grep "cifs" /etc/fstab 2>/dev/null | awk '{print $1}' | cut -d/ -f3 | sort -u || true) + if [[ -n "$FSTAB_SERVERS" ]]; then + while IFS= read -r server; do + if [[ -n "$server" ]]; then + echo -n "$(translate "Testing") $server: " + if pct exec "$CTID" -- ping -c 1 -W 2 "$server" >/dev/null 2>&1; then + echo -e "${GN}$(translate "Reachable")${CL}" + + + if pct exec "$CTID" -- nc -z -w 2 "$server" 445 2>/dev/null; then + echo " $(translate "SMB port 445:"): ${GN}$(translate "Open")${CL}" + elif pct exec "$CTID" -- nc -z -w 2 "$server" 139 2>/dev/null; then + echo " $(translate "NetBIOS port 139:"): ${GN}$(translate "Open")${CL}" + else + echo " $(translate "SMB ports:"): ${RD}$(translate "Closed")${CL}" + fi + + + echo -n " $(translate "Guest access test:"): " + if pct exec "$CTID" -- smbclient -L "$server" -N >/dev/null 2>&1; then + echo -e "${GN}$(translate "Available")${CL}" + else + echo -e "${YW}$(translate "Requires authentication")${CL}" + fi + else + echo -e "${RD}$(translate "Unreachable")${CL}" + fi + fi + done <<< "$FSTAB_SERVERS" + else + echo "$(translate "No Samba servers configured to test.")" + fi + + + echo "" + echo "$(translate "Stored credentials:")" + CRED_FILES=$(pct exec "$CTID" -- find "$CREDENTIALS_DIR" -name "*.cred" 2>/dev/null || true) + if [[ -n "$CRED_FILES" ]]; then + while IFS= read -r cred_file; do + if [[ -n "$cred_file" ]]; then + FILENAME=$(basename "$cred_file") + echo " • $FILENAME" + fi + done <<< "$CRED_FILES" + else + echo " $(translate "No stored credentials found.")" + fi + + else + echo "$(translate "CIFS Client: NOT INSTALLED")" + echo "" + echo "$(translate "Run 'Mount Samba Share' to install CIFS client automatically.")" + fi + + echo "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + +# === Main Menu === +while true; do + CHOICE=$(dialog --title "$(translate "Samba Client Manager - CT") $CTID" \ + --menu "$(translate "Choose an option:")" 20 70 12 \ + "1" "$(translate "Mount Samba Share")" \ + "2" "$(translate "View Current Mounts")" \ + "3" "$(translate "Unmount Samba Share")" \ + "4" "$(translate "Test Samba Connectivity")" \ + "5" "$(translate "Exit")" \ + 3>&1 1>&2 2>&3) + + RETVAL=$? + if [[ $RETVAL -ne 0 ]]; then + exit 0 + fi + + case $CHOICE in + 1) mount_samba_share ;; + 2) view_samba_mounts ;; + 3) unmount_samba_share ;; + 4) test_samba_connectivity ;; + 5) exit 0 ;; + *) exit 0 ;; + esac +done \ No newline at end of file diff --git a/scripts/share/samba_host.sh b/scripts/share/samba_host.sh new file mode 100644 index 0000000..4b8bba8 --- /dev/null +++ b/scripts/share/samba_host.sh @@ -0,0 +1,1264 @@ +#!/bin/bash +# ========================================================== +# ProxMenu Host - Samba Host Manager for Proxmox Host +# ========================================================== +# Based on ProxMenux by MacRimi +# ========================================================== +# Description: +# This script allows you to manage Samba/CIFS client mounts on Proxmox Host: +# - Mount external Samba shares on the host +# - Configure permanent mounts with credentials +# - Auto-discover Samba servers +# - Integrate with Proxmox storage system +# ========================================================== + +# Configuration +REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main" +BASE_DIR="/usr/local/share/proxmenux" +UTILS_FILE="$BASE_DIR/utils.sh" +VENV_PATH="/opt/googletrans-env" +CREDENTIALS_DIR="/etc/samba/credentials" + +if [[ -f "$UTILS_FILE" ]]; then + source "$UTILS_FILE" +fi + +load_language +initialize_cache + + +if ! command -v pveversion >/dev/null 2>&1; then + dialog --backtitle "ProxMenux" --title "$(translate "Error")" --msgbox "$(translate "This script must be run on a Proxmox host.")" 8 60 + exit 1 +fi + +discover_samba_servers() { + show_proxmenux_logo + msg_title "$(translate "Samba Host Manager - Proxmox Host")" + msg_info "$(translate "Scanning network for Samba servers...")" + + + HOST_IP=$(hostname -I | awk '{print $1}') + NETWORK=$(echo "$HOST_IP" | cut -d. -f1-3).0/24 + + + for pkg in nmap samba-common-bin; do + if ! which ${pkg%%-*} >/dev/null 2>&1; then + apt-get install -y "$pkg" &>/dev/null + fi + done + + SERVERS=$(nmap -p 139,445 --open "$NETWORK" 2>/dev/null | grep -B 4 -E "(139|445)/tcp open" | grep "Nmap scan report" | awk '{print $5}' | sort -u || true) + if [[ -z "$SERVERS" ]]; then + cleanup + whiptail --title "$(translate "No Servers Found")" --msgbox "$(translate "No Samba servers found on the network.")\n\n$(translate "You can add servers manually.")" 10 60 + return 1 + fi + + SERVER_LINES=() + while IFS= read -r server; do + [[ -z "$server" ]] && continue + + NB_NAME=$(nmblookup -A "$server" 2>/dev/null | awk '/<00> -.*B / {print $1; exit}') + + if [[ -z "$NB_NAME" || "$NB_NAME" == "$server" || "$NB_NAME" == "address" || "$NB_NAME" == "-" ]]; then + NB_NAME="Unknown" + fi + + SERVER_LINES+=("$server|$NB_NAME ($server)") + done <<< "$SERVERS" + + IFS=$'\n' SORTED=($(printf "%s\n" "${SERVER_LINES[@]}" | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n)) + + OPTIONS=() + declare -A SERVER_IPS + i=1 + for entry in "${SORTED[@]}"; do + server="${entry%%|*}" + label="${entry#*|}" + OPTIONS+=("$i" "$label") + SERVER_IPS["$i"]="$server" + ((i++)) + done + + if [[ ${#OPTIONS[@]} -eq 0 ]]; then + cleanup + whiptail --title "$(translate "No Valid Servers")" --msgbox "$(translate "No accessible Samba servers found.")" 8 50 + return 1 + fi + + msg_ok "$(translate "Samba servers detected")" + CHOICE=$(dialog --title "$(translate "Select Samba Server")" \ + --menu "$(translate "Choose a Samba server:")" 20 80 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) + + if [[ -n "$CHOICE" ]]; then + SAMBA_SERVER="${SERVER_IPS[$CHOICE]}" + return 0 + else + return 1 + fi +} + + + + + +select_samba_server() { + METHOD=$(dialog --backtitle "ProxMenux" --title "$(translate "Samba Server Selection")" --menu "$(translate "How do you want to select the Samba server?")" 15 70 3 \ + "auto" "$(translate "Auto-discover servers on network")" \ + "manual" "$(translate "Enter server IP/hostname manually")" \ + "recent" "$(translate "Select from recent servers")" 3>&1 1>&2 2>&3) + + case "$METHOD" in + auto) + discover_samba_servers || return 1 + ;; + manual) + clear + SAMBA_SERVER=$(whiptail --inputbox "$(translate "Enter Samba server IP:")" 10 60 --title "$(translate "Samba Server")" 3>&1 1>&2 2>&3) + [[ -z "$SAMBA_SERVER" ]] && return 1 + ;; + recent) + clear + RECENT=$(grep "cifs" /etc/fstab 2>/dev/null | awk '{print $1}' | cut -d/ -f3 | sort -u || true) + if [[ -z "$RECENT" ]]; then + dialog --backtitle "ProxMenux" --title "$(translate "No Recent Servers")" --msgbox "\n$(translate "No recent Samba servers found.")" 8 50 + return 1 + fi + + OPTIONS=() + while IFS= read -r server; do + [[ -n "$server" ]] && OPTIONS+=("$server" "$(translate "Recent Samba server")") + done <<< "$RECENT" + + SAMBA_SERVER=$(whiptail --title "$(translate "Recent Samba Servers")" --menu "$(translate "Choose a recent server:")" 20 70 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) + [[ -n "$SAMBA_SERVER" ]] && return 0 || return 1 + ;; + *) + return 1 + ;; + esac + return 0 +} + +validate_guest_access() { + local server="$1" + + show_proxmenux_logo + msg_info "$(translate "Testing comprehensive guest access to server") $server..." + + GUEST_LIST_OUTPUT=$(smbclient -L "$server" -N 2>&1) + GUEST_LIST_RESULT=$? + + if [[ $GUEST_LIST_RESULT -ne 0 ]]; then + cleanup + if echo "$GUEST_LIST_OUTPUT" | grep -qi "access denied\|logon failure"; then + whiptail --title "$(translate "Guest Access Denied")" \ + --msgbox "$(translate "Guest access is not allowed on this server.")\n\n$(translate "You need to use username and password authentication.")" \ + 10 70 + else + whiptail --title "$(translate "Guest Access Error")" \ + --msgbox "$(translate "Guest access failed.")\n\n$(translate "Error details:")\n$(echo "$GUEST_LIST_OUTPUT" | head -3)" \ + 12 70 + fi + return 1 + fi + sleep 2 + msg_ok "$(translate "Guest share listing successful")" + + GUEST_SHARES=$(echo "$GUEST_LIST_OUTPUT" | awk '/Disk/ && !/IPC\$/ && !/ADMIN\$/ && !/print\$/ {print $1}' | grep -v "^$") + if [[ -z "$GUEST_SHARES" ]]; then + whiptail --title "$(translate "No Guest Shares")" \ + --msgbox "$(translate "Guest access works for listing, but no shares are available.")\n\n$(translate "The server may require authentication for actual share access.")" \ + 10 70 + return 1 + fi + + msg_ok "$(translate "Found guest-accessible shares:") $(echo "$GUEST_SHARES" | wc -l)" + + msg_info "$(translate "Step 2: Testing actual share access with guest...")" + ACCESSIBLE_SHARES="" + FAILED_SHARES="" + sleep 1 + while IFS= read -r share; do + if [[ -n "$share" ]]; then + + SHARE_TEST_OUTPUT=$(smbclient "//$server/$share" -N -c "ls" 2>&1) + SHARE_TEST_RESULT=$? + + if [[ $SHARE_TEST_RESULT -eq 0 ]]; then + msg_ok "$(translate "Guest access confirmed for share:") $share" + ACCESSIBLE_SHARES="$ACCESSIBLE_SHARES$share\n" + else + msg_warn "$(translate "Guest access denied for share:") $share" + FAILED_SHARES="$FAILED_SHARES$share\n" + + if echo "$SHARE_TEST_OUTPUT" | grep -qi "access denied\|logon failure\|authentication"; then + msg_warn " $(translate "Reason: Authentication required")" + elif echo "$SHARE_TEST_OUTPUT" | grep -qi "permission denied"; then + msg_warn " $(translate "Reason: Permission denied")" + else + msg_warn " $(translate "Reason: Access denied")" + fi + fi + fi + done <<< "$GUEST_SHARES" + + + ACCESSIBLE_COUNT=$(echo -e "$ACCESSIBLE_SHARES" | grep -v "^$" | wc -l) + FAILED_COUNT=$(echo -e "$FAILED_SHARES" | grep -v "^$" | wc -l) + + echo -e "" + msg_info2 "$(translate "Guest Access Validation Results:")" + echo -e "${TAB}${BGN}$(translate "Shares found:")${CL} ${BL}$(echo "$GUEST_SHARES" | wc -l)${CL}" + echo -e "${TAB}${BGN}$(translate "Guest accessible:")${CL} ${GN}$ACCESSIBLE_COUNT${CL}" + echo -e "${TAB}${BGN}$(translate "Authentication required:")${CL} ${YW}$FAILED_COUNT${CL}" + + if [[ $ACCESSIBLE_COUNT -gt 0 ]]; then + msg_ok "$(translate "Guest access validated successfully!")" + echo -e "" + echo -e "${TAB}${BOLD}$(translate "Available shares for guest access:")${CL}" + while IFS= read -r share; do + [[ -n "$share" ]] && echo -e "${TAB}• ${BL}$share${CL}" + done <<< "$(echo -e "$ACCESSIBLE_SHARES" | grep -v "^$")" + echo -e + msg_success "$(translate "Press Enter to continue...")" + read -r + clear + + VALIDATED_GUEST_SHARES="$ACCESSIBLE_SHARES" + return 0 + else + msg_success "$(translate "Press Enter to continue...")" + read -r + whiptail --title "$(translate "Guest Access Failed")" \ + --msgbox "$(translate "While the server allows guest listing, no shares are actually accessible without authentication.")\n\n$(translate "You need to use username and password authentication.")" \ + 12 70 + clear + return 1 + fi +} + +get_samba_credentials() { + while true; do + CHOICE=$(whiptail --title "$(translate "Samba Credentials")" \ + --menu "$(translate "Select authentication mode:")" 13 60 2 \ + "1" "$(translate "Configure with username and password")" \ + "2" "$(translate "Configure as guest (no authentication)")" \ + 3>&1 1>&2 2>&3) + + if [[ $? -ne 0 ]]; then + return 1 + fi + + case "$CHOICE" in + 1) + + while true; do + USERNAME=$(whiptail --inputbox "$(translate "Enter username for Samba server:")" 10 60 --title "$(translate "Username")" 3>&1 1>&2 2>&3) + if [[ $? -ne 0 ]]; then + break + fi + if [[ -z "$USERNAME" ]]; then + whiptail --title "$(translate "Error")" --msgbox "$(translate "Username cannot be empty.")" 8 50 + continue + fi + + while true; do + PASSWORD=$(whiptail --passwordbox "$(translate "Enter password for") $USERNAME:" 10 60 --title "$(translate "Password")" 3>&1 1>&2 2>&3) + if [[ $? -ne 0 ]]; then + break + fi + if [[ -z "$PASSWORD" ]]; then + whiptail --title "$(translate "Error")" --msgbox "$(translate "Password cannot be empty.")" 8 50 + continue + fi + + PASSWORD_CONFIRM=$(whiptail --passwordbox "$(translate "Confirm password for") $USERNAME:" 10 60 --title "$(translate "Confirm Password")" 3>&1 1>&2 2>&3) + if [[ $? -ne 0 ]]; then + continue + fi + if [[ -z "$PASSWORD_CONFIRM" ]]; then + whiptail --title "$(translate "Error")" --msgbox "$(translate "Password confirmation cannot be empty.")" 8 50 + continue + fi + + if [[ "$PASSWORD" == "$PASSWORD_CONFIRM" ]]; then + + show_proxmenux_logo + msg_info "$(translate "Validating credentials with server") $SAMBA_SERVER..." + + + TEMP_CRED="/tmp/validate_cred_$$" + cat > "$TEMP_CRED" << EOF +username=$USERNAME +password=$PASSWORD +EOF + chmod 600 "$TEMP_CRED" + + + SHARES_OUTPUT=$(smbclient -L "$SAMBA_SERVER" -A "$TEMP_CRED" 2>&1) + SHARES_RESULT=$? + + if [[ $SHARES_RESULT -eq 0 ]]; then + + FIRST_SHARE=$(echo "$SHARES_OUTPUT" | awk '/Disk/ && !/IPC\$/ && !/ADMIN\$/ && !/print\$/ {print $1; exit}') + + if [[ -n "$FIRST_SHARE" ]]; then + + SHARE_TEST_OUTPUT=$(smbclient "//$SAMBA_SERVER/$FIRST_SHARE" -A "$TEMP_CRED" -c "ls" 2>&1) + SHARE_TEST_RESULT=$? + + rm -f "$TEMP_CRED" + + if [[ $SHARE_TEST_RESULT -eq 0 ]]; then + + cleanup + if echo "$SHARE_TEST_OUTPUT" | grep -qi "guest"; then + whiptail --title "$(translate "Authentication Error")" \ + --msgbox "$(translate "The server connected you as guest instead of the specified user.")\n\n$(translate "This means the credentials are incorrect.")\n\n$(translate "Please check:")\n• $(translate "Username is correct")\n• $(translate "Password is correct")\n• $(translate "User account exists on server")" \ + 14 70 + else + msg_ok "$(translate "Credentials validated successfully")" + USE_GUEST=false + return 0 + fi + else + + cleanup + if echo "$SHARE_TEST_OUTPUT" | grep -qi "access denied\|logon failure\|authentication\|NT_STATUS_LOGON_FAILURE"; then + whiptail --title "$(translate "Authentication Error")" \ + --msgbox "$(translate "Invalid username or password.")\n\n$(translate "Error details:")\n$(echo "$SHARE_TEST_OUTPUT" | head -2)\n\n$(translate "Please check:")\n• $(translate "Username is correct")\n• $(translate "Password is correct")\n• $(translate "User account exists on server")" \ + 16 70 + elif echo "$SHARE_TEST_OUTPUT" | grep -qi "connection refused\|network unreachable"; then + whiptail --title "$(translate "Network Error")" \ + --msgbox "$(translate "Cannot connect to server") $SAMBA_SERVER\n\n$(translate "Please check network connectivity.")" \ + 10 60 + return 1 + else + whiptail --title "$(translate "Share Access Error")" \ + --msgbox "$(translate "Failed to access share with provided credentials.")\n\n$(translate "Error details:")\n$(echo "$SHARE_TEST_OUTPUT" | head -3)" \ + 12 70 + fi + fi + else + + cleanup + whiptail --title "$(translate "No Shares Available")" \ + --msgbox "$(translate "Cannot validate credentials - no shares available for testing.")\n\n$(translate "The server may not have accessible shares.")" \ + 10 70 + fi + else + + rm -f "$TEMP_CRED" + + + if echo "$SHARES_OUTPUT" | grep -qi "access denied\|logon failure\|authentication\|NT_STATUS_LOGON_FAILURE"; then + cleanup + whiptail --title "$(translate "Authentication Error")" \ + --msgbox "$(translate "Invalid username or password.")\n\n$(translate "Please check:")\n• $(translate "Username is correct")\n• $(translate "Password is correct")\n• $(translate "User account exists on server")\n• $(translate "Account is not locked")" \ + 12 70 + elif echo "$SHARES_OUTPUT" | grep -qi "connection refused\|network unreachable"; then + cleanup + whiptail --title "$(translate "Network Error")" \ + --msgbox "$(translate "Cannot connect to server") $SAMBA_SERVER\n\n$(translate "Please check network connectivity.")" \ + 10 60 + return 1 + else + cleanup + whiptail --title "$(translate "Connection Error")" \ + --msgbox "$(translate "Failed to connect to server.")\n\n$(translate "Error details:")\n$(echo "$SHARES_OUTPUT" | head -3)" \ + 12 70 + fi + fi + + break + else + cleanup + whiptail --title "$(translate "Password Mismatch")" \ + --msgbox "$(translate "Passwords do not match. Please try again.")" \ + 8 50 + + fi + done + + if [[ $? -ne 0 ]]; then + break + fi + done + ;; + 2) + + if validate_guest_access "$SAMBA_SERVER"; then + USE_GUEST=true + return 0 + fi + ;; + *) + return 1 + ;; + esac + + + if ! whiptail --yesno "$(translate "Authentication failed.")\n\n$(translate "Do you want to try different credentials or authentication method?")" 10 70 --title "$(translate "Try Again")"; then + return 1 + fi + + done +} + +select_samba_share() { + if ! which smbclient >/dev/null 2>&1; then + whiptail --title "$(translate "SMB Client Error")" \ + --msgbox "$(translate "smbclient command is not working properly.")\n\n$(translate "Please check the installation.")" \ + 10 60 + return 1 + fi + + + if [[ "$USE_GUEST" == "true" ]]; then + + if [[ -n "$VALIDATED_GUEST_SHARES" ]]; then + SHARES=$(echo -e "$VALIDATED_GUEST_SHARES" | grep -v "^$") + msg_ok "$(translate "Using pre-validated guest shares")" + else + + SHARES_OUTPUT=$(smbclient -L "$SAMBA_SERVER" -N 2>&1) + SHARES_RESULT=$? + if [[ $SHARES_RESULT -eq 0 ]]; then + SHARES=$(echo "$SHARES_OUTPUT" | awk '/Disk/ && !/IPC\$/ && !/ADMIN\$/ && !/print\$/ {print $1}' | grep -v "^$") + else + msg_error "$(translate "Failed to get shares")" + return 1 + fi + fi + else + + TEMP_CRED="/tmp/temp_smb_cred_$$" + cat > "$TEMP_CRED" << EOF +username=$USERNAME +password=$PASSWORD +EOF + chmod 600 "$TEMP_CRED" + + SHARES_OUTPUT=$(smbclient -L "$SAMBA_SERVER" -A "$TEMP_CRED" 2>&1) + SHARES_RESULT=$? + + rm -f "$TEMP_CRED" + + if [[ $SHARES_RESULT -ne 0 ]]; then + msg_error "$(translate "Unexpected error getting shares")" + whiptail --title "$(translate "SMB Error")" \ + --msgbox "$(translate "Failed to get shares from") $SAMBA_SERVER\n\n$(translate "This is unexpected since credentials were validated.")" \ + 12 80 + return 1 + fi + + SHARES=$(echo "$SHARES_OUTPUT" | awk '/Disk/ && !/IPC\$/ && !/ADMIN\$/ && !/print\$/ {print $1}' | grep -v "^$") + fi + + msg_ok "$(translate "Shares retrieved successfully")" + + if [[ -z "$SHARES" ]]; then + whiptail --title "$(translate "No Shares Found")" \ + --msgbox "$(translate "No shares found on server") $SAMBA_SERVER\n\n$(translate "You can enter the share name manually.")" \ + 12 70 + + SAMBA_SHARE=$(whiptail --inputbox "$(translate "Enter Samba share name:")" 10 60 --title "$(translate "Share Name")" 3>&1 1>&2 2>&3) + [[ -z "$SAMBA_SHARE" ]] && return 1 + return 0 + fi + + + OPTIONS=() + while IFS= read -r share; do + if [[ -n "$share" && "$share" != "IPC$" && "$share" != "ADMIN$" && "$share" != "print$" ]]; then + + if [[ "$USE_GUEST" == "true" ]]; then + if echo -e "$VALIDATED_GUEST_SHARES" | grep -q "^$share$"; then + OPTIONS+=("$share" "$(translate "Guest accessible share")") + fi + else + + OPTIONS+=("$share" "$(translate "Samba share")") + fi + fi + done <<< "$SHARES" + + if [[ ${#OPTIONS[@]} -eq 0 ]]; then + whiptail --title "$(translate "No Available Shares")" \ + --msgbox "$(translate "No accessible shares found.")\n\n$(translate "You can enter the share name manually.")" \ + 10 70 + + SAMBA_SHARE=$(whiptail --inputbox "$(translate "Enter Samba share name:")" 10 60 --title "$(translate "Share Name")" 3>&1 1>&2 2>&3) + [[ -z "$SAMBA_SHARE" ]] && return 1 + return 0 + fi + + SAMBA_SHARE=$(whiptail --title "$(translate "Select Samba Share")" --menu "$(translate "Choose a share to mount:")" 20 70 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) + [[ -n "$SAMBA_SHARE" ]] && return 0 || return 1 +} + +select_host_mount_point() { + while true; do + METHOD=$(whiptail --title "$(translate "Select Mount Point")" --menu "$(translate "Where do you want to mount the Samba share on the host?")" 15 70 4 \ + "mnt" "$(translate "Create folder in /mnt")" \ + "media" "$(translate "Create folder in /media")" \ + "srv" "$(translate "Create folder in /srv")" \ + "custom" "$(translate "Enter custom path")" 3>&1 1>&2 2>&3) + + case "$METHOD" in + mnt) + DEFAULT_NAME="${SAMBA_SHARE}" + FOLDER_NAME=$(whiptail --inputbox "$(translate "Enter folder name for /mnt:")" 10 60 "$DEFAULT_NAME" --title "$(translate "Folder in /mnt")" 3>&1 1>&2 2>&3) + if [[ -n "$FOLDER_NAME" ]]; then + MOUNT_POINT="/mnt/$FOLDER_NAME" + return 0 + fi + ;; + media) + DEFAULT_NAME="samba_${SAMBA_SERVER}_${SAMBA_SHARE}" + FOLDER_NAME=$(whiptail --inputbox "$(translate "Enter folder name for /media:")" 10 60 "$DEFAULT_NAME" --title "$(translate "Folder in /media")" 3>&1 1>&2 2>&3) + if [[ -n "$FOLDER_NAME" ]]; then + MOUNT_POINT="/media/$FOLDER_NAME" + return 0 + fi + ;; + srv) + DEFAULT_NAME="samba_${SAMBA_SERVER}_${SAMBA_SHARE}" + FOLDER_NAME=$(whiptail --inputbox "$(translate "Enter folder name for /srv:")" 10 60 "$DEFAULT_NAME" --title "$(translate "Folder in /srv")" 3>&1 1>&2 2>&3) + if [[ -n "$FOLDER_NAME" ]]; then + MOUNT_POINT="/srv/$FOLDER_NAME" + return 0 + fi + ;; + custom) + MOUNT_POINT=$(whiptail --inputbox "$(translate "Enter full path for mount point:")" 10 70 "/mnt/samba_share" --title "$(translate "Custom Path")" 3>&1 1>&2 2>&3) + if [[ -n "$MOUNT_POINT" ]]; then + return 0 + fi + ;; + *) + return 1 + ;; + esac + done +} + +configure_host_mount_options() { + MOUNT_TYPE=$(whiptail --title "$(translate "Mount Options")" --menu "$(translate "Select mount configuration:")" 15 70 4 \ + "default" "$(translate "Default options")" \ + "readonly" "$(translate "Read-only mount")" \ + "performance" "$(translate "Performance optimized")" \ + "custom" "$(translate "Custom options")" 3>&1 1>&2 2>&3) + + [[ $? -ne 0 ]] && return 1 + + case "$MOUNT_TYPE" in + default) + MOUNT_OPTIONS="rw,file_mode=0664,dir_mode=0775,iocharset=utf8" + ;; + readonly) + MOUNT_OPTIONS="ro,file_mode=0444,dir_mode=0555,iocharset=utf8" + ;; + performance) + MOUNT_OPTIONS="rw,file_mode=0664,dir_mode=0775,iocharset=utf8,cache=strict,rsize=1048576,wsize=1048576" + ;; + custom) + MOUNT_OPTIONS=$(whiptail --inputbox "$(translate "Enter custom mount options:")" 10 70 "rw,file_mode=0664,dir_mode=0775" --title "$(translate "Custom Options")" 3>&1 1>&2 2>&3) + [[ $? -ne 0 ]] && return 1 + [[ -z "$MOUNT_OPTIONS" ]] && MOUNT_OPTIONS="rw,file_mode=0664,dir_mode=0775" + ;; + *) + MOUNT_OPTIONS="rw,file_mode=0664,dir_mode=0775,iocharset=utf8" + ;; + esac + + + if whiptail --yesno "$(translate "Do you want to make this mount permanent?")\n\n$(translate "This will add the mount to /etc/fstab so it persists after reboot.")" 10 70 --title "$(translate "Permanent Mount")"; then + PERMANENT_MOUNT=true + else + if [[ $? -eq 1 ]]; then + PERMANENT_MOUNT=false + else + return 1 + fi + fi + + + if [[ "$USE_GUEST" != "true" ]]; then + + if whiptail --yesno "$(translate "Do you want to add this as Proxmox storage?")\n\n$(translate "This will make the Samba share available as storage in Proxmox web interface.")" 10 70 --title "$(translate "Proxmox Storage")"; then + PROXMOX_STORAGE=true + + STORAGE_ID=$(whiptail --inputbox "$(translate "Enter storage ID for Proxmox:")" 10 60 "cifs-$(echo $SAMBA_SERVER | tr '.' '-')" --title "$(translate "Storage ID")" 3>&1 1>&2 2>&3) + STORAGE_ID_RESULT=$? + + if [[ $STORAGE_ID_RESULT -ne 0 ]]; then + if whiptail --yesno "$(translate "Storage ID input was cancelled.")\n\n$(translate "Do you want to continue without Proxmox storage integration?")" 10 70 --title "$(translate "Continue Without Storage")"; then + PROXMOX_STORAGE=false + else + return 1 + fi + else + [[ -z "$STORAGE_ID" ]] && STORAGE_ID="cifs-$(echo $SAMBA_SERVER | tr '.' '-')" + fi + else + DIALOG_RESULT=$? + if [[ $DIALOG_RESULT -eq 1 ]]; then + PROXMOX_STORAGE=false + else + return 1 + fi + fi + fi + + return 0 +} + +create_credentials_file() { + if [[ "$USE_GUEST" == "true" ]]; then + return 0 + fi + + mkdir -p "$CREDENTIALS_DIR" + chmod 700 "$CREDENTIALS_DIR" + + + CRED_FILE="$CREDENTIALS_DIR/${SAMBA_SERVER}_${SAMBA_SHARE}.cred" + + + cat > "$CRED_FILE" << EOF +username=$USERNAME +password=$PASSWORD +EOF + + chmod 600 "$CRED_FILE" + msg_ok "$(translate "Credentials file created securely.")" +} + +add_proxmox_cifs_storage() { + local storage_id="$1" + local server="$2" + local share="$3" + local mount_point="$4" + + if ! which pvesm >/dev/null 2>&1; then + msg_error "$(translate "pvesm command not found. This should not happen on Proxmox.")" + echo "Press Enter to continue..." + read -r + return 1 + fi + + msg_ok "$(translate "pvesm command found")" + + + if pvesm status "$storage_id" >/dev/null 2>&1; then + msg_warn "$(translate "Storage ID already exists:") $storage_id" + if ! whiptail --yesno "$(translate "Storage ID already exists. Do you want to remove and recreate it?")" 8 60 --title "$(translate "Storage Exists")"; then + return 0 + fi + pvesm remove "$storage_id" 2>/dev/null || true + fi + + msg_ok "$(translate "Storage ID is available")" + + + CONTENT_LIST="backup,iso,vztmpl" + + + if [[ "$USE_GUEST" == "true" ]]; then + msg_warn "$(translate "Attempting to add guest access storage to Proxmox...")" + + + PVESM_OUTPUT="" + PVESM_RESULT=1 + + msg_info "$(translate "Trying method 1: guest option...")" + PVESM_OUTPUT=$(pvesm add cifs "$storage_id" \ + --server "$server" \ + --share "$share" \ + --content "$CONTENT_LIST" \ + --options "guest" 2>&1) + PVESM_RESULT=$? + cleanup + + if [[ $PVESM_RESULT -ne 0 ]]; then + msg_info "$(translate "Trying method 2: without guest option...")" + PVESM_OUTPUT=$(pvesm add cifs "$storage_id" \ + --server "$server" \ + --share "$share" \ + --content "$CONTENT_LIST" 2>&1) + PVESM_RESULT=$? + cleanup + fi + + else + PVESM_OUTPUT=$(pvesm add cifs "$storage_id" \ + --server "$server" \ + --share "$share" \ + --username "$USERNAME" \ + --password "$PASSWORD" \ + --content "$CONTENT_LIST" 2>&1) + PVESM_RESULT=$? + fi + + if [[ $PVESM_RESULT -eq 0 ]]; then + msg_ok "$(translate "CIFS storage added successfully to Proxmox!")" + echo -e "" + echo -e "${TAB}${BOLD}$(translate "Storage Added Information:")${CL}" + echo -e "${TAB}${BGN}$(translate "Storage ID:")${CL} ${BL}$storage_id${CL}" + echo -e "${TAB}${BGN}$(translate "Server:")${CL} ${BL}$server${CL}" + echo -e "${TAB}${BGN}$(translate "Share:")${CL} ${BL}$share${CL}" + echo -e "${TAB}${BGN}$(translate "Content Types:")${CL} ${BL}$CONTENT_LIST${CL}" + echo -e "${TAB}${BGN}$(translate "Authentication:")${CL} ${BL}$([ "$USE_GUEST" == "true" ] && echo "Guest" || echo "User: $USERNAME")${CL}" + echo -e "" + msg_ok "$(translate "Storage is now available in Proxmox web interface under Datacenter > Storage")" + return 0 + else + + msg_error "$(translate "Failed to add CIFS storage to Proxmox.")" + echo "${TAB}Error details: $PVESM_OUTPUT" + msg_warn "$(translate "The Samba share is still mounted, but not added as Proxmox storage.")" + msg_info2 "$(translate "You can add it manually through:")" + echo -e "${TAB}• $(translate "Proxmox web interface: Datacenter > Storage > Add > SMB/CIFS")" + echo -e "${TAB}• $(translate "Command line:"): pvesm add cifs $storage_id --server $server --share $share --username $USERNAME --password [PASSWORD] --content backup,iso,vztmpl" + + return 1 + fi +} + +mount_host_samba_share() { + + if ! which smbclient >/dev/null 2>&1; then + msg_info "$(translate "Installing Samba client tools...")" + apt-get update &>/dev/null + apt-get install -y cifs-utils smbclient &>/dev/null + msg_ok "$(translate "Samba client tools installed")" + fi + + # Step 1: + select_samba_server || return + + # Step 2: + get_samba_credentials || return + + # Step 3: + select_samba_share || return + + # Step 4: + select_host_mount_point || return + + # Step 5: + configure_host_mount_options || return + + + show_proxmenux_logo + msg_title "$(translate "Mount Samba Share on Host")" + + + if ! test -d "$MOUNT_POINT"; then + if mkdir -p "$MOUNT_POINT"; then + msg_ok "$(translate "Mount point created on host.")" + else + msg_error "$(translate "Failed to create mount point on host.")" + return 1 + fi + fi + + + if mount | grep -q "$MOUNT_POINT"; then + msg_warn "$(translate "Something is already mounted at") $MOUNT_POINT" + if ! whiptail --yesno "$(translate "Do you want to unmount it first?")" 8 60 --title "$(translate "Already Mounted")"; then + return + fi + umount "$MOUNT_POINT" 2>/dev/null || true + fi + + + if [[ "$USE_GUEST" != "true" ]]; then + create_credentials_file + CRED_OPTION="credentials=$CRED_FILE" + else + CRED_OPTION="guest" + fi + + + FULL_OPTIONS="$MOUNT_OPTIONS,$CRED_OPTION" + UNC_PATH="//$SAMBA_SERVER/$SAMBA_SHARE" + + msg_info "$(translate "Mounting Samba share...")" + if mount -t cifs "$UNC_PATH" "$MOUNT_POINT" -o "$FULL_OPTIONS" > /dev/null 2>&1; then + msg_ok "$(translate "Samba share mounted successfully on host!")" + + + if touch "$MOUNT_POINT/.test_write" 2>/dev/null; then + rm "$MOUNT_POINT/.test_write" 2>/dev/null + msg_ok "$(translate "Write access confirmed.")" + else + msg_warn "$(translate "Read-only access (or no write permissions).")" + fi + + + if [[ "$PERMANENT_MOUNT" == "true" ]]; then + + sed -i "\|$MOUNT_POINT|d" /etc/fstab + FSTAB_ENTRY="$UNC_PATH $MOUNT_POINT cifs $FULL_OPTIONS 0 0" + echo "$FSTAB_ENTRY" >> /etc/fstab + msg_ok "$(translate "Added to /etc/fstab for permanent mounting.")" + + + systemctl daemon-reload 2>/dev/null || true + msg_ok "$(translate "Systemd configuration reloaded.")" + fi + + + if [[ "$PROXMOX_STORAGE" == "true" ]]; then + add_proxmox_cifs_storage "$STORAGE_ID" "$SAMBA_SERVER" "$SAMBA_SHARE" "$MOUNT_POINT" + fi + + + echo -e "" + echo -e "${TAB}${BOLD}$(translate "Host Mount Information:")${CL}" + echo -e "${TAB}${BGN}$(translate "Server:")${CL} ${BL}$SAMBA_SERVER${CL}" + echo -e "${TAB}${BGN}$(translate "Share:")${CL} ${BL}$SAMBA_SHARE${CL}" + echo -e "${TAB}${BGN}$(translate "Host Mount Point:")${CL} ${BL}$MOUNT_POINT${CL}" + echo -e "${TAB}${BGN}$(translate "Options:")${CL} ${BL}$MOUNT_OPTIONS${CL}" + echo -e "${TAB}${BGN}$(translate "Authentication:")${CL} ${BL}$([ "$USE_GUEST" == "true" ] && echo "Guest" || echo "User: $USERNAME")${CL}" + echo -e "${TAB}${BGN}$(translate "Permanent:")${CL} ${BL}$PERMANENT_MOUNT${CL}" + if [[ "$PROXMOX_STORAGE" == "true" ]]; then + echo -e "${TAB}${BGN}$(translate "Proxmox Storage ID:")${CL} ${BL}$STORAGE_ID${CL}" + fi + + else + msg_error "$(translate "Failed to mount Samba share on host.")" + echo -e "${TAB}$(translate "This should not happen since credentials were validated.")" + echo -e "${TAB}$(translate "Please check system logs for details.")" + + + if [[ "$USE_GUEST" != "true" && -n "$CRED_FILE" ]]; then + rm -f "$CRED_FILE" 2>/dev/null || true + fi + fi + + echo -e "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + +view_host_samba_mounts() { + show_proxmenux_logo + msg_title "$(translate "Current Samba Mounts on Host")" + + echo -e "$(translate "Samba/CIFS mounts on Proxmox host:"):" + echo "==================================" + + + CURRENT_MOUNTS=$(mount -t cifs 2>/dev/null || true) + if [[ -n "$CURRENT_MOUNTS" ]]; then + echo -e "${BOLD}$(translate "Currently Mounted:")${CL}" + echo "$CURRENT_MOUNTS" + echo "" + else + echo "$(translate "No Samba shares currently mounted on host.")" + echo "" + fi + + + FSTAB_CIFS=$(grep "cifs" /etc/fstab 2>/dev/null || true) + if [[ -n "$FSTAB_CIFS" ]]; then + echo -e "${BOLD}$(translate "Permanent Mounts (fstab):")${CL}" + echo "$FSTAB_CIFS" + echo "" + + echo -e "${TAB}${BOLD}$(translate "Mount Details:")${CL}" + while IFS= read -r fstab_line; do + if [[ -n "$fstab_line" && ! "$fstab_line" =~ ^# ]]; then + UNC_PATH=$(echo "$fstab_line" | awk '{print $1}') + MOUNT_POINT=$(echo "$fstab_line" | awk '{print $2}') + OPTIONS=$(echo "$fstab_line" | awk '{print $4}') + + + SERVER=$(echo "$UNC_PATH" | cut -d/ -f3) + SHARE=$(echo "$UNC_PATH" | cut -d/ -f4) + + echo -e "${TAB}${BGN}$(translate "Server:")${CL} ${BL}$SERVER${CL}" + echo -e "${TAB}${BGN}$(translate "Share:")${CL} ${BL}$SHARE${CL}" + echo -e "${TAB}${BGN}$(translate "Host Mount Point:")${CL} ${BL}$MOUNT_POINT${CL}" + echo -e "${TAB}${BGN}$(translate "Options:")${CL} ${BL}$OPTIONS${CL}" + + + if echo "$OPTIONS" | grep -q "guest"; then + echo -e "${TAB}${BGN}$(translate "Authentication:")${CL} ${BL}Guest${CL}" + elif echo "$OPTIONS" | grep -q "credentials="; then + CRED_FILE=$(echo "$OPTIONS" | grep -o "credentials=[^,]*" | cut -d= -f2) + echo -e "${TAB}${BGN}$(translate "Authentication:")${CL} ${BL}Credentials ($CRED_FILE)${CL}" + fi + + + if mount | grep -q "$MOUNT_POINT"; then + echo -e "${TAB}${BGN}$(translate "Status:")${CL} ${GN}$(translate "Mounted")${CL}" + else + echo -e "${TAB}${BGN}$(translate "Status:")${CL} ${RD}$(translate "Not Mounted")${CL}" + fi + echo "" + fi + done <<< "$FSTAB_CIFS" + else + echo "$(translate "No permanent Samba mounts configured on host.")" + fi + + + echo -e "${BOLD}$(translate "Proxmox CIFS Storage:")${CL}" + if which pvesm >/dev/null 2>&1; then + CIFS_STORAGES=$(pvesm status 2>/dev/null | grep "cifs" | awk '{print $1}' || true) + if [[ -n "$CIFS_STORAGES" ]]; then + while IFS= read -r storage_id; do + if [[ -n "$storage_id" ]]; then + echo -e "${TAB}${BGN}$(translate "Storage ID:")${CL} ${BL}$storage_id${CL}" + + + STORAGE_INFO=$(pvesm config "$storage_id" 2>/dev/null || true) + if [[ -n "$STORAGE_INFO" ]]; then + SERVER=$(echo "$STORAGE_INFO" | grep "server" | awk '{print $2}') + SHARE=$(echo "$STORAGE_INFO" | grep "share" | awk '{print $2}') + CONTENT=$(echo "$STORAGE_INFO" | grep "content" | awk '{print $2}') + USERNAME=$(echo "$STORAGE_INFO" | grep "username" | awk '{print $2}') + + [[ -n "$SERVER" ]] && echo -e "${TAB} ${BGN}$(translate "Server:")${CL} ${BL}$SERVER${CL}" + [[ -n "$SHARE" ]] && echo -e "${TAB} ${BGN}$(translate "Share:")${CL} ${BL}$SHARE${CL}" + [[ -n "$CONTENT" ]] && echo -e "${TAB} ${BGN}$(translate "Content:")${CL} ${BL}$CONTENT${CL}" + [[ -n "$USERNAME" ]] && echo -e "${TAB} ${BGN}$(translate "Username:")${CL} ${BL}$USERNAME${CL}" + fi + echo "" + fi + done <<< "$CIFS_STORAGES" + else + echo -e "${TAB}$(translate "No CIFS storage configured in Proxmox")" + fi + else + echo -e "${TAB}$(translate "pvesm command not available")" + fi + + + CRED_FILES=$(find "$CREDENTIALS_DIR" -name "*.cred" 2>/dev/null || true) + if [[ -n "$CRED_FILES" ]]; then + echo -e "${BOLD}$(translate "Stored Credentials:")${CL}" + while IFS= read -r cred_file; do + if [[ -n "$cred_file" ]]; then + FILENAME=$(basename "$cred_file") + echo -e "${TAB}• $FILENAME" + fi + done <<< "$CRED_FILES" + echo "" + fi + + echo "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + +unmount_host_samba_share() { + + MOUNTS=$(mount -t cifs 2>/dev/null | awk '{print $3}' | sort -u || true) + FSTAB_MOUNTS=$(grep -E "cifs" /etc/fstab 2>/dev/null | grep -v "^#" | awk '{print $2}' | sort -u || true) + + + ALL_MOUNTS=$(echo -e "$MOUNTS\n$FSTAB_MOUNTS" | sort -u | grep -v "^$" || true) + + if [[ -z "$ALL_MOUNTS" ]]; then + dialog --backtitle "ProxMenux" --title "$(translate "No Mounts")" --msgbox "\n$(translate "No Samba mounts found on host.")" 8 50 + return + fi + + OPTIONS=() + while IFS= read -r mount_point; do + if [[ -n "$mount_point" ]]; then + + UNC_PATH=$(mount | grep "$mount_point" | awk '{print $1}' || grep "$mount_point" /etc/fstab | awk '{print $1}' || echo "Unknown") + SERVER=$(echo "$UNC_PATH" | cut -d/ -f3) + SHARE=$(echo "$UNC_PATH" | cut -d/ -f4) + OPTIONS+=("$mount_point" "$SERVER/$SHARE") + fi + done <<< "$ALL_MOUNTS" + + SELECTED_MOUNT=$(dialog --backtitle "ProxMenux" --title "$(translate "Unmount Samba Share")" --menu "$(translate "Select mount point to unmount:")" 20 80 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) + [[ -z "$SELECTED_MOUNT" ]] && return + + + UNC_PATH=$(mount | grep "$SELECTED_MOUNT" | awk '{print $1}' || grep "$SELECTED_MOUNT" /etc/fstab | awk '{print $1}' || echo "Unknown") + SERVER=$(echo "$UNC_PATH" | cut -d/ -f3) + SHARE=$(echo "$UNC_PATH" | cut -d/ -f4) + + + PROXMOX_STORAGE="" + if which pvesm >/dev/null 2>&1; then + + CIFS_STORAGES=$(pvesm status 2>/dev/null | grep "cifs" | awk '{print $1}' || true) + while IFS= read -r storage_id; do + if [[ -n "$storage_id" ]]; then + STORAGE_INFO=$(pvesm config "$storage_id" 2>/dev/null || true) + STORAGE_SERVER=$(echo "$STORAGE_INFO" | grep "server" | awk '{print $2}') + STORAGE_SHARE=$(echo "$STORAGE_INFO" | grep "share" | awk '{print $2}') + if [[ "$STORAGE_SERVER" == "$SERVER" && "$STORAGE_SHARE" == "$SHARE" ]]; then + PROXMOX_STORAGE="$storage_id" + break + fi + fi + done <<< "$CIFS_STORAGES" + fi + + + CONFIRMATION_MSG="$(translate "Are you sure you want to unmount this Samba share?")\n\n$(translate "Mount Point:"): $SELECTED_MOUNT\n$(translate "Server:"): $SERVER\n$(translate "Share:"): $SHARE\n\n$(translate "This will:")\n• $(translate "Unmount the Samba share")\n• $(translate "Remove from /etc/fstab")" + + if [[ -n "$PROXMOX_STORAGE" ]]; then + CONFIRMATION_MSG="$CONFIRMATION_MSG\n• $(translate "Remove Proxmox storage:"): $PROXMOX_STORAGE" + fi + + CONFIRMATION_MSG="$CONFIRMATION_MSG\n• $(translate "Remove credentials file")\n• $(translate "Remove mount point directory")" + + if whiptail --yesno "$CONFIRMATION_MSG" 18 80 --title "$(translate "Confirm Unmount")"; then + show_proxmenux_logo + msg_title "$(translate "Unmount Samba Share from Host")" + + + if [[ -n "$PROXMOX_STORAGE" ]]; then + if pvesm remove "$PROXMOX_STORAGE" 2>/dev/null; then + msg_ok "$(translate "Proxmox storage removed successfully.")" + else + msg_warn "$(translate "Failed to remove Proxmox storage, continuing with unmount...")" + fi + fi + + + if mount | grep -q "$SELECTED_MOUNT"; then + if umount "$SELECTED_MOUNT"; then + msg_ok "$(translate "Successfully unmounted.")" + else + msg_warn "$(translate "Failed to unmount. Trying force unmount...")" + if umount -f "$SELECTED_MOUNT" 2>/dev/null; then + msg_ok "$(translate "Force unmount successful.")" + else + msg_error "$(translate "Failed to unmount. Mount point may be busy.")" + echo -e "${TAB}$(translate "Try closing any applications using the mount point.")" + fi + fi + fi + + + CRED_FILE=$(grep "$SELECTED_MOUNT" /etc/fstab 2>/dev/null | grep -o "credentials=[^,]*" | cut -d= -f2 || true) + + + sed -i "\|[[:space:]]$SELECTED_MOUNT[[:space:]]|d" /etc/fstab + msg_ok "$(translate "Removed from /etc/fstab.")" + + + if [[ -n "$CRED_FILE" && "$CRED_FILE" != "guest" ]]; then + if test -f "$CRED_FILE"; then + rm -f "$CRED_FILE" + msg_ok "$(translate "Credentials file removed.")" + fi + fi + + echo -e "" + msg_ok "$(translate "Samba share unmounted successfully from host!")" + + if [[ -n "$PROXMOX_STORAGE" ]]; then + echo -e "${TAB}${BGN}$(translate "Proxmox storage removed:")${CL} ${BL}$PROXMOX_STORAGE${CL}" + fi + echo -e "${TAB}${BGN}$(translate "Mount point unmounted:")${CL} ${BL}$SELECTED_MOUNT${CL}" + echo -e "${TAB}${BGN}$(translate "Removed from fstab:")${CL} ${BL}Yes${CL}" + fi + + echo -e "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + +manage_proxmox_cifs_storage() { + if ! command -v pvesm >/dev/null 2>&1; then + dialog --backtitle "ProxMenux" --title "$(translate "Error")" --msgbox "\n$(translate "pvesm command not found. This should not happen on Proxmox.")" 8 60 + return + fi + + + CIFS_STORAGES=$(pvesm status 2>/dev/null | awk '$2 == "cifs" {print $1}') + if [[ -z "$CIFS_STORAGES" ]]; then + dialog --backtitle "ProxMenux" --title "$(translate "No CIFS Storage")" --msgbox "\n$(translate "No CIFS storage found in Proxmox.")" 8 60 + return + fi + + + OPTIONS=() + while IFS= read -r storage_id; do + if [[ -n "$storage_id" ]]; then + STORAGE_INFO=$(pvesm config "$storage_id" 2>/dev/null || true) + SERVER=$(echo "$STORAGE_INFO" | grep "server" | awk '{print $2}') + SHARE=$(echo "$STORAGE_INFO" | grep "share" | awk '{print $2}') + + if [[ -n "$SERVER" && -n "$SHARE" ]]; then + OPTIONS+=("$storage_id" "$SERVER/$SHARE") + else + OPTIONS+=("$storage_id" "$(translate "CIFS Storage")") + fi + fi + done <<< "$CIFS_STORAGES" + + SELECTED_STORAGE=$(dialog --backtitle "ProxMenux" --title "$(translate "Manage Proxmox CIFS Storage")" --menu "$(translate "Select storage to manage:")" 20 80 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) + [[ -z "$SELECTED_STORAGE" ]] && return + + + STORAGE_INFO=$(pvesm config "$SELECTED_STORAGE" 2>/dev/null || true) + SERVER=$(echo "$STORAGE_INFO" | grep "server" | awk '{print $2}') + SHARE=$(echo "$STORAGE_INFO" | grep "share" | awk '{print $2}') + CONTENT=$(echo "$STORAGE_INFO" | grep "content" | awk '{print $2}') + + if whiptail --yesno "$(translate "Are you sure you want to REMOVE storage:")\n\n$SELECTED_STORAGE\n\n$(translate "WARNING: This will permanently remove the storage from Proxmox configuration.")\n$(translate "The Samba mount on the host will NOT be affected.")" 14 80 --title "$(translate "Remove Storage")"; then + show_proxmenux_logo + msg_title "$(translate "Remove Storage")" + + if pvesm remove "$SELECTED_STORAGE" 2>/dev/null; then + msg_ok "$(translate "Storage removed successfully from Proxmox.")" + echo -e "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r + else + msg_error "$(translate "Failed to remove storage.")" + fi + fi +} + +test_host_samba_connectivity() { + show_proxmenux_logo + msg_title "$(translate "Test Samba Connectivity on Host")" + + echo -e "$(translate "Samba/CIFS Client Status on Proxmox Host:"):" + echo "==================================" + + + if which smbclient >/dev/null 2>&1; then + echo "$(translate "CIFS Client Tools: AVAILABLE")" + + + if which mount.cifs >/dev/null 2>&1; then + echo "$(translate "CIFS Mount Tools: AVAILABLE")" + else + echo "$(translate "CIFS Mount Tools: NOT AVAILABLE")" + fi + + echo "" + echo "$(translate "Current CIFS mounts on host:")" + CURRENT_MOUNTS=$(mount -t cifs 2>/dev/null || true) + if [[ -n "$CURRENT_MOUNTS" ]]; then + echo "$CURRENT_MOUNTS" + else + echo "$(translate "No CIFS mounts active on host.")" + fi + + echo "" + echo "$(translate "Testing network connectivity...")" + + + FSTAB_SERVERS=$(grep "cifs" /etc/fstab 2>/dev/null | awk '{print $1}' | cut -d/ -f3 | sort -u || true) + if [[ -n "$FSTAB_SERVERS" ]]; then + while IFS= read -r server; do + if [[ -n "$server" ]]; then + echo -n "$(translate "Testing") $server: " + if ping -c 1 -W 2 "$server" >/dev/null 2>&1; then + echo -e "${GN}$(translate "Reachable")${CL}" + + + if nc -z -w 2 "$server" 445 2>/dev/null; then + echo " $(translate "SMB port 445:"): ${GN}$(translate "Open")${CL}" + elif nc -z -w 2 "$server" 139 2>/dev/null; then + echo " $(translate "NetBIOS port 139:"): ${GN}$(translate "Open")${CL}" + else + echo " $(translate "SMB ports:"): ${RD}$(translate "Closed")${CL}" + fi + + + echo -n " $(translate "Guest access test:"): " + if smbclient -L "$server" -N >/dev/null 2>&1; then + echo -e "${GN}$(translate "Available")${CL}" + else + echo -e "${YW}$(translate "Requires authentication")${CL}" + fi + else + echo -e "${RD}$(translate "Unreachable")${CL}" + fi + fi + done <<< "$FSTAB_SERVERS" + else + echo "$(translate "No Samba servers configured to test.")" + fi + + + echo "" + echo "$(translate "Proxmox CIFS Storage Status:")" + if which pvesm >/dev/null 2>&1; then + CIFS_STORAGES=$(pvesm status 2>/dev/null | grep "cifs" || true) + if [[ -n "$CIFS_STORAGES" ]]; then + echo "$CIFS_STORAGES" + else + echo "$(translate "No CIFS storage configured in Proxmox.")" + fi + else + echo "$(translate "pvesm command not available.")" + fi + + + echo "" + echo "$(translate "Stored credentials:")" + CRED_FILES=$(find "$CREDENTIALS_DIR" -name "*.cred" 2>/dev/null || true) + if [[ -n "$CRED_FILES" ]]; then + while IFS= read -r cred_file; do + if [[ -n "$cred_file" ]]; then + FILENAME=$(basename "$cred_file") + echo " • $FILENAME" + fi + done <<< "$CRED_FILES" + else + echo " $(translate "No stored credentials found.")" + fi + + else + echo "$(translate "CIFS Client Tools: NOT AVAILABLE")" + echo "" + echo "$(translate "Installing CIFS client tools...")" + apt-get update &>/dev/null + apt-get install -y cifs-utils smbclient &>/dev/null + echo "$(translate "CIFS client tools installed.")" + fi + + echo "" + msg_success "$(translate "Press Enter to return to menu...")" + read -r +} + +# === Main Menu === +while true; do + CHOICE=$(dialog --backtitle "ProxMenux" --title "$(translate "Samba Host Manager - Proxmox Host")" \ + --menu "$(translate "Choose an option:")" 22 80 14 \ + "1" "$(translate "Mount Samba Share on Host")" \ + "2" "$(translate "View Current Host Samba Mounts")" \ + "3" "$(translate "Unmount Samba Share from Host")" \ + "4" "$(translate "Remove Proxmox CIFS Storage")" \ + "5" "$(translate "Test Samba Connectivity")" \ + "6" "$(translate "Exit")" \ + 3>&1 1>&2 2>&3) + + RETVAL=$? + if [[ $RETVAL -ne 0 ]]; then + exit 0 + fi + + case $CHOICE in + 1) mount_host_samba_share ;; + 2) view_host_samba_mounts ;; + 3) unmount_host_samba_share ;; + 4) manage_proxmox_cifs_storage ;; + 5) test_host_samba_connectivity ;; + 6) exit 0 ;; + *) exit 0 ;; + esac +done