From 42f744c89e82bbb7300bccc9598984b8f4ffbff2 Mon Sep 17 00:00:00 2001 From: Gcaufy Date: Sun, 16 Feb 2020 23:12:49 +0800 Subject: [PATCH] first init --- .gitignore | 4 + README.md | 14 +++ lib/ask/list.sh | 302 +++++++++++++++++++++++++++++++++++++++++++++ lib/ask/text.sh | 284 ++++++++++++++++++++++++++++++++++++++++++ lib/table/table.sh | 121 ++++++++++++++++++ lib/util.sh | 44 +++++++ sshman | 252 +++++++++++++++++++++++++++++++++++++ 7 files changed, 1021 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 lib/ask/list.sh create mode 100644 lib/ask/text.sh create mode 100644 lib/table/table.sh create mode 100644 lib/util.sh create mode 100755 sshman diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1b0601c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea +.DS_Store +servers.db +servers.db.* diff --git a/README.md b/README.md new file mode 100644 index 0000000..7b6b18a --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# SSH Man + +A shell script to manage all your ssh config. works nicely with `fzf`. + + +# Usage + +``` +$ s +``` + +# Install + +comming soon diff --git a/lib/ask/list.sh b/lib/ask/list.sh new file mode 100644 index 0000000..234504a --- /dev/null +++ b/lib/ask/list.sh @@ -0,0 +1,302 @@ +#!/bin/bash + +#ref: https://github.com/tanhauhau/Inquirer.sh + +# store the current set options +OLD_SET=$- +set -e + +arrow="$(echo -e '\xe2\x9d\xaf')" +checked="$(echo -e '\xe2\x97\x89')" +unchecked="$(echo -e '\xe2\x97\xaf')" + +black="$(tput setaf 0)" +red="$(tput setaf 1)" +green="$(tput setaf 2)" +yellow="$(tput setaf 3)" +blue="$(tput setaf 4)" +magenta="$(tput setaf 5)" +cyan="$(tput setaf 6)" +white="$(tput setaf 7)" +bold="$(tput bold)" +normal="$(tput sgr0)" +dim=$'\e[2m' + +print() { + echo "$1" + tput el +} + +join() { + local IFS=$'\n' + local _join_list + eval _join_list=( '"${'${1}'[@]}"' ) + local first=true + for item in ${_join_list[@]}; do + if [ "$first" = true ]; then + printf "%s" "$item" + first=false + else + printf "${2-, }%s" "$item" + fi + done +} + +function gen_env_from_options() { + local IFS=$'\n' + local _indices + local _env_names + local _checkbox_selected + eval _indices=( '"${'${1}'[@]}"' ) + eval _env_names=( '"${'${2}'[@]}"' ) + + for i in $(gen_index ${#_env_names[@]}); do + _checkbox_selected[$i]=false + done + + for i in ${_indices[@]}; do + _checkbox_selected[$i]=true + done + + for i in $(gen_index ${#_env_names[@]}); do + printf "%s=%s\n" "${_env_names[$i]}" "${_checkbox_selected[$i]}" + done +} + +on_default() { + true; +} + +on_keypress() { + local OLD_IFS + local IFS + local key + OLD_IFS=$IFS + local on_up=${1:-on_default} + local on_down=${2:-on_default} + local on_space=${3:-on_default} + local on_enter=${4:-on_default} + local on_left=${5:-on_default} + local on_right=${6:-on_default} + local on_ascii=${7:-on_default} + local on_backspace=${8:-on_default} + _break_keypress=false + while IFS="" read -rsn1 key; do + case "$key" in + $'\x1b') + read -rsn1 key + if [[ "$key" == "[" ]]; then + read -rsn1 key + case "$key" in + 'A') eval $on_up;; + 'B') eval $on_down;; + 'D') eval $on_left;; + 'C') eval $on_right;; + esac + fi + ;; + ' ') eval $on_space ' ';; + [a-z0-9A-Z\!\#\$\&\+\,\-\.\/\;\=\?\@\[\]\^\_\{\}\~]) eval $on_ascii $key;; + $'\x7f') eval $on_backspace $key;; + '') eval $on_enter $key;; + esac + if [ $_break_keypress = true ]; then + break + fi + done + IFS=$OLD_IFS +} + +gen_index() { + local k=$1 + local l=0 + if [ $k -gt 0 ]; then + for l in $(seq $k) + do + echo "$l-1" | bc + done + fi +} + +cleanup() { + # Reset character attributes, make cursor visible, and restore + # previous screen contents (if possible). + tput sgr0 + tput cnorm + stty echo + + # Restore `set e` option to its orignal value + if [[ $OLD_SET =~ e ]] + then set -e + else set +e + fi +} + +control_c() { + cleanup + exit $? +} + +select_indices() { + local _select_list + local _select_indices + local _select_selected=() + eval _select_list=( '"${'${1}'[@]}"' ) + eval _select_indices=( '"${'${2}'[@]}"' ) + local _select_var_name=$3 + eval $_select_var_name\=\(\) + for i in $(gen_index ${#_select_indices[@]}); do + eval $_select_var_name\+\=\(\""${_select_list[${_select_indices[$i]}]}"\"\) + done +} + + + +# Support VIM hjkl move +on_list_input_ascii() { + key=$1 + if [[ $key == 'k' || $key == 'K' || $key == 'h' || $key == 'H' ]]; then + on_list_input_up + elif [[ $key == 'j' || $key == 'J' || $key == 'l' || $key == 'L' ]]; then + on_list_input_down + fi +} + +on_list_input_up() { + remove_list_instructions + tput cub "$(tput cols)" + + printf " ${_list_options[$_list_selected_index]}" + tput el + + if [ $_list_selected_index = 0 ]; then + _list_selected_index=$((${#_list_options[@]}-1)) + tput cud $((${#_list_options[@]}-1)) + tput cub "$(tput cols)" + else + _list_selected_index=$((_list_selected_index-1)) + + tput cuu1 + tput cub "$(tput cols)" + tput el + fi + + printf "${cyan}${arrow} %s ${normal}" "${_list_options[$_list_selected_index]}" +} + +on_list_input_down() { + remove_list_instructions + tput cub "$(tput cols)" + + printf " ${_list_options[$_list_selected_index]}" + tput el + + if [ $_list_selected_index = $((${#_list_options[@]}-1)) ]; then + _list_selected_index=0 + tput cuu $((${#_list_options[@]}-1)) + tput cub "$(tput cols)" + else + _list_selected_index=$((_list_selected_index+1)) + tput cud1 + tput cub "$(tput cols)" + tput el + fi + printf "${cyan}${arrow} %s ${normal}" "${_list_options[$_list_selected_index]}" +} + +on_list_input_enter_space() { + local OLD_IFS + OLD_IFS=$IFS + IFS=$'\n' + + tput cud $((${#_list_options[@]}-${_list_selected_index})) + tput cub "$(tput cols)" + + for i in $(seq $((${#_list_options[@]}+1))); do + tput el1 + tput el + tput cuu1 + done + tput cub "$(tput cols)" + + tput cuf $((${#prompt}+3)) + printf "${cyan}${_list_options[$_list_selected_index]}${normal}" + tput el + + tput cud1 + tput cub "$(tput cols)" + tput el + + _break_keypress=true + IFS=$OLD_IFS +} + +remove_list_instructions() { + if [ $_first_keystroke = true ]; then + tput cuu $((${_list_selected_index}+1)) + tput cub "$(tput cols)" + tput cuf $((${#prompt}+3)) + tput el + tput cud $((${_list_selected_index}+1)) + _first_keystroke=false + fi +} + +_list_input() { + local i + local j + prompt=$1 + eval _list_options=( '"${'${2}'[@]}"' ) + + _list_selected_index=0 + _first_keystroke=true + + trap control_c SIGINT EXIT + + stty -echo + tput civis + + print "${normal}${green}?${normal} ${bold}${prompt}${normal} ${dim}(Use arrow keys)${normal}" + + for i in $(gen_index ${#_list_options[@]}); do + tput cub "$(tput cols)" + if [ $i = 0 ]; then + print "${cyan}${arrow} ${_list_options[$i]} ${normal}" + else + print " ${_list_options[$i]}" + fi + tput el + done + + for j in $(gen_index ${#_list_options[@]}); do + tput cuu1 + done + + on_keypress on_list_input_up on_list_input_down on_list_input_enter_space on_list_input_enter_space on_list_input_up on_list_input_down on_list_input_ascii on_list_input_up + +} + + +list_input() { + _list_input "$1" "$2" + local var_name=$3 + eval $var_name=\'"${_list_options[$_list_selected_index]}"\' + unset _list_selected_index + unset _list_options + unset _break_keypress + unset _first_keystroke + + cleanup +} + +list_input_index() { + _list_input "$1" "$2" + local var_name=$3 + eval $var_name=\'"$_list_selected_index"\' + unset _list_selected_index + unset _list_options + unset _break_keypress + unset _first_keystroke + + cleanup +} diff --git a/lib/ask/text.sh b/lib/ask/text.sh new file mode 100644 index 0000000..0a8977e --- /dev/null +++ b/lib/ask/text.sh @@ -0,0 +1,284 @@ +#!/bin/bash + +#ref: https://github.com/tanhauhau/Inquirer.sh + +# store the current set options +OLD_SET=$- +set -e + +arrow="$(echo -e '\xe2\x9d\xaf')" +checked="$(echo -e '\xe2\x97\x89')" +unchecked="$(echo -e '\xe2\x97\xaf')" + +black="$(tput setaf 0)" +red="$(tput setaf 1)" +green="$(tput setaf 2)" +yellow="$(tput setaf 3)" +blue="$(tput setaf 4)" +magenta="$(tput setaf 5)" +cyan="$(tput setaf 6)" +white="$(tput setaf 7)" +bold="$(tput bold)" +normal="$(tput sgr0)" +gray="\033[1;30m" +dim=$'\e[2m' + +print() { + echo "$1" + tput el +} + +join() { + local IFS=$'\n' + local _join_list + eval _join_list=( '"${'${1}'[@]}"' ) + local first=true + for item in ${_join_list[@]}; do + if [ "$first" = true ]; then + printf "%s" "$item" + first=false + else + printf "${2-, }%s" "$item" + fi + done +} + +function gen_env_from_options() { + local IFS=$'\n' + local _indices + local _env_names + local _checkbox_selected + eval _indices=( '"${'${1}'[@]}"' ) + eval _env_names=( '"${'${2}'[@]}"' ) + + for i in $(gen_index ${#_env_names[@]}); do + _checkbox_selected[$i]=false + done + + for i in ${_indices[@]}; do + _checkbox_selected[$i]=true + done + + for i in $(gen_index ${#_env_names[@]}); do + printf "%s=%s\n" "${_env_names[$i]}" "${_checkbox_selected[$i]}" + done +} + +on_default() { + true; +} + +on_keypress() { + local OLD_IFS + local IFS + local key + OLD_IFS=$IFS + local on_up=${1:-on_default} + local on_down=${2:-on_default} + local on_space=${3:-on_default} + local on_enter=${4:-on_default} + local on_left=${5:-on_default} + local on_right=${6:-on_default} + local on_ascii=${7:-on_default} + local on_backspace=${8:-on_default} + _break_keypress=false + while IFS="" read -rsn1 key; do + case "$key" in + $'\x1b') + read -rsn1 key + if [[ "$key" == "[" ]]; then + read -rsn1 key + case "$key" in + 'A') eval $on_up;; + 'B') eval $on_down;; + 'D') eval $on_left;; + 'C') eval $on_right;; + esac + fi + ;; + ' ') eval $on_space ' ';; + [a-z0-9A-Z\!\#\$\&\+\,\-\.\/\;\=\?\@\[\]\^\_\{\}\~]) eval $on_ascii $key;; + $'\x7f') eval $on_backspace $key;; + '') eval $on_enter $key;; + esac + if [ $_break_keypress = true ]; then + break + fi + done + IFS=$OLD_IFS +} + +gen_index() { + local k=$1 + local l=0 + if [ $k -gt 0 ]; then + for l in $(seq $k) + do + echo "$l-1" | bc + done + fi +} + +cleanup() { + # Reset character attributes, make cursor visible, and restore + # previous screen contents (if possible). + tput sgr0 + tput cnorm + stty echo + + # Restore `set e` option to its orignal value + if [[ $OLD_SET =~ e ]] + then set -e + else set +e + fi +} + +control_c() { + cleanup + exit $? +} + +select_indices() { + local _select_list + local _select_indices + local _select_selected=() + eval _select_list=( '"${'${1}'[@]}"' ) + eval _select_indices=( '"${'${2}'[@]}"' ) + local _select_var_name=$3 + eval $_select_var_name\=\(\) + for i in $(gen_index ${#_select_indices[@]}); do + eval $_select_var_name\+\=\(\""${_select_list[${_select_indices[$i]}]}"\"\) + done +} + + + + + +on_text_input_left() { + remove_regex_failed + if [ $_current_pos -gt 0 ]; then + tput cub1 + _current_pos=$(($_current_pos-1)) + fi +} + +on_text_input_right() { + remove_regex_failed + if [ $_current_pos -lt ${#_text_input} ]; then + tput cuf1 + _current_pos=$(($_current_pos+1)) + fi +} + +on_text_input_enter() { + remove_regex_failed + + _text_input=$([ -z "$_text_input" ] && echo $_text_default_value || echo $_text_input) + + # Only use validator to check, because you can use regexp in your validator + if [[ "$(eval $_text_input_validator "$_text_input")" = true ]]; then + tput cub "$(tput cols)" + tput cuf $((${#_read_prompt}-${#_text_default_tip} - 36)) + printf "${cyan}${_text_input}${normal}" + tput el + tput cud1 + tput cub "$(tput cols)" + tput el + eval $var_name=\'"${_text_input}"\' + _break_keypress=true + else + _text_input_regex_failed=true + tput civis + tput cud1 + tput cub "$(tput cols)" + tput el + printf "${red}>>${normal} $_text_input_regex_failed_msg" + tput cuu1 + tput cub "$(tput cols)" + tput cuf $((${#_read_prompt}-19)) + tput el + _text_input="" + _current_pos=0 + tput cnorm + fi +} + +on_text_input_ascii() { + remove_regex_failed + local c=$1 + + if [ "$c" = '' ]; then + c=' ' + fi + + local rest="${_text_input:$_current_pos}" + _text_input="${_text_input:0:$_current_pos}$c$rest" + _current_pos=$(($_current_pos+1)) + + tput civis + printf "$c$rest" + tput el + if [ ${#rest} -gt 0 ]; then + tput cub ${#rest} + fi + tput cnorm +} + +on_text_input_backspace() { + remove_regex_failed + if [ $_current_pos -gt 0 ]; then + local start="${_text_input:0:$(($_current_pos-1))}" + local rest="${_text_input:$_current_pos}" + _current_pos=$(($_current_pos-1)) + tput cub 1 + tput el + tput sc + printf "$rest" + tput rc + _text_input="$start$rest" + fi +} + +remove_regex_failed() { + if [ $_text_input_regex_failed = true ]; then + _text_input_regex_failed=false + tput sc + tput cud1 + tput el1 + tput el + tput rc + fi +} + +text_input_default_validator() { + echo true; +} + +text_input() { + local prompt=$1 + local var_name=$2 + local _text_default_value=$3 + # If there are default value, then show as a gray tip + local _text_default_tip=$([ -z "$_text_default_value" ] && echo "" || echo "(${_text_default_value})") + local _text_input_regex_failed_msg=${4:-"Input validation failed"} + local _text_input_validator=${5:-text_input_default_validator} + local _read_prompt_start=$'\e[32m?\e[39m\e[1m' + local _read_prompt_end=$'\e[22m' + local _read_prompt="$( echo "$_read_prompt_start ${prompt} ${gray}${_text_default_tip}${normal} $_read_prompt_end")" + local _current_pos=0 + local _text_input_regex_failed=false + local _text_input="" + printf "$_read_prompt" + + + trap control_c SIGINT EXIT + + stty -echo + tput cnorm + + on_keypress on_default on_default on_text_input_ascii on_text_input_enter on_text_input_left on_text_input_right on_text_input_ascii on_text_input_backspace + eval $var_name=\'"${_text_input}"\' + + cleanup +} diff --git a/lib/table/table.sh b/lib/table/table.sh new file mode 100644 index 0000000..981ec0e --- /dev/null +++ b/lib/table/table.sh @@ -0,0 +1,121 @@ +#!/bin/bash + +# Author : Gcaufy +# Date : 2020/02/16 + +dbpath="" +COL_SEP="[}" + +function table_refresh () { + cat "$dbpath" | grep "^#ID:" | cat > "$dbpath.tmp.deleted" + cat "$dbpath" | grep "^#" | grep -v "^#ID:" | cat > "$dbpath.tmp.comment" + cat "$dbpath" | grep -v "^#" | cat | column -t > "$dbpath.tmp.db" + cat "$dbpath.tmp.comment" "$dbpath.tmp.db" "$dbpath.tmp.deleted" > "$dbpath" + rm "$dbpath.tmp.db" > /dev/null 2>&1 + rm "$dbpath.tmp.comment" > /dev/null 2>&1 + rm "$dbpath.tmp.deleted" > /dev/null 2>&1 +} + +function table_init () { + dbpath=$1 + _columns=$2 + + if [[ ! -f "$dbpath" ]]; then + touch "$dbpath" + if ! [[ -z $_columns ]]; then + echo "ID"$(printf "$COL_SEP%s" "${_columns[@]}") | column -t -s $"$COL_SEP" > $dbpath + fi + else + table_refresh + fi +} + +function table_check_init () { + if [[ ! -f "$dbpath" ]]; then + printf '%s\n' "Please init table first" >&2 # write error message to stderr + exit 1 + fi +} + +function row_insert () { + table_check_init + + local _row=( "$@" ) + if [[ -z $_row ]]; then + printf 'Error: %s\n' 'Table insert empty row' >&2 + fi + local _rowstr=$(printf "$COL_SEP%s" "${_row[@]}") + local _tid="ID:"$(md5 -q -s "$_rowstr$RANDOM" | cut -c1-10) + + echo $(printf "%s$COL_SEP%s" "$_tid" "$_rowstr") | column -t -s $"$COL_SEP" >> $dbpath + table_refresh + echo $_tid +} + +function row_update () { + local _row=( "$@" ) + local _id=${_row[0]} + + if [[ -z $_row ]]; then + printf 'Error: %s\n' 'Table insert empty row' >&2 + fi + local _rowstr=$(printf "$COL_SEP%s" "${_row[@]}") + _rowstr=$(echo $(printf "%s" $_rowstr) | column -t -s $"$COL_SEP") + sed -i '' "s/^$_id.*/$_rowstr/" $dbpath + table_refresh +} +function row_delete () { + _id=$1 + sed -i '' "s/^$_id/#$_id/" "$dbpath" + table_refresh +} +function row_select () { + local _id=$1 + if [[ -z $_id ]]; then + cat $dbpath | grep "^ID:" | column -t + else + cat $dbpath | grep "^$_id" + fi +} +function test_row_insert () { + echo "======TEST: test row insert" + _row=( 'Jim' 'Green' '20' ) + _id=$(row_insert ${_row[@]}) + echo "Insert ID: $_id" +} +function test_row_delete () { + echo "======TEST: test row delete" + _row=( 'Jim' 'White' '21' ) + _id=$(row_insert ${_row[@]}) + row_delete $_id +} +function test_row_update () { + echo "======TEST: test row update" + local _row=( 'Jim' 'White' '21' ) + local _id=$(row_insert ${_row[@]}) + local _newrow=( 'Han' 'Meimei' '18' ) + row_update "$_id" "${_newrow[@]}" +} +function test_row_select () { + echo "======TEST: test row select" + local _row=( "Jay-Simth" "White" "92" ) + local _id=$(row_insert "${_row[@]}") + local _newrow + row_select + read -r -a _newrow <<< $(row_select "$_id") + echo ${_newrow[@]} | column -t +} +function test_table_init () { + echo "======TEST: test table init" + local _columns=( "FirstName" "LastName" "Age" ) + rm /tmp/bash_table.db + table_init "/tmp/bash_table.db" "${_columns[@]}" +} +# +# test_table_init +# test_row_insert +# test_row_update +# test_row_delete +# test_row_select +# echo "======TEST: DONE" +# cat /tmp/bash_table.db | column -t diff --git a/lib/util.sh b/lib/util.sh new file mode 100644 index 0000000..731cb8e --- /dev/null +++ b/lib/util.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +log() { + printf '\033[0;34m%s\033[0m\n' "${1}" +} +info() { + printf "\e[34m[I]\e[0m ${1}\n" +} +error() { + printf "\e[31m[✘]\e[0m ${1}\n" +} +success() { + printf "\e[32m[✔]\e[0m ${1}\n" +} +link() { + if [ -L ${2} ]; then + unlink ${2} + elif [ -e ${2} ]; then + info "Existing ${2} found, Moving ${2} -> ${2}.previous" + mv ${2} ${2}.previous + fi + ln -s ${1} ${2} + success "${2} -> ${1}" +} + +# Check if a command exists +function exists() { + command -v "$1" >/dev/null 2>&1 +} + +# Check num, >= min && <= max +function numberin () { + local _seq=$1 + local _min=$2 + local _max=$3 + local _re='^[0-9]+$' + if ! [[ $_seq =~ $_re ]] ; then + echo false + elif ! [[ $_seq -ge $_min && $_seq -le $_max ]]; then + echo false + else + echo true + fi +} diff --git a/sshman b/sshman new file mode 100755 index 0000000..3c2129a --- /dev/null +++ b/sshman @@ -0,0 +1,252 @@ +#!/bin/bash + +# Author : Gcaufy +# Date : 2020/02/16 + +FZF_COMMAND=fzfz +UNAME=$(uname) + +if [[ $UNAME == "Darwin" ]]; then + DIR=$(dirname $(readlink $0)) +else + DIR=$(dirname $(readlink -f $0)) +fi + +# Include lib files +for file in $(find "$DIR/lib" -name "*.sh"); do source "$file"; done + +DB_FILE="$DIR/servers.db" + +SERVER_CONFIGS=() +# Server config length +SERVER_CONFIG_LENGTH=${#SERVER_CONFIGS[*]} + +# Table schema +COL_INDEX_ID=0 +COL_INDEX_NAME=1 +COL_INDEX_USER=2 +COL_INDEX_HOST=3 +COL_INDEX_PORT=4 +COL_INDEX_TYPE=5 +COL_INDEX_PASSCODE=6 +COL_INDEX_FREQUENCY=7 +COLUMNS=( "NAME" "USER" "HOST" "PORT" "TYPE" "PASSCODE", "FREQUENCY" ) + +# Init +function init () { + # Init table with columns + table_init $DB_FILE "${COLUMNS[@]}" + + while read line + do + if [ ${line:0:1} != '#' ]; then + SERVER_CONFIGS+=("$line") + fi + done <<< "$(row_select | awk '{print $NF,$0}' | sort -nr | cut -f2- -d' ')" + + SERVER_CONFIG_LENGTH=${#SERVER_CONFIGS[*]} +} + +# Validate server sequence number +# It's a number and >=1 && <= length +# Return boolean +function validate_choose () { + local _choose=$1 + echo $(numberin $_choose "1" $SERVER_CONFIG_LENGTH) +} + +function validate_seq () { + local _seq=$1 + echo $(numberin $_seq "0" $(($SERVER_CONFIG_LENGTH - 1))) +} + +# Print list +function list () { + local _item="" + ( + printf 'SEQ NO\tSERVER NAME\tSSH HOST\tFREQUENCY\n' + for ((i=0;i<${SERVER_CONFIG_LENGTH};i++)); + do + _item=(${SERVER_CONFIGS[$i]}) #将一维sites字符串赋值到数组 + _seq=$(($i+1)) + printf '%s\t%s\t%s\t%s\t%s\n' ${_seq} ${_item[$COL_INDEX_NAME]} ${_item[$COL_INDEX_USER]}@${_item[$COL_INDEX_HOST]} ${_item[$COL_INDEX_FREQUENCY]} + done + ) | column -t -s $'\t' +} + +# Choose a server from list +# If fzf exsit, then use fzf, otherwise manully select a number +# return empty(CTRL + C) or a server sequence number. +function choose () { + local _type=$1 + if exists $FZF_COMMAND; then + local _wording="[Login] Choose the server>" + if [[ $_type == "edit" ]]; then + _wording="[Edit] Choose the server>" + elif [[ $_type == "delete" ]]; then + _wording="[Delete] Choose the server>" + fi + local _choose=$(list | fzf --no-preview --no-sort --prompt="$_wording" | awk '{print $1}') + if ! [[ -z $_choose ]]; then + local _seq=$(($_choose - 1)) + if ! [[ $(validate_seq $_seq) == false ]]; then + echo $_seq + fi + fi + else + list >&2; + echo -e "\n" >&2 + local _wording="[Login] Choose the server [1 - $SERVER_CONFIG_LENGTH]>" + if [[ $_type == "edit" ]]; then + _wording="[Edit] Choose the server [1 - $SERVER_CONFIG_LENGTH]>" + elif [[ $_type == "delete" ]]; then + _wording="[Delete] Choose server [1 - $SERVER_CONFIG_LENGTH]>" + fi + text_input "$_wording" _choose "1" "The number should be 1 - $SERVER_CONFIG_LENGTH" validate_choose >&2 + local _seq=$(($_choose - 1)) + echo "$_seq" + fi +} + +# Edit one record +function edit () { + local _seq=$(choose "edit" | xargs echo -n) + + if ! [[ $(validate_seq $_seq) == false ]]; then + local _item=(${SERVER_CONFIGS[$_seq]}) + local _id=${_item[$COL_INDEX_ID]} + + text_input "SSH Host:" _host ${_item[$COL_INDEX_HOST]} + text_input "Login User:" _user ${_item[$COL_INDEX_USER]} + text_input "Server Name:" _name ${_item[$COL_INDEX_NAME]} + text_input "Port:" _port ${_item[$COL_INDEX_PORT]} + + local _type=${_item[$COL_INDEX_TYPE]} + + if [[ $_type == "Passcode" ]]; then + text_input "Passcode:" _pwd ${_item[$COL_INDEX_PASSCODE]} + else + text_input "PemKeyFile Path:" _pwd ${_item[$COL_INDEX_PASSCODE]} + fi + + local _editrow=( "$_id" "$_name" "$_user" "$_host" "$_port" "$_type" "$_pwd" "0" ) + local _rst=$(row_update "${_editrow[@]}") + + success "Update config successfully" + fi +} + +# Add new server config +function new () { + text_input "SSH Host:" _host "127.0.0.1" + text_input "Login User:" _user "root" + text_input "Server Name:" _name "$_user@$_host" + text_input "Port:" _port "22" + + _methods=( 'Passcode' 'PemKeyFile' ) + list_input "Login Method:" _methods _type + if [[ $_type == "Passcode" ]]; then + text_input "Passcode:" _pwd "root" + else + text_input "PemKeyFile Path:" _pwd "~/.ssh/my.pem" + fi + + local _newrow=( "$_name" "$_user" "$_host" "$_port" "$_type" "$_pwd" "0" ) + local _newid=$(row_insert "${_newrow[@]}") + + success "Create config successfully" +} + +# delete one record +function delete () { + local _seq=$(choose "delete" | xargs echo -n) + + if ! [[ $(validate_seq $_seq) == false ]]; then + local _item=(${SERVER_CONFIGS[$_seq]}) + echo "Try to delete server [${_item[$COL_INDEX_NAME]}]" + + row_delete "${_item[$COL_INDEX_ID]}" + success "Delete config [${_item[$COL_INDEX_NAME]}] successfully" + fi +} + +# SSH expert login +function login(){ + local _seq=$1 + + # Invalid seq no, then choose again + if [[ $(validate_seq $_seq) == false ]]; then + _seq=$(choose "login" | xargs echo -n) + if ! [[ $(validate_seq $_seq) == false ]]; then + login $_seq + fi + else + local _item=(${SERVER_CONFIGS[$_seq]}) + + echo "Try to login server【${_item[$COL_INDEX_NAME]}】" + + # Update row frequency + _item[$COL_INDEX_FREQUENCY]=$((_item[$COL_INDEX_FREQUENCY] + 1)) + row_update ${_item[@]} + + # Expert command + command=" + expect { + \"*assword\" {set timeout 6000; send \"${_item[$COL_INDEX_PASSCODE]}\n\"; exp_continue ; sleep 3; } + \"*passphrase\" {set timeout 6000; send \"${_item[$COL_INDEX_PASSCODE]}\r\n\"; exp_continue ; sleep 3; } + \"yes/no\" {send \"yes\n\"; exp_continue;} + \"$*\" {send \"echo Login Success\n\";} + \"Last\" {send \"echo Login Success\n\";} + \"Welcome\" {send \"echo Login Success\n\";} + } + interact + "; + local _type=${_item[$COL_INDEX_TYPE]} + if ! [[ "$_type" == "Passcode" ]]; then + expect -c " + spawn ssh -p ${_item[$COL_INDEX_PORT]} -i ${_item[$COL_INDEX_PASSCODE]} ${_item[$COL_INDEX_USER]}@${_item[$COL_INDEX_HOST]} + ${command} + " + else + expect -c " + spawn ssh -p ${_item[$COL_INDEX_PORT]} ${_item[$COL_INDEX_USER]}@${_item[$COL_INDEX_HOST]} + ${command} + " + fi + echo "Loggout【${_item[$COL_INDEX_NAME]}】" + fi +} + +# Main entry +function main () { + # Init table + init + + # If there is no servers, then go to add one + if [[ $SERVER_CONFIG_LENGTH -le 0 ]] ;then + new + main $1 + else + # Parse arguments + if [ 1 == $# ]; then + if [ 'list' == $1 ] || [ '-l' == $1 ]; then + list "login" + elif [ 'edit' == $1 ] || [ '-e' == $1 ]; then + edit + elif [ 'new' == $1 ] || [ '-n' == $1 ]; then + new + elif [ 'delete' == $1 ] || [ '-d' == $1 ]; then + delete + else + login $1 + fi + else + local _seq=$(choose "login" | xargs echo -n) + if ! [[ $(validate_seq $_seq) == false ]]; then + login $_seq + fi + fi + fi +} + +main $1