#!/bin/bash

#       ___           ___           ___           ___
#      /\__\         /\  \         /\  \         /\__\
#     /:/  /         \:\  \        \:\  \       /::|  |
#    /:/__/           \:\  \        \:\  \     /:|:|  |
#   /::\  \ ___       /::\  \       /::\  \   /:/|:|__|__
#  /:/\:\  /\__\     /:/\:\__\     /:/\:\__\ /:/ |::::\__\
#  \/__\:\/:/  /    /:/  \/__/    /:/  \/__/ \/__/~~/:/  /
#       \::/  /    /:/  /        /:/  /            /:/  /
#       /:/  /     \/__/         \/__/            /:/  /
#      /:/  /                                    /:/  /
#      \/__/                                     \/__/
#
# Copyright (c) 2023, Robert Swinford <robert.swinford<...at...>gmail.com>
#
# For the full copyright and license information, please view the LICENSE file
# that was distributed with this source code.

set -euf -o pipefail
#set -x

function print_version {
	printf "\
equine $(httm --version | cut -f2 -d' ')
" 1>&2
	exit 0
}

function print_usage {
	local equine="\e[31mequine\e[0m"
	local httm="\e[31mhttm\e[0m"

	printf "\
$equine is a script to mount APFS snapshots for use with $httm.  
$equine has two modes: 1) mount and unmount of local APFS snapshots, 
and 2) mount and unmount of remote APFS snapshots, located on a Time 
Machine network volume (NAS).  

This script is *not* for use with Time Machines which utilize direct 
attached storage (DAS).

USAGE:
	equine [OPTIONS]

OPTIONS:
	--mount-local:
		Attempt to mount your local Time Machine snapshots.

	--unmount-local:
		Attempt to unmount your local Time Machine snapshots.

	--mount-remote:
		Attempt to mount your remote Time Machine snapshots and the Time 
		Machine image file, and connect the server.

	--unmount-remote:
		Attempt to unmount your remote Time Machine snapshots and the Time 
		Machine image file, and disconnect the server.

	--help:
		Display this dialog.

	--version:
		Display script version.

" 1>&2
	exit 1
}

print_err_exit() {
	print_err "${@}"
	exit 1
}

print_err() {
	printf "%s\n" "Error: $*" 1>&2
}

