| 1 | #!/usr/bin/env bash |
| 2 | set -euo pipefail |
| 3 | |
| 4 | # Get tailscale status |
| 5 | status_output=$(tailscale status 2>&1) || { |
| 6 | echo '{"text":"TS: down","tooltip":"Tailscale not running","class":"disconnected"}' |
| 7 | exit 0 |
| 8 | } |
| 9 | |
| 10 | # Check if we're connected (if status command worked, we have output) |
| 11 | if [[ -z "$status_output" ]] || echo "$status_output" | grep -qi "stopped\|not running"; then |
| 12 | echo '{"text":"TS: off","tooltip":"Tailscale is not running","class":"disconnected"}' |
| 13 | exit 0 |
| 14 | fi |
| 15 | |
| 16 | # Get current hostname |
| 17 | my_hostname=$(tailscale status --self --json 2>/dev/null | jq -r '.Self.HostName // empty' 2>/dev/null) || my_hostname="" |
| 18 | |
| 19 | # Count online devices (lines that don't contain "offline") |
| 20 | online_count=$(echo "$status_output" | grep -v "^#" | grep -v "offline" | grep -c "^[0-9]" 2>/dev/null) || online_count=0 |
| 21 | total_count=$(echo "$status_output" | grep -v "^#" | grep -c "^[0-9]" 2>/dev/null) || total_count=0 |
| 22 | |
| 23 | # Build tooltip with device list |
| 24 | tooltip="Tailnet Devices ($online_count/$total_count online)" |
| 25 | tooltip+=$'\n'"─────────────────────────────" |
| 26 | |
| 27 | while IFS= read -r line; do |
| 28 | # Skip comments and empty lines |
| 29 | [[ "$line" =~ ^# ]] && continue |
| 30 | [[ -z "$line" ]] && continue |
| 31 | |
| 32 | # Parse: IP hostname user OS status... |
| 33 | ip=$(echo "$line" | awk '{print $1}') |
| 34 | hostname=$(echo "$line" | awk '{print $2}') |
| 35 | os=$(echo "$line" | awk '{print $4}') |
| 36 | |
| 37 | # Skip if not a valid IP line |
| 38 | [[ ! "$ip" =~ ^100\. ]] && continue |
| 39 | |
| 40 | # Check if online or offline |
| 41 | if echo "$line" | grep -q "offline"; then |
| 42 | status_icon="○" |
| 43 | # Extract last seen info (already includes "ago") |
| 44 | last_seen=$(echo "$line" | grep -oP 'last seen \K[^,]+' | sed 's/ *$//' || echo "") |
| 45 | device_info="$status_icon $hostname ($os) - offline" |
| 46 | [[ -n "$last_seen" ]] && device_info="$status_icon $hostname ($os) - $last_seen" |
| 47 | else |
| 48 | status_icon="●" |
| 49 | device_info="$status_icon $hostname ($os) - $ip" |
| 50 | fi |
| 51 | |
| 52 | # Mark current device |
| 53 | if [[ "$hostname" == "$my_hostname" ]]; then |
| 54 | device_info="$device_info [this device]" |
| 55 | fi |
| 56 | |
| 57 | tooltip+=$'\n'"$device_info" |
| 58 | done <<< "$status_output" |
| 59 | |
| 60 | # Determine class based on status |
| 61 | if [[ "$online_count" -gt 0 ]]; then |
| 62 | class="connected" |
| 63 | else |
| 64 | class="partial" |
| 65 | fi |
| 66 | |
| 67 | # Output JSON |
| 68 | jq -cn --arg text "TS: $online_count" --arg tooltip "$tooltip" --arg class "$class" \ |
| 69 | '{text:$text, tooltip:$tooltip, class:$class}' |