Automate mounting and unmounting encrypted volumes?

My current living situation has put my computer in my bedroom for the first time in a long time. This also means my backup drive is there, and it’s a badly behaved Western Digital drive that will make noise all night unless it’s completely powered down OR all of its volumes are ejected and my iMac stays awake. (Put the Mac to sleep and the drive screams, it’s stupid.)

I want to automatically unmount the volumes at night and remount them in the morning. Unmounting them manually is easy enough, but remounting them is a huge pain. They’re encrypted, and for some reason Disk Utility will not mount them (it silently fails). So every morning I go to terminal, run multiple diskutil coreStorage unlockVolume and diskutil mount commands, all while juggling 1Password to find my volume passphrases. It’s a mess.

What utility or script will save me from this? My only criterion is to not save the volume passphrases in the clear as part of the process. Putting them in my keychain is fine, since the iMac is in my house and if someone gains physical access I have bigger problems.

I was only able to test this with APFS but I assume it is very similar.

Step Zero: Get the Disk ID for your drive

mount | egrep '^/'

Mine looks like this:

/dev/disk1s5s1 on / (apfs, sealed, local, read-only, journaled)
/dev/disk1s4 on /System/Volumes/VM (apfs, local, noexec, journaled, noatime, nobrowse)
/dev/disk1s2 on /System/Volumes/Preboot (apfs, local, journaled, nobrowse)
/dev/disk1s6 on /System/Volumes/Update (apfs, local, journaled, nobrowse)
/dev/disk1s1 on /System/Volumes/Data (apfs, local, journaled, nobrowse)
/dev/disk1s3 on /Volumes/Recovery (apfs, local, journaled, nobrowse)
/dev/disk1s5 on /Volumes/SSD 1 (apfs, sealed, local, journaled, nobrowse)
/dev/disk8s1 on /Volumes/Dual-USB (apfs, local, nodev, nosuid, journaled, noowners)

Note the /dev/disk8s1 for Dual-USB that will come in handy later.

Step One: Come up with a unique name to be stored in keychain

This can be anything you want, but it must not match anything else in your keychain.

I chose

KEYCHAIN_NAME='Dual-USB'

because I was working with a USB thumb drive that has both USB-A and USB-C, hence “dual”.

Step Two: Create a password

This can anything you like, but since we’re using it with a shell script, I recommend not using these characters:

 '
 "
 !
 ?

just to be safe.

Save your password in 1Password (because safety) and then assign it to the variable PW_ADD like so:

PW_ADD='WHEITHWJ$H@#iu2y359y24h'

Step Three: Add password to keychain

security add-generic-password \
-a "$KEYCHAIN_NAME" \
-s "$KEYCHAIN_NAME" \
-w "$PW_ADD"

You can put that all on one line if you remove the \ at the end of each line.

Step Four: Check Keychain Access.app

Make sure the password is in there as you’d expect.

Step Five: Retrieve the password from keychain into a variable

PW=$(security find-generic-password \
-a "$KEYCHAIN_NAME" \
-s "$KEYCHAIN_NAME" \
-g 2>&1 \
| awk -F'"' '/^password/{print $2}')

Step Six: use the $PW variable with diskutil

diskutil apfs unlockVolume /dev/disk8s1 -passphrase "$PW"

Now, there are several things to note here:

  • /dev/disk8s1 is specific to my system and probably won’t be the same for yours. You should have yours from Step Zero. My concern is that this device ID might not be consistent across reboots, etc depending on other drives that might get attached/detached.

  • this is an APFS-specific command. I guess the HFS version is diskutil coreStorage unlockVolume?

  • I did not need a diskutil mount command. If you do, I’ll need to know more about that.

Assuming that the device ID does stay the same, I should be able to script this like so:

#!/usr/bin/env zsh -f

PATH="/usr/local/bin:/usr/bin:/usr/sbin:/sbin:/bin"

	# where should it be mounted when it's mounted 
MNTPNT='/Volumes/Dual-USB'

if [[ -d "$MNTPNT" ]]
then
	echo "$NAME: '$MNTPNT' is already mounted." >>/dev/stderr
	exit 0
fi 

	## This can be whatever you like as long as it is unique 
	## And the same as what you used when adding the password TO keychain
KEYCHAIN_NAME='Dual-USB'

