Files
ProxMenux/scripts/share/nfs_client.sh
T
MacRimi 085cbf7e68 scope the change journal to host changes, in three sections
The change journal exists so a sysadmin sees what ProxMenux changed on the host — its own configuration, packages and services — not how it uses the host or configures a guest. Several scripts recorded operations that are neither: disk passthrough to a VM, container conversions, VM import/export, mounting a share into an LXC. Those are restored to their original, uninstrumented form. Scripts that operate on the host while also installing a package now record only the package: format-disk keeps its exFAT-tools install, the UUP ISO builder its build dependencies, and the share/host scripts their packages, services and /etc/fstab writes, while the mount and unmount operations they used to log are dropped.

The page now reads in three sections: what ProxMenux optimized after install (each function under its menu name), what its other host scripts changed (by script), and what it installed (packages and utilities, each referencing the script that installed it). A file reads as created or modified with its diff, a service shows its state transition, and the undo line appears only when a revert is possible.
2026-09-10 15:20:39 +02:00

737 lines
29 KiB
Bash

#!/bin/bash
# ==========================================================
# ProxMenux - NFS 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 NFS client mounts from inside a Proxmox LXC container.
# Requires a privileged container (kernel NFS client needs
# capabilities that unprivileged CTs do not expose).
#
# Features:
# - Mount NFS shares (temporary and permanent via /etc/fstab).
# - List current NFS mounts inside the CT.
# - Unmount and remove NFS shares cleanly.
# - Auto-discover NFS servers on the local network.
# ==========================================================
# Configuration
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
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
# Load shared functions
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_nfs_client() {
local FUNC_VERSION="1.0"
pmx_journal_context "install_nfs_client" "$FUNC_VERSION"
if pct exec "$CTID" -- dpkg -s nfs-common &>/dev/null; then
return 0
fi
show_proxmenux_logo
msg_title "$(translate "Installing NFS Client in LXC")"
# Pre-flight: refuse non-Debian-family CTs. The script targets
# `apt-get` only — Alpine / Rocky / AlmaLinux fail with cryptic
# errors mid-flow. Audit Tier 6 — `nfs_client.sh`/`samba_client.sh`
# asume distro Debian-family sin detección.
if ! pct exec "$CTID" -- bash -c 'command -v apt-get' &>/dev/null; then
msg_error "$(translate "This container does not have apt-get. NFS client installation only supports Debian/Ubuntu containers.")"
msg_success "$(translate "Press Enter to return to menu...")"
read -r
return 1
fi
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() {
local FUNC_VERSION="1.0"
pmx_journal_context "discover_nfs_servers" "$FUNC_VERSION"
show_proxmenux_logo
msg_title "$(translate "Mount NFS Client in LXC")"
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
pmx_install_pkg nmap
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=$(whiptail --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")" 3>&1 1>&2 2>&3)
case "$METHOD" in
auto)
discover_nfs_servers || return 1
;;
manual)
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
;;
*)
return 1
;;
esac
return 0
}
select_nfs_export() {
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 \
"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)
# 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
;;
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 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
;;
3)
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 \
"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,hard,rsize=1048576,wsize=1048576,timeo=600,retrans=2"
;;
2)
MOUNT_OPTIONS="ro,hard,rsize=1048576,wsize=1048576,timeo=600,retrans=2"
;;
3)
MOUNT_OPTIONS=$(whiptail --inputbox "$(translate "Enter custom mount options:")" \
10 70 "rw,hard,rsize=1048576,wsize=1048576,timeo=600,retrans=2" \
--title "$(translate "Custom Options")" 3>&1 1>&2 2>&3)
[[ $? -ne 0 ]] && return 1
[[ -z "$MOUNT_OPTIONS" ]] && MOUNT_OPTIONS="rw,hard"
;;
*)
MOUNT_OPTIONS="rw,hard,rsize=65536,wsize=65536,timeo=600,retrans=2"
;;
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() {
local FUNC_VERSION="1.0"
# Step 0: Install NFS client first
install_nfs_client || return
# Step 1: Select server
select_nfs_server || return
show_proxmenux_logo
msg_title "$(translate "Mount NFS Share on Host")"
msg_ok "$(translate "NFS server Selected")"
# Step 2: Select export
select_nfs_export || return
msg_ok "$(translate "NFS export Selected")"
# 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
pmx_journal_context "mount_nfs_share" "$FUNC_VERSION"
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 --in-place "\|$MOUNT_POINT|d" /etc/fstab
FSTAB_ENTRY="$NFS_PATH $MOUNT_POINT nfs ${MOUNT_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
# 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() {
local FUNC_VERSION="1.0"
pmx_journal_context "unmount_nfs_share" "$FUNC_VERSION"
# 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 --in-place "\|[[:space:]]$SELECTED_MOUNT[[:space:]]|d" /etc/fstab
msg_ok "$(translate "Removed from /etc/fstab.")"
# Actually unmount it now (the previous version only edited fstab,
# so the share remained mounted until the CT rebooted). Try a
# graceful umount first, fall back to lazy umount if busy. Audit
# Tier 7 — `unmount_nfs_share` only borra fstab.
if pct exec "$CTID" -- mountpoint -q "$SELECTED_MOUNT" 2>/dev/null; then
if pct exec "$CTID" -- umount "$SELECTED_MOUNT" 2>/dev/null; then
msg_ok "$(translate "Unmounted") $SELECTED_MOUNT"
elif pct exec "$CTID" -- umount -l "$SELECTED_MOUNT" 2>/dev/null; then
msg_warn "$(translate "Mount was busy — performed lazy unmount") $SELECTED_MOUNT"
else
msg_warn "$(translate "Could not unmount") $SELECTED_MOUNT $(translate "automatically. Reboot LXC to fully release.")"
fi
fi
echo -e ""
msg_ok "$(translate "NFS share unmount completed.")"
fi
echo -e ""
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
test_nfs_connectivity() {
local FUNC_VERSION="1.0"
pmx_journal_context "test_nfs_connectivity" "$FUNC_VERSION"
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