feat: 更新 setup-microsocks.sh 脚本,添加实例管理功能和来源限制支持
This commit is contained in:
+489
-60
@@ -1,9 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
# Debian/Ubuntu MicroSocks multi-instance installer.
|
||||
# Status messages go to stderr; created socks5:// links go to stdout.
|
||||
# Debian/Ubuntu MicroSocks multi-instance installer and manager.
|
||||
# Status messages go to stderr; socks5:// links and instance lists go to stdout.
|
||||
|
||||
ACTION="create"
|
||||
COUNT=0
|
||||
COUNT_SET=0
|
||||
START_PORT=1080
|
||||
@@ -12,6 +13,8 @@ LISTEN_ADDR="0.0.0.0"
|
||||
PUBLIC_HOST=""
|
||||
COMMON_USER=""
|
||||
COMMON_PASS=""
|
||||
ALLOW_LIST=""
|
||||
ALLOW_ALL=0
|
||||
NO_AUTH=0
|
||||
AUTO_BIND=0
|
||||
REPLACE=0
|
||||
@@ -22,7 +25,17 @@ APT_UPDATED=0
|
||||
MICROSOCKS_BIN=""
|
||||
WORK_DIR=""
|
||||
CONF_DIR="/etc/microsocks"
|
||||
ACL_FILE="$CONF_DIR/acl.nft"
|
||||
UNIT_FILE="/etc/systemd/system/microsocks@.service"
|
||||
SOURCE_BIN="/usr/local/bin/microsocks"
|
||||
|
||||
INST_LISTEN=""
|
||||
INST_USER=""
|
||||
INST_PASS=""
|
||||
INST_BIND=""
|
||||
INST_HOST=""
|
||||
INST_ALLOW=""
|
||||
INST_FIREWALL=0
|
||||
|
||||
declare -a BIND_ADDRESSES=()
|
||||
declare -a RESOLVED_BINDS=()
|
||||
@@ -30,6 +43,8 @@ declare -a PORTS=()
|
||||
declare -a USERS=()
|
||||
declare -a PASSWORDS=()
|
||||
declare -a LOCAL_ADDRESSES=()
|
||||
declare -a ALLOW_ARGS=()
|
||||
declare -a TARGET_PORTS=()
|
||||
|
||||
|
||||
log() {
|
||||
@@ -45,27 +60,49 @@ die() {
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
用法:sudo ./setup-microsocks.sh [选项]
|
||||
用法:
|
||||
sudo ./setup_microsocks.sh [create] [选项] 创建代理(默认)
|
||||
sudo ./setup_microsocks.sh list [PORT...] 查看实例状态
|
||||
sudo ./setup_microsocks.sh links [PORT...] 输出连接字符串;可用 --host 替换主机
|
||||
sudo ./setup_microsocks.sh update [PORT...] --allow CIDR | --allow-all | --host HOST
|
||||
sudo ./setup_microsocks.sh start|stop|restart [PORT...]
|
||||
sudo ./setup_microsocks.sh remove PORT... [-y]
|
||||
|
||||
创建选项:
|
||||
-n, --count N 创建 N 个代理;多个 --bind 时可省略
|
||||
--start-port PORT 起始端口,默认 1080
|
||||
--listen IP 监听地址,默认 0.0.0.0
|
||||
--host HOST 返回链接使用的公网 IP 或域名
|
||||
--bind IP 绑定出站源地址;可重复指定
|
||||
--auto-bind 自动轮流使用本机所有全局 IP 作为出口
|
||||
--allow CIDR 仅允许这些来源 IP/网段连接;可重复指定或用逗号分隔
|
||||
--user USER 所有实例使用同一用户名
|
||||
--password PASS 所有实例使用同一密码
|
||||
--no-auth 仅允许在回环监听地址上关闭认证
|
||||
--replace 覆盖相同端口的既有 microsocks 实例
|
||||
--open-firewall 自动放行已启用的 UFW/firewalld 端口
|
||||
--non-interactive 不询问确认
|
||||
-y, --non-interactive 不询问确认
|
||||
--dry-run 仅显示计划和链接,不修改系统
|
||||
-h, --help 显示帮助
|
||||
EOF
|
||||
usage_examples
|
||||
}
|
||||
|
||||
|
||||
usage_examples() {
|
||||
cat <<'EOF'
|
||||
|
||||
说明:
|
||||
管理命令省略 PORT 时作用于全部实例(remove 除外);start/stop 同时开启/关闭开机自启。
|
||||
来源限制写入 nftables 表 inet microsocks,与 UFW/firewalld 叠加生效;本机回环不受限。
|
||||
|
||||
示例:
|
||||
sudo ./setup-microsocks.sh
|
||||
sudo ./setup-microsocks.sh --count 3 --start-port 1080
|
||||
sudo ./setup-microsocks.sh --bind 10.0.14.56 --bind 10.0.225.167
|
||||
sudo ./setup_microsocks.sh
|
||||
sudo ./setup_microsocks.sh --count 3 --start-port 1080
|
||||
sudo ./setup_microsocks.sh --bind 10.0.14.56 --bind 10.0.225.167
|
||||
sudo ./setup_microsocks.sh --allow 203.0.113.5,198.51.100.0/24
|
||||
sudo ./setup_microsocks.sh update 1080 --allow 203.0.113.7
|
||||
sudo ./setup_microsocks.sh links
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -75,6 +112,18 @@ need_value() {
|
||||
}
|
||||
|
||||
|
||||
parse_cli() {
|
||||
case "${1:-}" in
|
||||
create|list|links|update|start|stop|restart|remove) ACTION=$1; shift ;;
|
||||
esac
|
||||
if [[ $ACTION == create ]]; then
|
||||
parse_args "$@"
|
||||
else
|
||||
parse_manage_args "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
parse_args() {
|
||||
while (($#)); do
|
||||
case "$1" in
|
||||
@@ -83,13 +132,14 @@ parse_args() {
|
||||
--listen) need_value "$@"; LISTEN_ADDR=$2; shift 2 ;;
|
||||
--host) need_value "$@"; PUBLIC_HOST=$2; shift 2 ;;
|
||||
--bind) need_value "$@"; BIND_ADDRESSES+=("$2"); shift 2 ;;
|
||||
--allow) need_value "$@"; ALLOW_ARGS+=("$2"); shift 2 ;;
|
||||
--user) need_value "$@"; COMMON_USER=$2; shift 2 ;;
|
||||
--password) need_value "$@"; COMMON_PASS=$2; shift 2 ;;
|
||||
--auto-bind) AUTO_BIND=1; shift ;;
|
||||
--no-auth) NO_AUTH=1; shift ;;
|
||||
--replace) REPLACE=1; shift ;;
|
||||
--open-firewall) OPEN_FIREWALL=1; shift ;;
|
||||
--non-interactive) NON_INTERACTIVE=1; shift ;;
|
||||
-y|--non-interactive) NON_INTERACTIVE=1; shift ;;
|
||||
--dry-run) DRY_RUN=1; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) die "未知参数: $1" ;;
|
||||
@@ -98,6 +148,21 @@ parse_args() {
|
||||
}
|
||||
|
||||
|
||||
parse_manage_args() {
|
||||
while (($#)); do
|
||||
case "$1" in
|
||||
--allow) need_value "$@"; ALLOW_ARGS+=("$2"); shift 2 ;;
|
||||
--allow-all) ALLOW_ALL=1; shift ;;
|
||||
--host) need_value "$@"; PUBLIC_HOST=$2; shift 2 ;;
|
||||
-y|--non-interactive) NON_INTERACTIVE=1; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
-*) die "未知参数: $1" ;;
|
||||
*) is_uint "$1" || die "无效的端口: $1"; TARGET_PORTS+=("$1"); shift ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
prompt_number() {
|
||||
local prompt=$1 default=$2 value
|
||||
read -r -p "$prompt [$default]: " value
|
||||
@@ -107,6 +172,7 @@ prompt_number() {
|
||||
|
||||
interactive_options() {
|
||||
[[ -t 0 && $NON_INTERACTIVE -eq 0 ]] || return 0
|
||||
local answer
|
||||
if [[ $COUNT_SET -eq 0 ]]; then
|
||||
COUNT=$(prompt_number "代理数量" 1)
|
||||
COUNT_SET=1
|
||||
@@ -115,9 +181,12 @@ interactive_options() {
|
||||
START_PORT=$(prompt_number "起始端口" 1080)
|
||||
fi
|
||||
if is_uint "$COUNT" && ((COUNT > 1 && ${#BIND_ADDRESSES[@]} == 0)); then
|
||||
local answer
|
||||
read -r -p "是否按检测到的本机 IP 自动分配出口?[y/N]: " answer
|
||||
[[ $answer =~ ^[Yy]$ ]] && AUTO_BIND=1
|
||||
if [[ $answer =~ ^[Yy]$ ]]; then AUTO_BIND=1; fi
|
||||
fi
|
||||
if ((${#ALLOW_ARGS[@]} == 0)); then
|
||||
read -r -p "允许连接的来源 IP/CIDR(逗号分隔,留空不限制): " answer
|
||||
if [[ -n $answer ]]; then ALLOW_ARGS+=("$answer"); fi
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -149,6 +218,39 @@ valid_host() {
|
||||
}
|
||||
|
||||
|
||||
# Prints the merged networks, or the first invalid value on failure.
|
||||
normalize_cidrs() {
|
||||
python3 - "$@" <<'PY'
|
||||
import ipaddress
|
||||
import sys
|
||||
|
||||
values = [v.strip() for arg in sys.argv[1:] for v in arg.split(",") if v.strip()]
|
||||
if not values:
|
||||
sys.exit(1)
|
||||
networks = []
|
||||
for value in values:
|
||||
try:
|
||||
networks.append(ipaddress.ip_network(value, strict=False))
|
||||
except ValueError:
|
||||
print(value)
|
||||
sys.exit(1)
|
||||
merged = []
|
||||
for version in (4, 6):
|
||||
family = [n for n in networks if n.version == version]
|
||||
merged += [str(n) for n in ipaddress.collapse_addresses(family)]
|
||||
print(" ".join(merged))
|
||||
PY
|
||||
}
|
||||
|
||||
|
||||
prepare_allow_list() {
|
||||
((${#ALLOW_ARGS[@]} > 0)) || return 0
|
||||
local output
|
||||
output=$(normalize_cidrs "${ALLOW_ARGS[@]}") || die "无效的 --allow 地址: ${output:-空}"
|
||||
ALLOW_LIST=$output
|
||||
}
|
||||
|
||||
|
||||
validate_options() {
|
||||
if [[ $COUNT_SET -eq 0 ]]; then
|
||||
COUNT=$((${#BIND_ADDRESSES[@]} > 1 ? ${#BIND_ADDRESSES[@]} : 1))
|
||||
@@ -162,6 +264,7 @@ validate_options() {
|
||||
[[ -z $COMMON_PASS ]] || valid_token "$COMMON_PASS" || die "密码只能使用字母、数字和 ._~-"
|
||||
[[ -z $PUBLIC_HOST ]] || valid_host "$PUBLIC_HOST" || die "--host 格式无效"
|
||||
valid_ip "$LISTEN_ADDR" || die "--listen 必须是 IPv4 或 IPv6 地址"
|
||||
prepare_allow_list
|
||||
}
|
||||
|
||||
|
||||
@@ -175,6 +278,26 @@ validate_auth_mode() {
|
||||
}
|
||||
|
||||
|
||||
validate_manage_options() {
|
||||
local allow_used=$((${#ALLOW_ARGS[@]} > 0 || ALLOW_ALL == 1))
|
||||
((ALLOW_ALL == 0 || ${#ALLOW_ARGS[@]} == 0)) || die "--allow 不能和 --allow-all 同时使用"
|
||||
case "$ACTION" in
|
||||
update)
|
||||
((allow_used == 1)) || [[ -n $PUBLIC_HOST ]] ||
|
||||
die "update 需要 --allow、--allow-all 或 --host"
|
||||
;;
|
||||
links) ((allow_used == 0)) || die "links 只支持 --host 选项" ;;
|
||||
*)
|
||||
((allow_used == 0)) || die "$ACTION 不支持 --allow"
|
||||
[[ -z $PUBLIC_HOST ]] || die "$ACTION 不支持 --host"
|
||||
;;
|
||||
esac
|
||||
[[ $ACTION != remove || ${#TARGET_PORTS[@]} -gt 0 ]] || die "remove 必须指定端口"
|
||||
[[ -z $PUBLIC_HOST ]] || valid_host "$PUBLIC_HOST" || die "--host 格式无效"
|
||||
prepare_allow_list
|
||||
}
|
||||
|
||||
|
||||
detect_os() {
|
||||
[[ -r /etc/os-release ]] || die "无法识别操作系统"
|
||||
# shellcheck disable=SC1091
|
||||
@@ -190,7 +313,7 @@ detect_os() {
|
||||
apt_update_once() {
|
||||
[[ $APT_UPDATED -eq 1 ]] && return 0
|
||||
log "更新 APT 索引"
|
||||
DEBIAN_FRONTEND=noninteractive apt-get update
|
||||
DEBIAN_FRONTEND=noninteractive apt-get update >&2
|
||||
APT_UPDATED=1
|
||||
}
|
||||
|
||||
@@ -203,17 +326,19 @@ ensure_base_tools() {
|
||||
((${#missing[@]} == 0)) && return 0
|
||||
[[ $DRY_RUN -eq 0 ]] || die "dry-run 缺少命令: ${missing[*]}"
|
||||
apt_update_once
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y curl openssl python3 iproute2 util-linux ca-certificates
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y curl openssl python3 iproute2 util-linux ca-certificates >&2
|
||||
}
|
||||
|
||||
|
||||
install_from_source() {
|
||||
apt_update_once
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y git build-essential ca-certificates
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y git build-essential ca-certificates >&2
|
||||
log "从上游源码编译 microsocks"
|
||||
rm -rf -- "$WORK_DIR/microsocks"
|
||||
git clone --depth 1 https://github.com/rofl0r/microsocks.git "$WORK_DIR/microsocks"
|
||||
make -C "$WORK_DIR/microsocks"
|
||||
install -m 0755 "$WORK_DIR/microsocks/microsocks" /usr/bin/microsocks
|
||||
make -C "$WORK_DIR/microsocks" >&2
|
||||
install -m 0755 "$WORK_DIR/microsocks/microsocks" "$SOURCE_BIN"
|
||||
MICROSOCKS_BIN=$SOURCE_BIN
|
||||
}
|
||||
|
||||
|
||||
@@ -224,12 +349,11 @@ install_microsocks() {
|
||||
fi
|
||||
apt_update_once
|
||||
log "尝试从 APT 安装 microsocks"
|
||||
if DEBIAN_FRONTEND=noninteractive apt-get install -y microsocks; then
|
||||
if DEBIAN_FRONTEND=noninteractive apt-get install -y microsocks >&2; then
|
||||
MICROSOCKS_BIN=$(command -v microsocks)
|
||||
return 0
|
||||
fi
|
||||
install_from_source
|
||||
MICROSOCKS_BIN=/usr/bin/microsocks
|
||||
}
|
||||
|
||||
|
||||
@@ -401,15 +525,21 @@ format_url_host() {
|
||||
}
|
||||
|
||||
|
||||
format_link() {
|
||||
local user=$1 pass=$2 host port=$4
|
||||
host=$(format_url_host "$3")
|
||||
if [[ -n $user ]]; then
|
||||
printf 'socks5://%s:%s@%s:%s\n' "$user" "$pass" "$host" "$port"
|
||||
else
|
||||
printf 'socks5://%s:%s\n' "$host" "$port"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
print_links() {
|
||||
local index host
|
||||
host=$(format_url_host "$PUBLIC_HOST")
|
||||
local index
|
||||
for ((index = 0; index < COUNT; index++)); do
|
||||
if [[ -n ${USERS[index]} ]]; then
|
||||
printf 'socks5://%s:%s@%s:%s\n' "${USERS[index]}" "${PASSWORDS[index]}" "$host" "${PORTS[index]}"
|
||||
else
|
||||
printf 'socks5://%s:%s\n' "$host" "${PORTS[index]}"
|
||||
fi
|
||||
format_link "${USERS[index]}" "${PASSWORDS[index]}" "$PUBLIC_HOST" "${PORTS[index]}"
|
||||
done
|
||||
}
|
||||
|
||||
@@ -420,8 +550,9 @@ show_plan() {
|
||||
for ((index = 0; index < COUNT; index++)); do
|
||||
bind=${RESOLVED_BINDS[index]:-自动}
|
||||
auth=${USERS[index]:-无认证}
|
||||
log "端口 ${PORTS[index]};出口 $bind;用户 $auth"
|
||||
log "端口 ${PORTS[index]};出口 ${bind};用户 $auth"
|
||||
done
|
||||
log "来源限制: ${ALLOW_LIST:-不限制}"
|
||||
log "链接主机: $PUBLIC_HOST"
|
||||
}
|
||||
|
||||
@@ -434,14 +565,24 @@ confirm_plan() {
|
||||
}
|
||||
|
||||
|
||||
bind_supported() {
|
||||
local usage
|
||||
usage=$({ "$MICROSOCKS_BIN" -h || true; } 2>&1 | grep -m1 '^usage:' || true)
|
||||
# microsocks 1.0.1 lists a bare "-b" flag; only "-b bindaddr" accepts an address.
|
||||
[[ $usage =~ [[:space:]]-b[[:space:]]+[[:alnum:]] ]]
|
||||
}
|
||||
|
||||
|
||||
check_bind_support() {
|
||||
local has_bind=0 address help
|
||||
local has_bind=0 address
|
||||
for address in "${RESOLVED_BINDS[@]}"; do
|
||||
[[ -n $address ]] && has_bind=1
|
||||
done
|
||||
((has_bind == 0)) && return 0
|
||||
help=$({ "$MICROSOCKS_BIN" -h || true; } 2>&1)
|
||||
grep -Eq -- '(^|[[:space:]])-b([[:space:]]|$)' <<<"$help" || die "当前 microsocks 不支持 -b 出站绑定"
|
||||
bind_supported && return 0
|
||||
log "$MICROSOCKS_BIN 的 -b 不能指定出站地址,改用上游源码编译"
|
||||
install_from_source
|
||||
bind_supported || die "当前 microsocks 不支持 -b 出站绑定"
|
||||
}
|
||||
|
||||
|
||||
@@ -492,20 +633,38 @@ write_unit_file() {
|
||||
}
|
||||
|
||||
|
||||
write_instance_config() {
|
||||
local index=$1 port=${PORTS[$1]} temporary="$WORK_DIR/${PORTS[$1]}.conf"
|
||||
render_instance_config() {
|
||||
local auth_options="" bind_options=""
|
||||
[[ -z ${USERS[index]} ]] || auth_options="-u ${USERS[index]} -P ${PASSWORDS[index]}"
|
||||
[[ -z ${RESOLVED_BINDS[index]} ]] || bind_options="-b ${RESOLVED_BINDS[index]}"
|
||||
{
|
||||
printf 'LISTEN_ADDR=%s\n' "$LISTEN_ADDR"
|
||||
printf 'SOCKS_USER=%s\n' "${USERS[index]}"
|
||||
printf 'SOCKS_PASS=%s\n' "${PASSWORDS[index]}"
|
||||
printf 'AUTH_OPTIONS="%s"\n' "$auth_options"
|
||||
printf 'BIND_OPTIONS="%s"\n' "$bind_options"
|
||||
printf 'PUBLIC_HOST=%s\n' "$PUBLIC_HOST"
|
||||
} >"$temporary"
|
||||
install -m 0600 "$temporary" "$CONF_DIR/$port.conf"
|
||||
[[ -z $INST_USER ]] || auth_options="-u $INST_USER -P $INST_PASS"
|
||||
[[ -z $INST_BIND ]] || bind_options="-b $INST_BIND"
|
||||
printf 'LISTEN_ADDR=%s\n' "$INST_LISTEN"
|
||||
printf 'SOCKS_USER=%s\n' "$INST_USER"
|
||||
printf 'SOCKS_PASS=%s\n' "$INST_PASS"
|
||||
printf 'AUTH_OPTIONS="%s"\n' "$auth_options"
|
||||
printf 'BIND_OPTIONS="%s"\n' "$bind_options"
|
||||
printf 'PUBLIC_HOST=%s\n' "$INST_HOST"
|
||||
printf 'ALLOW_CIDRS="%s"\n' "$INST_ALLOW"
|
||||
printf 'FIREWALL_OPENED=%s\n' "$INST_FIREWALL"
|
||||
}
|
||||
|
||||
|
||||
save_instance() {
|
||||
local temporary="$WORK_DIR/$1.conf"
|
||||
render_instance_config >"$temporary"
|
||||
install -m 0600 "$temporary" "$CONF_DIR/$1.conf"
|
||||
}
|
||||
|
||||
|
||||
write_instance_config() {
|
||||
local index=$1
|
||||
INST_LISTEN=$LISTEN_ADDR
|
||||
INST_USER=${USERS[index]}
|
||||
INST_PASS=${PASSWORDS[index]}
|
||||
INST_BIND=${RESOLVED_BINDS[index]}
|
||||
INST_HOST=$PUBLIC_HOST
|
||||
INST_ALLOW=$ALLOW_LIST
|
||||
INST_FIREWALL=$OPEN_FIREWALL
|
||||
save_instance "${PORTS[index]}"
|
||||
}
|
||||
|
||||
|
||||
@@ -520,33 +679,279 @@ install_configuration() {
|
||||
}
|
||||
|
||||
|
||||
ensure_active() {
|
||||
systemctl is-active --quiet "$1" && return 0
|
||||
journalctl -u "$1" -n 30 --no-pager >&2 || true
|
||||
die "$1 启动失败"
|
||||
}
|
||||
|
||||
|
||||
start_services() {
|
||||
local port
|
||||
local port unit
|
||||
for port in "${PORTS[@]}"; do
|
||||
if systemctl is-active --quiet "microsocks@$port.service"; then
|
||||
systemctl restart "microsocks@$port.service"
|
||||
unit="microsocks@$port.service"
|
||||
if systemctl is-active --quiet "$unit"; then
|
||||
systemctl restart "$unit" || true
|
||||
else
|
||||
systemctl enable --now "microsocks@$port.service"
|
||||
fi
|
||||
if ! systemctl is-active --quiet "microsocks@$port.service"; then
|
||||
journalctl -u "microsocks@$port.service" -n 30 --no-pager >&2 || true
|
||||
die "microsocks@$port 启动失败"
|
||||
systemctl enable --now "$unit" || true
|
||||
fi
|
||||
ensure_active "$unit"
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
ufw_active() {
|
||||
command -v ufw >/dev/null && ufw status | head -n 1 | grep -qw active
|
||||
}
|
||||
|
||||
|
||||
firewalld_active() {
|
||||
command -v firewall-cmd >/dev/null && systemctl is-active --quiet firewalld
|
||||
}
|
||||
|
||||
|
||||
open_detected_firewall() {
|
||||
[[ $OPEN_FIREWALL -eq 1 ]] || return 0
|
||||
local port firewalld=0
|
||||
if command -v ufw >/dev/null && ufw status | head -n 1 | grep -qw active; then
|
||||
for port in "${PORTS[@]}"; do ufw allow "$port/tcp"; done
|
||||
local port
|
||||
if ufw_active; then
|
||||
for port in "${PORTS[@]}"; do ufw allow "$port/tcp" >&2; done
|
||||
fi
|
||||
if command -v firewall-cmd >/dev/null && systemctl is-active --quiet firewalld; then
|
||||
firewalld=1
|
||||
for port in "${PORTS[@]}"; do firewall-cmd --permanent --add-port="$port/tcp"; done
|
||||
if firewalld_active; then
|
||||
for port in "${PORTS[@]}"; do firewall-cmd --permanent --add-port="$port/tcp" >&2; done
|
||||
firewall-cmd --reload >&2
|
||||
fi
|
||||
((firewalld == 0)) || firewall-cmd --reload
|
||||
}
|
||||
|
||||
|
||||
close_detected_firewall() {
|
||||
(($# > 0)) || return 0
|
||||
local port
|
||||
if ufw_active; then
|
||||
for port in "$@"; do ufw delete allow "$port/tcp" >&2 || log "UFW 未能删除 $port/tcp"; done
|
||||
fi
|
||||
if firewalld_active; then
|
||||
for port in "$@"; do
|
||||
firewall-cmd --permanent --remove-port="$port/tcp" >&2 || log "firewalld 未能删除 $port/tcp"
|
||||
done
|
||||
firewall-cmd --reload >&2
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
render_acl_rules() {
|
||||
local port cidr v4 v6
|
||||
local -a ports=() cidrs=()
|
||||
mapfile -t ports < <(instance_ports)
|
||||
for port in "${ports[@]}"; do
|
||||
load_instance "$port"
|
||||
[[ -n $INST_ALLOW ]] || continue
|
||||
read -r -a cidrs <<<"$INST_ALLOW"
|
||||
v4="" v6=""
|
||||
for cidr in "${cidrs[@]}"; do
|
||||
if [[ $cidr == *:* ]]; then v6+=${v6:+, }$cidr; else v4+=${v4:+, }$cidr; fi
|
||||
done
|
||||
[[ -z $v4 ]] || printf '\t\ttcp dport %s ip saddr { %s } accept\n' "$port" "$v4"
|
||||
[[ -z $v6 ]] || printf '\t\ttcp dport %s ip6 saddr { %s } accept\n' "$port" "$v6"
|
||||
printf '\t\ttcp dport %s drop\n' "$port"
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
write_acl_file() {
|
||||
local temporary="$WORK_DIR/acl.nft"
|
||||
{
|
||||
printf 'table inet microsocks\ndelete table inet microsocks\n'
|
||||
printf 'table inet microsocks {\n\tchain input {\n'
|
||||
printf '\t\ttype filter hook input priority 0; policy accept;\n'
|
||||
printf '\t\tiif "lo" accept\n%s\n\t}\n}\n' "$1"
|
||||
} >"$temporary"
|
||||
nft -c -f "$temporary" || die "nftables 规则校验失败"
|
||||
install -m 0600 "$temporary" "$ACL_FILE"
|
||||
}
|
||||
|
||||
|
||||
# Reload the allow list before every start so it survives reboots and fails closed.
|
||||
install_acl_hook() {
|
||||
local dropin="$UNIT_FILE.d/10-acl.conf" temporary="$WORK_DIR/10-acl.conf"
|
||||
cat >"$temporary" <<EOF
|
||||
[Service]
|
||||
ExecStartPre=+/bin/sh -c 'test ! -e $ACL_FILE || exec nft -f $ACL_FILE'
|
||||
EOF
|
||||
cmp -s "$temporary" "$dropin" && return 0
|
||||
install -d -m 0755 "$UNIT_FILE.d"
|
||||
install -m 0644 "$temporary" "$dropin"
|
||||
systemctl daemon-reload
|
||||
}
|
||||
|
||||
|
||||
ensure_nft() {
|
||||
command -v nft >/dev/null && return 0
|
||||
apt_update_once
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y nftables >&2
|
||||
}
|
||||
|
||||
|
||||
acl_table_loaded() {
|
||||
command -v nft >/dev/null && nft list table inet microsocks >/dev/null 2>&1
|
||||
}
|
||||
|
||||
|
||||
apply_acl() {
|
||||
local rules
|
||||
rules=$(render_acl_rules)
|
||||
if [[ -z $rules ]]; then
|
||||
rm -f -- "$ACL_FILE"
|
||||
if acl_table_loaded; then
|
||||
nft delete table inet microsocks
|
||||
log "已移除来源限制规则"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
ensure_nft
|
||||
write_acl_file "$rules"
|
||||
install_acl_hook
|
||||
nft -f "$ACL_FILE"
|
||||
log "来源限制已生效(nftables 表 inet microsocks)"
|
||||
}
|
||||
|
||||
|
||||
instance_ports() {
|
||||
local conf name
|
||||
for conf in "$CONF_DIR"/*.conf; do
|
||||
name=${conf##*/}
|
||||
name=${name%.conf}
|
||||
if [[ -f $conf ]] && is_uint "$name"; then
|
||||
printf '%s\n' "$name"
|
||||
fi
|
||||
done | sort -n
|
||||
}
|
||||
|
||||
|
||||
load_instance() {
|
||||
local line key value
|
||||
INST_LISTEN="" INST_USER="" INST_PASS="" INST_BIND=""
|
||||
INST_HOST="" INST_ALLOW="" INST_FIREWALL=0
|
||||
while IFS= read -r line || [[ -n $line ]]; do
|
||||
key=${line%%=*}
|
||||
value=${line#*=}
|
||||
value=${value#\"}
|
||||
value=${value%\"}
|
||||
case "$key" in
|
||||
LISTEN_ADDR) INST_LISTEN=$value ;;
|
||||
SOCKS_USER) INST_USER=$value ;;
|
||||
SOCKS_PASS) INST_PASS=$value ;;
|
||||
BIND_OPTIONS) INST_BIND=${value#-b } ;;
|
||||
PUBLIC_HOST) INST_HOST=$value ;;
|
||||
ALLOW_CIDRS) INST_ALLOW=$value ;;
|
||||
FIREWALL_OPENED) INST_FIREWALL=$value ;;
|
||||
esac
|
||||
done <"$CONF_DIR/$1.conf"
|
||||
}
|
||||
|
||||
|
||||
resolve_targets() {
|
||||
local port
|
||||
local -a existing=()
|
||||
mapfile -t existing < <(instance_ports)
|
||||
if ((${#existing[@]} == 0)); then
|
||||
[[ $ACTION == list || $ACTION == links ]] || die "没有找到 microsocks 实例"
|
||||
log "没有找到 microsocks 实例"
|
||||
exit 0
|
||||
fi
|
||||
((${#TARGET_PORTS[@]} > 0)) || TARGET_PORTS=("${existing[@]}")
|
||||
mapfile -t TARGET_PORTS < <(printf '%s\n' "${TARGET_PORTS[@]}" | sort -nu)
|
||||
for port in "${TARGET_PORTS[@]}"; do
|
||||
[[ -f $CONF_DIR/$port.conf ]] || die "实例 $port 不存在"
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
print_row() {
|
||||
printf '%-6s %-10s %-16s %-16s %-14s %s\n' "$@"
|
||||
}
|
||||
|
||||
|
||||
list_instances() {
|
||||
local port state allow
|
||||
print_row PORT STATE LISTEN EGRESS USER ALLOW
|
||||
for port in "${TARGET_PORTS[@]}"; do
|
||||
load_instance "$port"
|
||||
state=$(systemctl is-active "microsocks@$port.service" 2>/dev/null || true)
|
||||
allow=${INST_ALLOW// /,}
|
||||
print_row "$port" "${state:-unknown}" "$INST_LISTEN" "${INST_BIND:-default}" \
|
||||
"${INST_USER:--}" "${allow:-any}"
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
print_instance_links() {
|
||||
local port
|
||||
for port in "${TARGET_PORTS[@]}"; do
|
||||
load_instance "$port"
|
||||
format_link "$INST_USER" "$INST_PASS" "${PUBLIC_HOST:-$INST_HOST}" "$port"
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
update_instances() {
|
||||
local port
|
||||
for port in "${TARGET_PORTS[@]}"; do
|
||||
load_instance "$port"
|
||||
if ((${#ALLOW_ARGS[@]} > 0 || ALLOW_ALL == 1)); then
|
||||
INST_ALLOW=$ALLOW_LIST
|
||||
fi
|
||||
if [[ -n $PUBLIC_HOST ]]; then
|
||||
INST_HOST=$PUBLIC_HOST
|
||||
fi
|
||||
save_instance "$port"
|
||||
log "已更新 ${port}:来源限制 ${INST_ALLOW:-不限制};链接主机 $INST_HOST"
|
||||
done
|
||||
apply_acl
|
||||
print_instance_links
|
||||
}
|
||||
|
||||
|
||||
control_instances() {
|
||||
local port unit
|
||||
for port in "${TARGET_PORTS[@]}"; do
|
||||
unit="microsocks@$port.service"
|
||||
case "$ACTION" in
|
||||
start) systemctl enable --now "$unit" || true ;;
|
||||
stop) systemctl disable --now "$unit" ;;
|
||||
restart) systemctl restart "$unit" || true ;;
|
||||
esac
|
||||
[[ $ACTION == stop ]] || ensure_active "$unit"
|
||||
log "$unit: $(systemctl is-active "$unit" || true)"
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
confirm_remove() {
|
||||
[[ -t 0 && $NON_INTERACTIVE -eq 0 ]] || return 0
|
||||
local answer
|
||||
read -r -p "确认删除实例 ${TARGET_PORTS[*]}?[y/N]: " answer
|
||||
[[ $answer =~ ^[Yy]$ ]] || exit 0
|
||||
}
|
||||
|
||||
|
||||
remove_instances() {
|
||||
local port
|
||||
local -a opened=()
|
||||
for port in "${TARGET_PORTS[@]}"; do
|
||||
load_instance "$port"
|
||||
[[ $INST_FIREWALL != 1 ]] || opened+=("$port")
|
||||
systemctl disable --now "microsocks@$port.service" || log "停止 microsocks@$port 失败"
|
||||
rm -f -- "$CONF_DIR/$port.conf"
|
||||
log "已删除 microsocks@$port"
|
||||
done
|
||||
apply_acl
|
||||
close_detected_firewall "${opened[@]}"
|
||||
}
|
||||
|
||||
|
||||
acquire_lock() {
|
||||
exec 9>/run/lock/microsocks-setup.lock
|
||||
flock -n 9 || die "另一个 microsocks 任务正在运行"
|
||||
}
|
||||
|
||||
|
||||
@@ -568,8 +973,7 @@ prepare_plan() {
|
||||
}
|
||||
|
||||
|
||||
main() {
|
||||
parse_args "$@"
|
||||
run_create() {
|
||||
interactive_options
|
||||
detect_os
|
||||
[[ $DRY_RUN -eq 1 || $EUID -eq 0 ]] || die "请使用 root 或 sudo 执行"
|
||||
@@ -583,11 +987,11 @@ main() {
|
||||
return 0
|
||||
fi
|
||||
confirm_plan
|
||||
exec 9>/run/lock/microsocks-setup.lock
|
||||
flock -n 9 || die "另一个安装任务正在运行"
|
||||
acquire_lock
|
||||
install_microsocks
|
||||
check_bind_support
|
||||
install_configuration
|
||||
apply_acl
|
||||
start_services
|
||||
open_detected_firewall
|
||||
log "请同时在云平台安全组/安全列表中放行对应 TCP 端口"
|
||||
@@ -595,6 +999,31 @@ main() {
|
||||
}
|
||||
|
||||
|
||||
run_manage() {
|
||||
[[ $EUID -eq 0 ]] || die "请使用 root 或 sudo 执行"
|
||||
command -v systemctl >/dev/null || die "未检测到 systemd"
|
||||
validate_manage_options
|
||||
resolve_targets
|
||||
case "$ACTION" in
|
||||
list) list_instances ;;
|
||||
links) print_instance_links ;;
|
||||
update) acquire_lock; update_instances ;;
|
||||
start|stop|restart) acquire_lock; control_instances ;;
|
||||
remove) confirm_remove; acquire_lock; remove_instances ;;
|
||||
esac
|
||||
}
|
||||
|
||||
|
||||
main() {
|
||||
parse_cli "$@"
|
||||
if [[ $ACTION == create ]]; then
|
||||
run_create
|
||||
else
|
||||
run_manage
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
trap cleanup EXIT
|
||||
WORK_DIR=$(mktemp -d /tmp/microsocks-setup.XXXXXX)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user