Allows editing share path, mount point, and options for an existing fstab entry. CIFS entries also offer credential update (backs up the old credentials file before overwriting). Backs up fstab before any change and offers immediate remount to apply. https://claude.ai/code/session_014CCYqVwW6d6f5dw1qRokYt
451 lines
16 KiB
Bash
451 lines
16 KiB
Bash
#!/usr/bin/env bash
|
|
# mount-network-drive.sh — Mount a network share (SMB/CIFS or NFS) and add
|
|
# it to /etc/fstab so it survives reboots.
|
|
#
|
|
# Usage:
|
|
# sudo bash mount-network-drive.sh
|
|
#
|
|
# Supports:
|
|
# - SMB/CIFS (Windows shares, Samba, NAS)
|
|
# - NFS (Linux/NAS exports)
|
|
|
|
set -euo pipefail
|
|
|
|
# ── Helpers ───────────────────────────────────────────────────────────────────
|
|
info() { printf '\033[0;34m[INFO]\033[0m %s\n' "$*"; }
|
|
ok() { printf '\033[0;32m[OK]\033[0m %s\n' "$*"; }
|
|
warn() { printf '\033[1;33m[WARN]\033[0m %s\n' "$*"; }
|
|
err() { printf '\033[0;31m[ERROR]\033[0m %s\n' "$*" >&2; }
|
|
|
|
[[ "$(id -u)" == "0" ]] || { err "Run with sudo: sudo bash $0"; exit 1; }
|
|
|
|
# Actual user who invoked sudo
|
|
ACTUAL_USER="${SUDO_USER:-$USER}"
|
|
ACTUAL_HOME="$(getent passwd "$ACTUAL_USER" | cut -d: -f6)"
|
|
ACTUAL_UID="$(id -u "$ACTUAL_USER")"
|
|
ACTUAL_GID="$(id -g "$ACTUAL_USER")"
|
|
|
|
# ── Install packages ──────────────────────────────────────────────────────────
|
|
ensure_packages() {
|
|
local pkgs=()
|
|
case "$1" in
|
|
smb) command -v mount.cifs &>/dev/null || pkgs+=(cifs-utils) ;;
|
|
nfs) command -v mount.nfs &>/dev/null || pkgs+=(nfs-common) ;;
|
|
esac
|
|
if [[ ${#pkgs[@]} -gt 0 ]]; then
|
|
info "Installing: ${pkgs[*]}"
|
|
apt-get install -y "${pkgs[@]}" -qq
|
|
fi
|
|
}
|
|
|
|
# ── Already in fstab? ─────────────────────────────────────────────────────────
|
|
fstab_has() {
|
|
grep -qs "$1" /etc/fstab
|
|
}
|
|
|
|
# ── Show existing network mounts ──────────────────────────────────────────────
|
|
show_existing() {
|
|
local entries
|
|
entries=$(grep -E 'cifs|nfs' /etc/fstab 2>/dev/null || true)
|
|
if [[ -n "$entries" ]]; then
|
|
info "Existing network mounts in /etc/fstab:"
|
|
echo "$entries" | sed 's/^/ /'
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
# ── SMB/CIFS ──────────────────────────────────────────────────────────────────
|
|
mount_smb() {
|
|
ensure_packages smb
|
|
|
|
echo ""
|
|
echo " Enter the share details."
|
|
echo " Format: //server/share (e.g. //192.168.1.10/media or //nas/homes)"
|
|
echo ""
|
|
|
|
local share=""
|
|
while [[ -z "$share" ]]; do
|
|
read -r -p " Share path (//server/share): " share
|
|
done
|
|
|
|
local default_mount
|
|
default_mount="/mnt/$(echo "$share" | sed 's|//[^/]*/||; s|/|-|g')"
|
|
local mount_point=""
|
|
read -r -p " Mount point [$default_mount]: " mount_point
|
|
mount_point="${mount_point:-$default_mount}"
|
|
|
|
# Credentials
|
|
echo ""
|
|
local use_creds=""
|
|
read -r -p " Does this share require a username/password? [Y/n]: " use_creds
|
|
local creds_file="" creds_opt=""
|
|
|
|
if [[ "${use_creds,,}" != "n" ]]; then
|
|
local smb_user="" smb_pass="" smb_domain=""
|
|
read -r -p " Username: " smb_user
|
|
read -r -s -p " Password: " smb_pass; echo ""
|
|
read -r -p " Domain (leave blank if none): " smb_domain
|
|
|
|
# Store credentials file — root-owned, root-readable only
|
|
creds_file="/etc/samba/credentials.$(echo "$share" | sed 's|[^a-zA-Z0-9]|_|g')"
|
|
mkdir -p /etc/samba
|
|
cat > "$creds_file" << CREDS
|
|
username=${smb_user}
|
|
password=${smb_pass}
|
|
${smb_domain:+domain=${smb_domain}}
|
|
CREDS
|
|
chmod 600 "$creds_file"
|
|
chown root:root "$creds_file"
|
|
ok "Credentials saved to $creds_file (root-only, 600)"
|
|
creds_opt="credentials=${creds_file}"
|
|
else
|
|
creds_opt="guest"
|
|
fi
|
|
|
|
# SMB version
|
|
echo ""
|
|
echo " SMB version (leave blank to let the system negotiate):"
|
|
echo " 3.0 — modern NAS, Windows 2012+, Samba 4+"
|
|
echo " 2.1 — older NAS, Windows 7/2008 R2"
|
|
echo " 1.0 — legacy only (insecure, not recommended)"
|
|
local smb_ver=""
|
|
read -r -p " SMB version [auto]: " smb_ver
|
|
|
|
local ver_opt=""
|
|
[[ -n "$smb_ver" ]] && ver_opt=",vers=${smb_ver}"
|
|
|
|
local opts="uid=${ACTUAL_UID},gid=${ACTUAL_GID},${creds_opt}${ver_opt},iocharset=utf8,nofail,_netdev"
|
|
|
|
_do_mount "cifs" "$share" "$mount_point" "$opts"
|
|
}
|
|
|
|
# ── NFS ───────────────────────────────────────────────────────────────────────
|
|
mount_nfs() {
|
|
ensure_packages nfs
|
|
|
|
echo ""
|
|
echo " Format: server:/export (e.g. 192.168.1.10:/mnt/data or nas:/homes)"
|
|
echo ""
|
|
|
|
local share=""
|
|
while [[ -z "$share" ]]; do
|
|
read -r -p " NFS export (server:/path): " share
|
|
done
|
|
|
|
local default_mount
|
|
default_mount="/mnt/$(echo "$share" | sed 's|[:/]|-|g; s|^-||')"
|
|
local mount_point=""
|
|
read -r -p " Mount point [$default_mount]: " mount_point
|
|
mount_point="${mount_point:-$default_mount}"
|
|
|
|
echo ""
|
|
echo " NFS version:"
|
|
echo " 4 — recommended (NFSv4, supports Kerberos, ACLs)"
|
|
echo " 3 — older servers/NAS"
|
|
local nfs_ver=""
|
|
read -r -p " NFS version [4]: " nfs_ver
|
|
nfs_ver="${nfs_ver:-4}"
|
|
|
|
local opts="nfsvers=${nfs_ver},rw,soft,intr,nofail,_netdev"
|
|
|
|
_do_mount "nfs" "$share" "$mount_point" "$opts"
|
|
}
|
|
|
|
# ── Common mount + fstab logic ────────────────────────────────────────────────
|
|
_do_mount() {
|
|
local fstype="$1" share="$2" mount_point="$3" opts="$4"
|
|
|
|
# Create mount point
|
|
if [[ ! -d "$mount_point" ]]; then
|
|
mkdir -p "$mount_point"
|
|
ok "Created mount point: $mount_point"
|
|
fi
|
|
|
|
# Test mount
|
|
echo ""
|
|
info "Testing mount..."
|
|
if mount -t "$fstype" -o "$opts" "$share" "$mount_point"; then
|
|
ok "Mounted successfully at $mount_point"
|
|
else
|
|
err "Mount failed. Check the share path, credentials, and network connectivity."
|
|
rmdir "$mount_point" 2>/dev/null || true
|
|
return 1
|
|
fi
|
|
|
|
# Show what's there
|
|
echo ""
|
|
info "Contents of $mount_point:"
|
|
ls "$mount_point" | head -20 | sed 's/^/ /' || true
|
|
echo ""
|
|
|
|
# fstab
|
|
local add_fstab=""
|
|
read -r -p " Add to /etc/fstab so it mounts on boot? [Y/n]: " add_fstab
|
|
if [[ "${add_fstab,,}" != "n" ]]; then
|
|
if fstab_has "$mount_point"; then
|
|
warn "$mount_point already in /etc/fstab — skipping."
|
|
else
|
|
# Backup fstab
|
|
local bk="/etc/fstab.backup.$(date +%Y%m%d-%H%M%S)"
|
|
cp /etc/fstab "$bk"
|
|
info "Backed up /etc/fstab → $bk"
|
|
|
|
printf '\n# %s — added by mount-network-drive.sh %s\n' \
|
|
"$share" "$(date +%Y-%m-%d)" >> /etc/fstab
|
|
printf '%-40s %-25s %-6s %s 0 0\n' \
|
|
"$share" "$mount_point" "$fstype" "$opts" >> /etc/fstab
|
|
|
|
ok "Added to /etc/fstab"
|
|
info "Verify with: sudo mount -a"
|
|
fi
|
|
fi
|
|
|
|
# Ownership hint
|
|
echo ""
|
|
info "To make this drive writable by $ACTUAL_USER:"
|
|
echo " sudo chown -R ${ACTUAL_USER}:${ACTUAL_USER} $mount_point"
|
|
echo ""
|
|
ok "Done. $share is mounted at $mount_point"
|
|
}
|
|
|
|
# ── Edit a mount ─────────────────────────────────────────────────────────────
|
|
edit_mount() {
|
|
echo ""
|
|
local entries=()
|
|
while IFS= read -r line; do
|
|
[[ -z "$line" || "$line" == \#* ]] && continue
|
|
entries+=("$line")
|
|
done < <(grep -E '\bcifs\b|\bnfs\b' /etc/fstab 2>/dev/null || true)
|
|
|
|
if [[ ${#entries[@]} -eq 0 ]]; then
|
|
warn "No network mounts found in /etc/fstab."
|
|
return 0
|
|
fi
|
|
|
|
echo " Network mounts in /etc/fstab:"
|
|
echo ""
|
|
local i=1
|
|
for e in "${entries[@]}"; do
|
|
local mp; mp=$(echo "$e" | awk '{print $2}')
|
|
local src; src=$(echo "$e" | awk '{print $1}')
|
|
printf " %2d) %-30s %s\n" "$i" "$src" "$mp"
|
|
i=$(( i + 1 ))
|
|
done
|
|
echo ""
|
|
|
|
local choice=""
|
|
read -r -p " Edit entry number [cancel]: " choice
|
|
[[ -z "$choice" || ! "$choice" =~ ^[0-9]+$ ]] && { info "Cancelled."; return 0; }
|
|
|
|
local idx=$(( choice - 1 ))
|
|
[[ "$idx" -lt 0 || "$idx" -ge ${#entries[@]} ]] && { warn "Invalid selection."; return 0; }
|
|
|
|
local target_entry="${entries[$idx]}"
|
|
local target_src; target_src=$(echo "$target_entry" | awk '{print $1}')
|
|
local target_mp; target_mp=$(echo "$target_entry" | awk '{print $2}')
|
|
local target_fs; target_fs=$(echo "$target_entry" | awk '{print $3}')
|
|
local target_opts; target_opts=$(echo "$target_entry" | awk '{print $4}')
|
|
|
|
echo ""
|
|
echo " Current values:"
|
|
printf " Share : %s\n" "$target_src"
|
|
printf " Mount point: %s\n" "$target_mp"
|
|
printf " Type : %s\n" "$target_fs"
|
|
printf " Options : %s\n" "$target_opts"
|
|
echo ""
|
|
echo " Press Enter to keep the current value."
|
|
echo ""
|
|
|
|
# Share path
|
|
local new_src=""
|
|
read -r -p " Share path [$target_src]: " new_src
|
|
new_src="${new_src:-$target_src}"
|
|
|
|
# Mount point
|
|
local new_mp=""
|
|
read -r -p " Mount point [$target_mp]: " new_mp
|
|
new_mp="${new_mp:-$target_mp}"
|
|
|
|
# Options — offer guided or raw edit
|
|
echo ""
|
|
echo " Options — edit raw fstab options string, or press Enter to keep."
|
|
local new_opts=""
|
|
read -r -p " Options [$target_opts]: " new_opts
|
|
new_opts="${new_opts:-$target_opts}"
|
|
|
|
# Credentials update (CIFS only)
|
|
local creds_file="/etc/samba/credentials.$(echo "$target_src" | sed 's|[^a-zA-Z0-9]|_|g')"
|
|
if [[ "$target_fs" == "cifs" && -f "$creds_file" ]]; then
|
|
echo ""
|
|
local update_creds=""
|
|
read -r -p " Update credentials (username/password)? [y/N]: " update_creds
|
|
if [[ "${update_creds,,}" == "y" ]]; then
|
|
local smb_user="" smb_pass="" smb_domain=""
|
|
read -r -p " Username: " smb_user
|
|
read -r -s -p " Password: " smb_pass; echo ""
|
|
read -r -p " Domain (leave blank if none): " smb_domain
|
|
local bk_creds="${creds_file}.backup.$(date +%Y%m%d-%H%M%S)"
|
|
cp "$creds_file" "$bk_creds"
|
|
cat > "$creds_file" << CREDS
|
|
username=${smb_user}
|
|
password=${smb_pass}
|
|
${smb_domain:+domain=${smb_domain}}
|
|
CREDS
|
|
chmod 600 "$creds_file"
|
|
chown root:root "$creds_file"
|
|
ok "Credentials updated (old saved to $(basename "$bk_creds"))"
|
|
fi
|
|
fi
|
|
|
|
# Write changes
|
|
local bk="/etc/fstab.backup.$(date +%Y%m%d-%H%M%S)"
|
|
cp /etc/fstab "$bk"
|
|
info "Backed up /etc/fstab → $bk"
|
|
|
|
# Replace the old line with the new one
|
|
local new_line
|
|
new_line=$(printf '%-40s %-25s %-6s %s 0 0' "$new_src" "$new_mp" "$target_fs" "$new_opts")
|
|
# Escape for sed
|
|
local escaped_old escaped_new
|
|
escaped_old=$(printf '%s\n' "$target_entry" | sed 's/[[\.*^$()+?{|]/\\&/g')
|
|
escaped_new=$(printf '%s\n' "$new_line" | sed 's/[[\.*^$()+?{|]/\\&/g; s|/|\\/|g')
|
|
sed -i "s|${escaped_old}|${new_line}|" /etc/fstab
|
|
ok "fstab updated"
|
|
|
|
# Remount if mount point changed or options changed
|
|
if mountpoint -q "$target_mp" 2>/dev/null; then
|
|
echo ""
|
|
local remount=""
|
|
read -r -p " Remount now to apply changes? [Y/n]: " remount
|
|
if [[ "${remount,,}" != "n" ]]; then
|
|
umount "$target_mp" 2>/dev/null || warn "Could not unmount cleanly — may still be in use"
|
|
[[ "$new_mp" != "$target_mp" ]] && mkdir -p "$new_mp"
|
|
if mount -t "$target_fs" -o "$new_opts" "$new_src" "$new_mp"; then
|
|
ok "Remounted at $new_mp"
|
|
else
|
|
err "Remount failed — check options and connectivity."
|
|
info "Manual retry: sudo mount -t $target_fs -o $new_opts $new_src $new_mp"
|
|
fi
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# ── Remove a mount ────────────────────────────────────────────────────────────
|
|
remove_mount() {
|
|
echo ""
|
|
local entries=()
|
|
while IFS= read -r line; do
|
|
[[ -z "$line" || "$line" == \#* ]] && continue
|
|
entries+=("$line")
|
|
done < <(grep -E '\bcifs\b|\bnfs\b' /etc/fstab 2>/dev/null || true)
|
|
|
|
if [[ ${#entries[@]} -eq 0 ]]; then
|
|
warn "No network mounts found in /etc/fstab."
|
|
return 0
|
|
fi
|
|
|
|
echo " Network mounts in /etc/fstab:"
|
|
echo ""
|
|
local i=1
|
|
for e in "${entries[@]}"; do
|
|
local mp; mp=$(echo "$e" | awk '{print $2}')
|
|
local src; src=$(echo "$e" | awk '{print $1}')
|
|
printf " %2d) %-30s %s\n" "$i" "$src" "$mp"
|
|
i=$(( i + 1 ))
|
|
done
|
|
echo ""
|
|
|
|
local choice=""
|
|
read -r -p " Remove entry number [cancel]: " choice
|
|
[[ -z "$choice" || ! "$choice" =~ ^[0-9]+$ ]] && { info "Cancelled."; return 0; }
|
|
|
|
local idx=$(( choice - 1 ))
|
|
[[ "$idx" -lt 0 || "$idx" -ge ${#entries[@]} ]] && { warn "Invalid selection."; return 0; }
|
|
|
|
local target_entry="${entries[$idx]}"
|
|
local target_mp; target_mp=$(echo "$target_entry" | awk '{print $2}')
|
|
local target_src; target_src=$(echo "$target_entry" | awk '{print $1}')
|
|
|
|
warn "Will unmount $target_mp and remove from /etc/fstab."
|
|
local confirm=""
|
|
read -r -p " Confirm? [y/N]: " confirm
|
|
[[ "${confirm,,}" == "y" ]] || { info "Cancelled."; return 0; }
|
|
|
|
# Unmount
|
|
if mountpoint -q "$target_mp" 2>/dev/null; then
|
|
umount "$target_mp" && ok "Unmounted $target_mp" || warn "umount failed — may still be in use"
|
|
fi
|
|
|
|
# Remove from fstab (match the mount point field)
|
|
local bk="/etc/fstab.backup.$(date +%Y%m%d-%H%M%S)"
|
|
cp /etc/fstab "$bk"
|
|
info "Backed up /etc/fstab → $bk"
|
|
# Remove the entry line and its preceding comment if added by this script
|
|
sed -i "\|${target_src}|d" /etc/fstab
|
|
sed -i "\|# ${target_src} — added by mount-network-drive.sh|d" /etc/fstab
|
|
ok "Removed from /etc/fstab"
|
|
|
|
# Remove credentials file if it exists
|
|
local creds_file
|
|
creds_file="/etc/samba/credentials.$(echo "$target_src" | sed 's|[^a-zA-Z0-9]|_|g')"
|
|
if [[ -f "$creds_file" ]]; then
|
|
local rm_creds=""
|
|
read -r -p " Remove credentials file $creds_file? [Y/n]: " rm_creds
|
|
[[ "${rm_creds,,}" != "n" ]] && rm -f "$creds_file" && ok "Removed $creds_file"
|
|
fi
|
|
}
|
|
|
|
# ── Show current mounts ───────────────────────────────────────────────────────
|
|
show_mounts() {
|
|
echo ""
|
|
echo " Currently mounted network shares:"
|
|
echo ""
|
|
mount | grep -E 'type (cifs|nfs)' | awk '{print " " $1 " → " $3}' || echo " (none)"
|
|
echo ""
|
|
echo " /etc/fstab network entries:"
|
|
echo ""
|
|
grep -E '\bcifs\b|\bnfs\b' /etc/fstab | sed 's/^/ /' || echo " (none)"
|
|
echo ""
|
|
}
|
|
|
|
# ── Main ──────────────────────────────────────────────────────────────────────
|
|
main() {
|
|
echo ""
|
|
echo "┌──────────────────────────────────────────────────────────┐"
|
|
echo "│ Network Drive Mount Manager │"
|
|
echo "│ SMB/CIFS (Windows/Samba) and NFS shares │"
|
|
echo "└──────────────────────────────────────────────────────────┘"
|
|
echo ""
|
|
|
|
show_existing
|
|
|
|
while true; do
|
|
echo " What would you like to do?"
|
|
echo " 1) Mount an SMB/CIFS share (Windows, Samba, NAS)"
|
|
echo " 2) Mount an NFS share"
|
|
echo " 3) Show current network mounts"
|
|
echo " 4) Edit a mount"
|
|
echo " 5) Remove a mount"
|
|
echo " 0) Quit"
|
|
echo ""
|
|
read -r -p " Choice [1]: " action
|
|
action="${action:-1}"
|
|
echo ""
|
|
|
|
case "$action" in
|
|
1) mount_smb ;;
|
|
2) mount_nfs ;;
|
|
3) show_mounts ;;
|
|
4) edit_mount ;;
|
|
5) remove_mount ;;
|
|
0|q|Q) break ;;
|
|
*) warn "Invalid choice." ;;
|
|
esac
|
|
echo ""
|
|
done
|
|
|
|
ok "Done."
|
|
}
|
|
|
|
main "$@"
|