#!/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 # 出口一旦绑成 USB gadget 网卡,所有代理请求都会超时(客户端看到 SOCKS rep=0x01)。 # 在打网络探测之前先点名,免得只剩一个含糊的 FAIL。 cfg_json="${INSTALL_DIR:-/opt/cellular-proxy}/etc/config.json" cfg_bind="" if command -v python3 >/dev/null 2>&1 && [[ -f "$cfg_json" ]]; then cfg_bind="$(python3 -c ' import json, sys try: cfg = json.load(open(sys.argv[1])) for o in cfg.get("outbounds") or []: if o.get("tag") == "cellular": print(o.get("bind_interface") or "") break except Exception: pass ' "$cfg_json" 2>/dev/null || true)" fi for _ifc in "${CELLULAR_IFACE:-}" "$cfg_bind"; do [[ -n "$_ifc" ]] || continue if iface_is_usb_gadget "$_ifc"; then err "出口绑在 USB gadget 网卡 $_ifc 上(那是本机 -> 上游主机的下行链路),代理必定超时" err "修复: sudo cpxy rebind && sudo cpxy generate" exit 4 fi done 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 :${PORT} $URL" echo "面板: http://:${PANEL_PORT}/"