Skip to content

Instantly share code, notes, and snippets.

@lsr00ter
Last active July 22, 2025 03:20
Show Gist options
  • Save lsr00ter/816486fe6d317de22b83801ca7977e1b to your computer and use it in GitHub Desktop.
Save lsr00ter/816486fe6d317de22b83801ca7977e1b to your computer and use it in GitHub Desktop.
Mount an NFS share on macOS gracefully to ensure seamless and reliable access.
#!/bin/bash
# Default values
FILE="/etc/nfs.conf"
LINE="nfs.client.mount.options = vers=4"
REMOTE_SERVER=""
SERVER_DIR=""
MOUNTED_DIR=""
UNMOUNT=false
# Function to display usage
usage() {
echo "Usage: $0 [-s <remote_server>] [-m <mount_dir>] [-f <config_file>] [-l <line>] [-u]"
echo " -s: Remote NFS server (e.g., 192.168.1.100)"
echo " -m: Local mount directory (e.g., /mnt/nfs)"
echo " -f: NFS configuration file (default: /etc/nfs.conf)"
echo " -l: Line to append (default: nfs.client.mount.options = vers=4)"
echo " -u: Unmount the NFS share instead of mounting"
exit 1
}
# Function to check if running as root
check_root() {
if [[ $EUID -ne 0 ]]; then
echo "Error: This operation requires root privileges. Please run with sudo."
exit 1
fi
}
# Function to validate IP or hostname
validate_server() {
local server=$1
if [[ -z "$server" ]]; then
echo "Error: Remote server cannot be empty."
exit 1
fi
if ! ping -c 1 -W 2 "$server" &>/dev/null; then
echo "Error: Cannot reach server $server. Check network or server address."
exit 1
fi
}
# Function to prompt for input if variable is empty
prompt_if_empty() {
local var_name=$1
local prompt_msg=$2
local var_value=${!var_name}
if [[ -z "$var_value" ]]; then
read -e -p "$prompt_msg: " input
input="${input/#\~/$HOME}"
eval "$var_name='$input'"
fi
}
# Function to select NFS export from showmount
select_nfs_export() {
local server=$1
echo "Fetching NFS exports from $server..."
exports=$(showmount -e "$server" | tail -n +2)
if [[ -z "$exports" ]]; then
echo "Error: No exports found on $server."
exit 1
fi
# Convert exports to array, preserving spaces
IFS=$'\n' read -d '' -r -a export_array <<< "$exports"
echo "Available NFS exports:"
for i in "${!export_array[@]}"; do
# Extract the full path, including spaces, before the client list
path=$(echo "${export_array[$i]}" | sed 's/^[[:space:]]*\(.*\)[[:space:]]\+.*$/\1/')
echo "[$i] $path"
done
# Prompt for selection
while true; do
read -p "Select an export by number (0-${#export_array[@]}-1): " choice
if [[ "$choice" =~ ^[0-9]+$ && "$choice" -ge 0 && "$choice" -lt "${#export_array[@]}" ]]; then
# Extract the selected path, including spaces
SERVER_DIR=$(echo "${export_array[$choice]}" | sed 's/^[[:space:]]*\(.*\)[[:space:]]\+.*$/\1/')
break
else
echo "Invalid choice. Please enter a number between 0 and ${#export_array[@]}-1."
fi
done
}
# Parse command-line arguments
while getopts "s:m:f:l:u" opt; do
case $opt in
s) REMOTE_SERVER="$OPTARG" ;;
m) MOUNTED_DIR="$OPTARG" ;;
f) FILE="$OPTARG" ;;
l) LINE="$OPTARG" ;;
u) UNMOUNT=true ;;
*) usage ;;
esac
done
# Prompt for missing values if not provided
prompt_if_empty "REMOTE_SERVER" "Enter remote NFS server (e.g., 192.168.1.100)"
prompt_if_empty "MOUNTED_DIR" "Enter local mount directory (e.g., /mnt/nfs)"
# Validate inputs
if [[ ! -f "$FILE" && ! $UNMOUNT ]]; then
echo "Error: Configuration file $FILE does not exist."
exit 1
fi
validate_server "$REMOTE_SERVER"
if [[ -z "$MOUNTED_DIR" || ! "$MOUNTED_DIR" =~ ^/ ]]; then
echo "Error: Mount directory must be a valid absolute path (e.g., /mnt/nfs)."
exit 1
fi
# Select NFS export if mounting
if ! $UNMOUNT; then
select_nfs_export "$REMOTE_SERVER"
if [[ -z "$SERVER_DIR" || ! "$SERVER_DIR" =~ ^/ ]]; then
echo "Error: Selected server directory is invalid."
exit 1
fi
fi
# Unmount logic
if $UNMOUNT; then
check_root
if grep -qE "^${REMOTE_SERVER}:${SERVER_DIR// /\\ } ${MOUNTED_DIR// /\\ } " /proc/mounts; then
if sudo umount "$MOUNTED_DIR"; then
echo "Successfully unmounted $MOUNTED_DIR."
sudo rmdir "$MOUNTED_DIR" 2>/dev/null && echo "Removed mount directory $MOUNTED_DIR."
else
echo "Error: Failed to unmount $MOUNTED_DIR."
exit 1
fi
else
echo "Error: $MOUNTED_DIR is not a mount point."
exit 1
fi
..................................................................................
exit 0
fi
# Check and append configuration line
CONFIG_MODIFIED=false
if ! grep -Fx "$LINE" "$FILE" > /dev/null; then
check_root
if echo "$LINE" >> "$FILE"; then
echo "Line appended to $FILE: $LINE"
CONFIG_MODIFIED=true
else
echo "Error: Failed to append $LINE to $FILE."
exit 1
fi
fi
# Restart NFS service if configuration was modified
if $CONFIG_MODIFIED; then
if sudo systemctl restart nfs-client.target 2>/dev/null || systemctl restart nfs-utils.service 2>/dev/null; then
echo "NFS client service restarted."
else
echo "Warning: Failed to restart NFS service. Changes may not take effect until reboot."
fi
fi
# Create mount directory
check_root
if [[ ! -d "$MOUNTED_DIR" ]]; then
if sudo mkdir -p "$MOUNTED_DIR"; then
echo "Created mount directory $MOUNTED_DIR."
else
echo "Error: Failed to create mount directory $MOUNTED_DIR."
exit 1
fi
elif [[ -n "$(ls -A "$MOUNTED_DIR")" ]]; then
echo "Error: Mount directory $MOUNTED_DIR is not empty."
exit 1
fi
# Mount NFS share
if sudo mount -o rw,vers=4 -t nfs "$REMOTE_SERVER:$SERVER_DIR" "$MOUNTED_DIR"; then
echo "Successfully mounted $REMOTE_SERVER:$SERVER_DIR on $MOUNTED_DIR."
else
echo "Error: Failed to mount $REMOTE_SERVER:$SERVER_DIR on $MOUNTED_DIR."
exit 1
fi
# Verify mount
if grep -qE "^${REMOTE_SERVER}:${SERVER_DIR// /\\ } ${MOUNTED_DIR// /\\ } " /proc/mounts && df -h "$MOUNTED_DIR" > /dev/null; then
echo "Mount verified. Details:"
df -h "$MOUNTED_DIR"
else
echo "Warning: Mount appears incomplete or inaccessible."
exit 1
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment