fix: 安装启动后不再卡住,保证脚本退出
- 去掉可能阻塞的 systemctl status 全量输出 - verify 使用 connect/max 超时;安装阶段 8s 快速验证 - 验证失败只告警,不阻塞安装结束
This commit is contained in:
Executable
+49
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
source "$ROOT_DIR/scripts/lib.sh"
|
||||
if [[ -f "${CONFIG_FILE:-$ROOT_DIR/config/settings.conf}" ]]; then
|
||||
load_config
|
||||
else
|
||||
CELLULAR_IFACE_PATTERNS="${CELLULAR_IFACE_PATTERNS:-wwan,wwp,usb,enx,ppp,cdc,rmnet,ccmni,mbim,qmi}"
|
||||
REQUIRE_CELLULAR_IFACE=false
|
||||
CELLULAR_IFACE=
|
||||
fi
|
||||
|
||||
echo "========== 主机 =========="
|
||||
echo "hostname : $(hostname)"
|
||||
echo "kernel : $(uname -r)"
|
||||
echo "arch : $(uname -m)"
|
||||
echo "mem_total: $(awk '/MemTotal/ {printf "%.0f MB", $2/1024}' /proc/meminfo 2>/dev/null || echo unknown)"
|
||||
echo "mem_avail: $(awk '/MemAvailable/ {printf "%.0f MB", $2/1024}' /proc/meminfo 2>/dev/null || echo unknown)"
|
||||
echo
|
||||
|
||||
echo "========== 接口列表 =========="
|
||||
printf '%-12s %-10s %-18s %-12s %s\n' "IFACE" "STATE" "IPv4" "DRIVER" "SCORE"
|
||||
default_if="$(detect_default_iface || true)"
|
||||
while IFS='|' read -r name state ip; do
|
||||
[[ -z "$name" ]] && continue
|
||||
is_virtual_or_skip_iface "$name" && continue
|
||||
sc="$(score_iface_as_cellular "$name" "$default_if")"
|
||||
drv="$(iface_driver "$name" || true)"
|
||||
printf '%-12s %-10s %-18s %-12s %s\n' "$name" "$state" "${ip:-}" "${drv:-}" "$sc"
|
||||
done < <(list_ifaces)
|
||||
echo
|
||||
|
||||
echo "========== 默认路由 =========="
|
||||
ip route show default 2>/dev/null || true
|
||||
echo
|
||||
echo "default iface: ${default_if:-none}"
|
||||
echo
|
||||
|
||||
echo "========== 数据网卡(代理出口) =========="
|
||||
cell="$(detect_cellular_iface "${CELLULAR_IFACE:-}" 2>/dev/null || true)"
|
||||
if [[ -n "$cell" ]]; then
|
||||
echo "CELLULAR_IFACE=$cell ip=$(detect_source_ip "$cell" || true) driver=$(iface_driver "$cell" || true)"
|
||||
echo "说明: 走本代理的流量将从该网卡出(数据流量)"
|
||||
else
|
||||
echo "未能自动探测数据网卡。"
|
||||
echo "请手动: CELLULAR_IFACE=网卡名 或 cpxy rebind"
|
||||
exit 2
|
||||
fi
|
||||
Executable
+92
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env bash
|
||||
# 仅下载 sing-box(轻量核心)
|
||||
set -euo pipefail
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
source "$ROOT_DIR/scripts/lib.sh"
|
||||
load_config
|
||||
need_root
|
||||
ensure_dirs
|
||||
mkdir -p /var/lib/cellular-proxy
|
||||
|
||||
ARCH="$(arch_go)"
|
||||
TMP="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
# GitHub 下载代理:默认 https://git.86482425.xyz
|
||||
# 空字符串 = 直连 GitHub;也可设为其它镜像前缀(末尾可带或不带 /)
|
||||
GITHUB_PROXY="${GITHUB_PROXY:-https://git.86482425.xyz}"
|
||||
|
||||
github_url() {
|
||||
# 把 https://github.com/... 加上代理前缀
|
||||
local raw="$1"
|
||||
local proxy="${GITHUB_PROXY:-}"
|
||||
if [[ -z "$proxy" ]]; then
|
||||
echo "$raw"
|
||||
return
|
||||
fi
|
||||
proxy="${proxy%/}"
|
||||
# 已是代理地址则不重复加
|
||||
case "$raw" in
|
||||
"${proxy}"/*) echo "$raw"; return ;;
|
||||
esac
|
||||
# 常见 ghproxy 风格:https://proxy/https://github.com/...
|
||||
echo "${proxy}/${raw}"
|
||||
}
|
||||
|
||||
download() {
|
||||
local url="$1" dest="$2"
|
||||
info "下载: $url"
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fL --retry 3 --connect-timeout 20 -o "$dest" "$url"
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget -O "$dest" "$url"
|
||||
else
|
||||
die "需要 curl 或 wget"
|
||||
fi
|
||||
}
|
||||
|
||||
download_github() {
|
||||
# 先走代理,失败再直连 GitHub
|
||||
local raw="$1" dest="$2"
|
||||
local proxied
|
||||
proxied="$(github_url "$raw")"
|
||||
if [[ "$proxied" != "$raw" ]]; then
|
||||
info "经 GitHub 代理下载: $proxied"
|
||||
if download "$proxied" "$dest"; then
|
||||
return 0
|
||||
fi
|
||||
warn "代理下载失败,尝试直连 GitHub: $raw"
|
||||
fi
|
||||
download "$raw" "$dest"
|
||||
}
|
||||
|
||||
if [[ "$SING_BOX_SOURCE" == "local" ]]; then
|
||||
[[ -n "$SING_BOX_BIN" && -x "$SING_BOX_BIN" ]] || die "SING_BOX_SOURCE=local 但 SING_BOX_BIN 无效"
|
||||
install -m 0755 "$SING_BOX_BIN" "$INSTALL_DIR/bin/sing-box"
|
||||
info "已安装本地 sing-box"
|
||||
elif command -v sing-box >/dev/null 2>&1 && [[ -z "${FORCE_DOWNLOAD:-}" ]]; then
|
||||
install -m 0755 "$(command -v sing-box)" "$INSTALL_DIR/bin/sing-box"
|
||||
info "复用系统 sing-box: $(command -v sing-box)"
|
||||
else
|
||||
ver="$SING_BOX_VERSION"
|
||||
name="sing-box-${ver}-linux-${ARCH}"
|
||||
raw_url="https://github.com/SagerNet/sing-box/releases/download/v${ver}/${name}.tar.gz"
|
||||
download_github "$raw_url" "$TMP/sb.tgz"
|
||||
tar -xzf "$TMP/sb.tgz" -C "$TMP"
|
||||
install -m 0755 "$TMP/${name}/sing-box" "$INSTALL_DIR/bin/sing-box"
|
||||
info "已安装 sing-box v${ver}"
|
||||
fi
|
||||
|
||||
# 安装中文静态 UI
|
||||
mkdir -p "$INSTALL_DIR/ui"
|
||||
if [[ -f "$ROOT_DIR/ui/index.html" ]]; then
|
||||
cp -a "$ROOT_DIR/ui/." "$INSTALL_DIR/ui/"
|
||||
info "已安装内置中文面板 -> $INSTALL_DIR/ui"
|
||||
else
|
||||
warn "未找到 ui/index.html"
|
||||
fi
|
||||
|
||||
info "二进制就绪:"
|
||||
ls -la "$INSTALL_DIR/bin"
|
||||
du -sh "$INSTALL_DIR/bin" "$INSTALL_DIR/ui" 2>/dev/null || true
|
||||
Executable
+156
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env bash
|
||||
# 生成 sing-box:代理流量全部 bind 数据网卡出口
|
||||
set -euo pipefail
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
source "$ROOT_DIR/scripts/lib.sh"
|
||||
load_config
|
||||
|
||||
OUT_DIR="${1:-$ROOT_DIR/generated}"
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
cell="$(resolve_cellular 2>/dev/null || true)"
|
||||
if [[ -z "$cell" ]]; then
|
||||
resolve_cellular >/dev/null || true
|
||||
fi
|
||||
src_ip="${CELLULAR_SOURCE_IP:-}"
|
||||
if [[ -z "$src_ip" && -n "$cell" ]]; then
|
||||
src_ip="$(detect_source_ip "$cell" || true)"
|
||||
fi
|
||||
|
||||
if [[ -z "$cell" ]]; then
|
||||
warn "CELLULAR_IFACE 为空,临时用 lo 生成配置(启动前务必修正)"
|
||||
cell_for_cfg=lo
|
||||
else
|
||||
cell_for_cfg="$cell"
|
||||
fi
|
||||
|
||||
if [[ "$PANEL_SECRET" == "please-change-me" || "$PANEL_SECRET" == "change-this-secret" ]]; then
|
||||
warn "PANEL_SECRET 仍是默认值,请修改 settings.conf"
|
||||
fi
|
||||
if [[ "$PROXY_LISTEN_HOST" != "127.0.0.1" && "$PROXY_LISTEN_HOST" != "::1" && -z "$PROXY_USER" ]]; then
|
||||
warn "代理监听 $PROXY_LISTEN_HOST 且未设置 PROXY_USER/PASS(局域网建议加鉴权)"
|
||||
fi
|
||||
|
||||
export GEN_OUT="$OUT_DIR/config.json"
|
||||
export GEN_CELL="$cell_for_cfg"
|
||||
export GEN_SRC="$src_ip"
|
||||
export GEN_PROXY_HOST="$PROXY_LISTEN_HOST"
|
||||
export GEN_PROXY_PORT="$PROXY_MIXED_PORT"
|
||||
export GEN_PROXY_USER="$PROXY_USER"
|
||||
export GEN_PROXY_PASS="$PROXY_PASS"
|
||||
export GEN_PANEL_HOST="$PANEL_LISTEN_HOST"
|
||||
export GEN_PANEL_PORT="$PANEL_PORT"
|
||||
export GEN_PANEL_SECRET="$PANEL_SECRET"
|
||||
export GEN_DNS="$ENABLE_DNS"
|
||||
export GEN_LOG="$LOG_LEVEL"
|
||||
export GEN_UI_DIR="$INSTALL_DIR/ui"
|
||||
|
||||
python3 <<'PY'
|
||||
import json, os
|
||||
from pathlib import Path
|
||||
|
||||
cell = os.environ["GEN_CELL"]
|
||||
src = os.environ.get("GEN_SRC") or ""
|
||||
log_level = os.environ.get("GEN_LOG") or "warn"
|
||||
ui_dir = os.environ.get("GEN_UI_DIR") or "/opt/cellular-proxy/ui"
|
||||
|
||||
users = []
|
||||
u = os.environ.get("GEN_PROXY_USER") or ""
|
||||
p = os.environ.get("GEN_PROXY_PASS") or ""
|
||||
if u:
|
||||
users.append({"username": u, "password": p})
|
||||
|
||||
inbound = {
|
||||
"type": "mixed",
|
||||
"tag": "mixed-in",
|
||||
"listen": os.environ["GEN_PROXY_HOST"],
|
||||
"listen_port": int(os.environ["GEN_PROXY_PORT"]),
|
||||
}
|
||||
if users:
|
||||
inbound["users"] = users
|
||||
|
||||
# 唯一业务出口:强制绑定数据网卡
|
||||
out_cell = {
|
||||
"type": "direct",
|
||||
"tag": "cellular",
|
||||
"bind_interface": cell,
|
||||
}
|
||||
if src:
|
||||
out_cell["inet4_bind_address"] = src
|
||||
|
||||
cfg = {
|
||||
"log": {"level": log_level, "timestamp": True},
|
||||
"inbounds": [inbound],
|
||||
"outbounds": [
|
||||
out_cell,
|
||||
{"type": "block", "tag": "block"},
|
||||
],
|
||||
# 所有代理流量最终都走 cellular(数据)
|
||||
"route": {
|
||||
"rules": [],
|
||||
"final": "cellular",
|
||||
"auto_detect_interface": False,
|
||||
},
|
||||
"experimental": {
|
||||
"clash_api": {
|
||||
"external_controller": f"{os.environ['GEN_PANEL_HOST']}:{os.environ['GEN_PANEL_PORT']}",
|
||||
"secret": os.environ["GEN_PANEL_SECRET"],
|
||||
"default_mode": "rule",
|
||||
"external_ui": ui_dir,
|
||||
},
|
||||
"cache_file": {
|
||||
"enabled": True,
|
||||
"path": "/var/lib/cellular-proxy/cache.db",
|
||||
"store_fakeip": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if (os.environ.get("GEN_DNS") or "true").lower() == "true":
|
||||
# DNS 也走数据出口,避免解析从 WiFi 出去
|
||||
cfg["dns"] = {
|
||||
"servers": [
|
||||
{
|
||||
"tag": "remote",
|
||||
"address": "1.1.1.1",
|
||||
"detour": "cellular",
|
||||
},
|
||||
],
|
||||
"final": "remote",
|
||||
"strategy": "ipv4_only",
|
||||
}
|
||||
|
||||
Path(os.environ["GEN_OUT"]).write_text(
|
||||
json.dumps(cfg, indent=2, ensure_ascii=False) + "\n", encoding="utf-8"
|
||||
)
|
||||
print("wrote", os.environ["GEN_OUT"])
|
||||
PY
|
||||
|
||||
cat > "$OUT_DIR/runtime.env" <<EOF
|
||||
INSTALL_DIR=${INSTALL_DIR}
|
||||
LOG_DIR=${LOG_DIR}
|
||||
CELLULAR_IFACE=${cell}
|
||||
CELLULAR_SOURCE_IP=${src_ip}
|
||||
PROXY_LISTEN_HOST=${PROXY_LISTEN_HOST}
|
||||
PROXY_MIXED_PORT=${PROXY_MIXED_PORT}
|
||||
PANEL_LISTEN_HOST=${PANEL_LISTEN_HOST}
|
||||
PANEL_PORT=${PANEL_PORT}
|
||||
EGRESS_CHECK_URL=${EGRESS_CHECK_URL}
|
||||
EXPECTED_CELLULAR_PUBLIC_IP=${EXPECTED_CELLULAR_PUBLIC_IP}
|
||||
MEMORY_MAX_MB=${MEMORY_MAX_MB}
|
||||
REQUIRE_CELLULAR_IFACE=${REQUIRE_CELLULAR_IFACE}
|
||||
EOF
|
||||
|
||||
cat > "$OUT_DIR/SUMMARY.txt" <<EOF
|
||||
generated_at=$(date -Iseconds)
|
||||
语义=走代理的连接一律从数据网卡出口
|
||||
cellular_iface=${cell}
|
||||
cellular_source_ip=${src_ip}
|
||||
proxy=${PROXY_LISTEN_HOST}:${PROXY_MIXED_PORT} (HTTP+SOCKS mixed)
|
||||
panel=http://<LAN-IP>:${PANEL_PORT}/ secret=见 settings PANEL_SECRET
|
||||
memory_max_mb=${MEMORY_MAX_MB}
|
||||
EOF
|
||||
|
||||
info "生成完成: $OUT_DIR"
|
||||
cat "$OUT_DIR/SUMMARY.txt"
|
||||
Executable
+226
@@ -0,0 +1,226 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
source "$ROOT_DIR/scripts/lib.sh"
|
||||
|
||||
AUTO_DETECT="${AUTO_DETECT:-true}"
|
||||
AUTO_VERIFY="${AUTO_VERIFY:-true}"
|
||||
SKIP_START="${SKIP_START:-false}"
|
||||
|
||||
if [[ ! -f "$ROOT_DIR/config/settings.conf" ]]; then
|
||||
cp "$ROOT_DIR/config/settings.conf.example" "$ROOT_DIR/config/settings.conf"
|
||||
warn "已创建 config/settings.conf(将自动探测数据网卡并生成密钥)"
|
||||
fi
|
||||
|
||||
load_config
|
||||
need_root
|
||||
|
||||
info "1/6 自动探测并绑定数据网卡"
|
||||
"$ROOT_DIR/scripts/detect.sh" || true
|
||||
|
||||
cell=""
|
||||
if [[ -n "${CELLULAR_IFACE:-}" ]] && iface_exists "$CELLULAR_IFACE"; then
|
||||
cell="$CELLULAR_IFACE"
|
||||
info "使用配置的数据网卡: $cell"
|
||||
elif [[ "$AUTO_DETECT" == "true" ]]; then
|
||||
cell="$(detect_cellular_iface "" || true)"
|
||||
fi
|
||||
|
||||
if [[ -z "$cell" ]]; then
|
||||
die "未能自动探测数据网卡。
|
||||
请插入/拨通数据模块后重试,或手动:
|
||||
1) ./scripts/detect.sh
|
||||
2) 编辑 config/settings.conf 设置 CELLULAR_IFACE=网卡名
|
||||
3) sudo ./scripts/install.sh"
|
||||
fi
|
||||
|
||||
src_ip="$(detect_source_ip "$cell" || true)"
|
||||
persist_cellular_to_settings "$ROOT_DIR/config/settings.conf" "$cell" "$src_ip"
|
||||
# 重新加载
|
||||
CELLULAR_IFACE="$cell"
|
||||
CELLULAR_SOURCE_IP="$src_ip"
|
||||
info "已绑定 CELLULAR_IFACE=$cell source_ip=${src_ip:-auto}"
|
||||
|
||||
# 随机面板密钥(若仍是默认)
|
||||
if [[ "$PANEL_SECRET" == "please-change-me" || "$PANEL_SECRET" == "change-this-secret" || -z "$PANEL_SECRET" ]]; then
|
||||
gen="$(openssl rand -hex 12 2>/dev/null || head -c 16 /dev/urandom | xxd -p | tr -d '\n')"
|
||||
tmp="$(mktemp)"
|
||||
awk -v k="PANEL_SECRET" -v v="$gen" '
|
||||
BEGIN { done=0 }
|
||||
index($0, k "=")==1 { print k "=" v; done=1; next }
|
||||
{ print }
|
||||
END { if (!done) print k "=" v }
|
||||
' "$ROOT_DIR/config/settings.conf" > "$tmp"
|
||||
mv "$tmp" "$ROOT_DIR/config/settings.conf"
|
||||
PANEL_SECRET="$gen"
|
||||
warn "已自动生成 PANEL_SECRET(见 settings.conf)"
|
||||
fi
|
||||
|
||||
# 再 load 一次保证变量一致
|
||||
load_config
|
||||
CELLULAR_IFACE="$cell"
|
||||
CELLULAR_SOURCE_IP="${src_ip:-$CELLULAR_SOURCE_IP}"
|
||||
|
||||
info "2/6 下载/安装 sing-box + UI"
|
||||
"$ROOT_DIR/scripts/fetch-binaries.sh"
|
||||
|
||||
info "3/6 生成配置(代理出口 bind = $cell)"
|
||||
"$ROOT_DIR/scripts/generate.sh" "$ROOT_DIR/generated"
|
||||
ensure_dirs
|
||||
mkdir -p /var/lib/cellular-proxy
|
||||
install -m 0644 "$ROOT_DIR/generated/config.json" "$INSTALL_DIR/etc/config.json"
|
||||
install -m 0644 "$ROOT_DIR/generated/runtime.env" "$INSTALL_DIR/etc/runtime.env"
|
||||
install -m 0644 "$ROOT_DIR/config/settings.conf" "$INSTALL_DIR/etc/settings.conf"
|
||||
cp -a "$ROOT_DIR/scripts" "$INSTALL_DIR/"
|
||||
cp -a "$ROOT_DIR/ui" "$INSTALL_DIR/" 2>/dev/null || true
|
||||
if ! "$INSTALL_DIR/bin/sing-box" check -c "$INSTALL_DIR/etc/config.json"; then
|
||||
die "sing-box 配置校验失败"
|
||||
fi
|
||||
|
||||
# 确认 bind_interface 写进配置
|
||||
if ! grep -q "\"bind_interface\": \"$cell\"" "$INSTALL_DIR/etc/config.json" \
|
||||
&& ! grep -q "\"bind_interface\": \"$cell\"" "$INSTALL_DIR/etc/config.json" 2>/dev/null; then
|
||||
# JSON 可能无空格差异
|
||||
if ! python3 - "$INSTALL_DIR/etc/config.json" "$cell" <<'PY'
|
||||
import json,sys
|
||||
c=json.load(open(sys.argv[1]))
|
||||
cell=sys.argv[2]
|
||||
ok=any(o.get("tag")=="cellular" and o.get("bind_interface")==cell for o in c.get("outbounds",[]))
|
||||
sys.exit(0 if ok else 1)
|
||||
PY
|
||||
then
|
||||
die "配置未正确绑定数据网卡 $cell"
|
||||
fi
|
||||
fi
|
||||
info "配置已确认 bind_interface=$cell"
|
||||
|
||||
install -m 0755 /dev/stdin "$INSTALL_DIR/bin/cpxy" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
BASE="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
export CONFIG_FILE="${CONFIG_FILE:-$BASE/etc/settings.conf}"
|
||||
cmd="${1:-help}"
|
||||
shift || true
|
||||
case "$cmd" in
|
||||
detect) exec "$BASE/scripts/detect.sh" "$@" ;;
|
||||
generate)
|
||||
# 重新探测:若 settings 里网卡丢失则自动补
|
||||
# shellcheck source=/dev/null
|
||||
source "$BASE/scripts/lib.sh"
|
||||
load_config
|
||||
if [[ -z "${CELLULAR_IFACE:-}" ]] || ! iface_exists "${CELLULAR_IFACE:-}"; then
|
||||
cell="$(detect_cellular_iface "" || true)"
|
||||
if [[ -n "$cell" ]]; then
|
||||
src="$(detect_source_ip "$cell" || true)"
|
||||
persist_cellular_to_settings "$CONFIG_FILE" "$cell" "$src"
|
||||
CELLULAR_IFACE="$cell"
|
||||
fi
|
||||
fi
|
||||
"$BASE/scripts/generate.sh" "$BASE/generated"
|
||||
install -m 0644 "$BASE/generated/config.json" "$BASE/etc/config.json"
|
||||
install -m 0644 "$BASE/generated/runtime.env" "$BASE/etc/runtime.env"
|
||||
install -m 0644 "$CONFIG_FILE" "$BASE/etc/settings.conf" 2>/dev/null || true
|
||||
"$BASE/bin/sing-box" check -c "$BASE/etc/config.json"
|
||||
systemctl restart cellular-proxy 2>/dev/null || true
|
||||
;;
|
||||
rebind)
|
||||
# shellcheck source=/dev/null
|
||||
source "$BASE/scripts/lib.sh"
|
||||
load_config
|
||||
cell="$(detect_cellular_iface "" || true)"
|
||||
[[ -n "$cell" ]] || die "未能探测数据网卡"
|
||||
src="$(detect_source_ip "$cell" || true)"
|
||||
persist_cellular_to_settings "$CONFIG_FILE" "$cell" "$src"
|
||||
info "已重新绑定 $cell"
|
||||
exec "$0" generate
|
||||
;;
|
||||
start) systemctl start cellular-proxy ;;
|
||||
stop) systemctl stop cellular-proxy ;;
|
||||
restart) systemctl restart cellular-proxy ;;
|
||||
status) systemctl status cellular-proxy --no-pager || true ;;
|
||||
verify) exec "$BASE/scripts/verify.sh" "$@" ;;
|
||||
logs) journalctl -u cellular-proxy -n "${1:-80}" -f ;;
|
||||
help|*)
|
||||
cat <<H
|
||||
cpxy — cellular-proxy
|
||||
语义: 走代理的连接一律从数据网卡出口
|
||||
cpxy detect | rebind | generate | start | stop | restart | status
|
||||
cpxy verify | logs [N]
|
||||
H
|
||||
;;
|
||||
esac
|
||||
EOF
|
||||
ln -sfn "$INSTALL_DIR/bin/cpxy" /usr/local/bin/cpxy
|
||||
|
||||
info "4/6 安装 systemd"
|
||||
MEM="${MEMORY_MAX_MB:-96}"
|
||||
install -m 0644 "$ROOT_DIR/systemd/cellular-proxy.service" /etc/systemd/system/cellular-proxy.service
|
||||
sed -i "s|@INSTALL_DIR@|$INSTALL_DIR|g" /etc/systemd/system/cellular-proxy.service
|
||||
sed -i "s|@LOG_DIR@|$LOG_DIR|g" /etc/systemd/system/cellular-proxy.service
|
||||
if [[ "$MEM" != "0" && -n "$MEM" ]]; then
|
||||
sed -i "s|@MEMORY_MAX@|${MEM}M|g" /etc/systemd/system/cellular-proxy.service
|
||||
else
|
||||
sed -i '/MemoryMax=@MEMORY_MAX@/d' /etc/systemd/system/cellular-proxy.service
|
||||
fi
|
||||
systemctl daemon-reload
|
||||
|
||||
info "5/6 启动服务"
|
||||
export SYSTEMD_PAGER=cat
|
||||
export SYSTEMD_COLORS=0
|
||||
if [[ "$SKIP_START" == "true" ]]; then
|
||||
systemctl enable cellular-proxy.service
|
||||
systemctl stop cellular-proxy.service 2>/dev/null || true
|
||||
info "已按 SKIP_START 跳过启动"
|
||||
else
|
||||
systemctl enable cellular-proxy.service
|
||||
systemctl restart cellular-proxy.service
|
||||
# 等待 active,最多约 8 秒(避免 status 卡住)
|
||||
ok=0
|
||||
for _ in 1 2 3 4 5 6 7 8; do
|
||||
if systemctl is-active --quiet cellular-proxy.service; then
|
||||
ok=1
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
active_state="$(systemctl is-active cellular-proxy.service 2>/dev/null || echo unknown)"
|
||||
info "服务状态: $active_state"
|
||||
if [[ "$ok" -ne 1 ]]; then
|
||||
err "服务未处于 active"
|
||||
systemctl show cellular-proxy.service -p ActiveState -p SubState -p Result -p ExecMainStatus --no-pager 2>/dev/null || true
|
||||
journalctl -u cellular-proxy -n 40 --no-pager 2>/dev/null || true
|
||||
die "启动失败,请检查数据网卡 $cell 是否 up 且有 IP"
|
||||
fi
|
||||
fi
|
||||
|
||||
info "6/6 自动验证出口(短超时,失败不阻塞安装结束)"
|
||||
if [[ "$SKIP_START" != "true" && "$AUTO_VERIFY" == "true" ]]; then
|
||||
set +e
|
||||
# 安装阶段用短超时;VERIFY_QUICK=1 跳过 detect 长输出
|
||||
VERIFY_QUICK=1 EGRESS_TIMEOUT="${EGRESS_TIMEOUT:-8}" \
|
||||
"$ROOT_DIR/scripts/verify.sh"
|
||||
vr=$?
|
||||
set -e
|
||||
if [[ "$vr" -eq 0 ]]; then
|
||||
info "出口验证通过:代理已从数据网卡出"
|
||||
else
|
||||
warn "自动验证未通过(exit=$vr)。安装仍算完成,稍后可: cpxy verify"
|
||||
warn "当前绑定: CELLULAR_IFACE=$cell ip=${src_ip:-}"
|
||||
fi
|
||||
else
|
||||
info "已跳过自动验证"
|
||||
fi
|
||||
|
||||
echo
|
||||
info "安装完成(全自动探测 + 绑定)"
|
||||
echo " 数据网卡: $cell (${src_ip:-no-ipv4})"
|
||||
echo " 语义: 不走代理 → 系统默认(WiFi);走代理 → 数据流量"
|
||||
echo " 代理: HTTP/SOCKS ${PROXY_LISTEN_HOST}:${PROXY_MIXED_PORT}"
|
||||
echo " 面板: http://<LAN-IP>:${PANEL_PORT}/"
|
||||
echo " 密钥: $PANEL_SECRET"
|
||||
echo " 配置: $INSTALL_DIR/etc/settings.conf"
|
||||
echo " 管理: cpxy rebind | verify | logs | status"
|
||||
echo " 若刚才像卡住:多半在测公网出口,现已改为短超时并保证退出"
|
||||
# 安装脚本始终以 0 结束(服务已 active);验证失败只告警
|
||||
exit 0
|
||||
Executable
+326
@@ -0,0 +1,326 @@
|
||||
#!/usr/bin/env bash
|
||||
# 公共库
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
CONFIG_FILE="${CONFIG_FILE:-$ROOT_DIR/config/settings.conf}"
|
||||
|
||||
log() { printf '[%s] %s\n' "$(date '+%F %T')" "$*" >&2; }
|
||||
info() { log "INFO $*"; }
|
||||
warn() { log "WARN $*"; }
|
||||
err() { log "ERROR $*"; }
|
||||
die() { err "$*"; exit 1; }
|
||||
|
||||
need_root() {
|
||||
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
|
||||
die "请用 root 运行(sudo)"
|
||||
fi
|
||||
}
|
||||
|
||||
load_config() {
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
die "缺少配置: $CONFIG_FILE
|
||||
请先: cp $ROOT_DIR/config/settings.conf.example $ROOT_DIR/config/settings.conf"
|
||||
fi
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "$CONFIG_FILE"
|
||||
set +a
|
||||
|
||||
INSTALL_DIR="${INSTALL_DIR:-/opt/cellular-proxy}"
|
||||
LOG_DIR="${LOG_DIR:-/var/log/cellular-proxy}"
|
||||
CELLULAR_IFACE="${CELLULAR_IFACE:-}"
|
||||
CELLULAR_SOURCE_IP="${CELLULAR_SOURCE_IP:-}"
|
||||
REQUIRE_CELLULAR_IFACE="${REQUIRE_CELLULAR_IFACE:-true}"
|
||||
CELLULAR_IFACE_PATTERNS="${CELLULAR_IFACE_PATTERNS:-wwan,wwp,usb,enx,ppp,cdc,rmnet,ccmni,mbim,qmi}"
|
||||
PROXY_LISTEN_HOST="${PROXY_LISTEN_HOST:-0.0.0.0}"
|
||||
PROXY_MIXED_PORT="${PROXY_MIXED_PORT:-7890}"
|
||||
PROXY_USER="${PROXY_USER:-}"
|
||||
PROXY_PASS="${PROXY_PASS:-}"
|
||||
PANEL_LISTEN_HOST="${PANEL_LISTEN_HOST:-0.0.0.0}"
|
||||
PANEL_PORT="${PANEL_PORT:-9090}"
|
||||
PANEL_SECRET="${PANEL_SECRET:-please-change-me}"
|
||||
ENABLE_DNS="${ENABLE_DNS:-true}"
|
||||
LOG_LEVEL="${LOG_LEVEL:-warn}"
|
||||
SING_BOX_SOURCE="${SING_BOX_SOURCE:-release}"
|
||||
SING_BOX_VERSION="${SING_BOX_VERSION:-1.11.7}"
|
||||
SING_BOX_BIN="${SING_BOX_BIN:-}"
|
||||
GITHUB_PROXY="${GITHUB_PROXY:-https://git.86482425.xyz}"
|
||||
MEMORY_MAX_MB="${MEMORY_MAX_MB:-96}"
|
||||
EGRESS_CHECK_URL="${EGRESS_CHECK_URL:-https://ifconfig.me}"
|
||||
EXPECTED_CELLULAR_PUBLIC_IP="${EXPECTED_CELLULAR_PUBLIC_IP:-}"
|
||||
|
||||
for v in REQUIRE_CELLULAR_IFACE ENABLE_DNS; do
|
||||
val="${!v}"
|
||||
case "${val,,}" in
|
||||
1|true|yes|on) printf -v "$v" '%s' true ;;
|
||||
*) printf -v "$v" '%s' false ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
iface_exists() {
|
||||
local ifc="$1"
|
||||
[[ -n "$ifc" ]] && [[ -d "/sys/class/net/$ifc" ]]
|
||||
}
|
||||
|
||||
is_virtual_or_skip_iface() {
|
||||
local name="$1"
|
||||
case "$name" in
|
||||
lo|docker*|br-*|veth*|virbr*|cni*|flannel*|tun*|tap*|wg*|zt*|tailscale*|easy*|et*|nlmon*|dummy*|ifb*|bond*|team*|macvlan*|ipvlan*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
# bridge that is not a physical device
|
||||
if [[ -d "/sys/class/net/$name/bridge" ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
list_ifaces() {
|
||||
local name state ip
|
||||
for name in $(ls /sys/class/net 2>/dev/null | sort); do
|
||||
[[ "$name" == "lo" ]] && continue
|
||||
state="$(cat "/sys/class/net/$name/operstate" 2>/dev/null || echo unknown)"
|
||||
ip="$(ip -4 -o addr show dev "$name" 2>/dev/null | awk '{print $4}' | head -1)"
|
||||
printf '%s|%s|%s\n' "$name" "$state" "${ip:-}"
|
||||
done
|
||||
}
|
||||
|
||||
detect_default_iface() {
|
||||
ip route show default 2>/dev/null | awk '/default/ {print $5; exit}'
|
||||
}
|
||||
|
||||
iface_has_carrier() {
|
||||
local ifc="$1" c
|
||||
c="$(cat "/sys/class/net/$ifc/carrier" 2>/dev/null || echo 0)"
|
||||
[[ "$c" == "1" ]]
|
||||
}
|
||||
|
||||
iface_driver() {
|
||||
local ifc="$1" link
|
||||
link="$(readlink -f "/sys/class/net/$ifc/device/driver" 2>/dev/null || true)"
|
||||
if [[ -n "$link" ]]; then
|
||||
basename "$link"
|
||||
return
|
||||
fi
|
||||
# some USB eth put driver under device
|
||||
if [[ -f "/sys/class/net/$ifc/device/uevent" ]]; then
|
||||
awk -F= '/^DRIVER=/{print $2; exit}' "/sys/class/net/$ifc/device/uevent" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
iface_looks_cellular_by_driver() {
|
||||
local ifc="$1" drv
|
||||
drv="$(iface_driver "$ifc" || true)"
|
||||
case "${drv,,}" in
|
||||
qmi_wwan|cdc_mbim|cdc_ncm|cdc_ether|cdc_wdm|option|huawei_cdc_ncm|rndis_host|GobiNet|GobiSerial|simcom*|rmnet*|mhi_net|ipa)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
iface_matches_patterns() {
|
||||
local name="$1" p
|
||||
local patterns="${CELLULAR_IFACE_PATTERNS:-wwan,wwp,usb,enx,ppp,cdc,rmnet,ccmni,mbim,qmi}"
|
||||
IFS=',' read -r -a arr <<< "$patterns"
|
||||
for p in "${arr[@]}"; do
|
||||
p="${p// /}"
|
||||
[[ -z "$p" ]] && continue
|
||||
if [[ "$name" == *"$p"* ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# 多默认路由时,metric 更大的往往是数据网(WiFi metric 更小优先)
|
||||
detect_secondary_default_iface() {
|
||||
local primary secondary
|
||||
primary="$(detect_default_iface || true)"
|
||||
secondary="$(
|
||||
ip route show default 2>/dev/null | awk -v p="$primary" '
|
||||
/default/ {
|
||||
iface=""; metric=0
|
||||
for (i=1;i<=NF;i++) {
|
||||
if ($i=="dev") iface=$(i+1)
|
||||
if ($i=="metric") metric=$(i+1)+0
|
||||
}
|
||||
if (iface!="" && iface!=p) {
|
||||
print metric, iface
|
||||
}
|
||||
}
|
||||
' | sort -n | awk 'END {print $2}'
|
||||
)"
|
||||
if [[ -n "$secondary" && "$secondary" != "$primary" ]]; then
|
||||
echo "$secondary"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# 打分选数据网卡(stdout 仅输出网卡名)
|
||||
# 更高分优先
|
||||
score_iface_as_cellular() {
|
||||
local name="$1"
|
||||
local default_if="$2"
|
||||
local state ip score=0 drv
|
||||
|
||||
is_virtual_or_skip_iface "$name" && { echo 0; return; }
|
||||
state="$(cat "/sys/class/net/$name/operstate" 2>/dev/null || echo unknown)"
|
||||
ip="$(ip -4 -o addr show dev "$name" 2>/dev/null | awk '{print $4}' | head -1 | cut -d/ -f1)"
|
||||
drv="$(iface_driver "$name" || true)"
|
||||
|
||||
# 名称
|
||||
if iface_matches_patterns "$name"; then score=$((score + 80)); fi
|
||||
# 驱动
|
||||
if iface_looks_cellular_by_driver "$name"; then score=$((score + 100)); fi
|
||||
# 非默认网卡(关键:系统默认走 WiFi)
|
||||
if [[ -n "$default_if" && "$name" != "$default_if" ]]; then score=$((score + 40)); fi
|
||||
# 有 IPv4
|
||||
if [[ -n "$ip" ]]; then score=$((score + 30)); fi
|
||||
# up/carrier
|
||||
if [[ "$state" == "up" || "$state" == "unknown" ]]; then score=$((score + 15)); fi
|
||||
if iface_has_carrier "$name"; then score=$((score + 10)); fi
|
||||
# 默认网卡通常是 WiFi/有线,大幅降权
|
||||
if [[ -n "$default_if" && "$name" == "$default_if" ]]; then score=$((score - 60)); fi
|
||||
# docker 等已在 skip;再防 enp/eth 当默认时
|
||||
case "$name" in
|
||||
eth*|enp*|eno*|ens*|wlan*|wlp*|wl*)
|
||||
if [[ "$name" == "$default_if" ]]; then score=$((score - 20)); fi
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "$score"
|
||||
}
|
||||
|
||||
# 自动探测数据网卡:配置 > 名称/驱动 > 次默认路由 > 非默认有 IP 物理口
|
||||
detect_cellular_iface() {
|
||||
local configured="${1:-}"
|
||||
local default_if name state ip best_name="" best_score=0 score
|
||||
|
||||
if [[ -n "$configured" ]]; then
|
||||
if [[ "$configured" == "lo" ]]; then
|
||||
warn "CELLULAR_IFACE=lo 无效,将尝试自动探测"
|
||||
elif iface_exists "$configured"; then
|
||||
echo "$configured"
|
||||
return 0
|
||||
else
|
||||
warn "配置的 CELLULAR_IFACE=$configured 不存在,将尝试自动探测"
|
||||
if [[ "${REQUIRE_CELLULAR_IFACE:-true}" == "true" && -n "${FORCE_CONFIGURED_ONLY:-}" ]]; then
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
default_if="$(detect_default_iface || true)"
|
||||
|
||||
# 1) 按评分扫描全部接口
|
||||
while IFS='|' read -r name state ip; do
|
||||
[[ -z "$name" ]] && continue
|
||||
is_virtual_or_skip_iface "$name" && continue
|
||||
score="$(score_iface_as_cellular "$name" "$default_if")"
|
||||
if [[ "$score" -gt "$best_score" ]]; then
|
||||
best_score="$score"
|
||||
best_name="$name"
|
||||
fi
|
||||
done < <(list_ifaces)
|
||||
|
||||
# 需要足够置信度(避免把唯一的 WiFi 当数据)
|
||||
if [[ -n "$best_name" && "$best_score" -ge 70 ]]; then
|
||||
info "自动探测数据网卡: $best_name (score=$best_score, default=$default_if, driver=$(iface_driver "$best_name" || true))"
|
||||
echo "$best_name"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 2) 多默认路由的次要口
|
||||
if name="$(detect_secondary_default_iface 2>/dev/null || true)"; then
|
||||
if [[ -n "$name" ]] && iface_exists "$name"; then
|
||||
info "根据次要默认路由探测数据网卡: $name"
|
||||
echo "$name"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3) 任意非默认、有 IPv4、非虚拟
|
||||
while IFS='|' read -r name state ip; do
|
||||
[[ -z "$name" ]] && continue
|
||||
is_virtual_or_skip_iface "$name" && continue
|
||||
[[ -z "$ip" ]] && continue
|
||||
if [[ -n "$default_if" && "$name" == "$default_if" ]]; then
|
||||
continue
|
||||
fi
|
||||
if [[ "$state" == "up" || "$state" == "unknown" ]]; then
|
||||
info "回退选择非默认网卡: $name"
|
||||
echo "$name"
|
||||
return 0
|
||||
fi
|
||||
done < <(list_ifaces)
|
||||
|
||||
if [[ -n "$best_name" && "$best_score" -gt 0 ]]; then
|
||||
warn "置信度较低,选用: $best_name (score=$best_score)"
|
||||
echo "$best_name"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
detect_source_ip() {
|
||||
local ifc="$1"
|
||||
ip -4 -o addr show dev "$ifc" 2>/dev/null | awk '{print $4}' | head -1 | cut -d/ -f1
|
||||
}
|
||||
|
||||
# 把探测结果写回 settings.conf
|
||||
persist_cellular_to_settings() {
|
||||
local conf="${1:-$CONFIG_FILE}"
|
||||
local cell="$2"
|
||||
local src_ip="${3:-}"
|
||||
[[ -f "$conf" ]] || return 1
|
||||
local tmp
|
||||
tmp="$(mktemp)"
|
||||
awk -v k="CELLULAR_IFACE" -v v="$cell" '
|
||||
BEGIN { done=0 }
|
||||
index($0, k "=")==1 { print k "=" v; done=1; next }
|
||||
{ print }
|
||||
END { if (!done) print k "=" v }
|
||||
' "$conf" > "$tmp"
|
||||
mv "$tmp" "$conf"
|
||||
if [[ -n "$src_ip" ]]; then
|
||||
tmp="$(mktemp)"
|
||||
awk -v k="CELLULAR_SOURCE_IP" -v v="$src_ip" '
|
||||
BEGIN { done=0 }
|
||||
index($0, k "=")==1 { print k "=" v; done=1; next }
|
||||
{ print }
|
||||
END { if (!done) print k "=" v }
|
||||
' "$conf" > "$tmp"
|
||||
mv "$tmp" "$conf"
|
||||
fi
|
||||
}
|
||||
|
||||
arch_go() {
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64) echo amd64 ;;
|
||||
aarch64|arm64) echo arm64 ;;
|
||||
armv7l|armhf) echo armv7 ;;
|
||||
*) die "不支持的架构: $(uname -m)" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
ensure_dirs() {
|
||||
mkdir -p "$INSTALL_DIR"/{bin,etc,ui,generated} "$LOG_DIR"
|
||||
}
|
||||
|
||||
resolve_cellular() {
|
||||
local cell
|
||||
if ! cell="$(detect_cellular_iface "$CELLULAR_IFACE")"; then
|
||||
if [[ "$REQUIRE_CELLULAR_IFACE" == "true" ]]; then
|
||||
die "无法确定数据网卡。请设置 CELLULAR_IFACE= 或运行 scripts/detect.sh / cpxy detect"
|
||||
fi
|
||||
warn "未找到数据网卡"
|
||||
cell=""
|
||||
fi
|
||||
echo "$cell"
|
||||
}
|
||||
Executable
+98
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
source "$ROOT_DIR/scripts/lib.sh"
|
||||
|
||||
if [[ -f "${CONFIG_FILE:-$ROOT_DIR/config/settings.conf}" ]]; then
|
||||
load_config
|
||||
elif [[ -f "$INSTALL_DIR/etc/settings.conf" ]]; then
|
||||
CONFIG_FILE="$INSTALL_DIR/etc/settings.conf"
|
||||
load_config
|
||||
elif [[ -f "$ROOT_DIR/generated/runtime.env" ]]; then
|
||||
set -a
|
||||
# shellcheck disable=SC1091
|
||||
source "$ROOT_DIR/generated/runtime.env"
|
||||
set +a
|
||||
else
|
||||
# 尝试已安装路径
|
||||
if [[ -f /opt/cellular-proxy/etc/settings.conf ]]; then
|
||||
CONFIG_FILE=/opt/cellular-proxy/etc/settings.conf
|
||||
load_config
|
||||
else
|
||||
load_config
|
||||
fi
|
||||
fi
|
||||
|
||||
URL="${EGRESS_CHECK_URL:-https://ifconfig.me}"
|
||||
# 安装阶段可用 EGRESS_TIMEOUT=8 缩短;手动 verify 默认 12
|
||||
TIMEOUT="${EGRESS_TIMEOUT:-12}"
|
||||
HOST="$PROXY_LISTEN_HOST"
|
||||
if [[ "$HOST" == "0.0.0.0" || "$HOST" == "::" || -z "$HOST" ]]; then
|
||||
HOST="127.0.0.1"
|
||||
fi
|
||||
PORT="${PROXY_MIXED_PORT:-7890}"
|
||||
QUICK="${VERIFY_QUICK:-false}"
|
||||
case "${QUICK,,}" in
|
||||
1|true|yes|on) QUICK=true ;;
|
||||
*) QUICK=false ;;
|
||||
esac
|
||||
|
||||
curl_ip() {
|
||||
# 单次探测,严格超时,绝不无限挂起
|
||||
local extra=("$@")
|
||||
# --max-time 总时长;--connect-timeout 连接
|
||||
curl -fsS --connect-timeout 3 --max-time "$TIMEOUT" "${extra[@]}" "$URL" 2>/dev/null \
|
||||
| tr -d '\r\n' \
|
||||
| head -c 64
|
||||
}
|
||||
|
||||
if [[ "$QUICK" != "true" ]]; then
|
||||
echo "========== 接口 =========="
|
||||
"$ROOT_DIR/scripts/detect.sh" || true
|
||||
echo
|
||||
fi
|
||||
|
||||
echo "========== 默认出口(不走代理,通常是 WiFi) =========="
|
||||
info "探测中(最多 ${TIMEOUT}s): $URL"
|
||||
direct_ip="$(curl_ip || true)"
|
||||
[[ -n "$direct_ip" ]] || direct_ip=FAIL
|
||||
echo "direct: $direct_ip"
|
||||
echo
|
||||
|
||||
echo "========== 代理出口(应是数据流量公网 IP) =========="
|
||||
auth=()
|
||||
if [[ -n "${PROXY_USER:-}" ]]; then
|
||||
auth=(--proxy-user "${PROXY_USER}:${PROXY_PASS}")
|
||||
fi
|
||||
info "SOCKS 探测中(最多 ${TIMEOUT}s): ${HOST}:${PORT}"
|
||||
socks_ip="$(curl_ip "${auth[@]}" --socks5-hostname "${HOST}:${PORT}" || true)"
|
||||
[[ -n "$socks_ip" ]] || socks_ip=FAIL
|
||||
echo "socks : $socks_ip"
|
||||
|
||||
info "HTTP 探测中(最多 ${TIMEOUT}s): ${HOST}:${PORT}"
|
||||
http_ip="$(curl_ip "${auth[@]}" -x "http://${HOST}:${PORT}" || true)"
|
||||
[[ -n "$http_ip" ]] || http_ip=FAIL
|
||||
echo "http : $http_ip"
|
||||
echo
|
||||
|
||||
if [[ "$direct_ip" == "FAIL" || "$socks_ip" == "FAIL" ]]; then
|
||||
err "探测失败(服务/数据网是否在线? journalctl -u cellular-proxy -e)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$direct_ip" == "$socks_ip" ]]; then
|
||||
warn "代理与直连公网 IP 相同 — 可能未绑到数据网卡,或数据与 WiFi 同出口"
|
||||
warn "请检查 CELLULAR_IFACE / 数据网是否 up / bind_interface"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ -n "${EXPECTED_CELLULAR_PUBLIC_IP:-}" && "$socks_ip" != "$EXPECTED_CELLULAR_PUBLIC_IP" ]]; then
|
||||
err "代理出口 $socks_ip 与 EXPECTED_CELLULAR_PUBLIC_IP=$EXPECTED_CELLULAR_PUBLIC_IP 不一致"
|
||||
exit 3
|
||||
fi
|
||||
|
||||
info "OK: 直连(WiFi)=$direct_ip 代理(数据)=$socks_ip"
|
||||
echo
|
||||
echo "局域网: curl --socks5-hostname <LAN-IP>:${PORT} $URL"
|
||||
echo "面板: http://<LAN-IP>:${PANEL_PORT}/"
|
||||
Reference in New Issue
Block a user