4G 模组开机自连 systemd 服务方案
适用:带 USB 4G 模组(EC800K-CN 等)的 Linux 板(Easy EAI / 灵眸等),把"上电后手动 ifconfig → 发 AT → dhclient"改成 systemd 开机自动跑。
1. 适用场景
- 背景:板子每次冷启动后,4G 网络都不会自动连。需要登录 → 手动拉起 USB 网卡 → 手动发 AT 指令让模组拨号 → 手动 dhclient 拿 IP。
- 目标:插上电,等上 15~30 秒,网卡自动 UP、IP 自动拿到、能直接 ping 通外网。后续 ssh / 业务程序起来就能用。
- 适用型号 :
- 模组侧:Quectel EC800K-CN USB 模组--- 使用用
AT+qnetdevctl=1,1,1触发 PDP 拨号。 - 板子侧:任何使用 systemd 启动的 Linux 板(EasyEAI、灵眸、Rockchip 系等)。
- 模组侧:Quectel EC800K-CN USB 模组--- 使用用
2. 前置准备
2.1 硬件
- 4G 模组(Quectel 系)已插上板子的 USB 口,SIM 卡已装好
- 天线已接,信号格不为 0
- 板子已经烧好系统,开机能进 shell
- 手动拉起 USB 网卡,让模组拨号,dhclient 拿 IP后可以上网
2.2 设备识别
插好模组,板子启动后跑:
# 看 USB 串口(模组一般会出来 ttyUSB0~ttyUSB2)
ls -l /dev/ttyUSB* 2>/dev/null
# 期望至少看到 ttyUSB0 / ttyUSB1 / ttyUSB2(分别对应 DM / AT / PPP)
# 看到 ttyUSB1 才能正常发 AT 指令
# 看 USB 网卡(systemd 命名后是 enxMAC 形式)
ip -br link | grep -E 'enx|eth|usb'
# 期望看到 enxae0c29a39b6d 这种(enx 前缀 + MAC 后 6 字节)
# 如果用 lsusb 也能确认模组是否枚举成功:
lsusb | grep -i quectel
# 期望看到 Quectel Wireless Solutions Co., Ltd. ... 一行
💡 记录你的实际值 :
ttyUSB1和enxae0c29a39b6d是后续脚本默认值。如果你板子上不是这两个(比如接口名是wwan0或 tty 是ttyUSB2),需要替换脚本里的IFACE/MODEM,或者在调用时用环境变量覆盖。
2.3 权限
整个流程都需要 CAP_NET_ADMIN(拉网卡)+ 写 /var/log 权限,所以必须以 root 跑:
# 切换 root
sudo su
# 或当前用户有 sudo 权限,跑命令前加 sudo
# 推荐直接 su 到 root,后面少敲
2.5 验证手动流程能跑通
这条最重要 :先把"手动的三步"自己跑一遍确认能联网,再上 systemd。免得 service 跑不起来时不知道是脚本问题还是网络问题。
sudo ifconfig enxae0c29a39b6d up
sudo stty -F /dev/ttyUSB1 -opost
sudo printf "AT+qnetdevctl=1,1,1\r\n" > /dev/ttyUSB1
sleep 10
sudo dhclient enxae0c29a39b6d
ip -4 addr show enxae0c29a39b6d # 期望看到 inet 10.x.x.x 这种地址
ping -c 2 8.8.8.8 # 期望有回应
全过才能进下一步。哪个不过就先解决哪个。
3. 工作流程
开机
└─ systemd 启动 /etc/systemd/system/auto-4g-connect.service
└─ 跑 /usr/local/bin/auto-4g-connect.sh
├─ 1. 等 enxae0c29a39b6d 网卡出现(≤30s)
├─ 2. ifconfig enxae0c29a39b6d up
├─ 3. 等 /dev/ttyUSB1 出现(≤60s)
├─ 4. stty 关 tty 输出处理,避免 \n 双重转 \r\n
├─ 5. 发 AT+qnetdevctl=1,1,1 触发 PDP 拨号
├─ 6. sleep 10s 等模组注册 + PDP 激活
└─ 7. dhclient enxae0c29a39b6d(失败重试 5 次)
典型 15~30 秒 完成,最坏情况约 2.5 分钟(设备枚举慢 + 信号差 + DHCP 多次 retry)。
4. 关键设计
| 设计 | 为什么这样 |
|---|---|
| IFACE / MODEM / AUTO_4G_LOG 环境变量覆盖 | 换板/换模组不动脚本;调试时改日志路径省得跟权限打架 |
| 自动 exec sudo 兜底 | atom 用户手敲也能跑;systemd 跑时直接是 root,不进 sudo 分支 |
| After=local-fs.target,不在 network.target 之后 | 自己先拉起网卡,避免和 NetworkManager 抢同一个网口 |
| Type=oneshot + RemainAfterExit=yes | 跑完视为成功;systemd status 显示 active (exited) |
| Restart=on-failure + 15s 重试 + 5 次封顶 | 开机早期 /dev/ttyUSB1 还没就绪时自动 retry;信号差时 DHCP 失败也能重试 |
| stty -F /dev/ttyUSB1 -opost | 关 tty 的 ONLCR 输出处理,避免 \n → \r\n 跟我们的 \r\n 打架 |
| DHCP 重试 5 次 | 模组拨号成功但 IP 还没下发到内核网卡时,给点容忍度 |
5. 文件清单
| 文件 | 作用 | 权限 | 谁读谁写 |
|---|---|---|---|
| /usr/local/bin/auto-4g-connect.sh | 主逻辑脚本 | 755 (rwxr-xr-x) | root |
| /etc/systemd/system/auto-4g-connect.service | systemd 单元 | 644 (rw-r--r--) | root |
| /var/log/auto-4g-connect.log | 脚本自己的日志 | 644 | root 写 |
| /etc/systemd/system/multi-user.target.wants/auto-4g-connect.service | enable 后自动生成的软链 | --- | systemd 读 |
📁 为什么脚本放
/usr/local/bin/、服务放/etc/systemd/system/?
/usr/local/是给"系统包管理器不管的、本地装的"软件用的。第三方脚本放这里最规范。/etc/systemd/system/优先级高于/lib/systemd/system/,放这里相当于"覆盖发行版默认",不会升级时被冲掉。
6. 完整脚本与配置
6.1 /usr/local/bin/auto-4g-connect.sh
#!/bin/bash
# auto-4g-connect.sh --- 上电自启:拉网卡 -> 触发模组拨号 -> DHCP 拿 IP
# 用法: sudo /usr/local/bin/auto-4g-connect.sh
# 可覆盖环境变量: IFACE / MODEM / AUTO_4G_LOG
set -u # 抓未定义变量,不要乱吞错
# ========== 配置区 ==========
IFACE="${IFACE:-enxae0c29a39b6d}" # 模组的 USB 网卡(systemd 命名,enx + MAC)
MODEM="${MODEM:-/dev/ttyUSB1}" # 模组 AT 指令口(Quectel 模组通常是 ttyUSB1)
LOG="${AUTO_4G_LOG:-/var/log/auto-4g-connect.log}"
# ========== 工具函数 ==========
log() { echo "$(date '+%F %T') $*" | tee -a "$LOG"; }
# ========== 权限兜底 ==========
# 必须 root:拉网卡要 CAP_NET_ADMIN,写 /var/log 要 root
if [ "$(id -u)" -ne 0 ]; then
echo "$(date '+%F %T') 非 root,转 sudo: $0 $*" >> "$LOG" 2>/dev/null
echo "[!] 需要 root,请用 sudo $0 $*" >&2
exec sudo "$0" "$@" # exec 替换当前进程,sudo 续上
fi
log "===== auto-connect start (pid=$$ iface=$IFACE modem=$MODEM) ====="
# ========== 1) 等网卡出现 ==========
# USB 设备枚举可能比 systemd 启动晚几秒
for i in $(seq 1 30); do
if ip link show dev "$IFACE" >/dev/null 2>&1; then
log "iface $IFACE 出现 (${i}s)"; break
fi
sleep 1
done
ip link show dev "$IFACE" >/dev/null 2>&1 || { log "ERROR: $IFACE not found"; exit 1; }
# ========== 2) 拉起网卡 ==========
# 优先 ifconfig(老习惯);没有就 fallback 到 ip 命令
if command -v ifconfig >/dev/null 2>&1; then
ifconfig "$IFACE" up 2>>"$LOG"
else
ip link set "$IFACE" up
fi
log "iface $IFACE up"
# ========== 3) 等模组 tty 出现 ==========
# ttyUSB* 出现可能比网卡再晚一点
for i in $(seq 1 60); do
[ -e "$MODEM" ] && { log "modem $MODEM 出现 (${i}s)"; break; }
sleep 1
done
[ -e "$MODEM" ] || { log "ERROR: $MODEM not present"; exit 1; }
# ========== 4) 关 tty 输出处理 ==========
# 默认 tty 会把输出里的 \n 转成 \r\n,跟我们的 \r\n 撞车会变成 \r\r\n
stty -F "$MODEM" -opost 2>/dev/null
sleep 2 # 给模组一秒钟稳一下
# ========== 5) 触发 PDP 拨号 ==========
# AT+qnetdevctl=1,1,1 是 Quectel 私有指令:1=start,1=cid,1=rat(自动选 4G/3G/2G)
printf "AT+qnetdevctl=1,1,1\r\n" > "$MODEM"
log "AT+qnetdevctl sent"
# ========== 6) 等模组注册 + PDP 激活 ==========
# 看 SIM/信号/网络,2~15s 都正常,冷启动可能更久
sleep 10
# ========== 7) DHCP,带重试 ==========
# dhclient / dhcpcd 二选一,都没有就报错退出
for i in 1 2 3 4 5; do
if command -v dhclient >/dev/null 2>&1; then
if dhclient "$IFACE" 2>>"$LOG"; then
log "dhclient OK (try $i)"; break
fi
elif command -v dhcpcd >/dev/null 2>&1; then
if dhcpcd "$IFACE" 2>>"$LOG"; then
log "dhcpcd OK (try $i)"; break
fi
else
log "ERROR: 没装 dhclient 也没装 dhcpcd,装一个: sudo apt install isc-dhcp-client"; exit 1
fi
log "DHCP try $i failed, sleep 5s & retry"
sleep 5
done
log "===== auto-connect end ====="
exit 0
6.2 /etc/systemd/system/auto-4g-connect.service
[Unit]
Description=Auto bring up 4G modem (Quectel AT+qnetdevctl) and DHCP
# local-fs 是几乎所有服务都依赖的"文件系统就绪"标记,最早期的同步点
# 这里不在 network.target 之后是因为我们要自己拉网卡,不能等别人先动
After=local-fs.target
Wants=local-fs.target
[Service]
Type=oneshot # 跑一次就退,不是常驻进程
RemainAfterExit=yes # 脚本退出后仍视为 active,这样 systemctl status 看着干净
ExecStart=/usr/local/bin/auto-4g-connect.sh
Restart=on-failure # 失败才重启(避免成功跑完被反复重启)
RestartSec=15 # 失败后 15 秒再试
StartLimitBurst=5 # 5 分钟内最多 5 次
StartLimitIntervalSec=300
[Install]
WantedBy=multi-user.target # 多用户模式启动时拉起(就是普通启动的常规 target)
7. 安装
7.1 一键安装(推荐)
SSH 上板后,整块复制粘贴到终端直接回车:
sudo bash <<'SETUP_EOF'
set -e
# 1) 写主脚本
cat > /usr/local/bin/auto-4g-connect.sh <<'AUTO_4G_SH'
#!/bin/bash
set -u
IFACE="${IFACE:-enxae0c29a39b6d}"
MODEM="${MODEM:-/dev/ttyUSB1}"
LOG="${AUTO_4G_LOG:-/var/log/auto-4g-connect.log}"
log() { echo "$(date '+%F %T') $*" | tee -a "$LOG"; }
if [ "$(id -u)" -ne 0 ]; then
echo "$(date '+%F %T') 非 root,转 sudo: $0 $*" >> "$LOG" 2>/dev/null
echo "[!] 需要 root,请用 sudo $0 $*" >&2
exec sudo "$0" "$@"
fi
log "===== auto-connect start (pid=$$ iface=$IFACE modem=$MODEM) ====="
for i in $(seq 1 30); do
ip link show dev "$IFACE" >/dev/null 2>&1 && { log "iface $IFACE 出现 (${i}s)"; break; }
sleep 1
done
ip link show dev "$IFACE" >/dev/null 2>&1 || { log "ERROR: $IFACE not found"; exit 1; }
if command -v ifconfig >/dev/null 2>&1; then
ifconfig "$IFACE" up 2>>"$LOG"
else
ip link set "$IFACE" up
fi
log "iface $IFACE up"
for i in $(seq 1 60); do
[ -e "$MODEM" ] && { log "modem $MODEM 出现 (${i}s)"; break; }
sleep 1
done
[ -e "$MODEM" ] || { log "ERROR: $MODEM not present"; exit 1; }
stty -F "$MODEM" -opost 2>/dev/null
sleep 2
printf "AT+qnetdevctl=1,1,1\r\n" > "$MODEM"
log "AT+qnetdevctl sent"
sleep 10
for i in 1 2 3 4 5; do
if command -v dhclient >/dev/null 2>&1; then
if dhclient "$IFACE" 2>>"$LOG"; then log "dhclient OK (try $i)"; break; fi
elif command -v dhcpcd >/dev/null 2>&1; then
if dhcpcd "$IFACE" 2>>"$LOG"; then log "dhcpcd OK (try $i)"; break; fi
fi
log "DHCP try $i failed, sleep 5s & retry"
sleep 5
done
log "===== auto-connect end ====="
exit 0
AUTO_4G_SH
# 2) 写 systemd 单元
cat > /etc/systemd/system/auto-4g-connect.service <<'AUTO_4G_UNIT'
[Unit]
Description=Auto bring up 4G modem (Quectel AT+qnetdevctl) and DHCP
After=local-fs.target
Wants=local-fs.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/auto-4g-connect.sh
Restart=on-failure
RestartSec=15
StartLimitBurst=5
StartLimitIntervalSec=300
[Install]
WantedBy=multi-user.target
AUTO_4G_UNIT
# 3) 上权限 + 注册服务 + 启动
chmod +x /usr/local/bin/auto-4g-connect.sh
systemctl daemon-reload
systemctl enable --now auto-4g-connect.service
# 4) 报告
echo ""
echo "================ 安装完成 ================"
ls -l /usr/local/bin/auto-4g-connect.sh /etc/systemd/system/auto-4g-connect.service
echo "-------------------------------------------"
systemctl status auto-4g-connect.service --no-pager -l || true
echo "-------------------------------------------"
echo "日志: journalctl -u auto-4g-connect.service -f"
echo " tail -f /var/log/auto-4g-connect.log"
echo "卸载: sudo systemctl disable --now auto-4g-connect.service && sudo rm /usr/local/bin/auto-4g-connect.sh /etc/systemd/system/auto-4g-connect.service"
echo "==========================================="
SETUP_EOF
7.2 分步安装
如果不想粘贴大块,可以先把两个文件用 vim 在板子上手敲(文件内容见 §6),然后跑:
# 1) 上权限
sudo chmod +x /usr/local/bin/auto-4g-connect.sh
# 2) systemd 重载新单元
sudo systemctl daemon-reload
# 3) 启用 + 立刻跑一次
sudo systemctl enable --now auto-4g-connect.service
# --now = enable + start 一起;以后只想启动用: sudo systemctl start auto-4g-connect.service
🔍
daemon-reload干啥:让 systemd 重新扫一遍/etc/systemd/system/看有没有新加的.service文件,不加这条 systemctl enable 会报unit not found。