有些设备没有固定显示器,或者经常断电重启。机器重新启动之后,第一件事通常不是看日志,而是先确认它当前拿到了什么 IP,能不能连上。

这篇文章记录一套比较实用的方案:设备开机后自动等待网络初始化,获取 IPv4 地址,然后通过 Bark 推送到手机。这样机器一启动,手机上就能收到类似下面这样的通知:

主机 nvidia 已开机,IP: 192.168.2.89

本文方案基于 systemd + NetworkManager + Bark,适合 Ubuntu / Debian 一类使用 systemd 的 Linux 机器。

方案目标

目标很明确:

  • 开机后自动执行,不需要手工登录
  • 等网卡拿到 IP 之后再发送,不抢跑
  • 推送失败时自动重试
  • 尽量减少对开机流程的干扰
  • 日志可追踪,方便排障

Bark 简介

Bark 是一个给 iPhone 推送通知的服务。只要拿到自己的 Bark 推送地址,就可以通过 HTTP 请求直接发消息。

例如:

https://bark.example.com/xxxxxxxxxxxxx/推送标题/推送内容

其中:

  • xxxxxxxxxxxxx 是设备 key
  • 后面的标题和内容需要做 URL 编码

脚本思路

脚本的逻辑分成三步:

  1. 先等网络在线
  2. 再轮询指定网卡的 IPv4 地址
  3. 拿到 IP 后调用 Bark 接口推送

为了减少依赖,发送部分不依赖 curl,而是直接使用系统自带的 python3 标准库完成 HTTP 请求。

开机推送脚本

脚本路径:

/home/nvidia/.local/bin/boot-ip-bark.sh

脚本内容如下:

#!/usr/bin/env bash
set -euo pipefail

export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

BARK_BASE="https://bark.example.com/xxxxxxxxxxxxxxxxxxxxxx"
TITLE="开机IP通知"
INTERFACE="${INTERFACE:-enP2p1s0}"
NM_WAIT_TIMEOUT="${NM_WAIT_TIMEOUT:-180}"
MAX_WAIT="${MAX_WAIT:-300}"
SLEEP_SEC="${SLEEP_SEC:-3}"
BARK_RETRIES="${BARK_RETRIES:-3}"
BARK_RETRY_SLEEP="${BARK_RETRY_SLEEP:-5}"

wait_for_network() {
  if command -v nm-online >/dev/null 2>&1; then
    nm-online -q -t "$NM_WAIT_TIMEOUT" || true
  fi
}

get_ip() {
  /usr/sbin/ip -4 -o addr show dev "$INTERFACE" scope global 2>/dev/null | awk '{split($4,a,"/"); print a[1]; exit}'
}

wait_for_network

ip_addr=""
for _ in $(seq 1 "$MAX_WAIT"); do
  ip_addr="$(get_ip || true)"
  if [ -n "$ip_addr" ]; then
    break
  fi
  sleep "$SLEEP_SEC"
done

if [ -z "$ip_addr" ]; then
  ip_addr="$(hostname -I 2>/dev/null | awk '{print $1}')"
fi

if [ -z "$ip_addr" ]; then
  logger -t boot-ip-bark "no IPv4 address found"
  exit 1
fi

host="$(hostname 2>/dev/null || echo unknown)"
content="主机 ${host} 已开机,IP: ${ip_addr}"

for attempt in $(seq 1 "$BARK_RETRIES"); do
  if /usr/bin/python3 - "$BARK_BASE" "$TITLE" "$content" <<'PY'
import sys
from urllib.parse import quote
from urllib.request import Request, urlopen

base, title, content = sys.argv[1:4]
url = f"{base}/{quote(title, safe='')}/{quote(content, safe='')}"
req = Request(url, headers={"User-Agent": "boot-ip-bark/1.0"})
with urlopen(req, timeout=10) as resp:
    resp.read()
PY
  then
    logger -t boot-ip-bark "sent bark notification for ${host} ${ip_addr}"
    exit 0
  fi
  sleep "$BARK_RETRY_SLEEP"
done

logger -t boot-ip-bark "failed to send bark notification for ${host} ${ip_addr}"
exit 1

systemd 配置

相比 crontab @rebootsystemd 更适合做这种开机任务:

  • 启动顺序可控
  • 日志统一进 journalctl
  • 失败后可自动重试
  • 方便查看状态

服务文件路径:

/etc/systemd/system/boot-ip-bark.service

内容如下:

[Unit]
Description=Send boot IP notification to Bark
Wants=NetworkManager-wait-online.service
After=NetworkManager-wait-online.service NetworkManager.service network-online.target

[Service]
Type=oneshot
User=nvidia
Group=nvidia
Environment=INTERFACE=enP2p1s0
Environment=NM_WAIT_TIMEOUT=180
Environment=MAX_WAIT=300
Environment=SLEEP_SEC=3
Environment=BARK_RETRIES=3
Environment=BARK_RETRY_SLEEP=5
ExecStart=/home/nvidia/.local/bin/boot-ip-bark.sh
Restart=on-failure
RestartSec=20s
TimeoutStartSec=25min
Nice=10
IOSchedulingClass=idle

[Install]
WantedBy=multi-user.target

启用命令:

sudo systemctl daemon-reload
sudo systemctl enable NetworkManager-wait-online.service
sudo systemctl enable boot-ip-bark.service
sudo systemctl start boot-ip-bark.service

验证方式

可以用下面几个命令检查是否正常:

查看服务状态:

systemctl status boot-ip-bark.service

查看本次开机日志:

journalctl -b -u boot-ip-bark.service

查看脚本自己的日志:

journalctl -b -t boot-ip-bark

如果推送成功,通常能看到类似日志:

sent bark notification for agile-nvidia 10.8.26.89

关键细节和踩坑

这套配置里,最容易踩的坑主要有几个。

1. 不要只靠 @reboot

cron@reboot 触发时机通常太早,而且环境变量很精简。脚本里经常会出现命令找不到、网络还没起来、IP 为空等问题。

2. curl 不一定存在

很多最小化系统没有安装 curl。如果脚本依赖 curl,开机时就可能直接失败。用 python3 标准库可以减少一个不必要依赖。

3. network-online.target 不等于一定拿到业务 IP

很多人会误以为只要依赖了 network-online.target 就可以直接推送。实际上不一定。最稳妥的做法仍然是脚本里自己再确认一遍指定网卡的 IPv4。

4. 这类服务可能拉长启动时间

如果机器没插网线、DHCP 很慢,等待网络的服务可能会拖长 boot 时间。它通常不会让系统完全无法进入,但会延长启动链路。

如果你的目标是“任何情况下都不能影响开机”,更适合改成:

  • systemd timer 延迟执行
  • 或者启动后异步执行,而不是挂在关键启动链路上

适合什么场景

这套方案特别适合下面这些场景:

  • 小主机、工控机、NAS、开发板
  • 没有显示器常驻连接的设备
  • 经常断电重启的实验机
  • 远程维护的 Linux 设备

尤其是 DHCP 环境下,设备每次启动 IP 都可能变化,手机能第一时间收到新地址,维护效率会高很多。

总结

如果你只是想实现“开机后把当前 IP 推送到手机”,核心并不复杂,真正难的是把它做得稳定。

一套可用的实现至少要满足这几点:

  • systemd 管理,而不是单纯依赖 cron
  • 明确等待网络和等待 IP 的逻辑
  • 减少外部依赖
  • 保留日志,方便排错

这样机器重启后,你不用先猜它拿到了哪个地址,手机上直接就能收到通知。