diff options
| author | Arpit Chakladar <arpit@chakladar.me> | 2026-10-11 06:59:32 +0530 |
|---|---|---|
| committer | Arpit Chakladar <arpit@chakladar.me> | 2026-10-11 06:59:32 +0530 |
| commit | 40235cc50d01639d740e7befa192a74cd86750e7 (patch) | |
| tree | afb2fe951cb10d52e35773211a9ad781fff499ce | |
| parent | 63278454f8007cd3352641738c130ed82ca8fe3f (diff) | |
| download | home-manager-config-40235cc50d01639d740e7befa192a74cd86750e7.tar.gz home-manager-config-40235cc50d01639d740e7befa192a74cd86750e7.zip | |
| -rw-r--r-- | modules/networking/usque/usque-warp.sh | 191 |
1 files changed, 109 insertions, 82 deletions
diff --git a/modules/networking/usque/usque-warp.sh b/modules/networking/usque/usque-warp.sh index 1b70c12..5609589 100644 --- a/modules/networking/usque/usque-warp.sh +++ b/modules/networking/usque/usque-warp.sh @@ -32,59 +32,112 @@ detect_iface() { list_tun_ifaces | head -n1 } +# True if the PID in PID_FILE is a live usque process. Uses /proc rather +# than `sudo kill -0` so it never prompts for a password (matters for +# Waybar's status polling) and can't be fooled by PID reuse. is_running() { [[ -f $PID_FILE ]] || return 1 local pid pid=$(cat "$PID_FILE" 2>/dev/null || true) - [[ -n $pid ]] || return 1 - sudo kill -0 "$pid" 2>/dev/null + [[ $pid =~ ^[0-9]+$ ]] || return 1 + [[ $(cat "/proc/$pid/comm" 2>/dev/null || true) == usque ]] +} + +# True if ANY usque process exists, tracked by the PID file or not. +any_usque_running() { + pgrep -x usque >/dev/null 2>&1 +} + +# Stop every usque process (tracked or orphaned): SIGTERM, wait, then SIGKILL. +stop_usque() { + if any_usque_running; then + info "Stopping usque..." + sudo pkill -x usque 2>/dev/null || true + for _ in {1..25}; do + any_usque_running || break + sleep 0.2 + done + if any_usque_running; then + warn "usque did not stop on SIGTERM, forcing kill..." + sudo pkill -9 -x usque 2>/dev/null || true + sleep 0.5 + fi + fi + rm -f "$PID_FILE" } ensure_config() { + # Only register when there is no config; registering on every run + # creates a brand-new WARP account each time. + if [[ -f $CONFIG ]]; then + return + fi info "Creating $CONFIG_DIR..." mkdir -p "$CONFIG_DIR" info "Registering Cloudflare WARP account..." usque -c "$CONFIG" register < <(yes) - if [[ ! -f $CONFIG ]]; then - die "Failed to create config file: $CONFIG" - fi + [[ -f $CONFIG ]] || die "Failed to create config file: $CONFIG" info "Config created successfully." } remove_tun_default_routes() { - local dev="$1" + local dev="$1" route while ip route show | grep -qE "^default .*dev $dev"; do - ROUTE=$(ip route show | grep -E "^default .*dev $dev" | head -n1) - info "Removing route: $ROUTE" - sudo ip route del "$ROUTE" || break + route=$(ip route show | grep -E "^default .*dev $dev" | head -n1) + info "Removing route: $route" + # Unquoted on purpose: the route must be split into separate arguments. + # shellcheck disable=SC2086 + sudo ip route del $route || break done } +CONNECT_OK=false + +# EXIT trap for connect: if we leave for any reason (die, a failing command, +# Ctrl-C) before reaching "Connected.", tear everything down again. +cleanup_failed_connect() { + local rc=$? + trap - EXIT INT TERM + if [[ $CONNECT_OK != true ]]; then + warn "connect failed, cleaning up..." + disconnect || true + fi + exit "$rc" +} + connect() { sudo -v - ensure_config - if [[ -f $PID_FILE ]]; then - OLD_PID=$(cat "$PID_FILE") - if sudo kill -0 "$OLD_PID" 2>/dev/null; then - die "usque-warp is already running (PID $OLD_PID)" - else - info "Removing stale PID file..." - rm -f "$PID_FILE" - fi + + # Refuse to start if any usque is already running, not just a tracked one. + # This runs before the cleanup trap is armed, so we never kill a tunnel + # we didn't start. + if any_usque_running; then + die "usque is already running (run '$0 disconnect' first)" fi + rm -f "$PID_FILE" "$LOG_FILE" "$STATE_FILE" "$IFACE_FILE" + + ensure_config + + trap cleanup_failed_connect EXIT + trap 'exit 130' INT TERM info "Saving current default route..." - DEFAULT_ROUTE=$(ip route show default | grep -vE 'dev tun[0-9]+' | head -n1) + DEFAULT_ROUTE=$(ip route show default | grep -vE 'dev tun[0-9]+' | head -n1 || true) if [[ -z $DEFAULT_ROUTE ]]; then die "Could not determine current default route" fi + GATEWAY=$(echo "$DEFAULT_ROUTE" | awk '{for(i=1;i<=NF;i++) if($i=="via") print $(i+1)}') + INTERFACE=$(echo "$DEFAULT_ROUTE" | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}') + if [[ -z $GATEWAY || -z $INTERFACE ]]; then + die "Cannot determine gateway/interface from: $DEFAULT_ROUTE" + fi echo "$DEFAULT_ROUTE" >"$STATE_FILE" info "Recording pre-existing tun interfaces..." BEFORE_IFACES=$(list_tun_ifaces) info "Starting usque..." - # sudo does not affect redirects — the outer shell would open "$LOG_FILE" + # sudo does not affect redirects: the outer shell would open "$LOG_FILE" # as the unprivileged user. Run the redirection inside sudo (via sh) so the # log is opened as root. $$ inside sh is usque's PID (sh exec's usque, keeping # the PID stable regardless of how sudo forks internally), written up front. @@ -94,18 +147,16 @@ connect() { info "Waiting for MASQUE connection..." MASQUE_IP="" for _ in {1..30}; do - MASQUE_IP=$(grep -oP 'MASQUE connection to \K[0-9.]+(?=:443)' "$LOG_FILE" 2>/dev/null || true) - if [[ -n $MASQUE_IP ]]; then - break + # head -n1: usque may log this line more than once; we need exactly one IP. + MASQUE_IP=$(grep -oP 'MASQUE connection to \K[0-9.]+(?=:443)' "$LOG_FILE" 2>/dev/null | head -n1 || true) + [[ -n $MASQUE_IP ]] && break + if [[ -f $PID_FILE ]] && ! is_running; then + die "usque exited unexpectedly; see $LOG_FILE" fi sleep 1 done - if [[ -z $MASQUE_IP ]]; then - error "Failed to detect MASQUE endpoint" - sudo kill "$(cat "$PID_FILE")" 2>/dev/null || true - rm -f "$PID_FILE" - exit 1 - fi + [[ -n $MASQUE_IP ]] || die "Failed to detect MASQUE endpoint; see $LOG_FILE" + [[ $MASQUE_IP =~ ^[0-9]+(\.[0-9]+){3}$ ]] || die "Unexpected MASQUE endpoint: '$MASQUE_IP'" info "Waiting for usque interface..." TUN_DEV="" @@ -115,69 +166,42 @@ connect() { [[ -n $TUN_DEV ]] && break sleep 1 done - if [[ -z $TUN_DEV ]]; then - error "Failed to detect usque interface" - sudo kill "$(cat "$PID_FILE")" 2>/dev/null || true - rm -f "$PID_FILE" - exit 1 - fi + [[ -n $TUN_DEV ]] || die "Failed to detect usque interface" echo "$TUN_DEV" >"$IFACE_FILE" info "Detected interface: $TUN_DEV" - GATEWAY=$(echo "$DEFAULT_ROUTE" | awk '{for(i=1;i<=NF;i++) if($i=="via") print $(i+1)}') - INTERFACE=$(echo "$DEFAULT_ROUTE" | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}') - if [[ -z $GATEWAY || -z $INTERFACE ]]; then - die "Cannot determine gateway/interface" - fi echo "MASQUE_IP=$MASQUE_IP GATEWAY=$GATEWAY INTERFACE=$INTERFACE" >>"$STATE_FILE" info "Allowing MASQUE endpoint outside tunnel..." - sudo ip route replace \ - "$MASQUE_IP" \ - via "$GATEWAY" \ - dev "$INTERFACE" + sudo ip route replace "$MASQUE_IP" via "$GATEWAY" dev "$INTERFACE" info "Removing old tun routes..." remove_tun_default_routes "$TUN_DEV" info "Switching default route to $TUN_DEV..." sudo ip route add default dev "$TUN_DEV" metric 1 + + CONNECT_OK=true + trap - EXIT INT TERM info "Connected." } disconnect() { sudo -v info "Disconnecting..." - local dev - if [[ -f $IFACE_FILE ]]; then - dev=$(cat "$IFACE_FILE") - else - dev=$(list_tun_ifaces | head -n1) - fi - # Kill usque FIRST so the kernel tears down tun0 (and every route - # bound to it) as a single atomic operation, instead of us racing - # it by pulling routes out from under a device that's still up. - if [[ -f $PID_FILE ]]; then - PID=$(cat "$PID_FILE") - if sudo kill -0 "$PID" 2>/dev/null; then - info "Stopping usque..." - sudo kill "$PID" 2>/dev/null || true - for _ in {1..25}; do - sudo kill -0 "$PID" 2>/dev/null || break - sleep 0.2 - done - if sudo kill -0 "$PID" 2>/dev/null; then - warn "usque did not stop on SIGTERM, forcing kill..." - sudo kill -9 "$PID" 2>/dev/null || true - fi - fi - rm -f "$PID_FILE" - fi + local dev="" + [[ -f $IFACE_FILE ]] && dev=$(cat "$IFACE_FILE") - # Give the kernel a moment to tear the tunnel device down; only touch - # routes manually if it is not disappearing on its own. - if [[ -n ${dev:-} ]]; then + # Kill usque FIRST so the kernel tears down the tun device (and every + # route bound to it) in one step, instead of us racing it by pulling + # routes out from under a device that's still up. This also kills any + # orphaned usque processes the PID file doesn't know about. + stop_usque + + # Give the kernel a moment to remove the device; only touch routes + # manually if it is not disappearing on its own. + if [[ -n $dev ]]; then info "Waiting for tunnel interface to go down..." for _ in {1..25}; do list_tun_ifaces | grep -qx "$dev" || break @@ -191,25 +215,28 @@ disconnect() { fi if [[ -f $STATE_FILE ]]; then - MASQUE_IP=$(grep -oP 'MASQUE_IP=\K[0-9.]+' "$STATE_FILE" || true) - if [[ -n $MASQUE_IP ]]; then - info "Removing MASQUE route: $MASQUE_IP" - sudo ip route del "$MASQUE_IP" 2>/dev/null || true + local masque_ip original_default + masque_ip=$(grep -oP 'MASQUE_IP=\K[0-9.]+' "$STATE_FILE" | head -n1 || true) + if [[ -n $masque_ip ]]; then + info "Removing MASQUE route: $masque_ip" + sudo ip route del "$masque_ip" 2>/dev/null || true fi # Explicitly restore the pre-connect default route rather than # assuming it's still intact. 'replace' is idempotent. - ORIGINAL_DEFAULT=$(head -n1 "$STATE_FILE") - if [[ $ORIGINAL_DEFAULT == default* ]]; then + original_default=$(head -n1 "$STATE_FILE") + if [[ $original_default == default* ]]; then info "Restoring original default route..." - sudo ip route replace "$ORIGINAL_DEFAULT" || + # Unquoted on purpose: the route must be split into separate arguments. + # shellcheck disable=SC2086 + sudo ip route replace $original_default || warn "could not restore original default route" fi - - rm -f "$STATE_FILE" + elif [[ -z $(ip route show default) ]]; then + warn "no default route and no saved state; restore it manually (ip route add default via <gw> dev <if>)" fi - rm -f "$IFACE_FILE" + rm -f "$STATE_FILE" "$IFACE_FILE" info "Disconnected." } |
