From 86efd98624659a83f3f95d1842b13f6a8c846585 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:04:56 +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 | 187 +++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 149 insertions(+), 38 deletions(-) diff --git a/hy2.sh b/hy2.sh index b308c31..1c8abad 100644 --- a/hy2.sh +++ b/hy2.sh @@ -1,8 +1,10 @@ #!/usr/bin/env bash set -Eeuo pipefail +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' "$*"; } @@ -52,15 +54,20 @@ generate_password() { echo } -get_server_ip() { - local ipv4 ipv6 +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)" - echo "${ipv4}|${ipv6}" + printf '%s|%s\n' "$ipv4" "$ipv6" } apt_update_and_install_base() { - if ! confirm_yn "即将执行 apt update 并安装基础依赖 curl sed ufw openssl,是否继续?"; then + if ! confirm_yn "即将执行 apt update 并安装基础依赖 curl sed ufw jq openssl,是否继续?"; then red "已取消。" exit 1 fi @@ -70,7 +77,7 @@ apt_update_and_install_base() { apt update -y blue "==> 安装基础依赖" - apt install -y curl sed ufw openssl + apt install -y curl sed ufw jq openssl } disable_existing_firewalls() { @@ -168,15 +175,118 @@ install_hysteria2() { green "Hysteria 2 安装完成。" } -run_domain_selector() { - if confirm_yn "是否执行外部域名筛选脚本?"; then - blue "==> 执行域名筛选脚本" - bash <(curl -sL https://raw.githubusercontent.com/ccxkai233/Domain_Selector/main/domain_check.sh) || true +cf_api() { + local method="$1" + local endpoint="$2" + local token="$3" + local data="${4:-}" + + if [[ -n "$data" ]]; then + curl -fsSL -X "$method" "https://api.cloudflare.com/client/v4${endpoint}" \ + -H "Authorization: Bearer ${token}" \ + -H "Content-Type: application/json" \ + --data "$data" else - yellow "已跳过域名筛选脚本。" + curl -fsSL -X "$method" "https://api.cloudflare.com/client/v4${endpoint}" \ + -H "Authorization: Bearer ${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" +} + +delete_existing_dns_record_if_needed() { + local zone_id="$1" + local full_domain="$2" + local type="$3" + local token="$4" + local resp record_ids rid + + 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')" + + 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" + fi +} + +create_cf_dns_record() { + 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}')" + + resp="$(cf_api POST "/zones/${zone_id}/dns_records" "$token" "$payload")" + success="$(printf '%s' "$resp" | jq -r '.success')" + + if [[ "$success" != "true" ]]; then + red "创建 Cloudflare DNS 记录失败:" + printf '%s\n' "$resp" | jq . + exit 1 + fi +} + +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}" + local zone_id + + zone_id="$(get_cf_zone_id "$main_domain" "$token")" + + blue "==> 在 Cloudflare 中创建 DNS 记录" + echo "Zone: ${main_domain}" + echo "完整域名: ${full_domain}" + + 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}" + 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 + fi + + printf '%s\n' "$full_domain" +} + backup_existing_config() { if [[ -f "${CONFIG_FILE}" ]]; then local backup_file="${CONFIG_FILE}.bak.$(date +%Y%m%d_%H%M%S)" @@ -186,7 +296,7 @@ backup_existing_config() { } write_config() { - local domain="$1" + local full_domain="$1" local email="$2" local cf_token="$3" local password="$4" @@ -199,7 +309,7 @@ listen: :443 acme: domains: - - ${domain} + - ${full_domain} email: ${email} type: dns dns: @@ -232,7 +342,7 @@ listen: :443 acme: domains: - - ${domain} + - ${full_domain} email: ${email} type: dns dns: @@ -270,42 +380,35 @@ start_service() { } show_result() { - local domain="$1" + local full_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}@${domain}:443/?sni=${domain}&insecure=0" + local share_link="hysteria2://${password}@${full_domain}:443/?sni=${full_domain}&insecure=0" echo green "================= HY2 节点信息 =================" - echo "服务名: ${SERVICE_NAME}" - echo "配置文件: ${CONFIG_FILE}" - echo "域名: ${domain}" + echo "域名: ${full_domain}" echo "端口: 443" - echo "认证方式: password" echo "密码: ${password}" echo "伪装站点: ${proxy_url}" - [[ -n "${ipv4}" ]] && echo "服务器 IPv4: ${ipv4}" - [[ -n "${ipv6}" ]] && echo "服务器 IPv6: ${ipv6}" + [[ -n "${ipv4}" ]] && echo "IPv4: ${ipv4}" + [[ -n "${ipv6}" ]] && echo "IPv6: ${ipv6}" echo echo "代理链接:" echo "${share_link}" echo - echo "systemd 状态:" - systemctl --no-pager --full status "${SERVICE_NAME}" || true - echo - echo "最近日志:" - journalctl --no-pager -n 30 -u "${SERVICE_NAME}" || true + echo "hysteria 状态:" + systemctl status "${SERVICE_NAME}" --no-pager -l | sed -n '1,8p' echo "================================================" } main() { require_root - if ! confirm_yn "本脚本将更新软件源、关闭现有防火墙、配置 UFW、安装并配置 Hysteria 2,是否继续?"; then + if ! confirm_yn "本脚本将更新软件源、关闭现有防火墙、配置 UFW、安装并配置 Hysteria 2、自动创建 Cloudflare DNS,是否继续?"; then red "用户取消执行。" exit 1 fi @@ -316,26 +419,34 @@ main() { require_cmd sed require_cmd systemctl require_cmd ufw + require_cmd jq + require_cmd openssl - local domain email cf_token password proxy_url ip_info + local main_domain email cf_token password sub_prefix full_domain ip_info proxy_url - domain="$(prompt_nonempty '请输入用于签发证书的域名: ')" + main_domain="$(prompt_nonempty '请输入主域名(例如 example.com): ')" email="$(prompt_nonempty '请输入 ACME 邮箱: ')" cf_token="$(prompt_nonempty '请输入 Cloudflare 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" + + echo + green "自动生成的 Hysteria 域名: ${full_domain}" + echo "默认伪装站点: ${proxy_url}" + echo "随机密码: ${password}" + echo disable_existing_firewalls configure_ufw install_hysteria2 - run_domain_selector - - proxy_url="$(prompt_nonempty '请输入最终用于 masquerade 的完整 URL(例如 https://example.com/): ')" - - write_config "${domain}" "${email}" "${cf_token}" "${password}" "${proxy_url}" + write_config "${full_domain}" "${email}" "${cf_token}" "${password}" "${proxy_url}" start_service - - ip_info="$(get_server_ip)" - show_result "${domain}" "${password}" "${proxy_url}" "${ip_info}" + show_result "${full_domain}" "${password}" "${proxy_url}" "${ip_info}" } main "$@" \ No newline at end of file