Skip to content

EC20 搭建个人备用手机卡方案

备用手机卡这件事,平时存在感很低,但真到需要的时候又很麻烦。

比如一些账号还绑定在旧号码上,偶尔要收一条验证码;某张卡长期不用,又担心被运营商回收;或者出门在外时,家里的备用卡刚好收到一条重要短信。最原始的做法是把卡插在旧手机里长期充电,但这个方案并不稳定:手机会没电、系统会杀后台、远程查看短信也不方便。

这次我换了一个更偏 Homelab 的做法:把备用手机卡插到移远 EC20/EC25 这类 4G 模组里,接入家里的 Docker VM,通过 VoHive 管理短信和模组状态,再用青龙做定时保号提醒。

目标

这个方案要解决的问题很明确:

  1. 备用手机卡长期在线。
  2. 可以在浏览器里远程查看短信。
  3. 可以通过 API 发送短信。
  4. 有公网入口,但需要鉴权保护。
  5. 定期自动发一条保号短信,避免长期沉默。
  6. 整套服务要能被 Docker 管理和迁移。

最后形成的链路大概是这样:

text
SIM 卡
  -> EC20/EC25 USB 模组
  -> PVE 直通到 Docker VM
  -> VoHive 管理短信和模组
  -> Nginx Proxy Manager / Authelia 做入口保护
  -> QingLong 定时调用 VoHive API 发保号短信

硬件和系统结构

我这里的环境是家庭服务器上跑 PVE,里面有一台专门的 Docker VM。4G 模组通过 USB 接到 PVE,再直通给 Docker VM。

核心组件:

  • PVE:负责虚拟机和 USB 设备直通
  • Docker VM:运行实际服务
  • EC20/EC25 模组:承载手机卡、短信和移动网络能力
  • VoHive:提供短信收发、模组管理、Web UI 和 API
  • QingLong:负责定时任务
  • Nginx Proxy Manager + Authelia:负责公网入口和访问保护

模组识别正常时,在 Docker VM 里一般能看到类似设备:

bash
ls -lah /dev/ttyUSB* /dev/cdc-wdm*
mmcli -L
lsusb

常见表现是:

text
/dev/ttyUSB0
/dev/ttyUSB1
/dev/ttyUSB2
/dev/ttyUSB3
/dev/cdc-wdm0
QUECTEL Mobile Broadband Module

这里不强依赖必须是 EC20。EC20、EC25 这类移远模组的整体思路类似,区别更多在制式、驱动细节和具体设备枚举上。

部署 VoHive

VoHive 的定位比较适合这个场景:它不是单纯的短信转发脚本,而是一个面向 EC20 / 高通 410 设备的管理平台,覆盖短信、VoWiFi、eSIM、模组状态和 API。

我的部署方式是单独放一个 Docker 目录:

text
/home/<user>/vohive-docker

docker-compose.yml 可以保持得比较直接:

yaml
services:
  vohive:
    image: iniwex/vohive:latest
    container_name: vohive
    restart: unless-stopped
    network_mode: host
    privileged: true
    volumes:
      - ./config:/app/config
      - ./data:/app/data
      - ./logs:/app/logs
      - /dev:/dev
    environment:
      - TZ=Asia/Shanghai
      - CONFIG_PATH=/app/config/config.yaml

这里有几个关键点:

  • network_mode: host:减少模组管理和本机网络探测的干扰。
  • privileged: true:让容器能访问底层设备。
  • /dev:/dev:把宿主机的 USB 串口、QMI 设备暴露给容器。
  • ./config./data./logs:配置、数据、日志都落在宿主机目录,方便备份和迁移。

启动:

bash
cd /home/<user>/vohive-docker
docker compose up -d

启动后先看容器状态和日志:

bash
docker ps
docker logs -f vohive

如果日志里出现过类似 QMI: initial sync failed,不一定就是致命问题。我的实际情况里它出现过,但服务仍然正常启动,Web UI 和短信接口都能使用。遇到这类日志时,优先看页面能否打开、设备是否在线、短信接口是否可用,不要只凭一行初始化日志下结论。

镜像拉取的坑

