#! /bin/bash

# --------------------------------------------------- #
#                       sshmnt                        #
# --------------------------------------------------- #
# sshmnt is a script used with sshfs and fuse to help #
# manage multiple ssh based network-filesystem        #
# profiles and to help organize + simplify mounting   #
# and unmounting.                                     #
#                                                     #
# Developer: Prurigro                                 #
# Contact: prurigro at gmail dot com                  #
# Version: 1.6                                        #
#                                                     #
# If you find this script useful but have ideas about #
# how to make it better, found bugs, need help using  #
# it or anything else; feel free to contect me. I     #
# wrote it because I wanted something that would      #
# manage ssh mounts, but do it in more of an 'arch    #
# way' than whats currently out there; however, I     #
# know there's room for improvement and I'd love to   #
# hear some input as to any directions people would   #
# like to see this take :)                            #
#                                                     #
# Distributed under the GPLv3; copies can be obtained #
# on gnu.org @ http://www.gnu.org/copyleft/gpl.html   #
# --------------------------------------------------- #

headinglist=('location' 'ip' 'port' 'login' 'path' 'mntopts' 'mountpath' 'editor')
profile=()

if [ ! -z $XDG_CONFIG_HOME ]; then
  confdir=$XDG_CONFIG_HOME
else
  confdir=$HOME/.config
fi

config='sshmntconfig'
exitstatus=0

function connect {
    echo
    if [ $1 = "-m" -o $1 = "m" ]; then
      echo "mounting: ${profile[0]}"
    elif [ $1 = "-s" -o $1 = "s" ]; then
      echo "connecting to: ${profile[0]}"
    elif [ $1 = "-i" -o $1 = "i" ]; then
      echo "location: ${profile[0]}"
    fi
    echo "${headinglist[1]}: ${profile[1]}"
    echo "${headinglist[2]}: ${profile[2]}"
    echo "${headinglist[3]}: ${profile[3]}"
    if [ $1 = "-s" -o $1 = "s" ]; then
        echo
        ssh "${profile[3]}"@"${profile[1]}" -p "${profile[2]}"
    else
        echo "${headinglist[4]}: ${profile[4]}"
        if [ ! -z "${profile[5]}" ]; then
            echo "mount options: ${profile[5]}"
            mountoptions="-o ${profile[5]}"
        fi
        if [ $1 = "-m" -o $1 = "m" ]; then
            sshfs "${profile[3]}"@"${profile[1]}":"${profile[4]}" "${mountpath}/${profile[0]}" -p "${profile[2]}" ${mountoptions} && \
            echo -e "\nRemote directory now mounted at: ${mountpath}/${profile[0]}\n"
        elif [ $1 = "-i" -o $1 = "i" ]; then
            echo "${headinglist[6]}: ${mountpath}/${profile[0]}"
            echo -n "status: "
            checkmountpath ${profile[0]}
            if [ $? = "1" ]; then
                echo -e "CONNECTED\n"
            else
                echo -e "DISCONNECTED\n"
            fi
        fi
    fi
}