PW=$(security find-generic-password -a "$KEYCHAIN_NAME" -s "$KEYCHAIN_NAME" -g 2>&1 | awk -F'"' '/^password/{print $2}')

	## Change this for HFS if necessary
diskutil apfs unlockVolume /dev/disk8s1 -passphrase "$PW"

if [[ -d "$MNTPNT" ]]
then
	echo "$NAME: '$MNTPNT' mounted successfully."
	exit 0
else		
	echo "$NAME: '$MNTPNT' failed to mount" >>/dev/stderr
	exit 1
fi 

exit 0
#EOF

So… that’s a start. Let’s try that and go from there. Also, how are you unmounting the drive? Are you doing it from Finder or Terminal?

My hunch is that we’ll have to find a way to use diskutil list to get the device ID dynamically, but we’ll cross that bridge when we come to it.

1 Like

Regarding the issue of the changing device endpoint, you should be able to use the devices unique UUID to reference it.
That’s how it works under Linux and it seems like it is similar on macOS. Here an entry I found where the process of finding and using the UUID is described: https://apple.stackexchange.com/questions/235230/how-to-mount-disk-by-uuid-or-label-in-os-x-el-capitan#236877

Above and beyond, TJ, thank you! It’ll give it a go…and we’ll see if I get it all operating or whether I’ll give up and put a handful of shell commands including the passwords in a 1P secure note.

Here’s a better version using the UUID, which you will need to get yourself, as described in the code below.

  1. Note again that I am using diskutil apfs unlockVolume not HFS

  2. Note that if you eject the drive using Finder, you will need to disconnect and reconnect the drive for this to work. The key here is to unmount the drive but not eject the drive.

diskutil unmount "/Volumes/Your Drive Name Here"

will leave the drive accessible to diskutil.

Here’s the updated script:

#!/usr/bin/env zsh -f
# Purpose: mount an APFS encrypted drive with password in keychain

PATH="/usr/local/bin:/usr/bin:/usr/sbin:/sbin:/bin"

	# To find the UUID, with the drive mounted, run
	#		diskutil list -plist | less
	# in Terminal and look for the <key>DiskUUID</key> associated with your drive
	#
	# OR
	#
	# With the drive mounted, open Disk Utility.app, select the drive, then select Info
	# and look for the File System UUID

UUID='XXXXXX-XXXX-XXXX-XXXXX-XXXXXXX'

######################################################################################################
## You should not HAVE to change anything below this line 

INFO=$(diskutil list -plist | tr '\012' ' ' | tr -s ' |\t' ' ' | sed 's#<array>#\
<array>#g' | fgrep -i "$UUID" )

if [[ "$INFO" == "" ]]
then
	echo "$NAME: Unable to find information for $UUID. Try unplugging and replugging the device." >>/dev/stderr 
	exit 2
fi 	

MNTPNT=$(echo "$INFO" | sed -e 's#.*<key>VolumeName</key> <string>##g' -e 's#</string>.*##g')

if [[ -d "/Volumes/$MNTPNT" ]]
then
	echo "$NAME: '/Volumes/$MNTPNT' is already mounted"
	exit 0
fi	

DEVICE=$(echo "$INFO" | sed 's#</string> #</string>\
#g' \
| fgrep '<key>DeviceIdentifier</key> <string>' \
| head -1 \
| sed 's#.*<string>##g ; s#</string>##g')


	## This can be whatever you like as long as it is unique
KEYCHAIN_NAME='Dual-USB'

##### This is how you ADD the entry to the keychain
## security add-generic-password  -a "$KEYCHAIN_NAME" -s "$KEYCHAIN_NAME" -w "$PW"

##### This is how you GET the entry to the keychain
## security find-generic-password -a "$KEYCHAIN_NAME" -s "$KEYCHAIN_NAME" -g 2>&1 | awk -F'"' '/^password/{print $2}'

PW=$(security find-generic-password -a "$KEYCHAIN_NAME" -s "$KEYCHAIN_NAME" -g 2>&1 | awk -F'"' '/^password/{print $2}')

diskutil apfs unlockVolume "/dev/$DEVICE" -passphrase "$PW"

if [[ -d "/Volumes/$MNTPNT" ]]
then
	echo "$NAME: mounted"
	exit 0
else
	echo "$NAME: Failed to mount '$DEVICE' to '/Volumes/$MNTPNT'." >>/dev/stderr
	exit 1
fi

exit 0
#EOF
1 Like