commit 498481c491a347d6b78a0231500578ef81f6d382 Author: Michael Krayer Date: Thu Mar 18 10:58:17 2021 +0100 initial commit diff --git a/session b/session new file mode 100755 index 0000000..47eb76f --- /dev/null +++ b/session @@ -0,0 +1,283 @@ +#!/bin/bash +shopt -s nullglob + +## Setup configuration +CONFIG_FILE="$HOME/.sessionrc" +typeset -A config +# Set defaults +config=( + [socket_dir]="/tmp/" + [socket_base]="session-$UID" + [shell]="/bin/bash" + [redraw]="winch" +) +# Source from configuration file if available +if [ -f "$CONFIG_FILE" ]; then + while read line + do + if echo $line | grep -F = &>/dev/null + then + varname=$(echo "$line" | cut -d '=' -f 1) + config[$varname]=$(echo "$line" | cut -d '=' -f 2- | envsubst) + fi + done < $CONFIG_FILE +fi + +## Set some variables +SOCKET_PREFIX="${config[socket_dir]}/${config[socket_base]}-" +SOCKET_SUFFIX=".socket" + +# Exit codes +EXIT_SUCCESS=0 +EXIT_INVALID_MODE=1 +EXIT_SESSION_EXISTS=2 +EXIT_SESSION_NOT_EXIST=3 +EXIT_ALREADY_IN_SESSION=4 +EXIT_INVALID_CONFIG=5 +EXIT_CONFIG_EXISTS=6 +EXIT_INVALID_CHOICE=7 + +## Functions +print_help() { + echo >&2 "Usage: $(basename $0) mode [args]" + echo >&2 "Manage detachable shell sessions using 'dtach'." + echo >&2 "Available modes: " + echo >&2 " attach | att | a attach to an existing session" + echo >&2 " new | | n create a new session" + echo >&2 " terminate | term | t terminate an existing session (SIGTERM)" + echo >&2 " kill | | k send SIGKILL to an existing session" + echo >&2 " list | ls | l list all available sessions" + echo >&2 " info | | i print information on a session" + echo >&2 " help | | h display this help message" + echo >&2 "" + echo >&2 "$(basename $0) attach [name]" + echo >&2 " Attach to specified session. If no session name is provided, an interactive" + echo >&2 " selection menu is provided. Use to detach." + echo >&2 "" + echo >&2 "$(basename $0) new [name]" + echo >&2 " Create a new session. If no session name is provided, a generic name will" + echo >&2 " be generated. Use to detach." + echo >&2 "" + echo >&2 "$(basename $0) terminate [name]" + echo >&2 " Terminate a specified session. If no session name is provided, all sessions" + echo >&2 " will be terminated upon confirmation." + echo >&2 "" + echo >&2 "$(basename $0) kill [name]" + echo >&2 " Same as 'terminate' but SIGKILL is sent to the shell instead of SIGTERM." + echo >&2 "" + echo >&2 "$(basename $0) list" + echo >&2 " List all available sessions." + echo >&2 "" + echo >&2 "$(basename $0) info [name]" + echo >&2 " Print information on a specified session. If no session name is provided," + echo >&2 " information on all sessions will be printed. The output contains the session" + echo >&2 " name, the timestamp of creation and the path of its socket." + echo >&2 "" + echo >&2 "$(basename $0) help" + echo >&2 " Print this help message." + echo >&2 "" + echo >&2 "$(basename $0) is configured using the file '$CONFIG_FILE', which, if" + echo >&2 "non-existent, can be initialized with '$(basename $0) config generate'." +} + +get_available_sessions() { + local socket_paths=(${SOCKET_PREFIX}*${SOCKET_SUFFIX}) + local socket_names=() + for spath in ${socket_paths[@]}; do + sname=${spath##${SOCKET_PREFIX}} + sname=${sname%%${SOCKET_SUFFIX}} + socket_names+=("${sname}") + done + echo ${socket_names[@]} +} + +get_session_path() { + echo "${SOCKET_PREFIX}${1}${SOCKET_SUFFIX}" +} + +is_existing_session_path() { + if [ -S $1 ]; then + true + else + false + fi +} + +is_existing_session_name() { + is_existing_session_path $(get_session_path $1) +} + +get_new_session_name() { + local ii=1 + while is_existing_session_name $ii; do + ii=$((ii+1)) + done + echo $ii +} + +exit_if_in_session() { + if [ ! -z $DTACH_SOCKET_PATH ]; then + echo >&2 "Cannot attach to a session from within a session." + exit $EXIT_ALREADY_IN_SESSION + fi +} + +exit_if_existing_session() { + if ! is_existing_session_name $session_name; then + echo >&2 "Session '$session_name' does not exist." + exit $EXIT_SESSION_NOT_EXIST + fi +} + +exit_if_nonexisting_session() { + if ! is_existing_session_name $session_name; then + echo >&2 "Session '$session_name' does not exist." + exit $EXIT_SESSION_NOT_EXIST + fi +} + +print_session_info() { + local session_name=$1 + local session_path=$(get_session_path $session_name) + local session_time=$(date -r $session_path "+%Y-%m-%d %H:%M:%S") + printf "%-24s %-20s %s\n" "$session_name" "$session_time" "$session_path" +} + +## Parse first argument (= mode argument) +if [[ "$#" -lt 1 ]]; then + print_help + exit $EXIT_INVALID_MODE +fi +mode=$1 +shift + +## Now enter mode specific code +case $mode in + "attach"|"att"|"a") + exit_if_in_session + # If no session name specified: select interactively + if [ -z "$1" ]; then + sessions_avail=($(get_available_sessions)) + if [ ${#sessions_avail[@]} -eq 0 ]; then + exit $EXIT_SUCCESS + fi + echo >&2 "Select a session (q to quit)" + select session_name in ${sessions_avail[@]} + do + if [ -z "$session_name" ]; then + exit $EXIT_SUCCESS + fi + exit_if_nonexisting_session $session_name + break + done + else + session_name=$1 + exit_if_nonexisting_session $session_name + fi + session_path=$(get_session_path $session_name) + CMD="dtach -a $session_path -r ${config[redraw]}" + exec $CMD + ;; + "new"|"n") + exit_if_in_session + #PROMPT_COMMAND="echo -ne \"\033]0;$1 (on $HOSTNAME)\007\"" + # PS1="\[\e]0;My Titlito\a\]>>" + # If no session name specified: generate a generic one + if [ -z "$1" ]; then + session_name=$(get_new_session_name) + else + session_name=$1 + exit_if_existing_session $session_name + fi + session_path=$(get_session_path $session_name) + CMD="env DTACH_SOCKET_PATH=$session_path DTACH_SESSION_NAME=$session_name " + CMD+="dtach -c $session_path -r ${config[redraw]} ${config[shell]}" + echo $CMD + exec $CMD + ;; + "terminate"|"term"|"t"|"kill"|"k") + # Check if we are in terminate/kill mode + if [[ ${mode::1} == "k" ]]; then + SIGNAL="-9" # SIGKILL + CLEANUP=1 # requires manual cleanup + else + SIGNAL="-15" # SIGTERM + CLEANUP=0 + fi + # If no session name specified: terminate all sessions + if [ -z "$1" ]; then + read -p "Terminate all sessions? [y/N] " -n 1 -r + printf "\n" + case $REPLY in + "y"|"Y") + session_name=($(get_available_sessions)) + ;; + *) + echo "no" + ;; + esac + else + session_name=$1 + exit_if_nonexisting_session $session_name + fi + for sname in ${session_name[@]}; do + spath=$(get_session_path $sname) + PID=$(fuser $spath 2> /dev/null) + CMD="kill $SIGNAL $PID" + exec $CMD + if [[ "$CLEANUP" -eq 1 ]]; then + CMD="rm $spath" + exec $CMD + fi + done + exit $EXIT_SUCCESS + ;; + "list"|"ls"|"l") + sessions_avail=($(get_available_sessions)) + for sname in ${sessions_avail[@]}; do + printf "$sname" + if [[ "$sname" == "$DTACH_SESSION_NAME" ]]; then + printf "*" + fi + printf "\n" + done + exit $EXIT_SUCCESS + ;; + "info"|"i") + # If no session name specified: info for all sessions + if [ -z "$1" ]; then + session_name=($(get_available_sessions)) + else + session_name=$1 + exit_if_nonexisting_session $session_name + fi + for sname in ${session_name[@]}; do + print_session_info $sname + done + exit $EXIT_SUCCESS + ;; + "help"|"h") + print_help + exit $EXIT_SUCCESS + ;; + "config") + if [[ "$1" != "generate" ]]; then + echo >&2 "Use '$(basename $0) config generate' to generate default configuration file." + exit $EXIT_INVALID_CONFIG + fi + if [ -f "$CONFIG_FILE" ]; then + echo >&2 "Configuration file '$CONFIG_FILE' already exists. Please remove it first." + exit $EXIT_CONFIG_EXISTS + fi + printf "" > $CONFIG_FILE + for key in ${!config[@]}; do + echo "${key}=${config[$key]}" >> $CONFIG_FILE + done + exit $EXIT_SUCCESS + ;; + *) + echo >&2 "Invalid mode: ${mode}" + print_help + exit $EXIT_INVALID_MODE + ;; +esac