From dcd6982fd6af6679611a327524e478c8eaf4bc0a Mon Sep 17 00:00:00 2001 From: Dan Date: Wed, 3 Jun 2026 09:53:36 +0800 Subject: [PATCH] feat: added ssh launcher --- ssh.sh | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100755 ssh.sh diff --git a/ssh.sh b/ssh.sh new file mode 100755 index 0000000..fbf6475 --- /dev/null +++ b/ssh.sh @@ -0,0 +1,133 @@ +#!/bin/sh + +set -eu + +KNOWN_CRED_FILE="$HOME/.ssh/known_creds" +KNOWN_HOSTS_FILE="$HOME/.ssh/known_hosts" + +# === 1. Load credentials from file === +TMP_CREDS=$(mktemp) +trap 'rm -f "$TMP_CREDS"' EXIT +if [ -f "$KNOWN_CRED_FILE" ]; then + cp "$KNOWN_CRED_FILE" "$TMP_CREDS" +fi + +# === 2. Parse known_hosts (deduped) === +TMP_HOSTS=$(mktemp) +trap 'rm -f "$TMP_CREDS" "$TMP_HOSTS"' EXIT +if [ -f "$KNOWN_HOSTS_FILE" ]; then + grep -v '^[[:space:]]*#' "$KNOWN_HOSTS_FILE" | \ + awk ' + { + split($1, parts, ",") + for (i in parts) { + h = parts[i] + gsub(/[][]/, "", h) + port = 22 + if (match(h, /:[0-9]+$/)) { + port = substr(h, RSTART+1) + h = substr(h, 1, RSTART-1) + } + key = h ":" port + if (!(key in seen)) { seen[key] = 1; print h "|" port } + } + }' > "$TMP_HOSTS" +fi + +# Check if hosts found +if [ ! -s "$TMP_HOSTS" ]; then + echo "⚠️ No hosts in ~/.ssh/known_hosts. Run 'ssh user@host' first." >&2 + exit 1 +fi + +# === 3. Build menu + store full credentials === +hosts_data=() +echo "📋 Available hosts:" +i=1 +while IFS='|' read -r host port; do + user="" + pass="" + # Safely extract credentials (avoid grep failure) + match=$(grep "^$host|" "$TMP_CREDS" 2>/dev/null | head -n1) || true + if [ -n "$match" ]; then + IFS='|' read -r _ user _ pass <<< "$match" + fi + + # Determine mode: 1 = auto-pass, 0 = manual + mode=0 + if [ -n "$user" ] && [ -n "$pass" ]; then + mode=1 + fi + + hosts_data+=("$host|$port|$user|$pass|$mode") + if [ "$mode" -eq 1 ]; then + echo "$i) $host ($port) — $user (auto-pass)" + else + echo "$i) $host ($port)" + fi + i=$((i + 1)) +done < "$TMP_HOSTS" +echo "$i) Cancel" + +# === 4. Prompt for host === +PS3="Select: " +while true; do + read -r choice || exit 0 + if [ "$choice" = "$i" ]; then + echo "Cancelled." + exit 0 + elif [ "$choice" -ge 1 ] && [ "$choice" -lt "$i" ]; then + break + fi + echo "Invalid. Try again:" +done + +# Extract selected host/credentials +item="${hosts_data[$((choice-1))]}" +IFS='|' read -r host port user pass mode <<< "$item" + +# === 5. Get credentials if not auto-pass === +if [ "$mode" -eq 0 ]; then + read -rp "Username [$USER]: " user + user="${user:-$USER}" + + stty -echo 2>/dev/null || true + read -rp "Password: " pass + stty echo 2>/dev/null || true + echo + + # Save if requested + read -rp "Save credentials? [y/N]: " save + if [ "$save" = "y" ] || [ "$save" = "Y" ]; then + if [ -f "$KNOWN_CRED_FILE" ]; then + grep -v "^$host|" "$KNOWN_CRED_FILE" > "${KNOWN_CRED_FILE}.tmp" || true + mv "${KNOWN_CRED_FILE}.tmp" "$KNOWN_CRED_FILE" + fi + echo "$host|$user|$port|$pass" >> "$KNOWN_CRED_FILE" + chmod 600 "$KNOWN_CRED_FILE" + fi +fi + +# === 6. Launch SSH — reuse existing window/pane === +# Ensure sshpass is available +if ! command -v sshpass >/dev/null 2>&1; then + echo "⚠️ Install sshpass: brew install hudochenkov/sshpass/sshpass" + exit 1 +fi + +# Get WezTerm pane ID if available +pane_id="" +if osascript -e 'application "WezTerm" is running' >/dev/null 2>&1; then + pane_id=$(wezterm cli list-panes -q 2>/dev/null | head -n1 | cut -d' ' -f1) || true +fi + +# Create safe command with quoted password +safe_cmd=$(printf 'sshpass -p %q ssh -p %s %s@%s' "$pass" "$port" "$user" "$host") + +# Reuse pane or spawn new tab +if [ -n "$pane_id" ]; then + wezterm cli send-text --pane "$pane_id" "$safe_cmd\n" +else + # Fallback: spawn new tab in existing window + wezterm cli spawn --cwd "$HOME" -- sh -c "$safe_cmd" +fi