Files
my-singbox-example/script/singbox.sh
dichgrem db26e0df53 style:add_pic
fix:dns
2025-11-10 17:27:49 +08:00

565 lines
16 KiB
Bash
Raw 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
# install_singbox.sh
# 版本号
SCRIPT_VERSION="1.12.12"
set -euo pipefail
# 颜色定义
RED=$'\033[31m'
GREEN=$'\033[32m'
YELLOW=$'\033[33m'
BLUE=$'\033[34m'
CYAN=$'\033[36m'
BOLD=$'\033[1m'
NC=$'\033[0m'
# 权限检查
if [[ $EUID -ne 0 ]]; then
printf "${RED}错误:请以 root 用户或使用 sudo 运行此脚本${NC}\n" >&2
exit 1
fi
# 修复:使用正确的配置目录
CONFIG_DIR=/etc/sing-box
STATE_FILE="$CONFIG_DIR/state.env"
BIN_NAME=sing-box
# 检测网络类型
detect_network_type() {
local has_ipv4=false
local has_ipv6=false
# 检测IPv4
if ping -4 -c1 -W2 8.8.8.8 &>/dev/null || curl -4 -s --connect-timeout 3 https://api.ipify.org &>/dev/null; then
has_ipv4=true
fi
# 检测IPv6
if ping -6 -c1 -W2 2001:4860:4860::8888 &>/dev/null || curl -6 -s --connect-timeout 3 https://api64.ipify.org &>/dev/null; then
has_ipv6=true
fi
if $has_ipv4 && $has_ipv6; then
echo "dual"
elif $has_ipv6; then
echo "ipv6"
elif $has_ipv4; then
echo "ipv4"
else
echo "none"
fi
}
# 获取服务器IP地址
get_server_ip() {
local network_type=$(detect_network_type)
local ip=""
case "$network_type" in
"ipv6")
# 纯IPv6环境
ip=$(curl -6 -s --connect-timeout 5 https://api64.ipify.org 2>/dev/null || \
curl -6 -s --connect-timeout 5 https://ifconfig.co 2>/dev/null || \
ip -6 addr show scope global | grep inet6 | head -n1 | awk '{print $2}' | cut -d'/' -f1)
;;
"dual"|"ipv4")
# 双栈或IPv4环境
ip=$(curl -4 -s --connect-timeout 5 https://api.ipify.org 2>/dev/null || \
curl -4 -s --connect-timeout 5 https://ifconfig.me 2>/dev/null || \
ip -4 addr show scope global | grep inet | head -n1 | awk '{print $2}' | cut -d'/' -f1)
;;
*)
# 无法检测到网络
ip=$(ip addr show scope global | grep -oP '(?<=inet6?\s)\S+' | head -n1 | cut -d'/' -f1)
;;
esac
echo "$ip"
}
# 检查本地与远程版本,并提示
check_update() {
if command -v curl &>/dev/null && command -v grep &>/dev/null; then
LOCAL_VER=$($BIN_NAME version 2>/dev/null | head -n1 | awk '{print $NF}') || LOCAL_VER="未安装"
local network_type=$(detect_network_type)
local curl_opts=""
[[ "$network_type" == "ipv6" ]] && curl_opts="-6"
LATEST_VER=$(curl $curl_opts -s --connect-timeout 10 https://api.github.com/repos/SagerNet/sing-box/releases/latest 2>/dev/null |
grep '"tag_name"' | head -n1 | cut -d '"' -f4 | sed 's/^v//') || LATEST_VER="未知"
if [[ "$LOCAL_VER" != "$LATEST_VER" && "$LATEST_VER" != "未知" ]]; then
printf "${YELLOW}检测到新版本:${LATEST_VER},当前版本:${LOCAL_VER}。请选择 8) 升级 Sing-box 二进制。${NC}\n"
fi
fi
}
# 安装 Sing-box 并生成配置
install_singbox() {
printf "${CYAN}===== 安装 Sing-box 并生成配置 =====${NC}\n"
printf "${YELLOW}请输入用户名称 (name 字段,例如 AK-JP-100G)${NC}"
read -r NAME
[[ -z "$NAME" ]] && { printf "${RED}名称不能为空,退出。${NC}\n" >&2; exit 1; }
printf "${YELLOW}请输入 SNI 域名 (默认: s0.awsstatic.com)${NC}"
read -r SNI
SNI=${SNI:-s0.awsstatic.com}
read -rp "请输入监听端口 (默认: 443) " PORT
PORT=${PORT:-443}
update_singbox
hash -r
BIN_PATH=$(command -v $BIN_NAME || true)
[[ -z "$BIN_PATH" ]] && {
printf "${RED}未找到 $BIN_NAME,可执行文件路径异常,请检查安装${NC}\n" >&2
exit 1
}
VERSION=$($BIN_PATH version | head -n1 | awk '{print $NF}')
printf "${GREEN}已安装/更新 sing-box 版本:%s${NC}\n" "$VERSION"
UUID=$($BIN_PATH generate uuid)
KEY_OUTPUT=$($BIN_PATH generate reality-keypair)
PRIVATE_KEY=$(echo "$KEY_OUTPUT" | awk -F': ' '/PrivateKey/ {print $2}')
PUB_KEY=$(echo "$KEY_OUTPUT" | awk -F': ' '/PublicKey/ {print $2}')
SHORT_ID=$(openssl rand -hex 8)
FP="firefox"
SERVER_IP=$(get_server_ip)
SPX="/"
mkdir -p "$CONFIG_DIR"
# 根据网络类型选择 DNS
NET_TYPE=$(detect_network_type)
if [[ "$NET_TYPE" == "ipv6" ]]; then
DNS_SERVER1="2606:4700:4700::1111" # Cloudflare IPv6
DNS_SERVER2="2620:fe::fe" # Quad9 IPv6
DNS_STRATEGY="prefer_ipv6"
elif [[ "$NET_TYPE" == "dual" || "$NET_TYPE" == "ipv4" ]]; then
DNS_SERVER1="8.8.8.8"
DNS_SERVER2="1.1.1.1"
DNS_STRATEGY="prefer_ipv4"
else
DNS_SERVER1="8.8.8.8"
DNS_SERVER2="1.1.1.1"
DNS_STRATEGY="prefer_ipv4"
fi
cat >"$CONFIG_DIR/config.json" <<EOF
{
"log": {
"disabled": false,
"level": "info"
},
"dns": {
"servers": [
{
"type": "tls",
"server": "$DNS_SERVER1",
"server_port": 853,
"tls": { "min_version": "1.2" }
},
{
"type": "tls",
"server": "$DNS_SERVER2",
"server_port": 853,
"tls": { "min_version": "1.2" }
}
],
"strategy": "$DNS_STRATEGY"
},
"inbounds": [
{
"type": "vless",
"tag": "VLESSReality",
"listen": "::",
"listen_port": ${PORT},
"users": [
{
"name": "${NAME}",
"uuid": "${UUID}",
"flow": "xtls-rprx-vision"
}
],
"tls": {
"enabled": true,
"server_name": "${SNI}",
"reality": {
"enabled": true,
"handshake": {
"server": "${SNI}",
"server_port": 443
},
"private_key": "${PRIVATE_KEY}",
"short_id": "${SHORT_ID}"
}
}
}
],
"route": {
"rules": [
{ "type": "default", "outbound": "direct" }
]
},
"outbounds": [
{ "type": "direct", "tag": "direct" }
]
}
EOF
cat >"$STATE_FILE" <<EOF
NAME="$NAME"
SNI="$SNI"
UUID="$UUID"
PUB_KEY="$PUB_KEY"
SHORT_ID="$SHORT_ID"
FP="$FP"
SERVER_IP="$SERVER_IP"
PORT="$PORT"
SPX="$SPX"
EOF
systemctl enable sing-box.service
systemctl restart sing-box.service
printf "${GREEN}安装并启动完成DNS 已根据网络类型自动配置。${NC}\n"
}
# 查看服务状态
status_singbox() {
printf "${CYAN}===== Sing-box 服务状态 =====${NC}\n"
if systemctl status sing-box.service &>/dev/null; then
systemctl status sing-box.service --no-pager
else
printf "${YELLOW}服务未安装。${NC}\n"
fi
}
# 开启服务
start_singbox() {
systemctl daemon-reload
systemctl enable sing-box.service 2>/dev/null || true
systemctl start sing-box.service 2>/dev/null || true
}
# 停止服务
stop_singbox(){
systemctl stop sing-box.service 2>/dev/null || true
systemctl disable sing-box.service 2>/dev/null || true
systemctl daemon-reload
}
# 显示 VLESS Reality 链接 + 二维码
show_link() {
printf "${CYAN}===== 您的 VLESS Reality 链接 =====${NC}\n"
# 如果状态文件不存在,尝试从 config.json 读取并生成
if [[ ! -f "$STATE_FILE" ]]; then
if [[ -f "$CONFIG_DIR/config.json" ]]; then
NAME=$(grep -oP '"name"\s*:\s*"\K[^"]+' "$CONFIG_DIR/config.json")
UUID=$(grep -oP '"uuid"\s*:\s*"\K[^"]+' "$CONFIG_DIR/config.json")
SNI=$(grep -oP '"server_name"\s*:\s*"\K[^"]+' "$CONFIG_DIR/config.json")
PUB_KEY=$(grep -oP '"public_key"\s*:\s*"\K[^"]+' "$CONFIG_DIR/config.json")
SHORT_ID=$(grep -oP '"short_id"\s*:\s*"\K[^"]+' "$CONFIG_DIR/config.json")
FP="firefox"
SERVER_IP=$(get_server_ip)
PORT=$(grep -oP '"listen_port"\s*:\s*\K[^,}]+' "$CONFIG_DIR/config.json")
SPX="/"
# 保存新的 state.env
mkdir -p "$CONFIG_DIR"
cat >"$STATE_FILE" <<EOF
NAME="$NAME"
SNI="$SNI"
UUID="$UUID"
PUB_KEY="$PUB_KEY"
SHORT_ID="$SHORT_ID"
FP="$FP"
SERVER_IP="$SERVER_IP"
PORT="$PORT"
SPX="$SPX"
EOF
else
printf "${RED}未找到配置文件,请先安装。${NC}\n"
return
fi
fi
# 读取 state.env
source "$STATE_FILE"
local formatted_ip="$SERVER_IP"
if [[ "$SERVER_IP" =~ ":" ]]; then
formatted_ip="[$SERVER_IP]"
fi
LINK="vless://${UUID}@${formatted_ip}:${PORT}?security=reality&sni=${SNI}&fp=${FP}&pbk=${PUB_KEY}&sid=${SHORT_ID}&spx=${SPX}&type=tcp&flow=xtls-rprx-vision&encryption=none#${NAME}"
printf "${GREEN}%s${NC}\n\n" "$LINK"
# 生成二维码
if command -v qrencode &>/dev/null; then
printf "${CYAN}===== 二维码 =====${NC}\n"
qrencode -t ANSIUTF8 "$LINK"
printf "\n"
else
printf "${YELLOW}未安装 qrencode无法生成二维码。\n"
printf "安装方法apt install qrencode 或 yum install qrencode${NC}\n"
fi
}
# 卸载 Sing-box
uninstall_singbox() {
printf "${CYAN}===== 卸载 Sing-box =====${NC}\n"
# 停止并禁用服务
systemctl stop sing-box.service 2>/dev/null || true
systemctl disable sing-box.service 2>/dev/null || true
systemctl daemon-reload
# 删除服务文件
rm -f /etc/systemd/system/sing-box.service
# 删除配置目录
rm -rf /etc/singbox
rm -rf /etc/sing-box
# 删除 Sing-box 可执行文件
rm -f /usr/bin/sing-box
# 删除 env 文件
rm -f /etc/sing-box/state.env
printf "${GREEN}卸载完成。${NC}\n"
}
# 重新安装
reinstall_singbox() {
uninstall_singbox
install_singbox
}
# 升级/安装 Sing-box 二进制
update_singbox() {
printf "${CYAN}===== 升级/安装 Sing-box 二进制 =====${NC}\n"
set -e -o pipefail
# 检测体系架构
ARCH_RAW=$(uname -m)
case "${ARCH_RAW}" in
'x86_64') ARCH='amd64';;
'x86' | 'i686' | 'i386') ARCH='386';;
'aarch64' | 'arm64') ARCH='arm64';;
'armv7l') ARCH='armv7';;
's390x') ARCH='s390x';;
*) echo "❌ 不支持的架构: ${ARCH_RAW}"; return 1;;
esac
# 检测网络类型
local network_type=$(detect_network_type)
echo "🌐 当前网络模式: $network_type"
local curl_opts=""
case "$network_type" in
"ipv6")
curl_opts="-6"
echo "📡 使用 IPv6 连接"
;;
"dual")
echo "📡 双栈网络,优先使用 IPv4"
;;
"ipv4")
curl_opts="-4"
echo "📡 使用 IPv4 连接"
;;
"none")
echo "⚠️ 无法检测到网络连接,尝试默认方式"
;;
esac
# 获取最新版本号
VERSION=$(curl $curl_opts -fsSL --connect-timeout 15 https://api.github.com/repos/SagerNet/sing-box/releases/latest 2>/dev/null |
grep '"tag_name"' | head -n1 | cut -d '"' -f4 | sed 's/^v//') || VERSION=""
if [[ -z "$VERSION" ]]; then
echo "⚠️ 获取版本失败,尝试备用源..."
VERSION=$(curl $curl_opts -fsSL --connect-timeout 15 https://fastly.jsdelivr.net/gh/SagerNet/sing-box@latest/version.txt 2>/dev/null || echo "")
fi
[[ -z "$VERSION" ]] && { echo "❌ 无法获取最新版本号"; return 1; }
echo "🔖 最新版本v${VERSION}"
PKG_URL="https://github.com/SagerNet/sing-box/releases/download/v${VERSION}/sing-box_${VERSION}_linux_${ARCH}.deb"
echo "⬇️ 正在下载 ${PKG_URL}"
curl $curl_opts -fL --connect-timeout 30 -o /tmp/sing-box.deb "$PKG_URL" || {
echo "❌ 下载失败,请检查网络。"
return 1
}
sudo dpkg -i /tmp/sing-box.deb || {
echo "⚠️ dpkg 安装失败,尝试修复依赖..."
sudo apt-get install -f -y
sudo dpkg -i /tmp/sing-box.deb
}
rm -f /tmp/sing-box.deb
NEW_VER=$($BIN_NAME version 2>/dev/null | head -n1 | awk '{print $NF}')
echo "✅ Sing-box 已升级到版本:$NEW_VER"
echo "🔁 正在重启服务..."
if systemctl restart sing-box.service; then
systemctl daemon-reload
echo "✅ 服务已重启。"
else
echo "⚠️ 服务重启失败,请手动检查。"
fi
}
# 更换 SNI 域名
change_sni() {
printf "${CYAN}===== 更换 SNI 域名 =====${NC}\n"
[[ -f "$CONFIG_DIR/config.json" ]] || {
printf "${RED}配置文件不存在,请先安装。${NC}\n"
return
}
printf "${YELLOW}请输入新的 SNI 域名 (当前: $(
source "$STATE_FILE"
echo "$SNI"
))${NC}"
read -r NEW_SNI
[[ -z "$NEW_SNI" ]] && {
printf "${RED}SNI 域名不能为空,取消更换。${NC}\n"
return
}
sed -i -E '/"reality": *\{/,/}/ s/"server_name": *"[^"]*"/"server_name": "'"$NEW_SNI"'"/' "$CONFIG_DIR/config.json"
sed -i -E '/"handshake": *\{/,/}/ s/"server": *"[^"]*"/"server": "'"$NEW_SNI"'"/' "$CONFIG_DIR/config.json"
sed -i "s/^SNI=.*/SNI=\"$NEW_SNI\"/" "$STATE_FILE"
systemctl restart sing-box.service &&
printf "${GREEN}SNI 已更换为 $NEW_SNI,服务已重启。${NC}\n" ||
printf "${RED}服务重启失败,请手动检查。${NC}\n"
}
# 设置BBR算法
set_bbr() {
if ! sysctl net.ipv4.tcp_available_congestion_control &>/dev/null; then
echo "❌ 系统不支持 TCP 拥塞控制设置"
return 1
fi
echo "📋 支持的 TCP 拥塞控制算法:"
sysctl net.ipv4.tcp_available_congestion_control
current=$(sysctl -n net.ipv4.tcp_congestion_control)
echo "⚡ 当前使用的算法: $current"
if [ "$current" == "bbr" ]; then
echo "✅ 当前已经在使用 BBR"
return 0
fi
read -p "⚠️ 当前使用的不是 BBR是否切换为 BBR(y/n): " confirm
if [[ "$confirm" =~ ^[Yy]$ ]]; then
# 临时生效
sudo sysctl -w net.ipv4.tcp_congestion_control=bbr
echo "✅ 已切换为 BBR临时"
# 永久生效
if ! grep -q "^net.ipv4.tcp_congestion_control" /etc/sysctl.conf; then
echo "net.ipv4.tcp_congestion_control = bbr" | sudo tee -a /etc/sysctl.conf
else
sudo sed -i "s/^net.ipv4.tcp_congestion_control.*/net.ipv4.tcp_congestion_control = bbr/" /etc/sysctl.conf
fi
echo "✅ 已写入 /etc/sysctl.conf重启后永久生效"
else
echo "❌ 未修改 TCP 拥塞控制算法"
fi
}
# 更新脚本自身
update_self() {
local script_path="${BASH_SOURCE[0]}"
local tmp_file="/tmp/install_singbox.sh.tmp"
printf "${CYAN}===== 更新脚本自身 =====${NC}\n"
if command -v curl &>/dev/null; then
local url="https://raw.githubusercontent.com/Dichgrem/singbox-example/refs/heads/main/script/singbox.sh"
echo "$url 下载最新脚本..."
# 根据网络类型选择curl参数
local network_type=$(detect_network_type)
local curl_opts=""
[[ "$network_type" == "ipv6" ]] && curl_opts="-6"
if curl $curl_opts -fsSL --connect-timeout 15 "$url" -o "$tmp_file"; then
echo "下载成功,准备替换本地脚本..."
chmod +x "$tmp_file"
mv "$tmp_file" "$script_path"
echo "脚本更新完成。"
echo "重启脚本..."
exec bash "$script_path"
else
echo "${RED}下载失败,无法更新脚本。${NC}"
rm -f "$tmp_file"
fi
else
echo "${RED}未安装 curl无法自动更新脚本。${NC}"
fi
}
# 菜单主循环
check_update
printf "${BLUE}当前脚本版本:${SCRIPT_VERSION}${NC}\n"
# 显示网络类型
NETWORK_TYPE=$(detect_network_type)
printf "${BLUE}检测到网络类型:${NETWORK_TYPE}${NC}\n"
# 显示 Sing-box 版本
if command -v sing-box >/dev/null 2>&1; then
SINGBOX_VERSION=$(sing-box version 2>/dev/null | head -n 1)
else
SINGBOX_VERSION="未安装"
fi
printf "${BLUE}当前 Sing-box 版本:${SINGBOX_VERSION}${NC}\n"
while true; do
printf "${BOLD}${BLUE}请选择操作:${NC}\n"
printf " ${YELLOW}1)${NC} 安装 Sing-box&&Reality\n"
printf " ${YELLOW}2)${NC} 查看服务状态\n"
printf " ${YELLOW}3)${NC} 开启服务\n"
printf " ${YELLOW}4)${NC} 停止服务\n"
printf " ${YELLOW}5)${NC} 卸载服务\n"
printf " ${YELLOW}6)${NC} 显示节点链接\n"
printf " ${YELLOW}7)${NC} 重新安装 Sing-box\n"
printf " ${YELLOW}8)${NC} 升级 Sing-box 二进制\n"
printf " ${YELLOW}9)${NC} 更换 SNI 域名\n"
printf " ${YELLOW}10)${NC} 设置 BBR 算法\n"
printf " ${YELLOW}11)${NC} 更新脚本自身\n"
printf " ${YELLOW}0)${NC} 退出\n"
printf "${BOLD}输入数字 [0-11]: ${NC}"
read -r choice
case "$choice" in
1) install_singbox ;;
2) status_singbox ;;
3) start_singbox ;;
4) stop_singbox ;;
5) uninstall_singbox ;;
6) show_link ;;
7) reinstall_singbox ;;
8) update_singbox ;;
9) change_sni ;;
10) set_bbr ;;
11) update_self ;;
0)
printf "${GREEN}退出。${NC}\n"
exit 0
;;
*) printf "${RED}无效选项,请重试。${NC}\n" ;;
esac
echo
done