kvz.io
Published on

Keep Mounted Network Drives Alive on OSX

Authors
  • avatar
    Name
    Kevin van Zonneveld
    Twitter
    @kvz

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 intact. 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 intact. All good
[2012-12-25 22:49:01] INFO: Mounts intact. 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 intact. All good
[2012-12-26 18:21:04] INFO: Mounts intact. 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 intact. All good
[2012-12-27 01:46:04] INFO: Mounts intact. All good
[2012-12-27 01:47:06] INFO: Mounts intact. 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 :)

Legacy Comments (7)

These comments were imported from the previous blog system (Disqus).

MrSenorLoveDaddy
MrSenorLoveDaddy·

Newb here. I'm having the same problem, my NAS keeps unmounting after my mac mini has been asleep overnight. It's a pain because if I want to use Plex in another room, it can't find the volume. So does your solution work for my situation as well? I'm not familiar with scripts, how exactly do i go about using your script?

Kev van Zonneveld
Kev van Zonneveld·

It's a shell script. Some knowledge of that & terminals is definitely required here. I would advice against using it if you have no experience with that.

Eric
Eric·

Thanks. Very helpful script. Wouldn't run as is on Mac OS X 10.7.5 but with a few tweaks I was up and running in no time.

MrSenorLoveDaddy
MrSenorLoveDaddy·

How do you get this to work? I'm a newb with scripts.

Eric
Eric·

The key change for me was the grep (line 45 above) in the mount_exists function. Slightly different output to the mount command on 10.7.5 I guess.

My new mount_exists function is:

function mount_exists () {
local share="${1}"
local dir="/Volumes/${share}"
echo "$(/sbin/mount |egrep -i "on ${dir}" |egrep -i "mounted by ${USER}" |wc -l)"
}

You should just paste the line (minus the echo"$( )") into shell to see what it returns in order to ensure that it is working correctly. Make sure that it returns a 1 when you are indeed mounted. With the original script above, I was always returning a 0.

macmini:~ plex$ /sbin/mount |egrep -i "on ${dir}" |egrep -i "mounted by ${USER}" |wc -l
1
macmini:~ plex$

Eric
Eric·

If you haven't already, you will most definitely have to tweak lines 33 to 35 and line 37 to match your particular scheme. Test line 93 in the shell replace the variables with the names defined in those lines to make sure that you can mount properly. For instance, NASHOST can be an ip address or a hostname (in my case NAS0). NASSHARES is the folder or folders on your NAS that you wish to mount. This is all very specific to every person. In Kevin's script, his NASUSER="${USER}" which means his shell user is the same as his NAS user. In my case, my shell user is "plex" and my NASUSER is "PlexMedia".

My setup looks like this:
[ -n "${NASSHARES}" ] || NASSHARES="PlexMedia"
[ -n "${NASHOST}" ] || NASHOST="NAS0"
[ -n "${NASUSER}" ] || NASUSER="PlexMedia"
[ -n "${STAMPFILE}" ] || STAMPFILE=".remounter.stamp"
[ -n "${NASPASS}" ] || NASPASS="MYPASSWORD"
[ -n "${FORCEREMOUNT}" ] || FORCEREMOUNT=0
[ -n "${REBOOTONCRIT}" ] || REBOOTONCRIT=0

macmini:~ plex$ ls -l /Volumes/
drwx------ 1 plex staff 264 Jun 8 08:03 PlexMedia

macmini:~ plex$ mount
afp_2QLWyu1cojsM4A3N181JKu3k-5.2d00000d on /Volumes/PlexMedia (afpfs, nodev, nosuid, mounted by plex)

Bob1066
Bob1066·

Hi, very useful script, however something odd is happening. My drive mounts as expected, but later when the script runs again the mounted drive is replaced by an empty folder with the share name. Even stranger sometimes a new folder appears that has the same name appended with numbers.....? I can erase the folders and this works for a while then it happens again.