all One-shot VPS check
To check a new VPS in one go, run the command below. It downloads and checksum-verifies the xyz.sh sysinfo, bench, speedtest --overseas, route --cn and unlock scripts, runs them in order, and prints a summary. Speed tests are overseas only; the three-network back-route is still included. Nothing is uploaded unless you add --share.
$ curl -fsSL xyz.sh/all | bash
1676ecfb7444a94cc26f1f6a0300774c8a0571fd9f44957a6e5c3c807966dce8
· Matches the script curl receives. You can also fetch /all.sh.sha256
Every script passes bash -n and ShellCheck and was tested with curl | bash as root in the official Docker images of Debian 12/11, Ubuntu 24.04/22.04/20.04/18.04, Rocky 9, AlmaLinux 9 and CentOS 7 (see “Container test results” on each page). Containers lack systemd, kernel modules and real public routes, so those parts still need a real VPS.
What it does
Runs the published xyz.sh scripts in order: sysinfo, bench --local (256MiB dd), speedtest --overseas, route --cn and unlock. Speed tests cover the nearest node plus Hong Kong, Tokyo, Singapore and the US West; mainland China speed-test nodes are not used. The three-network back-route is unchanged. Each step is downloaded from xyz.sh and verified against /<name>.sh.sha256 before it runs; a failing step does not stop the rest, and a summary is printed at the end. Sharing is opt-in: only with --share (or answering y on a terminal; -y never implies sharing) are the summary and log uploaded to xyz.sh, with IP addresses and the hostname masked first. Results are kept for 30 days and a delete command is printed. Skip steps with --skip-bench, --skip-speed, --skip-route, --skip-unlock.
Container test results
Results of curl | bash as root without a terminal in each distribution's official Docker image. partial means the container could not verify everything (for example no systemd, no kernel module, or the test protocol blocked by the test environment's proxy) and a real VPS is needed to confirm. (2026-11-11)
| OS | Result |
|---|---|
| debian:12 | partial |
| debian:11 | partial |
| ubuntu:24.04 | partial |
| ubuntu:22.04 | partial |
| ubuntu:20.04 | partial |
| ubuntu:18.04 | partial |
| rockylinux:9 | partial |
| almalinux:9 | partial |
| centos:7 | partial |
FAQ
- Is this script safe?
- xyz.sh adds no telemetry and performs no hidden actions. Read the full source on the page or download it with curl -fsSL xyz.sh/<name>.sh first; every script has a /<name>.sh.sha256 checksum. Piping a remote script to bash is still a risk — review the source first. Sharing is opt-in: only --share, or answering y on a terminal, uploads anything; -y never implies it. IPv4 is masked to a.b.x.x and IPv6 and the hostname are hidden before upload; results are kept in xyz.sh's Cloudflare KV for 30 days on noindex pages.
- Which operating systems are supported?
- Declared support: Debian, Ubuntu, CentOS, Rocky Linux, and AlmaLinux. The script detects the distribution and warns on an unknown one. It was tested in Docker containers of these systems (syntax, --dry-run, and whatever can run in a container; see tests/RESULTS.md); parts that need systemd, kernel modules, or the public internet still need a real VPS.
- Do I need root?
- No. It runs as a normal user; as root some tools can be installed system-wide.
- How long does it take and how much traffic?
- Usually 5–15 minutes. Overseas speed tests and the disk test take longest; speed tests use a few hundred MB to a few GB. Skip parts with --skip-speed, --skip-bench and so on.
Full source
This is exactly what curl receives (common library + script body). Read it before running.
#!/usr/bin/env bash
# Served by xyz.sh/all
# Human-readable page: https://xyz.sh/all
# This file is the common library followed by the script body.
# No telemetry is added by xyz.sh.
# =============================================================================
# xyz.sh common library
# Shared helpers for every script served by xyz.sh.
#
# Promises:
# - No telemetry, analytics, or background "phone-home" calls
# - No hidden installs or config changes beyond what the script announces
# - Official upstream URLs are printed before any remote code is executed
# - Destructive or system-changing steps ask for confirmation (or YES=1 / -y)
# =============================================================================
if [[ -n "${XYZ_COMMON_LOADED:-}" ]]; then
# When sourced a second time, return. When executed as a file, return fails.
# shellcheck disable=SC2317
return 0 2>/dev/null || true
fi
# shellcheck disable=SC2034
XYZ_COMMON_LOADED=1
set -euo pipefail
XYZ_VERSION="1.3.0"
XYZ_BASE="${XYZ_BASE:-https://xyz.sh}"
XYZ_NAME="${XYZ_NAME:-unknown}"
# --- language ---------------------------------------------------------------
# Override with XYZ_LANG=zh or XYZ_LANG=en.
if [[ -z "${XYZ_LANG:-}" ]]; then
case "${LC_ALL:-${LC_MESSAGES:-${LANG:-}}}" in
zh*|ZH*|cmn*|CMN*) XYZ_LANG="zh" ;;
*) XYZ_LANG="en" ;;
esac
fi
xyz_t() {
# xyz_t "English text" "中文文本"
if [[ "${XYZ_LANG}" == "zh" ]]; then
printf '%s' "${2:-$1}"
else
printf '%s' "$1"
fi
}
# --- colors (TTY only) ------------------------------------------------------
# XYZ_COLOR=always|never|auto (default auto: TTY and no NO_COLOR); --no-color.
_xyz_color=0
case "${XYZ_COLOR:-auto}" in
always) _xyz_color=1 ;;
never) _xyz_color=0 ;;
*) [[ -t 1 && -z "${NO_COLOR:-}" ]] && _xyz_color=1 ;;
esac
[[ "${XYZ_NO_COLOR:-0}" == "1" ]] && _xyz_color=0
if [[ "${_xyz_color}" == "1" ]]; then
XYZ_C_RESET=$'\033[0m'
XYZ_C_BOLD=$'\033[1m'
XYZ_C_DIM=$'\033[2m'
XYZ_C_RED=$'\033[31m'
XYZ_C_GREEN=$'\033[32m'
XYZ_C_YELLOW=$'\033[33m'
XYZ_C_CYAN=$'\033[36m'
else
XYZ_C_RESET=""
XYZ_C_BOLD=""
XYZ_C_DIM=""
XYZ_C_RED=""
XYZ_C_GREEN=""
XYZ_C_YELLOW=""
XYZ_C_CYAN=""
fi
xyz_info() { printf '%s[INFO]%s %s\n' "${XYZ_C_GREEN}" "${XYZ_C_RESET}" "$*"; }
xyz_warn() { printf '%s[WARN]%s %s\n' "${XYZ_C_YELLOW}" "${XYZ_C_RESET}" "$*" >&2; }
xyz_error() { printf '%s[ERROR]%s %s\n' "${XYZ_C_RED}" "${XYZ_C_RESET}" "$*" >&2; }
xyz_step() { printf '%s==>%s %s\n' "${XYZ_C_CYAN}" "${XYZ_C_RESET}" "$*"; }
# --- args / init ------------------------------------------------------------
XYZ_ARGS=()
XYZ_YES=0
XYZ_DRY_RUN=0
xyz_parse_global_args() {
local filtered=()
local arg
for arg in "$@"; do
case "${arg}" in
-y|--yes)
XYZ_YES=1
;;
--lang=zh|zh)
XYZ_LANG="zh"
;;
--lang=en|en)
XYZ_LANG="en"
;;
--dry-run)
XYZ_DRY_RUN=1
;;
--no-color)
XYZ_NO_COLOR=1
XYZ_C_RESET=""; XYZ_C_BOLD=""; XYZ_C_DIM=""; XYZ_C_RED=""
XYZ_C_GREEN=""; XYZ_C_YELLOW=""; XYZ_C_CYAN=""
;;
--help|-h)
XYZ_HELP=1
filtered+=("${arg}")
;;
*)
filtered+=("${arg}")
;;
esac
done
XYZ_ARGS=("${filtered[@]+"${filtered[@]}"}")
if [[ "${YES:-}" =~ ^(1|yes|true|YES)$ || "${XYZ_ASSUME_YES:-}" == "1" ]]; then
XYZ_YES=1
fi
# Exported so individual scripts can read flags set here.
export XYZ_ARGS XYZ_YES XYZ_LANG XYZ_HELP="${XYZ_HELP:-}" XYZ_DRY_RUN="${XYZ_DRY_RUN:-0}"
export XYZ_NO_COLOR="${XYZ_NO_COLOR:-0}"
if declare -F xyz_ui_init >/dev/null; then xyz_ui_init; fi
}
xyz_banner() {
local title="${XYZ_TITLE:-$XYZ_NAME}"
printf '\n%s%sxyz.sh/%s%s v%s\n' "${XYZ_C_BOLD}" "${XYZ_C_CYAN}" "${XYZ_NAME}" "${XYZ_C_RESET}" "${XYZ_VERSION}"
printf '%s\n' "${XYZ_C_DIM}----------------------------------------${XYZ_C_RESET}"
printf ' %s: %s/%s\n' "$(xyz_t "Page" "页面")" "${XYZ_BASE}" "${XYZ_NAME}"
printf ' %s: %s/%s.sh\n' "$(xyz_t "Raw" "源码")" "${XYZ_BASE}" "${XYZ_NAME}"
if [[ -n "${XYZ_UPSTREAM:-}" ]]; then
printf ' %s: %s\n' "$(xyz_t "Upstream" "上游来源")" "${XYZ_UPSTREAM}"
fi
printf ' %s\n' "$(xyz_t "No telemetry, no hidden actions." "无遥测、无隐藏操作。")"
printf '%s\n\n' "${XYZ_C_DIM}----------------------------------------${XYZ_C_RESET}"
if [[ -n "${title}" && "${title}" != "${XYZ_NAME}" ]]; then
xyz_step "${title}"
fi
}
xyz_init() {
xyz_parse_global_args "$@"
xyz_detect_os
xyz_banner
}
# --- root / OS --------------------------------------------------------------
xyz_is_root() {
[[ "${EUID:-$(id -u)}" -eq 0 ]]
}
xyz_require_root() {
if ! xyz_is_root; then
xyz_error "$(xyz_t \
"This script must be run as root. Try: sudo bash" \
"请使用 root 运行。可尝试:sudo bash")"
exit 1
fi
}
xyz_prefer_root() {
if ! xyz_is_root; then
xyz_warn "$(xyz_t \
"Not running as root; some information or tests may be limited." \
"当前不是 root,部分信息或测试可能不完整。")"
fi
}
XYZ_OS_ID=""
XYZ_OS_LIKE=""
XYZ_OS_VERSION=""
XYZ_OS_FAMILY="" # debian | rhel | unknown
XYZ_OS_PRETTY=""
xyz_detect_os() {
XYZ_OS_ID=""
XYZ_OS_LIKE=""
XYZ_OS_VERSION=""
XYZ_OS_FAMILY="unknown"
XYZ_OS_PRETTY="unknown"
if [[ -r /etc/os-release ]]; then
# shellcheck disable=SC1091
. /etc/os-release
XYZ_OS_ID="${ID:-}"
XYZ_OS_LIKE="${ID_LIKE:-}"
XYZ_OS_VERSION="${VERSION_ID:-}"
XYZ_OS_PRETTY="${PRETTY_NAME:-$XYZ_OS_ID}"
export XYZ_OS_ID XYZ_OS_LIKE XYZ_OS_VERSION XYZ_OS_FAMILY XYZ_OS_PRETTY
fi
case "${XYZ_OS_ID}" in
debian|ubuntu)
XYZ_OS_FAMILY="debian"
;;
centos|rhel|rocky|almalinux|alma|fedora)
XYZ_OS_FAMILY="rhel"
;;
*)
case " ${XYZ_OS_ID} ${XYZ_OS_LIKE} " in
*debian*|*ubuntu*) XYZ_OS_FAMILY="debian" ;;
*rhel*|*centos*|*fedora*|*rocky*|*alma*) XYZ_OS_FAMILY="rhel" ;;
*) : ;;
esac
;;
esac
}
xyz_os_supported() {
case "${XYZ_OS_ID}" in
debian|ubuntu|centos|rocky|almalinux) return 0 ;;
*) : ;;
esac
case " ${XYZ_OS_LIKE} " in
*debian*|*ubuntu*|*rhel*|*centos*|*rocky*|*alma*) return 0 ;;
*) : ;;
esac
return 1
}
xyz_require_supported_os() {
xyz_detect_os
if ! xyz_os_supported; then
xyz_warn "$(xyz_t \
"Unrecognized distribution: ${XYZ_OS_ID:-unknown} (${XYZ_OS_PRETTY}). Officially supported: Debian, Ubuntu, CentOS, Rocky, AlmaLinux." \
"未能识别的发行版:${XYZ_OS_ID:-unknown}(${XYZ_OS_PRETTY})。官方支持:Debian、Ubuntu、CentOS、Rocky、AlmaLinux。")"
if ! xyz_confirm "$(xyz_t "Continue anyway?" "仍然继续?")"; then
exit 1
fi
else
xyz_info "$(xyz_t "Detected OS:" "检测到系统:") ${XYZ_OS_PRETTY} (${XYZ_OS_FAMILY})"
fi
}
# --- packages / commands ----------------------------------------------------
xyz_need_cmd() {
command -v "$1" >/dev/null 2>&1
}
xyz_pkg_install() {
xyz_detect_os
if (( $# == 0 )); then
return 0
fi
case "${XYZ_OS_FAMILY}" in
debian)
export DEBIAN_FRONTEND=noninteractive
apt-get update -y || xyz_warn "$(xyz_t "apt-get update reported errors (a repository may be unreachable); trying to install anyway." "apt-get update 报错(可能有软件源无法访问),仍尝试安装。")"
apt-get install -y --no-install-recommends "$@"
;;
rhel)
if xyz_need_cmd dnf; then
dnf install -y "$@"
elif xyz_need_cmd yum; then
yum install -y "$@"
else
xyz_error "$(xyz_t "Neither dnf nor yum is available." "找不到 dnf 或 yum。")"
exit 1
fi
;;
*)
xyz_error "$(xyz_t "Cannot install packages on this OS automatically." "无法在此系统上自动安装软件包。")"
exit 1
;;
esac
}
xyz_ensure_cmds() {
local pkg
local missing=()
for pkg in "$@"; do
if ! xyz_need_cmd "${pkg}"; then
missing+=("${pkg}")
fi
done
if ((${#missing[@]} > 0)); then
xyz_info "$(xyz_t "Installing missing commands:" "正在安装缺失命令:") ${missing[*]}"
xyz_pkg_install "${missing[@]}"
fi
}
# --- confirmation (safe with curl | bash) -----------------------------------
# A readable /dev/tty node is not enough: in containers or curl|bash without a
# controlling terminal, open() fails with ENXIO ("No such device or address").
# [[ -r /dev/tty ]] is true in that case and must not be used as the check.
xyz_open_tty() {
# Opens /dev/tty into XYZ_TTY_FD if it is a real terminal. Caller must close.
local fd
XYZ_TTY_FD=""
# Brace group so a failed open cannot leak "No such device or address".
if { exec {fd}<>/dev/tty; } 2>/dev/null && [[ -t "${fd}" ]]; then
XYZ_TTY_FD="${fd}"
return 0
fi
if [[ -n "${fd:-}" ]]; then
exec {fd}>&- 2>/dev/null || true
fi
return 1
}
xyz_close_tty() {
if [[ -n "${XYZ_TTY_FD:-}" ]]; then
exec {XYZ_TTY_FD}>&- 2>/dev/null || true
XYZ_TTY_FD=""
fi
}
xyz_confirm() {
local prompt="$1"
if [[ "${XYZ_YES}" == "1" ]]; then
xyz_info "$(xyz_t "Auto-confirmed (-y / YES=1):" "已自动确认(-y / YES=1):") ${prompt}"
return 0
fi
local reply=""
if xyz_open_tty; then
printf '%s [y/N] ' "${prompt}" >&"${XYZ_TTY_FD}"
# shellcheck disable=SC2162
read -r reply <&"${XYZ_TTY_FD}" || true
xyz_close_tty
else
xyz_warn "$(xyz_t \
"No TTY available for confirmation. Re-run with -y or YES=1 if you accept." \
"没有可用的终端来确认。若你已了解并同意,请加 -y 或 YES=1 重试。")"
return 1
fi
case "${reply}" in
y|Y|yes|YES|Yes) return 0 ;;
*) return 1 ;;
esac
}
# --- download helpers -------------------------------------------------------
# Create the temp dir in the main shell. xyz_tmpdir is usually called inside
# $(...), a subshell, so creating it (and its EXIT trap) there would delete the
# directory as soon as the subshell returned.
xyz_init_tmpdir() {
if [[ -z "${XYZ_TMPDIR:-}" || ! -d "${XYZ_TMPDIR}" ]]; then
XYZ_TMPDIR="$(mktemp -d "${TMPDIR:-/tmp}/xyzsh.XXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '${XYZ_TMPDIR}'" EXIT
fi
}
xyz_tmpdir() {
if [[ -z "${XYZ_TMPDIR:-}" || ! -d "${XYZ_TMPDIR}" ]]; then
xyz_error "internal: temp dir not initialised"
exit 1
fi
printf '%s' "${XYZ_TMPDIR}"
}
xyz_fetch() {
# xyz_fetch URL DEST
local url="$1"
local dest="$2"
xyz_info "$(xyz_t "Downloading" "正在下载"): ${url}"
xyz_info "$(xyz_t "Saving to" "保存到"): ${dest}"
if xyz_need_cmd curl; then
curl -fsSL --connect-timeout 20 --max-time 180 --retry 2 --retry-delay 1 \
-o "${dest}" "${url}"
elif xyz_need_cmd wget; then
wget -q --timeout=60 -O "${dest}" "${url}"
else
xyz_error "$(xyz_t "Need curl or wget to download files." "需要 curl 或 wget 才能下载文件。")"
exit 1
fi
if [[ ! -s "${dest}" ]]; then
xyz_error "$(xyz_t "Download failed or file is empty:" "下载失败或文件为空:") ${url}"
exit 1
fi
if xyz_need_cmd sha256sum; then
xyz_info "SHA256: $(sha256sum "${dest}" | awk '{print $1}')"
elif xyz_need_cmd shasum; then
xyz_info "SHA256: $(shasum -a 256 "${dest}" | awk '{print $1}')"
fi
}
xyz_run_upstream() {
# xyz_run_upstream URL [args...]
# Downloads an official installer, shows the URL and a short preview, then
# runs it only after confirmation (unless -y / YES=1).
local url="$1"
shift
local dest
xyz_init_tmpdir
dest="$(xyz_tmpdir)/upstream.sh"
xyz_fetch "${url}" "${dest}"
chmod +x "${dest}"
xyz_info "$(xyz_t "Preview (first 20 lines):" "预览(前 20 行):")"
sed -n '1,20p' "${dest}" || true
printf '%s\n' "${XYZ_C_DIM}...${XYZ_C_RESET}"
# Default --dry-run: verify the download and preview, do not execute.
# Scripts that forward --dry-run to upstream (e.g. docker) set
# XYZ_FORWARD_DRY_RUN=1 so the official script still runs with that flag.
if [[ "${XYZ_DRY_RUN:-0}" == "1" && "${XYZ_FORWARD_DRY_RUN:-0}" != "1" ]]; then
xyz_info "$(xyz_t \
"Dry-run: official script downloaded and previewed; not executing it." \
"演练模式:已下载并预览官方脚本,不会执行。")"
return 0
fi
if ! xyz_confirm "$(xyz_t \
"Run this official upstream script?" \
"运行这份官方上游脚本?")"; then
xyz_error "$(xyz_t "Aborted by user." "已取消。")"
exit 1
fi
xyz_step "$(xyz_t "Executing upstream script" "正在执行上游脚本")"
# Never let the upstream script inherit our stdin: under `curl | bash` that
# is the rest of this very script. Give it the terminal if there is one,
# otherwise /dev/null. Interactive installers set XYZ_UPSTREAM_NEEDS_TTY=1.
if xyz_open_tty; then
local tty_fd="${XYZ_TTY_FD}"
XYZ_TTY_FD=""
bash "${dest}" "$@" <&"${tty_fd}"
local rc=$?
exec {tty_fd}>&- 2>/dev/null || true
return "${rc}"
fi
if [[ "${XYZ_UPSTREAM_NEEDS_TTY:-0}" == "1" ]]; then
xyz_error "$(xyz_t \
"This official installer is interactive and needs a terminal. Log in with SSH and run: bash <(curl -fsSL ${XYZ_BASE}/${XYZ_NAME})" \
"这个官方安装脚本需要交互,必须在终端里运行。请用 SSH 登录后执行:bash <(curl -fsSL ${XYZ_BASE}/${XYZ_NAME})")"
exit 1
fi
bash "${dest}" "$@" </dev/null
}
# --- small shared helpers ----------------------------------------------------
xyz_strip_ansi() {
sed -e 's/\x1b\[[0-9;?]*[A-Za-z]//g' -e 's/\r//g'
}
# Effective SSH port: sshd -T (what sshd really uses) > config files > env > 22.
xyz_effective_ssh_port() {
local port=""
if xyz_need_cmd sshd; then
port="$(sshd -T 2>/dev/null | awk '$1=="port" {print $2; exit}' || true)"
fi
if [[ -z "${port}" && -d /etc/ssh/sshd_config.d ]]; then
port="$(cat /etc/ssh/sshd_config.d/*.conf 2>/dev/null | awk 'tolower($1)=="port" {print $2; exit}' || true)"
fi
if [[ -z "${port}" && -f /etc/ssh/sshd_config ]]; then
port="$(awk 'tolower($1)=="port" {print $2; exit}' /etc/ssh/sshd_config || true)"
fi
if [[ -z "${port}" && -n "${SSH_CONNECTION:-}" ]]; then
port="$(printf '%s' "${SSH_CONNECTION}" | awk '{print $4}')"
fi
printf '%s' "${port:-22}"
}
xyz_has_systemd() {
[[ -d /run/systemd/system ]] && xyz_need_cmd systemctl
}
# Back up a file once per run as FILE.xyz-bak-YYYYmmddHHMMSS and print the path.
xyz_backup_file() {
local f="$1"
local bak
bak="${f}.xyz-bak-$(date +%Y%m%d%H%M%S)"
if [[ -e "${f}" ]]; then
cp -a "${f}" "${bak}"
printf '%s' "${bak}"
fi
}
# --- misc -------------------------------------------------------------------
xyz_usage_exit() {
if [[ "${1:-}" != "" ]]; then
printf '%s\n' "$1"
fi
exit 0
}
# xyz.sh terminal report helpers: frames, colored badges, bar charts.
# Shared by speedtest-cn, route, unlock and all. Pure bash, no telemetry.
#
# Colors: on only when stdout is a terminal (or XYZ_COLOR=always). Off with
# --no-color, NO_COLOR=1 or XYZ_COLOR=never.
# Glyphs: Unicode box drawing when the locale is UTF-8, otherwise ASCII
# (force with XYZ_ASCII=1 or XYZ_UTF8=1). Frames are 78 columns wide.
# shellcheck disable=SC2034
XYZ_UI_LOADED=1
XYZ_UI_WIDTH=78
xyz_ui_init() {
local want="${XYZ_COLOR:-auto}"
[[ "${want}" == "auto" && -n "${NO_COLOR:-}" ]] && want="never"
[[ "${XYZ_NO_COLOR:-0}" == "1" ]] && want="never"
XYZ_UI_COLOR=0
case "${want}" in
always) XYZ_UI_COLOR=1 ;;
never) XYZ_UI_COLOR=0 ;;
*) [[ -t 1 ]] && XYZ_UI_COLOR=1 ;;
esac
if [[ "${XYZ_UI_COLOR}" == "1" ]]; then
U_X=$'\033[0m'; U_B=$'\033[1m'; U_D=$'\033[2m'
U_RED=$'\033[31m'; U_G=$'\033[32m'; U_Y=$'\033[33m'
U_BL=$'\033[34m'; U_M=$'\033[35m'; U_C=$'\033[36m'
else
U_X=""; U_B=""; U_D=""; U_RED=""; U_G=""; U_Y=""; U_BL=""; U_M=""; U_C=""
fi
XYZ_UI_UTF8=0
if [[ "${XYZ_ASCII:-0}" != "1" ]]; then
case "${LC_ALL:-${LC_CTYPE:-${LANG:-}}}" in
*[Uu][Tt][Ff]-8*|*[Uu][Tt][Ff]8*) XYZ_UI_UTF8=1 ;;
*) XYZ_UI_UTF8=0 ;;
esac
[[ "${XYZ_UTF8:-0}" == "1" ]] && XYZ_UI_UTF8=1
fi
if [[ "${XYZ_UI_UTF8}" == "1" ]]; then
G_H="─"; G_V="│"; G_TL="╭"; G_TR="╮"; G_BL="╰"; G_BR="╯"; G_LT="├"; G_RT="┤"
G_FULL="█"; G_EMPTY="░"; G_OK="✔"; G_BAD="✘"; G_UNK="?"; G_DOT="●"; G_STAR="★"
G_DOWN="↓"; G_UP="↑"; G_WARN="⚠"; G_MID="·"; G_TIMES="×"
else
G_H="-"; G_V="|"; G_TL="+"; G_TR="+"; G_BL="+"; G_BR="+"; G_LT="+"; G_RT="+"
G_FULL="#"; G_EMPTY="."; G_OK="+"; G_BAD="x"; G_UNK="?"; G_DOT="*"; G_STAR="*"
G_DOWN="v"; G_UP="^"; G_WARN="!"; G_MID="-"; G_TIMES="x"
fi
}
# Display width of a string: ANSI codes removed, CJK counted as 2 columns.
xyz_ui_w() {
local s="$1" pre rest
while [[ "${s}" == *$'\033['* ]]; do
pre="${s%%$'\033['*}"
rest="${s#*$'\033['}"
rest="${rest#*m}"
s="${pre}${rest}"
done
local LC_ALL=C
local a="${s//[^ -~]/}"
local one="${s//[^$'\xc2'-$'\xdf'$'\xe2']/}"
local two="${s//[^$'\xe3'-$'\xef']/}"
printf '%d' $(( ${#a} + ${#one} + 2 * ${#two} ))
}
xyz_ui_rep() {
# xyz_ui_rep CHAR N
local out="" n="$2"
while (( n > 0 )); do out="${out}$1"; n=$((n - 1)); done
printf '%s' "${out}"
}
xyz_ui_pad() {
# xyz_ui_pad TEXT WIDTH (left aligned, never truncates)
local w
w="$(xyz_ui_w "$1")"
printf '%s%s' "$1" "$(xyz_ui_rep ' ' $(( $2 - w )))"
}
xyz_ui_lpad() {
# xyz_ui_lpad TEXT WIDTH (right aligned)
local w
w="$(xyz_ui_w "$1")"
printf '%s%s' "$(xyz_ui_rep ' ' $(( $2 - w )))" "$1"
}
xyz_ui_top() {
local title=" $1 " w
w="$(xyz_ui_w "${title}")"
printf '%s%s%s%s%s%s%s\n' "${U_C}" "${G_TL}${G_H}" "${U_X}${U_B}" "${title}" "${U_X}${U_C}" \
"$(xyz_ui_rep "${G_H}" $(( XYZ_UI_WIDTH - 3 - w )))${G_TR}" "${U_X}"
}
xyz_ui_sep() {
local label="${1:-}" w=0
if [[ -n "${label}" ]]; then
label=" ${label} "
w="$(xyz_ui_w "${label}")"
printf '%s%s%s%s%s%s%s\n' "${U_C}" "${G_LT}${G_H}" "${U_X}${U_B}" "${label}" "${U_X}${U_C}" \
"$(xyz_ui_rep "${G_H}" $(( XYZ_UI_WIDTH - 3 - w )))${G_RT}" "${U_X}"
else
printf '%s%s%s\n' "${U_C}" "${G_LT}$(xyz_ui_rep "${G_H}" $(( XYZ_UI_WIDTH - 2 )))${G_RT}" "${U_X}"
fi
}
xyz_ui_row() {
local text="$1" inner=$(( XYZ_UI_WIDTH - 4 )) plain
if (( $(xyz_ui_w "${text}") > inner )); then
# too wide: drop colors and cut to fit, ending with "~"
plain="$(printf '%s' "${text}" | sed 's/\x1b\[[0-9;]*m//g')"
plain="${plain:0:inner}"
while (( $(xyz_ui_w "${plain}") > inner - 1 )); do plain="${plain%?}"; done
text="${plain}~"
fi
printf '%s%s%s %s %s%s%s\n' "${U_C}" "${G_V}" "${U_X}" "$(xyz_ui_pad "${text}" "${inner}")" "${U_C}" "${G_V}" "${U_X}"
}
xyz_ui_bottom() {
printf '%s%s%s\n' "${U_C}" "${G_BL}$(xyz_ui_rep "${G_H}" $(( XYZ_UI_WIDTH - 2 )))${G_BR}" "${U_X}"
}
xyz_ui_bar() {
# xyz_ui_bar VALUE MAX WIDTH [COLOR] -> filled/empty bar (at least 1 cell if VALUE > 0)
local n color="${4:-${U_G}}"
n="$(awk -v v="${1:-0}" -v m="${2:-0}" -v w="$3" 'BEGIN {
if (m <= 0 || v <= 0) { print 0; exit }
n = int(v / m * w + 0.5); if (n < 1) n = 1; if (n > w) n = w; print n }')"
printf '%s%s%s%s%s' "${color}" "$(xyz_ui_rep "${G_FULL}" "${n}")" "${U_D}" "$(xyz_ui_rep "${G_EMPTY}" $(( $3 - n )))" "${U_X}"
}
xyz_ui_usage() {
# xyz_ui_usage PERCENT WIDTH -> colored usage bar + percent (red > 90, yellow > 75)
local p="${1:-0}" color="${U_G}"
p="${p%%.*}"; [[ "${p}" =~ ^[0-9]+$ ]] || p=0
(( p > 75 )) && color="${U_Y}"
(( p > 90 )) && color="${U_RED}"
printf '%s %s%3d%%%s' "$(xyz_ui_bar "${p}" 100 "$2" "${color}")" "${color}" "${p}" "${U_X}"
}
xyz_ui_badge() {
# xyz_ui_badge yes|no|partial|unknown|ok|skip [TEXT]
local txt="${2:-}"
case "$1" in
yes|ok) printf '%s%s %s%s' "${U_G}" "${G_OK}" "${txt:-$(xyz_t "yes" "解锁")}" "${U_X}" ;;
no|fail) printf '%s%s %s%s' "${U_RED}" "${G_BAD}" "${txt:-$(xyz_t "no" "不可用")}" "${U_X}" ;;
partial) printf '%s%s %s%s' "${U_Y}" "${G_UNK}" "${txt:-$(xyz_t "partial" "部分")}" "${U_X}" ;;
skip) printf '%s%s %s%s' "${U_D}" "-" "${txt:-$(xyz_t "skipped" "跳过")}" "${U_X}" ;;
*) printf '%s%s %s%s' "${U_Y}" "${G_UNK}" "${txt:-$(xyz_t "unknown" "未知")}" "${U_X}" ;;
esac
}
xyz_ui_line_class() {
# premium | good | ordinary | other
case "$1" in
CN2-GIA|9929/CUII|9929|CMIN2) printf 'premium' ;;
CN2-GT) printf 'good' ;;
163|4837|CMI|CMNET) printf 'ordinary' ;;
*) printf 'other' ;;
esac
}
xyz_ui_linetags() {
# xyz_ui_linetags "CN2-GIA 9929/CUII" -> colored tags
local t out="" c
for t in $1; do
case "$(xyz_ui_line_class "${t}")" in
premium) c="${U_G}${U_B}" ;;
good) c="${U_C}" ;;
ordinary) c="${U_Y}" ;;
*) c="${U_D}" ;;
esac
out="${out}${out:+ }${c}${t}${U_X}"
done
printf '%s' "${out:-${U_D}?${U_X}}"
}
xyz_ui_fill() {
# Replace empty TSV fields with "-" (bash read collapses consecutive tabs).
awk -F'\t' -v OFS='\t' '{ for (i = 1; i <= NF; i++) if ($i == "") $i = "-"; print }' "$1"
}
xyz_ui_carrier() {
case "$1" in
nearest) xyz_t "Local" "本地" ;;
ct) xyz_t "Telecom" "电信" ;;
cu) xyz_t "Unicom" "联通" ;;
cm) xyz_t "Mobile" "移动" ;;
custom) xyz_t "Custom" "自定义" ;;
hk) xyz_t "HK" "香港" ;;
tokyo) xyz_t "Tokyo" "东京" ;;
singapore) xyz_t "SGP" "新加坡" ;;
us-west) xyz_t "US West" "美西" ;;
*) printf '%s' "$1" ;;
esac
}
xyz_ui_carrier_color() {
case "$1" in
ct|tokyo) printf '%s' "${U_BL}" ;;
cu) printf '%s' "${U_RED}" ;;
cm|singapore) printf '%s' "${U_G}" ;;
hk) printf '%s' "${U_Y}" ;;
*) printf '%s' "${U_M}" ;;
esac
}
xyz_ui_fmt_num() {
# 2 decimals below 1000, 1 decimal above
awk -v v="${1:-0}" 'BEGIN { if (v >= 1000) printf "%.1f", v; else printf "%.2f", v }'
}
# xyz_ui_speed_rows FILE
# FILE: carrier id label down up ping loss status [engine] [note] (TSV, Mbps / ms)
xyz_ui_speed_rows() {
local f="$1" max carrier id label down up lat loss status engine note head
[[ -s "${f}" ]] || return 0
max="$(awk -F'\t' '$8=="ok" { if ($4+0 > m) m = $4+0; if ($5+0 > m) m = $5+0 } END { print m+0 }' "${f}")"
while IFS=$'\t' read -r carrier id label down up lat loss status engine note; do
[[ "${engine}" == "-" ]] && engine=""
head="$(xyz_ui_carrier_color "${carrier}")$(xyz_ui_pad "$(xyz_ui_carrier "${carrier}")" 8)${U_X}${U_B}${label}${U_X}${U_D} #${id}${engine:+ ${G_MID} ${engine}}${U_X}"
xyz_ui_row "${head}"
if [[ "${status}" == "ok" ]]; then
local pc="${U_G}"
lat="${lat:-?}"
awk -v p="${lat}" 'BEGIN { exit !(p+0 > 150) }' 2>/dev/null && pc="${U_Y}"
awk -v p="${lat}" 'BEGIN { exit !(p+0 > 250) }' 2>/dev/null && pc="${U_RED}"
xyz_ui_row " ${G_DOWN} $(xyz_ui_bar "${down}" "${max}" 14 "${U_G}") $(xyz_ui_lpad "$(xyz_ui_fmt_num "${down}")" 7) Mbps ${G_UP} $(xyz_ui_bar "${up}" "${max}" 14 "${U_C}") $(xyz_ui_lpad "$(xyz_ui_fmt_num "${up}")" 7) Mbps ${pc}$(xyz_ui_lpad "${lat}" 6)ms${U_X}"
else
[[ "${note}" == "-" ]] && note=""
xyz_ui_row " $(xyz_ui_badge skip "$(xyz_t "skipped" "跳过")")${U_D}${note:+ ${G_MID} ${note:0:62}}${U_X}"
fi
done < <(xyz_ui_fill "${f}")
}
xyz_ui_speed_table() {
# xyz_ui_speed_table FILE TITLE
xyz_ui_top "$2"
xyz_ui_speed_rows "$1"
xyz_ui_bottom
}
# Route grid. FILE: target label kind hops [key] (key like bj-ct)
xyz_ui_route_key() {
case "$1" in
219.141.140.10) printf 'bj-ct' ;; 202.106.195.68) printf 'bj-cu' ;; 221.179.155.161) printf 'bj-cm' ;;
202.96.209.133) printf 'sh-ct' ;; 210.22.97.1) printf 'sh-cu' ;; 211.136.112.200) printf 'sh-cm' ;;
58.60.188.222) printf 'gz-ct' ;; 210.21.196.6) printf 'gz-cu' ;; 120.196.165.24) printf 'gz-cm' ;;
*) printf '' ;;
esac
}
xyz_ui_route_rows() {
local f="$1" target label kind hops key city c cell line extra=0
local -A K=()
[[ -s "${f}" ]] || return 0
while IFS=$'\t' read -r target label kind hops key; do
[[ -z "${key}" || "${key}" == "-" ]] && key="$(xyz_ui_route_key "${target}")"
if [[ -n "${key}" ]]; then
K["${key}"]="${kind}"
else
extra=1
fi
done < <(xyz_ui_fill "${f}")
if (( ${#K[@]} > 0 )); then
xyz_ui_row "${U_D}$(xyz_ui_pad "" 10)$(xyz_ui_pad "$(xyz_ui_carrier ct)" 21)$(xyz_ui_pad "$(xyz_ui_carrier cu)" 21)$(xyz_ui_carrier cm)${U_X}"
for city in bj sh gz; do
case "${city}" in
bj) line="$(xyz_ui_pad "$(xyz_t "Beijing" "北京")" 10)" ;;
sh) line="$(xyz_ui_pad "$(xyz_t "Shanghai" "上海")" 10)" ;;
*) line="$(xyz_ui_pad "$(xyz_t "Guangzhou" "广州")" 10)" ;;
esac
for c in ct cu cm; do
cell="${K[${city}-${c}]-}"
[[ "${cell}" == *" ("* || "${cell}" == unknown* || "${cell}" == 未识别* ]] && cell="?"
if [[ -z "${cell}" ]]; then cell="${U_D}-${U_X}"; else cell="$(xyz_ui_linetags "${cell}")"; fi
line="${line}$(xyz_ui_pad "${cell}" 21)"
done
xyz_ui_row "${line}"
done
fi
if [[ "${extra}" == "1" ]]; then
while IFS=$'\t' read -r target label kind hops key; do
[[ -z "${key}" || "${key}" == "-" ]] && key="$(xyz_ui_route_key "${target}")"
[[ -n "${key}" ]] && continue
xyz_ui_row "$(xyz_ui_pad "${label:-${target}}" 22) $(xyz_ui_linetags "${kind}")"
done < <(xyz_ui_fill "${f}")
fi
xyz_ui_row "${U_D}$(xyz_t "Legend:" "图例:")${U_X} ${U_G}${U_B}$(xyz_t "premium" "优质")${U_X} CN2-GIA/9929/CMIN2 ${U_C}CN2-GT${U_X} ${U_Y}$(xyz_t "ordinary" "普通")${U_X} 163/4837/CMI"
}
xyz_ui_route_score() {
# prints 0-100 (average best class per target) or nothing
local f="$1" kind t best n=0 sum=0 cls
[[ -s "${f}" ]] || return 0
while IFS=$'\t' read -r _ _ kind _ _; do
best=0
for t in ${kind}; do
cls="$(xyz_ui_line_class "${t}")"
case "${cls}" in premium) best=100 ;; good) (( best < 75 )) && best=75 ;; ordinary) (( best < 40 )) && best=40 ;; *) : ;; esac
done
n=$((n + 1)); sum=$((sum + best))
done < "${f}"
(( n > 0 )) && printf '%d' $(( sum / n ))
}
# Unlock grid. FILE: service status region note
xyz_ui_unlock_rows() {
local f="$1" svc st reg note cells=() i cell
[[ -s "${f}" ]] || return 0
while IFS=$'\t' read -r svc st reg note; do
cell="$(xyz_ui_badge "${st}" "$(xyz_ui_pad "${svc}" 16)")"
[[ "${reg}" != "-" ]] && cell="${cell} ${U_D}${reg:0:12}${U_X}"
cells+=("${cell}")
done < <(xyz_ui_fill "${f}")
for (( i = 0; i < ${#cells[@]}; i += 2 )); do
xyz_ui_row "$(xyz_ui_pad "${cells[$i]}" 37)${cells[$((i + 1))]-}"
done
}
xyz_ui_unlock_count() {
# prints "yes total"
awk -F'\t' '{ n++; if ($2 == "yes") y++ } END { printf "%d %d\n", y, n }' "$1"
}
xyz_ui_init
XYZ_NAME="all"
# xyz.sh/all — one-shot VPS check: system info, light bench, overseas
# speed tests (nearest + Hong Kong / Tokyo / Singapore / US West),
# back-route to CT/CU/CM, and streaming / AI unlock, followed by a summary.
#
# Each step is the published xyz.sh script, downloaded from xyz.sh and
# verified against its /<name>.sh.sha256 before it runs:
# sysinfo, bench --local, speedtest --overseas, route --cn, unlock
#
# Sharing is opt-in: with --share (or answering "y" at the final prompt on a
# terminal; -y does NOT imply sharing) the summary and log are uploaded to
# xyz.sh with IP addresses and the hostname masked. Results expire after 30
# days and can be deleted with the printed command.
#
# Usage:
# curl -fsSL xyz.sh/all | bash -s -- -y
# curl -fsSL xyz.sh/all | bash -s -- -y --share
# curl -fsSL xyz.sh/all | bash -s -- -y --skip-speed --skip-route
# curl -fsSL xyz.sh/all | bash -s -- --dry-run
# curl -fsSL xyz.sh/all | bash -s -- -y --no-color
#
# Supported: Debian, Ubuntu, CentOS, Rocky, AlmaLinux. Root recommended
# (installs the Ookla CLI and NextTrace after confirmation). No telemetry.
set -euo pipefail
if [[ -z "${XYZ_COMMON_LOADED:-}" ]]; then
_xyz_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [[ -f "${_xyz_dir}/lib/common.sh" ]]; then
# shellcheck source=lib/common.sh
source "${_xyz_dir}/lib/common.sh"
else
echo "xyz.sh: missing lib/common.sh" >&2
exit 1
fi
fi
if [[ -z "${XYZ_UI_LOADED:-}" ]]; then
_xyz_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib/report.sh
source "${_xyz_dir}/lib/report.sh"
fi
XYZ_NAME="all"
XYZ_TITLE="$(xyz_t "One-shot VPS check" "一键综合测试")"
xyz_init "$@"
SKIP_SYS=0; SKIP_BENCH=0; SKIP_SPEED=0; SKIP_CN=0; SKIP_ROUTE=0; SKIP_UNLOCK=0
SHARE="ask"
for arg in "${XYZ_ARGS[@]+"${XYZ_ARGS[@]}"}"; do
case "${arg}" in
--skip-sysinfo) SKIP_SYS=1 ;;
--skip-bench) SKIP_BENCH=1 ;;
--skip-speed) SKIP_SPEED=1 ;;
--skip-cn) SKIP_CN=1 ;;
--skip-route) SKIP_ROUTE=1 ;;
--skip-unlock) SKIP_UNLOCK=1 ;;
--share) SHARE="yes" ;;
--no-share) SHARE="no" ;;
--help|-h) XYZ_HELP=1 ;;
*)
xyz_error "$(xyz_t "Unknown argument:" "未知参数:") ${arg}"
exit 1
;;
esac
done
if [[ "${XYZ_HELP:-}" == "1" ]]; then
cat <<HINT
$(xyz_t "Usage:" "用法:")
curl -fsSL xyz.sh/all | bash -s -- -y
curl -fsSL xyz.sh/all | bash -s -- -y --share
curl -fsSL xyz.sh/all | bash -s -- -y --skip-speed --skip-route
curl -fsSL xyz.sh/all | bash -s -- --dry-run
$(xyz_t "Steps: sysinfo, bench --local (256MiB dd), speedtest --overseas, route --cn, unlock." "步骤:sysinfo、bench --local(256MiB dd)、speedtest --overseas、route --cn、unlock。")
$(xyz_t "Speed tests cover the nearest node plus Hong Kong, Tokyo, Singapore and US West. Mainland China speed-test nodes are not used; back-route (--cn) is unchanged." "测速覆盖就近节点以及香港、东京、新加坡、美西,不再测国内节点;三网回程路由不变。")
$(xyz_t "Skip steps with --skip-sysinfo --skip-bench --skip-speed --skip-route --skip-unlock." "可用 --skip-sysinfo --skip-bench --skip-speed --skip-route --skip-unlock 跳过步骤。")
$(xyz_t "--share uploads the masked report to xyz.sh (30 days). -y never implies sharing." "--share 把脱敏后的报告上传到 xyz.sh(保留 30 天)。-y 不会自动分享。")
$(xyz_t "--no-color (or NO_COLOR=1) prints the dashboard without colors; non-UTF-8 terminals get ASCII frames." "--no-color(或 NO_COLOR=1)关闭颜色;非 UTF-8 终端自动改用 ASCII 边框。")
HINT
exit 0
fi
xyz_require_supported_os
xyz_prefer_root
xyz_init_tmpdir
TMP="$(xyz_tmpdir)"
LOG="${TMP}/all.log"
export XYZ_RESULT_FILE="${TMP}/results.tsv"
: > "${LOG}"
: > "${XYZ_RESULT_FILE}"
sha_of() {
if xyz_need_cmd sha256sum; then sha256sum "$1" | awk '{print $1}'
else shasum -a 256 "$1" | awk '{print $1}'; fi
}
# fetch_script NAME -> path of a verified payload
fetch_script() {
local name="$1" dest="${TMP}/${1}.sh" want got
curl -fsSL --connect-timeout 15 --max-time 60 -A "curl xyz.sh/all" -o "${dest}" "${XYZ_BASE}/${name}.sh"
want="$(curl -fsSL --connect-timeout 15 --max-time 30 -A "curl xyz.sh/all" "${XYZ_BASE}/${name}.sh.sha256" | awk '{print $1}')"
got="$(sha_of "${dest}")"
if [[ -z "${want}" || "${want}" != "${got}" ]]; then
xyz_error "$(xyz_t "Checksum mismatch for" "校验失败:") ${name}.sh (want ${want:-?}, got ${got})"
return 1
fi
xyz_info "$(xyz_t "Verified" "已校验") ${XYZ_BASE}/${name}.sh sha256=${got}" >&2
printf '%s' "${dest}"
}
STEPS=()
[[ "${SKIP_SYS}" == "0" ]] && STEPS+=("sysinfo|")
[[ "${SKIP_BENCH}" == "0" ]] && STEPS+=("bench|--local")
# --skip-cn is accepted for compatibility with 1.1/1.2 command lines but
# is ignored: all no longer runs a mainland China speed test.
if [[ "${SKIP_CN}" == "1" ]]; then
xyz_info "$(xyz_t "--skip-cn is ignored in 1.3.0: all no longer runs a mainland China speed test." "1.3.0 起 --skip-cn 已无效果:all 不再进行国内测速。")"
fi
if [[ "${SKIP_SPEED}" == "0" ]]; then
STEPS+=("speedtest|--overseas")
fi
[[ "${SKIP_ROUTE}" == "0" ]] && STEPS+=("route|--cn")
[[ "${SKIP_UNLOCK}" == "0" ]] && STEPS+=("unlock|")
xyz_step "$(xyz_t "Plan" "计划")"
for s in "${STEPS[@]+"${STEPS[@]}"}"; do
printf ' - %s/%s %s\n' "${XYZ_BASE}" "${s%%|*}" "${s#*|}"
done
# XYZ_ALL_REPLAY=DIR re-renders a saved run (DIR/results.tsv + DIR/all.log)
# without running any step; used for tests and screenshots.
REPLAY="${XYZ_ALL_REPLAY:-}"
if [[ -n "${REPLAY}" ]]; then
cp "${REPLAY}/results.tsv" "${XYZ_RESULT_FILE}"
cp "${REPLAY}/all.log" "${LOG}"
STEPS=()
fi
declare -A PATHS=()
for s in "${STEPS[@]+"${STEPS[@]}"}"; do
n="${s%%|*}"
if p="$(fetch_script "${n}")"; then
PATHS["${n}"]="${p}"
else
exit 1
fi
done
if [[ "${XYZ_DRY_RUN:-0}" == "1" ]]; then
xyz_info "$(xyz_t \
"Dry-run: every step was downloaded and its SHA256 verified; nothing was run." \
"演练模式:已下载所有步骤并校验 SHA256,未运行任何测试。")"
exit 0
fi
STATUS=()
if [[ -n "${REPLAY}" ]]; then
while IFS=$'\t' read -r _ n rc; do STATUS+=("${n}|${rc}"); done < <(awk -F'\t' '$1=="step"' "${XYZ_RESULT_FILE}")
fi
CHILD_ARGS=()
# Children write through tee (not a TTY): pass our color decision down.
if [[ "${XYZ_UI_COLOR}" == "1" ]]; then export XYZ_COLOR=always; else export XYZ_COLOR=never; fi
[[ "${XYZ_NO_COLOR:-0}" == "1" ]] && CHILD_ARGS+=(--no-color)
[[ "${XYZ_YES}" == "1" ]] && CHILD_ARGS+=(-y)
CHILD_ARGS+=("--lang=${XYZ_LANG}")
for s in "${STEPS[@]+"${STEPS[@]}"}"; do
n="${s%%|*}"
read -r -a extra <<< "${s#*|}"
echo | tee -a "${LOG}"
xyz_step "[$(( ${#STATUS[@]} + 1 ))/${#STEPS[@]}] ${n} ${s#*|}" | tee -a "${LOG}"
set +e
XYZ_DD_MB=256 bash "${PATHS[${n}]}" "${CHILD_ARGS[@]}" "${extra[@]+"${extra[@]}"}" </dev/null 2>&1 | tee -a "${LOG}"
rc="${PIPESTATUS[0]}"
set -e
STATUS+=("${n}|${rc}")
done
# ---- facts ---------------------------------------------------------------------
fact() { printf 'sys\t%s\t%s\n' "$1" "$2" >> "${XYZ_RESULT_FILE}"; }
if [[ -z "${REPLAY}" ]]; then
fact date "$(date -u '+%Y-%m-%d %H:%M UTC')"
fact os "${XYZ_OS_PRETTY}"
fact kernel "$(uname -r)"
fact arch "$(uname -m)"
fact virt "$( (systemd-detect-virt 2>/dev/null || true) | head -1)"
fact cpu "$(awk -F: '/model name/ {gsub(/^ +/,"",$2); print $2; exit}' /proc/cpuinfo 2>/dev/null)"
fact cores "$(nproc 2>/dev/null || echo 1)"
fact mem_total_mb "$(awk '/MemTotal/ {printf "%d", $2/1024}' /proc/meminfo)"
fact mem_avail_mb "$(awk '/MemAvailable/ {printf "%d", $2/1024}' /proc/meminfo)"
fact swap_total_mb "$(awk '/SwapTotal/ {printf "%d", $2/1024}' /proc/meminfo)"
fact swap_free_mb "$(awk '/SwapFree/ {printf "%d", $2/1024}' /proc/meminfo)"
read -r _dk_total _dk_used _dk_avail _dk_pct < <(df -Pk / 2>/dev/null | awk 'NR==2 {gsub("%","",$5); print $2, $3, $4, $5}') || true
fact disk_total_kb "${_dk_total:-0}"
fact disk_used_kb "${_dk_used:-0}"
fact disk_avail_kb "${_dk_avail:-0}"
fact disk_pct "${_dk_pct:-0}"
fact dd_write "$(grep -E 'copied' "${LOG}" | head -1 | awk -F', ' '{print $NF}')"
fact dd_read "$(grep -E 'copied' "${LOG}" | sed -n 2p | awk -F', ' '{print $NF}')"
fact cpu_loop_s "$(grep -E 'awk loop wall time' "${LOG}" | head -1 | awk '{print $(NF-1)}')"
for st in "${STATUS[@]}"; do
printf 'step\t%s\t%s\n' "${st%%|*}" "${st#*|}" >> "${XYZ_RESULT_FILE}"
done
fi
sysv() { awk -F'\t' -v k="$1" '$1=="sys" && $2==k { print $3; exit }' "${XYZ_RESULT_FILE}"; }
human_kb() { awk -v k="${1:-0}" 'BEGIN { if (k >= 1048576) printf "%.1fG", k/1048576; else printf "%.0fM", k/1024 }'; }
SPEED_F="${TMP}/v-speed.tsv"; ROUTE_F="${TMP}/v-route.tsv"; UNLOCK_F="${TMP}/v-unlock.tsv"
awk -F'\t' -v OFS='\t' '$1=="speedtest" || $1=="speedtest-cn" { $1=""; sub(/^\t/, ""); print }' "${XYZ_RESULT_FILE}" > "${SPEED_F}"
awk -F'\t' -v OFS='\t' '$1=="route" { $1=""; sub(/^\t/, ""); print }' "${XYZ_RESULT_FILE}" > "${ROUTE_F}"
awk -F'\t' -v OFS='\t' '$1=="unlock" { $1=""; sub(/^\t/, ""); print }' "${XYZ_RESULT_FILE}" > "${UNLOCK_F}"
# ---- score (same formula as the xyz.sh/r report page) ----------------------------
# route 40%, speed 30%, unlock 20%, system 10%; missing parts are left out.
S_ROUTE="$(xyz_ui_route_score "${ROUTE_F}")"
# Same formula as src/report.ts computeScore: China rows (legacy reports) use
# down/3; overseas rows use down/10 for nearest and down/5 for regional.
S_SPEED="$(awk -F'\t' '
function cap(v, d) { s = v/d; return s > 100 ? 100 : s }
$8=="ok" && $1 ~ /^(ct|cu|cm)$/ { cn += cap($4, 3); ncn++ }
$8=="ok" && $1 !~ /^(ct|cu|cm)$/ { ov += cap($4, ($1=="nearest" ? 10 : 5)); nov++ }
$8=="ok" && $1=="nearest" { near = cap($4, 10); hn = 1 }
END { if (ncn) printf "%d", cn/ncn; else if (nov) printf "%d", ov/nov; else if (hn) printf "%d", near }
' "${SPEED_F}")"
S_UNLOCK="$(awk -F'\t' '{ n++; if ($2=="yes") y += 1; else if ($2=="partial") y += 0.5 }
END { if (n) printf "%d", y * 100 / n }' "${UNLOCK_F}")"
S_SYS="$(awk -v d="$(sysv disk_pct)" -v ma="$(sysv mem_avail_mb)" -v mt="$(sysv mem_total_mb)" 'BEGIN {
s = 100; if (d > 95) s = 40; else if (d > 90) s = 60; else if (d > 80) s = 85
if (mt > 0 && ma / mt < 0.10) s -= 20; if (s < 0) s = 0; printf "%d", s }')"
SCORE="$(awk -v r="${S_ROUTE}" -v sp="${S_SPEED}" -v u="${S_UNLOCK}" -v sy="${S_SYS}" 'BEGIN {
t = 0; w = 0
if (r != "") { t += r * 40; w += 40 }
if (sp != "") { t += sp * 30; w += 30 }
if (u != "") { t += u * 20; w += 20 }
if (sy != "") { t += sy * 10; w += 10 }
if (w) printf "%d", t / w + 0.5 }')"
sed -i '/^sys\tscore/d' "${XYZ_RESULT_FILE}"
printf 'sys\tscore\t%s\nsys\tscore_parts\troute=%s speed=%s unlock=%s system=%s\n' \
"${SCORE}" "${S_ROUTE}" "${S_SPEED}" "${S_UNLOCK}" "${S_SYS}" >> "${XYZ_RESULT_FILE}"
verdict() {
local s="${1:-0}"
if (( s >= 85 )); then printf '%s%s %s%s' "${U_G}${U_B}" "${G_STAR}" "$(xyz_t "Excellent" "优秀")" "${U_X}"
elif (( s >= 70 )); then printf '%s%s%s' "${U_G}" "$(xyz_t "Good" "良好")" "${U_X}"
elif (( s >= 50 )); then printf '%s%s%s' "${U_Y}" "$(xyz_t "Fair" "一般")" "${U_X}"
else printf '%s%s%s' "${U_RED}" "$(xyz_t "Poor" "较差")" "${U_X}"; fi
}
dashboard() {
local mt ma pct dk_pct note line st yes total prem n
xyz_ui_top "xyz.sh/all ${G_MID} $(xyz_t "VPS report" "VPS 测试报告") ${G_MID} $(sysv date)"
if [[ -n "${SCORE}" ]]; then
local sc="${U_G}"; (( SCORE < 70 )) && sc="${U_Y}"; (( SCORE < 50 )) && sc="${U_RED}"
xyz_ui_row "${U_B}$(xyz_ui_pad "$(xyz_t "Score" "综合评分")" 10)${U_X}${sc}${U_B}$(xyz_ui_lpad "${SCORE}" 3)${U_X}/100 $(xyz_ui_bar "${SCORE}" 100 30 "${sc}") $(verdict "${SCORE}")"
xyz_ui_row "${U_D}$(xyz_ui_pad "" 10)$(xyz_t "route" "线路") ${S_ROUTE:--} ${G_MID} $(xyz_t "speed" "速度") ${S_SPEED:--} ${G_MID} $(xyz_t "unlock" "解锁") ${S_UNLOCK:--} ${G_MID} $(xyz_t "system" "系统") ${S_SYS:--}${U_X}"
fi
xyz_ui_sep "$(xyz_t "System" "系统")"
xyz_ui_row "$(xyz_ui_pad "OS" 10)$(sysv os) ${G_MID} $(sysv kernel) ${G_MID} $(sysv arch)$( [[ -n "$(sysv virt)" ]] && printf ' %s %s' "${G_MID}" "$(sysv virt)")"
xyz_ui_row "$(xyz_ui_pad "CPU" 10)$(sysv cpu | cut -c1-48) ${G_TIMES} $(sysv cores)$( [[ -n "$(sysv cpu_loop_s)" ]] && printf ' %s(loop %ss)%s' "${U_D}" "$(sysv cpu_loop_s)" "${U_X}")"
mt="$(sysv mem_total_mb)"; ma="$(sysv mem_avail_mb)"
if [[ -n "${mt}" && "${mt}" -gt 0 ]]; then
pct=$(( (mt - ma) * 100 / mt ))
xyz_ui_row "$(xyz_ui_pad "$(xyz_t "Memory" "内存")" 10)$(xyz_ui_usage "${pct}" 24) $(awk -v a="${ma}" -v t="${mt}" -v w="$(xyz_t "GiB free" "GiB 可用")" 'BEGIN { printf "%.1f / %.1f %s", a/1024, t/1024, w }')"
fi
dk_pct="$(sysv disk_pct)"
note=""
(( ${dk_pct:-0} > 90 )) && note=" ${U_RED}${U_B}${G_WARN} $(xyz_t "almost full" "空间不足")${U_X}"
xyz_ui_row "$(xyz_ui_pad "$(xyz_t "Disk /" "磁盘 /")" 10)$(xyz_ui_usage "${dk_pct:-0}" 24) $(human_kb "$(sysv disk_avail_kb)") $(xyz_t "free of" "可用 / 共") $(human_kb "$(sysv disk_total_kb)")${note}"
if [[ -n "$(sysv dd_write)" ]]; then
xyz_ui_row "$(xyz_ui_pad "$(xyz_t "Disk I/O" "磁盘 I/O")" 10)dd 256MiB $(xyz_t "write" "写") ${U_B}$(sysv dd_write)${U_X} ${G_MID} $(xyz_t "read" "读") ${U_B}$(sysv dd_read)${U_X}"
fi
if [[ -s "${SPEED_F}" ]]; then
xyz_ui_sep "$(xyz_t "Speed (Mbps, ping ms)" "测速(Mbps,延迟 ms)")"
xyz_ui_speed_rows "${SPEED_F}"
fi
if [[ -s "${ROUTE_F}" ]]; then
xyz_ui_sep "$(xyz_t "Back-route (heuristic)" "回程线路(推断)")"
xyz_ui_route_rows "${ROUTE_F}"
fi
if [[ -s "${UNLOCK_F}" ]]; then
read -r yes total < <(xyz_ui_unlock_count "${UNLOCK_F}")
xyz_ui_sep "$(xyz_t "Unlock" "解锁") ${yes}/${total}"
xyz_ui_unlock_rows "${UNLOCK_F}"
line="$(awk -F'\t' -v m="${G_MID}" '$1=="ip" { printf "%s %s %s %s %s %s", $2, m, $3, m, $4, $5; exit }' "${XYZ_RESULT_FILE}")"
[[ -n "${line}" ]] && xyz_ui_row "${U_D}IP${U_X} ${line}"
fi
xyz_ui_sep "$(xyz_t "Verdict" "结论")"
if [[ -s "${ROUTE_F}" ]]; then
n="$(wc -l < "${ROUTE_F}" | tr -d ' ')"
prem="$(awk -F'\t' '$3 ~ /CN2-GIA|9929|CMIN2/' "${ROUTE_F}" | wc -l | tr -d ' ')"
if (( prem * 3 >= n * 2 )); then
xyz_ui_row "$(xyz_ui_badge ok "$(xyz_t "Premium back-route on" "优质回程:") ${prem}/${n} $(xyz_t "targets" "个目标")") $(xyz_ui_linetags "$(cut -f3 "${ROUTE_F}" | tr ' ' '\n' | grep -E '^(CN2-GIA|CN2-GT|9929/CUII|CMIN2)$' | sort -u | tr '\n' ' ')")"
elif (( prem > 0 )); then
xyz_ui_row "$(xyz_ui_badge partial "$(xyz_t "Mixed back-route: premium on" "回程混合:优质") ${prem}/${n}")"
else
xyz_ui_row "$(xyz_ui_badge no "$(xyz_t "Ordinary back-route (no CN2 GIA / 9929 / CMIN2 seen)" "普通回程(未见 CN2 GIA / 9929 / CMIN2)")")"
fi
fi
if [[ -s "${SPEED_F}" ]]; then
ncn="$(awk -F'\t' '$1 ~ /^(ct|cu|cm)$/' "${SPEED_F}" | wc -l | tr -d ' ')"
ncn_ok="$(awk -F'\t' '$8=="ok" && $1 ~ /^(ct|cu|cm)$/' "${SPEED_F}" | wc -l | tr -d ' ')"
nov_ok="$(awk -F'\t' '$8=="ok" && $1 !~ /^(ct|cu|cm)$/' "${SPEED_F}" | wc -l | tr -d ' ')"
nov="$(awk -F'\t' '$1 !~ /^(ct|cu|cm)$/' "${SPEED_F}" | wc -l | tr -d ' ')"
if (( ncn > 0 )); then
if (( ncn_ok > 0 )); then
xyz_ui_row "$(xyz_ui_badge ok "$(xyz_t "China speed test:" "国内测速:") ${ncn_ok} $(xyz_t "carrier(s) measured" "个运营商有结果")")"
else
xyz_ui_row "$(xyz_ui_badge no "$(xyz_t "No China node finished a speed test (see warnings above)" "国内节点均未完成测速(见上方警告)")")"
fi
elif (( nov_ok > 0 )); then
xyz_ui_row "$(xyz_ui_badge ok "$(xyz_t "Overseas speed test:" "境外测速:") ${nov_ok} $(xyz_t "node(s) measured" "个节点有结果")")"
elif (( nov > 0 )); then
xyz_ui_row "$(xyz_ui_badge no "$(xyz_t "No overseas node finished a speed test (see warnings above)" "境外节点均未完成测速(见上方警告)")")"
fi
fi
if [[ -s "${UNLOCK_F}" ]]; then
if (( yes == total )); then xyz_ui_row "$(xyz_ui_badge ok "$(xyz_t "Unlock" "解锁") ${yes}/${total}")"
else xyz_ui_row "$(xyz_ui_badge partial "$(xyz_t "Unlock" "解锁") ${yes}/${total}: $(awk -F'\t' '$2!="yes" { printf "%s%s", sep, $1; sep=", " }' "${UNLOCK_F}") $(xyz_t "not confirmed" "未确认")")"; fi
fi
if (( ${dk_pct:-0} > 90 )); then
xyz_ui_row "$(xyz_ui_badge no "$(xyz_t "Disk / is" "磁盘 / 已用") ${dk_pct}%$(xyz_t " full, free space before installing anything" ",安装软件前请先清理空间")")"
fi
line=""
for st in "${STATUS[@]+"${STATUS[@]}"}"; do
if [[ "${st#*|}" == "0" ]]; then line="${line}${U_G}${G_OK}${U_X} ${st%%|*} "
else line="${line}${U_RED}${G_BAD}${U_X} ${st%%|*}(${st#*|}) "; fi
done
xyz_ui_row "${U_D}$(xyz_t "Steps" "步骤")${U_X} ${line}"
xyz_ui_bottom
}
SUMMARY="${TMP}/summary.txt"
( XYZ_COLOR=never; XYZ_NO_COLOR=1; xyz_ui_init; dashboard ) > "${SUMMARY}"
echo
dashboard
echo
# ---- optional share ---------------------------------------------------------------
want_share=0
if [[ "${SHARE}" == "yes" ]]; then
want_share=1
elif [[ "${SHARE}" == "ask" ]] && xyz_open_tty; then
printf '%s [y/N] ' "$(xyz_t "Upload this report (IPs and hostname masked) to get a share link?" "上传本报告(IP 与主机名已脱敏)生成分享链接?")" >&"${XYZ_TTY_FD}"
reply=""
read -r reply <&"${XYZ_TTY_FD}" || true
xyz_close_tty
[[ "${reply}" =~ ^(y|Y|yes|YES)$ ]] && want_share=1
fi
if [[ "${want_share}" == "1" ]]; then
REPORT="${TMP}/report.txt"
host="$(hostname 2>/dev/null || true)"
cc="$(awk -F'\t' '$1=="ip" {print $2; exit}' "${XYZ_RESULT_FILE}")"
[[ "${cc}" =~ ^[A-Z]{2}$ ]] || cc=""
routes="$(awk -F'\t' '$1=="route" {print $4}' "${XYZ_RESULT_FILE}" | tr ' ' '\n' | grep -E '^(CN2-GIA|CN2-GT|163|9929/CUII|4837|CMIN2|CMI)$' | sort -u | tr '\n' ',' | sed 's/,$//' || true)"
{
printf 'xyz-meta: version=%s country=%s routes=%s score=%s\n\n' "${XYZ_VERSION}" "${cc}" "${routes}" "${SCORE}"
printf '==== xyz-data v1 ====\n'
cat "${XYZ_RESULT_FILE}"
printf '==== end xyz-data ====\n\n'
cat "${SUMMARY}"
printf '\n==== log ====\n'
cat "${LOG}"
} | xyz_strip_ansi \
| sed -E 's/\b([0-9]{1,3}\.[0-9]{1,3})\.[0-9]{1,3}\.[0-9]{1,3}\b/\1.x.x/g' \
| sed -E 's/\b([0-9a-fA-F]{1,4}:[0-9a-fA-F]{1,4}):[0-9a-fA-F:]{2,}\b/\1:x:x/g' \
> "${REPORT}.tmp"
if [[ -n "${host}" && "${#host}" -ge 3 ]]; then
sed "s/${host//./\\.}/<host>/g" "${REPORT}.tmp" > "${REPORT}"
else
mv "${REPORT}.tmp" "${REPORT}"
fi
# Server limit is 64 KiB; keep the summary and the head of the log.
head -c 60000 "${REPORT}" > "${REPORT}.cut"
xyz_info "$(xyz_t "Uploading" "正在上传") $(wc -c < "${REPORT}.cut") bytes -> ${XYZ_BASE}/api/r"
resp="$(curl -fsS --max-time 30 -X POST -H 'content-type: text/plain; charset=utf-8' \
--data-binary @"${REPORT}.cut" "${XYZ_BASE}/api/r" 2>&1 || true)"
url="$(printf '%s' "${resp}" | grep -o '"url":"[^"]*"' | cut -d'"' -f4)"
del="$(printf '%s' "${resp}" | grep -o '"delete":"[^"]*"' | cut -d'"' -f4)"
if [[ -n "${url}" ]]; then
xyz_info "$(xyz_t "Share link (30 days):" "分享链接(30 天有效):") ${url}"
[[ -n "${del}" ]] && xyz_info "$(xyz_t "Delete it any time:" "随时删除:") curl -X DELETE '${del}'"
else
xyz_warn "$(xyz_t "Upload failed:" "上传失败:") ${resp:0:200}"
fi
else
xyz_info "$(xyz_t "Not shared. Add --share to upload a masked copy." "未分享。加 --share 可上传脱敏报告。")"
fi
xyz_info "$(xyz_t "All steps finished." "全部步骤完成。")"
Recommended VPS
These slots are placeholders. No vendor or affiliate link has been added, and nothing here is a purchase button. Real plans and compliant referral links will be filled in later.
Suggested for:United States
Vendor TBD
Region, specs, and price will be filled in after onboarding. This is not a checkout link.
Vendor TBD
Region, specs, and price will be filled in after onboarding. This is not a checkout link.
Vendor TBD
Region, specs, and price will be filled in after onboarding. This is not a checkout link.