function prep_exec {
	[[ -n "$(
		command -v plutil
		exit 0
	)" ]] || print_err_exit "'plutil' is required to execute 'equine'.  Please check that 'plutil' is in your path."
	[[ -n "$(
		command -v tmutil
		exit 0
	)" ]] || print_err_exit "'tmutil' is required to execute 'equine'.  Please check that 'tmutil' is in your path."
	[[ -n "$(
		command -v mount_apfs
		exit 0
	)" ]] || print_err_exit "'mount_apfs' is required to execute 'equine'.  Please check that 'mount_apfs' is in your path."
	[[ -n "$(
		command -v mount_smbfs
		exit 0
	)" ]] || print_err_exit "'mount_smbfs' is required to execute 'equine'.  Please check that 'mount_smbfs' is in your path."
	[[ -n "$(
		command -v mount
		exit 0
	)" ]] || print_err_exit "'mount' is required to execute 'equine'.  Please check that 'mount' is in your path."
	[[ -n "$(
		command -v cut
		exit 0
	)" ]] || print_err_exit "'cut' is required to execute 'equine'.  Please check that 'cut' is in your path."
	[[ -n "$(
		command -v grep
		exit 0
	)" ]] || print_err_exit "'grep' is required to execute 'equine'.  Please check that 'grep' is in your path."
	[[ -n "$(
		command -v xargs
		exit 0
	)" ]] || print_err_exit "'xargs' is required to execute 'equine'.  Please check that 'xargs' is in your path."
	[[ -n "$(
		command -v hdiutil
		exit 0
	)" ]] || print_err_exit "'hdiutil' is required to execute 'equine'.  Please check that 'hdiutil' is in your path."
}

function mount_remote() {
	local server="$( plutil -p /Library/Preferences/com.apple.TimeMachine.plist | grep "NetworkURL" | cut -d '"' -f4 )"
	local mount_source="$( plutil -p /Library/Preferences/com.apple.TimeMachine.plist | grep "LastKnownVolumeName" | cut -d '"' -f4  )"

	[[ -n "$server" ]] || print_err_exit "Could not determine server address, perhaps none is specified?"
	[[ -n "$mount_source" ]] || print_err_exit "Could not determine mount source from server name"

	local sys_defined_dir="$( find /Volumes/.timemachine -name "$mount_source" -print -quit )"
	local dirname="/Volumes/$mount_source"
	[[ -z "$sys_defined_dir" ]] || dirname="$sys_defined_dir"

	if [[ "$( mount | grep -c "$dirname" )" -eq 0 ]]; then
		printf "%s\n" "Connecting to remote Time Machine: $server ..."
		
		[[ -d "$dirname" ]] || mkdir -p "$dirname" || \
		print_err_exit "mkdir failed, likely because it was prevented by System Integrity Protection, may need to disable Filesystem Protections"
		
		mount_smbfs -o nobrowse "$server" "$dirname" 2>/dev/null || true
	else
		printf "%s\n" "Skip connecting to remote server, as Time Machine already mounted ..."
	fi

	# Wait for server to connect
	timeout 30s bash -c "until [[ "$( mount | grep -c "$dirname" )" -gt 0 ]]; do sleep 1; done" || \
	print_err_exit "Wait for server to be mounted timed out.  Quitting."

	local image_name="$(plutil -p /Library/Preferences/com.apple.TimeMachine.plist | grep LocalizedDiskImageVolumeName | cut -d '"' -f4)"
	[[ -n "$image_name" ]] || print_err_exit "Could not determine Time Machine disk image name, perhaps none is specified?"

	if [[ "$( mount | grep -c "$image_name" )" -eq 0 ]]; then
		printf "%s\n" "Mounting sparse bundle (this may include an fsck): $image_name ..."
		local bundle_name="$( find "$dirname" -type d -iname "*.sparsebundle" -print -quit )"
		[[ -n "$bundle_name" ]] || print_err_exit "Could not find sparsebundle in location specified"
		hdiutil attach -readonly -nobrowse "$bundle_name" || print_err_exit "Attaching disk image failed.  Quitting."
	else
		printf "%s\n" "Skip mounting sparse bundle, as $image_name appears to already be mounted ..."
	fi

	printf "%s\n" "Discovering backup locations (this can take a few seconds)..."
	local backups="$( tmutil listbackups / )"
	local device="$( mount | grep "$image_name" | cut -d' ' -f1 | tail -1 )"
	local uuid="$( echo "$backups" | cut -d "/" -f4 | head -1 )"

	[[ -n "$device" ]] || print_err_exit "Could not determine Time Machine device from image give"
	[[ -n "$uuid" ]] || print_err_exit "Could not determine uuid from list of backup locations"

	[[ "$( mount | grep -c "$image_name" )" -gt 0 ]] || print_err_exit "Time machine disk image did not mount"

	[[ -d "/Volumes/.timemachine/$uuid" ]] || mkdir -p "/Volumes/.timemachine/$uuid" || \
	print_err_exit "mkdir failed, likely because it was prevented by System Integrity Protection, may need to disable Filesystem Protections"
	
	printf "%s\n" "Mounting snapshots..."
	for snap in $( echo "$backups" | xargs basename ); do
		[[ -d "/Volumes/.timemachine/$uuid/$snap" ]] || mkdir -p "/Volumes/.timemachine/$uuid/$snap" || \
		print_err_exit "mkdir failed, likely because it was prevented by System Integrity Protection, may need to disable Filesystem Protections"
		
		printf "%s\n" "Mounting snapshot "com.apple.TimeMachine.$snap" from "$device" at "/Volumes/.timemachine/$uuid/$snap""
		
		[[ -d "/Volumes/.timemachine/$uuid/$snap" ]] && mount_apfs -o ro,nobrowse,noowners,noatime,nodev,nosuid -s "com.apple.TimeMachine.$snap" "$device" "/Volumes/.timemachine/$uuid/$snap" 2>/dev/null || true
	done
}

function unmount_remote() {
	printf "%s\n" "Unmounting any mounted snapshots...."
	mount | grep "com.apple.TimeMachine.*.backup@" | cut -d' ' -f1 | xargs -I{} umount "{}" 2>/dev/null  || true

	local image_name="$(plutil -p /Library/Preferences/com.apple.TimeMachine.plist | grep LocalizedDiskImageVolumeName | cut -d '"' -f4)"
	[[ -n "$image_name" ]] || print_err "Could not determine Time Machine disk image name, perhaps none is specified?"
	local sub_device="$( mount | grep "$image_name" | cut -d' ' -f1 | tail -1 )"
	local device="$( echo $sub_device | cut -d's' -f1 )"
	[[ -n "$sub_device" ]] || print_err "Could not determine subdevice from disk image given"
	[[ -n "$device" ]] || print_err "Could not determine device from disk image given"

	local server="$( plutil -p /Library/Preferences/com.apple.TimeMachine.plist | grep "NetworkURL" | cut -d '"' -f4 )"
	local mount_source="$( plutil -p /Library/Preferences/com.apple.TimeMachine.plist | grep "LastKnownVolumeName" | cut -d '"' -f4  )"

	local sys_defined_dir="$( find /Volumes/.timemachine -name "$mount_source" -print -quit )"
	local dirname="/Volumes/$mount_source"
	[[ -z "$sys_defined_dir" ]] || dirname="$sys_defined_dir"

	[[ "$(tmutil status | grep -c "Running = 0")" -gt 0 ]] || \
	print_err_exit "Backup running.  'equine' will not unmount sparsebundle or server while backup is running.  Quitting."

	printf "%s\n" "Attempting to unmount Time Machine sparse bundle: $image_name ..."
	[[ -z "$sub_device" ]] || diskutil unmount "$sub_device" 2>/dev/null || true
	[[ -z "$device" ]] || diskutil unmountDisk "$device" 2>/dev/null || true

	[[ -n "$server" ]] || print_err "Could not determine server, perhaps none is specified?"
	[[ -n "$mount_source" ]] || print_err "Could not determine mount source from server name given"

	printf "%s\n" "Attempting to unmount/disconnect from Time Machine server: $server ..."
	[[ -z "$mount_source" ]] || diskutil unmount force "$dirname" 2>/dev/null || true
	[[ -z "$mount_source" ]] || diskutil unmountDisk force "$dirname" 2>/dev/null || true
}

function mount_local() {
	printf "%s\n" "Discovering backup locations (this can take a few seconds)..."
	local backups="$( tmutil listlocalsnapshots /System/Volumes/Data | grep -v ':' )"
	local mounts="$( mount )"
	local device="$( echo "$mounts" | grep "/System/Volumes/Data " | cut -d' ' -f1 )"
	local hostname="$( hostname )"

	[[ -n "$device" ]] || print_err_exit "Could not determine Time Machine device from image give"

	printf "%s\n" "Mounting snapshots..."
	for snap in $( echo "$backups" ); do
		if [[ $( echo "$mounts" | grep -c "$snap" ) -gt 0 ]]; then
			continue
		fi

		local snap_uuid="$( echo $snap | cut -d'.' -f4 )"
		[[ -d "/Volumes/com.apple.TimeMachine.localsnapshots/Backups.backupdb/$hostname/$snap_uuid/Data" ]] || \
		mkdir -p "/Volumes/com.apple.TimeMachine.localsnapshots/Backups.backupdb/$hostname/$snap_uuid/Data" || \
		print_err_exit "mkdir failed, likely because it was prevented by System Integrity Protection, may need to disable Filesystem Protections"

		printf "%s\n" "Mounting snapshot "$snap" from "$device" at "/Volumes/com.apple.TimeMachine.localsnapshots/Backups.backupdb/$hostname/$snap_uuid/Data""
		[[ -d "/Volumes/com.apple.TimeMachine.localsnapshots/Backups.backupdb/$hostname/$snap_uuid/Data" ]] && \
		mount_apfs -o ro,nobrowse,noowners,noatime,nodev,nosuid -s "$snap" "$device" "/Volumes/com.apple.TimeMachine.localsnapshots/Backups.backupdb/$hostname/$snap_uuid/Data" 2>/dev/null || true
	done
}

function unmount_local() {
	[[ "$(tmutil status | grep -c "Running = 0")" -gt 0 ]] || \
	print_err_exit "Backup running.  'equine' will not unmount local snapshots while backup is running.  Quitting."

	printf "%s\n" "Unmounting any mounted snapshots...."
	mount | grep "com.apple.TimeMachine.*.local@" | cut -d' ' -f1 | xargs -I{} umount "{}" 2>/dev/null  || true
}

function exec_equine() {
	[[ $# -ge 1 ]] || print_usage
	[[ "$1" != "-h" && "$1" != "--help" ]] || print_usage
	[[ "$1" != "-V" && "$1" != "--version" ]] || print_version

	[[ "$( uname )" == "Darwin" ]] || print_err_exit "This script requires you run it on MacOS"
	[[ "$EUID" -eq 0 ]] || print_err_exit "This script requires you run it as root"
	prep_exec

	while [[ $# -ge 1 ]]; do
		if [[ "$1" == "--mount-remote" ]]; then
			mount_remote
			break
		elif [[ "$1" == "--unmount-remote" ]]; then
			unmount_remote
			break
		elif [[ "$1" == "--mount-local" ]]; then
			mount_local
			break
		elif [[ "$1" == "--unmount-local" ]]; then
			unmount_local
			break
		else
			print_err_exit "User must specify whether to mount or unmount the Time Machine volumes."
			break
		fi
	done
}

exec_equine "${@}"
