Initial commit: Hyprland 0.57 rice for Fedora 44

Dark minimalist setup for a sysadmin workstation: rounded blur windows with
the DMS pink accent, keyboard-driven SSH hub (Mod+S), run-or-raise app
binds, Alt+Tab window switching, OCR of screen regions, hotkey cheatsheet,
DMS network-editor plugin and the systemd session target for portals.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-30 13:50:27 +05:00
co-authored by Claude Sonnet 5.5
commit 946d93faa7
19 changed files with 2068 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
#!/bin/sh
# Searchable hotkey reference; type to filter, Esc to close.
pkill -x fuzzel && exit 0
fuzzel --dmenu --prompt 'hotkeys ❯ ' --width 70 --lines 24 \
--font 'JetBrains Mono:size=13' \
--background 191112f2 --text-color f0dee0ff --prompt-color e91e63ff \
--selection-color 261d1eff --selection-text-color f0dee0ff \
--border-color e91e63ff --border-width 2 --border-radius 16 \
< "$HOME/.config/hypr/cheatsheet.txt" >/dev/null
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh
# Lock with DankMaterialShell (matches the shell theme). Fall back to swaylock-effects / swaylock
# when DMS is not running or the IPC call fails.
pgrep -x swaylock >/dev/null && exit 0
if pgrep -x dms >/dev/null && timeout 3 dms ipc call lock lock >/dev/null 2>&1; then
exit 0
fi
fx=$HOME/.local/bin/swaylock-effects
if [ -x "$fx" ]; then
exec "$fx" -f -C "$HOME/.config/swaylock/config-effects"
fi
exec swaylock -f
+13
View File
@@ -0,0 +1,13 @@
#!/bin/bash
# Select a screen region, OCR it (English + Russian) and put the text in the clipboard.
if ! command -v tesseract >/dev/null; then
notify-send -a ocr "OCR" "tesseract is not installed:\nsudo dnf install tesseract tesseract-langpack-eng tesseract-langpack-rus"
exit 1
fi
geom=$(slurp -b 00000066 -c e91e63ff -w 2) || exit 0
img() { grim -g "$geom" -; }
if command -v magick >/dev/null; then img() { grim -g "$geom" - | magick png:- -resize 200% -colorspace Gray png:-; }; fi
text=$(img | tesseract stdin stdout -l eng+rus --psm 6 2>/dev/null | sed 's/[[:space:]]*$//')
[ -z "$text" ] && { notify-send -a ocr "OCR" "nothing recognised"; exit 1; }
printf '%s' "$text" | wl-copy
notify-send -a ocr "Copied $(printf '%s\n' "$text" | wc -l) line(s)" "$(printf '%s\n' "$text" | head -2)"
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
# Alt+Tab: jump to the previously focused window (press again to jump back). RAISE_DRY=1 only prints.
target=$(hyprctl clients -j | jq -r '
[.[] | select(.workspace.name | startswith("special") | not) | select(.focusHistoryID > 0)]
| sort_by(.focusHistoryID) | .[0].address // empty')
[ -z "$target" ] && exit 0
[ -n "$RAISE_DRY" ] && { echo "focus: $target"; exit 0; }
hyprctl dispatch "hl.dsp.focus({ window = \"address:$target\" })" >/dev/null
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
# raise.sh <class-regex> <command...>
# Focus the app if it already has a window (also switches workspace/monitor). Pressing again while
# it is focused cycles through its other windows. If it is not running, launch <command>.
# RAISE_DRY=1 only prints what would happen.
re=$1; shift
act=$(hyprctl activewindow -j 2>/dev/null | jq -r '.address // ""')
target=$(hyprctl clients -j | jq -r --arg re "$re" --arg act "$act" '
[.[] | select(.class | test($re)) | select(.workspace.name | startswith("special") | not)]
| sort_by(.focusHistoryID) as $w
| if ($w | length) == 0 then ""
elif $w[0].address == $act and ($w | length) > 1 then $w[1].address
else $w[0].address end')
if [ -z "$target" ]; then
[ -n "$RAISE_DRY" ] && { echo "launch: $*"; exit 0; }
exec "$@"
fi
[ -n "$RAISE_DRY" ] && { echo "focus: $target"; exit 0; }
hyprctl dispatch "hl.dsp.focus({ window = \"address:$target\" })" >/dev/null
+214
View File
@@ -0,0 +1,214 @@
#!/bin/bash
# Keyboard-driven SSH hub (fzf). Launched by Mod+S in a floating kitty (class ssh-menu).
# Enter ssh Alt+T ssh+tmux Alt+F sftp Alt+Y copy addr Alt+K push key
# Alt+N new host Alt+E edit Alt+G group Alt+R toggle PROD Tab mark Alt+A/Z all/none
# Host metadata: "# group: X" / "# env: prod" comments above Host lines in ~/.ssh/config.
S=$(readlink -f "$0")
H="python3 -I $(dirname "$S")/ssh_hosts.py"
state=${XDG_STATE_HOME:-$HOME/.local/state}/ssh-menu
mkdir -p "$state"; touch "$state/history"
c_ok=$'\e[32m'; c_err=$'\e[31m'; c_dim=$'\e[2m'; c_hi=$'\e[38;2;233;30;99m'; c_off=$'\e[0m'
say() { printf '%s\n' "$*"; }
ask() { local v; read -e -r -p "$c_hi$1$c_off ${2:+[$2] }" v; printf '%s' "${v:-$2}"; }
yes() { local v; read -r -p "$c_hi$1$c_off [Y/n] " v; [[ ! $v =~ ^[nNнН] ]]; }
pause(){ read -r -s -n1 -p "${c_dim}press any key${c_off}"; echo; }
fz() { fzf --layout=reverse --height=45% --border=rounded --prompt="$1" --pointer='▌' \
--color='bg+:#261d1e,fg+:#ffffff,hl:#e91e63,hl+:#ff6398,pointer:#e91e63,prompt:#e91e63,border:#e91e63,info:#8e7a7d' "${@:2}"; }
# ---------- connect ----------
run_ssh() { # run_ssh <target> [remote command...]
local t=$1; shift
echo "$t" >> "$state/history"
# become a normal tiled window once we are a session, not a menu
[ "$SSH_MENU" = 1 ] && hyprctl dispatch 'hl.dsp.window.float({ action = "disable" })' >/dev/null 2>&1
local env; env=$($H env "$t")
if [ "$env" = prod ]; then printf '\e]2;⚠ PROD ssh: %s\a\e]11;#220a0f\a' "$t"
else printf '\e]2;ssh: %s\a' "$t"; fi
ssh "$t" "$@"; local rc=$?
printf '\e]111\a'
if [ $rc -ne 0 ]; then
printf '\n%sssh %s exited with %s%s (press Enter to close)' "$c_err" "$t" "$rc" "$c_off"; read -r _
fi
exit $rc
}
cmd_connect() { local t=${1:-$2}; [ -n "$t" ] && run_ssh "$t"; }
cmd_tmux() { run_ssh "$1" -t 'command -v tmux >/dev/null && exec tmux new-session -A -s main || exec $SHELL -l'; }
# ---------- quick actions ----------
cmd_sftp() {
local a jump url
read -r a jump < <($H addr "$1")
# jump hosts: let gvfs go through ssh config by alias; otherwise explicit user@host:port
if [ -n "$jump" ]; then url="sftp://$1/"; else url="sftp://$a/"; fi
notify-send -a ssh-menu "SFTP" "opening $a …"
# detached, so it survives the menu window closing
setsid -f nautilus --new-window "$url" >/dev/null 2>&1
}
cmd_copy() { read -r a _ < <($H addr "$1"); printf %s "$a" | wl-copy; notify-send -a ssh-menu "ssh" "copied $a"; }
# ---------- keys ----------
pick_key() {
local pubs sel name
mapfile -t pubs < <(ls "$HOME"/.ssh/*.pub 2>/dev/null)
sel=$( { printf '%s\n' "${pubs[@]}"; echo "+ generate new ed25519 key"; } | grep -v '^$' | fz 'key ❯ ' --height=30%) || return 1
if [[ $sel == "+ generate"* ]]; then
name=$(ask "key file name" "id_ed25519_$(date +%y%m)")
[ -e "$HOME/.ssh/$name" ] && { say "${c_err}$name exists${c_off}" >&2; return 1; }
ssh-keygen -t ed25519 -f "$HOME/.ssh/$name" -C "$USER@$(hostname -s)" >&2 || return 1
sel="$HOME/.ssh/$name.pub"
fi
printf '%s' "$sel"
}
cmd_keys() {
[ $# -eq 0 ] && return
local pub ok=() bad=() h
pub=$(pick_key) || return
say "${c_hi}key:${c_off} $pub ${c_hi}→${c_off} $*"; echo
for h in "$@"; do
say "${c_hi}── $h${c_off}"
if ssh-copy-id -i "$pub" "$h"; then ok+=("$h"); else bad+=("$h"); fi
done
echo; [ ${#ok[@]} -gt 0 ] && say "${c_ok}✓ ${ok[*]}${c_off}"
[ ${#bad[@]} -gt 0 ] && say "${c_err}✗ ${bad[*]}${c_off}"
pause
}
# ---------- new host / edit / group ----------
cmd_new() {
clear; say "${c_hi}── new SSH host ──${c_off} (Enter = default)"; echo
local alias hostname user port jump group env key
while :; do
alias=$(ask "alias (name in the menu)"); [ -z "$alias" ] && return
$H has "$alias" && { say "${c_err}already exists${c_off}"; continue; }
[[ $alias =~ [[:space:]*?!] ]] && { say "${c_err}no spaces or wildcards${c_off}"; continue; }
break
done
hostname=$(ask "HostName / IP" "$alias")
user=$(ask "User" "root")
port=$(ask "Port" "22")
jump=$(ask "ProxyJump (empty = none)")
group=$(ask "group (empty = none)")
yes "mark as PROD (red window)?" && env=prod || env=
key=$(ask "IdentityFile (empty = default)")
echo; say "${c_dim}Host $alias → $user@$hostname:$port ${jump:+via $jump }${group:+[$group] }${env:+PROD}${c_off}"
yes "save to ~/.ssh/config?" || return
$H add "$alias" "$hostname" "$user" "$port" "$jump" "$group" "$env" "$key" || { pause; return; }
say "${c_ok}saved${c_off} (backup in $state/backups)"
yes "push your public key to it now?" && cmd_keys "$alias"
echo "$alias" > "$state/lastnew"
}
cmd_edit() {
local ed=micro; command -v micro >/dev/null || ed=${EDITOR:-nano}
local l; l=$($H line "$1")
if [ "$ed" = micro ]; then micro "+$l" ~/.ssh/config; else "$ed" "+$l" ~/.ssh/config; fi
}
cmd_group() {
[ $# -eq 0 ] && return
local out rc g h
out=$($H groups | fz "group ❯ " --height=40% --print-query \
--header "pick a group or type a new name (empty = clear)"); rc=$?
[ $rc -gt 1 ] && return # Esc
mapfile -t o <<< "$out" # line 1 = query, line 2 = selection (if any)
g=${o[1]:-${o[0]}}
for h in "$@"; do $H set "$h" group "$g"; done
}
cmd_envtoggle() { for h in "$@"; do $H toggle-prod "$h"; done; }
# ---------- several hosts ----------
confirm_prod() { # confirm_prod host... ; asks (default: no) only when PROD hosts are involved
local prod=() h v
for h in "$@"; do [ "$($H env "$h")" = prod ] && prod+=("$h"); done
[ ${#prod[@]} -eq 0 ] && return 0
say "${c_err}${#prod[@]} of $# hosts are PROD:${c_off} ${prod[*]}"
read -r -p "${c_hi}continue?${c_off} [y/N] " v
[[ $v =~ ^[yYдД] ]]
}
cmd_broadcast() { # one tmux pane per host, keyboard input synced (Alt+S toggles)
[ $# -eq 0 ] && return
confirm_prod "$@" || return
local conf; conf="$(dirname "$S")/tmux-broadcast.conf"
local T=(tmux -L sshmenu -f "$conf") sess="bc-$(date +%H%M%S)" h first=1
for h in "$@"; do
echo "$h" >> "$state/history"
if [ $first = 1 ]; then "${T[@]}" new-session -d -s "$sess" -x 220 -y 60 "ssh $(printf %q "$h")"; first=0
else "${T[@]}" split-window -t "$sess" "ssh $(printf %q "$h")"; "${T[@]}" select-layout -t "$sess" tiled; fi
"${T[@]}" select-pane -t "$sess" -T "$h"
done
"${T[@]}" setw -t "$sess" synchronize-panes on
setsid -f kitty --title "broadcast: $# hosts" "${T[@]}" attach -t "$sess" >/dev/null 2>&1
}
cmd_run() { # run one command on all marked hosts, identical answers merged
[ $# -eq 0 ] && return
local cmd v
set -o history; HISTFILE="$state/cmd_history"; history -r
say "${c_dim}run on: $*${c_off}"
read -e -r -p "${c_hi}\$${c_off} " cmd; [ -z "$cmd" ] && return
history -s "$cmd"; history -w
confirm_prod "$@" || return
clear
$H run "$cmd" "$@" | less -RFX
pause
}
cmd_web() { # open the host's web UI; first time asks for the URL and stores it as "# url:"
local url; url=$($H url "$1")
if [ -z "$url" ]; then
url=$(ask "web UI for $1" "$($H url-default "$1")"); [ -z "$url" ] && return
$H set "$1" url "$url"
fi
setsid -f flatpak run com.google.Chrome "$url" >/dev/null 2>&1
}
# ---------- dispatcher ----------
case "$1" in
connect) shift; cmd_connect "$@"; exit ;;
tmux) shift; cmd_tmux "$@"; exit ;;
sftp) shift; cmd_sftp "$@"; exit ;;
copy) shift; cmd_copy "$@"; exit ;;
keys) shift; cmd_keys "$@"; exit ;;
new) cmd_new; exit ;;
edit) shift; cmd_edit "$@"; exit ;;
group) shift; cmd_group "$@"; exit ;;
envtoggle) shift; cmd_envtoggle "$@"; exit ;;
broadcast) shift; cmd_broadcast "$@"; exit ;;
run) shift; cmd_run "$@"; exit ;;
web) shift; cmd_web "$@"; exit ;;
preview) shift; $H preview "$1"; exit ;;
list) $H list; exit ;;
live) $H live; exit ;;
esac
# ---------- menu ----------
# hotkey table: accent key + dim description, 4 columns
key() { printf '\e[1;38;2;248;187;217m%-6s\e[0m\e[38;2;150;135;140m%-15s\e[0m' "$1" "$2"; }
hdr=$(
key Enter connect; key Alt+T tmux; key Alt+F sftp; key Alt+W web; echo
key Alt+Y 'copy addr'; key Alt+N 'new host'; key Alt+E 'edit config'; key Alt+R 'toggle PROD'; echo
key Tab mark; key Alt+A 'mark all'; key Alt+Z unmark; key Alt+G group; echo
key Alt+B broadcast; key Alt+X 'run cmd'; key Alt+K 'push key'
)
$H list | fzf --ansi --multi --layout=reverse --height=100% --tiebreak=index \
--style=full:rounded --padding=1,2 --highlight-line \
--prompt=' ' --pointer='▌' --gutter=' ' --marker='✓' --info=inline-right --no-scrollbar \
--input-label=' ssh ' --list-label=' hosts ' --header-label=' keys ' --preview-label=' details ' \
--ghost='type to filter, or user@host to connect to anything' \
--header "$hdr" \
--color='bg+:#2a2024,fg+:#ffffff,hl:#e91e63,hl+:#ff6398,pointer:#e91e63,prompt:#e91e63,info:#8e7a7d,marker:#f8bbd9,spinner:#e91e63,ghost:#6b5a5e,gutter:-1' \
--color='border:#3a2a2e,list-border:#3a2a2e,header-border:#3a2a2e,preview-border:#3a2a2e,input-border:#e91e63' \
--color='label:#e91e63,input-label:#e91e63,list-label:#8e7a7d,header-label:#8e7a7d,preview-label:#8e7a7d,header:-1' \
--preview "$S preview {2}" --preview-window='right:32%:wrap' \
--bind "start:reload($S live)" \
--bind "enter:become($S connect {2} {q})" \
--bind "alt-t:become($S tmux {2})" \
--bind "alt-f:execute-silent($S sftp {2})+abort" \
--bind "alt-y:execute-silent($S copy {2})+abort" \
--bind "alt-k:execute($S keys {+2})+reload($S list)" \
--bind "alt-n:execute($S new)+reload($S list)+transform-query(cat $state/lastnew 2>/dev/null; rm -f $state/lastnew)" \
--bind "alt-e:execute($S edit {2})+reload($S live)" \
--bind "alt-g:execute($S group {+2})+reload($S list)" \
--bind "alt-r:execute-silent($S envtoggle {+2})+reload($S list)" \
--bind "alt-b:execute($S broadcast {+2})+abort" \
--bind "alt-x:execute($S run {+2})" \
--bind "alt-w:execute($S web {2})+reload($S list)+abort" \
--bind "alt-a:select-all,alt-z:deselect-all"
+326
View File
@@ -0,0 +1,326 @@
#!/usr/bin/env python3
"""Helper for ssh-menu.sh: parse and edit ~/.ssh/config.
Host metadata lives in comments directly above a `Host` line:
# group: Proxmox
# env: prod
Host pm
"""
import os
import re
import shutil
import socket
import subprocess
import sys
import time
import zlib
from collections import Counter
from concurrent.futures import ThreadPoolExecutor
HOME = os.path.expanduser("~")
CFG = os.path.join(HOME, ".ssh", "config")
STATE = os.path.join(os.environ.get("XDG_STATE_HOME", HOME + "/.local/state"), "ssh-menu")
HIST = os.path.join(STATE, "history")
CACHE = os.path.join(STATE, "status.tsv")
BACKUPS = os.path.join(STATE, "backups")
os.makedirs(BACKUPS, exist_ok=True)
G, R, Y, D, B, X = "\x1b[32m", "\x1b[31m", "\x1b[33m", "\x1b[2m", "\x1b[1;31m", "\x1b[0m"
def read_lines():
try:
return open(CFG).read().split("\n")
except FileNotFoundError:
return []
def backup():
if os.path.exists(CFG):
shutil.copy2(CFG, os.path.join(BACKUPS, "config-" + time.strftime("%Y%m%d-%H%M%S")))
old = sorted(os.listdir(BACKUPS))[:-20]
for f in old:
os.remove(os.path.join(BACKUPS, f))
def write_lines(lines):
backup()
text = "\n".join(lines)
if not text.endswith("\n"):
text += "\n"
with open(CFG, "w") as f:
f.write(text)
os.chmod(CFG, 0o600)
def parse():
hosts, meta = [], {}
for i, l in enumerate(read_lines()):
m = re.match(r"^\s*#\s*(group|env|url)\s*:\s*(.+?)\s*$", l, re.I)
if m:
meta[m.group(1).lower()] = m.group(2)
continue
m = re.match(r"^\s*host\s+(.+?)\s*$", l, re.I)
if m:
for a in m.group(1).split():
if not re.search(r"[*?!]", a):
hosts.append(dict(alias=a, group=meta.get("group", ""), env=meta.get("env", ""), url=meta.get("url", ""), line=i + 1))
meta = {}
elif l.strip() == "" or not l.lstrip().startswith("#"):
meta = {}
return hosts
def find(alias):
return next((h for h in parse() if h["alias"] == alias), None)
def resolve(alias):
"""ssh -G -> dict(user, hostname, port, proxyjump, identity)"""
info = dict(user="", hostname=alias, port="22", proxyjump="", identity="")
try:
out = subprocess.run(["ssh", "-G", alias], capture_output=True, text=True, timeout=3).stdout
except Exception:
return info
ids = []
for l in out.splitlines():
k, _, v = l.partition(" ")
if k == "user": info["user"] = v
elif k == "hostname": info["hostname"] = v
elif k == "port": info["port"] = v
elif k == "proxyjump": info["proxyjump"] = v
elif k == "identityfile": ids.append(os.path.expanduser(v))
info["identity"] = next((p for p in ids if os.path.exists(p)), "")
return info
def probe(info):
if info["proxyjump"]:
return "jump"
try:
socket.create_connection((info["hostname"], int(info["port"])), timeout=0.6).close()
return "up"
except Exception:
return "down"
def addr(info):
return f'{info["user"]}@{info["hostname"]}:{info["port"]}'
def load_cache():
c = {}
try:
for l in open(CACHE):
a, ad, st = l.rstrip("\n").split("\t")
c[a] = (ad, st)
except Exception:
pass
return c
def uses():
try:
return Counter(open(HIST).read().split())
except FileNotFoundError:
return Counter()
PALETTE = [(248, 187, 217), (125, 207, 255), (158, 206, 106), (224, 175, 104), (187, 154, 247), (115, 218, 202)]
def rgb(r, g, b, bold=False):
return f"\x1b[{'1;' if bold else ''}38;2;{r};{g};{b}m"
FIXED = {"proxmox": (248, 187, 217), "services": (125, 207, 255), "git": (158, 206, 106), "legacy": (130, 120, 125)}
def group_color(name):
if name.lower() in FIXED:
return FIXED[name.lower()]
return PALETTE[zlib.crc32(name.lower().encode()) % len(PALETTE)]
def group_cell(name, width):
if not name:
return " " * width
return rgb(*group_color(name)) + f"{name:<{width}}" + X
def addr_cell(ad):
"""user@host:port with the host highlighted and the rest dimmed"""
m = re.match(r"^(.*)@(.*):(\d+)$", ad or "")
if not m:
return D + (ad or "") + X
u, h, p = m.groups()
return f"{rgb(120, 108, 112)}{u}@{X}{rgb(205, 195, 200)}{h}{X}{rgb(120, 108, 112)}" + ("" if p == "22" else f":{p}") + X
def cmd_list(live):
hosts = parse()
cnt = uses()
hosts.sort(key=lambda h: (-cnt.get(h["alias"], 0), h["alias"].lower()))
if live:
def work(h):
i = resolve(h["alias"])
return h["alias"], addr(i), probe(i)
with ThreadPoolExecutor(32) as ex:
rows = list(ex.map(work, hosts))
with open(CACHE, "w") as f:
for a, ad, st in rows:
f.write(f"{a}\t{ad}\t{st}\n")
cache = {a: (ad, st) for a, ad, st in rows}
else:
cache = load_cache()
dots = {"up": G + "●" + X, "down": "\x1b[1;31m●" + X, "jump": Y + "●" + X}
for h in hosts:
ad, st = cache.get(h["alias"], ("", "?"))
dot = dots.get(st, D + "·" + X)
prod = rgb(255, 92, 108, bold=True) + "PROD" + X if h["env"].lower() == "prod" else " "
print(f'{dot} {h["alias"]:<24} {group_cell(h["group"], 11)} {prod} {addr_cell(ad)}')
def cmd_preview(alias):
lab = lambda t: rgb(120, 108, 112) + f"{t:<7}" + X
val = lambda t: rgb(235, 225, 230) + str(t) + X
h = find(alias)
if not h:
print(f"{alias}\n\n{D}not in ~/.ssh/config:\nwill connect as typed{X}")
return
i = resolve(alias)
st = probe(i)
col = {"up": (120, 235, 90), "down": (255, 92, 108), "jump": (224, 175, 104)}[st]
prod = " " + rgb(255, 92, 108, bold=True) + "PROD" + X if h["env"].lower() == "prod" else ""
print(rgb(*col, bold=True) + "● " + alias + X + prod)
print(f'{D}{addr(i)}{X}\n')
if i["proxyjump"]:
print(lab("via") + val(i["proxyjump"]))
print(lab("key") + val(os.path.basename(i["identity"]) or "default / agent"))
if h["group"]:
print(lab("group") + rgb(*group_color(h["group"])) + h["group"] + X)
if h["url"]:
print(lab("web") + val(h["url"]))
print(lab("used") + val(f'{uses().get(alias, 0)}x'))
if st != "jump":
try:
out = subprocess.run(["ping", "-c1", "-W1", i["hostname"]], capture_output=True, text=True, timeout=2).stdout
m = re.search(r"time=([\d.]+)", out)
print(lab("ping") + (val(m.group(1) + " ms") if m else rgb(255, 92, 108) + "no reply" + X))
except Exception:
print(lab("ping") + "-")
print(f"\n{D}~/.ssh/config:{h['line']}{X}")
for l in read_lines()[h["line"] - 1:h["line"] + 12]:
if l.strip() == "" or (re.match(r"^\s*host\s", l, re.I) and not l.lower().lstrip().startswith("host " + alias.lower())):
break
print(D + l + X)
def host_index(lines, alias):
for i, l in enumerate(lines):
m = re.match(r"^\s*host\s+(.+?)\s*$", l, re.I)
if m and alias in m.group(1).split():
return i
return -1
def set_meta(alias, key, value):
lines = read_lines()
idx = host_index(lines, alias)
if idx < 0:
sys.exit(f"{alias}: not found")
j = idx
while j > 0 and lines[j - 1].lstrip().startswith("#"):
j -= 1
block = [l for l in lines[j:idx] if not re.match(rf"^\s*#\s*{key}\s*:", l, re.I)]
if value:
block.append(f"# {key}: {value}")
lines[j:idx] = block
write_lines(lines)
def cmd_add(alias, hostname, user, port, jump, group, env, identity):
if find(alias) or re.search(r"[\s*?!]", alias):
sys.exit("bad or duplicate alias")
lines = read_lines()
while lines and lines[-1].strip() == "":
lines.pop()
lines.append("")
if group: lines.append(f"# group: {group}")
if env: lines.append(f"# env: {env}")
lines.append(f"Host {alias}")
lines.append(f" HostName {hostname or alias}")
if user: lines.append(f" User {user}")
if port and port != "22": lines.append(f" Port {port}")
if jump: lines.append(f" ProxyJump {jump}")
if identity:
lines.append(f" IdentityFile {identity}")
lines.append(" IdentitiesOnly yes")
write_lines(lines)
def cmd_run(cmd, hosts):
"""Run cmd on all hosts in parallel; hosts with identical output are merged into one block."""
def one(h):
try:
p = subprocess.run(["ssh", "-T", "-o", "BatchMode=yes", "-o", "ConnectTimeout=6", h, cmd],
stdin=subprocess.DEVNULL, capture_output=True, text=True, timeout=60)
return h, p.returncode, (p.stdout + p.stderr).rstrip("\n")
except subprocess.TimeoutExpired:
return h, -1, "timeout"
except Exception as e:
return h, -1, str(e)
with ThreadPoolExecutor(16) as ex:
res = list(ex.map(one, hosts))
groups, order = {}, []
for h, rc, out in res:
k = (rc, out)
if k not in groups:
groups[k] = []
order.append(k)
groups[k].append(h)
print(f"{D}$ {cmd}{X}\n")
for rc, out in order:
hs = groups[(rc, out)]
note = "" if rc == 0 else f" {D}{'ssh error' if rc in (-1, 255) else f'exit {rc}'}{X}"
print(f'{(G if rc == 0 else R)}●{X} {rgb(235, 225, 230, bold=True)}{", ".join(hs)}{X}{note}')
for l in out.splitlines():
print(" " + l)
print()
def url_default(alias):
i, h = resolve(alias), find(alias)
port = ""
if h and h["group"].lower() == "proxmox":
port = ":8007" if alias == "pbs" else ":8006"
return f'https://{i["hostname"]}{port}'
def main():
a = sys.argv[1:]
c = a[0] if a else "list"
if c == "list": cmd_list(False)
elif c == "live": cmd_list(True)
elif c == "preview": cmd_preview(a[1])
elif c == "addr":
i = resolve(a[1]); print(addr(i), "jump" if i["proxyjump"] else "")
elif c == "env":
h = find(a[1]); print(h["env"] if h else "")
elif c == "line":
h = find(a[1]); print(h["line"] if h else 1)
elif c == "groups":
print("\n".join(sorted({h["group"] for h in parse() if h["group"]})))
elif c == "url":
h = find(a[1]); print(h["url"] if h else "")
elif c == "url-default": print(url_default(a[1]))
elif c == "run": cmd_run(a[1], a[2:])
elif c == "has": sys.exit(0 if find(a[1]) else 1)
elif c == "set": set_meta(a[1], a[2], a[3] if len(a) > 3 else "")
elif c == "toggle-prod":
h = find(a[1])
if h: set_meta(a[1], "env", "" if h["env"].lower() == "prod" else "prod")
elif c == "add": cmd_add(*a[1:9])
main()
+16
View File
@@ -0,0 +1,16 @@
# Isolated tmux server for the SSH menu's broadcast windows (tmux -L sshmenu); your ~/.tmux.conf is untouched.
set -g status off
set -g mouse on
set -g escape-time 0
set -g allow-set-title off
set -g remain-on-exit failed
set -g default-terminal "tmux-256color"
set -ga terminal-overrides ",*:Tc"
set -g pane-border-status top
set -g pane-border-lines single
set -g pane-border-style "fg=#3a2a2e"
set -g pane-active-border-style "fg=#e91e63"
set -g pane-border-format " #{pane_title}#{?pane_synchronized, #[fg=#e91e63]sync#[default],} "
# Alt+S: typing goes to all panes / only this one. Alt+Z: zoom the current pane.
bind -n M-s setw synchronize-panes
bind -n M-z resize-pane -Z