feat: 更新 setup-microsocks.sh 脚本,添加实例管理功能和来源限制支持

This commit is contained in:
2026-09-26 19:35:22 +05:30
parent 7eba1eea28
commit f6dd4c0766
+489 -60
View File
@@ -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)