这次比较折腾的一点是镜像拉取。

国内环境下,直接拉 iniwex/vohive:latest 不一定顺利。我遇到过几类情况:

  • Docker mirror 返回 403 Forbidden
  • registry-1.docker.io 直连超时
  • 某些代理源能解析 manifest,但下载 layer 时超时
  • 换另一个代理源又返回未知错误

最后更稳的做法是借助可用的 HTTP 代理,用 regctl 先导出镜像 tar,再 docker load

bash
mkdir -p /home/<user>/bin /home/<user>/tmp

HTTP_PROXY=http://<proxy-host>:<proxy-port> \
HTTPS_PROXY=http://<proxy-host>:<proxy-port> \
  curl -L -o /home/<user>/bin/regctl \
  https://github.com/regclient/regclient/releases/download/v0.11.5/regctl-linux-amd64

chmod +x /home/<user>/bin/regctl

HTTP_PROXY=http://<proxy-host>:<proxy-port> \
HTTPS_PROXY=http://<proxy-host>:<proxy-port> \
  /home/<user>/bin/regctl image export \
  docker.io/iniwex/vohive:latest \
  /home/<user>/tmp/vohive-latest.tar

docker load -i /home/<user>/tmp/vohive-latest.tar

这个方式的好处是可控。普通 docker pull 失败时,错误经常比较粗;用 regctl image export 至少能更明确地知道卡在 manifest、layer 下载还是网络代理上。

Web 入口和访问保护

VoHive 管的是短信和手机卡,不建议裸奔在公网。

我的做法是:

  • 内网直接访问 VoHive 的 Web UI。
  • 公网通过 Nginx Proxy Manager 反代。
  • 外层加 Authelia 认证。
  • VoHive 自己仍然保留登录密码。

这样即使公网入口暴露,也至少有两层门:

text
浏览器
  -> HTTPS 域名
  -> Nginx Proxy Manager
  -> Authelia
  -> VoHive 登录

如果只是内网使用,可以先不做公网入口。等短信收发、模组识别、API 调用都稳定后,再把公网链路接上。这样排查问题时会简单很多。

复刻基础能力:SimRelay

VoHive 能力比较完整,但它目前没有开放源代码。对个人备用卡这个场景来说,我并不一定需要完整的多设备、代理、eSIM、VoWiFi 能力,最核心的是这几件事:

  • 能通过 EC20 的 AT 串口读取设备状态。
  • 能读取收件箱、未读短信和已发送短信。
  • 能发送中文短信。
  • 能提供一个简单 Web 控制台。
  • 能把这些能力封装成 HTTP API,方便后续接入青龙或其他自动化。

所以我后来按自己的需求复刻了部分基础功能,单独做了一个开源项目:SimRelay

SimRelay 第一版优先支持单个移远 EC20 模组,通过 Linux USB 串口发送 AT 命令,默认使用 UCS2 文本短信模式来兼容中文短信。它不追求覆盖 VoHive 的完整能力,而是把“备用卡托管”最常用的短信收发、设备状态和 Web 控制台先做稳。

本地运行大概是这样:

bash
./simrelay \
  --device /dev/ttyUSB2 \
  --baud 115200 \
  --listen :7575 \
  --timeout 5s

Docker 部署时需要把 EC20 的 AT 串口映射进容器。如果还想补齐运营商、网络制式、频段、信号强度等信息,也可以把 QMI 设备映射进去:

yaml
services:
  simrelay:
    image: simrelay:latest
    container_name: simrelay
    restart: unless-stopped
    environment:
      TZ: Asia/Shanghai
      SIMRELAY_DEVICE: /dev/ttyUSB3
      SIMRELAY_BAUD: "115200"
      SIMRELAY_LISTEN: ":7575"
      SIMRELAY_TIMEOUT: 5s
      SIMRELAY_ADMIN_USERNAME: admin
      SIMRELAY_ADMIN_PASSWORD: ${SIMRELAY_ADMIN_PASSWORD:-change-me}
      SIMRELAY_QMI_DEVICE: /dev/cdc-wdm0
    devices:
      - /dev/ttyUSB3:/dev/ttyUSB3
      - /dev/cdc-wdm0:/dev/cdc-wdm0
    ports:
      - "7576:7575"

