Files
ubuntu-post-install/tools/mount-network-drive.sh
T
Claude 653963da08 tools: add edit mount option to mount-network-drive.sh
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
2026-06-08 13:28:00 +00:00

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 "$@"