#!/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 # 始终以当前网卡实 IP 为准:WWAN 重拨后 iface 名常不变,但 CELLULAR_SOURCE_IP 会过期。 # 过期的 inet4_bind_address 会导致 sing-box 出站失败 → 客户端 SOCKS rep=0x01。 live_src="" if [[ -n "$cell" ]]; then live_src="$(detect_source_ip "$cell" || true)" fi src_ip="${live_src:-${CELLULAR_SOURCE_IP:-}}" if [[ -n "$cell" && -n "$live_src" && "${CELLULAR_SOURCE_IP:-}" != "$live_src" ]]; then info "源 IP 已漂移: ${CELLULAR_SOURCE_IP:-<空>} → $live_src,写回 settings" persist_cellular_to_settings "${CONFIG_FILE:-$ROOT_DIR/etc/settings.conf}" "$cell" "$live_src" || true # 兼容 live 安装布局:settings 在 etc/ if [[ -f /opt/cellular-proxy/etc/settings.conf ]]; then persist_cellular_to_settings /opt/cellular-proxy/etc/settings.conf "$cell" "$live_src" || true fi CELLULAR_SOURCE_IP="$live_src" 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" < "$OUT_DIR/SUMMARY.txt" <:${PANEL_PORT}/ secret=见 settings PANEL_SECRET memory_max_mb=${MEMORY_MAX_MB} EOF info "生成完成: $OUT_DIR" cat "$OUT_DIR/SUMMARY.txt"