mirror of
https://github.com/neoromantique/dotfiles.git
synced 2026-03-14 06:02:54 +03:00
102 lines
4.0 KiB
Bash
102 lines
4.0 KiB
Bash
#!/usr/bin/env bash
|
|
# VPN status for waybar - outputs JSON
|
|
|
|
get_status() {
|
|
local wg_status=""
|
|
local nb_status=""
|
|
local ts_status=""
|
|
local text="VPN off"
|
|
local tooltip="No VPN active"
|
|
local class="disconnected"
|
|
local has_tunnel=false # true if WG, NetBird, or TS with exit node
|
|
|
|
# Check NetBird
|
|
if command -v netbird &>/dev/null; then
|
|
nb_json=$(netbird status --json 2>/dev/null)
|
|
if [[ -n "$nb_json" ]]; then
|
|
nb_mgmt=$(echo "$nb_json" | jq -r '.management.connected // false')
|
|
if [[ "$nb_mgmt" == "true" ]]; then
|
|
nb_ip=$(echo "$nb_json" | jq -r '.netbirdIp // empty' | cut -d'/' -f1)
|
|
nb_fqdn=$(echo "$nb_json" | jq -r '.fqdn // empty')
|
|
nb_connected=$(echo "$nb_json" | jq -r '.peers.connected // 0')
|
|
nb_total=$(echo "$nb_json" | jq -r '.peers.total // 0')
|
|
|
|
# Extract network name from fqdn (e.g., box-128-45.lumia.overlay -> lumia)
|
|
nb_net=$(echo "$nb_fqdn" | awk -F'.' '{print $(NF-1)}')
|
|
|
|
nb_status="NB:${nb_net}(${nb_connected}/${nb_total})"
|
|
tooltip="NetBird: ${nb_fqdn}\nIP: ${nb_ip}\nPeers: ${nb_connected}/${nb_total}"
|
|
# NetBird mesh connection alone doesn't route all traffic like a VPN
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Check WireGuard interfaces (excluding NetBird's wt0)
|
|
wg_ifaces=$(ip link show type wireguard 2>/dev/null | grep -oP '^\d+: \K[^:]+' | grep -v '^wt')
|
|
if [[ -n "$wg_ifaces" ]]; then
|
|
wg_name=$(echo "$wg_ifaces" | head -1)
|
|
wg_status="WG:$wg_name"
|
|
[[ -z "$tooltip" || "$tooltip" == "No VPN active" ]] && tooltip=""
|
|
[[ -n "$tooltip" ]] && tooltip+="\n"
|
|
tooltip+="WireGuard: $wg_name"
|
|
has_tunnel=true
|
|
fi
|
|
|
|
# Check Tailscale
|
|
if command -v tailscale &>/dev/null && tailscale status &>/dev/null; then
|
|
ts_json=$(tailscale status --json 2>/dev/null)
|
|
if [[ -n "$ts_json" ]]; then
|
|
ts_state=$(echo "$ts_json" | jq -r '.BackendState // empty')
|
|
|
|
if [[ "$ts_state" == "Running" ]]; then
|
|
# Get tailnet name (domain)
|
|
tailnet=$(echo "$ts_json" | jq -r '.CurrentTailnet.Name // empty')
|
|
tailnet_short="${tailnet%.ts.net}"
|
|
tailnet_short="${tailnet_short%.tail*}"
|
|
|
|
# Check for exit node
|
|
exit_node_id=$(echo "$ts_json" | jq -r '.ExitNodeStatus.ID // empty')
|
|
exit_online=$(echo "$ts_json" | jq -r '.ExitNodeStatus.Online // false')
|
|
|
|
if [[ -n "$exit_node_id" && "$exit_online" == "true" ]]; then
|
|
exit_host=$(echo "$ts_json" | jq -r '
|
|
.Peer | to_entries[] | select(.value.ExitNode == true) | .value.HostName // "exit"
|
|
' 2>/dev/null)
|
|
[[ -z "$exit_host" || "$exit_host" == "null" ]] && exit_host="exit"
|
|
|
|
ts_status="TS:${tailnet_short}→${exit_host}"
|
|
[[ -n "$tooltip" ]] && tooltip+="\n"
|
|
tooltip+="Tailscale: ${tailnet}\nExit: ${exit_host}"
|
|
has_tunnel=true
|
|
else
|
|
ts_status="TS:${tailnet_short}"
|
|
[[ -n "$tooltip" ]] && tooltip+="\n"
|
|
tooltip+="Tailscale: ${tailnet} (no exit)"
|
|
# No exit node = no tunnel, don't set has_tunnel
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Build final output
|
|
local parts=()
|
|
[[ -n "$wg_status" ]] && parts+=("$wg_status")
|
|
[[ -n "$nb_status" ]] && parts+=("$nb_status")
|
|
[[ -n "$ts_status" ]] && parts+=("$ts_status")
|
|
|
|
if [[ ${#parts[@]} -gt 0 ]]; then
|
|
text=$(IFS=' | '; echo "${parts[*]}")
|
|
fi
|
|
|
|
# Only green if actual tunnel (WG, NetBird, or TS+exit)
|
|
if [[ "$has_tunnel" == true ]]; then
|
|
class="connected"
|
|
elif [[ ${#parts[@]} -gt 0 ]]; then
|
|
class="connected-no-tunnel"
|
|
fi
|
|
|
|
printf '{"text": "%s", "tooltip": "%s", "class": "%s"}\n' "$text" "$tooltip" "$class"
|
|
}
|
|
|
|
get_status
|