feat: 合并边缘节点运维并完善 Surge 配置安全
迁入通用节点部署工具、离线交付页生成器和共用技能,隔离真实配置与交付产物。统一 Codex 与 Claude Code 开发约定,补充节点迁移与运维文档。 完善 FINAL、策略组引用、输入校验、Surge 字段编码与 AI DNS 例外保护,增加隔离测试和订阅验证工具。 验证:全量类型检查、44 项单测、前端构建、脚本语法、技能入口与凭据隔离检查均通过。
This commit is contained in:
87
.claude/skills/edge-node-multi-protocol/scripts/13-install-singbox-quic.sh
Executable file
87
.claude/skills/edge-node-multi-protocol/scripts/13-install-singbox-quic.sh
Executable file
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env bash
|
||||
# QUIC plane: install sing-box and serve whichever of Hysteria2 / TUIC has ports
|
||||
# configured in config.env, each with a direct and a WARP-clean-exit inbound.
|
||||
#
|
||||
# Additive by design -- safe to run on a node already serving the six TCP inbounds:
|
||||
# * sing-box is its own service on its own UDP ports; 3x-ui / Xray / nginx are
|
||||
# never touched, and nothing here writes to 3x-ui's database or nginx's config.
|
||||
# * The one shared resource is the acme.sh wildcard cert. sing-box reads it at
|
||||
# startup only, so the renew hook is EXTENDED (nginx reload + sing-box restart)
|
||||
# rather than replaced -- otherwise the QUIC plane quietly serves an expired
|
||||
# cert about 60 days later.
|
||||
#
|
||||
# Ports: set PORT_HY2_DIRECT / PORT_HY2_WARP and/or PORT_TUIC_DIRECT / PORT_TUIC_WARP.
|
||||
# Unset pairs are skipped. Putting a Hysteria2 inbound on UDP 443 is deliberate: it
|
||||
# reads as ordinary HTTP/3 traffic to that host, and it does not collide with nginx,
|
||||
# which holds TCP 443 (different protocol, different socket).
|
||||
#
|
||||
# Remember the firewall has two layers on some providers: this script opens the UDP
|
||||
# ports in ufw, but a cloud security group (AWS/GCP/Azure) must allow them too.
|
||||
. "$(dirname "$0")/_lib.sh"
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
require_vars NODE_PASSWORD SUB_COVER DOMAIN_ROOT
|
||||
[ -n "${PORT_HY2_DIRECT:-}${PORT_HY2_WARP:-}${PORT_TUIC_DIRECT:-}${PORT_TUIC_WARP:-}" ] || {
|
||||
echo "ERROR: no QUIC ports set in config.env (PORT_HY2_* / PORT_TUIC_*)" >&2; exit 1; }
|
||||
# TUIC authenticates with uuid+password, so it needs XUI_UUID.
|
||||
if [ -n "${PORT_TUIC_DIRECT:-}${PORT_TUIC_WARP:-}" ]; then require_vars XUI_UUID; fi
|
||||
|
||||
COVER="$SUB_COVER.$DOMAIN_ROOT"
|
||||
CERTDIR="/etc/nginx/ssl/$DOMAIN_ROOT"
|
||||
|
||||
echo "== install sing-box =="
|
||||
run 'if ! command -v sing-box >/dev/null 2>&1; then
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
for i in $(seq 1 60); do fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1 || break; sleep 10; done
|
||||
mkdir -p /etc/apt/keyrings
|
||||
curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc
|
||||
chmod a+r /etc/apt/keyrings/sagernet.asc
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/sagernet.asc] https://deb.sagernet.org/ * *" > /etc/apt/sources.list.d/sagernet.list
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq sing-box >/dev/null || { echo "ERROR: sing-box install failed"; exit 1; }
|
||||
fi
|
||||
sing-box version | head -1'
|
||||
|
||||
echo "== verify the cert the QUIC plane will serve =="
|
||||
run "[ -s '$CERTDIR/fullchain.pem' ] && [ -s '$CERTDIR/privkey.pem' ] || { echo 'ERROR: wildcard cert missing at $CERTDIR — run 03-sign-*-cert.sh first'; exit 1; }
|
||||
echo cert-present"
|
||||
|
||||
echo "== generate config =="
|
||||
push "$HERE/13-singbox-config.py" /root/_singbox_config.py
|
||||
run "COVER='$COVER' CERT_DIR='$CERTDIR' WARP_SOCKS_PORT='${WARP_SOCKS_PORT:-40000}' \
|
||||
NODE_PASSWORD='$NODE_PASSWORD' XUI_UUID='${XUI_UUID:-}' \
|
||||
PORT_HY2_DIRECT='${PORT_HY2_DIRECT:-}' PORT_HY2_WARP='${PORT_HY2_WARP:-}' \
|
||||
HY2_OBFS_PASSWORD='${HY2_OBFS_PASSWORD:-}' \
|
||||
HY2_UP_MBPS='${HY2_UP_MBPS:-500}' HY2_DOWN_MBPS='${HY2_DOWN_MBPS:-500}' \
|
||||
PORT_TUIC_DIRECT='${PORT_TUIC_DIRECT:-}' PORT_TUIC_WARP='${PORT_TUIC_WARP:-}' \
|
||||
TUIC_CC='${TUIC_CC:-bbr}' \
|
||||
python3 /root/_singbox_config.py
|
||||
rm -f /root/_singbox_config.py
|
||||
sing-box check -c /etc/sing-box/config.json && echo config-valid"
|
||||
|
||||
echo "== open the UDP ports in ufw =="
|
||||
UDP_PORTS="${PORT_HY2_DIRECT:-} ${PORT_HY2_WARP:-} ${PORT_TUIC_DIRECT:-} ${PORT_TUIC_WARP:-}"
|
||||
run "for p in $UDP_PORTS; do [ -n \"\$p\" ] && ufw allow \$p/udp >/dev/null 2>&1; done
|
||||
ufw status | grep -E '/udp' || echo 'no udp rules (is ufw active?)'"
|
||||
|
||||
echo "== enable + start =="
|
||||
run 'systemctl enable sing-box >/dev/null 2>&1 || true
|
||||
systemctl restart sing-box
|
||||
sleep 4
|
||||
systemctl is-active sing-box
|
||||
ss -ulnp | grep sing-box || echo "WARN: sing-box has no UDP sockets"'
|
||||
|
||||
echo "== extend the cert-renew hook to restart sing-box =="
|
||||
# Resolve acme.sh on the box rather than writing `~/.acme.sh`: on a sudo-user login
|
||||
# the tilde expands in the *login* shell (HOME=/home/<user>) before sudo runs, so a
|
||||
# root-installed acme.sh is missed and the hook silently never gets updated.
|
||||
run "ACME=\$(for c in /root/.acme.sh/acme.sh \"\$HOME/.acme.sh/acme.sh\"; do [ -x \"\$c\" ] && echo \"\$c\" && break; done)
|
||||
[ -n \"\$ACME\" ] || { echo 'ERROR: acme.sh not found — cannot update the renew hook'; exit 1; }
|
||||
echo \"using \$ACME\"
|
||||
\$ACME --install-cert -d '$DOMAIN_ROOT' --ecc \
|
||||
--key-file '$CERTDIR/privkey.pem' \
|
||||
--fullchain-file '$CERTDIR/fullchain.pem' \
|
||||
--reloadcmd 'systemctl reload nginx; systemctl restart sing-box' 2>&1 | tail -2"
|
||||
|
||||
echo
|
||||
echo "NOTE: on AWS/GCP/Azure the cloud security group must also allow inbound UDP:$UDP_PORTS"
|
||||
Reference in New Issue
Block a user