From f1c09bae257119ea3968b921b0a7954ee969c1f2 Mon Sep 17 00:00:00 2001 From: MatMoul Date: Sun, 31 May 2026 21:12:16 +0200 Subject: [PATCH] feat: add ssh menu script with sample YAML config --- .gitignore | 1 + mtm-ssh-menu | 162 +++++++++++++++++++++++++++++++ sample-config/global.yaml | 6 ++ sample-config/hosts/dev.yaml | 17 ++++ sample-config/hosts/prod.yaml | 15 +++ sample-config/hosts/staging.yaml | 17 ++++ 6 files changed, 218 insertions(+) create mode 100644 .gitignore create mode 100755 mtm-ssh-menu create mode 100644 sample-config/global.yaml create mode 100644 sample-config/hosts/dev.yaml create mode 100644 sample-config/hosts/prod.yaml create mode 100644 sample-config/hosts/staging.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f733c4b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +config/ diff --git a/mtm-ssh-menu b/mtm-ssh-menu new file mode 100755 index 0000000..0bade56 --- /dev/null +++ b/mtm-ssh-menu @@ -0,0 +1,162 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CONFIG_DIR="$SCRIPT_DIR/config" + +usage() { + cat <<'EOF' +Usage: sshm [--config-dir DIR] + +Options: + --help Show this help message + --config-dir DIR Use a custom config directory + +The config directory must contain: + - global.yaml + - hosts/*.yaml +EOF +} + +parse_args() { + while [[ $# -gt 0 ]]; do + case "$1" in + --config-dir) + if [[ $# -lt 2 ]]; then + printf 'Error: --config-dir requires a value.\n' >&2 + exit 1 + fi + CONFIG_DIR="$2" + shift 2 + ;; + --help|-h) + usage + exit 0 + ;; + *) + printf 'Error: unknown argument: %s\n' "$1" >&2 + usage >&2 + exit 1 + ;; + esac + done +} +parse_args "$@" + +dependency_check() { + if ! command -v yq >/dev/null 2>&1; then + printf 'Error: yq is required but not installed.\n' >&2 + exit 1 + fi + if ! command -v jq >/dev/null 2>&1; then + printf 'Error: jq is required but not installed.\n' >&2 + exit 1 + fi + if ! command -v ssh >/dev/null 2>&1; then + printf 'Error: ssh is required but not installed.\n' >&2 + exit 1 + fi + #if command -v fzf >/dev/null 2>&1; then + # printf 'Error: fzf is required but not installed.\n' >&2 + # exit 1 + #fi +} +dependency_check + +GLOBAL_CONFIG="$CONFIG_DIR/global.yaml" +HOSTS_DIR="$CONFIG_DIR/hosts" +DEFAULT_SSH_USER="root" +DEFAULT_SSH_PORT="22" +DEFAULT_SSH_OPTIONS="" +SSH_JUMP_HOSTS={} +SERVERS="" + +load_config() { + local GLOBAL_CONFIG_CONTENT + GLOBAL_CONFIG_CONTENT="$(<"$GLOBAL_CONFIG")" + DEFAULT_SSH_USER=$(yq -r '.ssh.default_user // "'$DEFAULT_SSH_USER'"' <<<"$GLOBAL_CONFIG_CONTENT") + DEFAULT_SSH_PORT=$(yq -r '.ssh.default_port // "'$DEFAULT_SSH_PORT'"' <<<"$GLOBAL_CONFIG_CONTENT") + DEFAULT_SSH_OPTIONS=$(yq -r '.ssh.default_options // "'"$DEFAULT_SSH_OPTIONS"'"' <<<"$GLOBAL_CONFIG_CONTENT") + SSH_JUMP_HOSTS=$(yq -r '.ssh.jump_hosts // {}' <<<"$GLOBAL_CONFIG_CONTENT") + + shopt -s nullglob + for file in "$HOSTS_DIR"/*.yaml; do + local FILE_CONTENT GROUP_NAME GROUP_SERVERS INDEX + FILE_CONTENT="$(<"$file")" + GROUP_NAME=$(basename "$file") + GROUP_NAME=${GROUP_NAME%.*} + GROUP_NAME=$(yq -r '.group // "'"$GROUP_NAME"'"' <<<"$FILE_CONTENT") + GROUP_SERVERS=$(yq -r '.servers // []' <<<"$FILE_CONTENT") + #echo "$GROUP_NAME" + INDEX=0 + + for ((i=0; i<$(jq -r '. | length' <<<"$GROUP_SERVERS"); i++)); do + local SSH_SERVER_NAME SSH_SERVER_ALIASES ALIASES SSH_SERVER_HOST SSH_SERVER_PORT SSH_SERVER_USER + SSH_SERVER_NAME=$(jq -r '.'"[$i]"'.name // ""' <<<"$GROUP_SERVERS") + SSH_SERVER_ALIASES=$(jq -r '.'"[$i]"'.aliases // ""' <<<"$GROUP_SERVERS") + SSH_SERVER_HOST=$(jq -r '.'"[$i]"'.host // ""' <<<"$GROUP_SERVERS") + SSH_SERVER_PORT=$(jq -r '.'"[$i]"'.port // "'"$DEFAULT_SSH_PORT"'"' <<<"$GROUP_SERVERS") + SSH_SERVER_USER=$(jq -r '.'"[$i]"'.port // "'"$DEFAULT_SSH_USER"'"' <<<"$GROUP_SERVERS") + #SSH_JUMP_HOST=$(jq -r '.'"[$i]"'.jump_host // ""' <<<"$GROUP_SERVERS") + #if [ "$SSH_JUMP_HOST" != "" ]; then + # SSH_JUMP_HOST=$(jq -r '.yverdon // ""' <<<"$SSH_JUMP_HOSTS") + #fi + #SSH_OPTIONS=$(jq -r '.'"[$i]"'.options // ""' <<<"$GROUP_SERVERS") + + if [ "$SSH_SERVER_ALIASES" != "" ]; then + ALIASES="("$(echo "$SSH_SERVER_ALIASES" | jq -r 'join(", ")')")" + #echo "$SSH_SERVER_NAME $ALIASES" + #else + # echo "$SSH_SERVER_NAME" + fi + #if [ "$SSH_JUMP_HOST" == "" ]; then + # echo "$SSH_SERVER_USER@$SSH_SERVER_HOST:$SSH_SERVER_PORT" + #else + # echo "$SSH_SERVER_USER@$SSH_SERVER_HOST:$SSH_SERVER_PORT via $SSH_JUMP_HOST" + #fi + #echo "$SSH_OPTIONS" + #echo '{ "name": "'"$SSH_SERVER_NAME"'" }' | jq + if [ "$SERVERS" != "" ]; then + SERVERS+="\n" + fi + SERVERS+="${GROUP_NAME} | ${INDEX} | ${SSH_SERVER_HOST} | ${SSH_SERVER_NAME} ${ALIASES} | ${SSH_SERVER_USER} | ${SSH_SERVER_PORT}" + INDEX=$((INDEX+1)) + done + + done + shopt -u nullglob + +} +popup_menu() { + local SERVER + SERVER=$(echo -e "${SERVERS}" | column -t -s "|" -o "|" | fzf -e --tac --with-nth=1,3,4,5,6 --delimiter="|") + local GROUP_NAME HOST_INDEX + GROUP_NAME=$(echo "$SERVER" | awk -F '|' '{print $1}' | xargs) + HOST_INDEX=$(echo "$SERVER" | awk -F '|' '{print $2}' | xargs) + ssh_connect "$GROUP_NAME" "$HOST_INDEX" +} +ssh_connect() { + local SERVER SSH_SERVER_USER SSH_SERVER_HOST SSH_SERVER_PORT SSH_SERVER_OPTIONS SSH_JUMP_HOST + SERVER=$(cat "$HOSTS_DIR/$1.yaml" | yq -r '.servers'"[$2]") + SSH_SERVER_USER=$(jq -r '.user // "'"$DEFAULT_SSH_USER"'"' <<<"$SERVER") + SSH_SERVER_HOST=$(jq -r '.host // ""' <<<"$SERVER") + SSH_SERVER_PORT=$(jq -r '.port // "'"$DEFAULT_SSH_PORT"'"' <<<"$SERVER") + + SSH_SERVER_OPTIONS="-p $SSH_SERVER_PORT" + SSH_JUMP_HOST=$(jq -r '.jump_host // ""' <<<"$SERVER") + if [ "$SSH_JUMP_HOST" != "" ]; then + SSH_JUMP_HOST=$(jq -r '.'"$SSH_JUMP_HOST"' // ""' <<<"${SSH_JUMP_HOSTS}") + SSH_JUMP_HOST="-t $SSH_JUMP_HOST ssh -p $SSH_SERVER_PORT" + SSH_SERVER_OPTIONS="" + fi + + echo "ssh ${SSH_SERVER_OPTIONS} ${SSH_JUMP_HOST} ${SSH_SERVER_USER}@${SSH_SERVER_HOST}" + ssh ${SSH_SERVER_OPTIONS} ${SSH_JUMP_HOST} "${SSH_SERVER_USER}"@"${SSH_SERVER_HOST}" +} + +main() { + load_config + popup_menu +} + +main diff --git a/sample-config/global.yaml b/sample-config/global.yaml new file mode 100644 index 0000000..d026593 --- /dev/null +++ b/sample-config/global.yaml @@ -0,0 +1,6 @@ +ssh: + default_user: root + default_port: 22 + default_options: "" + jump_hosts: + office: "user@192.168.10.11" diff --git a/sample-config/hosts/dev.yaml b/sample-config/hosts/dev.yaml new file mode 100644 index 0000000..f19053b --- /dev/null +++ b/sample-config/hosts/dev.yaml @@ -0,0 +1,17 @@ +group: dev +servers: + - name: api-1 + aliases: + - api + - backend + host: 192.168.10.11 + user: dev + port: 2222 + + - name: web-1 + aliases: + - frontend + - ui + host: 192.168.10.12 + user: dev + port: 22 diff --git a/sample-config/hosts/prod.yaml b/sample-config/hosts/prod.yaml new file mode 100644 index 0000000..c393384 --- /dev/null +++ b/sample-config/hosts/prod.yaml @@ -0,0 +1,15 @@ +group: prod +servers: + - name: web-1 + aliases: + - frontend + - web + host: web-1.prod.internal + jump_host: office + + - name: db-1 + aliases: + - database + - sql + host: 10.0.0.20 + jump_host: office diff --git a/sample-config/hosts/staging.yaml b/sample-config/hosts/staging.yaml new file mode 100644 index 0000000..b1712ae --- /dev/null +++ b/sample-config/hosts/staging.yaml @@ -0,0 +1,17 @@ +group: staging +servers: + - name: app-1 + aliases: + - app + - application + host: staging-app.example.com + user: ubuntu + port: 22 + ssh_options: "" + + - name: worker-1 + aliases: + - worker + host: 10.20.0.15 + user: ubuntu + port: 22