diff --git a/files/etc/sysupgrade.conf b/files/etc/sysupgrade.conf index 3f0d904..1c0eb75 100644 --- a/files/etc/sysupgrade.conf +++ b/files/etc/sysupgrade.conf @@ -1,4 +1,6 @@ /etc/parahub/keys /etc/parahub/role +/etc/parahub/wg_vps_private.key +/etc/parahub/wg_vps_public.key /etc/yggdrasil.conf /etc/dropbear/ diff --git a/files/etc/uci-defaults/99-parahub-mesh b/files/etc/uci-defaults/99-parahub-mesh index b22a653..6ea268b 100755 --- a/files/etc/uci-defaults/99-parahub-mesh +++ b/files/etc/uci-defaults/99-parahub-mesh @@ -81,6 +81,15 @@ echo -e "${ROOT_PASSWORD}\n${ROOT_PASSWORD}" | passwd root >/dev/null 2>&1 # Ensure dropbear directory has strict permissions (required for key auth) chmod 700 /etc/dropbear +# Generate WireGuard keypair for VPS gateway (Bumblebee only) +if [ "$ROLE" != "bee" ]; then + if [ ! -f /etc/parahub/wg_vps_private.key ]; then + umask 077 + wg genkey > /etc/parahub/wg_vps_private.key + wg pubkey < /etc/parahub/wg_vps_private.key > /etc/parahub/wg_vps_public.key + fi +fi + # ============================================================================ # 4. NETWORK CONFIGURATION # ============================================================================ @@ -229,7 +238,15 @@ set network.wan=interface set network.wan.device='${WAN_DEV}' set network.wan.proto='dhcp' -# --- Policy routing: guest traffic → Mullvad table 100 --- +# --- VPS gateway WireGuard (disabled until heartbeat activates it) --- +set network.vps_gateway=interface +set network.vps_gateway.proto='wireguard' +set network.vps_gateway.private_key_file='/etc/parahub/wg_vps_private.key' +set network.vps_gateway.mtu='1420' +set network.vps_gateway.ip4table='100' +set network.vps_gateway.auto='0' + +# --- Policy routing: guest traffic → table 100 --- add network rule set network.@rule[-1].src='${GUEST_SUBNET}/24' set network.@rule[-1].lookup='100' @@ -476,9 +493,20 @@ add firewall forwarding set firewall.@forwarding[-1].src='lan' set firewall.@forwarding[-1].dest='wan' -# NOTE: guest → mullvad_local forwarding is added by parahub-mullvad setup. -# Without Mullvad configured, guests have NO internet (kill switch by design). -# parahub-gw-check monitors Mullvad and sets gw_mode accordingly. +# --- Zone: vps_gateway (default VPN exit for guests, activated by heartbeat) --- +add firewall zone +set firewall.@zone[-1].name='vps_gateway' +set firewall.@zone[-1].input='REJECT' +set firewall.@zone[-1].output='ACCEPT' +set firewall.@zone[-1].forward='REJECT' +set firewall.@zone[-1].masq='1' +set firewall.@zone[-1].mtu_fix='1' +add_list firewall.@zone[-1].network='vps_gateway' + +# --- Forwarding: guest → vps_gateway (active once heartbeat enables interface) --- +add firewall forwarding +set firewall.@forwarding[-1].src='guest' +set firewall.@forwarding[-1].dest='vps_gateway' # --- Rule: guest DHCP (allow guests to get IP) --- add firewall rule @@ -773,6 +801,7 @@ fi chmod +x /usr/bin/parahub-heartbeat chmod +x /usr/bin/parahub-autoupdate chmod +x /usr/bin/parahub-gw-check +chmod +x /usr/bin/parahub-vps-setup # Cron: heartbeat every 5 min, gateway health check every 2 min, OTA nightly echo "*/5 * * * * /usr/bin/parahub-heartbeat" >> /etc/crontabs/root @@ -797,10 +826,10 @@ else logger -t parahub-mesh "Guest: ${PUBLIC_SSID} @ ${GUEST_IP}/24" logger -t parahub-mesh "Mesh ID: ${MESH_ID}" logger -t parahub-mesh "Yggdrasil: ${YGG_ADDR}" - logger -t parahub-mesh "Guest internet: via local Mullvad (parahub-mullvad setup required)" - logger -t parahub-mesh "Kill switch: guest blocked without Mullvad (by design)" + logger -t parahub-mesh "Guest internet: VPS gateway (auto-configured via heartbeat)" + logger -t parahub-mesh "Optional: parahub-mullvad setup for lower latency" logger -t parahub-mesh "Guest IPv6: Yggdrasil SLAAC (full speed, firewall restricted)" - logger -t parahub-mesh "bat0 gw_mode: client (promoted to server by gw-check when Mullvad active)" + logger -t parahub-mesh "bat0 gw_mode: client (promoted to server by gw-check when WireGuard active)" fi exit 0 diff --git a/files/usr/bin/parahub-gw-check b/files/usr/bin/parahub-gw-check index 43b7010..fe7ea3f 100644 --- a/files/usr/bin/parahub-gw-check +++ b/files/usr/bin/parahub-gw-check @@ -1,15 +1,15 @@ #!/bin/sh # Parahub Gateway Health Check -# Monitors Mullvad WireGuard status and switches batman-adv gw_mode: -# - Mullvad active (recent handshake) → gw_mode=server (advertise as gateway) -# - Mullvad down or not configured → gw_mode=client (relay only) +# Monitors WireGuard status (VPS gateway or Mullvad) and switches batman-adv gw_mode: +# - WireGuard active (recent handshake) → gw_mode=server (advertise as gateway) +# - WireGuard down or not configured → gw_mode=client (relay only) # # Run via cron every 2 minutes. Bumblebee only. [ "$(cat /etc/parahub/role 2>/dev/null)" = "bumblebee" ] || exit 0 -# Check if Mullvad WireGuard interface exists and has recent handshake -MULLVAD_OK=0 +# Check if any WireGuard interface has a recent handshake +WG_OK=0 if command -v wg >/dev/null 2>&1; then # Get latest handshake timestamp from any WireGuard interface LAST_HS=$(wg show all latest-handshakes 2>/dev/null | awk '{print $NF}' | sort -rn | head -1) @@ -17,20 +17,20 @@ if command -v wg >/dev/null 2>&1; then NOW=$(date +%s) AGE=$((NOW - LAST_HS)) # Handshake within last 5 minutes = alive - [ "$AGE" -lt 300 ] && MULLVAD_OK=1 + [ "$AGE" -lt 300 ] && WG_OK=1 fi fi CURRENT_MODE=$(batctl gw_mode 2>/dev/null | awk '{print $1}') -if [ "$MULLVAD_OK" = "1" ]; then +if [ "$WG_OK" = "1" ]; then if [ "$CURRENT_MODE" != "server" ]; then batctl gw_mode server - logger -t parahub-gw "Mullvad active, switched to gw_mode=server" + logger -t parahub-gw "WireGuard active, switched to gw_mode=server" fi else if [ "$CURRENT_MODE" != "client" ]; then batctl gw_mode client - logger -t parahub-gw "Mullvad down, switched to gw_mode=client" + logger -t parahub-gw "WireGuard down, switched to gw_mode=client" fi fi diff --git a/files/usr/bin/parahub-heartbeat b/files/usr/bin/parahub-heartbeat index e6b647f..c5641c0 100755 --- a/files/usr/bin/parahub-heartbeat +++ b/files/usr/bin/parahub-heartbeat @@ -19,6 +19,12 @@ UPTIME="$(cut -d. -f1 /proc/uptime)" MESH_IP=$(ip -4 addr show br-private 2>/dev/null | grep -o 'inet [0-9.]*' | cut -d' ' -f2) MESH_IP="${MESH_IP:-unknown}" +# Read WireGuard VPS public key (Bumblebee only) +WG_PUBKEY="" +if [ -f /etc/parahub/wg_vps_public.key ]; then + WG_PUBKEY=$(cat /etc/parahub/wg_vps_public.key 2>/dev/null) +fi + # Detect hardware from board_name HW=$(cat /tmp/sysinfo/board_name 2>/dev/null) case "$HW" in @@ -33,7 +39,7 @@ case "$HW" in esac FW_VERSION=$(cat /etc/parahub/version 2>/dev/null || echo "unknown") -PAYLOAD="{\"mac\":\"${MAC}\",\"hostname\":\"${HOSTNAME}\",\"yggdrasil_address\":\"${YGG_ADDR}\",\"firmware_version\":\"${FW_VERSION}\",\"hardware_profile\":\"${HW}\",\"uptime\":${UPTIME},\"private_ssid\":\"${SSID}\",\"firmware_role\":\"${ROLE}\",\"mesh_ip\":\"${MESH_IP}\"}" +PAYLOAD="{\"mac\":\"${MAC}\",\"hostname\":\"${HOSTNAME}\",\"yggdrasil_address\":\"${YGG_ADDR}\",\"firmware_version\":\"${FW_VERSION}\",\"hardware_profile\":\"${HW}\",\"uptime\":${UPTIME},\"private_ssid\":\"${SSID}\",\"firmware_role\":\"${ROLE}\",\"mesh_ip\":\"${MESH_IP}\",\"wg_public_key\":\"${WG_PUBKEY}\"}" RESPONSE="" @@ -70,3 +76,15 @@ if [ "$ROLE" != "bee" ] && [ -x /usr/bin/parahub-speed-control ] && [ -n "$RESPO parahub-speed-control add "$IP" done fi + +# VPS gateway auto-configuration (Bumblebee only, skip if Mullvad is configured) +if [ "$ROLE" != "bee" ] && [ -n "$RESPONSE" ] && [ ! -f /etc/parahub/mullvad_account ]; then + VPS_ENDPOINT=$(echo "$RESPONSE" | jsonfilter -e '$.vps_gateway.endpoint' 2>/dev/null) + VPS_PUBKEY=$(echo "$RESPONSE" | jsonfilter -e '$.vps_gateway.public_key' 2>/dev/null) + VPS_IP=$(echo "$RESPONSE" | jsonfilter -e '$.vps_gateway.assigned_ip' 2>/dev/null) + VPS_KEEPALIVE=$(echo "$RESPONSE" | jsonfilter -e '$.vps_gateway.keepalive' 2>/dev/null) + + if [ -n "$VPS_ENDPOINT" ] && [ -n "$VPS_PUBKEY" ] && [ -n "$VPS_IP" ]; then + /usr/bin/parahub-vps-setup "$VPS_ENDPOINT" "$VPS_PUBKEY" "$VPS_IP" "${VPS_KEEPALIVE:-25}" + fi +fi diff --git a/files/usr/bin/parahub-mullvad b/files/usr/bin/parahub-mullvad index 6d3fc24..7124fd4 100755 --- a/files/usr/bin/parahub-mullvad +++ b/files/usr/bin/parahub-mullvad @@ -3,7 +3,7 @@ # # Allows the node owner to run Mullvad directly on the router. # Guest traffic routes through the local WireGuard tunnel instead of -# the default GRE6→VPS path, giving lower latency and using the +# the default VPS gateway path, giving lower latency and using the # nearest Mullvad server. # # Usage: @@ -150,8 +150,8 @@ set network.@wireguard_mullvad_local[-1].route_allowed_ips='1' set network.@wireguard_mullvad_local[-1].persistent_keepalive='25' WG_EOF - # Disable GRE6 tunnel (WG replaces it in table 100) - uci set network.vpn_tunnel.auto='0' + # Disable VPS gateway (Mullvad replaces it in table 100) + uci -q set network.vps_gateway.auto='0' 2>/dev/null || true uci commit network # --- Step 6: Firewall zone for mullvad_local --- @@ -213,8 +213,10 @@ cmd_status() { echo "" wg show mullvad_local 2>/dev/null || echo "WireGuard interface: not up" else - echo "Mode: VPS GATEWAY (GRE6 tunnel)" - echo "VPS: 91.98.123.238 -> Mullvad Portugal" + echo "Mode: VPS GATEWAY (WireGuard)" + echo "VPS: 185.47.131.84 -> Mullvad" + echo "" + wg show vps_gateway 2>/dev/null || echo "VPS gateway: not active" fi echo "" @@ -222,6 +224,8 @@ cmd_status() { local fwd_idx if fwd_idx=$(find_guest_forwarding); then echo " guest -> $(uci -q get "firewall.@forwarding[$fwd_idx].dest")" + else + echo " (none)" fi } @@ -241,8 +245,8 @@ cmd_remove() { uci -q delete network.mullvad_local 2>/dev/null || true while uci -q delete network.@wireguard_mullvad_local[0] 2>/dev/null; do :; done - # Re-enable GRE6 tunnel - uci -q delete network.vpn_tunnel.auto 2>/dev/null || true + # Re-enable VPS gateway + uci -q delete network.vps_gateway.auto 2>/dev/null || true uci commit network # Remove firewall zone @@ -251,10 +255,10 @@ cmd_remove() { uci delete "firewall.@zone[$zone_idx]" fi - # Switch guest forwarding back to vpn_tunnel + # Switch guest forwarding back to vps_gateway local fwd_idx if fwd_idx=$(find_guest_forwarding); then - uci set "firewall.@forwarding[$fwd_idx].dest=vpn_tunnel" + uci set "firewall.@forwarding[$fwd_idx].dest=vps_gateway" fi uci commit firewall diff --git a/files/usr/bin/parahub-vps-setup b/files/usr/bin/parahub-vps-setup new file mode 100755 index 0000000..b4fa37d --- /dev/null +++ b/files/usr/bin/parahub-vps-setup @@ -0,0 +1,126 @@ +#!/bin/sh +# Parahub Mesh — VPS WireGuard Gateway Setup +# Called by heartbeat to auto-configure the VPS tunnel for guest internet. +# +# Usage: parahub-vps-setup +# Example: parahub-vps-setup 185.47.131.84:51820 10.99.0.2/16 25 + +ENDPOINT="$1" +PUBKEY="$2" +ASSIGNED_IP="$3" +KEEPALIVE="${4:-25}" +STATE_FILE="/tmp/vps_gateway_state" + +if [ -z "$ENDPOINT" ] || [ -z "$PUBKEY" ] || [ -z "$ASSIGNED_IP" ]; then + logger -t parahub-vps "Error: missing arguments" + exit 1 +fi + +# Check if config unchanged and interface is up — skip reconfiguration +if [ -f "$STATE_FILE" ]; then + OLD_STATE=$(cat "$STATE_FILE") + NEW_STATE="${ENDPOINT}|${PUBKEY}|${ASSIGNED_IP}|${KEEPALIVE}" + if [ "$OLD_STATE" = "$NEW_STATE" ]; then + # Check if interface is actually up + if ip link show vps_gateway >/dev/null 2>&1; then + exit 0 + fi + fi +fi + +logger -t parahub-vps "Configuring VPS gateway: ${ENDPOINT} -> ${ASSIGNED_IP}" + +# Bootstrap: create interface + zone + forwarding if missing (OTA from pre-VPS firmware) +if ! uci -q get network.vps_gateway >/dev/null 2>&1; then + logger -t parahub-vps "Bootstrap: creating vps_gateway interface (OTA upgrade)" + uci batch <<-BOOTSTRAP_NET +set network.vps_gateway=interface +set network.vps_gateway.proto='wireguard' +set network.vps_gateway.private_key_file='/etc/parahub/wg_vps_private.key' +set network.vps_gateway.mtu='1420' +set network.vps_gateway.ip4table='100' +BOOTSTRAP_NET + uci commit network +fi + +# Bootstrap firewall zone if missing +ZONE_EXISTS=0 +idx=0 +while uci -q get "firewall.@zone[$idx]" >/dev/null 2>&1; do + zname=$(uci -q get "firewall.@zone[$idx].name") + if [ "$zname" = "vps_gateway" ]; then + ZONE_EXISTS=1 + break + fi + idx=$((idx + 1)) +done + +if [ "$ZONE_EXISTS" = "0" ]; then + logger -t parahub-vps "Bootstrap: creating vps_gateway firewall zone" + uci batch <<-BOOTSTRAP_FW +add firewall zone +set firewall.@zone[-1].name='vps_gateway' +set firewall.@zone[-1].input='REJECT' +set firewall.@zone[-1].output='ACCEPT' +set firewall.@zone[-1].forward='REJECT' +set firewall.@zone[-1].masq='1' +set firewall.@zone[-1].mtu_fix='1' +add_list firewall.@zone[-1].network='vps_gateway' +BOOTSTRAP_FW + + # Add guest → vps_gateway forwarding if no guest forwarding exists + FWD_EXISTS=0 + fidx=0 + while uci -q get "firewall.@forwarding[$fidx]" >/dev/null 2>&1; do + src=$(uci -q get "firewall.@forwarding[$fidx].src") + if [ "$src" = "guest" ]; then + FWD_EXISTS=1 + break + fi + fidx=$((fidx + 1)) + done + + if [ "$FWD_EXISTS" = "0" ]; then + uci add firewall forwarding >/dev/null + uci set "firewall.@forwarding[-1].src=guest" + uci set "firewall.@forwarding[-1].dest=vps_gateway" + fi + + uci commit firewall +fi + +# Remove old peer sections +while uci -q delete network.@wireguard_vps_gateway[0] 2>/dev/null; do :; done + +# Set address and add peer +uci -q delete network.vps_gateway.addresses 2>/dev/null || true +uci add_list network.vps_gateway.addresses="${ASSIGNED_IP}" + +# Split endpoint into host:port +EP_HOST="${ENDPOINT%:*}" +EP_PORT="${ENDPOINT##*:}" + +uci batch <<-PEER_EOF +add network wireguard_vps_gateway +set network.@wireguard_vps_gateway[-1].public_key='${PUBKEY}' +set network.@wireguard_vps_gateway[-1].endpoint_host='${EP_HOST}' +set network.@wireguard_vps_gateway[-1].endpoint_port='${EP_PORT}' +add_list network.@wireguard_vps_gateway[-1].allowed_ips='0.0.0.0/0' +set network.@wireguard_vps_gateway[-1].route_allowed_ips='1' +set network.@wireguard_vps_gateway[-1].persistent_keepalive='${KEEPALIVE}' +PEER_EOF + +# Enable interface (remove auto='0') +uci -q delete network.vps_gateway.auto 2>/dev/null || true +uci commit network + +# Bring up the interface (minimal restart) +ifup vps_gateway 2>/dev/null + +# Reload firewall rules +fw4 reload 2>/dev/null + +# Save state for next run +echo "${ENDPOINT}|${PUBKEY}|${ASSIGNED_IP}|${KEEPALIVE}" > "$STATE_FILE" + +logger -t parahub-vps "VPS gateway activated: ${ASSIGNED_IP} via ${ENDPOINT}" diff --git a/scripts/build.sh b/scripts/build.sh index 30cbd70..cf2f6f1 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -11,7 +11,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" OPENWRT_VERSION="${OPENWRT_VERSION:-25.12.0-rc4}" -PARAHUB_BUILD="3" +PARAHUB_BUILD="4" FIRMWARE_VERSION="${OPENWRT_VERSION}-ph${PARAHUB_BUILD}" # ============================================================================