#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" YQ_VERSION="${YQ_VERSION:-v4.52.4}" tmp_dir="$(mktemp -d)" trap 'rm -rf "$tmp_dir"' EXIT mkdir -p "$tmp_dir/bin" "$tmp_dir/runtime" "$tmp_dir/config" "$tmp_dir/logs" install_test_yq() { local target="$tmp_dir/bin/yq" local system_yq if [ -n "${CLASH_TEST_YQ_BIN:-}" ] && [ -x "$CLASH_TEST_YQ_BIN" ]; then cp "$CLASH_TEST_YQ_BIN" "$target" chmod +x "$target" return 0 fi if [ -x "$PROJECT_DIR/runtime/bin/yq" ]; then cp "$PROJECT_DIR/runtime/bin/yq" "$target" chmod +x "$target" return 0 fi system_yq="$(command -v yq 2>/dev/null || true)" if [ -n "${system_yq:-}" ] && "$system_yq" --version 2>/dev/null | grep -q 'version v4'; then cp "$system_yq" "$target" chmod +x "$target" return 0 fi local arch file url archive extract_dir arch="$(uname -m)" case "$arch" in x86_64|amd64) file="yq_linux_amd64.tar.gz" ;; aarch64|arm64) file="yq_linux_arm64.tar.gz" ;; armv7l|armv7*) file="yq_linux_arm.tar.gz" ;; *) echo "not ok - unsupported test architecture for yq: $arch" >&2; return 1 ;; esac url="https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/${file}" archive="$tmp_dir/$file" extract_dir="$tmp_dir/yq-extract" mkdir -p "$extract_dir" curl -fsSL "$url" -o "$archive" tar -xzf "$archive" -C "$extract_dir" find "$extract_dir" -type f -name 'yq*' -perm -111 -print -quit | xargs -r -I{} cp "{}" "$target" [ -x "$target" ] || chmod +x "$target" 2>/dev/null || true [ -x "$target" ] || { echo "not ok - failed to install temporary yq" >&2; return 1; } } install_test_yq export PROJECT_DIR export RUNTIME_DIR="$tmp_dir/runtime" export BIN_DIR="$tmp_dir/bin" export LOG_DIR="$tmp_dir/logs" export CONFIG_DIR="$tmp_dir/config" unset CLASH_IPV6 # shellcheck source=scripts/core/config.sh source "$PROJECT_DIR/scripts/core/config.sh" # Keep this regression test independent from the developer's project .env. read_env_value() { return 1; } resolve_runtime_ports() { printf 'MIXED_PORT_RESOLVED=7891\n' printf 'EXTERNAL_CONTROLLER_RESOLVED=0.0.0.0:9090\n' printf 'CLASH_DNS_PORT_RESOLVED=1053\n' } ensure_controller_secret() { printf 'test-secret\n'; } runtime_dashboard_dir() { printf '%s/dashboard\n' "$RUNTIME_DIR"; } tun_enabled() { printf 'false\n'; } tun_stack() { printf 'mixed\n'; } tun_auto_route() { printf 'false\n'; } tun_auto_redirect() { printf 'false\n'; } tun_strict_route() { printf 'false\n'; } tun_dns_hijack() { printf 'any:53\n'; } set_config_allow_lan false actual="$("$BIN_DIR/yq" eval '.["allow-lan"]' "$CONFIG_DIR/template.yaml")" if [ "$actual" != "false" ]; then echo "not ok - writes disabled LAN setting: got '$actual', expected 'false'" >&2 exit 1 fi echo "ok - writes disabled LAN setting" actual="$(config_allow_lan)" if [ "$actual" != "false" ]; then echo "not ok - reads disabled LAN setting: got '$actual', expected 'false'" >&2 exit 1 fi echo "ok - reads disabled LAN setting" sample_config="$tmp_dir/runtime-config.yaml" cat > "$sample_config" <<'YAML' port: 7890 socks-port: 7891 redir-port: 7892 tproxy-port: 7893 mixed-port: 7890 external-controller: 0.0.0.0:9090 secret: old-secret allow-lan: false ipv6: true dns: ipv6: true proxies: - name: hy2-ipv6 type: hysteria2 server: "2001:db8::1" port: 443 password: test-password - name: anytls type: anytls server: 192.0.2.1 port: 443 password: test-password sni: example.com proxy-groups: [] rules: [] YAML normalize_runtime_config "$sample_config" assert_file_yq() { local name="$1" local file="$2" local expr="$3" local expected="$4" local actual actual="$("$BIN_DIR/yq" eval "$expr" "$file")" if [ "$actual" != "$expected" ]; then echo "not ok - $name: got '$actual', expected '$expected'" >&2 "$BIN_DIR/yq" eval '.' "$file" >&2 || true return 1 fi echo "ok - $name" } assert_yq() { assert_file_yq "$1" "$sample_config" "$2" "$3" } assert_yq "keeps resolved mixed-port" '.["mixed-port"]' "7891" assert_yq "removes legacy port" 'has("port")' "false" assert_yq "removes legacy socks-port" 'has("socks-port")' "false" assert_yq "removes legacy redir-port" 'has("redir-port")' "false" assert_yq "removes legacy tproxy-port" 'has("tproxy-port")' "false" assert_yq "keeps controller normalization" '.["external-controller"]' "0.0.0.0:9090" assert_yq "keeps LAN proxy disabled" '.["allow-lan"]' "false" cp "$sample_config" "$RUNTIME_DIR/config.yaml" actual="$(runtime_config_allow_lan)" if [ "$actual" != "false" ]; then echo "not ok - reports disabled runtime LAN status: got '$actual', expected 'false'" >&2 exit 1 fi echo "ok - reports disabled runtime LAN status" assert_yq "preserves subscription IPv6" '.ipv6' "true" assert_yq "preserves subscription DNS IPv6" '.dns.ipv6' "true" assert_yq "preserves Hysteria2 proxy type" '.proxies[0].type' "hysteria2" assert_yq "preserves IPv6-only proxy server" '.proxies[0].server' "2001:db8::1" assert_yq "preserves AnyTLS proxy type" '.proxies[1].type' "anytls" assert_yq "preserves AnyTLS SNI" '.proxies[1].sni' "example.com" CLASH_IPV6=false normalize_runtime_config "$sample_config" assert_yq "IPv6 off disables kernel IPv6" '.ipv6' "false" assert_yq "IPv6 off disables DNS IPv6" '.dns.ipv6' "false" unset CLASH_IPV6 normalize_runtime_config "$sample_config" assert_yq "auto preserves disabled kernel IPv6" '.ipv6' "false" assert_yq "auto preserves disabled DNS IPv6" '.dns.ipv6' "false" CLASH_IPV6=true normalize_runtime_config "$sample_config" assert_yq "IPv6 on enables kernel IPv6" '.ipv6' "true" assert_yq "IPv6 on enables DNS IPv6" '.dns.ipv6' "true" unset CLASH_IPV6 default_config="$tmp_dir/default-ipv6-config.yaml" cat > "$default_config" <<'YAML' proxies: [] proxy-groups: [] rules: [] YAML normalize_runtime_config "$default_config" assert_file_yq "auto keeps kernel IPv6 default enabled" "$default_config" '.ipv6' "true" assert_file_yq "auto keeps legacy DNS IPv6 default disabled" "$default_config" '.dns.ipv6' "false" default_subscription_ua="$( unset CLASH_SUBSCRIPTION_UA CLASH_SUB_UA subconverter_subscription_user_agent )" if [ "$default_subscription_ua" != "clash-verge/v2.4.0" ]; then echo "not ok - modern protocol subscription UA: got '$default_subscription_ua'" >&2 exit 1 fi echo "ok - modern protocol subscription UA" share_links="$tmp_dir/modern-share-links.txt" cat > "$share_links" <<'EOF' vmess://legacy hysteria2://password@example.com:443 hy2://password@example.com:443 anytls://password@example.com:443 EOF converted_links="$(local_subscription_share_links_to_subconverter_url "$share_links")" case "$converted_links" in *"hysteria2://"*"hy2://"*"anytls://"*) echo "ok - recognizes Hysteria2, hy2 and AnyTLS share links" ;; *) echo "not ok - modern share links were dropped: $converted_links" >&2 exit 1 ;; esac