Compare commits
10
Commits
0.0.1
...
ba79cedcb1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba79cedcb1 | ||
|
|
87945adae3 | ||
|
|
69131534fe | ||
|
|
c29d0dcb5d | ||
|
|
c942d95ec6 | ||
|
|
ba7df89340 | ||
|
|
8650b71c3c | ||
|
|
59f37d8455 | ||
|
|
ccd38c5e31 | ||
|
|
c0fcd67312 |
+5
-2
@@ -5,6 +5,9 @@
|
|||||||
- Main entry point: `mtm-ssh-menu`
|
- Main entry point: `mtm-ssh-menu`
|
||||||
- Configuration: `~/.config/mtm-ssh-menu/global.yaml` and `hosts/*.yaml` by default.
|
- Configuration: `~/.config/mtm-ssh-menu/global.yaml` and `hosts/*.yaml` by default.
|
||||||
- Dependencies: `bash`, `ssh`, `fzf`, `jq`, `yq`.
|
- Dependencies: `bash`, `ssh`, `fzf`, `jq`, `yq`.
|
||||||
- Sample config is stored in `sample-config/`.
|
- Sample config is stored in `sample-config/` and mirrors current script behavior.
|
||||||
- Documentation language: English.
|
- Documentation language: English.
|
||||||
- Current task focus: keep README aligned with the working script and sample configuration.
|
- Current task focus: keep README and memory aligned with the working script and sample configuration.
|
||||||
|
- Sample host files currently use numbered names: `01_prod.yaml`, `02_staging.yaml`, `03_dev.yaml`.
|
||||||
|
- Known follow-up improvements:
|
||||||
|
- Each group should have default values.
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
# mtm-ssh-menu
|
# mtm-ssh-menu
|
||||||
|
|
||||||
A small Bash utility to browse SSH targets from YAML configuration and connect to them with `fzf`.
|
A Bash SSH target picker built around `fzf` and YAML configuration.
|
||||||
|
|
||||||
## Features
|
## What it does
|
||||||
|
|
||||||
- Interactive selection with `fzf`
|
- Lists SSH targets from grouped YAML files
|
||||||
- Global defaults for SSH user, port, and jump hosts
|
- Uses the `group` value from each YAML file as the displayed group name
|
||||||
- Per-host overrides in grouped YAML files
|
- Allows the YAML file name to differ from the group name
|
||||||
- Support for jump hosts via named aliases
|
- Supports global defaults for user, port, and jump hosts
|
||||||
- Simple, dependency-light Bash script
|
- Supports per-host overrides
|
||||||
|
- Can also browse entries from `~/.ssh/known_hosts`
|
||||||
|
- Launches `ssh` directly from the selected entry
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- `bash`
|
|
||||||
- `ssh`
|
- `ssh`
|
||||||
- `fzf`
|
- `fzf`
|
||||||
- `jq`
|
- `jq`
|
||||||
@@ -21,13 +22,14 @@ A small Bash utility to browse SSH targets from YAML configuration and connect t
|
|||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./mtm-ssh-menu [--config-dir DIR]
|
./mtm-ssh-menu [--config-dir DIR] [-k [user]]
|
||||||
```
|
```
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--help`: show help
|
- `-h`, `--help`: show help
|
||||||
- `--config-dir DIR`: use a custom configuration directory
|
- `--config-dir DIR`: use a custom configuration directory
|
||||||
|
- `-k`, `--known-hosts [user]`: include hosts from `~/.ssh/known_hosts`
|
||||||
|
|
||||||
By default, the script reads configuration from:
|
By default, the script reads configuration from:
|
||||||
|
|
||||||
@@ -45,12 +47,14 @@ ssh:
|
|||||||
default_options: ""
|
default_options: ""
|
||||||
jump_hosts:
|
jump_hosts:
|
||||||
office: "user@192.168.10.11"
|
office: "user@192.168.10.11"
|
||||||
|
office-alt: "user@192.168.10.11:2222"
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### `hosts/dev.yaml`
|
### `hosts/01_prod.yaml`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
group: dev
|
group: prod
|
||||||
servers:
|
servers:
|
||||||
- name: api-1
|
- name: api-1
|
||||||
aliases:
|
aliases:
|
||||||
@@ -59,8 +63,33 @@ servers:
|
|||||||
host: 192.168.10.11
|
host: 192.168.10.11
|
||||||
user: dev
|
user: dev
|
||||||
port: 2222
|
port: 2222
|
||||||
|
|
||||||
|
- name: web-1
|
||||||
|
aliases:
|
||||||
|
- frontend
|
||||||
|
- ui
|
||||||
|
host: 192.168.10.12
|
||||||
|
user: dev
|
||||||
|
port: 22
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `hosts/03_dev.yaml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
group: dev
|
||||||
|
servers:
|
||||||
|
- name: web-1
|
||||||
|
aliases:
|
||||||
|
- frontend
|
||||||
|
- web
|
||||||
|
host: web-1.prod.internal
|
||||||
|
jump_host: office
|
||||||
|
```
|
||||||
|
|
||||||
|
The file name does not need to match the `group` value. The script reads the `group` from the YAML content and uses the file path internally to load the selected host.
|
||||||
|
|
||||||
|
The sample configuration now uses numbered file names such as `01_prod.yaml`, `02_staging.yaml`, and `03_dev.yaml`.
|
||||||
|
|
||||||
Each host entry can define:
|
Each host entry can define:
|
||||||
|
|
||||||
- `name`
|
- `name`
|
||||||
@@ -70,19 +99,20 @@ Each host entry can define:
|
|||||||
- `port`
|
- `port`
|
||||||
- `jump_host`
|
- `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
|
## Sample configuration
|
||||||
|
|
||||||
A complete sample configuration is available in `sample-config/`.
|
A complete sample configuration is available in `sample-config/`.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- The script requires `yq`, `jq`, `ssh`, and `fzf` to be installed.
|
- `jump_host` values are resolved by name from `global.yaml`.
|
||||||
- Jump hosts are resolved by name from `global.yaml`.
|
- Jump hosts support `user@host` and `user@host:port`; if the port is omitted, `22` is used.
|
||||||
- If a host does not define a user or port, global defaults are used.
|
- If a host does not define a user or port, global defaults are used.
|
||||||
|
- `default_options` is present in the config but not consumed by the current script.
|
||||||
|
- The sample config includes `ssh_options` in one file, but the current script does not consume it yet.
|
||||||
|
- Sample host examples are stored as `sample-config/hosts/01_prod.yaml`, `02_staging.yaml`, and `03_dev.yaml`.
|
||||||
|
- The `group` key is required for each host config file; the file name is only an internal storage detail.
|
||||||
|
- When `-k` is used, entries from `known_hosts` are shown with the selected user or `root` by default.
|
||||||
|
- `known_hosts` entries use port `22`.
|
||||||
|
- The script uses strict Bash mode (`set -euo pipefail`).
|
||||||
|
- The script name shown in usage is `sshm`, while the repository file is `mtm-ssh-menu`.
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ if git rev-parse -q --verify "refs/tags/${VERSION}" >/dev/null; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
git push
|
||||||
git checkout "${TAGBRANCH}"
|
git checkout "${TAGBRANCH}"
|
||||||
git merge "${RELEASEBRANCH}"
|
git merge "${RELEASEBRANCH}"
|
||||||
git push origin "${TAGBRANCH}"
|
git push origin "${TAGBRANCH}"
|
||||||
|
|||||||
+66
-28
@@ -2,14 +2,17 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
CONFIG_DIR="$HOME/.config/mtm-ssh-menu"
|
CONFIG_DIR="$HOME/.config/mtm-ssh-menu"
|
||||||
|
USE_KNOWN_HOSTS=0
|
||||||
|
USE_KNOWN_HOSTS_USER=$(whoami)
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
Usage: sshm [--config-dir DIR]
|
Usage: sshm [--config-dir DIR] [-k [user]]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--help Show this help message
|
-h | --help Show this help message
|
||||||
--config-dir DIR Use a custom config directory
|
--config-dir DIR Use a custom config directory
|
||||||
|
-k | --known-hosts [user] Include known_hosts
|
||||||
|
|
||||||
The config directory must contain:
|
The config directory must contain:
|
||||||
- global.yaml
|
- global.yaml
|
||||||
@@ -28,6 +31,15 @@ parse_args() {
|
|||||||
CONFIG_DIR="$2"
|
CONFIG_DIR="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
|
--known-hosts|-k)
|
||||||
|
USE_KNOWN_HOSTS=1
|
||||||
|
if [ $# -eq 1 ]; then
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
USE_KNOWN_HOSTS_USER="$2"
|
||||||
|
shift 2
|
||||||
|
fi
|
||||||
|
;;
|
||||||
--help|-h)
|
--help|-h)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
@@ -82,12 +94,13 @@ load_config() {
|
|||||||
for file in "$HOSTS_DIR"/*.yaml; do
|
for file in "$HOSTS_DIR"/*.yaml; do
|
||||||
local FILE_CONTENT GROUP_NAME GROUP_SERVERS INDEX
|
local FILE_CONTENT GROUP_NAME GROUP_SERVERS INDEX
|
||||||
FILE_CONTENT="$(<"$file")"
|
FILE_CONTENT="$(<"$file")"
|
||||||
GROUP_NAME=$(basename "$file")
|
GROUP_NAME=$(yq -r '.group // empty' <<<"$FILE_CONTENT")
|
||||||
GROUP_NAME=${GROUP_NAME%.*}
|
if [ "$GROUP_NAME" = "" ]; then
|
||||||
GROUP_NAME=$(yq -r '.group // "'"$GROUP_NAME"'"' <<<"$FILE_CONTENT")
|
printf 'Error: missing group name in config file: %s\n' "$file" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
GROUP_SERVERS=$(yq -r '.servers // []' <<<"$FILE_CONTENT")
|
GROUP_SERVERS=$(yq -r '.servers // []' <<<"$FILE_CONTENT")
|
||||||
INDEX=0
|
INDEX=0
|
||||||
|
|
||||||
for ((i=0; i<$(jq -r '. | length' <<<"$GROUP_SERVERS"); i++)); do
|
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
|
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_NAME=$(jq -r '.'"[$i]"'.name // ""' <<<"$GROUP_SERVERS")
|
||||||
@@ -95,6 +108,7 @@ load_config() {
|
|||||||
SSH_SERVER_HOST=$(jq -r '.'"[$i]"'.host // ""' <<<"$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_PORT=$(jq -r '.'"[$i]"'.port // "'"$DEFAULT_SSH_PORT"'"' <<<"$GROUP_SERVERS")
|
||||||
SSH_SERVER_USER=$(jq -r '.'"[$i]"'.user // "'"$DEFAULT_SSH_USER"'"' <<<"$GROUP_SERVERS")
|
SSH_SERVER_USER=$(jq -r '.'"[$i]"'.user // "'"$DEFAULT_SSH_USER"'"' <<<"$GROUP_SERVERS")
|
||||||
|
ALIASES=""
|
||||||
if [ "$SSH_SERVER_ALIASES" != "" ]; then
|
if [ "$SSH_SERVER_ALIASES" != "" ]; then
|
||||||
# shellcheck disable=SC2027
|
# shellcheck disable=SC2027
|
||||||
ALIASES="("$(echo "$SSH_SERVER_ALIASES" | jq -r 'join(", ")')")"
|
ALIASES="("$(echo "$SSH_SERVER_ALIASES" | jq -r 'join(", ")')")"
|
||||||
@@ -102,42 +116,66 @@ load_config() {
|
|||||||
if [ "$SERVERS" != "" ]; then
|
if [ "$SERVERS" != "" ]; then
|
||||||
SERVERS+="\n"
|
SERVERS+="\n"
|
||||||
fi
|
fi
|
||||||
SERVERS+="${GROUP_NAME} | ${INDEX} | ${SSH_SERVER_HOST} | ${SSH_SERVER_NAME} ${ALIASES} | ${SSH_SERVER_USER} | ${SSH_SERVER_PORT}"
|
SERVERS+="${GROUP_NAME} | ${file} | ${INDEX} | ${SSH_SERVER_HOST} | ${SSH_SERVER_NAME} ${ALIASES} | ${SSH_SERVER_USER} | ${SSH_SERVER_PORT}"
|
||||||
INDEX=$((INDEX+1))
|
INDEX=$((INDEX+1))
|
||||||
done
|
done
|
||||||
|
|
||||||
done
|
done
|
||||||
shopt -u nullglob
|
shopt -u nullglob
|
||||||
}
|
}
|
||||||
|
load_known_hosts() {
|
||||||
|
local FILE_CONTENT GROUP_NAME
|
||||||
|
FILE_CONTENT=$(cat "$HOME"/.ssh/known_hosts | awk -F ' ' '{print $1}' | sort | awk '!seen[$1]++')
|
||||||
|
GROUP_NAME="Local"
|
||||||
|
for host in $FILE_CONTENT; do
|
||||||
|
if [ "$SERVERS" != "" ]; then
|
||||||
|
SERVERS+="\n"
|
||||||
|
fi
|
||||||
|
SERVERS+="${GROUP_NAME} | known_hosts | ${host} | ${host} | ${USE_KNOWN_HOSTS_USER} | 22"
|
||||||
|
done
|
||||||
|
}
|
||||||
popup_menu() {
|
popup_menu() {
|
||||||
local SERVER
|
local SERVER
|
||||||
SERVER=$(echo -e "${SERVERS}" | column -t -s "|" -o "|" | fzf -e --tac --with-nth=1,3,4,5,6 --delimiter="|")
|
SERVER=$(echo -e "${SERVERS}" | column -t -s "|" -o "|" | fzf -e --layout=reverse --with-nth=1,4,5,6,7 --delimiter="|")
|
||||||
local GROUP_NAME HOST_INDEX
|
local GROUP_NAME GROUP_SOURCE HOST_INDEX
|
||||||
GROUP_NAME=$(echo "$SERVER" | awk -F '|' '{print $1}' | xargs)
|
GROUP_NAME=$(echo "$SERVER" | awk -F '|' '{print $1}' | xargs)
|
||||||
HOST_INDEX=$(echo "$SERVER" | awk -F '|' '{print $2}' | xargs)
|
GROUP_SOURCE=$(echo "$SERVER" | awk -F '|' '{print $2}' | xargs)
|
||||||
ssh_connect "$GROUP_NAME" "$HOST_INDEX"
|
HOST_INDEX=$(echo "$SERVER" | awk -F '|' '{print $3}' | xargs)
|
||||||
|
ssh_connect "$GROUP_SOURCE" "$HOST_INDEX"
|
||||||
}
|
}
|
||||||
ssh_connect() {
|
ssh_connect() {
|
||||||
local SERVER SSH_SERVER_USER SSH_SERVER_HOST SSH_SERVER_PORT SSH_SERVER_OPTIONS SSH_JUMP_HOST
|
if [[ "$HOST_INDEX" != *"."* ]]; then
|
||||||
SERVER=$(cat "$HOSTS_DIR/$1.yaml" | yq -r '.servers'"[$2]")
|
local SERVER SSH_SERVER_USER SSH_SERVER_HOST SSH_SERVER_PORT SSH_SERVER_OPTIONS SSH_JUMP_HOST
|
||||||
SSH_SERVER_USER=$(jq -r '.user // "'"$DEFAULT_SSH_USER"'"' <<<"$SERVER")
|
SERVER=$(yq -r '.servers['"$2"']' "$1")
|
||||||
SSH_SERVER_HOST=$(jq -r '.host // ""' <<<"$SERVER")
|
SSH_SERVER_USER=$(jq -r '.user // "'"$DEFAULT_SSH_USER"'"' <<<"$SERVER")
|
||||||
SSH_SERVER_PORT=$(jq -r '.port // "'"$DEFAULT_SSH_PORT"'"' <<<"$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_SERVER_OPTIONS="-p $SSH_SERVER_PORT"
|
||||||
SSH_JUMP_HOST=$(jq -r '.jump_host // ""' <<<"$SERVER")
|
SSH_JUMP_HOST=$(jq -r '.jump_host // ""' <<<"$SERVER")
|
||||||
if [ "$SSH_JUMP_HOST" != "" ]; then
|
if [ "$SSH_JUMP_HOST" != "" ]; then
|
||||||
SSH_JUMP_HOST=$(jq -r '.'"$SSH_JUMP_HOST"' // ""' <<<"${SSH_JUMP_HOSTS}")
|
SSH_JUMP_HOST=$(jq -r '.'"$SSH_JUMP_HOST"' // ""' <<<"${SSH_JUMP_HOSTS}")
|
||||||
SSH_JUMP_HOST="-t $SSH_JUMP_HOST ssh -p $SSH_SERVER_PORT"
|
SSH_JUMP_HOST_USER=$(cut -d'@' -f1 <<<"$SSH_JUMP_HOST")
|
||||||
SSH_SERVER_OPTIONS=""
|
SSH_JUMP_HOST_TARGET=$(cut -d'@' -f2- <<<"$SSH_JUMP_HOST")
|
||||||
|
SSH_JUMP_HOST_TARGET_HOST=$(cut -d':' -f1 <<<"$SSH_JUMP_HOST_TARGET")
|
||||||
|
SSH_JUMP_HOST_TARGET_PORT=$(cut -s -d':' -f2 <<<"$SSH_JUMP_HOST_TARGET")
|
||||||
|
if [ "$SSH_JUMP_HOST_TARGET_PORT" = "" ]; then
|
||||||
|
SSH_JUMP_HOST_TARGET_PORT="22"
|
||||||
|
fi
|
||||||
|
SSH_JUMP_HOST="-t ${SSH_JUMP_HOST_USER}@${SSH_JUMP_HOST_TARGET_HOST} ssh -p ${SSH_JUMP_HOST_TARGET_PORT}"
|
||||||
|
SSH_SERVER_OPTIONS=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
ssh ${SSH_SERVER_OPTIONS} ${SSH_JUMP_HOST} "${SSH_SERVER_USER}"@"${SSH_SERVER_HOST}"
|
||||||
|
else
|
||||||
|
ssh "${USE_KNOWN_HOSTS_USER}"@"${2}"
|
||||||
fi
|
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() {
|
main() {
|
||||||
|
if [ "$USE_KNOWN_HOSTS" == 1 ]; then
|
||||||
|
load_known_hosts
|
||||||
|
fi
|
||||||
load_config
|
load_config
|
||||||
popup_menu
|
popup_menu
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,3 +4,5 @@ ssh:
|
|||||||
default_options: ""
|
default_options: ""
|
||||||
jump_hosts:
|
jump_hosts:
|
||||||
office: "user@192.168.10.11"
|
office: "user@192.168.10.11"
|
||||||
|
dev: "user@192.168.10.11:2222"
|
||||||
|
backup: "admin@10.0.0.5:2200"
|
||||||
|
|||||||
Reference in New Issue
Block a user