135 lines
6.9 KiB
Bash
135 lines
6.9 KiB
Bash
#!/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 并断开现有连接。'
|