Compare commits
2 Commits
6053d91091
...
b06337813d
| Author | SHA1 | Date | |
|---|---|---|---|
| b06337813d | |||
| ea5655cd96 |
213
hy2.sh
213
hy2.sh
@@ -4,6 +4,9 @@ trap 'echo "[错误] 第 $LINENO 行执行失败:$BASH_COMMAND" >&2' ERR
|
|||||||
|
|
||||||
CONFIG_FILE="/etc/hysteria/config.yaml"
|
CONFIG_FILE="/etc/hysteria/config.yaml"
|
||||||
SERVICE_NAME="hysteria-server.service"
|
SERVICE_NAME="hysteria-server.service"
|
||||||
|
DEVICE_USERS=(macminim4 iphone12p ipadmini5 pve_debian other)
|
||||||
|
USERPASS_ENTRIES=()
|
||||||
|
TRAFFIC_STATS_SECRET=""
|
||||||
|
|
||||||
red() { printf '\033[31m%s\033[0m\n' "$*"; }
|
red() { printf '\033[31m%s\033[0m\n' "$*"; }
|
||||||
green() { printf '\033[32m%s\033[0m\n' "$*"; }
|
green() { printf '\033[32m%s\033[0m\n' "$*"; }
|
||||||
@@ -89,8 +92,16 @@ wait_for_apt_lock() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
generate_password() {
|
generate_password() {
|
||||||
openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 24
|
local length="${1:-36}"
|
||||||
echo
|
local password=""
|
||||||
|
|
||||||
|
while ((${#password} < length)); do
|
||||||
|
password+="$(
|
||||||
|
openssl rand -base64 48 | tr -dc 'A-Za-z0-9'
|
||||||
|
)"
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '%s\n' "${password:0:length}"
|
||||||
}
|
}
|
||||||
|
|
||||||
mask_secret() {
|
mask_secret() {
|
||||||
@@ -103,6 +114,13 @@ mask_secret() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
yaml_quote() {
|
||||||
|
local value="$1"
|
||||||
|
value="${value//\\/\\\\}"
|
||||||
|
value="${value//\"/\\\"}"
|
||||||
|
printf '"%s"' "$value"
|
||||||
|
}
|
||||||
|
|
||||||
validate_domain() {
|
validate_domain() {
|
||||||
local domain="$1"
|
local domain="$1"
|
||||||
[[ -n "$domain" ]] || return 1
|
[[ -n "$domain" ]] || return 1
|
||||||
@@ -369,12 +387,96 @@ backup_existing_config() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
write_config() {
|
set_config_permissions() {
|
||||||
|
local service_user service_group
|
||||||
|
|
||||||
|
service_user="$(systemctl show -p User --value "${SERVICE_NAME}" 2>/dev/null || true)"
|
||||||
|
service_user="${service_user//$'\n'/}"
|
||||||
|
|
||||||
|
if [[ -n "${service_user}" && "${service_user}" != "root" ]]; then
|
||||||
|
service_group="$(id -gn "${service_user}" 2>/dev/null || true)"
|
||||||
|
if [[ -n "${service_group}" ]]; then
|
||||||
|
chown root:"${service_group}" "${CONFIG_FILE}"
|
||||||
|
chmod 640 "${CONFIG_FILE}"
|
||||||
|
green "已按服务账户 ${service_user}:${service_group} 设置配置文件权限为 640"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
chown root:root "${CONFIG_FILE}"
|
||||||
|
chmod 644 "${CONFIG_FILE}"
|
||||||
|
yellow "未识别到可用的服务账户组,已回退为 root:root 且权限 644"
|
||||||
|
}
|
||||||
|
|
||||||
|
render_userpass_block() {
|
||||||
|
local indent="${1:-4}"
|
||||||
|
local prefix="" entry username password
|
||||||
|
|
||||||
|
prefix="$(printf '%*s' "${indent}" '')"
|
||||||
|
for entry in "${USERPASS_ENTRIES[@]}"; do
|
||||||
|
username="${entry%%:*}"
|
||||||
|
password="${entry#*:}"
|
||||||
|
printf '%s%s: %s\n' "${prefix}" "${username}" "$(yaml_quote "$password")"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
render_config() {
|
||||||
local domain="$1"
|
local domain="$1"
|
||||||
local email="$2"
|
local email="$2"
|
||||||
local cf_token="$3"
|
local cf_token="$3"
|
||||||
local password="$4"
|
local proxy_url="$4"
|
||||||
local proxy_url="$5"
|
|
||||||
|
cat <<EOF
|
||||||
|
listen: :443
|
||||||
|
|
||||||
|
acme:
|
||||||
|
domains:
|
||||||
|
- $(yaml_quote "$domain")
|
||||||
|
email: $(yaml_quote "$email")
|
||||||
|
type: dns
|
||||||
|
dns:
|
||||||
|
name: cloudflare
|
||||||
|
config:
|
||||||
|
cloudflare_api_token: $(yaml_quote "$cf_token")
|
||||||
|
|
||||||
|
auth:
|
||||||
|
type: userpass
|
||||||
|
userpass:
|
||||||
|
EOF
|
||||||
|
render_userpass_block 4
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
trafficStats:
|
||||||
|
listen: 127.0.0.1:9999
|
||||||
|
secret: $(yaml_quote "$TRAFFIC_STATS_SECRET")
|
||||||
|
|
||||||
|
disableUDP: false
|
||||||
|
udpIdleTimeout: 90s
|
||||||
|
|
||||||
|
resolver:
|
||||||
|
type: https
|
||||||
|
https:
|
||||||
|
addr: 1.1.1.1:443
|
||||||
|
timeout: 10s
|
||||||
|
sni: cloudflare-dns.com
|
||||||
|
insecure: false
|
||||||
|
|
||||||
|
speedTest: true
|
||||||
|
|
||||||
|
masquerade:
|
||||||
|
type: proxy
|
||||||
|
proxy:
|
||||||
|
url: $(yaml_quote "$proxy_url")
|
||||||
|
rewriteHost: true
|
||||||
|
insecure: false
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
render_preview_config() {
|
||||||
|
local domain="$1"
|
||||||
|
local email="$2"
|
||||||
|
local cf_token="$3"
|
||||||
|
local proxy_url="$4"
|
||||||
local masked_token
|
local masked_token
|
||||||
|
|
||||||
if ! validate_domain "$domain"; then
|
if ! validate_domain "$domain"; then
|
||||||
@@ -383,32 +485,23 @@ write_config() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
masked_token="$(mask_secret "$cf_token")"
|
masked_token="$(mask_secret "$cf_token")"
|
||||||
|
render_config "$domain" "$email" "$masked_token" "$proxy_url"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_config() {
|
||||||
|
local domain="$1"
|
||||||
|
local email="$2"
|
||||||
|
local cf_token="$3"
|
||||||
|
local proxy_url="$4"
|
||||||
|
|
||||||
|
if ! validate_domain "$domain"; then
|
||||||
|
red "域名无效,拒绝写入配置:${domain@Q}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
blue "==> 即将写入如下配置到 ${CONFIG_FILE}"
|
blue "==> 即将写入如下配置到 ${CONFIG_FILE}"
|
||||||
cat <<EOF
|
render_preview_config "$domain" "$email" "$cf_token" "$proxy_url"
|
||||||
listen: :443
|
|
||||||
|
|
||||||
acme:
|
|
||||||
domains:
|
|
||||||
- ${domain}
|
|
||||||
email: ${email}
|
|
||||||
type: dns
|
|
||||||
dns:
|
|
||||||
name: cloudflare
|
|
||||||
config:
|
|
||||||
cloudflare_api_token: ${masked_token}
|
|
||||||
|
|
||||||
auth:
|
|
||||||
type: password
|
|
||||||
password: ${password}
|
|
||||||
|
|
||||||
masquerade:
|
|
||||||
type: proxy
|
|
||||||
proxy:
|
|
||||||
url: ${proxy_url}
|
|
||||||
rewriteHost: true
|
|
||||||
EOF
|
|
||||||
echo
|
echo
|
||||||
|
|
||||||
if ! confirm_yn "是否确认写入配置文件?"; then
|
if ! confirm_yn "是否确认写入配置文件?"; then
|
||||||
@@ -419,32 +512,9 @@ EOF
|
|||||||
mkdir -p /etc/hysteria
|
mkdir -p /etc/hysteria
|
||||||
backup_existing_config
|
backup_existing_config
|
||||||
|
|
||||||
cat > "${CONFIG_FILE}" <<EOF
|
render_config "$domain" "$email" "$cf_token" "$proxy_url" > "${CONFIG_FILE}"
|
||||||
listen: :443
|
|
||||||
|
|
||||||
acme:
|
set_config_permissions
|
||||||
domains:
|
|
||||||
- ${domain}
|
|
||||||
email: ${email}
|
|
||||||
type: dns
|
|
||||||
dns:
|
|
||||||
name: cloudflare
|
|
||||||
config:
|
|
||||||
cloudflare_api_token: ${cf_token}
|
|
||||||
|
|
||||||
auth:
|
|
||||||
type: password
|
|
||||||
password: ${password}
|
|
||||||
|
|
||||||
masquerade:
|
|
||||||
type: proxy
|
|
||||||
proxy:
|
|
||||||
url: ${proxy_url}
|
|
||||||
rewriteHost: true
|
|
||||||
EOF
|
|
||||||
|
|
||||||
chown root:root "${CONFIG_FILE}"
|
|
||||||
chmod 644 "${CONFIG_FILE}"
|
|
||||||
green "配置已写入 ${CONFIG_FILE}"
|
green "配置已写入 ${CONFIG_FILE}"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -463,25 +533,34 @@ start_service() {
|
|||||||
|
|
||||||
show_result() {
|
show_result() {
|
||||||
local domain="$1"
|
local domain="$1"
|
||||||
local password="$2"
|
local proxy_url="$2"
|
||||||
local proxy_url="$3"
|
local ip_info="$3"
|
||||||
local ip_info="$4"
|
|
||||||
local ipv4="${ip_info%%|*}"
|
local ipv4="${ip_info%%|*}"
|
||||||
local ipv6="${ip_info##*|}"
|
local ipv6="${ip_info##*|}"
|
||||||
|
local entry username password
|
||||||
local share_link="hysteria2://${password}@${domain}:443/?sni=${domain}&insecure=0"
|
|
||||||
|
|
||||||
echo
|
echo
|
||||||
green "================= HY2 节点信息 ================="
|
green "================= HY2 节点信息 ================="
|
||||||
echo "域名: ${domain}"
|
echo "域名: ${domain}"
|
||||||
echo "端口: 443"
|
echo "端口: 443"
|
||||||
echo "密码: ${password}"
|
echo "认证方式: userpass"
|
||||||
echo "伪装站点: ${proxy_url}"
|
echo "伪装站点: ${proxy_url}"
|
||||||
|
echo "流量统计监听: 127.0.0.1:9999"
|
||||||
|
echo "流量统计密钥: ${TRAFFIC_STATS_SECRET}"
|
||||||
[[ -n "${ipv4}" ]] && echo "IPv4: ${ipv4}"
|
[[ -n "${ipv4}" ]] && echo "IPv4: ${ipv4}"
|
||||||
[[ -n "${ipv6}" ]] && echo "IPv6: ${ipv6}"
|
[[ -n "${ipv6}" ]] && echo "IPv6: ${ipv6}"
|
||||||
echo
|
echo
|
||||||
echo "代理链接:"
|
echo "客户端连接参数:"
|
||||||
echo "${share_link}"
|
echo " server: ${domain}:443"
|
||||||
|
echo " sni: ${domain}"
|
||||||
|
echo " auth: userpass"
|
||||||
|
echo
|
||||||
|
echo "设备账号:"
|
||||||
|
for entry in "${USERPASS_ENTRIES[@]}"; do
|
||||||
|
username="${entry%%:*}"
|
||||||
|
password="${entry#*:}"
|
||||||
|
echo " ${username}: ${password}"
|
||||||
|
done
|
||||||
echo
|
echo
|
||||||
echo "hysteria 状态:"
|
echo "hysteria 状态:"
|
||||||
systemctl --no-pager --full status "${SERVICE_NAME}" || true
|
systemctl --no-pager --full status "${SERVICE_NAME}" || true
|
||||||
@@ -509,7 +588,7 @@ main() {
|
|||||||
require_cmd python3
|
require_cmd python3
|
||||||
require_python3_json || { red "python3 缺少 json 模块,无法解析 Cloudflare API 返回值。"; exit 1; }
|
require_python3_json || { red "python3 缺少 json 模块,无法解析 Cloudflare API 返回值。"; exit 1; }
|
||||||
|
|
||||||
local email zone subdomain proxy_url password ip_info ipv4 ipv6 domain
|
local email zone subdomain default_subdomain proxy_url ip_info ipv4 ipv6 domain username
|
||||||
|
|
||||||
email="$(prompt_nonempty '请输入 ACME 邮箱: ')"
|
email="$(prompt_nonempty '请输入 ACME 邮箱: ')"
|
||||||
zone="$(prompt_nonempty '请输入 Cloudflare Zone(例如 example.com): ')"
|
zone="$(prompt_nonempty '请输入 Cloudflare Zone(例如 example.com): ')"
|
||||||
@@ -522,7 +601,11 @@ main() {
|
|||||||
CF_API_TOKEN="$(prompt_nonempty '请输入 Cloudflare API Token: ')"
|
CF_API_TOKEN="$(prompt_nonempty '请输入 Cloudflare API Token: ')"
|
||||||
export CF_API_TOKEN
|
export CF_API_TOKEN
|
||||||
|
|
||||||
password="$(generate_password)"
|
USERPASS_ENTRIES=()
|
||||||
|
for username in "${DEVICE_USERS[@]}"; do
|
||||||
|
USERPASS_ENTRIES+=("${username}:$(generate_password 36)")
|
||||||
|
done
|
||||||
|
TRAFFIC_STATS_SECRET="$(generate_password 36)"
|
||||||
|
|
||||||
ip_info="$(get_server_ip)"
|
ip_info="$(get_server_ip)"
|
||||||
ipv4="${ip_info%%|*}"
|
ipv4="${ip_info%%|*}"
|
||||||
@@ -549,9 +632,9 @@ main() {
|
|||||||
domain="$(prompt_nonempty '请输入已存在并已解析到本机的完整域名: ')"
|
domain="$(prompt_nonempty '请输入已存在并已解析到本机的完整域名: ')"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
write_config "${domain}" "${email}" "${CF_API_TOKEN}" "${password}" "${proxy_url}"
|
write_config "${domain}" "${email}" "${CF_API_TOKEN}" "${proxy_url}"
|
||||||
start_service
|
start_service
|
||||||
show_result "${domain}" "${password}" "${proxy_url}" "${ip_info}"
|
show_result "${domain}" "${proxy_url}" "${ip_info}"
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
Reference in New Issue
Block a user