VPS 一键脚本

all 一键综合测试

要一次性检查一台新 VPS,运行下面的命令。它会依次下载并校验 xyz.sh 上的 sysinfo、bench、speedtest --overseas、route --cn 和 unlock 脚本再运行,最后打印汇总。测速只测境外节点,国内三网回程路由仍会测。默认不上传任何东西;加 --share 才会生成分享链接。

命令
$ curl -fsSL xyz.sh/all | bash
最后更新:
声明支持的系统:Debian, Ubuntu, CentOS, Rocky, AlmaLinux
root 可选
SHA256:1676ecfb7444a94cc26f1f6a0300774c8a0571fd9f44957a6e5c3c807966dce8 · 与 curl 拿到的脚本一致,也可访问 /all.sh.sha256
阅读教程:VPS 一键综合测试教程(含分享结果)

所有脚本都通过 bash -n 与 ShellCheck 检查,并在 Debian 12/11、Ubuntu 24.04/22.04/20.04/18.04、Rocky 9、AlmaLinux 9、CentOS 7 的官方 Docker 镜像中以 root 运行 curl | bash 测试(结果见各页“容器测试结果”)。容器里没有 systemd、内核模块和真实公网线路,相关部分需要在真实 VPS 上确认。

它会做什么

一条命令依次运行 xyz.sh 上已发布的 sysinfo、bench --local(256MiB dd)、speedtest --overseas、route --cn 和 unlock。测速覆盖就近节点以及香港、东京、新加坡、美西,不再测试中国大陆测速节点;三网回程路由仍然保留。每一步都从 xyz.sh 下载并先用 /<name>.sh.sha256 校验再运行,任何一步失败都不会中断后面的步骤,最后输出一份汇总。分享是可选的:只有加 --share(或在终端里回答 y;-y 不会自动分享)才会把汇总和日志上传到 xyz.sh,上传前会把 IP 地址与主机名脱敏,结果保存 30 天,并给出删除命令。可用 --skip-bench、--skip-speed、--skip-route、--skip-unlock 跳过步骤。

支持的系统

  • Debian
  • Ubuntu
  • CentOS
  • Rocky
  • AlmaLinux

root 可选

参数与示例

$ 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

容器测试结果

在各发行版官方 Docker 镜像中以 root、无终端方式运行 curl | bash 的结果。partial 表示容器内无法完整验证(例如没有 systemd、内核模块或测速协议被测试环境的代理拦截),需要在真实 VPS 上确认。 (2026-11-11)

OS结果
debian:12partial
debian:11partial
ubuntu:24.04partial
ubuntu:22.04partial
ubuntu:20.04partial
ubuntu:18.04partial
rockylinux:9partial
almalinux:9partial
centos:7partial

常见问题

这个脚本安全吗?
xyz.sh 不埋点、不偷偷执行未说明的操作,完整源码可在页面上查看,也可以用 curl -fsSL xyz.sh/<name>.sh 下载后先读再运行;每个脚本都有 /<name>.sh.sha256 校验值。把远程脚本管道给 bash 仍有风险,请先阅读源码。分享是可选的:只有 --share 或在终端回答 y 才会上传,-y 不会自动分享。上传前会把 IPv4 改成 a.b.x.x、隐藏 IPv6 和主机名;结果只存在 xyz.sh 的 Cloudflare KV 中 30 天,页面设置为 noindex。
支持哪些系统?
声明支持 Debian、Ubuntu、CentOS、Rocky、AlmaLinux。脚本会检测发行版,无法识别时会提示。已在这些系统的 Docker 容器里做过语法、--dry-run 与可运行部分的测试(结果见 GitHub 上的 tests/RESULTS.md);依赖 systemd、内核模块或公网的部分需要在真实 VPS 上验证。
需要 root 吗?
不需要。普通用户也能运行;以 root 运行时部分工具可以安装到系统目录。
要跑多久、用多少流量?
通常 5–15 分钟。境外测速和磁盘测试最耗时,测速会用掉几百 MB 到几 GB 流量。可用 --skip-speed、--skip-bench 等跳过。

完整源码

以下就是 curl 实际拿到的内容(公共库 + 脚本正文)。运行前请先读一遍。

xyz.sh/all.sh
#!/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." "全部步骤完成。")"

推荐 VPS

以下为占位展示,尚未接入任何商家或联盟链接,也没有可点击的购买地址。上线推广前会再填入真实套餐与合规的返佣链接。

推荐方向:美国

占位 · 美西

商家待定

机房、配置与价格将在接入后填写。当前不是购买入口。

占位 · 高性价比

商家待定

机房、配置与价格将在接入后填写。当前不是购买入口。

占位 · 大内存 / 大硬盘

商家待定

机房、配置与价格将在接入后填写。当前不是购买入口。