Skip to content

Instantly share code, notes, and snippets.

@adamamyl
Created May 4, 2026 15:57
Show Gist options
  • Select an option

  • Save adamamyl/470cd8142fc1d4db8b6c3b819a2743cf to your computer and use it in GitHub Desktop.

Select an option

Save adamamyl/470cd8142fc1d4db8b6c3b819a2743cf to your computer and use it in GitHub Desktop.
find-ubuntu-mirrors
#!/usr/bin/env bash
# find-ubuntu-mirror
#
# Detects the host's real public IP via the container default gateway,
# geolocates it, then selects and benchmarks regional Ubuntu mirrors.
# Writes a DEB822-format sources file (.sources) for the running Ubuntu release.
#
# Works under: plain Docker, RunPod, LXC, and bare metal.
# Requires: curl, ip (iproute2), awk, sort, head — all standard on Ubuntu.
set -euo pipefail
# -- config -------------------------------------------------------------------
# resolved at runtime — see get_codename()
CODENAME=""
readonly COMPONENTS="main restricted universe multiverse"
readonly SOURCES_FILE="/etc/apt/sources.list.d/ubuntu.sources"
readonly BENCHMARK_COUNT=5 # how many mirror candidates to time
readonly BENCHMARK_FILE="ls-lR.gz" # standard large file present on all mirrors
readonly TIMEOUT=5 # curl timeout per request (seconds)
# Akamai's CDN-backed mirror — fast globally, anycast-routed, solid default
readonly AKAMAI_MIRROR="https://ubuntu.mirror.constant.com/ubuntu"
# canonical upstream — last-resort only
readonly FALLBACK_MIRROR="https://archive.ubuntu.com/ubuntu"
# -- helpers ------------------------------------------------------------------
log() { echo "[mirror] $*" >&2; }
warn() { echo "[mirror] WARN: $*" >&2; }
die() { echo "[mirror] ERROR: $*" >&2; exit 1; }
require() {
local cmd
for cmd in "$@"; do
command -v "$cmd" &>/dev/null || die "required command not found: $cmd"
done
}
# -- codename detection -------------------------------------------------------
# Resolves the Ubuntu release codename dynamically.
# Tries lsb_release first, then /etc/os-release, then the apt cache.
get_codename() {
local name=""
# lsb_release is the canonical source; may not be installed in minimal images
if command -v lsb_release &>/dev/null; then
name="$(lsb_release -cs 2>/dev/null)" || true
fi
# /etc/os-release is present on virtually every modern Ubuntu image
if [[ -z "$name" && -r /etc/os-release ]]; then
name="$(. /etc/os-release && echo "${UBUNTU_CODENAME:-${VERSION_CODENAME:-}}")" || true
fi
# last resort: scrape from the existing apt sources
if [[ -z "$name" ]]; then
name="$(
grep -rhP '^\s*(deb|Types:)' /etc/apt/sources.list /etc/apt/sources.list.d/ 2>/dev/null \
| grep -oP '(?<=ubuntu\s)\w+' \
| head -n1
)" || true
fi
[[ -n "$name" ]] || die "could not determine Ubuntu codename — is this Ubuntu?"
echo "$name"
}
# -- gateway / host IP detection ----------------------------------------------
# Finds the default gateway IP — works in Docker (eth0) and RunPod (various).
get_gateway_ip() {
ip route show default 2>/dev/null \
| awk '/default via/ { print $3; exit }'
}
# Resolves the *public* IP and country code in a single ipinfo.io call.
# ipinfo.io returns a JSON blob with .ip and .country fields.
# Falls back to plain IP-echo services if ipinfo is unreachable, then
# to the gateway IP itself for a best-effort region guess.
get_ipinfo() {
local gw="$1"
local json=""
json="$(curl -sf --max-time "$TIMEOUT" "https://ipinfo.io/json" 2>/dev/null)" || true
if [[ -n "$json" ]]; then
# parse without jq — ipinfo's JSON is simple enough for awk
local ip country
ip="$(echo "$json" | grep -oP '"ip"\s*:\s*"\K[^"]+' || true)"
country="$(echo "$json" | grep -oP '"country"\s*:\s*"\K[^"]+' || true)"
if [[ -n "$ip" && -n "$country" ]]; then
echo "$ip" "$country"
return
fi
fi
# ipinfo unreachable — fall back to plain IP echo + separate geo lookup
warn "ipinfo.io unreachable; trying fallback IP detection"
local pub_ip=""
local -a echo_services=(
"https://ifconfig.me/ip"
"https://api.ipify.org"
"https://icanhazip.com"
)
local svc
for svc in "${echo_services[@]}"; do
pub_ip="$(curl -sf --max-time "$TIMEOUT" "$svc" 2>/dev/null | tr -d '[:space:]')" || true
[[ "$pub_ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] && break
pub_ip=""
done
# try ip-api.com for country if we have a usable IP
local country=""
if [[ -n "$pub_ip" ]]; then
country="$(
curl -sf --max-time "$TIMEOUT" \
"http://ip-api.com/line/${pub_ip}?fields=countryCode" 2>/dev/null \
| tr -d '[:space:]'
)" || true
fi
# absolute last resort: gateway IP, GB default
echo "${pub_ip:-$gw}" "${country:-GB}"
}
# -- mirror candidates --------------------------------------------------------
# Fetches the live mirror list for a country code from mirrors.ubuntu.com.
# Ubuntu maintains per-CC text files at http://mirrors.ubuntu.com/CC.txt —
# one http:// URL per line, geolocation-curated by Canonical.
# Falls back to GB if the country has no list, then to mirrors.txt (global).
fetch_mirror_list() {
local country="${1^^}"
local url="http://mirrors.ubuntu.com/${country}.txt"
local result=""
result="$(curl -sf --max-time "$TIMEOUT" "$url" 2>/dev/null)" || true
if [[ -z "$result" ]]; then
warn "falling back to global mirrors.txt"
result="$(curl -sf --max-time "$TIMEOUT" "http://mirrors.ubuntu.com/mirrors.txt" 2>/dev/null)" || true
fi
# strip blank lines, whitespace, limit to sane count
echo "$result" | awk 'NF { gsub(/[[:space:]]/, ""); print }' | head -20
}
# Returns a prioritised list of mirror URLs to benchmark.
# Akamai's anycast mirror always leads — fast globally regardless of region.
# Live country mirrors from mirrors.ubuntu.com follow.
# Canonical upstream is the last-resort backstop.
get_mirror_candidates() {
local country="${1^^}"
# Akamai CDN mirror — anycast, globally distributed, always first
local -a mirrors=("$AKAMAI_MIRROR")
# pull live CC list and append
mapfile -t cc_mirrors < <(fetch_mirror_list "$country")
mirrors+=("${cc_mirrors[@]}")
# canonical upstream always last — only reached if everything else times out
mirrors+=("$FALLBACK_MIRROR")
# deduplicate while preserving order
local seen=()
local m
for m in "${mirrors[@]}"; do
[[ " ${seen[*]} " =~ " ${m} " ]] || seen+=("$m")
done
printf '%s\n' "${seen[@]}"
}
# -- benchmarking -------------------------------------------------------------
# Times a single mirror by fetching a known large index file.
# Prints "milliseconds url" so results can be sorted numerically.
benchmark_mirror() {
local mirror="$1"
local url="${mirror%/}/dists/${CODENAME}/${BENCHMARK_FILE}"
local ms
ms="$(
curl -sf --max-time "$TIMEOUT" \
-o /dev/null \
-w '%{time_total}' \
"$url" 2>/dev/null
)" || { echo "9999 $mirror"; return; }
# convert fractional seconds → integer milliseconds for sorting
printf '%d %s\n' "$(awk "BEGIN { printf \"%d\", $ms * 1000 }")" "$mirror"
}
# Runs benchmarks against the first N candidates, returns the fastest URL.
pick_fastest_mirror() {
local -a candidates=("$@")
local results=()
local i=0
log "benchmarking up to ${BENCHMARK_COUNT} mirrors..."
local m
for m in "${candidates[@]}"; do
(( i >= BENCHMARK_COUNT )) && break
log " timing: $m"
results+=("$(benchmark_mirror "$m")")
(( i++ )) || true
done
# sort by time ascending, pick winner
local winner
winner="$(printf '%s\n' "${results[@]}" | sort -n | head -n1 | awk '{print $2}')"
echo "$winner"
}
# -- write sources file -------------------------------------------------------
# Writes a DEB822-format .sources file (replaces the old one-liner .list format).
write_sources_file() {
local mirror="$1"
log "writing DEB822 sources to: $SOURCES_FILE"
cat > "$SOURCES_FILE" <<EOF
# Ubuntu ${CODENAME^} — auto-generated by find-ubuntu-mirror.sh
# Mirror selected based on host geolocation. Regenerate at any time.
Types: deb
URIs: ${mirror}
Suites: ${CODENAME} ${CODENAME}-updates ${CODENAME}-backports
Components: ${COMPONENTS}
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
Types: deb
URIs: http://security.ubuntu.com/ubuntu
Suites: ${CODENAME}-security
Components: ${COMPONENTS}
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
EOF
}
# -- main ---------------------------------------------------------------------
main() {
require curl ip awk sort head
# resolve codename dynamically
CODENAME="$(get_codename)"
log "Ubuntu codename: $CODENAME"
# detect gateway
local gw
gw="$(get_gateway_ip)"
[[ -n "$gw" ]] || die "no default gateway found — is networking up?"
log "default gateway: $gw"
# resolve public IP and country via ipinfo.io
local public_ip country
read -r public_ip country < <(get_ipinfo "$gw")
log "public IP: $public_ip country: $country"
# get mirror candidates for region
mapfile -t candidates < <(get_mirror_candidates "$country")
log "found ${#candidates[@]} candidate mirror(s)"
# benchmark and pick fastest
local best_mirror
best_mirror="$(pick_fastest_mirror "${candidates[@]}")"
log "fastest mirror: $best_mirror"
# write sources
write_sources_file "$best_mirror"
log "done — run 'apt-get update' to apply"
echo "$best_mirror"
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment