6 Commits
Author SHA1 Message Date
matmoul b8aa1aaa8d chore: add release automation script 2026-05-31 22:15:15 +02:00
matmoul dc886c91cc docs: align README with current SSH menu configuration 2026-05-31 22:11:39 +02:00
matmoul 7166a8c3dd fix: read SSH server user from the correct config field 2026-05-31 21:55:32 +02:00
matmoul 334d2cbcaf fix: use user config dir and correct fzf check
Also stop printing the composed ssh command before connecting.
2026-05-31 21:25:05 +02:00
matmoul 71d9b0dfc5 fix: restore fzf dependency check and clean ssh menu script 2026-05-31 21:19:14 +02:00
matmoul f1c09bae25 feat: add ssh menu script with sample YAML config 2026-05-31 21:12:16 +02:00
9 changed files with 349 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
config/
+10
View File
@@ -0,0 +1,10 @@
# Project Memory
- Project: mtm-ssh-menu
- Type: Bash SSH target picker using `fzf` and YAML config.
- Main entry point: `mtm-ssh-menu`
- Configuration: `~/.config/mtm-ssh-menu/global.yaml` and `hosts/*.yaml` by default.
- Dependencies: `bash`, `ssh`, `fzf`, `jq`, `yq`.
- Sample config is stored in `sample-config/`.
- Documentation language: English.
- Current task focus: keep README aligned with the working script and sample configuration.
+86
View File
@@ -1,2 +1,88 @@
# mtm-ssh-menu
A small Bash utility to browse SSH targets from YAML configuration and connect to them with `fzf`.
## Features
- Interactive selection with `fzf`
- Global defaults for SSH user, port, and jump hosts
- Per-host overrides in grouped YAML files
- Support for jump hosts via named aliases
- Simple, dependency-light Bash script
## Requirements
- `bash`
- `ssh`
- `fzf`
- `jq`
- `yq`
## Usage
```bash
./mtm-ssh-menu [--config-dir DIR]
```
Options:
- `--help`: show help
- `--config-dir DIR`: use a custom configuration directory
By default, the script reads configuration from:
- `~/.config/mtm-ssh-menu/global.yaml`
- `~/.config/mtm-ssh-menu/hosts/*.yaml`
## Configuration format
### `global.yaml`
```yaml
ssh:
default_user: root
default_port: 22
default_options: ""
jump_hosts:
office: "user@192.168.10.11"
```
### `hosts/dev.yaml`
```yaml
group: dev
servers:
- name: api-1
aliases:
- api
- backend
host: 192.168.10.11
user: dev
port: 2222
```
Each host entry can define:
- `name`
- `aliases`
- `host`
- `user`
- `port`
- `jump_host`
## Example workflow
1. Create the config directory.
2. Add `global.yaml` and one or more files under `hosts/`.
3. Run the script.
4. Select a target in the menu and connect.
## Sample configuration
A complete sample configuration is available in `sample-config/`.
## Notes
- The script requires `yq`, `jq`, `ssh`, and `fzf` to be installed.
- Jump hosts are resolved by name from `global.yaml`.
- If a host does not define a user or port, global defaults are used.
Executable
+52
View File
@@ -0,0 +1,52 @@
#!/bin/bash
set -euo pipefail
declare -r VERSION=${1:-}
declare -r MESSAGE=${2:-"Release ${VERSION}"}
declare -r TAGBRANCH=main
declare -r RELEASEBRANCH=dev
declare CURRENTBRANCH=""
showHelp() {
echo "Usage: ./_release.sh <version> [message]"
}
cleanup() {
if [ -n "${CURRENTBRANCH}" ] && [ "${CURRENTBRANCH}" != "$(git rev-parse --abbrev-ref HEAD)" ]; then
git checkout "${CURRENTBRANCH}" >/dev/null 2>&1 || true
fi
}
trap cleanup EXIT
if [ "${VERSION}" == "" ]; then
showHelp
echo ""
echo "No version provided!"
exit 1
fi
CURRENTBRANCH=$(git rev-parse --abbrev-ref HEAD)
if [ "${CURRENTBRANCH}" != "${RELEASEBRANCH}" ]; then
echo "You are not on the ${RELEASEBRANCH} branch!"
echo "Use ${RELEASEBRANCH} branch to make a release!"
exit 1
fi
if ! git diff --quiet || ! git diff --cached --quiet; then
echo "Working tree is not clean. Commit or stash your changes first."
exit 1
fi
if git rev-parse -q --verify "refs/tags/${VERSION}" >/dev/null; then
echo "Tag ${VERSION} already exists."
exit 1
fi
git checkout "${TAGBRANCH}"
git merge "${RELEASEBRANCH}"
git push origin "${TAGBRANCH}"
git tag -a "${VERSION}" -m "${MESSAGE}"
git push origin "${VERSION}"
Executable
+145
View File
@@ -0,0 +1,145 @@
#!/usr/bin/env bash
set -euo pipefail
CONFIG_DIR="$HOME/.config/mtm-ssh-menu"
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")
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]"'.user // "'"$DEFAULT_SSH_USER"'"' <<<"$GROUP_SERVERS")
if [ "$SSH_SERVER_ALIASES" != "" ]; then
# shellcheck disable=SC2027
ALIASES="("$(echo "$SSH_SERVER_ALIASES" | jq -r 'join(", ")')")"
fi
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}"
# shellcheck disable=SC2086
ssh ${SSH_SERVER_OPTIONS} ${SSH_JUMP_HOST} "${SSH_SERVER_USER}"@"${SSH_SERVER_HOST}"
}
main() {
load_config
popup_menu
}
main
+6
View File
@@ -0,0 +1,6 @@
ssh:
default_user: root
default_port: 22
default_options: ""
jump_hosts:
office: "user@192.168.10.11"
+17
View File
@@ -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
+15
View File
@@ -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
+17
View File
@@ -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