它和 VoHive 的关系更像是轻量替代方案,而不是完全复刻。VoHive 适合作为完整管理平台;SimRelay 更适合“我只想稳定托管一张备用卡,并能自己控制代码”的场景。

用 API 发短信

VoHive 的接口文档可以直接通过 Web UI 查看,核心是两步:

  1. 登录拿 token。
  2. 带上 Authorization: Bearer <token> 调用短信发送接口。

登录接口:

http
POST /api/auth/login
Content-Type: application/json

请求体:

json
{
  "username": "<username>",
  "password": "<password>"
}

返回里会有 token

发送短信接口:

http
POST /api/sms/send
Authorization: Bearer <token>
Content-Type: application/json

请求体最少只需要两个字段:

json
{
  "phone": "+10000000000",
  "message": "test message"
}

如果有多张卡或多个模组,也可以传 device_idimsi 来指定设备。

青龙做定时保号

备用卡最怕的是长期不用。很多运营商虽然规则不同,但保持周期性通信总归更稳。

我用青龙做了一个很简单的定时任务:每四个月发一条短信。比如每年 1 月 / 5 月 / 9 月1 号执行一次:

cron
1 1 1 1,5,9 *

任务命令:

bash
python3 /ql/data/scripts/vohive-send-keepalive-sms.py

脚本逻辑非常简单:

  1. 读取 VoHive 地址、账号、密码、目标手机号和短信内容。
  2. 调用 /api/auth/login 获取 token。
  3. 调用 /api/sms/send 发送短信。
  4. 打印发送结果,方便在青龙日志里确认。

为了避免把配置写死,脚本可以通过环境变量覆盖:

text
VOHIVE_BASE_URL
VOHIVE_USERNAME
VOHIVE_PASSWORD
VOHIVE_SMS_PHONE
VOHIVE_SMS_MESSAGE
VOHIVE_DEVICE_ID
VOHIVE_IMSI

实际维护时有一个小坑:青龙的“定时任务”和“脚本管理”不是一回事。

定时任务只保存命令和 cron;脚本管理展示的是 /ql/data/scripts 目录里的文件。如果只通过 API 创建了定时任务,但脚本文件没有同步到青龙容器的 /ql/data/scripts,那么任务列表里能看到任务,脚本管理里却看不到脚本。

所以正确顺序应该是:

text
先把脚本放进 /ql/data/scripts
再创建青龙定时任务
最后手动执行一次验证日志

验证命令:

bash
docker exec qinglong python3 /ql/data/scripts/vohive-send-keepalive-sms.py

如果返回里能看到类似 status=okdelivery_state=acked,说明 VoHive 已经接受发送请求。

备份和维护

这个方案里真正重要的不是容器,而是配置和数据目录。

建议至少备份这些路径:

text
vohive-docker/config
vohive-docker/data
vohive-docker/logs
qinglong-docker/data

日常维护可以关注几件事:

  • 模组重启后是否还能识别到 /dev/ttyUSB*/dev/cdc-wdm*
  • VoHive 页面里设备是否在线
  • 青龙定时任务是否仍然启用
  • 保号短信是否有执行日志
  • 公网入口是否仍然经过认证保护

如果更换 USB 口、调整 PVE 直通或重启 Docker VM,优先检查设备枚举是否变化。模组类设备最常见的问题不是应用挂了,而是底层设备没有正确交给容器。

总结

这套方案本质上是把一张“只能插在手机里的备用卡”,变成一个可管理的家庭基础设施:

  • EC20/EC25 负责移动网络和短信能力。
  • Docker 负责运行环境。
  • VoHive 或 SimRelay 负责 Web UI 和 API。
  • NPM + Authelia 负责入口保护。
  • QingLong 负责周期性自动化。

它不一定比旧手机方案更简单,但更可控、更容易备份,也更适合放进 Homelab 的统一运维体系里。对备用卡这种低频但关键的东西来说,稳定在线、能远程查看、能自动保号,比功能花哨更重要。

Last updated: