diff --git a/bin/omarchy-cn-mirror-apply b/bin/omarchy-cn-mirror-apply new file mode 100755 index 00000000..3ee99540 --- /dev/null +++ b/bin/omarchy-cn-mirror-apply @@ -0,0 +1,35 @@ +#!/bin/bash +# omarchy:summary=Apply a mirror profile or pin one mirror for pacman +# omarchy:args= +# omarchy:examples=omarchycn mirror apply china | omarchycn mirror apply official | omarchycn mirror apply tuna +# omarchy:requires-sudo=true + +set -euo pipefail + +source "$OMARCHY_PATH/cn/lib/mirror.sh" + +target="${1:?usage: omarchycn mirror apply }" + +case "$target" in +china) + echo "Benchmarking China mirrors..." + mapfile -t ranked < <(cn_mirror_rank $(cn_mirror_ids_by_region cn)) + if (( ${#ranked[@]} == 0 )); then + echo "No healthy China mirror reachable; keeping current mirrorlist" >&2 + exit 1 + fi + # Primary + up to two backups + cn_mirror_write_list china "${ranked[@]:0:3}" + ;; +official) + cn_mirror_write_list official geo worldwide + ;; +*) + url=$(cn_mirror_url "$target") + if [[ -z $url ]]; then + echo "Unknown mirror id: $target (see: omarchycn mirror benchmark)" >&2 + exit 1 + fi + cn_mirror_write_list "pin:$target" "$target" + ;; +esac diff --git a/bin/omarchy-cn-mirror-benchmark b/bin/omarchy-cn-mirror-benchmark new file mode 100755 index 00000000..97fdac47 --- /dev/null +++ b/bin/omarchy-cn-mirror-benchmark @@ -0,0 +1,27 @@ +#!/bin/bash +# omarchy:summary=Benchmark Arch mirrors for speed, latency, and sync freshness +# omarchy:examples=omarchycn mirror benchmark + +set -euo pipefail + +source "$OMARCHY_PATH/cn/lib/mirror.sh" + +printf '%-11s %-22s %12s %8s %10s\n' "ID" "NAME" "SPEED" "TTFB" "SYNC" + +for id in $(cn_mirror_ids); do + url=$(cn_mirror_url "$id") + read -r speed ttfb age < <(cn_mirror_probe "$url") + + if (( speed > 0 )); then + speed_h="$((speed / 1024)) KB/s" + else + speed_h="FAIL" + fi + case "$age" in + stale) sync_h="STALE" ;; + unknown) sync_h="?" ;; + *) sync_h="$((age / 60))m ago" ;; + esac + + printf '%-11s %-22s %12s %7ss %10s\n' "$id" "$(cn_mirror_name "$id")" "$speed_h" "${ttfb:0:5}" "$sync_h" +done diff --git a/bin/omarchy-cn-mirror-pin b/bin/omarchy-cn-mirror-pin new file mode 100755 index 00000000..1544b377 --- /dev/null +++ b/bin/omarchy-cn-mirror-pin @@ -0,0 +1,9 @@ +#!/bin/bash +# omarchy:summary=Pin pacman to a single mirror +# omarchy:args= +# omarchy:examples=omarchycn mirror pin tuna +# omarchy:requires-sudo=true + +set -euo pipefail + +exec omarchy-cn-mirror-apply "${1:?usage: omarchycn mirror pin }" diff --git a/bin/omarchy-cn-mirror-restore b/bin/omarchy-cn-mirror-restore new file mode 100755 index 00000000..0aa37a9c --- /dev/null +++ b/bin/omarchy-cn-mirror-restore @@ -0,0 +1,19 @@ +#!/bin/bash +# omarchy:summary=Restore the most recent mirrorlist backup +# omarchy:examples=omarchycn mirror restore +# omarchy:requires-sudo=true + +set -euo pipefail + +source "$OMARCHY_PATH/cn/lib/mirror.sh" + +backups=("$CN_MIRRORLIST".omarchycn-bak-*) +if [[ ! -e ${backups[0]} ]]; then + echo "No OmarchyCN mirrorlist backups found" >&2 + exit 1 +fi + +latest="${backups[-1]}" +sudo cp "$latest" "$CN_MIRRORLIST" +rm -f "$CN_PROFILE_FILE" +echo "Restored $CN_MIRRORLIST from ${latest##*/}" diff --git a/bin/omarchy-cn-mirror-status b/bin/omarchy-cn-mirror-status new file mode 100755 index 00000000..352d96cd --- /dev/null +++ b/bin/omarchy-cn-mirror-status @@ -0,0 +1,22 @@ +#!/bin/bash +# omarchy:summary=Show current pacman mirror profile and servers +# omarchy:examples=omarchycn mirror status + +set -euo pipefail + +source "$OMARCHY_PATH/cn/lib/mirror.sh" + +if [[ -f $CN_PROFILE_FILE ]]; then + echo "Profile: $(<"$CN_PROFILE_FILE")" +else + echo "Profile: not managed by OmarchyCN" +fi + +echo "Mirrorlist: $CN_MIRRORLIST" +echo "Servers:" +grep -E '^Server' "$CN_MIRRORLIST" | sed 's/^/ /' + +backups=("$CN_MIRRORLIST".omarchycn-bak-*) +if [[ -e ${backups[0]} ]]; then + echo "Backups: ${#backups[@]} (latest: ${backups[-1]##*/})" +fi diff --git a/cn/lib/mirror.sh b/cn/lib/mirror.sh new file mode 100644 index 00000000..2ce55c09 --- /dev/null +++ b/cn/lib/mirror.sh @@ -0,0 +1,77 @@ +# Sourced helpers for omarchy-cn-mirror-* commands + +CN_MIRRORS_JSON="$OMARCHY_PATH/cn/mirrors.json" +CN_MIRRORLIST="/etc/pacman.d/mirrorlist" +CN_PROFILE_FILE="$HOME/.config/omarchycn/mirror-profile" + +cn_mirror_ids() { + jq -r '.mirrors[].id' "$CN_MIRRORS_JSON" +} + +cn_mirror_url() { + jq -r --arg id "$1" '.mirrors[] | select(.id == $id) | .url' "$CN_MIRRORS_JSON" +} + +cn_mirror_name() { + jq -r --arg id "$1" '.mirrors[] | select(.id == $id) | .name' "$CN_MIRRORS_JSON" +} + +cn_mirror_ids_by_region() { + jq -r --arg r "$1" '.mirrors[] | select(.region == $r) | .id' "$CN_MIRRORS_JSON" +} + +# Probe one mirror: prints " " +cn_mirror_probe() { + local url="$1" + local speed ttfb lastsync age now + + read -r speed ttfb < <(curl -sS -o /dev/null -m 12 --connect-timeout 5 \ + -w '%{speed_download} %{time_starttransfer}\n' \ + "$url/core/os/x86_64/core.db" 2>/dev/null) || true + speed="${speed%%.*}" + [[ -z $speed ]] && speed=0 + + age="unknown" + lastsync=$(curl -sS -m 5 --connect-timeout 5 "$url/lastsync" 2>/dev/null | tr -cd '0-9') + if [[ -n $lastsync ]]; then + now=$(date +%s) + age=$((now - lastsync)) + (( age > 86400 )) && age="stale" + fi + + echo "$speed ${ttfb:-0} $age" +} + +# Rank ids by probe speed, excluding dead (speed 0) and stale mirrors +cn_mirror_rank() { + local id url speed ttfb age + + for id in "$@"; do + url=$(cn_mirror_url "$id") + read -r speed ttfb age < <(cn_mirror_probe "$url") + (( speed > 0 )) || continue + [[ $age == "stale" ]] && continue + echo "$speed $id" + done | sort -rn | awk '{print $2}' +} + +# Write mirrorlist from mirror ids (first = primary), with timestamped backup +cn_mirror_write_list() { + local profile="$1" + shift + local stamp id url content="" + + stamp=$(date +%Y%m%d-%H%M%S) + content="## OmarchyCN mirrorlist (profile: $profile, generated $stamp)\n" + for id in "$@"; do + url=$(cn_mirror_url "$id") + content+="## $id: $(cn_mirror_name "$id")\nServer = $url/\$repo/os/\$arch\n" + done + + sudo cp "$CN_MIRRORLIST" "$CN_MIRRORLIST.omarchycn-bak-$stamp" + printf '%b' "$content" | sudo tee "$CN_MIRRORLIST" > /dev/null + + mkdir -p "${CN_PROFILE_FILE%/*}" + echo "$profile" > "$CN_PROFILE_FILE" + echo "Mirrorlist written ($CN_MIRRORLIST), backup: $CN_MIRRORLIST.omarchycn-bak-$stamp" +} diff --git a/cn/mirrors.json b/cn/mirrors.json new file mode 100644 index 00000000..6edc3422 --- /dev/null +++ b/cn/mirrors.json @@ -0,0 +1,12 @@ +{ + "mirrors": [ + { "id": "tuna", "name": "清华大学 TUNA", "region": "cn", "url": "https://mirrors.tuna.tsinghua.edu.cn/archlinux" }, + { "id": "ustc", "name": "中国科学技术大学", "region": "cn", "url": "https://mirrors.ustc.edu.cn/archlinux" }, + { "id": "aliyun", "name": "阿里云", "region": "cn", "url": "https://mirrors.aliyun.com/archlinux" }, + { "id": "tencent", "name": "腾讯云", "region": "cn", "url": "https://mirrors.cloud.tencent.com/archlinux" }, + { "id": "netease", "name": "网易", "region": "cn", "url": "https://mirrors.163.com/archlinux" }, + { "id": "sjtu", "name": "上海交通大学", "region": "cn", "url": "https://mirror.sjtu.edu.cn/archlinux" }, + { "id": "geo", "name": "Arch 官方 Geo Mirror", "region": "global", "url": "https://geo.mirror.pkgbuild.com" }, + { "id": "worldwide", "name": "Arch 官方 Worldwide", "region": "global", "url": "https://mirror.rackspace.com/archlinux" } + ] +}