开机自动获取 IP 并通过 Bark 推送到手机
有些设备没有固定显示器,或者经常断电重启。机器重新启动之后,第一件事通常不是看日志,而是先确认它当前拿到了什么 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 编码
脚本思路
脚本的逻辑分成三步:
- 先等网络在线
- 再轮询指定网卡的 IPv4 地址
- 拿到 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="${BARK_BASE:-https://bark.example.com/xxxxxxxxxxxxxxxxxxxxxx}"
TITLE="${TITLE:-开机IP通知}"
MAX_WAIT="${MAX_WAIT:-300}"
SLEEP_SEC="${SLEEP_SEC:-3}"
STABLE_POLLS="${STABLE_POLLS:-5}"
BARK_RETRIES="${BARK_RETRIES:-3}"
BARK_RETRY_SLEEP="${BARK_RETRY_SLEEP:-5}"
INITIAL_DELAY="${INITIAL_DELAY:-10}"
log() {
logger -t boot-ip-bark "$*"
}
get_default_route_ip() {
/usr/sbin/ip -4 route get 1.1.1.1 2>/dev/null | awk '{
for (i = 1; i <= NF; i++) {
if ($i == "src") {
print $(i + 1)
exit
}
}
}'
}
get_default_route_iface() {
/usr/sbin/ip -4 route get 1.1.1.1 2>/dev/null | awk '{
for (i = 1; i <= NF; i++) {
if ($i == "dev") {
print $(i + 1)
exit
}
}
}'
}
get_fallback_ip() {
/usr/sbin/ip -4 -o addr show scope global up 2>/dev/null | awk '
$2 != "lo" {
split($4, a, "/")
print a[1]
exit
}
'
}
send_bark() {
local host="$1"
local ip_addr="$2"
local iface="$3"
local content
content="主机 ${host} 已开机,IP: ${ip_addr}"
if [ -n "$iface" ]; then
content="${content},接口: ${iface}"
fi
/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/2.0"})
with urlopen(req, timeout=10) as resp:
resp.read()
PY
}
sleep "$INITIAL_DELAY"
last_ip=""
stable_count=0
selected_ip=""
selected_iface=""
for _ in $(seq 1 "$MAX_WAIT"); do
current_ip="$(get_default_route_ip || true)"
current_iface="$(get_default_route_iface || true)"
if [ -z "$current_ip" ]; then
current_ip="$(get_fallback_ip || true)"
current_iface="${current_iface:-unknown}"
fi
if [ -z "$current_ip" ]; then
stable_count=0
sleep "$SLEEP_SEC"
continue
fi
if [ "$current_ip" = "$last_ip" ]; then
stable_count=$((stable_count + 1))
else
last_ip="$current_ip"
stable_count=1
log "candidate IP changed to ${current_ip} on ${current_iface:-unknown}"
fi
if [ "$stable_count" -ge "$STABLE_POLLS" ]; then
selected_ip="$current_ip"
selected_iface="$current_iface"
break
fi
sleep "$SLEEP_SEC"
done
if [ -z "$selected_ip" ]; then
log "no stable IPv4 address found"
exit 1
fi
host="$(hostname 2>/dev/null || echo unknown)"
for _ in $(seq 1 "$BARK_RETRIES"); do
if send_bark "$host" "$selected_ip" "$selected_iface"; then
log "sent bark notification for ${host} ${selected_ip} on ${selected_iface:-unknown}"
exit 0
fi
sleep "$BARK_RETRY_SLEEP"
done
log "failed to send bark notification for ${host} ${selected_ip} on ${selected_iface:-unknown}"
exit 1
systemd 配置
相比 crontab @reboot,systemd 更适合做这种开机任务:
- 启动顺序可控
- 日志统一进
journalctl - 失败后可自动重试
- 方便查看状态
服务文件路径:
/etc/systemd/system/boot-ip-bark.service
内容如下:
[Unit]
Description=Send boot IP notification to Bark
After=NetworkManager.service
[Service]
Type=oneshot
User=nvidia
Group=nvidia
ExecStart=/home/nvidia/.local/bin/boot-ip-bark.sh
TimeoutStartSec=20min
[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 的逻辑
- 减少外部依赖
- 保留日志,方便排错
这样机器重启后,你不用先猜它拿到了哪个地址,手机上直接就能收到通知。
- 感谢你赐予我前进的力量

