Bash · 2993 bytes Raw Blame History
1
2
3 #!/usr/bin/env zsh
4 #
5 # ssh‑quick‑connect.sh
6 # A SketchyBar plugin that shows the number of online Tailnet peers and,
7 # on click, pops up a list of online hosts that you can SSH into.
8 #
9 # Requirements:
10 # * Tailscale CLI (`tailscale`) and `jq`
11 # * iTerm2 or Terminal for the click‑launch (adjust OPEN_CMD below)
12 #
13 # Add to sketchybarrc:
14 # sketchybar --add item ssh_menu right \
15 # --set ssh_menu update_freq=60 \
16 # script="$PLUGDIR/ssh-quick-connect.sh" \
17 # icon=$'\uf489' \
18 # --subscribe ssh_menu mouse.clicked
19 #
20 ########################################################################
21
22 ITEM_NAME="${NAME:-ssh_menu}" # When run by SketchyBar, $NAME is set.
23 SENDER="${SENDER:-routine}" # "routine" when run via update_freq
24
25 # Resolve Tailscale CLI path explicitly because SketchyBar's PATH may be minimal
26 if command -v tailscale &>/dev/null; then
27 TS_BIN="$(command -v tailscale)"
28 elif [[ -x "/Applications/Tailscale.app/Contents/MacOS/Tailscale" ]]; then
29 TS_BIN="/Applications/Tailscale.app/Contents/MacOS/Tailscale"
30 else
31 TS_BIN=""
32 fi
33
34 OPEN_CMD='osascript -e "tell application \"iTerm2\" to create window with default profile command \"%s\""'
35
36 # ---------- Helpers --------------------------------------------------
37
38 json_status() {
39 [[ -z "$TS_BIN" ]] && return
40 "$TS_BIN" status --json 2>/dev/null
41 }
42
43 online_hosts() {
44 jq -r '.Peer[] | select(.Online==true) | .HostName' <<<"$1"
45 }
46
47 # ---------- Update routine (every 60 s) ------------------------------
48
49 if [[ "$SENDER" == "routine" ]]; then
50 TS_JSON=$(json_status)
51 if [[ -z "$TS_JSON" ]]; then
52 sketchybar --set "$ITEM_NAME" icon="󰼀" label="TS Down"
53 exit 0
54 fi
55
56 ONLINE=$(jq '[.Peer[] | select(.Online==true)] | length' <<<"$TS_JSON")
57 TOTAL=$(jq '.Peer | length' <<<"$TS_JSON")
58
59 ICON="󰼂" # default t/s icon
60 LABEL="$ONLINE/$TOTAL"
61
62 sketchybar --set "$ITEM_NAME" icon="$ICON" label="$LABEL"
63 exit 0
64 fi
65
66 # ---------- Click handler -------------------------------------------
67
68 if [[ "$SENDER" == "mouse.clicked" ]]; then
69 # ----- 1. nuke children using sketchybar --query ----------------
70 CHILDREN=($(sketchybar --query "$ITEM_NAME" | jq -r '.popup.items[]?'))
71 for C in "${CHILDREN[@]}"; do
72 sketchybar --remove "$C"
73 done
74
75 # ----- 2. rebuild list ----------------
76 TS_JSON=$(json_status)
77 HOSTS=($(online_hosts "$TS_JSON"))
78
79 if (( ${#HOSTS} == 0 )); then
80 sketchybar --add item ssh_nopeers popup."$ITEM_NAME" \
81 --set ssh_nopeers label="No peers online" icon=""
82 else
83 INDEX=0
84 for HOST in "${HOSTS[@]}"; do
85 POP="ssh_pop_$INDEX"
86 sketchybar --add item "$POP" popup."$ITEM_NAME" \
87 --set "$POP" label="$HOST" icon="" \
88 click_script="open -a iTerm \"ssh $HOST\""
89 (( INDEX++ ))
90 done
91 fi
92
93 # ----- 3. show popup (always flips state) ----------------
94 sketchybar --toggle "$ITEM_NAME" popup
95 exit 0
96 fi