function findvalue {
    if [ $(echo "$1" | \grep -c "ip=\[") = 1 -a "$2" = "ip" ]; then
        value=$(echo "$1" | \grep "$2"\=\\[[^\\]*\\]\: -E -o | sed s/\\[// | sed s/\\]:$/:/)
    else
        value=$(echo "$1" | \grep "$2"\=[^\:]*\: -E -o)
    fi

    if [ -n "$value" ]; then
        value=$(echo "$value" | sed s/[0-9A-Za-z]*\=//)
        value=$(echo "$value" | sed s/:$//)
        return 0
    fi

    return 1
}

#Reads config file and loads information into the variables
function parseconf {
    #If an option that requires mountpath is selected, but the mountpath line is missing, use user input
    if [ $(cat "$2" | \grep -E ^"${headinglist[6]}" -c) = 0 ]; then
        if [ "$3" = "m" -o "$3" = "-m" -o "$3" = "u" -o "$3" = "-u" -o "$3" = "l" -o "$3" = "-l" ]; then
            echo "The mountpath line is missing from $confdir/$config"
            echo "Please add one like the following example"
            echo -e "eg: 'mountpath: /home/user/sshmnt:'\n"
            echo "Select the path you would like to mount your shares on"
            toggle=0
            while [ $toggle -eq 0 ]; do
                echo -n "mountpath: "; read mountpath
                if [ -d $mountpath ]; then
                    toggle=1
                else
                    echo "$mountpath is not a directory, please try again"
                fi
            done
        fi
    fi

    #If the editor option is selected, but the editor line is missing, use user input
    if [ $(cat "$2" | \grep -E ^"${headinglist[7]}" -c) = 0 ]; then
        if [ "$3" = "e" -o "$3" = "-e" ]; then
            echo "The editor line is missing from $confdir/$config"
            echo "Please add one like the following example"
            echo -e "eg: 'editor=vim:', replacing 'vim' with your favorite editor\n"
            echo "Select the editor you would like to edit the config with"
            toggle=0
            while [ $toggle -eq 0 ]; do
                echo -n "editor: "; read editor
                if [ $(command -v $editor | wc -l) -ge 1 ]; then
                    toggle=1
                else
                    echo "$editor cannot be executed, please try again"
                fi
            done
        fi
    fi

    while read line; do
        if [ $(echo "$line" | \grep ^\ *# -c) = 0 ]; then
            #Mount Path
            findvalue "$line" "${headinglist[6]}"
            if [ "$?" = 0 ]; then
                mountpath="$value"
            fi

            #Editor
            findvalue "$line" "${headinglist[7]}"
            if [ "$?" = 0 ]; then
                editor="$value"
            fi

            #Location
            findvalue "$line" "${headinglist[0]}"
            if [ "$?" = 0 ]; then
                if [ $(echo "$locations" | \grep "$value" -c) != "0" ]; then
                    error "config"
                fi
                locations="$locations$value\n"
                if [ -n "$value" -a "$value" = "$1" ]; then
                    x=0
                    while [ $x -lt $((${#headinglist[@]} - 1)) ]; do
                        findvalue "$line" ${headinglist[$x]}
                        profile[$x]="$value"
                        x=$(($x+1))
                    done
                fi
            fi
        fi
    done < "$2"

    #if a profile is needed, make sure all fields are filled out
    #if they aren't, ask the user to supply the missing ones
    if [ "$3" = "-m" -o "$3" = "m" -o "$3" = "-s" -o "$3" = "s" ]; then
        if [ -z ${profile[0]} ]; then
            if [ -z "$1" ]; then
                error "syntax"
            fi
            echo
            echo "Temporary location: $1"
            profile[0]="$1"
        fi

        x=1
        y=$((${#headinglist[@]}-3))

        #If its ssh don't worry about 'path', if its unmount skip this
        if [ "$3" = "-s" -o "$3" = "s" ]; then
            y=$((y-1))
        elif [ "$3" = "-u" -o "$3" = "u" ]; then
            y=1
        fi

        while [ $x -lt "$y" ]; do
            if [ -z ${profile[$x]} ]; then
                echo -n "input '${headinglist[$x]}'"
                if [ ${headinglist[$x]} = "port" ]; then
                    #The user can skip entering a value to use the default (22)
                    echo -n " (SSH Default: 22): "
                    read profile[$x]
                    if [ -z ${profile[$x]} ]; then
                        profile[$x]=22
                    fi
                    x=$(($x+1))
                elif [ ${headinglist[$x]} = "mntopts" ]; then
                    #Mount options are optional and set through the profile only
                    x=$(($x+1))
                else
                    #All other mount options are required, and the script won't continue unless they have values
                    echo -n ": "
                    read profile[$x]
                    if [ -z ${profile[$x]} ]; then
                        echo -e "Incorrect value\n"
                    else
                        x=$(($x+1))
                    fi
                fi
            else
                x=$(($x+1))
            fi
        done
    fi
}

#Returns true if the mount folder exists in mtab (and is thus mounted), false otherwise
function checkmountpath {
    if [ $(\grep "$mountpath/$1 " /etc/mtab -c) = "0" ]; then
        return 0
    else
        return 1
    fi
}

#Deletes the mountfolder if its not in use anymore (and avoids rmdir error messages by checking)
function checkdelete {
    checkmountpath "$1"
    if [ $? = "0" -a -d "$mountpath"/"$1" ]; then
        if [ $(ls -A1 "$mountpath"/"$1" | wc -l) = "0" ]; then
            rmdir "$mountpath"/"$1"
        fi
    fi
}

#Outputs all the locations defined in the config file and displays whether or not they are mounted
function locations {
    for x in $(echo -e -n "$locations"); do
        LOCATIONSIZE=$(echo -n "$x" | wc -c)
        checkmountpath "$x"
        if [ $? = "1" ]; then
            SPACESRIGHT=$(echo $(tput cols)-$LOCATIONSIZE-11 | bc)
            echo -e -n "\E[30;42m$x "
            for i in `seq 1 $SPACESRIGHT`; do
                echo -n "-"
            done
            echo -n " CONNECTED"
            tput sgr0
        else
            SPACESRIGHT=$(echo $(tput cols)-$LOCATIONSIZE-14 | bc)
            echo -e -n "\E[31;40m$x "
            for i in `seq 1 $SPACESRIGHT`; do
                echo -n "-"
            done
            echo -n " DISCONNECTED"
            tput sgr0
        fi
        echo
    done
}

#Outputs help text
function help {
    echo -e "Usage: 'sshmnt option [arguments]'\n"
    echo "options:"
    echo -e "\t-m (or m) location: attempts to mount location"
    echo -e "\t-u (or u) location: attempts to unmount location"
    echo -e "\t-s (or s) location: attempts to login to location"
    echo -e "\t-i (or i) location: displays information about a given location"
    echo -e "\t-l (or l): displays a list of all defined locations"
    echo -e "\t-e (or e): starts editing the config file"
    echo -e "\t-h (or h): displays this help message"
}

#This function holds all the error messages; keeping things tidy :)
function error {
    case "$1" in
        syntax)
            echo -e "Syntax Error!\n"
            help
            ;;
        mounted)
            echo "This share is already mounted"
            checkdelete "$2"
            ;;
        notempty)
            echo "Mount directory contain files, please remove them and try again"
            checkdelete "$2"
            ;;
        notmounted)
            echo "The share you requested is not currently mounted"
            ;;
        mount)
            echo "There was an error mounting your share"
            ;;
        umount)
            echo "There was an error unmounting your share"
            ;;
        config)
            echo "There is an error in the config file, please fix it"
            ;;
        createconfig)
            echo "There was an error creating your config file"
            ;;
        missing)
            echo "The requested profile does not exist"
            ;;
        generic)
            echo "There was an error running sshmnt"
            ;;
    esac
    exit 1
}

#Installs a template config file if none exists
#This has the only call that could write over a file, so a double check makes sure it doesn't
function installtemplate {
    echo -e "$confdir/$config was missing or empty and so a template is being created\n"
    #TEMPLATE CREATION#
    #The 'if' is to double check that the config doesn't exist before making a new one
    if [ -s "$confdir/$config" ]; then
        error "createconfig"
        exit 1
    else
        echo "#CONFIG#" > "$confdir/$config"

        echo -e "Use absolute paths to define locations (avoid variables and '~')\n"

        echo "The mount path is the directory mounts will be created in"
        echo "Use absolute paths and ensure you have write permissions"
        echo "eg: mount path: /home/user/sshmnt"
        toggle=0
        while [ $toggle -eq 0 ]; do
            echo -n "mount path: "; read mountpath
            if [ -d $mountpath ]; then
                toggle=1
            else
                echo "$mountpath is not a directory, please try again"
            fi
        done
        echo "mountpath=$mountpath:" | sed "s/\/:$/:/g" >> "$confdir/$config"

        echo "Select the editor you would like to edit the config with"
        echo "eg: editor: vim"
        toggle=0
        while [ $toggle -eq 0 ]; do
            echo -n "editor: "; read editor
            if [ $(command -v $editor | wc -l) -ge 1 ]; then
                toggle=1
            else
                echo "$editor cannot be executed, please try again"
            fi
        done
        echo "editor=$editor:" >> "$confdir/$config"

        echo "#PROFILES#" >> "$confdir/$config"
        echo "#After each heading you must put an '=' and after each value you must put a ':'" >> "$confdir/$config"
        echo "#Available headings:" >> "$confdir/$config"
        echo "# location: this is the name of the profile (required)" >> "$confdir/$config"
        echo "# login: the name of the user to login as (required)" >> "$confdir/$config"
        echo "# ip: the url or ip of the server (required, ipv6 addresses must be surrounded in [ and ])" >> "$confdir/$config"
        echo "# port: the port the ssh daemon is running on (required, the default port used by most daemons is 22)" >> "$confdir/$config"
        echo "# path: the remote directory to use as the highest level of the mounted share (required, mount only)" >> "$confdir/$config"
        echo "# mntopts: sshfs mount options exactly as they'd appear following the -o option (optional, mount only)" >> "$confdir/$config"
        echo
        echo "#Example:" >> "$confdir/$config"
        echo "# location=name:login=username:ip=address:port=22:path=/basepath/of/mount:mntopts=uid=99,gid=99:" >> "$confdir/$config"
        echo -e "\nNow please run 'sshmnt -e' to edit $confdir/$config"
        exit 0
    fi
}

#This is the main program
install -d $confdir || error "generic"

if [ -s "$confdir/$config" ]; then
    parseconf "$2" "$confdir/$config" "$1"
else
    installtemplate
fi

if [ -n "$3" ]; then
    error "syntax"
elif [ -n "$1" -a -n "$2" ]; then
    if [ "$1" = "m" -o "$1" = "-m" ]; then
        trap 'checkdelete "$2"' 2 #Traps on ctrl-c
        checkmountpath "$2"
        if [ $? = "1" ]; then
            error "mounted" "$2"
        fi
        if [ -d "$mountpath"/"$2" ]; then 
            if [ $(ls -A1 "$mountpath"/"$2" | wc -l) != 0 ]; then
                error "notempty" "$2"
            fi
        fi
        install -d "$mountpath"/"$2" || error "generic"
        connect "$1"
        exitstatus=$?
        checkdelete "$2"
        if [ "$exitstatus" != 0 ]; then
            error "mount"
        fi

    elif [ "$1" = "u" -o "$1" = "-u" ]; then
        checkmountpath "$2"
        if [ $? = "1" ]; then
            fusermount -u "$mountpath"/"$2"
            exitstatus=$?
            checkdelete "$2"
            if [ "$exitstatus" != 0 ]; then
                error "umount"
            fi
        else
            error "notmounted"
        fi
    elif [ "$1" = "s" -o "$1" = "-s" ]; then
        connect "$1"
    elif [ "$1" = "-i" -o "$1" = "i" ]; then
        if [ $(cat "$confdir/$config" | \grep -c -E "location=$2:") = 0 ]; then
            error "missing"
            exit 1
        fi
        connect "$1"
    else
        error "syntax"
    fi
elif [ -n "$1" -a -z "$2" ]; then
    if [ "$1" = "l" -o "$1" = "-l" ]; then
        locations
    elif [ "$1" = "e" -o "$1" = "-e" ]; then
        "$editor" "$confdir/$config"
    elif [ "$1" = "h" -o "$1" = "help" -o "$1" = "-h" -o "$1" = "-help" -o "$1" = "--help" ]; then
        help
    else
        error "syntax"
    fi
else
    error "syntax"
fi
