Files

135 lines
6.9 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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__
VERSION=__SING_BOX_VERSION__
echo "安装 sing-box $VERSION(带用户统计模块)"
DOMAIN='' PORT='' LISTEN_PORT=''
while [ "$#" -gt 0 ]; do
[ "$#" -ge 2 ] || { echo "参数缺少值:$1"; exit 1; }
case "$1" in
--domain) DOMAIN=$2;; --port) PORT=$2;; --listen-port) LISTEN_PORT=$2;;
*) echo "未知参数:$1"; exit 1;;
esac
shift 2
done
[ "$(id -u)" -eq 0 ] && [ -d /run/systemd/system ] || { echo '需要 root 和 systemd Linux。'; exit 1; }
ROOT=/etc/hy2-panel-anytls
if [ -d "$ROOT" ] || [ -e /etc/systemd/system/hy2-panel-anytls.service ]; then
[ "$(cat "$ROOT/install-id" 2>/dev/null || true)" = "$INSTALL_ID" ] || { echo '已有其他 AnyTLS 安装,停止以避免覆盖。'; exit 1; }
fi
[ -n "$DOMAIN" ] || read -r -p '域名前缀(例如 jp2)或完整域名:' DOMAIN </dev/tty
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 下的单级域名。'; exit 1; }
[ -n "$PORT" ] || read -r -p 'AnyTLS 公网 TCP 端口:' PORT </dev/tty
if [ -z "$LISTEN_PORT" ] && [ -t 0 ]; then read -r -p "本机 TCP 监听端口(回车使用 $PORT):" LISTEN_PORT </dev/tty; fi
LISTEN_PORT=${LISTEN_PORT:-$PORT}
for port in "$PORT" "$LISTEN_PORT"; do
if ! [[ "$port" =~ ^[1-9][0-9]{0,4}$ ]] || (( port > 65535 )); then echo '端口须为 1–65535。'; exit 1; fi
done
case "$(uname -m)" in x86_64) ARCH=amd64;; aarch64|arm64) ARCH=arm64;; *) echo '暂支持 x86_64 和 ARM64。'; exit 1;; esac
if command -v apt-get >/dev/null; then
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y curl jq ca-certificates python3 python3-venv openssl
elif command -v dnf >/dev/null; then dnf install -y curl jq ca-certificates python3 python3-pip openssl
else echo '当前安装脚本支持 Debian/Ubuntu 或使用 dnf 的发行版。'; exit 1; fi
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT
printf 'Authorization: Bearer %s\n' "$TOKEN" > "$WORK/headers"
# Panel serves a pinned, unmodified upstream build with the V2Ray stats module.
curl -fsSL --retry 3 --connect-timeout 20 --max-time 300 "$PANEL/api/install/sing-box/$ARCH" -o "$WORK/sing-box"
curl -fsSL --retry 3 --max-time 60 "$PANEL/api/install/sing-box/$ARCH?sha256" -o "$WORK/checksum"
printf '%s %s\n' "$(cat "$WORK/checksum")" "$WORK/sing-box" | sha256sum -c -
# Retain local management ports on retries. These are never exposed publicly.
if [ -f "$ROOT/ports.json" ]; then
jq -e --arg domain "$DOMAIN" --argjson port "$PORT" --argjson listen "$LISTEN_PORT" '.domain == $domain and .port == $port and .listenPort == $listen' "$ROOT/ports.json" >/dev/null || { echo '重试安装请使用原域名和端口;变更配置需生成新的安装记录。'; exit 1; }
cp "$ROOT/ports.json" "$WORK/ports.json"
else
python3 - "$PORT" "$LISTEN_PORT" "$DOMAIN" > "$WORK/ports.json" <<'PY'
import json,socket,sys
sockets=[]
for _ in range(2):
s=socket.socket();s.bind(('127.0.0.1',0));sockets.append(s)
print(json.dumps(dict(domain=sys.argv[3],port=int(sys.argv[1]),listenPort=int(sys.argv[2]),statsPort=sockets[0].getsockname()[1],controlPort=sockets[1].getsockname()[1])))
PY
fi
jq -n --slurpfile ports "$WORK/ports.json" '{action:"prepare",ports:$ports[0]}' > "$WORK/request"
api() {
local status
status=$(curl -sS --connect-timeout 15 --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; return 1; fi
}
api
systemctl stop hy2-panel-anytls-agent.service hy2-panel-anytls.service 2>/dev/null || true
install -d -m 700 "$ROOT" /usr/local/lib/hy2-panel-anytls /var/lib/hy2-panel-anytls
printf '%s' "$INSTALL_ID" > "$ROOT/install-id"
install -m 600 "$WORK/ports.json" "$ROOT/ports.json"
jq -er '.config' "$WORK/response" > "$ROOT/config.json"
jq -er '.certificate' "$WORK/response" > "$ROOT/server.crt"
jq -er '.privateKey' "$WORK/response" > "$ROOT/server.key"
jq -e '.agent' "$WORK/response" > "$ROOT/agent.json"
jq -er '.agentSource' "$WORK/response" > /usr/local/lib/hy2-panel-anytls/agent.py
jq -er '.statsProto' "$WORK/response" > /usr/local/lib/hy2-panel-anytls/stats.proto
install -m 755 "$WORK/sing-box" /usr/local/lib/hy2-panel-anytls/sing-box
/usr/local/lib/hy2-panel-anytls/sing-box check -c "$ROOT/config.json"
python3 -m venv /usr/local/lib/hy2-panel-anytls/venv
/usr/local/lib/hy2-panel-anytls/venv/bin/pip -q install 'grpcio==1.76.0' 'grpcio-tools==1.76.0'
/usr/local/lib/hy2-panel-anytls/venv/bin/python -m grpc_tools.protoc -I /usr/local/lib/hy2-panel-anytls --python_out=/usr/local/lib/hy2-panel-anytls /usr/local/lib/hy2-panel-anytls/stats.proto
cat > /etc/systemd/system/hy2-panel-anytls.service <<'UNIT'
[Unit]
Description=AnyTLS sing-box managed by HY2 Panel
After=network-online.target
[Service]
ExecStart=/usr/local/lib/hy2-panel-anytls/sing-box run -c /etc/hy2-panel-anytls/config.json
Restart=on-failure
RestartSec=3
LimitNOFILE=1048576
NoNewPrivileges=true
ProtectHome=true
ProtectSystem=strict
PrivateTmp=true
UNIT
cat > /etc/systemd/system/hy2-panel-anytls-agent.service <<'UNIT'
[Unit]
Description=HY2 Panel AnyTLS sync agent
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/usr/local/lib/hy2-panel-anytls/venv/bin/python /usr/local/lib/hy2-panel-anytls/agent.py
ExecStopPost=/bin/systemctl stop hy2-panel-anytls.service
Restart=always
RestartSec=5
NoNewPrivileges=true
ProtectHome=true
ProtectSystem=strict
ReadWritePaths=/etc/hy2-panel-anytls /var/lib/hy2-panel-anytls
PrivateTmp=true
[Install]
WantedBy=multi-user.target
UNIT
systemctl daemon-reload
systemctl restart hy2-panel-anytls.service
CONTROL=$(jq -r '.controlPort' "$ROOT/ports.json")
jq -er '.experimental.clash_api.secret | "Authorization: Bearer " + .' "$ROOT/config.json" > "$WORK/control-header"
READY=0
for attempt in {1..15}; do
if curl -fsS --max-time 2 -H @"$WORK/control-header" "http://127.0.0.1:$CONTROL/connections" -o /dev/null; then READY=1; break; fi
sleep 1
done
[ "$READY" -eq 1 ] || { systemctl stop hy2-panel-anytls.service; echo 'sing-box 健康检查失败。'; exit 1; }
printf '{"action":"complete"}' > "$WORK/request"
for attempt in {1..3}; do
if api; then break; fi
[ "$attempt" -lt 3 ] || { systemctl stop hy2-panel-anytls.service; exit 1; }
sleep 2
done
# Only the agent starts at boot; it requires a valid panel lease before starting the core.
systemctl enable --now hy2-panel-anytls-agent.service
echo "AnyTLS 节点 $SLUG 已安装。TCP $PORT → $LISTEN_PORT;管理端口仅监听本机。"
echo '用户变更约 15 秒同步;应用变更会重启 sing-box 并断开现有连接。'