feat: extend get_server_public_ip with checkip.amazonaws.com + ifconfig.io

Add 2 services to the public IP detection cascade (was 4, now 6),
giving better coverage for AWS / GCP / OCI deployments where a single
endpoint can rate-limit or be unreachable from a VPC private subnet.
checkip.amazonaws.com is listed first because it tends to stay reachable
from cloud private subnets routed through a NAT Gateway. ifconfig.io
serves as an alternative to ifconfig.me for downtime cases.

First-wins order preserved (no behavior change when first service
responds). RU + EN parity maintained.

+ tests/test_v5140_public_ip_services.bats (+9 tests):
  structural RU/EN parity on the 6 endpoints, functional fallthrough,
  invalid IP format skip, cache short-circuit.
This commit is contained in:
Ivan Bondarev 2026-05-18 22:21:38 +03:00
parent 6b9d5b1e45
commit 073de10982
3 changed files with 189 additions and 3 deletions

View file

@ -57,6 +57,15 @@ get_main_nic() {
}
# Определение внешнего IP-адреса сервера (с кэшированием)
#
# Список 6 сервисов покрывает основные NAT и cloud-сценарии.
# checkip.amazonaws.com добавлен для AWS / GCP / OCI: на инстансе
# за NAT-Gateway отдельные сервисы могут rate-limit'ить, а AWS
# endpoint остаётся доступным даже из VPC private subnet.
# ifconfig.io - альтернатива ifconfig.me на случай downtime.
# Порядок: AWS-сервис первым (наиболее надёжен в cloud), далее
# по убыванию uptime SLA. First-wins: при первом валидном ответе
# остальные не запрашиваются.
_CACHED_PUBLIC_IP=""
get_server_public_ip() {
if [[ -n "$_CACHED_PUBLIC_IP" ]]; then
@ -64,7 +73,14 @@ get_server_public_ip() {
return 0
fi
local ip="" svc
for svc in https://ifconfig.me https://api.ipify.org https://icanhazip.com https://ipinfo.io/ip; do
for svc in \
https://checkip.amazonaws.com \
https://ifconfig.me \
https://api.ipify.org \
https://icanhazip.com \
https://ipinfo.io/ip \
https://ifconfig.io
do
ip=$(curl -4 -sf --max-time 5 "$svc" 2>/dev/null | tr -d '[:space:]')
if [[ -n "$ip" && "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
_CACHED_PUBLIC_IP="$ip"

View file

@ -56,7 +56,15 @@ get_main_nic() {
ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1); exit}'
}
# Detect server public IP (with caching)
# Detect server public IP (with caching).
#
# The 6-service list covers common NAT and cloud scenarios.
# checkip.amazonaws.com is added for AWS / GCP / OCI: on an instance
# behind a NAT Gateway some services may rate-limit, while the AWS
# endpoint stays reachable even from a VPC private subnet.
# ifconfig.io is an alternative to ifconfig.me for downtime cases.
# Order: AWS service first (most reliable in cloud), then by uptime SLA.
# First-wins: when one service returns a valid IP, the rest are skipped.
_CACHED_PUBLIC_IP=""
get_server_public_ip() {
if [[ -n "$_CACHED_PUBLIC_IP" ]]; then
@ -64,7 +72,14 @@ get_server_public_ip() {
return 0
fi
local ip="" svc
for svc in https://ifconfig.me https://api.ipify.org https://icanhazip.com https://ipinfo.io/ip; do
for svc in \
https://checkip.amazonaws.com \
https://ifconfig.me \
https://api.ipify.org \
https://icanhazip.com \
https://ipinfo.io/ip \
https://ifconfig.io
do
ip=$(curl -4 -sf --max-time 5 "$svc" 2>/dev/null | tr -d '[:space:]')
if [[ -n "$ip" && "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
_CACHED_PUBLIC_IP="$ip"

View file

@ -0,0 +1,155 @@
#!/usr/bin/env bats
# Tests for get_server_public_ip extended service list (v5.14.0 MyAI-avy9).
#
# v5.14.0 extends the public IP detection from 4 to 6 services, adding
# checkip.amazonaws.com (AWS-friendly, reachable from VPC private subnets)
# and ifconfig.io (alternative to ifconfig.me for downtime cases).
# Order is first-wins: checkip.amazonaws.com first, then by uptime SLA.
load test_helper
setup() {
TEST_DIR=$(mktemp -d)
export AWG_DIR="$TEST_DIR"
log() { :; }
log_warn() { :; }
log_error() { :; }
log_debug() { :; }
export -f log log_warn log_error log_debug
source "$BATS_TEST_DIRNAME/../awg_common.sh"
_CACHED_PUBLIC_IP=""
}
teardown() {
rm -rf "$TEST_DIR"
unset -f curl 2>/dev/null || true
}
@test "get_server_public_ip: structural RU - service list contains 6 endpoints" {
local FILE="${BATS_TEST_DIRNAME}/../awg_common.sh"
local block
block=$(awk '/^get_server_public_ip\(\) \{$/,/^}$/' "$FILE")
[[ "$block" == *"checkip.amazonaws.com"* ]]
[[ "$block" == *"ifconfig.me"* ]]
[[ "$block" == *"api.ipify.org"* ]]
[[ "$block" == *"icanhazip.com"* ]]
[[ "$block" == *"ipinfo.io/ip"* ]]
[[ "$block" == *"ifconfig.io"* ]]
}
@test "get_server_public_ip: structural EN - service list contains 6 endpoints" {
local FILE="${BATS_TEST_DIRNAME}/../awg_common_en.sh"
local block
block=$(awk '/^get_server_public_ip\(\) \{$/,/^}$/' "$FILE")
[[ "$block" == *"checkip.amazonaws.com"* ]]
[[ "$block" == *"ifconfig.me"* ]]
[[ "$block" == *"api.ipify.org"* ]]
[[ "$block" == *"icanhazip.com"* ]]
[[ "$block" == *"ipinfo.io/ip"* ]]
[[ "$block" == *"ifconfig.io"* ]]
}
@test "get_server_public_ip: RU and EN service lists match (parity)" {
local RU_FILE="${BATS_TEST_DIRNAME}/../awg_common.sh"
local EN_FILE="${BATS_TEST_DIRNAME}/../awg_common_en.sh"
local ru_count en_count
ru_count=$(awk '/^get_server_public_ip\(\) \{$/,/^}$/' "$RU_FILE" | grep -cE 'https://')
en_count=$(awk '/^get_server_public_ip\(\) \{$/,/^}$/' "$EN_FILE" | grep -cE 'https://')
[ "$ru_count" -eq "$en_count" ]
[ "$ru_count" -eq 6 ]
}
@test "get_server_public_ip: first service success returns valid IP" {
# shellcheck disable=SC2317
curl() {
local url="${@: -1}"
if [[ "$url" == "https://checkip.amazonaws.com" ]]; then
echo "203.0.113.42"
return 0
fi
return 1
}
export -f curl
run get_server_public_ip
[ "$status" -eq 0 ]
[ "$output" = "203.0.113.42" ]
}
@test "get_server_public_ip: first service fails, falls through to second" {
# shellcheck disable=SC2317
curl() {
local url="${@: -1}"
if [[ "$url" == "https://checkip.amazonaws.com" ]]; then
return 1
fi
if [[ "$url" == "https://ifconfig.me" ]]; then
echo "198.51.100.7"
return 0
fi
return 1
}
export -f curl
run get_server_public_ip
[ "$status" -eq 0 ]
[ "$output" = "198.51.100.7" ]
}
@test "get_server_public_ip: all 6 services fail returns 1 with empty output" {
# shellcheck disable=SC2317
curl() { return 1; }
export -f curl
run get_server_public_ip
[ "$status" -ne 0 ]
[ -z "$output" ]
}
@test "get_server_public_ip: invalid IP format from one service skips to next" {
# shellcheck disable=SC2317
curl() {
local url="${@: -1}"
case "$url" in
"https://checkip.amazonaws.com")
echo "not-an-ip"
return 0
;;
"https://ifconfig.me")
echo "192.0.2.99"
return 0
;;
*) return 1 ;;
esac
}
export -f curl
run get_server_public_ip
[ "$status" -eq 0 ]
[ "$output" = "192.0.2.99" ]
}
@test "get_server_public_ip: last-in-list ifconfig.io success after 5 fails" {
# shellcheck disable=SC2317
curl() {
local url="${@: -1}"
if [[ "$url" == "https://ifconfig.io" ]]; then
echo "172.16.0.1"
return 0
fi
return 1
}
export -f curl
run get_server_public_ip
[ "$status" -eq 0 ]
[ "$output" = "172.16.0.1" ]
}
@test "get_server_public_ip: cached value short-circuits subsequent calls" {
_CACHED_PUBLIC_IP="10.20.30.40"
# No curl mock - if cache miss happens, real curl would run (test fails).
run get_server_public_ip
[ "$status" -eq 0 ]
[ "$output" = "10.20.30.40" ]
}