diff --git a/README.org b/README.org index 3e2d16c..8fd8254 100644 --- a/README.org +++ b/README.org @@ -17,6 +17,42 @@ I use stow to deploy all this to where it need to go (mostly ~/.confg). ** starship ** tmux ** waybar +*** WireGuard Module +The Waybar includes a custom WireGuard VPN status module that shows connection status, TX/RX stats, and peer info. + +Setup: + +1. Install openresolv (needed by wg-quick for DNS): + #+begin_src bash :tangle no + sudo pacman -S openresolv + #+end_src + +2. Configure sudo for wg-quick (required for click-to-toggle binds): + #+begin_src bash :tangle no + echo "$USER ALL=(ALL) NOPASSWD: /usr/bin/wg-quick" | sudo tee /etc/sudoers.d/wireguard + #+end_src + +3. Create a WireGuard config (if not exists): + #+begin_src bash :tangle no + sudo cp /etc/wireguard/work-laptop.conf /etc/wireguard/ + sudo chmod 600 /etc/wireguard/work-laptop.conf + #+end_src + +4. Refresh Waybar to pick up new config: + #+begin_src bash :tangle no + killall -q waybar && waybar -c ~/.config/waybar/config.jsonc & -s ~/.config/waybar/style.css & + #+end_src + +Or use the launch script: + #+begin_src bash :tangle no + ~/.config/waybar/launch.sh + #+end_src + +Keybinds: +- ~$mainMod+Ctrl+V~ — Connect WireGuard (work-laptop) +- ~$mainMod+Ctrl+Shift+V~ — Disconnect WireGuard + +The module polls /usr/bin/wg every 10 seconds. It shows a lock icon with TX/RX when connected, or an unlock icon when disconnected. ** Wireplumber ** wofi ** yazi diff --git a/scripts/wireguard-status.sh b/scripts/wireguard-status.sh new file mode 100755 index 0000000..2ee81d1 --- /dev/null +++ b/scripts/wireguard-status.sh @@ -0,0 +1,129 @@ +#!/usr/bin/env bash +# wireguard-status.sh -- JSON status for Waybar +# Outputs: {"text":"...","class":"connected|disconnected","tooltip":"..."} +# +# Waybar config example (in ~/.config/waybar/config.jsonc): +# "custom/wireguard": { +# "exec": "$HOME/.config/waybar/scripts/wireguard-status.sh", +# "interval": 10, +# "return-type": "json" +# } + +set -euo pipefail + +# --- Config ------------------------------------------------------- +ICON_LOCKED="\uf023" # nf-fa-lock (FontAwesome) +ICON_UNLOCKED="\uf09e" # nf-fa-unlock +# Override with environment variables if set: +ICON_LOCKED="${WG_ICON_LOCKED:-$ICON_LOCKED}" +ICON_UNLOCKED="${WG_ICON_UNLOCKED:-$ICON_UNLOCKED}" + +# --- Helpers ------------------------------------------------------ +die() { echo "$*" >&2; exit 1; } + +# Find the wg binary — works on NixOS, standard Linux, etc. +find_wg() { + if command -v wg &>/dev/null; then + echo "wg" + elif [ -x /run/current-system/sw/bin/wg ]; then + echo "/run/current-system/sw/bin/wg" + elif [ -x /usr/bin/wg ]; then + echo "/usr/bin/wg" + else + return 1 + fi +} + +# Find the ip binary +find_ip() { + if command -v ip &>/dev/null; then + echo "ip" + elif [ -x /run/current-system/sw/bin/ip ]; then + echo "/run/current-system/sw/bin/ip" + elif [ -x /usr/sbin/ip ]; then + echo "/usr/sbin/ip" + elif [ -x /sbin/ip ]; then + echo "/sbin/ip" + else + return 1 + fi +} + +format_bytes() { + local bytes=$1 + if [ "$bytes" -lt 1024 ]; then + echo "${bytes}B" + elif [ "$bytes" -lt $((1024 * 1024)) ]; then + echo "$(awk "BEGIN { printf \"%.1f KiB\", $bytes / 1024 }")" + elif [ "$bytes" -lt $((1024 * 1024 * 1024)) ]; then + echo "$(awk "BEGIN { printf \"%.1f MiB\", $bytes / (1024 * 1024) }")" + else + echo "$(awk "BEGIN { printf \"%.2f GiB\", $bytes / (1024 * 1024 * 1024) }")" + fi +} + +# --- Main --------------------------------------------------------- + +WG_BIN=$(find_wg 2>/dev/null) || { + # wg not installed — report as disconnected + printf '{"text":"%s","class":"disconnected","tooltip":"WireGuard not installed"}\n' "$ICON_UNLOCKED" + exit 0 +} + +IP_BIN=$(find_ip 2>/dev/null) || true + +# Find all WireGuard interfaces +INTERFACES=$("$WG_BIN" show interfaces 2>/dev/null) || { + # No WireGuard interfaces exist (wg module not loaded?) + printf '{"text":"%s","class":"disconnected","tooltip":"No WireGuard interfaces"}\n' "$ICON_UNLOCKED" + exit 0 +} + +# Parse into array, handling possible single-line space-separated output +IFS=' ' read -ra ifaces <<< "$INTERFACES" +if [ ${#ifaces[@]} -eq 0 ]; then + printf '{"text":"%s","class":"disconnected","tooltip":"No WireGuard interfaces"}\n' "$ICON_UNLOCKED" + exit 0 +fi + +# Just use the first interface (most setups have one) +IFACE="${ifaces[0]}" + +# Get transfer stats +TRANSFER=$("$WG_BIN" show "$IFACE" transfer 2>/dev/null) || { + printf '{"text":"%s","class":"disconnected","tooltip":"Cannot read stats for %s"}\n' "$ICON_UNLOCKED" "$IFACE" + exit 0 +} + +# Parse: "rx_bytes tx_bytes" +RX_BYTES=$(echo "$TRANSFER" | awk '{print $1}') +TX_BYTES=$(echo "$TRANSFER" | awk '{print $2}') + +RX_HUMAN=$(format_bytes "${RX_BYTES:-0}") +TX_HUMAN=$(format_bytes "${TX_BYTES:-0}") + +# Get endpoint info for tooltip +ENDPOINT=$("$WG_BIN" show "$IFACE" endpoints 2>/dev/null | head -1 | awk '{print $2}') || ENDPOINT="" +PEER=$("$WG_BIN" show "$IFACE" peers 2>/dev/null | head -1) || PEER="" + +# Build status line +STATUS_TEXT="${ICON_LOCKED} \u2193${RX_HUMAN} \u2191${TX_HUMAN}" + +# Build tooltip +TOOLTIP="${IFACE}" +if [ -n "$PEER" ]; then + TOOLTIP="${TOOLTIP}\nPeer: ${PEER}" +fi +if [ -n "$ENDPOINT" ]; then + TOOLTIP="${TOOLTIP}\nEndpoint: ${ENDPOINT}" +fi +# Add IP info if ip is available +if [ -n "$IP_BIN" ]; then + IFACE_IP=$("$IP_BIN" -4 addr show dev "$IFACE" 2>/dev/null | awk '/inet / {print $2}') || IFACE_IP="" + if [ -n "$IFACE_IP" ]; then + TOOLTIP="${TOOLTIP}\nIP: ${IFACE_IP}" + fi +fi +TOOLTIP="${TOOLTIP}\nRX: ${RX_HUMAN}\nTX: ${TX_HUMAN}" + +printf '{"text":"%s","class":"connected","tooltip":"%s"}\n' "$STATUS_TEXT" "$TOOLTIP" diff --git a/waybar/.config/waybar/config.jsonc b/waybar/.config/waybar/config.jsonc index 22cf676..e71f18c 100644 --- a/waybar/.config/waybar/config.jsonc +++ b/waybar/.config/waybar/config.jsonc @@ -28,6 +28,7 @@ "pulseaudio", "bluetooth", "network", + "custom/wireguard", "battery", "custom/exit" ] diff --git a/waybar/.config/waybar/modules.json b/waybar/.config/waybar/modules.json index 80d301e..1b951be 100644 --- a/waybar/.config/waybar/modules.json +++ b/waybar/.config/waybar/modules.json @@ -138,6 +138,15 @@ //"on-click": "~/dotfiles/.settings/networkmanager.sh" }, + // wireguard + "custom/wireguard": { + "exec": "$HOME/.config/waybar/scripts/wireguard-status.sh", + "interval": 10, + "return-type": "json", + "on-click": "kitty sudo wg-quick up work-laptop", + "on-click-right": "kitty sudo wg-quick down work-laptop" + }, + // battery "battery": { "states": { diff --git a/waybar/.config/waybar/style.css b/waybar/.config/waybar/style.css index a916161..08b46de 100644 --- a/waybar/.config/waybar/style.css +++ b/waybar/.config/waybar/style.css @@ -213,6 +213,21 @@ tooltip label { } /* custom modules */ +#custom-wireguard { + margin: 0px 0px 0px 5px; + padding: 1px 8px 0px 8px; + font-size: 16px; + color: @text; + border-radius: 5px; + background-color: @set; +} +#custom-wireguard.connected { + background-color: @color5; +} +#custom-wireguard.disconnected { + background-color: @set; +} + #custom-exit { margin: 0px 18px 0px 5px; padding: 0px;