feat: 支持证书托管和节点自动安装
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -n "${BASH_VERSION:-}" ] || exec bash "$0" "$@"
|
||||
set -Eeuo pipefail
|
||||
umask 077
|
||||
PANEL=__PANEL__
|
||||
TOKEN=__TOKEN__
|
||||
INSTALL_ID=__INSTALL_ID__
|
||||
SLUG=__SLUG__
|
||||
DOMAIN=""
|
||||
HY2_PORT=""
|
||||
STATS_PORT=""
|
||||
HY2_LISTEN=""
|
||||
STATS_LISTEN=""
|
||||
while [ "$#" -gt 0 ]; do
|
||||
[ "$#" -ge 2 ] || { echo "参数缺少值:$1"; exit 1; }
|
||||
case "$1" in
|
||||
--domain) DOMAIN=$2;;
|
||||
--hy2-port) HY2_PORT=$2;;
|
||||
--stats-port) STATS_PORT=$2;;
|
||||
--hy2-listen-port) HY2_LISTEN=$2;;
|
||||
--stats-listen-port) STATS_LISTEN=$2;;
|
||||
*) echo "未知参数:$1"; exit 1;;
|
||||
esac
|
||||
shift 2
|
||||
done
|
||||
[ "$(id -u)" -eq 0 ] || { echo '请使用 root 执行。'; exit 1; }
|
||||
command -v systemctl >/dev/null || { echo '需要使用 systemd 的 Linux 服务器。'; exit 1; }
|
||||
[ -d /run/systemd/system ] || { echo 'systemd 未运行。'; exit 1; }
|
||||
case "$(uname -m)" in
|
||||
x86_64) ARCH=amd64;; aarch64|arm64) ARCH=arm64;;
|
||||
armv7l) ARCH=arm;; *) echo '暂不支持此 CPU 架构。'; exit 1;;
|
||||
esac
|
||||
if [ -e /etc/hysteria/config.yaml ] || [ -e /etc/systemd/system/hy2-panel-node.service ]; then
|
||||
[ "$(cat /etc/hysteria/.panel-install-id 2>/dev/null || true)" = "$INSTALL_ID" ] || {
|
||||
echo '检测到已有 HY2 配置;为避免覆盖,安装已停止。请在新服务器运行。'; exit 1;
|
||||
}
|
||||
fi
|
||||
for unit in hysteria-server.service hysteria-server@config.service; do
|
||||
if systemctl is-active --quiet "$unit"; then echo '已有 Hysteria 服务正在运行,安装已停止。'; exit 1; fi
|
||||
done
|
||||
read_port() {
|
||||
local value
|
||||
while true; do
|
||||
read -r -p "$1" value </dev/tty
|
||||
if [[ "$value" =~ ^[0-9]{1,5}$ ]] && (( 10#$value >= 1 && 10#$value <= 65535 )); then
|
||||
printf '%s' "$((10#$value))"; return
|
||||
fi
|
||||
echo '端口必须为 1–65535。' >&2
|
||||
done
|
||||
}
|
||||
echo "节点:$SLUG"
|
||||
if [ -z "$DOMAIN" ]; then read -r -p '节点域名(输入 jp1 或 jp1.p.xn--876a.net):' DOMAIN </dev/tty; fi
|
||||
DOMAIN=$(printf '%s' "$DOMAIN" | tr '[:upper:]' '[:lower:]')
|
||||
if [[ "$DOMAIN" =~ ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$ ]]; then DOMAIN="$DOMAIN.p.xn--876a.net"; fi
|
||||
[[ "$DOMAIN" =~ ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.p\.xn--876a\.net$ ]] || { echo '请填写 .p.xn--876a.net 下的单级子域名,例如 jp1.p.xn--876a.net。'; exit 1; }
|
||||
[ -n "$HY2_PORT" ] || HY2_PORT=$(read_port 'HY2 公网 UDP 端口:')
|
||||
[ -n "$STATS_PORT" ] || STATS_PORT=$(read_port '流量统计公网 TCP 端口:')
|
||||
if [ -z "$HY2_LISTEN" ] && [ -z "$STATS_LISTEN" ] && [ -t 0 ]; then
|
||||
read -r -p 'NAT 内外端口是否不同?[y/N] ' MAPPING </dev/tty
|
||||
if [[ "$MAPPING" =~ ^[yY]$ ]]; then
|
||||
HY2_LISTEN=$(read_port 'HY2 本机 UDP 监听端口:')
|
||||
STATS_LISTEN=$(read_port '流量统计本机 TCP 监听端口:')
|
||||
fi
|
||||
fi
|
||||
HY2_LISTEN=${HY2_LISTEN:-$HY2_PORT}
|
||||
STATS_LISTEN=${STATS_LISTEN:-$STATS_PORT}
|
||||
for port in "$HY2_PORT" "$STATS_PORT" "$HY2_LISTEN" "$STATS_LISTEN"; do
|
||||
if ! [[ "$port" =~ ^[1-9][0-9]{0,4}$ ]] || (( port > 65535 )); then echo '端口必须为 1–65535(无前导零)。'; exit 1; fi
|
||||
done
|
||||
if ! command -v curl >/dev/null || ! command -v jq >/dev/null || ! command -v openssl >/dev/null; then
|
||||
if command -v apt-get >/dev/null; then
|
||||
apt-get update
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y curl jq openssl ca-certificates
|
||||
elif command -v dnf >/dev/null; then dnf install -y curl jq openssl ca-certificates
|
||||
elif command -v yum >/dev/null; then yum install -y curl jq openssl ca-certificates
|
||||
else echo '请先安装 curl、jq、openssl 和 ca-certificates。'; exit 1; fi
|
||||
fi
|
||||
WORK=$(mktemp -d)
|
||||
STARTED=0
|
||||
cleanup() {
|
||||
local status=$?
|
||||
rm -rf "$WORK"
|
||||
if [ "$status" -ne 0 ]; then
|
||||
if [ "$STARTED" -eq 1 ]; then systemctl stop hy2-panel-node.service || true; fi
|
||||
echo '安装未完成。修复网络/端口问题后,可在凭据有效期内使用相同脚本和端口重试。' >&2
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
printf 'Authorization: Bearer %s\n' "$TOKEN" > "$WORK/headers"
|
||||
# 使用 Hysteria 官方自动化下载入口,仅允许 HTTPS。
|
||||
curl --proto '=https' --proto-redir '=https' -fSL --retry 3 --connect-timeout 20 --max-time 300 \
|
||||
"https://download.hysteria.network/app/latest/hysteria-linux-$ARCH" -o "$WORK/hysteria"
|
||||
chmod 700 "$WORK/hysteria"
|
||||
"$WORK/hysteria" version
|
||||
jq -n --arg domain "$DOMAIN" --argjson h "$HY2_PORT" --argjson s "$STATS_PORT" --argjson hl "$HY2_LISTEN" --argjson sl "$STATS_LISTEN" \
|
||||
'{action:"prepare",ports:{domain:$domain,hy2Port:$h,statsPort:$s,hy2ListenPort:$hl,statsListenPort:$sl}}' > "$WORK/request"
|
||||
api() {
|
||||
local status
|
||||
status=$(curl -sS --connect-timeout 20 --max-time 60 -o "$WORK/response" -w '%{http_code}' \
|
||||
-H @"$WORK/headers" -H 'Content-Type: application/json' --data-binary @"$WORK/request" "$PANEL/api/install")
|
||||
if [ "$status" != 200 ]; then
|
||||
jq -r '.error // "平台请求失败"' "$WORK/response" >&2 || true
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
api
|
||||
jq -er '.config' "$WORK/response" > "$WORK/config.yaml"
|
||||
jq -er '.certificate' "$WORK/response" > "$WORK/server.crt"
|
||||
jq -er '.privateKey' "$WORK/response" > "$WORK/server.key"
|
||||
jq -er '"Authorization: " + .statsSecret' "$WORK/response" > "$WORK/stats-header"
|
||||
openssl x509 -in "$WORK/server.crt" -noout -checkend 0 >/dev/null
|
||||
openssl x509 -in "$WORK/server.crt" -pubkey -noout > "$WORK/cert.pub"
|
||||
openssl pkey -in "$WORK/server.key" -pubout > "$WORK/key.pub"
|
||||
cmp -s "$WORK/cert.pub" "$WORK/key.pub" || { echo '证书与私钥不匹配。'; exit 1; }
|
||||
install -d -m 700 /etc/hysteria /usr/local/lib/hy2-panel
|
||||
# 仅允许重试本安装记录生成的配置。
|
||||
printf '%s' "$INSTALL_ID" > /etc/hysteria/.panel-install-id
|
||||
systemctl stop hy2-panel-node.service 2>/dev/null || true
|
||||
install -m 755 "$WORK/hysteria" /usr/local/lib/hy2-panel/hysteria
|
||||
install -m 600 "$WORK/config.yaml" /etc/hysteria/config.yaml
|
||||
install -m 600 "$WORK/server.crt" /etc/hysteria/server.crt
|
||||
install -m 600 "$WORK/server.key" /etc/hysteria/server.key
|
||||
cat > /etc/systemd/system/hy2-panel-node.service <<'UNIT'
|
||||
[Unit]
|
||||
Description=Hysteria 2 (HY2 Panel)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/local/lib/hy2-panel/hysteria server --config /etc/hysteria/config.yaml
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
LimitNOFILE=1048576
|
||||
NoNewPrivileges=true
|
||||
ProtectHome=true
|
||||
ProtectSystem=strict
|
||||
PrivateTmp=true
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
UNIT
|
||||
systemctl daemon-reload
|
||||
systemctl start hy2-panel-node.service
|
||||
STARTED=1
|
||||
READY=0
|
||||
for attempt in {1..15}; do
|
||||
if systemctl is-active --quiet hy2-panel-node.service && \
|
||||
curl -fsS --max-time 2 -H @"$WORK/stats-header" "http://127.0.0.1:$STATS_LISTEN/traffic" -o /dev/null; then
|
||||
READY=1; break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
[ "$READY" -eq 1 ] || { echo '服务健康检查失败,请检查端口占用和 systemctl status hy2-panel-node。'; exit 1; }
|
||||
# 完成通知可能已被平台处理但响应丢失;保留已健康启动的服务,避免误停。
|
||||
systemctl enable hy2-panel-node.service
|
||||
STARTED=0
|
||||
printf '{"action":"complete"}' > "$WORK/request"
|
||||
# 重试完成通知,平台支持幂等确认。
|
||||
for attempt in {1..3}; do
|
||||
if api; then break; fi
|
||||
[ "$attempt" -lt 3 ] || exit 1
|
||||
sleep 2
|
||||
done
|
||||
STARTED=0
|
||||
echo "安装完成,节点 $SLUG 已注册并启用。"
|
||||
echo "请在 NAT / 云防火墙中映射 UDP $HY2_PORT → $HY2_LISTEN、TCP $STATS_PORT → $STATS_LISTEN。"
|
||||
echo '流量统计 TCP 端口建议仅允许面板服务器 IP 访问。公网连通情况请在平台查看同步状态。'
|
||||
Reference in New Issue
Block a user