From daa3ae854fc5033d95d710e1b473930f11ef9e50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=9D=E4=BB=A3=E5=B0=BE?= Date: Thu, 19 Mar 2026 15:36:31 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20hy2.sh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hy2.sh | 220 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 108 insertions(+), 112 deletions(-) diff --git a/hy2.sh b/hy2.sh index a379ec7..d62d247 100644 --- a/hy2.sh +++ b/hy2.sh @@ -4,7 +4,6 @@ trap 'echo "[错误] 第 $LINENO 行执行失败:$BASH_COMMAND"' ERR CONFIG_FILE="/etc/hysteria/config.yaml" SERVICE_NAME="hysteria-server.service" -MASQUERADE_DEFAULT_URL="https://news.ycombinator.com/" red() { printf '\033[31m%s\033[0m\n' "$*"; } green() { printf '\033[32m%s\033[0m\n' "$*"; } @@ -49,23 +48,6 @@ confirm_yn() { esac } -generate_password() { - openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 24 - echo -} - -generate_sub_prefix() { - openssl rand -base64 32 | tr -dc 'a-z0-9' | head -c 8 - echo -} - -get_public_ips() { - local ipv4="" ipv6="" - ipv4="$(curl -4 -fsSL https://api.ipify.org || true)" - ipv6="$(curl -6 -fsSL https://api64.ipify.org || true)" - printf '%s|%s\n' "$ipv4" "$ipv6" -} - wait_for_apt_lock() { local timeout="${1:-300}" local waited=0 @@ -92,11 +74,24 @@ wait_for_apt_lock() { waited=$((waited + 2)) done + green "APT 锁已释放。" return 0 } +generate_password() { + openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 24 + echo +} + +get_server_ip() { + local ipv4 ipv6 + ipv4="$(curl -4 -fsSL https://api.ipify.org || true)" + ipv6="$(curl -6 -fsSL https://api64.ipify.org || true)" + echo "${ipv4}|${ipv6}" +} + apt_update_and_install_base() { - if ! confirm_yn "即将执行 apt update 并安装基础依赖 jq curl sed ufw openssl,是否继续?"; then + if ! confirm_yn "即将执行 apt update 并安装基础依赖 curl sed ufw openssl,是否继续?"; then red "已取消。" exit 1 fi @@ -109,7 +104,7 @@ apt_update_and_install_base() { blue "==> 安装基础依赖" wait_for_apt_lock 300 || { red "APT 被占用,无法继续。"; exit 1; } - apt install -y jq curl sed ufw openssl + apt install -y curl sed ufw openssl } disable_existing_firewalls() { @@ -207,113 +202,101 @@ install_hysteria2() { green "Hysteria 2 安装完成。" } -cf_api() { +run_domain_selector() { + if confirm_yn "是否执行外部域名筛选脚本?"; then + blue "==> 执行域名筛选脚本" + bash <(curl -sL https://raw.githubusercontent.com/ccxkai233/Domain_Selector/main/domain_check.sh) || true + else + yellow "已跳过域名筛选脚本。" + fi +} + +cf_api_request() { local method="$1" - local endpoint="$2" - local token="$3" - local data="${4:-}" + local url="$2" + local data="${3:-}" if [[ -n "$data" ]]; then - curl -fsSL -X "$method" "https://api.cloudflare.com/client/v4${endpoint}" \ - -H "Authorization: Bearer ${token}" \ + curl -fsSL -X "$method" "$url" \ + -H "Authorization: Bearer ${CF_API_TOKEN}" \ -H "Content-Type: application/json" \ --data "$data" else - curl -fsSL -X "$method" "https://api.cloudflare.com/client/v4${endpoint}" \ - -H "Authorization: Bearer ${token}" \ + curl -fsSL -X "$method" "$url" \ + -H "Authorization: Bearer ${CF_API_TOKEN}" \ -H "Content-Type: application/json" fi } get_cf_zone_id() { - local main_domain="$1" - local token="$2" - local resp zone_id - resp="$(cf_api GET "/zones?name=${main_domain}&status=active" "$token")" - zone_id="$(printf '%s' "$resp" | jq -r '.result[0].id // empty')" - - if [[ -z "$zone_id" ]]; then - red "未找到 Cloudflare Zone:${main_domain}" - red "请确认主域名已接入 Cloudflare,且 API Token 对该 Zone 具有 DNS 编辑权限。" - exit 1 - fi - - printf '%s\n' "$zone_id" + local zone="$1" + cf_api_request GET "https://api.cloudflare.com/client/v4/zones?name=${zone}" \ + | sed -n 's/.*"id":"\([^"]*\)".*/\1/p' | head -n 1 } -delete_existing_dns_record_if_needed() { +delete_existing_cf_dns_record() { local zone_id="$1" local full_domain="$2" local type="$3" - local token="$4" - local resp record_ids rid + local record_id - resp="$(cf_api GET "/zones/${zone_id}/dns_records?type=${type}&name=${full_domain}" "$token")" - record_ids="$(printf '%s' "$resp" | jq -r '.result[].id // empty')" + record_id="$( + cf_api_request GET "https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records?name=${full_domain}&type=${type}" \ + | sed -n 's/.*"id":"\([^"]*\)".*/\1/p' | head -n 1 + )" - if [[ -n "$record_ids" ]]; then - while IFS= read -r rid; do - [[ -z "$rid" ]] && continue - cf_api DELETE "/zones/${zone_id}/dns_records/${rid}" "$token" >/dev/null - done <<< "$record_ids" + if [[ -n "$record_id" ]]; then + cf_api_request DELETE "https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records/${record_id}" >/dev/null fi } -create_cf_dns_record() { +create_cf_dns_record_type() { local zone_id="$1" local full_domain="$2" local type="$3" local content="$4" - local token="$5" - local payload resp success - payload="$(jq -nc \ - --arg type "$type" \ - --arg name "$full_domain" \ - --arg content "$content" \ - '{type:$type,name:$name,content:$content,ttl:120,proxied:false}')" + [[ -z "$content" ]] && return 0 - resp="$(cf_api POST "/zones/${zone_id}/dns_records" "$token" "$payload")" - success="$(printf '%s' "$resp" | jq -r '.success')" + local payload + payload=$( + cat </dev/null } -create_cloudflare_dns_records() { - local main_domain="$1" - local sub_prefix="$2" - local token="$3" - local ip_info="$4" - local ipv4="${ip_info%%|*}" - local ipv6="${ip_info##*|}" - local full_domain="${sub_prefix}.${main_domain}" +create_cloudflare_dns_record() { + local zone="$1" + local subdomain="$2" + local ipv4="$3" + local ipv6="$4" + local full_domain="${subdomain}.${zone}" local zone_id - zone_id="$(get_cf_zone_id "$main_domain" "$token")" + echo "==> 在 Cloudflare 中创建 DNS 记录" >&2 + echo "Zone: ${zone}" >&2 + echo "完整域名: ${full_domain}" >&2 - blue "==> 在 Cloudflare 中创建 DNS 记录" - echo "Zone: ${main_domain}" - echo "完整域名: ${full_domain}" + zone_id="$(get_cf_zone_id "$zone")" + if [[ -z "$zone_id" ]]; then + red "无法获取 Cloudflare Zone ID,请检查 Token 权限或 Zone 名称。" + exit 1 + fi + + delete_existing_cf_dns_record "$zone_id" "$full_domain" "A" || true + delete_existing_cf_dns_record "$zone_id" "$full_domain" "AAAA" || true if [[ -n "$ipv4" ]]; then - delete_existing_dns_record_if_needed "$zone_id" "$full_domain" "A" "$token" - create_cf_dns_record "$zone_id" "$full_domain" "A" "$ipv4" "$token" - green "已创建 A 记录 -> ${ipv4}" + create_cf_dns_record_type "$zone_id" "$full_domain" "A" "$ipv4" + echo "已创建 A 记录 -> ${ipv4}" >&2 fi if [[ -n "$ipv6" ]]; then - delete_existing_dns_record_if_needed "$zone_id" "$full_domain" "AAAA" "$token" - create_cf_dns_record "$zone_id" "$full_domain" "AAAA" "$ipv6" "$token" - green "已创建 AAAA 记录 -> ${ipv6}" - fi - - if [[ -z "$ipv4" && -z "$ipv6" ]]; then - red "未获取到当前机器公网 IPv4/IPv6,无法创建 DNS 记录。" - exit 1 + create_cf_dns_record_type "$zone_id" "$full_domain" "AAAA" "$ipv6" + echo "已创建 AAAA 记录 -> ${ipv6}" >&2 fi printf '%s\n' "$full_domain" @@ -328,7 +311,7 @@ backup_existing_config() { } write_config() { - local full_domain="$1" + local domain="$1" local email="$2" local cf_token="$3" local password="$4" @@ -341,7 +324,7 @@ listen: :443 acme: domains: - - ${full_domain} + - ${domain} email: ${email} type: dns dns: @@ -374,7 +357,7 @@ listen: :443 acme: domains: - - ${full_domain} + - ${domain} email: ${email} type: dns dns: @@ -412,17 +395,18 @@ start_service() { } show_result() { - local full_domain="$1" + local domain="$1" local password="$2" local proxy_url="$3" local ip_info="$4" local ipv4="${ip_info%%|*}" local ipv6="${ip_info##*|}" - local share_link="hysteria2://${password}@${full_domain}:443/?sni=${full_domain}&insecure=0" + + local share_link="hysteria2://${password}@${domain}:443/?sni=${domain}&insecure=0" echo green "================= HY2 节点信息 =================" - echo "域名: ${full_domain}" + echo "域名: ${domain}" echo "端口: 443" echo "密码: ${password}" echo "伪装站点: ${proxy_url}" @@ -433,14 +417,17 @@ show_result() { echo "${share_link}" echo echo "hysteria 状态:" - systemctl status "${SERVICE_NAME}" --no-pager -l | sed -n '1,8p' + systemctl --no-pager --full status "${SERVICE_NAME}" || true + echo + echo "最近日志:" + journalctl --no-pager -n 30 -u "${SERVICE_NAME}" || true echo "================================================" } main() { require_root - if ! confirm_yn "本脚本将更新软件源、关闭现有防火墙、配置 UFW、安装并配置 Hysteria 2、自动创建 Cloudflare DNS,是否继续?"; then + if ! confirm_yn "本脚本将更新软件源、关闭现有防火墙、配置 UFW、安装并配置 Hysteria 2,是否继续?"; then red "用户取消执行。" exit 1 fi @@ -451,34 +438,43 @@ main() { require_cmd sed require_cmd systemctl require_cmd ufw - require_cmd jq require_cmd openssl - local main_domain email cf_token password sub_prefix full_domain ip_info proxy_url + local email zone subdomain proxy_url password ip_info ipv4 ipv6 domain - main_domain="$(prompt_nonempty '请输入主域名(例如 example.com): ')" email="$(prompt_nonempty '请输入 ACME 邮箱: ')" - cf_token="$(prompt_nonempty '请输入 Cloudflare API Token: ')" + zone="$(prompt_nonempty '请输入 Cloudflare Zone(例如 example.com): ')" + subdomain="$(prompt_nonempty '请输入要创建的子域名前缀(例如 hy2): ')" + CF_API_TOKEN="$(prompt_nonempty '请输入 Cloudflare API Token: ')" + export CF_API_TOKEN password="$(generate_password)" - sub_prefix="$(generate_sub_prefix)" - ip_info="$(get_public_ips)" - full_domain="$(create_cloudflare_dns_records "$main_domain" "$sub_prefix" "$cf_token" "$ip_info")" - proxy_url="$MASQUERADE_DEFAULT_URL" + ip_info="$(get_server_ip)" + ipv4="${ip_info%%|*}" + ipv6="${ip_info##*|}" - echo - green "自动生成的 Hysteria 域名: ${full_domain}" - echo "默认伪装站点: ${proxy_url}" - echo "随机密码: ${password}" - echo + if [[ -z "$ipv4" && -z "$ipv6" ]]; then + red "无法获取服务器公网 IP。" + exit 1 + fi disable_existing_firewalls configure_ufw install_hysteria2 - write_config "${full_domain}" "${email}" "${cf_token}" "${password}" "${proxy_url}" + run_domain_selector + + proxy_url="$(prompt_nonempty '请输入最终用于 masquerade 的完整 URL(例如 https://example.com/): ')" + + if confirm_yn "是否在 Cloudflare 中自动创建 DNS 记录 ${subdomain}.${zone}?"; then + domain="$(create_cloudflare_dns_record "$zone" "$subdomain" "$ipv4" "$ipv6")" + else + domain="$(prompt_nonempty '请输入已存在并已解析到本机的完整域名: ')" + fi + + write_config "${domain}" "${email}" "${CF_API_TOKEN}" "${password}" "${proxy_url}" start_service - show_result "${full_domain}" "${password}" "${proxy_url}" "${ip_info}" + show_result "${domain}" "${password}" "${proxy_url}" "${ip_info}" } main "$@" \ No newline at end of file