feat: bind_interface-only by default + WWAN watch auto-rebind
- Default BIND_SOURCE_IP=false: do not pin inet4_bind_address (stale
source IP after WWAN re-dial was the main rep=0x01 cause).
- Add scripts/watch-cellular.sh: ip monitor events + poll, regenerate
when iface missing or pinned source IP drifts.
- systemd: cellular-proxy-watch.service + 2min timer oneshot fallback.
- cpxy watch {status|once|start|stop|logs}
- Install/upgrade enable watch when ENABLE_CELLULAR_WATCH=true.
This commit is contained in:
@@ -72,8 +72,22 @@ cpxy rebind
|
||||
cpxy status | logs
|
||||
cpxy auth --user u --pass p
|
||||
cpxy auth --show | --clear
|
||||
cpxy watch status # WWAN 自动监控
|
||||
cpxy watch once # 手动跑一轮检查
|
||||
```
|
||||
|
||||
### 重拨 / `rep=0x01` 自愈
|
||||
|
||||
| 层级 | 做法 |
|
||||
|------|------|
|
||||
| **绑定(推荐)** | 默认 `BIND_SOURCE_IP=false`:只 `bind_interface=wwanX`,**不 pin 私网源 IP**,重拨换地址通常不用 regenerate |
|
||||
| **监控** | `cellular-proxy-watch`:`ip monitor` 事件 + 60s 轮询;网卡改名/掉线/仍 pin 了 stale IP 时自动 `generate` |
|
||||
| **兜底 timer** | 每 2 分钟 oneshot 再检查一次 |
|
||||
| **手动** | `cpxy rebind && cpxy verify` |
|
||||
|
||||
关监控:`ENABLE_CELLULAR_WATCH=false` 后 `cpxy upgrade`,或 `cpxy watch stop`。
|
||||
若必须 pin 源 IP:`BIND_SOURCE_IP=true`(依赖 watch 同步,否则会再次 stale)。
|
||||
|
||||
- 代理:`LAN-IP:7890`(HTTP + SOCKS5)
|
||||
- 面板:`http://LAN-IP:9090/ui/`(密钥在安装结束输出 / `settings.conf`)
|
||||
- **UI 可直接配置代理账号密码**(管理 API 默认 `9091`,与面板 secret 相同)
|
||||
|
||||
@@ -17,12 +17,20 @@ LOG_DIR=/var/log/cellular-proxy
|
||||
# ---- 数据网卡(代理出口强制绑定)----
|
||||
# 留空则自动探测 wwan/usb/enx 等;建议手动填准
|
||||
CELLULAR_IFACE=
|
||||
# 可选:数据侧源 IPv4(一般留空)
|
||||
# 元数据:数据侧当前源 IPv4(generate/watch 会更新;默认不写入 sing-box)
|
||||
CELLULAR_SOURCE_IP=
|
||||
# 是否把源 IP pin 进 sing-box inet4_bind_address
|
||||
# false(默认,推荐):只 bind_interface=网卡名 → WWAN 重拨换私网 IP 时通常无需 regenerate
|
||||
# true:额外写 inet4_bind_address(需 watch/rebind 同步,否则易 stale → SOCKS rep=0x01)
|
||||
BIND_SOURCE_IP=false
|
||||
# 数据网卡不存在则不启动(推荐 true,避免代理静默从 WiFi 出)
|
||||
REQUIRE_CELLULAR_IFACE=true
|
||||
# 自动探测关键词(逗号分隔)
|
||||
CELLULAR_IFACE_PATTERNS=wwan,wwp,usb,enx,ppp,cdc
|
||||
# 后台监控(推荐开):网卡改名/掉线/无 IPv4 时自动 rebind;默认开启 long-running watch
|
||||
ENABLE_CELLULAR_WATCH=true
|
||||
# watch 轮询兜底间隔秒(事件驱动 ip monitor 为主)
|
||||
WATCH_INTERVAL_SEC=60
|
||||
|
||||
# ---- 代理监听(局域网其它设备用 0.0.0.0)----
|
||||
# mixed = 同一端口同时支持 HTTP 代理 与 SOCKS5
|
||||
|
||||
+18
-6
@@ -14,22 +14,33 @@ if [[ -z "$cell" ]]; then
|
||||
resolve_cellular >/dev/null || true
|
||||
fi
|
||||
|
||||
# 始终以当前网卡实 IP 为准:WWAN 重拨后 iface 名常不变,但 CELLULAR_SOURCE_IP 会过期。
|
||||
# 过期的 inet4_bind_address 会导致 sing-box 出站失败 → 客户端 SOCKS rep=0x01。
|
||||
# 绑定策略(优先更好的实现,而不是只靠定时器修 stale IP):
|
||||
# 默认 BIND_SOURCE_IP=false → 只写 bind_interface,不写 inet4_bind_address。
|
||||
# sing-box 1.11+ 在 wwan 重拨后仍能按网卡出口,无需锁定私网源 IP。
|
||||
# 实测:去掉 inet4_bind_address 后 SOCKS 仍从数据出口出(公网 IP ≠ WiFi)。
|
||||
# BIND_SOURCE_IP=true 时才 pin 源 IP(兼容特殊内核/多地址场景),并始终用 live IP。
|
||||
live_src=""
|
||||
if [[ -n "$cell" ]]; then
|
||||
live_src="$(detect_source_ip "$cell" || true)"
|
||||
fi
|
||||
src_ip="${live_src:-${CELLULAR_SOURCE_IP:-}}"
|
||||
BIND_SOURCE_IP="$(echo "${BIND_SOURCE_IP:-false}" | tr '[:upper:]' '[:lower:]')"
|
||||
if [[ -n "$cell" && -n "$live_src" && "${CELLULAR_SOURCE_IP:-}" != "$live_src" ]]; then
|
||||
info "源 IP 已漂移: ${CELLULAR_SOURCE_IP:-<空>} → $live_src,写回 settings"
|
||||
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
|
||||
# 默认不把源 IP 写进 sing-box,避免下次重拨 stale bind
|
||||
GEN_SRC_FOR_CFG=""
|
||||
if [[ "$BIND_SOURCE_IP" == "true" || "$BIND_SOURCE_IP" == "1" || "$BIND_SOURCE_IP" == "yes" ]]; then
|
||||
GEN_SRC_FOR_CFG="$src_ip"
|
||||
info "BIND_SOURCE_IP=true → pin inet4_bind_address=${GEN_SRC_FOR_CFG:-<空>}"
|
||||
else
|
||||
info "绑定模式: bind_interface only(不 pin 源 IP,重拨更稳)"
|
||||
fi
|
||||
|
||||
if [[ -z "$cell" ]]; then
|
||||
warn "CELLULAR_IFACE 为空,临时用 lo 生成配置(启动前务必修正)"
|
||||
@@ -47,7 +58,7 @@ fi
|
||||
|
||||
export GEN_OUT="$OUT_DIR/config.json"
|
||||
export GEN_CELL="$cell_for_cfg"
|
||||
export GEN_SRC="$src_ip"
|
||||
export GEN_SRC="${GEN_SRC_FOR_CFG:-}"
|
||||
export GEN_PROXY_HOST="$PROXY_LISTEN_HOST"
|
||||
export GEN_PROXY_PORT="$PROXY_MIXED_PORT"
|
||||
export GEN_PROXY_USER="$PROXY_USER"
|
||||
@@ -83,12 +94,13 @@ inbound = {
|
||||
if users:
|
||||
inbound["users"] = users
|
||||
|
||||
# 唯一业务出口:强制绑定数据网卡
|
||||
# 唯一业务出口:强制绑定数据网卡(默认仅 bind_interface)
|
||||
out_cell = {
|
||||
"type": "direct",
|
||||
"tag": "cellular",
|
||||
"bind_interface": cell,
|
||||
}
|
||||
# 仅当 GEN_SRC 非空(BIND_SOURCE_IP=true)时 pin 源 IP
|
||||
if src:
|
||||
out_cell["inet4_bind_address"] = src
|
||||
|
||||
|
||||
@@ -160,6 +160,23 @@ case "$cmd" in
|
||||
auth)
|
||||
exec "$BASE/scripts/apply-proxy-auth.sh" "$@"
|
||||
;;
|
||||
watch)
|
||||
case "${1:-status}" in
|
||||
once) exec "$BASE/scripts/watch-cellular.sh" --once ;;
|
||||
status)
|
||||
systemctl status cellular-proxy-watch.service --no-pager 2>/dev/null || true
|
||||
systemctl status cellular-proxy-watch.timer --no-pager 2>/dev/null || true
|
||||
;;
|
||||
start|enable)
|
||||
systemctl enable --now cellular-proxy-watch.service cellular-proxy-watch.timer 2>/dev/null || true
|
||||
;;
|
||||
stop|disable)
|
||||
systemctl disable --now cellular-proxy-watch.service cellular-proxy-watch.timer 2>/dev/null || true
|
||||
;;
|
||||
logs) journalctl -u cellular-proxy-watch -n "${2:-80}" --no-pager ;;
|
||||
*) exec "$BASE/scripts/watch-cellular.sh" "$@" ;;
|
||||
esac
|
||||
;;
|
||||
upgrade)
|
||||
# 一条命令在线增量升级(保留密钥/网卡)
|
||||
if [[ -x "$BASE/scripts/online-upgrade.sh" ]]; then
|
||||
@@ -178,6 +195,7 @@ cpxy — cellular-proxy
|
||||
cpxy verify | logs [N]
|
||||
cpxy auth --user U --pass P # 设置 7890 代理账号密码
|
||||
cpxy auth --clear | --show
|
||||
cpxy watch [status|once|start|stop|logs] # WWAN 自动监控
|
||||
cpxy upgrade # 一条命令在线增量升级
|
||||
cpxy upgrade --ui-only # 只更新面板
|
||||
cpxy upgrade --force-binary # 强制重下 sing-box
|
||||
@@ -204,6 +222,19 @@ if [[ -f "$ROOT_DIR/systemd/cellular-proxy-admin.service" ]]; then
|
||||
install -m 0644 "$ROOT_DIR/systemd/cellular-proxy-admin.service" /etc/systemd/system/cellular-proxy-admin.service
|
||||
sed -i "s|@INSTALL_DIR@|$INSTALL_DIR|g" /etc/systemd/system/cellular-proxy-admin.service
|
||||
fi
|
||||
# WWAN 自动监控:事件驱动 + 定时兜底
|
||||
install_watch_units() {
|
||||
local u
|
||||
for u in cellular-proxy-watch.service cellular-proxy-watch-once.service cellular-proxy-watch.timer; do
|
||||
if [[ -f "$ROOT_DIR/systemd/$u" ]]; then
|
||||
install -m 0644 "$ROOT_DIR/systemd/$u" "/etc/systemd/system/$u"
|
||||
sed -i "s|@INSTALL_DIR@|$INSTALL_DIR|g" "/etc/systemd/system/$u"
|
||||
sed -i "s|@LOG_DIR@|${LOG_DIR}|g" "/etc/systemd/system/$u"
|
||||
fi
|
||||
done
|
||||
chmod +x "$INSTALL_DIR/scripts/watch-cellular.sh" 2>/dev/null || true
|
||||
}
|
||||
install_watch_units
|
||||
systemctl daemon-reload
|
||||
|
||||
info "5/6 启动服务"
|
||||
@@ -239,6 +270,21 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
# 默认开启 watch(ENABLE_CELLULAR_WATCH!=false)
|
||||
watch_flag="$(echo "${ENABLE_CELLULAR_WATCH:-true}" | tr '[:upper:]' '[:lower:]')"
|
||||
if [[ "$watch_flag" == "false" || "$watch_flag" == "0" || "$watch_flag" == "no" ]]; then
|
||||
systemctl disable --now cellular-proxy-watch.service cellular-proxy-watch.timer 2>/dev/null || true
|
||||
info "ENABLE_CELLULAR_WATCH=false,未启用自动监控"
|
||||
else
|
||||
systemctl enable cellular-proxy-watch.service 2>/dev/null || true
|
||||
systemctl enable cellular-proxy-watch.timer 2>/dev/null || true
|
||||
if [[ "$SKIP_START" != "true" ]]; then
|
||||
systemctl restart cellular-proxy-watch.service 2>/dev/null || true
|
||||
systemctl restart cellular-proxy-watch.timer 2>/dev/null || true
|
||||
fi
|
||||
info "已启用 cellular-proxy-watch(网卡/源 IP 变化自动 rebind)"
|
||||
fi
|
||||
|
||||
info "6/6 自动验证出口(短超时,失败不阻塞安装结束)"
|
||||
if [[ "$SKIP_START" != "true" && "$AUTO_VERIFY" == "true" ]]; then
|
||||
set +e
|
||||
|
||||
@@ -31,8 +31,11 @@ load_config() {
|
||||
LOG_DIR="${LOG_DIR:-/var/log/cellular-proxy}"
|
||||
CELLULAR_IFACE="${CELLULAR_IFACE:-}"
|
||||
CELLULAR_SOURCE_IP="${CELLULAR_SOURCE_IP:-}"
|
||||
BIND_SOURCE_IP="${BIND_SOURCE_IP:-false}"
|
||||
REQUIRE_CELLULAR_IFACE="${REQUIRE_CELLULAR_IFACE:-true}"
|
||||
CELLULAR_IFACE_PATTERNS="${CELLULAR_IFACE_PATTERNS:-wwan,wwp,usb,enx,ppp,cdc,rmnet,ccmni,mbim,qmi}"
|
||||
ENABLE_CELLULAR_WATCH="${ENABLE_CELLULAR_WATCH:-true}"
|
||||
WATCH_INTERVAL_SEC="${WATCH_INTERVAL_SEC:-60}"
|
||||
PROXY_LISTEN_HOST="${PROXY_LISTEN_HOST:-0.0.0.0}"
|
||||
PROXY_MIXED_PORT="${PROXY_MIXED_PORT:-7890}"
|
||||
PROXY_USER="${PROXY_USER:-}"
|
||||
|
||||
@@ -273,6 +273,23 @@ case "$cmd" in
|
||||
auth)
|
||||
exec "$BASE/scripts/apply-proxy-auth.sh" "$@"
|
||||
;;
|
||||
watch)
|
||||
case "${1:-status}" in
|
||||
once) exec "$BASE/scripts/watch-cellular.sh" --once ;;
|
||||
status)
|
||||
systemctl status cellular-proxy-watch.service --no-pager 2>/dev/null || true
|
||||
systemctl status cellular-proxy-watch.timer --no-pager 2>/dev/null || true
|
||||
;;
|
||||
start|enable)
|
||||
systemctl enable --now cellular-proxy-watch.service cellular-proxy-watch.timer 2>/dev/null || true
|
||||
;;
|
||||
stop|disable)
|
||||
systemctl disable --now cellular-proxy-watch.service cellular-proxy-watch.timer 2>/dev/null || true
|
||||
;;
|
||||
logs) journalctl -u cellular-proxy-watch -n "${2:-80}" --no-pager ;;
|
||||
*) exec "$BASE/scripts/watch-cellular.sh" "$@" ;;
|
||||
esac
|
||||
;;
|
||||
logs) journalctl -u cellular-proxy -n "${1:-80}" -f ;;
|
||||
help|*)
|
||||
cat <<H
|
||||
@@ -281,6 +298,7 @@ cpxy — cellular-proxy
|
||||
cpxy detect | rebind | generate | start | stop | restart | status
|
||||
cpxy verify | logs [N]
|
||||
cpxy auth --user U --pass P | --clear | --show
|
||||
cpxy watch [status|once|start|stop|logs] # WWAN 自动监控
|
||||
cpxy upgrade # 一条命令在线增量升级
|
||||
cpxy upgrade --ui-only | --force-binary | --verify
|
||||
H
|
||||
@@ -305,6 +323,15 @@ if [[ -f "$ROOT_DIR/systemd/cellular-proxy-admin.service" ]]; then
|
||||
install -m 0644 "$ROOT_DIR/systemd/cellular-proxy-admin.service" /etc/systemd/system/cellular-proxy-admin.service
|
||||
sed -i "s|@INSTALL_DIR@|$INSTALL_DIR|g" /etc/systemd/system/cellular-proxy-admin.service
|
||||
fi
|
||||
# WWAN 自动监控
|
||||
for u in cellular-proxy-watch.service cellular-proxy-watch-once.service cellular-proxy-watch.timer; do
|
||||
if [[ -f "$ROOT_DIR/systemd/$u" ]]; then
|
||||
install -m 0644 "$ROOT_DIR/systemd/$u" "/etc/systemd/system/$u"
|
||||
sed -i "s|@INSTALL_DIR@|$INSTALL_DIR|g" "/etc/systemd/system/$u"
|
||||
sed -i "s|@LOG_DIR@|${LOG_DIR}|g" "/etc/systemd/system/$u"
|
||||
fi
|
||||
done
|
||||
chmod +x "$INSTALL_DIR/scripts/watch-cellular.sh" 2>/dev/null || true
|
||||
systemctl daemon-reload
|
||||
|
||||
export SYSTEMD_PAGER=cat
|
||||
@@ -335,6 +362,20 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
watch_flag="$(echo "${ENABLE_CELLULAR_WATCH:-true}" | tr '[:upper:]' '[:lower:]')"
|
||||
if [[ "$watch_flag" == "false" || "$watch_flag" == "0" || "$watch_flag" == "no" ]]; then
|
||||
systemctl disable --now cellular-proxy-watch.service cellular-proxy-watch.timer 2>/dev/null || true
|
||||
info "ENABLE_CELLULAR_WATCH=false,未启用自动监控"
|
||||
else
|
||||
systemctl enable cellular-proxy-watch.service 2>/dev/null || true
|
||||
systemctl enable cellular-proxy-watch.timer 2>/dev/null || true
|
||||
if [[ "$SKIP_START" != "true" ]]; then
|
||||
systemctl restart cellular-proxy-watch.service 2>/dev/null || true
|
||||
systemctl restart cellular-proxy-watch.timer 2>/dev/null || true
|
||||
fi
|
||||
info "已启用 cellular-proxy-watch(网卡/源 IP 变化自动 rebind)"
|
||||
fi
|
||||
|
||||
if [[ "$DO_VERIFY" == "true" && "$SKIP_START" != "true" ]]; then
|
||||
info "出口验证…"
|
||||
set +e
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
#!/usr/bin/env bash
|
||||
# 监控数据网卡:IP/链路变化时自动 regenerate(避免 stale bind 或网卡改名导致 rep=0x01)
|
||||
# 策略:
|
||||
# 1) 默认配置只 bind_interface(不写死源 IP)—— 多数场景无需本脚本也能自愈
|
||||
# 2) 本脚本仍负责:iface 丢失→重探、可选 BIND_SOURCE_IP 时同步源 IP、链路 down/up 后拉起
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
source "$ROOT_DIR/scripts/lib.sh"
|
||||
|
||||
CONFIG_FILE="${CONFIG_FILE:-$ROOT_DIR/etc/settings.conf}"
|
||||
if [[ ! -f "$CONFIG_FILE" && -f /opt/cellular-proxy/etc/settings.conf ]]; then
|
||||
CONFIG_FILE=/opt/cellular-proxy/etc/settings.conf
|
||||
ROOT_DIR=/opt/cellular-proxy
|
||||
fi
|
||||
export CONFIG_FILE
|
||||
|
||||
load_config
|
||||
|
||||
INTERVAL="${WATCH_INTERVAL_SEC:-60}"
|
||||
DEBOUNCE_SEC="${WATCH_DEBOUNCE_SEC:-3}"
|
||||
LOCK_DIR="${LOG_DIR:-/var/log/cellular-proxy}"
|
||||
mkdir -p "$LOCK_DIR" 2>/dev/null || true
|
||||
STATE_FILE="${LOCK_DIR}/watch.state"
|
||||
LOG_TAG="cellular-proxy-watch"
|
||||
|
||||
logw() { printf '[%s] %s %s\n' "$(date '+%F %T')" "$LOG_TAG" "$*" >&2; }
|
||||
|
||||
current_cfg_iface() {
|
||||
python3 - <<'PY' 2>/dev/null || true
|
||||
import json
|
||||
from pathlib import Path
|
||||
for p in ("/opt/cellular-proxy/etc/config.json",):
|
||||
try:
|
||||
c=json.loads(Path(p).read_text())
|
||||
for o in c.get("outbounds") or []:
|
||||
if o.get("tag")=="cellular":
|
||||
print(o.get("bind_interface") or "")
|
||||
raise SystemExit
|
||||
except Exception:
|
||||
pass
|
||||
print("")
|
||||
PY
|
||||
}
|
||||
|
||||
current_cfg_src() {
|
||||
python3 - <<'PY' 2>/dev/null || true
|
||||
import json
|
||||
from pathlib import Path
|
||||
try:
|
||||
c=json.loads(Path("/opt/cellular-proxy/etc/config.json").read_text())
|
||||
for o in c.get("outbounds") or []:
|
||||
if o.get("tag")=="cellular":
|
||||
print(o.get("inet4_bind_address") or "")
|
||||
raise SystemExit
|
||||
except Exception:
|
||||
pass
|
||||
print("")
|
||||
PY
|
||||
}
|
||||
|
||||
need_refresh() {
|
||||
load_config
|
||||
local cell="${CELLULAR_IFACE:-}"
|
||||
local live_src="" cfg_iface cfg_src
|
||||
cfg_iface="$(current_cfg_iface)"
|
||||
cfg_src="$(current_cfg_src)"
|
||||
|
||||
if [[ -z "$cell" ]] || ! iface_exists "$cell"; then
|
||||
logw "iface missing or empty (CELLULAR_IFACE=${cell:-<空>}) → rebind"
|
||||
return 0
|
||||
fi
|
||||
|
||||
live_src="$(detect_source_ip "$cell" || true)"
|
||||
if [[ -z "$live_src" ]]; then
|
||||
# 网卡在但无 IPv4:可能刚重拨;不立刻 die,等下一轮
|
||||
logw "wwan up but no IPv4 yet on $cell"
|
||||
# 若配置里仍绑旧源 IP,清掉/重生更安全
|
||||
if [[ -n "$cfg_src" ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -n "$cfg_iface" && "$cfg_iface" != "$cell" ]]; then
|
||||
logw "cfg iface $cfg_iface != settings $cell"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 仅当配置写了 inet4_bind_address 且与 live 不一致时才需要
|
||||
if [[ -n "$cfg_src" && "$cfg_src" != "$live_src" ]]; then
|
||||
logw "stale source IP cfg=$cfg_src live=$live_src"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# settings 里的 SOURCE_IP 过期也同步(即使 config 未 pin)
|
||||
if [[ -n "${CELLULAR_SOURCE_IP:-}" && "${CELLULAR_SOURCE_IP}" != "$live_src" ]]; then
|
||||
persist_cellular_to_settings "$CONFIG_FILE" "$cell" "$live_src" || true
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
do_refresh() {
|
||||
load_config
|
||||
local cell="${CELLULAR_IFACE:-}"
|
||||
local src=""
|
||||
|
||||
if [[ -z "$cell" ]] || ! iface_exists "$cell"; then
|
||||
cell="$(detect_cellular_iface "" || true)"
|
||||
if [[ -z "$cell" ]]; then
|
||||
logw "detect failed; skip"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
src="$(detect_source_ip "$cell" || true)"
|
||||
if [[ -z "$src" ]]; then
|
||||
logw "no IPv4 on $cell yet; skip generate"
|
||||
return 1
|
||||
fi
|
||||
persist_cellular_to_settings "$CONFIG_FILE" "$cell" "$src" || true
|
||||
logw "refresh: iface=$cell src=$src"
|
||||
# 走 cpxy generate(含 check + restart);失败则直接 scripts
|
||||
if command -v cpxy >/dev/null 2>&1; then
|
||||
cpxy generate >/dev/null 2>&1 || {
|
||||
# shellcheck source=/dev/null
|
||||
source "$ROOT_DIR/scripts/lib.sh"
|
||||
load_config
|
||||
"$ROOT_DIR/scripts/generate.sh" "$ROOT_DIR/generated"
|
||||
install -m 0644 "$ROOT_DIR/generated/config.json" "$ROOT_DIR/etc/config.json"
|
||||
install -m 0644 "$ROOT_DIR/generated/runtime.env" "$ROOT_DIR/etc/runtime.env" 2>/dev/null || true
|
||||
systemctl restart cellular-proxy 2>/dev/null || true
|
||||
}
|
||||
else
|
||||
"$ROOT_DIR/scripts/generate.sh" "$ROOT_DIR/generated"
|
||||
install -m 0644 "$ROOT_DIR/generated/config.json" "$ROOT_DIR/etc/config.json"
|
||||
systemctl restart cellular-proxy 2>/dev/null || true
|
||||
fi
|
||||
printf '%s iface=%s src=%s\n' "$(date -Iseconds)" "$cell" "$src" >"$STATE_FILE" 2>/dev/null || true
|
||||
logw "refresh done"
|
||||
}
|
||||
|
||||
# oneshot mode (systemd timer)
|
||||
if [[ "${1:-}" == "--once" ]]; then
|
||||
if need_refresh; then
|
||||
do_refresh || true
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# long-running: periodic + optional ip monitor events
|
||||
logw "start interval=${INTERVAL}s (debounce=${DEBOUNCE_SEC}s)"
|
||||
last_run=0
|
||||
trigger() {
|
||||
local now
|
||||
now="$(date +%s)"
|
||||
if (( now - last_run < DEBOUNCE_SEC )); then
|
||||
return 0
|
||||
fi
|
||||
last_run=$now
|
||||
if need_refresh; then
|
||||
do_refresh || true
|
||||
fi
|
||||
}
|
||||
|
||||
# initial
|
||||
trigger
|
||||
|
||||
# Prefer event-driven if available; always keep poll as safety net
|
||||
if command -v ip >/dev/null 2>&1; then
|
||||
(
|
||||
# address/link changes on any iface — cheap filter in shell
|
||||
ip -o monitor address link 2>/dev/null | while read -r line; do
|
||||
cell="${CELLULAR_IFACE:-}"
|
||||
# reload cell from settings occasionally
|
||||
if [[ -f "$CONFIG_FILE" ]]; then
|
||||
cell="$(awk -F= '/^CELLULAR_IFACE=/{print $2; exit}' "$CONFIG_FILE" 2>/dev/null || true)"
|
||||
fi
|
||||
if [[ -n "$cell" ]] && [[ "$line" == *"$cell"* || "$line" == *Deleted* || "$line" == *wwan* || "$line" == *usb* || "$line" == *enx* ]]; then
|
||||
sleep "$DEBOUNCE_SEC"
|
||||
trigger
|
||||
fi
|
||||
done
|
||||
) &
|
||||
mon_pid=$!
|
||||
trap 'kill $mon_pid 2>/dev/null || true; exit 0' TERM INT
|
||||
fi
|
||||
|
||||
while true; do
|
||||
sleep "$INTERVAL"
|
||||
trigger
|
||||
done
|
||||
@@ -0,0 +1,18 @@
|
||||
[Unit]
|
||||
Description=cellular-proxy WWAN watch once (timer)
|
||||
After=network-online.target
|
||||
ConditionPathExists=@INSTALL_DIR@/etc/settings.conf
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
EnvironmentFile=-@INSTALL_DIR@/etc/runtime.env
|
||||
EnvironmentFile=-@INSTALL_DIR@/etc/settings.conf
|
||||
ExecStart=@INSTALL_DIR@/scripts/watch-cellular.sh --once
|
||||
Nice=10
|
||||
MemoryMax=16M
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=full
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
StandardOutput=append:@LOG_DIR@/watch.log
|
||||
StandardError=append:@LOG_DIR@/watch.log
|
||||
@@ -0,0 +1,25 @@
|
||||
[Unit]
|
||||
Description=cellular-proxy WWAN watch (auto rebind on IP/link change)
|
||||
After=network-online.target cellular-proxy.service
|
||||
Wants=network-online.target
|
||||
PartOf=cellular-proxy.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
EnvironmentFile=-@INSTALL_DIR@/etc/runtime.env
|
||||
EnvironmentFile=-@INSTALL_DIR@/etc/settings.conf
|
||||
# 低内存:watch 脚本本身极轻;间隔可在 settings WATCH_INTERVAL_SEC 覆盖
|
||||
ExecStart=@INSTALL_DIR@/scripts/watch-cellular.sh
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
MemoryMax=16M
|
||||
Nice=10
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=full
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
StandardOutput=append:@LOG_DIR@/watch.log
|
||||
StandardError=append:@LOG_DIR@/watch.log
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=cellular-proxy WWAN watch timer (fallback oneshot)
|
||||
Requires=cellular-proxy-watch-once.service
|
||||
|
||||
[Timer]
|
||||
# 兜底:即使 long-running watch 挂了,也每 2 分钟 oneshot 检查
|
||||
OnBootSec=1min
|
||||
OnUnitActiveSec=2min
|
||||
AccuracySec=30s
|
||||
Persistent=true
|
||||
Unit=cellular-proxy-watch-once.service
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
Reference in New Issue
Block a user