19.6 C
New York
Saturday, October 19, 2024

macos – Unmount Quantity on Person Logout


After a lot experimentation I’ve arrived at a scripted answer, with a script that may function in two modes:

The primary is as a daemon, normally run as root (as launch daemon), which listens on a given socket for instructions figuring out the quantity you need to mount (should be unmounted), adopted by one other to substantiate that you just mounted it (proving you’ll be able to), and a 3rd to then unmount it, power unmount it, or clear the request. The behaviour is a bit simplistic, however ought to fairly set up a consumer had the power to mount the quantity, and subsequently is allowed to request that it then be unmounted, utilizing a easy random credential.

When not run in daemon mode, the script takes a quantity identifier (something supported by diskutil apfs unlockVolume, UUIDs most well-liked) and makes an attempt to unlock and mount the quantity. That you must have the password for the quantity within the keychain for the person working the script, and can be prompted to permit safety to entry it. The script usually makes an attempt to unmount a quantity by itself, nevertheless I’ve established that more often than not this may not work, as disk arbitration is normally unloaded earlier than the script makes an attempt to take action (that means diskutil unmount and umount each fail), as such if you wish to use this script with a launch agent that may unmount on logout, you have to have a daemon working on the identical system and set the --socket argument to match.

MountAPFS

Hopefully that is pretty clear in the way it’s supposed for use, because it contains examples and choices are documented. This isn’t meant for anybody that does not have some grasp of Terminal utilization and shell scripting (ZSH particularly) as you might want to customize it to do precisely what you need.

