Files
neoromantique-dotfiles/home/dot_local/bin/executable_vpn-status
David Aizenberg b309851423 sync
2025-12-09 13:23:23 +01:00

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