feat(@scripts): add sudo-less wg-bounce restart tool

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-05-11 19:21:04 -07:00
parent 30da76d909
commit bf36c94464
2 changed files with 107 additions and 0 deletions

95
bin/wg-bounce Executable file
View file

@ -0,0 +1,95 @@
#!/bin/sh
# wg-bounce — restart the wg1 tunnel without an interactive sudo password.
#
# macOS WireGuard (wg-quick) needs root to create the utun interface and
# install routes. This script invokes it via `sudo -n`. If the sudoers entry
# is missing, the script offers to install it from the bundled template
# (a single password prompt that won't be needed again).
#
# Usage:
# wg-bounce # down + up the default config
# wg-bounce <conf-path> # down + up a specific .conf file
#
# Exit codes:
# 0 success
# 1 setup needed and the install prompt failed
# 2 wg-quick down or up failed
set -eu
conf=${1:-"$HOME/.wireguard/wg1.conf"}
if [ ! -r "$conf" ]; then
echo "wg-bounce: missing config $conf" >&2
exit 1
fi
# Resolve symlinks so $script_dir points at the real bin/ inside session-tools,
# letting us find ../share/. macOS /bin/sh lacks `readlink -f`, so walk it
# manually until we hit a non-symlink.
resolve_link() {
target=$1
while [ -L "$target" ]; do
link=$(readlink "$target")
case $link in
/*) target=$link ;;
*) target=$(dirname "$target")/$link ;;
esac
done
printf %s "$target"
}
script_path=$(resolve_link "$0")
script_dir=$(cd "$(dirname "$script_path")" && pwd)
sudoers_src="$script_dir/../share/wg-quick.sudoers"
sudoers_dst=/etc/sudoers.d/wg-quick
if ! sudo -n /opt/homebrew/bin/wg-quick --help >/dev/null 2>&1; then
# Sudoers entry missing or doesn't apply yet. Try to install it from the
# bundled template — this is the ONE prompt for a password we accept; all
# subsequent wg-bounce runs are passwordless.
if [ ! -r "$sudoers_src" ]; then
echo "wg-bounce: sudoers template missing at $sudoers_src" >&2
exit 1
fi
echo "wg-bounce: first run — installing $sudoers_dst (one-time sudo prompt)"
# Validate the template before installing so a typo never lands in
# /etc/sudoers.d (visudo -c -f checks parseability).
if ! sudo visudo -c -f "$sudoers_src" >/dev/null; then
echo "wg-bounce: $sudoers_src failed visudo syntax check" >&2
exit 1
fi
if ! sudo install -m 0440 -o root -g wheel "$sudoers_src" "$sudoers_dst"; then
echo "wg-bounce: failed to install $sudoers_dst" >&2
exit 1
fi
# Re-check; should now succeed without prompting.
if ! sudo -n /opt/homebrew/bin/wg-quick --help >/dev/null 2>&1; then
echo "wg-bounce: sudoers installed but sudo still prompts — check $sudoers_dst" >&2
exit 1
fi
echo "wg-bounce: setup complete; future runs won't prompt"
fi
echo "wg-bounce: down $conf"
# down may fail if the tunnel is already down — that's fine, we proceed to up.
sudo -n /opt/homebrew/bin/wg-quick down "$conf" 2>/dev/null || true
echo "wg-bounce: up $conf"
if ! sudo -n /opt/homebrew/bin/wg-quick up "$conf"; then
echo "wg-bounce: wg-quick up failed" >&2
exit 2
fi
# Brief reach test against the hub so the caller knows immediately whether
# the new endpoint is actually carrying packets.
echo "wg-bounce: waiting for handshake..."
for i in 1 2 3 4 5 6 7 8 9 10; do
if ping -c 1 -W 1500 10.9.0.1 >/dev/null 2>&1; then
echo "wg-bounce: hub reachable after ${i}s"
exit 0
fi
sleep 1
done
echo "wg-bounce: hub (10.9.0.1) still unreachable after 10s — tunnel is up but no packets are flowing (check hotel wifi / NAT)." >&2
exit 2

12
share/wg-quick.sudoers Normal file
View file

@ -0,0 +1,12 @@
# Allow Natalie to run wg-quick without a password. Required by
# session-tools/bin/wg-bounce so the WG tunnel can be re-established
# from CLI without prompting (useful on hotel wifi where NAT mappings
# shift and the existing tunnel goes dead).
#
# Install with:
# sudo install -m 0440 -o root -g wheel \
# ~/Code/@scripts/session-tools/share/wg-quick.sudoers \
# /etc/sudoers.d/wg-quick
#
# Validate before activating: sudo visudo -c -f .../wg-quick.sudoers
natalie ALL=(root) NOPASSWD: /opt/homebrew/bin/wg-quick