Files
ProxMenux/scripts/share/samba_client.sh
T
MacRimiandClaude Opus 5 da8a480eff Add audit and reports page, and a change journal
ProxMenux modifies the host: it rewrites configuration files, installs packages, enables services. Until now nobody could say afterwards what had changed, and showing the script does not answer that question — a four-hundred-line function may alter two values, and the reader has no way to know which two. This adds the two halves of an answer.

The change journal records what ProxMenux does as it does it. Eleven bash primitives capture the previous state, apply the change and record it in the same step, writing to a spool that the Monitor reads back. One hundred and thirteen functions across twenty-five scripts are instrumented, covering post-install, shared storage, security tooling, container conversions, disk operations and the PVE 8 to 9 upgrade path. The page shows the difference — rotate 7 becoming rotate 14 — and never the script. Restore and backup scripts are deliberately left out: a restore puts the host back to a state some other script already recorded.

The Audit and reports page answers the other half: what state is this host in, regardless of who put it there. Forty-three checks across seven areas read the host and classify each result as critical, warning, observation, conformant, unverified or not applicable, with the evidence they read attached to each one. A declared policy lets the reader say what this particular host is expected to do — which guests must have a backup, which storages are essential — so the report judges the host against its own intent rather than a generic template. An inventory records the hardware, network and guest topology behind those readings, a comparison shows what moved between two runs, and six report profiles produce a printable document scoped to what the reader needs. Everything is available in the eight supported languages.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 21:06:04 +02:00

1125 lines
44 KiB
Bash

#!/bin/bash
# ==========================================================
# ProxMenux - Samba / CIFS Client Manager for LXC
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : GPL-3.0
# https://github.com/MacRimi/ProxMenux/blob/main/LICENSE
# Version : 1.0
# ==========================================================
# Description:
# Manages Samba / CIFS client mounts from inside a Proxmox LXC
# container. Requires a privileged container (mount.cifs needs
# capabilities that unprivileged CTs do not expose).
#
# Features:
# - Mount CIFS shares (temporary and permanent via /etc/fstab).
# - List current CIFS mounts inside the CT.
# - Unmount and remove shares cleanly.
# - Auto-discover Samba servers on the local network.
# - Credentials stored in /etc/samba/credentials (root:0600).
# ==========================================================
# Configuration
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
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
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
SHARE_COMMON_FILE="$LOCAL_SCRIPTS/global/share-common.func"
if ! source "$SHARE_COMMON_FILE" 2>/dev/null; then
msg_error "$(translate "Could not load shared functions. Script cannot continue.")"
exit 1
fi
load_language
initialize_cache
select_privileged_lxc
install_samba_client() {
local FUNC_VERSION="1.0"
pmx_journal_context "install_samba_client" "$FUNC_VERSION"
pmx_record_execution "install and prepare Samba client in CT ${CTID}" \
"pct exec ${CTID} -- install cifs-utils and smbclient; create ${CREDENTIALS_DIR}"
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...")"
# Mirror of nfs_client.sh: refuse non-Debian-family CTs early.
if ! pct exec "$CTID" -- bash -c 'command -v apt-get' &>/dev/null; then
msg_error "$(translate "This container does not have apt-get. Samba client installation only supports Debian/Ubuntu containers.")"
return 1
fi
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() {
local FUNC_VERSION="1.0"
pmx_journal_context "discover_samba_servers" "$FUNC_VERSION"
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
pmx_install_pkg "$pkg"
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 <ACTIVE>/ {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=$(whiptail --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=$(whiptail --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 \
"1" "$(translate "Create new folder in /mnt")" \
"2" "$(translate "Select from existing folders in /mnt")" \
"3" "$(translate "Enter custom path")" 3>&1 1>&2 2>&3)
case "$METHOD" in
1)
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
;;
2)
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
;;
3)
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 \
"1" "$(translate "Default options read/write")" \
"2" "$(translate "Read-only mount")" \
"3" "$(translate "Custom options")" 3>&1 1>&2 2>&3)
case "$MOUNT_TYPE" in
1)
MOUNT_OPTIONS="rw,file_mode=0664,dir_mode=0775,iocharset=utf8"
;;
2)
MOUNT_OPTIONS="ro,file_mode=0444,dir_mode=0555,iocharset=utf8"
;;
3)
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() {
local FUNC_VERSION="1.0"
pmx_journal_context "create_credentials_file" "$FUNC_VERSION"
if [[ "$USE_GUEST" == "true" ]]; then
return 0
fi
CRED_FILE="$CREDENTIALS_DIR/${SAMBA_SERVER}_${SAMBA_SHARE}.cred"
pmx_record_execution "create Samba credentials file ${CRED_FILE} in CT ${CTID}" \
"pct exec ${CTID} -- write credentials file and chmod 600"
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() {
local FUNC_VERSION="1.0"
# 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
pmx_journal_context "mount_samba_share" "$FUNC_VERSION"
pmx_record_execution "mount Samba share //${SAMBA_SERVER}/${SAMBA_SHARE} in CT ${CTID} at ${MOUNT_POINT}" \
"pct exec ${CTID} -- mount CIFS share; persistent=${PERMANENT_MOUNT}"
show_proxmenux_logo
msg_title "$(translate "Installing Samba Client in LXC")"
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 --in-place "\|$MOUNT_POINT|d" /etc/fstab
FSTAB_ENTRY="$UNC_PATH $MOUNT_POINT cifs ${FULL_OPTIONS},_netdev,x-systemd.automount,noauto 0 0"
pct exec "$CTID" -- bash -c "printf '%s\\n' '$FSTAB_ENTRY' | tee -a /etc/fstab >/dev/null"
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() {
local FUNC_VERSION="1.0"
pmx_journal_context "unmount_samba_share" "$FUNC_VERSION"
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)
pmx_record_execution "remove Samba mount ${SELECTED_MOUNT} from CT ${CTID}" \
"remove CT fstab entry and credentials file when present"
pct exec "$CTID" -- sed --in-place "\|[[: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