Last active
July 27, 2025 21:54
-
-
Save fakhamatia/d84bdddc39f555bef30574185a19bc53 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
TEST_URL="https://www.gstatic.com/generate_204" | |
MAX_LATENCY=1000 | |
ALLOWED_COUNTRIES="AT|BE|CA|CH|CZ|DE|DK|ES|FI|FL|FR|GB|HU|IE|IS|IT|LI|LU|NL|NO|PT|RO|SE|SK|SW|UK|US" | |
SLEEP=15 | |
TIMEOUT=$(awk "BEGIN {print (${MAX_LATENCY} / 1000) + 1}") | |
CURRENT=$(uci -q get passwall2.myshunt.default_node) | |
REMARK=$(uci -q get passwall2."$CURRENT".remarks) | |
SUB_COUNT=1 | |
COUNTRY_CHECK=1 | |
SWITCHED=0 | |
LOCK_FILE="/tmp/passwall-auto-switch.lock" | |
STATS_FILE="/root/passwall_stats.json" | |
log_msg() { | |
echo "$1" | |
logger -t internet-detector "$1" | |
} | |
if [ -f "$LOCK_FILE" ]; then | |
echo "Script already running. Exiting." | |
exit 1 | |
fi | |
touch "$LOCK_FILE" | |
cleanup() { | |
rm -f "$LOCK_FILE" | |
} | |
trap cleanup EXIT | |
get_nodes() { | |
uci show passwall2 | grep "=nodes" | cut -d. -f2 | cut -d= -f1 | while read -r node; do | |
proto=$(uci -q get passwall2."$node".protocol) | |
if [ "$proto" = "vmess" ] || [ "$proto" = "vless" ] || [ "$proto" = "trojan" ]; then | |
echo "$node" | |
fi | |
done | |
} | |
test_latency() { | |
ms=$(curl -o /dev/null -s -w "%{time_total}" --max-time "$TIMEOUT" "$TEST_URL") | |
echo $(awk "BEGIN {print int($ms * 1000)}") | |
} | |
change_node() { | |
node="${1:-_direct}" | |
uci set passwall2.myshunt.default_node="$node" | |
uci commit passwall2 | |
/etc/init.d/passwall2 restart 2>/dev/null | |
sleep "$SLEEP" | |
} | |
get_country_code() { | |
response=$(curl -s -w "HTTPSTATUS:%{http_code}" --max-time "$TIMEOUT" https://ipinfo.io/country) | |
body=$(echo "$response" | sed -e 's/HTTPSTATUS\:.*//g' | tr -d '\n') | |
status=$(echo "$response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://') | |
if [ "$status" != "200" ]; then | |
echo "UNKNOWN: HTTP $status" | |
elif [ -z "$body" ]; then | |
echo "UNKNOWN: Empty response" | |
else | |
echo "$body" | |
fi | |
} | |
NODES=$(get_nodes) | |
log_msg "Current node $CURRENT ($REMARK)" | |
CURRENT_FOUND=0 | |
AFTER_CURRENT="" | |
if [ "$CURRENT" = "_direct" ]; then | |
CURRENT_FOUND=1 | |
fi | |
for node in $NODES; do | |
if [ "$CURRENT_FOUND" = "1" ]; then | |
AFTER_CURRENT="$AFTER_CURRENT $node" | |
elif [ "$node" = "$CURRENT" ]; then | |
CURRENT_FOUND=1 | |
fi | |
done | |
ORDERED_NODES="$AFTER_CURRENT" | |
update_stats() { | |
country="${1:-UNKNOWN}" | |
status="${2:-skip}" | |
[ ! -f "$STATS_FILE" ] && echo "{}" >"$STATS_FILE" | |
OLD=$(cat "$STATS_FILE") | |
TOTAL=$(echo "$OLD" | jsonfilter -e "@.$country.total" 2>/dev/null || echo 0) | |
SUCCESS=$(echo "$OLD" | jsonfilter -e "@.$country.success" 2>/dev/null || echo 0) | |
FAIL=$(echo "$OLD" | jsonfilter -e "@.$country.fail" 2>/dev/null || echo 0) | |
SKIP=$(echo "$OLD" | jsonfilter -e "@.$country.skip" 2>/dev/null || echo 0) | |
case "$status" in | |
success) | |
TOTAL=$((TOTAL + 1)) | |
SUCCESS=$((SUCCESS + 1)) | |
;; | |
fail) | |
TOTAL=$((TOTAL + 1)) | |
FAIL=$((FAIL + 1)) | |
;; | |
skip) | |
TOTAL=$((TOTAL + 1)) | |
SKIP=$((SKIP + 1)) | |
;; | |
esac | |
NEW=$(echo "$OLD" | | |
jq ". + {\"$country\": {\"total\": $TOTAL, \"success\": $SUCCESS, \"fail\": $FAIL, \"skip\": $SKIP}}") | |
echo "$NEW" >"$STATS_FILE" | |
} | |
for node in $ORDERED_NODES; do | |
REMARK=$(uci -q get passwall2."$node".remarks) | |
ADDRESS=$(uci -q get passwall2."$node".address) | |
log_msg "Trying node $node ($REMARK)" | |
COUNTRY_HINT=$(echo "$REMARK" | sed -n 's/.*\([^[:space:]]\{2,3\}\)\[.*/\1/p') | |
COUNTRY="" | |
COUNTRY_FROM_IP=0 | |
NODE_CHANGED=0 | |
if [ "$COUNTRY_CHECK" -eq 1 ]; then | |
if [ -n "$COUNTRY_HINT" ]; then | |
if echo "$COUNTRY_HINT" | grep -qE "^($ALLOWED_COUNTRIES)$"; then | |
COUNTRY="$COUNTRY_HINT" | |
log_msg "Country detected from remark and allowed: $COUNTRY" | |
else | |
update_stats "$COUNTRY_HINT" skip | |
log_msg "Country $COUNTRY_HINT from remark is not allowed, skipping" | |
continue | |
fi | |
else | |
if [ "$NODE_CHANGED" -eq 0 ]; then | |
change_node $node | |
NODE_CHANGED=1 | |
fi | |
if [ "$COUNTRY_FROM_IP" -eq 0 ]; then | |
COUNTRY=$(get_country_code) | |
COUNTRY_FROM_IP=1 | |
fi | |
if echo "$COUNTRY" | grep -qE "^($ALLOWED_COUNTRIES)$"; then | |
log_msg "Country detected from IP and allowed: $COUNTRY" | |
elif echo "$COUNTRY" | grep -q "^UNKNOWN"; then | |
update_stats "UNKNOWN" fail | |
log_msg "Unable to detect country from IP, marking as fail" | |
continue | |
else | |
update_stats "$COUNTRY" skip | |
log_msg "Country $COUNTRY from IP is not allowed, skipping" | |
continue | |
fi | |
fi | |
fi | |
if [ "$NODE_CHANGED" -eq 0 ]; then | |
change_node $node | |
NODE_CHANGED=1 | |
fi | |
LATENCY=$(test_latency) | |
log_msg "Node $node latency: ${LATENCY}ms" | |
if [ "$LATENCY" -gt "$MAX_LATENCY" ] || [ "$LATENCY" -eq 0 ]; then | |
if [ "$COUNTRY_CHECK" -eq 1 ]; then | |
update_stats "$COUNTRY" fail | |
fi | |
log_msg "Latency too high or zero, marking as fail" | |
continue | |
fi | |
if [ "$COUNTRY_CHECK" -eq 1 ]; then | |
if [ "$COUNTRY_FROM_IP" -eq 1 ]; then | |
update_stats "$COUNTRY" success | |
log_msg "Node $node accepted" | |
SWITCHED=1 | |
break | |
else | |
COUNTRY_FINAL=$(get_country_code) | |
if echo "$COUNTRY_FINAL" | grep -qE "^($ALLOWED_COUNTRIES)$"; then | |
update_stats "$COUNTRY_FINAL" success | |
log_msg "Node $node accepted after final IP check" | |
SWITCHED=1 | |
break | |
elif echo "$COUNTRY_FINAL" | grep -q "^UNKNOWN"; then | |
update_stats "UNKNOWN" fail | |
log_msg "Final IP check failed, country unknown, marking as fail" | |
continue | |
else | |
update_stats "$COUNTRY_FINAL" skip | |
log_msg "Final IP check: country $COUNTRY_FINAL not allowed, skipping" | |
continue | |
fi | |
fi | |
else | |
log_msg "Node $node accepted" | |
SWITCHED=1 | |
break | |
fi | |
done | |
if [ "$SWITCHED" -eq 0 ]; then | |
log_msg "All nodes failed. Switching to direct." | |
change_node "_direct" | |
if [ "$SUB_COUNT" -eq 0 ]; then | |
log_msg "No subscriptions found." | |
else | |
i=0 | |
while [ "$i" -lt "$SUB_COUNT" ]; do | |
log_msg "Updating subscription $((i + 1))" | |
lua /usr/share/passwall2/subscribe.lua start "@subscribe_list[$i]" 2>/dev/null | |
sleep "$SLEEP" | |
i=$((i + 1)) | |
done | |
fi | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment