diff --git a/install.sh b/install.sh index 4380510..24f7a4a 100644 --- a/install.sh +++ b/install.sh @@ -9,7 +9,6 @@ log() { msg="$2" timestamp=$(date '+%Y-%m-%d %H:%M:%S') - # 颜色定义(使用转义序列) GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' @@ -39,62 +38,143 @@ if [ "$(id -u)" -ne 0 ]; then exit 1 fi +# 检查系统版本和型号 +check_system() { + log "INFO" "正在检测系统信息..." + if [ -f /etc/os-release ]; then + . /etc/os-release + SYSTEM_NAME="${PRETTY_NAME:-未知系统}" + SYSTEM_ID="${ID:-unknown}" + SYSTEM_VERSION="${VERSION_ID:-unknown}" + log "INFO" "系统: $SYSTEM_NAME ($SYSTEM_ID $SYSTEM_VERSION)" + else + log "WARN" "无法读取 /etc/os-release,系统信息未知" + SYSTEM_NAME="未知系统" + SYSTEM_ID="unknown" + SYSTEM_VERSION="unknown" + fi + ARCH=$(uname -m) + log "INFO" "架构: $ARCH" + if [ -f /proc/cpuinfo ]; then + HARDWARE_MODEL=$(grep -i "^Model" /proc/cpuinfo | cut -d: -f2 | sed 's/^[ \t]*//') + [ -z "$HARDWARE_MODEL" ] && HARDWARE_MODEL=$(grep -i "^Hardware" /proc/cpuinfo | cut -d: -f2 | sed 's/^[ \t]*//') + [ -z "$HARDWARE_MODEL" ] && HARDWARE_MODEL="未知型号" + else + HARDWARE_MODEL="未知型号" + fi + log "INFO" "硬件型号: $HARDWARE_MODEL" + case "$SYSTEM_ID" in + "ubuntu"|"debian") + if echo "$SYSTEM_VERSION" | grep -q "24.04"; then + log "INFO" "检测到 Ubuntu 24.04 或类似系统,使用 nftables" + USE_NFTABLES="yes" + else + log "INFO" "检测到 Debian/Ubuntu 系统,使用 iptables" + USE_NFTABLES="no" + fi + ;; + *) + log "WARN" "未知系统类型 ($SYSTEM_ID),默认使用 iptables" + USE_NFTABLES="no" + ;; + esac + if echo "$HARDWARE_MODEL" | grep -qi "NanoPi R2S"; then + log "INFO" "检测到 NanoPi R2S,优化网络接口检测" + EXPECTED_INTERFACE="end0" + else + EXPECTED_INTERFACE="" + fi +} + # 获取默认网络接口和IP地址 get_network_info() { - log "DEBUG" "开始检测网络接口和 IP 地址..." + log "INFO" "正在检测网络信息..." INTERFACE=$(ip route | grep default | awk '{print $5}' | head -n1) if [ -z "$INTERFACE" ]; then log "ERROR" "无法检测到默认网络接口" exit 1 fi + if [ -n "$EXPECTED_INTERFACE" ] && [ "$INTERFACE" != "$EXPECTED_INTERFACE" ]; then + log "WARN" "检测到的接口 ($INTERFACE) 与预期 ($EXPECTED_INTERFACE) 不符,使用检测到的接口" + fi IP_ADDR=$(ip -4 addr show "$INTERFACE" | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | head -n1) if [ -z "$IP_ADDR" ]; then log "ERROR" "无法获取 IP 地址" exit 1 fi - log "INFO" "检测到网络接口: $INTERFACE, IP 地址: $IP_ADDR" + NETMASK=$(ip -4 addr show "$INTERFACE" | grep -oP '(?<=inet\s)\d+(\.\d+){3}/\d+' | head -n1 | cut -d'/' -f2) + if [ -z "$NETMASK" ]; then + log "WARN" "无法检测子网掩码,默认使用 24 (255.255.255.0)" + NETMASK="24" + fi + GATEWAY=$(ip route | grep default | awk '{print $3}' | head -n1) + if [ -z "$GATEWAY" ]; then + log "ERROR" "无法检测到网关地址" + exit 1 + fi + log "INFO" "网络信息: 接口 $INTERFACE, IP $IP_ADDR/$NETMASK, 网关 $GATEWAY" } # 检查网络连接 check_network() { log "INFO" "检查网络连接..." - log "DEBUG" "执行 ping 测试到 8.8.8.8..." if ! ping -c 3 8.8.8.8 > /dev/null 2>&1; then log "ERROR" "无法连接到网络,请检查网络状态" exit 1 fi - log "DEBUG" "网络连接测试成功" + log "INFO" "网络连接正常" } # 更新系统和安装工具 update_and_install() { - log "INFO" "是否快速安装以跳过系统更新?(若安装失败请不要跳过,默认 N) [Y/N]: " + log "INFO" "快速安装(跳过更新)?[Y/N,默认N]: " read FAST_INSTALL case "$FAST_INSTALL" in [Yy]*) - log "INFO" "用户选择快速安装,跳过 apt upgrade..." + log "INFO" "选择快速安装,跳过系统更新" ;; *) - log "INFO" "用户选择完整安装,将执行 apt upgrade..." + log "INFO" "选择完整安装,执行系统更新" FAST_INSTALL="N" ;; esac - log "INFO" "更新软件源..." - log "DEBUG" "执行 apt update..." - apt update > /dev/null 2>&1 || { log "ERROR" "apt update 失败"; exit 1; } + log "INFO" "正在更新软件源..." + if ! apt update > /dev/null 2>&1; then + log "WARN" "软件源更新失败,尝试修复..." + apt update --fix-missing > /dev/null 2>&1 || { log "ERROR" "软件源更新失败,请检查网络或 /etc/apt/sources.list"; exit 1; } + else + log "INFO" "软件源更新完成" + fi if [ "$FAST_INSTALL" != "Y" ] && [ "$FAST_INSTALL" != "y" ]; then - log "INFO" "执行系统更新(此过程可能较耗时)..." - log "DEBUG" "执行 apt upgrade..." - apt upgrade -y > /dev/null 2>&1 || log "WARN" "apt upgrade 失败,继续执行..." + log "INFO" "正在更新系统(可能耗时较长)..." + apt upgrade -y > /dev/null 2>&1 || log "WARN" "系统更新失败,继续执行" fi log "INFO" "安装必要工具..." - log "DEBUG" "安装 wget、tar、iptables..." - apt install -y wget tar iptables > /dev/null 2>&1 || { log "ERROR" "安装基本工具失败"; exit 1; } - log "DEBUG" "尝试安装 ip6tables..." - apt install -y ip6tables > /dev/null 2>&1 || log "WARN" "无法安装 ip6tables,IPv6 支持可能受限,继续执行..." + apt install -y wget tar > /dev/null 2>&1 || { log "ERROR" "安装基本工具失败,请检查软件源"; exit 1; } + + if [ "$USE_NFTABLES" = "yes" ]; then + log "INFO" "安装 nftables..." + apt install -y nftables > /dev/null 2>&1 || { log "ERROR" "安装 nftables 失败"; exit 1; } + else + log "INFO" "安装 iptables..." + apt install -y iptables > /dev/null 2>&1 || { log "ERROR" "安装 iptables 失败"; exit 1; } + update-alternatives --set iptables /usr/sbin/iptables-legacy > /dev/null 2>&1 + fi + + apt install -y ip6tables > /dev/null 2>&1 || log "WARN" "无法安装 ip6tables,IPv6 支持可能受限" + + if ! command -v ipcalc > /dev/null 2>&1; then + log "INFO" "安装 ipcalc..." + apt install -y ipcalc > /dev/null 2>&1 || log "WARN" "安装 ipcalc 失败,使用默认子网掩码" + fi + + if ! command -v netplan > /dev/null 2>&1; then + log "INFO" "安装 netplan..." + apt install -y netplan.io > /dev/null 2>&1 || { log "ERROR" "安装 netplan.io 失败"; exit 1; } + fi } # 安装 sing-box @@ -108,32 +188,29 @@ install_singbox() { i386|i686) ARCH="386";; *) log "ERROR" "不支持的架构: $ARCH"; exit 1;; esac - log "INFO" "检测到系统: $OS-$ARCH" + log "INFO" "系统架构: $OS-$ARCH" SINGBOX_VERSION="1.11.4" DEFAULT_SINGBOX_URL="https://gh.aaa.team/github.com/SagerNet/sing-box/releases/download/v${SINGBOX_VERSION}/sing-box-${SINGBOX_VERSION}-${OS}-${ARCH}.tar.gz" - # 提示用户是否使用自定义下载地址 - log "INFO" "是否使用自定义 sing-box 下载地址?(默认 N,使用 ${DEFAULT_SINGBOX_URL}) [Y/N]: " + log "INFO" "使用自定义 sing-box 下载地址?[Y/N,默认N]: " read USE_CUSTOM_URL case "$USE_CUSTOM_URL" in [Yy]*) - log "INFO" "请输入自定义 sing-box 下载地址(需为 .tar.gz 格式):" + log "INFO" "请输入自定义下载地址(需为 .tar.gz 格式): " read SINGBOX_URL [ -z "$SINGBOX_URL" ] && { log "ERROR" "下载地址不能为空"; exit 1; } - log "INFO" "使用自定义下载地址: $SINGBOX_URL" + log "INFO" "使用自定义地址: $SINGBOX_URL" ;; *) SINGBOX_URL="$DEFAULT_SINGBOX_URL" - log "INFO" "使用默认下载地址: $SINGBOX_URL" + log "INFO" "使用默认地址: $SINGBOX_URL" ;; esac - log "INFO" "正在下载 sing-box..." - log "DEBUG" "执行 wget 下载 sing-box..." + log "INFO" "下载 sing-box..." wget -q -O sing-box.tar.gz "$SINGBOX_URL" || { log "ERROR" "下载 sing-box 失败"; exit 1; } log "INFO" "解压 sing-box..." - log "DEBUG" "执行 tar 解压..." tar -xzf sing-box.tar.gz || { log "ERROR" "解压 sing-box 失败"; exit 1; } [ -f "sing-box-${SINGBOX_VERSION}-${OS}-${ARCH}/sing-box" ] || { log "ERROR" "未找到 sing-box 可执行文件"; exit 1; } mv "sing-box-${SINGBOX_VERSION}-${OS}-${ARCH}/sing-box" /usr/local/bin/ @@ -141,40 +218,83 @@ install_singbox() { rm -rf sing-box.tar.gz "sing-box-${SINGBOX_VERSION}-${OS}-${ARCH}" command -v sing-box > /dev/null 2>&1 || { log "ERROR" "sing-box 安装失败"; exit 1; } - log "INFO" "sing-box 安装成功" + log "INFO" "sing-box 安装完成" } # 配置自动更新脚本 setup_update_script() { - log "INFO" "设置自动更新脚本..." + log "INFO" "配置自动更新脚本..." UP_CONFIG_URL="https://gh.aaa.team/https://raw.githubusercontent.com/Lsmoisu/sing-box-shell/refs/heads/main/upconfig.sh" - log "INFO" "正在下载更新脚本..." - log "DEBUG" "执行 wget 下载 upconfig.sh..." - wget -q -O /usr/local/bin/upconfig.sh "$UP_CONFIG_URL" || { log "ERROR" "下载 upconfig.sh 失败"; exit 1; } + log "INFO" "下载更新脚本..." + wget -q -O /usr/local/bin/upconfig.sh "$UP_CONFIG_URL" || { log "ERROR" "下载更新脚本失败"; exit 1; } chmod +x /usr/local/bin/upconfig.sh - [ -x /usr/local/bin/upconfig.sh ] || { log "ERROR" "upconfig.sh 设置权限失败"; exit 1; } + [ -x /usr/local/bin/upconfig.sh ] || { log "ERROR" "更新脚本权限设置失败"; exit 1; } - log "DEBUG" "配置 crontab 任务..." - (crontab -l 2>/dev/null | grep -v "upconfig.sh"; echo "* * * * * bash /usr/local/bin/upconfig.sh > /dev/null 2>&1") | crontab - || log "WARN" "crontab 配置失败" + (crontab -l 2>/dev/null | grep -v "upconfig.sh"; echo "* * * * * bash /usr/local/bin/upconfig.sh > /dev/null 2>&1") | crontab - || log "WARN" "配置 crontab 失败" + log "INFO" "自动更新脚本配置完成" } # 下载配置文件 download_config() { - DEFAULT_CONFIG_URL="https://sub.aaa.team/config-zz-realip-route" - log "INFO" "请输入配置文件 URL(回车使用默认: $DEFAULT_CONFIG_URL):" + DEFAULT_CONFIG_URL="https://sub.aaa.team/config-66ca38b4bd8d" + log "INFO" "请输入配置文件 URL(回车使用默认: $DEFAULT_CONFIG_URL): " read CONFIG_URL [ -z "$CONFIG_URL" ] && CONFIG_URL="$DEFAULT_CONFIG_URL" && log "INFO" "使用默认配置文件: $CONFIG_URL" mkdir -p /etc/sing-box - log "INFO" "正在下载配置文件: $CONFIG_URL" - log "DEBUG" "执行 wget 下载配置文件..." + log "INFO" "下载配置文件..." wget -q -O /etc/sing-box/config.json "$CONFIG_URL" || { log "ERROR" "下载配置文件失败"; exit 1; } + log "INFO" "配置文件下载完成" } +# 释放 53 端口 +release_port_53() { + log "INFO" "检查并释放 53 端口..." + + # 检查 53 端口是否被占用(TCP 和 UDP) + PORT_53_TCP=$(ss -tuln | grep -E ":53\s+.*(0.0.0.0|\[::\]|127.0.0.53|127.0.0.54)" | awk '{print $1 " " $5}' | sed 's/ */ /g') + PORT_53_UDP=$(ss -uuln | grep -E ":53\s+.*(0.0.0.0|\[::\]|127.0.0.53|127.0.0.54)" | awk '{print $1 " " $5}' | sed 's/ */ /g') + if [ -z "$PORT_53_TCP" ] && [ -z "$PORT_53_UDP" ]; then + log "INFO" "53 端口(TCP 和 UDP)未被占用,无需释放" + return 0 + fi + + # 显示占用详情(优化格式) + if [ -n "$PORT_53_TCP" ]; then + TCP_INFO=$(echo "$PORT_53_TCP" | tr '\n' '; ' | sed 's/; $//') + log "WARN" "53 端口 TCP 被占用: $TCP_INFO" + fi + if [ -n "$PORT_53_UDP" ]; then + UDP_INFO=$(echo "$PORT_53_UDP" | tr '\n' '; ' | sed 's/; $//') + log "WARN" "53 端口 UDP 被占用: $UDP_INFO" + fi + + # 直接停止 systemd-resolved + log "INFO" "尝试停止 systemd-resolved 服务以释放 53 端口..." + systemctl stop systemd-resolved > /dev/null 2>&1 || log "WARN" "无法停止 systemd-resolved,可能未运行" + systemctl disable systemd-resolved > /dev/null 2>&1 || log "WARN" "无法禁用 systemd-resolved,可能已禁用" + + # 等待 1 秒后再次检查 53 端口 + sleep 1 + PORT_53_TCP=$(ss -tuln | grep -E ":53\s+.*(0.0.0.0|\[::\]|127.0.0.53|127.0.0.54)" | awk '{print $1 " " $5}' | sed 's/ */ /g') + PORT_53_UDP=$(ss -uuln | grep -E ":53\s+.*(0.0.0.0|\[::\]|127.0.0.53|127.0.0.54)" | awk '{print $1 " " $5}' | sed 's/ */ /g') + if [ -n "$PORT_53_TCP" ] || [ -n "$PORT_53_UDP" ]; then + TCP_INFO=$(echo "$PORT_53_TCP" | tr '\n' '; ' | sed 's/; $//') + UDP_INFO=$(echo "$PORT_53_UDP" | tr '\n' '; ' | sed 's/; $//') + log "ERROR" "53 端口仍未完全释放,TCP: ${TCP_INFO:-无}, UDP: ${UDP_INFO:-无}" + log "INFO" "请手动检查占用 53 端口的进程并释放(使用 'ss -tulnp | grep :53' 查看)" + exit 1 + fi + + log "INFO" "53 端口释放成功" +} + + + + # 配置服务 setup_service() { log "INFO" "配置 sing-box 服务..." - log "DEBUG" "创建 systemd 服务文件..." cat << EOF > /etc/systemd/system/sing-box.service [Unit] Description=Sing-box Service @@ -190,163 +310,284 @@ User=root WantedBy=multi-user.target EOF - log "DEBUG" "启动 sing-box 服务..." - systemctl daemon-reload && systemctl enable sing-box && systemctl start sing-box || { log "ERROR" "服务配置失败"; exit 1; } + systemctl daemon-reload || { log "ERROR" "systemd 重新加载失败"; exit 1; } + systemctl enable sing-box > /dev/null 2>&1 || { log "ERROR" "启用 sing-box 服务失败"; exit 1; } + + # 尝试启动服务,最多重试 2 次 + MAX_RETRIES=2 + for i in $(seq 0 $MAX_RETRIES); do + systemctl start sing-box || log "WARN" "sing-box 服务启动失败,第 $i 次尝试" + sleep 2 + if systemctl is-active sing-box > /dev/null 2>&1; then + log "INFO" "sing-box 服务启动成功" + return 0 + fi + log "DEBUG" "等待服务启动... (尝试 $i/$MAX_RETRIES)" + done + + log "ERROR" "sing-box 服务启动失败,请检查日志: journalctl -u sing-box" + exit 1 } # 配置网络 configure_network() { log "INFO" "配置网络设置..." - log "DEBUG" "停止 systemd-resolved 服务..." - systemctl stop systemd-resolved 2>/dev/null && systemctl disable systemd-resolved 2>/dev/null || log "WARN" "systemd-resolved 未运行" + log "INFO" "设置接口 $INTERFACE 为静态 IP..." - log "DEBUG" "检查并配置 /etc/resolv.conf..." + if [ -d /etc/netplan ]; then + cat << EOF > /etc/netplan/01-netcfg.yaml +network: + version: 2 + ethernets: + $INTERFACE: + dhcp4: no + addresses: + - $IP_ADDR/$NETMASK + routes: + - to: default + via: $GATEWAY + nameservers: + addresses: [127.0.0.1, 8.8.8.8] +EOF + chmod 600 /etc/netplan/01-netcfg.yaml + netplan apply || { log "ERROR" "应用 netplan 配置失败"; exit 1; } + log "INFO" "已设置静态 IP: $IP_ADDR" + sleep 5 + else + NETMASK_DOT=$(ipcalc -m "$IP_ADDR/$NETMASK" | cut -d= -f2 2>/dev/null || echo "255.255.255.0") + cat << EOF > /etc/network/interfaces +auto lo +iface lo inet loopback + +auto $INTERFACE +iface $INTERFACE inet static + address $IP_ADDR + netmask $NETMASK_DOT + gateway $GATEWAY +EOF + systemctl restart networking || { log "ERROR" "重启网络服务失败"; exit 1; } + log "INFO" "已设置静态 IP: $IP_ADDR" + sleep 5 + fi + + RESOLV_CONF_BACKUP="/etc/resolv.conf.bak" if [ -e /etc/resolv.conf ]; then - # 检查是否为符号链接并移除 - [ -L /etc/resolv.conf ] && rm -f /etc/resolv.conf && log "DEBUG" "已移除 /etc/resolv.conf 符号链接" - # 检查 immutable 属性并移除 - if lsattr /etc/resolv.conf 2>/dev/null | grep -q "i----"; then - log "DEBUG" "检测到 /etc/resolv.conf 为 immutable,正在移除该属性..." - chattr -i /etc/resolv.conf || { log "ERROR" "无法移除 /etc/resolv.conf 的 immutable 属性,请手动检查"; exit 1; } + if [ -L /etc/resolv.conf ]; then + cp -a /etc/resolv.conf "$RESOLV_CONF_BACKUP" + rm -f /etc/resolv.conf + else + mv /etc/resolv.conf "$RESOLV_CONF_BACKUP" 2>/dev/null fi fi - - # 写入新的 resolv.conf - log "DEBUG" "写入 nameserver 127.0.0.1 到 /etc/resolv.conf..." - echo "nameserver 127.0.0.1" > /etc/resolv.conf || { log "ERROR" "无法写入 /etc/resolv.conf,请检查权限或文件系统"; exit 1; } - - # 设置 immutable 属性 - log "DEBUG" "设置 /etc/resolv.conf 为 immutable..." - chattr +i /etc/resolv.conf || log "WARN" "无法锁定 /etc/resolv.conf,可能被其他进程覆盖" - - log "DEBUG" "启用 IP 转发..." + echo "nameserver 127.0.0.1" > /etc/resolv.conf || { log "ERROR" "无法写入 /etc/resolv.conf"; exit 1; } + chattr +i /etc/resolv.conf || log "WARN" "无法锁定 /etc/resolv.conf" + sysctl -w net.ipv4.ip_forward=1 > /dev/null || { log "ERROR" "启用 IPv4 转发失败"; exit 1; } - sysctl -w net.ipv6.conf.all.forwarding=1 > /dev/null 2>&1 || log "WARN" "启用 IPv6 转发失败,可能不支持 IPv6" + sysctl -w net.ipv6.conf.all.forwarding=1 > /dev/null 2>&1 || log "WARN" "启用 IPv6 转发失败" echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf echo "net.ipv6.conf.all.forwarding=1" >> /etc/sysctl.conf + log "INFO" "网络配置完成" } -# 配置 iptables 和 ip6tables +# 配置防火墙 setup_firewall() { log "INFO" "配置防火墙规则..." - log "DEBUG" "清理 IPv4 防火墙规则..." - iptables -F && iptables -t nat -F || { log "ERROR" "清理 IPv4 规则失败"; exit 1; } - - # IPv4 规则 - iptables -A INPUT -p udp --dport 53 -j ACCEPT - iptables -A FORWARD -i "$INTERFACE" -j ACCEPT - iptables -t nat -A POSTROUTING -o "$INTERFACE" -j MASQUERADE - - # 检查 ip6tables 是否可用 - if command -v ip6tables > /dev/null 2>&1; then - log "DEBUG" "清理并配置 IPv6 防火墙规则..." - ip6tables -F || { log "ERROR" "清理 IPv6 规则失败"; exit 1; } - ip6tables -A INPUT -p udp --dport 53 -j ACCEPT - ip6tables -A FORWARD -i "$INTERFACE" -j ACCEPT - else - log "WARN" "ip6tables 未安装,跳过 IPv6 防火墙配置" - fi - - mkdir -p /etc/iptables - log "DEBUG" "保存防火墙规则..." - iptables-save > /etc/iptables/rules.v4 || { log "ERROR" "保存 IPv4 规则失败"; exit 1; } - [ -x /sbin/ip6tables ] && ip6tables-save > /etc/iptables/rules.v6 || log "WARN" "无法保存 IPv6 规则,IPv6 支持可能不可用" - - echo "iptables-persistent iptables-persistent/autosave_v4 boolean true" | debconf-set-selections - echo "iptables-persistent iptables-persistent/autosave_v6 boolean true" | debconf-set-selections - log "DEBUG" "安装 iptables-persistent..." - apt install -y iptables-persistent > /dev/null 2>&1 || { log "ERROR" "安装 iptables-persistent 失败"; exit 1; } + if [ "$USE_NFTABLES" = "yes" ]; then + nft flush ruleset + cat << EOF > /etc/nftables.conf +#!/usr/sbin/nft -f +flush ruleset + +table inet filter { + chain input { + type filter hook input priority 0; policy drop; + ct state established,related accept + ip saddr { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 } accept + tcp dport 22 accept + udp dport 53 accept + } + chain forward { + type filter hook forward priority 0; policy accept; + iifname "$INTERFACE" accept + } } +table inet nat { + chain postrouting { + type nat hook postrouting priority 100; policy accept; + oifname "$INTERFACE" masquerade + } +} +EOF + chmod +x /etc/nftables.conf + nft -f /etc/nftables.conf || { log "ERROR" "应用 nftables 规则失败"; exit 1; } + systemctl enable nftables 2>/dev/null || log "WARN" "无法启用 nftables 服务" + else + iptables -F && iptables -t nat -F || { log "ERROR" "清理 IPv4 规则失败"; exit 1; } + iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT + iptables -A INPUT -s 10.0.0.0/8 -j ACCEPT + iptables -A INPUT -s 172.16.0.0/12 -j ACCEPT + iptables -A INPUT -s 192.168.0.0/16 -j ACCEPT + iptables -A INPUT -p tcp --dport 22 -j ACCEPT + iptables -A INPUT -p udp --dport 53 -j ACCEPT + iptables -A FORWARD -i "$INTERFACE" -j ACCEPT + iptables -t nat -A POSTROUTING -o "$INTERFACE" -j MASQUERADE + + if command -v ip6tables > /dev/null 2>&1; then + ip6tables -F || { log "ERROR" "清理 IPv6 规则失败"; exit 1; } + ip6tables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT + ip6tables -A INPUT -p tcp --dport 22 -j ACCEPT + ip6tables -A INPUT -p udp --dport 53 -j ACCEPT + ip6tables -A FORWARD -i "$INTERFACE" -j ACCEPT + fi + + mkdir -p /etc/iptables + iptables-save > /etc/iptables/rules.v4 || { log "ERROR" "保存 IPv4 规则失败"; exit 1; } + [ -x /sbin/ip6tables ] && ip6tables-save > /etc/iptables/rules.v6 || log "WARN" "无法保存 IPv6 规则" + apt install -y iptables-persistent > /dev/null 2>&1 || log "WARN" "安装 iptables-persistent 失败" + fi + log "INFO" "防火墙配置完成" +} + +# 检查系统版本和型号 +check_system() { + log "INFO" "正在检测系统信息..." + if [ -f /etc/os-release ]; then + . /etc/os-release + SYSTEM_NAME="${PRETTY_NAME:-未知系统}" + SYSTEM_ID="${ID:-unknown}" + SYSTEM_VERSION="${VERSION_ID:-unknown}" + log "INFO" "系统: $SYSTEM_NAME ($SYSTEM_ID $SYSTEM_VERSION)" + else + log "WARN" "无法读取 /etc/os-release,系统信息未知" + SYSTEM_NAME="未知系统" + SYSTEM_ID="unknown" + SYSTEM_VERSION="unknown" + fi + ARCH=$(uname -m) + log "INFO" "架构: $ARCH" + if [ -f /proc/cpuinfo ]; then + # 只取第一行有效的硬件型号,避免重复 + HARDWARE_MODEL=$(grep -i -m 1 "^Model" /proc/cpuinfo | cut -d: -f2 | sed 's/^[ \t]*//') + [ -z "$HARDWARE_MODEL" ] && HARDWARE_MODEL=$(grep -i -m 1 "^Hardware" /proc/cpuinfo | cut -d: -f2 | sed 's/^[ \t]*//') + [ -z "$HARDWARE_MODEL" ] && HARDWARE_MODEL="未知型号" + else + HARDWARE_MODEL="未知型号" + fi + log "INFO" "硬件型号: $HARDWARE_MODEL" + case "$SYSTEM_ID" in + "ubuntu"|"debian") + if echo "$SYSTEM_VERSION" | grep -q "24.04"; then + log "INFO" "检测到 Ubuntu 24.04 或类似系统,使用 nftables" + USE_NFTABLES="yes" + else + log "INFO" "检测到 Debian/Ubuntu 系统,使用 iptables" + USE_NFTABLES="no" + fi + ;; + *) + log "WARN" "未知系统类型 ($SYSTEM_ID),默认使用 iptables" + USE_NFTABLES="no" + ;; + esac + if echo "$HARDWARE_MODEL" | grep -qi "NanoPi R2S"; then + log "INFO" "检测到 NanoPi R2S,优化网络接口检测" + EXPECTED_INTERFACE="end0" + else + EXPECTED_INTERFACE="" + fi +} + # 检查状态 check_status() { log "INFO" "检查服务状态..." log "DEBUG" "检查 sing-box 服务状态..." - systemctl status sing-box > /dev/null 2>&1 && log "INFO" "sing-box 服务运行正常" || log "ERROR" "sing-box 服务未运行" + if systemctl is-active sing-box > /dev/null 2>&1; then + log "INFO" "sing-box 服务运行正常" + else + log "ERROR" "sing-box 服务未运行,请检查日志: journalctl -u sing-box" + exit 1 + fi } # 卸载函数 uninstall() { - log "INFO" "开始卸载 sing-box 及相关配置..." + log "INFO" "开始卸载 sing-box..." + + systemctl stop sing-box 2>/dev/null && log "INFO" "已停止 sing-box 服务" || log "INFO" "sing-box 服务未运行" + systemctl disable sing-box 2>/dev/null && log "INFO" "已禁用 sing-box 服务" || log "INFO" "sing-box 服务未启用" + rm -f /etc/systemd/system/sing-box.service && systemctl daemon-reload || log "WARN" "删除服务文件失败" - # 停止并禁用 sing-box 服务 - log "INFO" "停止并禁用 sing-box 服务..." - systemctl stop sing-box 2>/dev/null - systemctl disable sing-box 2>/dev/null - rm -f /etc/systemd/system/sing-box.service - systemctl daemon-reload - log "DEBUG" "已移除 sing-box 服务文件" + rm -f /usr/local/bin/sing-box && log "INFO" "已移除 sing-box 可执行文件" || log "WARN" "未找到 sing-box 可执行文件" + rm -f /usr/local/bin/upconfig.sh && log "INFO" "已移除更新脚本" || log "WARN" "未找到更新脚本" + (crontab -l 2>/dev/null | grep -v "upconfig.sh") | crontab - || log "WARN" "清理 crontab 失败" + rm -rf /etc/sing-box && log "INFO" "已移除配置文件目录" || log "WARN" "未找到配置文件目录" - # 移除 sing-box 可执行文件 - log "INFO" "移除 sing-box 可执行文件..." - rm -f /usr/local/bin/sing-box && log "DEBUG" "已移除 /usr/local/bin/sing-box" || log "WARN" "未找到 /usr/local/bin/sing-box" - - # 移除自动更新脚本和 crontab 任务 - log "INFO" "移除自动更新脚本和 crontab 任务..." - rm -f /usr/local/bin/upconfig.sh && log "DEBUG" "已移除 /usr/local/bin/upconfig.sh" || log "WARN" "未找到 /usr/local/bin/upconfig.sh" - (crontab -l 2>/dev/null | grep -v "upconfig.sh") | crontab - || log "WARN" "清理 crontab 任务失败" - - # 移除配置文件 - log "INFO" "移除 sing-box 配置文件..." - rm -rf /etc/sing-box && log "DEBUG" "已移除 /etc/sing-box 目录" || log "WARN" "未找到 /etc/sing-box 目录" - - # 还原 /etc/resolv.conf - log "INFO" "还原 /etc/resolv.conf..." + RESOLV_CONF_BACKUP="/etc/resolv.conf.bak" if [ -e /etc/resolv.conf ]; then if lsattr /etc/resolv.conf 2>/dev/null | grep -q "i----"; then - log "DEBUG" "检测到 /etc/resolv.conf 为 immutable,正在移除该属性..." - chattr -i /etc/resolv.conf || log "WARN" "无法移除 /etc/resolv.conf 的 immutable 属性" + chattr -i /etc/resolv.conf fi - # 恢复默认 DNS(这里假设使用 8.8.8.8,可根据系统调整) - echo "nameserver 8.8.8.8" > /etc/resolv.conf && log "DEBUG" "已还原 /etc/resolv.conf" || log "WARN" "无法写入 /etc/resolv.conf" + rm -f /etc/resolv.conf + fi + if [ -e "$RESOLV_CONF_BACKUP" ]; then + mv "$RESOLV_CONF_BACKUP" /etc/resolv.conf || log "WARN" "无法恢复原始 resolv.conf" + else + ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf 2>/dev/null || log "WARN" "无法恢复默认 DNS 配置" fi - # 禁用 IP 转发并清理 sysctl 配置 - log "INFO" "禁用 IP 转发..." - sysctl -w net.ipv4.ip_forward=0 > /dev/null 2>&1 - sysctl -w net.ipv6.conf.all.forwarding=0 > /dev/null 2>&1 + sysctl -w net.ipv4.ip_forward=0 > /dev/null 2>&1 || log "WARN" "禁用 IPv4 转发失败" + sysctl -w net.ipv6.conf.all.forwarding=0 > /dev/null 2>&1 || log "WARN" "禁用 IPv6 转发失败" sed -i '/net.ipv4.ip_forward=1/d' /etc/sysctl.conf sed -i '/net.ipv6.conf.all.forwarding=1/d' /etc/sysctl.conf - sysctl -p > /dev/null 2>&1 && log "DEBUG" "已清理 /etc/sysctl.conf 中的 IP 转发配置" || log "WARN" "清理 sysctl 配置失败" + sysctl -p > /dev/null 2>&1 || log "WARN" "应用 sysctl 配置失败" - # 清理防火墙规则 - log "INFO" "清理防火墙规则..." - iptables -F && iptables -t nat -F && log "DEBUG" "已清理 IPv4 规则" || log "WARN" "清理 IPv4 规则失败" - if command -v ip6tables > /dev/null 2>&1; then - ip6tables -F && log "DEBUG" "已清理 IPv6 规则" || log "WARN" "清理 IPv6 规则失败" + if [ "$USE_NFTABLES" = "yes" ]; then + nft flush ruleset && rm -f /etc/nftables.conf && systemctl disable nftables 2>/dev/null || log "WARN" "清理 nftables 失败" + else + iptables -F && iptables -t nat -F || log "WARN" "清理 IPv4 规则失败" + [ -x /sbin/ip6tables ] && ip6tables -F || log "WARN" "清理 IPv6 规则失败" + rm -rf /etc/iptables && apt remove -y iptables-persistent > /dev/null 2>&1 || log "WARN" "清理 iptables 配置失败" fi - rm -rf /etc/iptables && log "DEBUG" "已移除 /etc/iptables 目录" || log "WARN" "未找到 /etc/iptables 目录" - # 移除 iptables-persistent(可选,因为可能是系统原有组件) - log "INFO" "卸载 iptables-persistent(可选)..." - apt remove -y iptables-persistent > /dev/null 2>&1 && log "DEBUG" "已卸载 iptables-persistent" || log "WARN" "卸载 iptables-persistent 失败或未安装" + if [ -f /etc/netplan/01-netcfg.yaml ]; then + INTERFACE=$(ip route | grep default | awk '{print $5}' | head -n1) + if [ -n "$INTERFACE" ]; then + rm -f /etc/netplan/01-netcfg.yaml + cat << EOF > /etc/netplan/01-netcfg.yaml +network: + version: 2 + ethernets: + $INTERFACE: + dhcp4: yes +EOF + chmod 600 /etc/netplan/01-netcfg.yaml + netplan apply || log "WARN" "恢复 DHCP 失败" + fi + fi - # 恢复 systemd-resolved(如果之前被禁用) - log "INFO" "尝试恢复 systemd-resolved 服务..." - systemctl enable systemd-resolved 2>/dev/null && systemctl start systemd-resolved 2>/dev/null && log "DEBUG" "已恢复 systemd-resolved" || log "WARN" "恢复 systemd-resolved 失败,可能未安装" - - log "INFO" "卸载完成!系统已尽可能恢复到安装前的状态。" + systemctl enable systemd-resolved 2>/dev/null && systemctl start systemd-resolved 2>/dev/null || log "WARN" "恢复 systemd-resolved 失败" + log "INFO" "卸载完成,系统已恢复" } # 主执行流程 main() { + check_system if [ "$1" = "uninstall" ]; then - log "DEBUG" "检测到 uninstall 参数,开始执行卸载..." uninstall else - log "DEBUG" "脚本开始执行安装流程..." get_network_info check_network update_and_install install_singbox setup_update_script download_config - setup_service - configure_network + configure_network # 先配置网络和 DNS + release_port_53 # 释放 53 端口 + setup_service # 最后启动 sing-box setup_firewall check_status log "INFO" "部署完成!请将其他设备的网关和 DNS 指向: $IP_ADDR" - log "DEBUG" "脚本执行完成" fi }