From 06a163614d09f7ad30078d40e287440b99143576 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 8 Jun 2026 13:13:15 +0000 Subject: [PATCH] tools: add mount-network-drive.sh for SMB/CIFS and NFS shares Interactive script to mount network drives and persist them in /etc/fstab: - SMB/CIFS: prompts for share path, mount point, credentials (saved to /etc/samba/credentials. at 600/root), SMB version - NFS: prompts for export path, mount point, NFS version - Tests the mount before writing fstab - Backs up /etc/fstab before any edit - Remove flow: unmounts, removes fstab entry, optionally removes creds file - Shows currently mounted network shares and fstab entries - Uses nofail + _netdev so a missing share doesn't block boot https://claude.ai/code/session_014CCYqVwW6d6f5dw1qRokYt --- tools/mount-network-drive.sh | 326 +++++++++++++++++++++++++++++++++++ 1 file changed, 326 insertions(+) create mode 100644 tools/mount-network-drive.sh diff --git a/tools/mount-network-drive.sh b/tools/mount-network-drive.sh new file mode 100644 index 0000000..9753292 --- /dev/null +++ b/tools/mount-network-drive.sh @@ -0,0 +1,326 @@ +#!/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" +} + +# ── 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) 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) remove_mount ;; + 0|q|Q) break ;; + *) warn "Invalid choice." ;; + esac + echo "" + done + + ok "Done." +} + +main "$@"