#!/bin/zsh
{

# Examples:
#   Standalone: ./MountAPFS 12345678-9012-3456-7890-12345678901234
#      (mount): ./MountAPFS --create ~/Library/Volumes/Foo 12345678-9012-3456-7890-12345678901234
#
#   Daemon:     ./MountAPFS --daemon --socket 61616
#   Consumer:     ./MountAPFS --socket 61616 12345678-9012-3456-7890-12345678901234

whereas [ $# -gt 0 ]; do
    case "$1" in
        # Set a listing that must be created (normally the quantity's mount level when a customized mount level is laid out in /and so forth/fstab)
        ('--create'|'--create-dir'|'--create-directory')
            CREATE_DIRECTORY="$2"; shift
            case "${CREATE_DIRECTORY:0:1}" in
                ("https://apple.stackexchange.com/")   ;;
                ('~')   CREATE_DIRECTORY="${HOME}${CREATE_DIRECTORY:1}"   ;;
                (*)     CREATE_DIRECTORY="${BASE_DIRECTORY}/${CREATE_DIRECTORY}"  ;;
            esac
        ;;
        # Runs this script in daemon mount (don't mount any volumes, as an alternative deal with the unmount of registered volumes on behalf of different duties).
        ('--daemon') DAEMON=1 ;;
        # The socket to pay attention on/connect with when working in/with a daemon script
        ('--socket') SOCKET="$2"; WAIT=1; shift ;;
        # The period of time to attend for the quantity to turn into accessible earlier than giving up. This feature can be utilized if there could also be a race situation between this and one other activity earlier than the quantity turns into accessible
        ('--timeout') TIMEOUT="$2"; shift ;;
        # Don't finish as soon as the quantity is mounted, as an alternative watch for a termination sign and try and unmount it
        ('--wait') WAIT=1 ;;
        # Allow verbose output; it will output quantity identifiers and tokens for tracing, however will solely output the final 4 characters of tokens to stop abuse (full tokens are 32 characters in size)
        ('-v'|'--verbose') VERBOSITY=$(($(echo "0${VERBOSITY}" | sed 's/[^0-9]*//g') + 1)) ;;
        # Express finish of arguments
        ('--') shift; break ;;
        (--*) echo "Unknown choice: $1" >&2; exit 2 ;;
        # Implicit finish of arguments (first quantity)
        (*) break ;;
    esac
    shift
finished

VERBOSITY=$(echo "0${VERBOSITY}" | sed 's/[^0-9]*//g')

if [[ -n "${SOCKET}" ]]; then
    [[ "${SOCKET}" = "$(echo "${SOCKET}" | sed 's/[^0-9]*//g')" ]] || { echo 'Invalid socket:' "${SOCKET}" >&2; exit 2; }
    [[ "${SOCKET}" -gt 0 ]] || { echo 'Invalid socket:' "${SOCKET}" >&2; exit 2; }
fi

if [ "${DAEMON}" = 1 ]; then
    [[ -n "${SOCKET}" ]] || { echo 'Daemon mode requires a socket' >&2; exit 2; }

    # Open netcat on the required socket
    coproc nc -kl localhost "${SOCKET}" || { echo 'Unable to open socket' >&2; exit 2; }
    entice 'coproc :' EXIT SIGHUP SIGINT SIGTERM
    
    [[ ${VERBOSITY} -gt 0 ]] && echo 'APFS daemon listening on socket:' "${SOCKET}"
    
    declare -A requested=()
    declare -A mounted=()
    whereas IFS='', learn -rd '' line; do
        cmd="${line:0:5}"
        worth="${line:5}"
        case "${cmd}" in
            # Signifies intention to mount a present unmounted quantity (given in worth).
            # Returns a token that should be utilized in future instructions
            ('mount')
                if mount=$(diskutil data "${worth}" 2>/dev/null | grep 'Mounted' | sed 's/[^:]*: *//') && [[ "${mount}" = 'No' ]]; then
                    token=$(echo "${worth}$(head -c 512 &p

                    [[ ${VERBOSITY} -gt 0 ]] && echo 'Accepted mount request for:' "${worth} assigned token ending with:" "${token: -4}"
                else
                    printf '%spercents' 'error' 'Quantity not discovered, or is already mounted' >&p
                    [[ ${VERBOSITY} -gt 0 ]] && echo 'Quantity not discovered or already mounted:' "${worth}" >&2
                fi
            ;;
            # Signifies that the beforehand registered quantity is now mounted. Quantity is recognized utilizing the distinctive token returned by the mount command. Now that the quantity has been mounted, it may be unmounted utilizing the unmnt or funmt command.
            # Returns the quantity that was examined
            ('mnted')
                quantity=${requested[$value]}
                if [ -n "${volume}" ]; then
                    if mount=$(diskutil data "${quantity}" 2>/dev/null | grep 'Mounted' | sed 's/[^:]*: *//') && [[ "${mount}" != 'No' ]]; then
                        mounted[${value}]=${quantity}
                        unset "requested[${token}]"
                        printf '%spercents' 'mnted' "${quantity}" >&p

                        [[ ${VERBOSITY} -gt 0 ]] && echo 'Confirmed mounting of:' "${quantity} utilizing token ending with:" "${worth: -4}"
                    else
                        printf '%spercents' 'error' 'Quantity not discovered, or will not be mounted' >&p
                        [[ ${VERBOSITY} -gt 0 ]] && echo 'Quantity not discovered or not mounted:' "${quantity}" >&2
                    fi
                else
                    printf '%spercents' 'error' 'Unknown token: use the mount command first' >&p
                    [[ ${VERBOSITY} -gt 0 ]] && echo "Obtained ${cmd} command out of sequence or invalid token ending with: ${token: -4}" >&2
                fi
            ;;
            # Requests {that a} beforehand mounted quantity to be unmounted. Quantity is recognized utilizing the distinctive token used within the mnted command.
            # The funmt command will try and forcibly unmount the quantity, and may solely be used if the unmnt command beforehand failed.
            # Returns the quantity that was unmounted
            ('unmnt'|'funmt')
                quantity=${mounted[$value]}
                if [ -n "${volume}" ]; then
                    if mount=$(diskutil data "${quantity}" 2>/dev/null | grep 'Mounted' | sed 's/[^:]*: *//') && [[ "${mount}" != 'No' ]]; then
                        [ "${cmd}" = 'funmt' ] && power="power " || power=""
                    
                        if error=$(diskutil unmount ${power}"${quantity}" 2>&1); then
                            unset "mounted[${token}]"
                            printf '%spercents' "${cmd}" "${quantity}" >&p

                            [[ ${VERBOSITY} -gt 0 ]] && echo 'Unmounted quantity:' "${quantity} utilizing token ending with:" "${token: -4}"
                        else
                            printf '%spercents' 'error' "Unable to unmount ${quantity}: ${error}" >&p
                            [[ ${VERBOSITY} -gt 0 ]] && echo 'Unable to mount:' "${quantity}: ${error}" >&2
                        fi
                    else
                        printf '%spercents' 'error' 'Quantity not discovered, or will not be mounted' >&p
                        [[ ${VERBOSITY} -gt 0 ]] && echo 'Quantity not discovered:' "${quantity}" >&2
                    fi
                else
                    printf '%spercents' 'error' 'Unknown token: use the mnted command first' >&p
                    [[ ${VERBOSITY} -gt 0 ]] && echo "Obtained ${cmd} command out of sequence: anticipated mnted" >&2
                fi
            ;;
            # Clear a token that's now not wanted
            ('clear')
                unset "requested[${value}]"
                unset "mounted[${value}]"
                
                printf '%spercents' 'clear' "${worth}" >&p
                
                [[ ${VERBOSITY} -gt 0 ]] && echo 'Cleared token ending with:' "${worth: -4}"
            ;;
            # Unknown command
            (*)
                printf '%spercents' 'error' "Unknown command: ${cmd}" >&p
                [[ ${VERBOSITY} -gt 0 ]] && echo 'Obtained unknown command:' "${cmd}" >&2
            ;;
        esac
    finished <&p

    coproc :
    [[ ${VERBOSITY} -gt 0 ]] && echo 'Terminating.'
else
    [[ -z "${BASE_DIRECTORY}" ]] && BASE_DIRECTORY="${HOME}/Library/Vaults/"
    [[ -d "${BASE_DIRECTORY}" && -w "${BASE_DIRECTORY}" ]] || { echo 'Lacking or unwritable base listing:' "${BASE_DIRECTORY}" >&2; exit 1; }

    [[ $# -lt 1 ]] && { echo 'Lacking quantity' >&2; exit 1; }
    VOLUME="$1"

    # If a timeout was given, wait till the quantity is prepared
    TIMEOUT=$(echo "${TIMEOUT}" | sed 's/[^0-9]*//g')
    if [[ -n "${TIMEOUT}" ]]; then
        whereas [[ "${TIMEOUT}" -gt 0 ]]; do
            diskutil data "${VOLUME}" 2>&1 >/dev/null && break
            TIMEOUT=$((${TIMEOUT} - 5))
            sleep 5
        finished
    fi

    # Be certain the quantity is offered to be unlocked
    error=$(diskutil data "${VOLUME}" 2>&1) || { echo 'Quantity not discovered:' "${VOLUME}:" "${error}" >&2; exit 3; }

    # If a mount level was given, attempt to create a listing (in any other case quantity will not mount over it)
    if [[ -n "${CREATE_DIRECTORY}" ]] && [[ ! -d "${CREATE_DIRECTORY}" ]]; then
        error=$(mkdir -m 700 "${CREATE_DIRECTORY}") || { echo 'Unable to create mount level:' "${CREATE_DIRECTORY}:" "${error}" >&2; exit 4; }
    fi
    
    # If a socket was given, register our intention to mount the quantity
    token=
    if [[ "${WAIT}" = 1 && -n "${SOCKET}" ]]; then
        socket_cmd() { native cmd="$1"; native worth="$2"
            coproc nc localhost "${SOCKET}" || { echo 'Unable to connect with socket' >&2; return 1; }
            
            native response=
            printf '%spercents' "${cmd}" "${worth}" >&p
            learn -rd '' response <&p
            
            case "${response:0:5}" in
                ("${cmd}")
                    printf '%s' "${response:5}"
                    coproc :
                    return 0
                ;;
                ('error')
                    echo "socket_cmd() error: ${response:5}" >&2
                    coproc :
                    return 2
                ;;
                (*)
                    echo 'Unknown/unsupported response:' "${response}" >&2
                    coproc :
                    return 3
                ;;
            esac
        }
        token=$(socket_cmd 'mount' "${VOLUME}") || SOCKET=
    fi

    if error=$(echo -e "$(safety find-generic-password -wa "${VOLUME}" | sed 's/../x&/g')" | diskutil apfs unlockVolume "${VOLUME}" -stdinpassphrase) || error2=$(diskutil mount "${VOLUME}"); then
        if [[ "${WAIT}" = 1 ]]; then
            # Verify mounting of quantity to socket (if registered)
            [[ -n "${token}" ]] && { volume_confirm=$(socket_cmd "mnted" "${token}") || token=; }
        
            printf '%s' 'Awaiting sign... '
        
            # Lure and wait till activity is ended, then lock the quantity
            cleanup_run=0
            cleanup() {
                [[ ${cleanup_run} = 0 ]] || return 0
                cleanup_run=1
            
                echo 'obtained.'
                printf '%s' 'Unmounting... '
                
                makes an attempt=5
                whereas [[ ${attempts} -gt 0 ]]; do
                    diskutil apfs lockVolume "${VOLUME}" >/dev/null && echo 'finished.' && break
                    [[ -n "${CREATE_DIRECTORY}" ]] && umount "${CREATE_DIRECTORY}" && echo 'finished.' && break
                    [[ -n "${token}" ]] && volume_confirm=$(socket_cmd 'unmnt' "${token}") && token= && echo 'finished.' && break
                    makes an attempt=$((${makes an attempt} - 1))
                    sleep 5
                finished
                if [[ ${attempts} = 0 ]]; then
                    if diskutil unmount power "${VOLUME}" >/dev/null; then
                        echo 'pressured.'
                    else
                        if [[ -z "${CREATE_DIRECTORY}" ]] || ! umount -f "${CREATE_DIRECTORY}"; then
                            if [[ -z "${token}" ]] || ! volume_confirm=$(socket_cmd 'funmt' "${token}"); then
                                echo 'failed.'
                                echo 'All makes an attempt to unmount failed' >&2
                            else
                                token=
                                echo 'pressured.'
                            fi
                        else
                            echo 'pressured.'
                        fi
                    fi
                fi
                [[ -n "${token}" ]] && socket_cmd 'clear' "${token}"
                
                # Clear all background duties
                coproc :
                [[ -n "${${(v)jobstates##*:*:}%=*}" ]] && kill ${${(v)jobstates##*:*:}%=*}
            }
            entice 'cleanup' SIGINT SIGHUP SIGTERM EXIT
            whereas true; do
                sleep 86400 &
                wait $!
            finished
        fi
    else
        echo 'Unable to mount quantity:' "${error}" "${error2}" >&2
        [[ -n "${token}" ]] && socket_cmd 'clear' "${token}"
    fi
fi

}

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles