#!/bin/bash # ========================================== # 更新 sing-box 配置文件中的监听端口,并开放对应防火墙规则 # 参数: 端口号(可选,不传则设置一个5000 到 15000的空闲端口) # --- 使用示例 --- # 指定端口 # update_port 8080 # ========================================== update_port(){ local PORT=$1 # 如果没有提供端口参数,则自动查找一个随机空闲端口 if [[ -z "$PORT" ]]; then PORT=$(find_free_port 5000 15000) fi local SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # 脚本文件夹绝对路径 local CONFIG_DIR="$SCRIPT_DIR/../config" modify_json_file "$CONFIG_DIR/config.json" ".inbounds[0].listen_port" "$PORT" port_manage allow "$PORT" echo "设置端口成功" } # 查找随机空闲端口 # 用途: # 在指定端口范围内,随机选择一个当前未被监听(TCP/UDP)的端口 # 参数: # $1: 起始端口(默认 10001) # $2: 结束端口(默认 65535) # $3: 最大随机尝试次数(默认 100) find_free_port() { local START="${1:-10001}" local MAX="${2:-65535}" local ATTEMPTS="${3:-100}" # ------------------------------------------------------------ # 收集当前正在监听的端口(TCP + UDP) # 优先使用 ss,其次 netstat # ------------------------------------------------------------ local USED_PORTS RAW if command -v ss >/dev/null 2>&1; then # ss 输出示例: # LISTEN 0 128 0.0.0.0:22 RAW=$(ss -lntu 2>/dev/null || true) USED_PORTS=$(printf "%s\n" "$RAW" \ | awk '{print $5}' \ | sed -E 's/.*[:]//g') elif command -v netstat >/dev/null 2>&1; then # netstat 输出示例: # tcp 0 0 0.0.0.0:22 RAW=$(netstat -lntu 2>/dev/null || true) USED_PORTS=$(printf "%s\n" "$RAW" \ | awk '{print $4}' \ | sed -E 's/.*[:]//g') else echo "Error: ss or netstat is required to check listening ports." >&2 return 2 fi # ------------------------------------------------------------ # 将已占用端口存入关联数组,便于 O(1) 判断 # 需要 bash 4+ # ------------------------------------------------------------ declare -A USED_MAP local P for P in $USED_PORTS; do # 过滤非数字字段(如 *、:::) if [[ $P =~ ^[0-9]+$ ]]; then USED_MAP["$P"]=1 fi done # ------------------------------------------------------------ # 随机尝试若干次 # 每次随机生成一个端口,只要未被监听就立即返回 # ------------------------------------------------------------ local PORT I for ((I=0; I&2 return 1 } # ========================================== # 函数名: port_manage # 功能: 开放端口和关闭端口 # 参数1: allow 或 deny (操作类型) # 参数2: 端口号 # --- 使用示例 --- # 开启端口 8888 # port_manage allow 8888 # 关闭端口 8888 # port_manage deny 8888 # ========================================== port_manage() { local ACTION=$1 local PORT=$2 if [[ -z "$PORT" ]]; then echo "错误: 未提供端口号" return 1 fi # 统一转换为小写,增强鲁棒性 ACTION=$(echo "$ACTION" | tr '[:upper:]' '[:lower:]') echo "--- 正在对端口 $PORT 执行 $ACTION 操作 ---" # 1. 防火墙 (Firewall) 逻辑处理 if command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active --quiet firewalld; then # CentOS/RHEL/Fedora (firewalld) if [ "$ACTION" == "allow" ]; then sudo firewall-cmd --zone=public --add-port=${PORT}/tcp --permanent sudo firewall-cmd --zone=public --add-port=${PORT}/udp --permanent elif [ "$ACTION" == "deny" ]; then sudo firewall-cmd --zone=public --remove-port=${PORT}/tcp --permanent sudo firewall-cmd --zone=public --remove-port=${PORT}/udp --permanent fi sudo firewall-cmd --reload echo "[OK] firewalld 规则已更新 ($ACTION)" elif command -v ufw >/dev/null 2>&1 && systemctl is-active --quiet ufw; then # Ubuntu/Debian (ufw) if [ "$ACTION" == "allow" ]; then sudo ufw allow ${PORT}/tcp sudo ufw allow ${PORT}/udp elif [ "$ACTION" == "deny" ]; then sudo ufw delete allow ${PORT}/tcp sudo ufw delete allow ${PORT}/udp fi echo "[OK] ufw 规则已更新 ($ACTION)" else # 防火墙未启用时,allow 操作直接跳过,deny 操作走 iptables 兜底 if [ "$ACTION" == "allow" ]; then echo "[!] 防火墙未启用,跳过防火墙规则配置" else # 兜底方案 (iptables) —— 仅 deny 时执行 sudo iptables -D INPUT -p tcp --dport ${PORT} -j ACCEPT 2>/dev/null sudo iptables -D INPUT -p udp --dport ${PORT} -j ACCEPT 2>/dev/null echo "[OK] iptables 规则已执行 ($ACTION)" fi fi # 2. SELinux 逻辑处理(与防火墙状态无关,始终执行) # getenforce 不存在说明未安装 SELinux,直接跳过 command -v getenforce >/dev/null 2>&1 || return 0 # Enforcing 模式才会拦截端口绑定,其他模式无需处理 local SELINUX_STATUS=$(getenforce) [ "$SELINUX_STATUS" == "Enforcing" ] || return 0 # semanage 不存在时的兜底方案: 开启全局网络连接开关(粒度较粗) if ! command -v semanage >/dev/null 2>&1; then if [ "$ACTION" == "allow" ]; then echo "[!] 警告: 未找到 semanage,尝试开启全局网络连接开关..." sudo setsebool -P httpd_can_network_connect 1 fi return 0 fi # 使用 semanage 精确管理端口标签 if [ "$ACTION" == "allow" ]; then # 先尝试添加 (-a),若标签已存在则改用修改 (-m) sudo semanage port -a -t http_port_t -p tcp ${PORT} 2>/dev/null || \ sudo semanage port -m -t http_port_t -p tcp ${PORT} elif [ "$ACTION" == "deny" ]; then sudo semanage port -d -t http_port_t -p tcp ${PORT} 2>/dev/null fi echo "[OK] SELinux 端口权限已更新 ($ACTION)" } # 通过交互方式更新端口 ask_update_port(){ local SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" local CONFIG_FILE="$SCRIPT_DIR/../config/config.json" local CURRENT_PORT=$(get_listen_port) local NEW_PORT=$(ask_update "请输入新的端口号" "$CURRENT_PORT") update_port "$NEW_PORT" } # 获取当前配置的监听端口 get_listen_port(){ local SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" local CONFIG_FILE="$SCRIPT_DIR/../config/config.json" local PORT=$(jq -r ".inbounds[0].listen_port" "$CONFIG_FILE") echo $PORT }