一、实现效果
本方案可以实现类似 Apple TV 家庭中枢离线提醒的效果:
- 家里网络正常时,家里设备定时向 VPS 上报状态
- VPS 超过指定时间没有收到上报,就通过 Bark 推送“家庭网络可能离线”
- 家里网络恢复后,VPS 再次收到上报,推送“家庭网络已恢复”
- RouterOS 外网恢复时,也可以推送当前公网 IP
- RouterOS 检测到公网 IP 变化时,也可以单独推送提醒
最终通知效果类似:
家庭网络可能离线
已超过 3 分钟未收到家里网络连通数据
恢复时:
家庭网络已恢复
已重新收到家里网络连通数据
RouterOS 外网恢复时:
路由器通知
外网恢复
IP:1.2.3.4
二、整体原理
家里断网后,家里设备已经无法主动发送通知,所以不能只靠家里的设备判断断网。
正确做法是:
家里设备定时访问 VPS
↓
VPS 记录最后一次上报时间
↓
VPS 每分钟检查一次
↓
超过 3 分钟没收到上报
↓
VPS 调用 Bark 推送离线通知
↓
家里恢复后重新上报
↓
VPS 调用 Bark 推送恢复通知
这种方式的关键是:Bark 服务部署在 VPS 上,家里断网时 VPS 仍然在线,可以继续给 iPhone 推送通知。
三、目录结构
本文以 OpenLiteSpeed Docker 站点为例。
宿主机目录:
/data/ols/html/wordpress/home-status
容器内目录:
/usr/local/lsws/Example/html/wordpress/home-status
最终目录结构:
/data/ols/html/wordpress/home-status
├── heartbeat.php
├── check_home.php
└── data
├── home.last
└── home.status
说明:
heartbeat.php 接收家里设备上报
check_home.php VPS 定时检查是否离线
home.last 最后一次上报时间
home.status 当前状态:online / offline
四、VPS 端:heartbeat.php
heartbeat.php 用来接收家里设备上报,并写入最后上报时间。
编辑文件:
nano /data/ols/html/wordpress/home-status/heartbeat.php
内容如下:
<?php
// heartbeat.php
// 自定义 Token,建议改成复杂一点
$allowToken = '你的自定义Token';
$token = $_GET['token'] ?? '';
if ($token !== $allowToken) {
http_response_code(403);
exit('Forbidden');
}
$device = $_GET['device'] ?? 'home';
$device = preg_replace('/[^a-zA-Z0-9_-]/', '', $device);
if ($device === '') {
$device = 'home';
}
$dataDir = __DIR__ . '/data';
if (!is_dir($dataDir)) {
if (!mkdir($dataDir, 0755, true)) {
http_response_code(500);
exit('mkdir failed');
}
}
$heartbeatFile = $dataDir . '/' . $device . '.last';
$result = file_put_contents($heartbeatFile, time());
if ($result === false) {
http_response_code(500);
exit('write failed');
}
echo "OK " . $device . " " . date('Y-m-d H:i:s');
测试不带 Token:
curl -i "https://你的域名/home-status/heartbeat.php?device=home"
正常应返回:
HTTP/2 403
Forbidden
测试带 Token:
curl -i "https://你的域名/home-status/heartbeat.php?device=home&token=你的自定义Token"
正常应返回:
HTTP/2 200
OK home 2026-06-23 10:51:02
五、VPS 端:check_home.php
check_home.php 用来定时检查 home.last 是否超时。
编辑文件:
nano /data/ols/html/wordpress/home-status/check_home.php
内容如下:
<?php
// check_home.php
// 只允许命令行执行,浏览器访问会显示 Forbidden
if (php_sapi_name() !== 'cli') {
http_response_code(403);
exit('Forbidden');
}
$device = 'home';
// 超过多少秒没收到上报就判断为离线
$offlineSeconds = 180; // 3分钟
// Bark 推送地址
// 如果使用官方 Bark:
$barkServer = 'https://api.day.app';
// 如果使用自建 Bark,改成自己的 Bark 域名,例如:
// $barkServer = 'https://你的Bark域名';
// Bark Key
$barkKey = '你的BarkKey';
$dataDir = __DIR__ . '/data';
$heartbeatFile = $dataDir . '/' . $device . '.last';
$statusFile = $dataDir . '/' . $device . '.status';
if (!is_dir($dataDir)) {
mkdir($dataDir, 0755, true);
}
$now = time();
$last = file_exists($heartbeatFile) ? intval(trim(file_get_contents($heartbeatFile))) : 0;
$oldStatus = file_exists($statusFile) ? trim(file_get_contents($statusFile)) : 'unknown';
$isOnline = ($last > 0 && ($now - $last) <= $offlineSeconds);
$newStatus = $isOnline ? 'online' : 'offline';
// 状态没变化,不重复推送
if ($newStatus === $oldStatus) {
echo "No change: {$newStatus}\n";
exit;
}
// 状态变化,写入新状态
file_put_contents($statusFile, $newStatus);
if ($newStatus === 'offline') {
$title = '家庭网络可能离线';
$body = '已超过 ' . intval($offlineSeconds / 60) . ' 分钟未收到家里网络连通数据';
$sound = 'alarm';
} else {
$title = '家庭网络已恢复';
$body = '已重新收到家里网络连通数据';
$sound = 'bell';
}
$url = rtrim($barkServer, '/') . '/' . $barkKey . '/'
. rawurlencode($title) . '/'
. rawurlencode($body)
. '?group=' . rawurlencode('家庭网络')
. '&sound=' . rawurlencode($sound);
$result = @file_get_contents($url);
echo date('Y-m-d H:i:s') . " {$device} {$oldStatus} -> {$newStatus}\n";
echo $result . "\n";
六、修复 data 目录权限
因为 OpenLiteSpeed Docker 容器里 PHP 通常以 nobody:nogroup 运行,所以需要给 data 目录写权限。
在 VPS 宿主机执行:
cd /data/ols/html/wordpress/home-status
mkdir -p data
chown -R nobody:nogroup data
chmod -R 755 data
如果宿主机用户组不同,也可以使用 UID/GID:
chown -R 65534:65534 /data/ols/html/wordpress/home-status/data
chmod -R 755 /data/ols/html/wordpress/home-status/data
确认:
ls -la /data/ols/html/wordpress/home-status/data
正常应看到:
home.last
home.status
七、Docker 环境下测试 check_home.php
由于 PHP 在 OpenLiteSpeed Docker 容器中,所以测试 check_home.php 要通过 docker exec 执行。
执行:
docker exec -it ols php /usr/local/lsws/Example/html/wordpress/home-status/check_home.php
正常输出可能是:
No change: online
或者状态变化时:
2026-06-23 10:34:06 home offline -> online
{"code":200,"message":"success","timestamp":1782210846}
说明判断和 Bark 推送正常。
八、VPS 添加定时检测任务
VPS 上每分钟执行一次 check_home.php。
在 VPS 宿主机执行:
crontab -e
添加:
* * * * * docker exec ols php /usr/local/lsws/Example/html/wordpress/home-status/check_home.php >/dev/null 2>&1
或者一条命令添加:
(crontab -l 2>/dev/null; echo '* * * * * docker exec ols php /usr/local/lsws/Example/html/wordpress/home-status/check_home.php >/dev/null 2>&1') | crontab -
查看:
crontab -l
应看到:
* * * * * docker exec ols php /usr/local/lsws/Example/html/wordpress/home-status/check_home.php >/dev/null 2>&1
九、家里设备添加定时上报
这一步要在家里的常开设备上执行,例如:
Ubuntu
iStoreOS
OpenWrt
NAS
RouterOS
如果是 Linux / Ubuntu,执行:
crontab -e
添加:
* * * * * curl -fsS "https://你的域名/home-status/heartbeat.php?device=home&token=你的自定义Token" >/dev/null 2>&1
也可以一条命令添加:
(crontab -l 2>/dev/null; echo '* * * * * curl -fsS "https://你的域名/home-status/heartbeat.php?device=home&token=你的自定义Token" >/dev/null 2>&1') | crontab -
查看:
crontab -l
十、OpenWrt / iStoreOS 上添加定时上报
如果家里设备是 OpenWrt / iStoreOS,添加后建议重启 cron。
编辑:
crontab -e
加入:
* * * * * curl -fsS "https://你的域名/home-status/heartbeat.php?device=home&token=你的自定义Token" >/dev/null 2>&1
重启 cron:
/etc/init.d/cron restart
/etc/init.d/cron enable
查看日志:
logread | grep cron
十一、检查当前状态
在 VPS 上执行:
cat /data/ols/html/wordpress/home-status/data/home.status
cat /data/ols/html/wordpress/home-status/data/home.last
date +%s
例如:
online
1782212101
1782212123
差值:
1782212123 - 1782212101 = 22 秒
小于 180 秒,说明家里设备正常上报,状态是在线。
十二、测试断网提醒
可以先不要真的断网,而是临时停掉家里设备的上报任务。
在家里设备执行:
crontab -e
把这一行前面加 #:
# * * * * * curl -fsS "https://你的域名/home-status/heartbeat.php?device=home&token=你的自定义Token" >/dev/null 2>&1
等待 3~4 分钟,应该收到 Bark 推送:
家庭网络可能离线
已超过 3 分钟未收到家里网络连通数据
恢复 cron 后,等待 1 分钟左右,会收到:
家庭网络已恢复
已重新收到家里网络连通数据
十三、RouterOS 外网恢复通知当前 IP
如果 RouterOS 的 address-list 里已经有自动更新的公网 IP,例如:
auto-wan-ip
可以直接从 address-list 取 IP,并通过 Bark 推送。
脚本如下:
:local ids [/ip firewall address-list find list="auto-wan-ip"]
:if ([:len $ids] = 0) do={
:log warning "auto-wan-ip is empty"
:return
}
:local pubip [/ip firewall address-list get [:pick $ids 0] address]
/tool fetch url="https://你的Bark域名/你的BarkKey" \
http-method=post \
http-header-field="Content-Type: application/x-www-form-urlencoded" \
http-data=("title=%E8%B7%AF%E7%94%B1%E5%99%A8%E9%80%9A%E7%9F%A5&body=%E5%A4%96%E7%BD%91%E6%81%A2%E5%A4%8D%0AIP%EF%BC%9A" . $pubip . "&group=%E5%AE%B6%E5%BA%AD%E7%BD%91%E7%BB%9C") \
keep-result=no
通知效果:
路由器通知
外网恢复
IP:1.2.3.4
其中:
group=%E5%AE%B6%E5%BA%AD%E7%BD%91%E7%BB%9C
对应 Bark 分组:
家庭网络
十四、RouterOS 公网 IP 变动通知
如果 auto-wan-ip 会自动更新,只需要做一个通知脚本,定时比较当前 IP 和上一次 IP。
建议新建 RouterOS 脚本:
check-wan-ip-change
脚本内容:
:local ids [/ip firewall address-list find list="auto-wan-ip"]
:if ([:len $ids] = 0) do={
:log warning "auto-wan-ip is empty"
:return
}
:local pubip [/ip firewall address-list get [:pick $ids 0] address]
:local envName "lastWanIP"
:local envId [/system script environment find name=$envName]
:if ([:len $envId] = 0) do={
/system script environment add name=$envName value=$pubip
:log info ("Init lastWanIP=" . $pubip)
:return
}
:local oldip [/system script environment get $envId value]
:if ($pubip != $oldip) do={
/system script environment set $envId value=$pubip
/tool fetch url="https://你的Bark域名/你的BarkKey" \
http-method=post \
http-header-field="Content-Type: application/x-www-form-urlencoded" \
http-data=("title=%E8%B7%AF%E7%94%B1%E5%99%A8%E9%80%9A%E7%9F%A5&body=IP%E5%B7%B2%E5%8F%98%E6%9B%B4%0A%E5%8E%9FIP%EF%BC%9A" . $oldip . "%0A%E6%96%B0IP%EF%BC%9A" . $pubip . "&group=%E5%AE%B6%E5%BA%AD%E7%BD%91%E7%BB%9C") \
keep-result=no
:log warning ("WAN IP changed: " . $oldip . " -> " . $pubip)
} else={
:log info ("WAN IP no change: " . $pubip)
}
添加定时任务,每 5 分钟检测一次:
/system scheduler add name=check-wan-ip-change interval=5m on-event="/system script run check-wan-ip-change"
通知效果:
路由器通知
IP已变更
原IP:1.2.3.4
新IP:5.6.7.8
十五、最终效果
完成后,整体链路如下:
家里设备
每分钟访问 heartbeat.php
↓
VPS 记录 home.last
↓
VPS 每分钟执行 check_home.php
↓
超过 3 分钟未收到上报
↓
Bark 推送家庭网络可能离线
↓
恢复上报后
↓
Bark 推送家庭网络已恢复
同时 RouterOS 可以补充:
外网恢复 → 推送当前 IP
公网 IP 变化 → 推送原 IP / 新 IP
最终 Bark 通知统一归类到:
家庭网络
十六、常见问题
1. heartbeat.php 返回 Forbidden
说明没有带 Token,或者 Token 不正确。
正确地址:
https://你的域名/home-status/heartbeat.php?device=home&token=你的自定义Token
2. heartbeat.php 返回 OK,但没有生成文件
一般是 data 目录权限问题。
执行:
chown -R nobody:nogroup /data/ols/html/wordpress/home-status/data
chmod -R 755 /data/ols/html/wordpress/home-status/data
Docker 环境也可以用 UID/GID:
chown -R 65534:65534 /data/ols/html/wordpress/home-status/data
3. check_home.php 浏览器访问 Forbidden
这是正常的。
check_home.php 只允许命令行执行,不建议通过浏览器访问。
正确测试方式:
docker exec -it ols php /usr/local/lsws/Example/html/wordpress/home-status/check_home.php
4. 一直收到离线通知
检查家里设备的定时任务是否正常:
crontab -l
手动测试:
curl -fsS "https://你的域名/home-status/heartbeat.php?device=home&token=你的自定义Token"
5. 离线后没有重复通知
这是正常设计。
home.status 用来避免重复推送:
online -> offline 推送离线
offline -> offline 不推送
offline -> online 推送恢复
online -> online 不推送
十七、安全建议
建议把 Token 改复杂一点,例如不要使用:
abc123
可以改成类似:
HomeNet_2026_xxxxxx
同时不要公开 heartbeat.php 的完整地址,避免别人伪造上报导致断网提醒失效。
另外,分享文章时请替换以下敏感信息:
你的域名
你的Bark域名
你的BarkKey
你的自定义Token