feat: 添加 setup-microsocks.sh 脚本以支持 Debian/Ubuntu 上的 MicroSocks 多实例安装
This commit is contained in:
Executable
+603
@@ -0,0 +1,603 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
# Debian/Ubuntu MicroSocks multi-instance installer.
|
||||||
|
# Status messages go to stderr; created socks5:// links go to stdout.
|
||||||
|
|
||||||
|
COUNT=0
|
||||||
|
COUNT_SET=0
|
||||||
|
START_PORT=1080
|
||||||
|
START_PORT_SET=0
|
||||||
|
LISTEN_ADDR="0.0.0.0"
|
||||||
|
PUBLIC_HOST=""
|
||||||
|
COMMON_USER=""
|
||||||
|
COMMON_PASS=""
|
||||||
|
NO_AUTH=0
|
||||||
|
AUTO_BIND=0
|
||||||
|
REPLACE=0
|
||||||
|
OPEN_FIREWALL=0
|
||||||
|
NON_INTERACTIVE=0
|
||||||
|
DRY_RUN=0
|
||||||
|
APT_UPDATED=0
|
||||||
|
MICROSOCKS_BIN=""
|
||||||
|
WORK_DIR=""
|
||||||
|
CONF_DIR="/etc/microsocks"
|
||||||
|
UNIT_FILE="/etc/systemd/system/microsocks@.service"
|
||||||
|
|
||||||
|
declare -a BIND_ADDRESSES=()
|
||||||
|
declare -a RESOLVED_BINDS=()
|
||||||
|
declare -a PORTS=()
|
||||||
|
declare -a USERS=()
|
||||||
|
declare -a PASSWORDS=()
|
||||||
|
declare -a LOCAL_ADDRESSES=()
|
||||||
|
|
||||||
|
|
||||||
|
log() {
|
||||||
|
printf '[microsocks] %s\n' "$*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
die() {
|
||||||
|
printf '[microsocks] 错误: %s\n' "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
用法:sudo ./setup-microsocks.sh [选项]
|
||||||
|
|
||||||
|
-n, --count N 创建 N 个代理;多个 --bind 时可省略
|
||||||
|
--start-port PORT 起始端口,默认 1080
|
||||||
|
--listen IP 监听地址,默认 0.0.0.0
|
||||||
|
--host HOST 返回链接使用的公网 IP 或域名
|
||||||
|
--bind IP 绑定出站源地址;可重复指定
|
||||||
|
--auto-bind 自动轮流使用本机所有全局 IP 作为出口
|
||||||
|
--user USER 所有实例使用同一用户名
|
||||||
|
--password PASS 所有实例使用同一密码
|
||||||
|
--no-auth 仅允许在回环监听地址上关闭认证
|
||||||
|
--replace 覆盖相同端口的既有 microsocks 实例
|
||||||
|
--open-firewall 自动放行已启用的 UFW/firewalld 端口
|
||||||
|
--non-interactive 不询问确认
|
||||||
|
--dry-run 仅显示计划和链接,不修改系统
|
||||||
|
-h, --help 显示帮助
|
||||||
|
|
||||||
|
示例:
|
||||||
|
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
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
need_value() {
|
||||||
|
[[ $# -ge 2 && -n ${2:-} ]] || die "$1 缺少参数"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
parse_args() {
|
||||||
|
while (($#)); do
|
||||||
|
case "$1" in
|
||||||
|
-n|--count) need_value "$@"; COUNT=$2; COUNT_SET=1; shift 2 ;;
|
||||||
|
--start-port) need_value "$@"; START_PORT=$2; START_PORT_SET=1; shift 2 ;;
|
||||||
|
--listen) need_value "$@"; LISTEN_ADDR=$2; shift 2 ;;
|
||||||
|
--host) need_value "$@"; PUBLIC_HOST=$2; shift 2 ;;
|
||||||
|
--bind) need_value "$@"; BIND_ADDRESSES+=("$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 ;;
|
||||||
|
--dry-run) DRY_RUN=1; shift ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) die "未知参数: $1" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
prompt_number() {
|
||||||
|
local prompt=$1 default=$2 value
|
||||||
|
read -r -p "$prompt [$default]: " value
|
||||||
|
printf '%s' "${value:-$default}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interactive_options() {
|
||||||
|
[[ -t 0 && $NON_INTERACTIVE -eq 0 ]] || return 0
|
||||||
|
if [[ $COUNT_SET -eq 0 ]]; then
|
||||||
|
COUNT=$(prompt_number "代理数量" 1)
|
||||||
|
COUNT_SET=1
|
||||||
|
fi
|
||||||
|
if [[ $START_PORT_SET -eq 0 ]]; then
|
||||||
|
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
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
is_uint() {
|
||||||
|
[[ $1 =~ ^[0-9]+$ ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
valid_token() {
|
||||||
|
[[ $1 =~ ^[A-Za-z0-9._~-]{1,128}$ ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
valid_ip() {
|
||||||
|
python3 - "$1" <<'PY' >/dev/null 2>&1
|
||||||
|
import ipaddress
|
||||||
|
import sys
|
||||||
|
ipaddress.ip_address(sys.argv[1])
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
valid_host() {
|
||||||
|
local host=${1#[}
|
||||||
|
host=${host%]}
|
||||||
|
valid_ip "$host" && return 0
|
||||||
|
[[ $host =~ ^[A-Za-z0-9]([A-Za-z0-9.-]{0,251}[A-Za-z0-9])?$ ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
validate_options() {
|
||||||
|
if [[ $COUNT_SET -eq 0 ]]; then
|
||||||
|
COUNT=$((${#BIND_ADDRESSES[@]} > 1 ? ${#BIND_ADDRESSES[@]} : 1))
|
||||||
|
fi
|
||||||
|
is_uint "$COUNT" && ((COUNT >= 1)) || die "代理数量必须是正整数"
|
||||||
|
is_uint "$START_PORT" || die "起始端口必须是整数"
|
||||||
|
((START_PORT >= 1024 && START_PORT + COUNT - 1 <= 65535)) || die "端口范围必须在 1024-65535"
|
||||||
|
((AUTO_BIND == 0 || ${#BIND_ADDRESSES[@]} == 0)) || die "--auto-bind 不能和 --bind 同时使用"
|
||||||
|
[[ -z $COMMON_USER && -z $COMMON_PASS ]] || [[ -n $COMMON_USER && -n $COMMON_PASS ]] || die "--user 和 --password 必须同时指定"
|
||||||
|
[[ -z $COMMON_USER ]] || valid_token "$COMMON_USER" || die "用户名只能使用字母、数字和 ._~-"
|
||||||
|
[[ -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 地址"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
validate_auth_mode() {
|
||||||
|
[[ $NO_AUTH -eq 0 ]] && return 0
|
||||||
|
[[ -z $COMMON_USER && -z $COMMON_PASS ]] || die "--no-auth 不能与用户名密码同时使用"
|
||||||
|
case "$LISTEN_ADDR" in
|
||||||
|
127.*|::1) ;;
|
||||||
|
*) die "为防止开放代理,--no-auth 仅允许监听回环地址" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
detect_os() {
|
||||||
|
[[ -r /etc/os-release ]] || die "无法识别操作系统"
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source /etc/os-release
|
||||||
|
local family="${ID:-} ${ID_LIKE:-}"
|
||||||
|
[[ $family == *debian* || $family == *ubuntu* ]] || die "仅支持 Debian/Ubuntu 系统"
|
||||||
|
command -v systemctl >/dev/null || die "未检测到 systemd"
|
||||||
|
[[ $(ps -p 1 -o comm= 2>/dev/null) == systemd ]] || die "PID 1 不是 systemd"
|
||||||
|
log "系统: ${PRETTY_NAME:-$ID}; 架构: $(uname -m)"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
apt_update_once() {
|
||||||
|
[[ $APT_UPDATED -eq 1 ]] && return 0
|
||||||
|
log "更新 APT 索引"
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get update
|
||||||
|
APT_UPDATED=1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ensure_base_tools() {
|
||||||
|
local missing=() command
|
||||||
|
for command in curl openssl python3 ip ss flock; do
|
||||||
|
command -v "$command" >/dev/null || missing+=("$command")
|
||||||
|
done
|
||||||
|
((${#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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
install_from_source() {
|
||||||
|
apt_update_once
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y git build-essential ca-certificates
|
||||||
|
log "从上游源码编译 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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
install_microsocks() {
|
||||||
|
if command -v microsocks >/dev/null; then
|
||||||
|
MICROSOCKS_BIN=$(command -v microsocks)
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
apt_update_once
|
||||||
|
log "尝试从 APT 安装 microsocks"
|
||||||
|
if DEBIAN_FRONTEND=noninteractive apt-get install -y microsocks; then
|
||||||
|
MICROSOCKS_BIN=$(command -v microsocks)
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
install_from_source
|
||||||
|
MICROSOCKS_BIN=/usr/bin/microsocks
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
oci_public_ip() {
|
||||||
|
curl -fsS --max-time 2 -H 'Authorization: Bearer Oracle' \
|
||||||
|
http://169.254.169.254/opc/v2/vnics/ 2>/dev/null | python3 -c '
|
||||||
|
import json, sys
|
||||||
|
items = json.load(sys.stdin)
|
||||||
|
print(next((x.get("publicIp") for x in items if x.get("publicIp")), ""))
|
||||||
|
' 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
first_global_ipv6() {
|
||||||
|
ip -o -6 addr show scope global | awk '{sub(/\/.*/, "", $4); print $4; exit}'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
detect_public_host() {
|
||||||
|
[[ -n $PUBLIC_HOST ]] && return 0
|
||||||
|
case "$LISTEN_ADDR" in
|
||||||
|
127.*|::1) PUBLIC_HOST=$LISTEN_ADDR; return 0 ;;
|
||||||
|
esac
|
||||||
|
if [[ $LISTEN_ADDR == *:* ]]; then
|
||||||
|
[[ $LISTEN_ADDR == "::" ]] && PUBLIC_HOST=$(first_global_ipv6 || true) ||
|
||||||
|
PUBLIC_HOST=$LISTEN_ADDR
|
||||||
|
[[ -n $PUBLIC_HOST ]] || die "没有可用于 IPv6 监听的全局地址"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
PUBLIC_HOST=$(oci_public_ip || true)
|
||||||
|
[[ -n $PUBLIC_HOST ]] && return 0
|
||||||
|
PUBLIC_HOST=$(curl -4 -fsS --max-time 5 https://api.ipify.org 2>/dev/null || true)
|
||||||
|
[[ -n $PUBLIC_HOST ]] && return 0
|
||||||
|
die "无法自动检测公网 IPv4,请使用 --host 指定,或改用 --listen ::"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
discover_local_addresses() {
|
||||||
|
mapfile -t LOCAL_ADDRESSES < <(
|
||||||
|
ip -o addr show up scope global |
|
||||||
|
awk '$2 !~ /^(docker|br-|veth|virbr|tailscale|wg)/ {sub(/\/.*/, "", $4); print $4}' |
|
||||||
|
sort -u
|
||||||
|
)
|
||||||
|
((${#LOCAL_ADDRESSES[@]} > 0)) || die "没有检测到全局 IPv4/IPv6 地址"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
address_is_local() {
|
||||||
|
local target=$1 address
|
||||||
|
for address in "${LOCAL_ADDRESSES[@]}"; do
|
||||||
|
[[ $address == "$target" ]] && return 0
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
validate_bind_addresses() {
|
||||||
|
local address
|
||||||
|
for address in "${BIND_ADDRESSES[@]}"; do
|
||||||
|
valid_ip "$address" || die "无效的 --bind 地址: $address"
|
||||||
|
address_is_local "$address" || die "--bind 地址不在本机: $address"
|
||||||
|
done
|
||||||
|
local size=${#BIND_ADDRESSES[@]}
|
||||||
|
((size == 0 || size == 1 || size == COUNT)) || die "--bind 数量必须是 1 或代理数量 $COUNT"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface_for_address() {
|
||||||
|
local target=$1
|
||||||
|
ip -o addr show | awk -v target="$target" '
|
||||||
|
{address=$4; sub(/\/.*/, "", address)}
|
||||||
|
address == target {interface=$2; sub(/@.*/, "", interface); print interface; exit}
|
||||||
|
'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
route_for_address() {
|
||||||
|
local address=$1
|
||||||
|
if [[ $address == *:* ]]; then
|
||||||
|
ip -6 route get 2606:4700:4700::1111 from "$address"
|
||||||
|
else
|
||||||
|
ip -4 route get 1.1.1.1 from "$address"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
validate_bind_routes() {
|
||||||
|
local address expected route actual
|
||||||
|
for address in "${RESOLVED_BINDS[@]}"; do
|
||||||
|
[[ -n $address ]] || continue
|
||||||
|
expected=$(interface_for_address "$address")
|
||||||
|
route=$(route_for_address "$address" 2>/dev/null) || die "$address 没有可用的出站路由"
|
||||||
|
actual=$(awk '{for(i=1;i<=NF;i++) if($i=="dev"){print $(i+1); exit}}' <<<"$route")
|
||||||
|
[[ -n $expected && $actual == "$expected" ]] ||
|
||||||
|
die "$address 应从 $expected 出口,内核实际选择 ${actual:-未知接口};请先修复策略路由"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
prepare_binds() {
|
||||||
|
local index size=${#BIND_ADDRESSES[@]}
|
||||||
|
if ((size == 0 && AUTO_BIND == 0)); then
|
||||||
|
for ((index = 0; index < COUNT; index++)); do RESOLVED_BINDS+=(""); done
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
discover_local_addresses
|
||||||
|
validate_bind_addresses
|
||||||
|
for ((index = 0; index < COUNT; index++)); do
|
||||||
|
if ((size == COUNT)); then
|
||||||
|
RESOLVED_BINDS+=("${BIND_ADDRESSES[index]}")
|
||||||
|
elif ((size == 1)); then
|
||||||
|
RESOLVED_BINDS+=("${BIND_ADDRESSES[0]}")
|
||||||
|
elif ((AUTO_BIND == 1)); then
|
||||||
|
RESOLVED_BINDS+=("${LOCAL_ADDRESSES[index % ${#LOCAL_ADDRESSES[@]}]}")
|
||||||
|
else
|
||||||
|
RESOLVED_BINDS+=("")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
prepare_credentials() {
|
||||||
|
local index
|
||||||
|
for ((index = 0; index < COUNT; index++)); do
|
||||||
|
if ((NO_AUTH == 1)); then
|
||||||
|
USERS+=(""); PASSWORDS+=("")
|
||||||
|
elif [[ -n $COMMON_USER ]]; then
|
||||||
|
USERS+=("$COMMON_USER"); PASSWORDS+=("$COMMON_PASS")
|
||||||
|
else
|
||||||
|
USERS+=("proxy$(openssl rand -hex 3)")
|
||||||
|
PASSWORDS+=("$(openssl rand -hex 16)")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
prepare_ports() {
|
||||||
|
local index
|
||||||
|
for ((index = 0; index < COUNT; index++)); do
|
||||||
|
PORTS+=("$((START_PORT + index))")
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
port_in_use() {
|
||||||
|
local port=$1
|
||||||
|
ss -H -ltn | awk -v suffix=":$port" '$4 ~ suffix "$" {found=1} END {exit !found}'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
validate_ports() {
|
||||||
|
local port config
|
||||||
|
for port in "${PORTS[@]}"; do
|
||||||
|
config="$CONF_DIR/$port.conf"
|
||||||
|
if [[ -e $config && $REPLACE -eq 0 ]]; then
|
||||||
|
die "$config 已存在;使用 --replace 才能覆盖"
|
||||||
|
fi
|
||||||
|
if [[ ! -e $config ]] && port_in_use "$port"; then
|
||||||
|
die "端口 $port 已被其他进程占用"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
format_url_host() {
|
||||||
|
local host=${1#[}
|
||||||
|
host=${host%]}
|
||||||
|
[[ $host == *:* ]] && printf '[%s]' "$host" || printf '%s' "$host"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
print_links() {
|
||||||
|
local index host
|
||||||
|
host=$(format_url_host "$PUBLIC_HOST")
|
||||||
|
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
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
show_plan() {
|
||||||
|
local index bind auth
|
||||||
|
log "计划创建 $COUNT 个实例,监听地址 $LISTEN_ADDR"
|
||||||
|
for ((index = 0; index < COUNT; index++)); do
|
||||||
|
bind=${RESOLVED_BINDS[index]:-自动}
|
||||||
|
auth=${USERS[index]:-无认证}
|
||||||
|
log "端口 ${PORTS[index]};出口 $bind;用户 $auth"
|
||||||
|
done
|
||||||
|
log "链接主机: $PUBLIC_HOST"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
confirm_plan() {
|
||||||
|
[[ -t 0 && $NON_INTERACTIVE -eq 0 ]] || return 0
|
||||||
|
local answer
|
||||||
|
read -r -p "继续安装并启动服务?[Y/n]: " answer
|
||||||
|
[[ ! $answer =~ ^[Nn]$ ]] || exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
check_bind_support() {
|
||||||
|
local has_bind=0 address help
|
||||||
|
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 出站绑定"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
write_unit_header() {
|
||||||
|
cat >"$1" <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=MicroSocks SOCKS5 Proxy on port %i
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
EnvironmentFile=$CONF_DIR/%i.conf
|
||||||
|
ExecStart=$MICROSOCKS_BIN -i \${LISTEN_ADDR} -p %i \$AUTH_OPTIONS \$BIND_OPTIONS
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=2
|
||||||
|
User=nobody
|
||||||
|
Group=nogroup
|
||||||
|
NoNewPrivileges=true
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
write_unit_sandbox() {
|
||||||
|
cat >>"$1" <<'EOF'
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=true
|
||||||
|
ProtectKernelTunables=true
|
||||||
|
ProtectKernelModules=true
|
||||||
|
ProtectControlGroups=true
|
||||||
|
RestrictAddressFamilies=AF_INET AF_INET6
|
||||||
|
LockPersonality=true
|
||||||
|
MemoryDenyWriteExecute=true
|
||||||
|
CapabilityBoundingSet=
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
write_unit_file() {
|
||||||
|
local temporary="$WORK_DIR/microsocks@.service"
|
||||||
|
write_unit_header "$temporary"
|
||||||
|
write_unit_sandbox "$temporary"
|
||||||
|
install -m 0644 "$temporary" "$UNIT_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
write_instance_config() {
|
||||||
|
local index=$1 port=${PORTS[$1]} temporary="$WORK_DIR/${PORTS[$1]}.conf"
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
install_configuration() {
|
||||||
|
install -d -m 0750 "$CONF_DIR"
|
||||||
|
write_unit_file
|
||||||
|
local index
|
||||||
|
for ((index = 0; index < COUNT; index++)); do
|
||||||
|
write_instance_config "$index"
|
||||||
|
done
|
||||||
|
systemctl daemon-reload
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
start_services() {
|
||||||
|
local port
|
||||||
|
for port in "${PORTS[@]}"; do
|
||||||
|
if systemctl is-active --quiet "microsocks@$port.service"; then
|
||||||
|
systemctl restart "microsocks@$port.service"
|
||||||
|
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 启动失败"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
fi
|
||||||
|
((firewalld == 0)) || firewall-cmd --reload
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
[[ -n ${WORK_DIR:-} && -d ${WORK_DIR:-} ]] || return 0
|
||||||
|
[[ $WORK_DIR == /tmp/microsocks-setup.* ]] || return 0
|
||||||
|
rm -rf -- "$WORK_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
prepare_plan() {
|
||||||
|
detect_public_host
|
||||||
|
prepare_binds
|
||||||
|
validate_bind_routes
|
||||||
|
prepare_ports
|
||||||
|
prepare_credentials
|
||||||
|
validate_ports
|
||||||
|
show_plan
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
main() {
|
||||||
|
parse_args "$@"
|
||||||
|
interactive_options
|
||||||
|
detect_os
|
||||||
|
[[ $DRY_RUN -eq 1 || $EUID -eq 0 ]] || die "请使用 root 或 sudo 执行"
|
||||||
|
ensure_base_tools
|
||||||
|
validate_options
|
||||||
|
validate_auth_mode
|
||||||
|
prepare_plan
|
||||||
|
if [[ $DRY_RUN -eq 1 ]]; then
|
||||||
|
log "dry-run:未修改系统;以下链接尚未生效"
|
||||||
|
print_links
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
confirm_plan
|
||||||
|
exec 9>/run/lock/microsocks-setup.lock
|
||||||
|
flock -n 9 || die "另一个安装任务正在运行"
|
||||||
|
install_microsocks
|
||||||
|
check_bind_support
|
||||||
|
install_configuration
|
||||||
|
start_services
|
||||||
|
open_detected_firewall
|
||||||
|
log "请同时在云平台安全组/安全列表中放行对应 TCP 端口"
|
||||||
|
print_links
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
trap cleanup EXIT
|
||||||
|
WORK_DIR=$(mktemp -d /tmp/microsocks-setup.XXXXXX)
|
||||||
|
|
||||||
|
if [[ ${BASH_SOURCE[0]} == "$0" ]]; then
|
||||||
|
main "$@"
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user