#!/usr/bin/env bash # install-network_drive.sh — Auto-mount Synology (SMB/CIFS) shares under /mnt/Synology. # Hardened: safe input handling, no eval-on-username, hostname validation, # fstab managed via begin/end markers (idempotent re-run), creds file 0600, # GNOME dock pinning is best-effort. set -euo pipefail IFS=$'\n\t' readonly SCRIPT_NAME="${0##*/}" DRY_RUN=0 SERVER_ADDR="" SMB_USER="" SMB_PASS="" MASTER_DIR="/mnt/Synology" usage() { cat <&2; } die() { printf '\033[1;31m[%s] ERROR:\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*" >&2; exit 1; } run() { if (( DRY_RUN )); then printf ' DRY-RUN: %s\n' "$*"; else eval "$@"; fi; } trap 'rc=$?; (( rc )) && printf "\033[1;31m[%s] failed at line %s (exit %d)\033[0m\n" "${SCRIPT_NAME%.sh}" "$LINENO" "$rc" >&2' ERR PASS_FROM_STDIN=0 while (( $# )); do case "$1" in --server) SERVER_ADDR="${2:?}"; shift ;; --user) SMB_USER="${2:?}"; shift ;; --password-stdin) PASS_FROM_STDIN=1 ;; --mount-root) MASTER_DIR="${2:?}"; shift ;; --dry-run) DRY_RUN=1 ;; -h|--help) usage; exit 0 ;; *) die "Unknown argument: $1 (try --help)" ;; esac shift done (( EUID == 0 )) || die "Must run as root. Try: sudo $SCRIPT_NAME" [[ -r /etc/os-release ]] || die "/etc/os-release not found." # shellcheck disable=SC1091 . /etc/os-release case "${ID:-}:${ID_LIKE:-}" in *ubuntu*|*debian*) : ;; *) die "Unsupported distro: ${PRETTY_NAME:-unknown}." ;; esac ACTUAL_USER="${SUDO_USER:-${USER:-}}" [[ -n "$ACTUAL_USER" && "$ACTUAL_USER" != "root" ]] || die "Run via sudo as a regular user." # Use getent so we don't depend on eval/~expansion or shell glob safety. USER_ENTRY="$(getent passwd "$ACTUAL_USER")" || die "User '$ACTUAL_USER' not found in passwd." USER_HOME="$(awk -F: '{print $6}' <<<"$USER_ENTRY")" USER_ID="$(awk -F: '{print $3}' <<<"$USER_ENTRY")" USER_GID="$(awk -F: '{print $4}' <<<"$USER_ENTRY")" export DEBIAN_FRONTEND=noninteractive log "Installing cifs-utils, smbclient..." run "apt-get update -qq" run "apt-get install -y cifs-utils smbclient" # --- Collect creds --- [[ -n "$SERVER_ADDR" ]] || read -rp "Synology NAS address or IP: " SERVER_ADDR "$CRED_FILE" chown "$USER_ID:$USER_GID" "$CRED_FILE" chmod 0600 "$CRED_FILE" fi # --- Query shares --- log "Discovering shares on //$SERVER_ADDR..." if (( DRY_RUN )); then SHARE_LIST=$'home\nphoto\nvideo' else SHARE_LIST="$(smbclient -L "//$SERVER_ADDR" -U "$SMB_USER%$SMB_PASS" -g 2>/dev/null \ | awk -F'|' '$1=="Disk" {print $2}' || true)" fi [[ -n "$SHARE_LIST" ]] || die "No shares returned. Check host, credentials, or network." # --- Parent mount dir --- run "mkdir -p '$MASTER_DIR'" run "chown '$USER_ID:$USER_GID' '$MASTER_DIR'" # --- Backup fstab once --- if [[ ! -f /etc/fstab.bak.synology ]]; then run "cp /etc/fstab /etc/fstab.bak.synology" log "Backed up fstab -> /etc/fstab.bak.synology" fi MARK_BEGIN="# >>> synology-master >>> (managed by ${SCRIPT_NAME})" MARK_END="# <<< synology-master <<<" # Remove any previous managed block (so re-run replaces, not appends). if grep -qF "$MARK_BEGIN" /etc/fstab; then if (( DRY_RUN )); then printf ' DRY-RUN: strip previous managed block from /etc/fstab\n' else sed -i "\|$MARK_BEGIN|,\|$MARK_END|d" /etc/fstab fi fi log "Writing managed block to /etc/fstab..." FSTAB_BLOCK="$MARK_BEGIN"$'\n' while IFS= read -r SHARE; do [[ -z "$SHARE" || "$SHARE" == "IPC\$" || "$SHARE" == "print\$" ]] && continue MOUNT_POINT="$MASTER_DIR/$SHARE" run "mkdir -p '$MOUNT_POINT'" run "chown '$USER_ID:$USER_GID' '$MOUNT_POINT'" FSTAB_BLOCK+="//${SERVER_ADDR}/${SHARE} ${MOUNT_POINT} cifs credentials=${CRED_FILE},uid=${USER_ID},gid=${USER_GID},_netdev,nofail,x-systemd.automount,x-systemd.idle-timeout=60 0 0"$'\n' done <<<"$SHARE_LIST" FSTAB_BLOCK+="$MARK_END"$'\n' if (( DRY_RUN )); then printf ' DRY-RUN: append fstab block:\n%s\n' "$FSTAB_BLOCK" else printf '%s' "$FSTAB_BLOCK" >>/etc/fstab fi log "Reloading systemd and mounting..." run "systemctl daemon-reload" run "mount -a -t cifs" # --- Desktop entry --- APPS_DIR="$USER_HOME/.local/share/applications" DESKTOP_FILENAME="synology-master.desktop" DESKTOP_FILE="$APPS_DIR/$DESKTOP_FILENAME" run "install -d -o '$USER_ID' -g '$USER_GID' -m 0755 '$APPS_DIR'" if (( DRY_RUN )); then printf ' DRY-RUN: write %s\n' "$DESKTOP_FILE" else cat >"$DESKTOP_FILE" </dev/null 2>&1; then log "Pinning to top of GNOME dock..." CURRENT_FAVS="$(sudo -u "$ACTUAL_USER" DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$USER_ID/bus" gsettings get org.gnome.shell favorite-apps 2>/dev/null || echo '[]')" if [[ "$CURRENT_FAVS" != *"$DESKTOP_FILENAME"* ]]; then CLEAN_FAVS="${CURRENT_FAVS#@as }" if [[ "$CLEAN_FAVS" == "[]" || -z "$CLEAN_FAVS" ]]; then NEW_FAVS="['$DESKTOP_FILENAME']" else NEW_FAVS="['$DESKTOP_FILENAME', ${CLEAN_FAVS:1}" fi sudo -u "$ACTUAL_USER" DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$USER_ID/bus" \ gsettings set org.gnome.shell favorite-apps "$NEW_FAVS" || warn "Dock pin failed (non-fatal)." else log "Already pinned." fi else log "GNOME not detected (or no active dbus session); skipping dock pin." fi log "Done. Shares mounted under $MASTER_DIR."