Keep Mounted Network Drives Alive on OSX

4 minute read

I love my NAS but because I tried to save a little money it does not run SABnzbd very well.

I've tried different approaches but find myself ending up downloading on OSX as it writes to a network share on my NAS. Too bad, but I'm archiving this one under the section first world problems.

The challenge I have now though, is when my Mac goes to sleep, my mounts disappear, and SABnzbd writes to the local filesystem instead. Cause as far as my downloading program could tell, it was already writing to a local filesystem, so it will just keep on doing that until my Mac's disk is at 100%.

I wrote a little script to prevent that.

You may not be running SABnzbd, but there are obviously many other use cases where you want a network mount to persist. Especially if you are automating something outside of the GUI.

With some small adjustments this could work for Linux/NFS/SMB as well.

Here we go

#!/usr/bin/env bash -e
# Monitors mounts, checks if they're writable, remounts if necessary.
# For OSX / AFP.
# If it had to remount, exits with code 1 so you can easily chain
# other scripts in such an event. e.g.:
#
#  NASPASS=******** ./remounter.sh || ./restart_downloader.sh restart
#

function _log () {
  local level="${1}"
  local str="${2}"
  echo "[$(date "+%Y-%m-%d %H:%M:%S")] ${level}: ${str}"
}
function info () {
  _log "INFO" "${1}"
}
function err  () {
  _log " ERR"  "${1}"
}
function crit () {
  _log "CRIT" "${1}";

  if [ "${REBOOTONCRIT}" = 1 ]; then
    echo "Warning, CRIT happened and reboot on crit was specified. "
    echo "Rebooting in 50 seconds"
    shutdown -r +50
  fi

  exit 1
}

[ -n "${NASSHARES}" ]    || NASSHARES="downloads video"
[ -n "${NASHOST}" ]      || NASHOST="nas.local"
[ -n "${NASUSER}" ]      || NASUSER="${USER}"
[ -n "${STAMPFILE}" ]    || STAMPFILE=".remounter.stamp"
[ -n "${NASPASS}" ]      || crit "Please set the shares password like so: NASPASS=******** ${0}"
[ -n "${FORCEREMOUNT}" ] || FORCEREMOUNT=0
[ -n "${REBOOTONCRIT}" ] || REBOOTONCRIT=0

function mount_exists () {
  local share="${1}"
  local dir="/Volumes/${share}"

  echo "$(mount |egrep -i "^//${NASUSER}@${NASHOST}/${share} on ${dir} " |wc -l)"
}

function mount_writable () {
  local share="${1}"
  local dir="/Volumes/${share}"

  local stamp="$(date "+%Y-%m-%d %H:%M:%S")"
  local stampfile="${dir}/${STAMPFILE}"

  echo "${stamp}" > "${stampfile}" || true

  local found="$(cat "${stampfile}")"

  [ -f "${stampfile}" ] && rm "${stampfile}"

  if [ "${found}" != "${stamp}" ]; then
    echo "0"
  else
    echo "1"
  fi
}

function remount () {
  local share="${1}"
  local dir="/Volumes/${share}"
  local stampfile="${dir}/${STAMPFILE}"

  info "remounting ${share}... "

  if [ -d "${dir}" ]; then
    # Try 3 times because this happened once:
    # //kevin@nas.local/downloads on /Volumes/downloads (afpfs, nodev, nosuid, mounted by kevin)
    # //kevin@nas.local/video on /Volumes/video (afpfs, nodev, nosuid, mounted by kevin)
    # //kevin@nas.local/downloads on /Volumes/downloads (afpfs, nodev, nosuid, mounted by kevin)
    # //kevin@nas.local/video on /Volumes/video (afpfs, nodev, nosuid, mounted by kevin)
    for i in `seq 1 3`; do
      umount -f "${dir}" || true
      sleep 1

      [ "$(mount_exists ${share})" -eq 0 ] && [ "$(mount_writable ${share})" -eq 0 ] && break
    done

    [ "$(mount_writable ${share})" -ne 0 ] && crit "Could not unmount ${share}. Still writable"
    [ "$(mount_exists ${share})" -ne 0 ] && crit "Could not unmount ${share}. Still exists"
  fi

  mkdir -p "${dir}"
  mount_afp -i "afp://${NASUSER}:${NASPASS}@${NASHOST}/${share}" "${dir}"
}

for share in ${NASSHARES}; do
  if [ "${FORCEREMOUNT}" = 1 ] || [ "$(mount_exists ${share})" -ne 1 ] || [ "$(mount_writable ${share})" -ne 1 ]; then
    [ -z "${missing}" ] || missing="${missing} "
    missing="${missing}${share}"
    remount "${share}"
  fi
done

if [ -z "${missing}" ]; then
  info "Mounts in tact. All good"
  exit 0
fi

info "Had to remount. "
exit 1

I set up a cronjob so it runs every minute like so:

$ crontab -e
* * * * * /location/remounter.sh || /location/restart_downloader.sh

To illustrate, here's remounter.sh in action. This happened while I was away, apparently my Mac went to sleep (otherwise you'd see a stamp every minute), and that totally broke the mounts afterwards, multiple times in different ways.

[2012-12-25 22:48:02] INFO: Mounts in tact. All good
[2012-12-25 22:49:01] INFO: Mounts in tact. All good
[2012-12-26 06:38:00] INFO: remounting downloads...
[2012-12-26 07:32:00] INFO: remounting downloads...
umount: /Volumes/downloads: not currently mounted
/blogscripts/remounter.sh: line 55: /Volumes/downloads/.remounter.stamp: Permission denied
cat: /Volumes/downloads/.remounter.stamp: No such file or directory
/blogscripts/remounter.sh: line 55: /Volumes/downloads/.remounter.stamp: Permission denied
cat: /Volumes/downloads/.remounter.stamp: No such file or directory
[2012-12-26 16:33:00] INFO: remounting downloads...
umount: /Volumes/downloads: not currently mounted
/blogscripts/remounter.sh: line 55: /Volumes/downloads/.remounter.stamp: Permission denied
cat: /Volumes/downloads/.remounter.stamp: No such file or directory
/blogscripts/remounter.sh: line 55: /Volumes/downloads/.remounter.stamp: Permission denied
cat: /Volumes/downloads/.remounter.stamp: No such file or directory
[2012-12-26 17:26:45] INFO: remounting video...
[2012-12-26 17:26:48] INFO: Had to remount.
[2012-12-26 17:27:04] INFO: Mounts in tact. All good
[2012-12-26 18:21:04] INFO: Mounts in tact. All good
mount_afp: AFPMountURL returned error -1069, errno is -1069
mount_afp: AFPMountURL returned error -5023, errno is -5023
[2012-12-27 01:45:02] INFO: Mounts in tact. All good
[2012-12-27 01:46:04] INFO: Mounts in tact. All good
[2012-12-27 01:47:06] INFO: Mounts in tact. All good

Would be a great idea to solo it so that you can lock it to avoid that if a script runs for a longtime, a new script spawns and overlaps the old one. That could lead to problems like high load / unexpected behavior.

Hope this helps :)

Leave a Comment Right Here