Skill: adminlove520/openclaw-gateway-watchdog (version 0.1.0)

# SKILL.md - Gateway Watchdog Skill > 一句话让 OpenClaw 安装 Gateway Watchdog ## 触发条件 - "安装 Gateway Watchdog" - "Gateway 监控" - "gateway watchdog" - "守护 Gateway" ## 功能 1. **自动安装** Gateway Watchdog 监控脚本 2. **自动配置** 钉钉通知（可选） 3. **自动设置** 开机自启 4. **自动运行** 监控服务 ## 安装流程 ### 第一步：克隆仓库 ```bash git clone https://github.com/adminlove520/gateway-watchdog.git cd gateway-watchdog ``` ### 第二步：配置（可选） ```bash cp config.example.py config.py # 编辑 config.py 填入钉钉 Webhook 和加签密钥 ``` ### 第三步：运行安装脚本 ```bash python install.py # 选择 "1" 安装开机自启 ``` ## 配置说明 | 配置项 | 说明 | 默认值 | |--------|------|--------| | WEBHOOK | 钉钉 Webhook URL | "" | | SECRET | 钉钉加签密钥 | "" | | CHECK_INTERVAL | 检查间隔（秒） | 60 | | NOTIFY_DAILY | 每天报平安 | True | ## 通知场景 - ✅ 启动时报平安 - ✅ Gateway 掉线报警 - ✅ 重启成功报平安 - ✅ 重启失败报警 - ☀️ 每天 8-10 点报平安 ## 使用命令 ```bash # 安装（已在仓库目录） python install.py # 查看状态 python install.py # 选择 4 # 卸载 python install.py # 选择 2 ``` ## 检查日志 ```bash # Windows type %USERPROFILE%\.openclaw\gateway-watchdog.log # Linux/macOS cat ~/.openclaw/gateway-watchdog.log ``` ## 作者 🦞 小溪 - 帮哥哥守护 Gateway

path: ARCHITECTURE.md
content: # OpenClaw Gateway Watchdog - 架构设计

## 系统架构

```
┌─────────────────────────────────────────────────────────────────────┐
│                         系统架构图                                    │
└─────────────────────────────────────────────────────────────────────┘

┌──────────────────┐     ┌──────────────────┐     ┌──────────────────┐
│   用户 (哥哥)     │     │   Watchdog       │     │   钉钉服务器      │
│                  │     │                  │     │                  │
│  - 接收通知      │     │  - 监控 Gateway  │     │  - Webhook       │
│  - 查看日志      │     │  - 自动重启      │     │  - 加签验证       │
└────────┬─────────┘     │  - 发送通知      │     └────────┬─────────┘
         │               └────────┬─────────┘              │
         │                        │                        │
         │                        │ HTTP                   │
         │                        │ POST                   │
         │                        ▼                        │
         │               ┌──────────────────┐              │
         │               │  OpenClaw        │              │
         │               │  Gateway         │              │
         │               │                  │              │
         │               │  - WebSocket     │              │
         │               │  - HTTP API      │              │
         │               │  - Port: 18789   │              │
         │               └──────────────────┘              │
         │                                                   │
         │  钉钉消息                                        │
         ◄──────────────────────────────────────────────────┘
```

## 监控流程

```
┌─────────────────────────────────────────────────────────────────────┐
│                        监控流程图                                    │
└─────────────────────────────────────────────────────────────────────┘

                              ┌─────────────────┐
                              │   启动 Watchdog  │
                              └────────┬────────┘
                                       │
                                       ▼
                        ┌────────────────────────┐
                        │  钉钉: 启动报平安      │
                        │  (NOTIFY_ON_STARTUP)  │
                        └────────┬───────────────┘
                                 │
                                 ▼
                    ┌─────────────────────────────┐
                    │      检查 Gateway           │
                    │   HTTP GET 127.0.0.1:18789 │
                    └─────────────┬───────────────┘
                                  │
                    ┌─────────────┴─────────────┐
                    │                           │
                    ▼                           ▼
            ┌──────────────┐           ┌──────────────┐
            │   状态 200   │           │   连接失败   │
            │              │           │              │
            │  Gateway     │           │   Gateway    │
            │  运行正常    │           │    掉线      │
            └──────┬───────┘           └──────┬───────┘
                   │                           │
                   ▼                           ▼
            ┌──────────────┐           ┌──────────────┐
            │  记录日志    │           │  钉钉: 掉线  │
            │  继续监控    │           │   报警       │
            └──────────────┘           └──────┬───────┘
                                              │
                                              ▼
                                    ┌─────────────────────┐
                                    │  杀掉 node 进程     │
                                    │  taskkill / pkill   │
                                    └──────────┬──────────┘
                                               │
                                               ▼
                                    ┌─────────────────────┐
                                    │  等待 2 秒         │
                                    └──────────┬──────────┘
                                               │
                                               ▼
                                    ┌─────────────────────┐
                                    │  启动 Gateway      │
                                    │  openclaw gateway  │
                                    │       start        │
                                    └──────────┬──────────┘
                                               │
                                               ▼
                                    ┌─────────────────────┐
                                    │  等待 5 秒         │
                                    └──────────┬──────────┘
                                               │
                                               ▼
                                    ┌─────────────────────┐
                                    │  再次检查 Gateway   │
                                    └─────────────┬───────┘
                                                  │
                                    ┌─────────────┴─────────────┐
                                    │                           │
                                    ▼                           ▼
                            ┌──────────────┐           ┌──────────────┐
                            │   状态 200   │           │   连接失败   │
                            │              │           │              │
                            │  重启成功    │           │  重启失败    │
                            └──────┬───────┘           └──────┬───────┘
                                   │                           │
                                   ▼                           ▼
                            ┌──────────────┐           ┌──────────────┐
                            │  钉钉: 恢复   │           │  钉钉: 失败   │
                            │   成功       │           │   需要人工    │
                            └──────────────┘           └──────────────┘
                                   │                           │
                                   └─────────────┬─────────────┘
                                                 │
                                                 ▼
                                    ┌─────────────────────────┐
                                    │   等待 60 秒后继续监控   │
                                    └─────────────────────────┘
```

## 每天报平安流程

```
┌─────────────────────────────────────────────────────────────────────┐
│                      每天报平安流程                                   │
└─────────────────────────────────────────────────────────────────────┘

       ┌──────────────────────────────────────────┐
       │           每 60 秒检查一次                │
       └────────────────────┬─────────────────────┘
                            │
                            ▼
                 ┌────────────────────────┐
                 │  检查是否需要报平安    │
                 │  - 距离上次 > 20 小时  │
                 │  - 当前时间 8-10 点    │
                 └───────────┬────────────┘
                             │
              ┌──────────────┴──────────────┐
              │                             │
              ▼                             ▼
      ┌──────────────┐            ┌──────────────┐
      │    需要      │            │    不需要     │
      │   报平安    │            │   继续监控    │
      └──────┬───────┘            └──────────────┘
             │
             ▼
      ┌──────────────┐
      │  钉钉: ☀️    │
      │  Gateway     │
      │  正常运行    │
      └──────────────┘
             │
             ▼
      ┌──────────────┐
      │  记录上次    │
      │  报平安时间  │
      └──────────────┘
```

## 数据流

```
┌─────────────────────────────────────────────────────────────────────┐
│                        数据流图                                      │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   配置      │────►│   监控      │────►│   动作      │────►│   通知      │
│  Config    │     │  Monitor    │     │   Action    │     │  Notifier   │
└─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘
      │                   │                   │                   │
      │                   │                   │                   │
      ▼                   ▼                   ▼                   ▼
  钉钉 Webhook      检查 HTTP          杀进程             钉钉 API
  钉钉 Secret       响应状态          启动进程           消息推送
  检查间隔           返回码             延时等待
  通知开关           True/False        重试次数
```

## 文件结构

```
gateway-watchdog/
│
├── 📄 gateway_monitor.py      # 主程序入口
│
├── 📄 config.example.py       # 配置示例文件
│   │
│   │   配置项说明:
│   │   ├── WEBHOOK            # 钉钉 Webhook URL
│   │   ├── SECRET             # 钉钉加签密钥
│   │   ├── CHECK_INTERVAL     # 检查间隔(秒)
│   │   ├── GATEWAY_PORT       # Gateway 端口
│   │   ├── NOTIFY_ON_*        # 各种通知开关
│   │   └── NOTIFY_DAILY       # 每日报平安
│
├── 📄 README.md               # 使用说明
│
├── 📄 CHANGELOG.md            # 更新日志
│
├── 📄 ARCHITECTURE.md         # 本文档
│
└── 📄 install.py              # 安装脚本 (可选)
```

## 关键模块

### 1. 配置模块 (Configuration)
- 读取配置文件
- 支持 config.py 覆盖默认配置
- 钉钉 Webhook 和加签密钥

### 2. 监控模块 (Monitor)
- HTTP 健康检查
- 心跳检测
- 状态判断

### 3. 动作模块 (Action)
- 杀掉进程 (taskkill / pkill)
- 启动 Gateway (openclaw gateway start)
- 重试机制

### 4. 通知模块 (Notifier)
- 钉钉 API 调用
- 加签验证
- 消息格式化

## 安全性

```
┌─────────────────────────────────────────────────────────────────────┐
│                        安全性设计                                    │
└─────────────────────────────────────────────────────────────────────┘

1. 钉钉加签
   ┌─────────────────────────────────────────────────────────────┐
   │  1. 获取 timestamp (毫秒)                                    │
   │  2. 拼接字符串: timestamp + "\n" + secret                  │
   │  3. HMAC-SHA256 签名                                        │
   │  4. Base64 编码                                             │
   │  5. URL 编码                                                │
   │  6. 拼接到 Webhook URL                                      │
   └─────────────────────────────────────────────────────────────┘

2. 进程隔离
   - Watchdog 运行在独立进程
   - 不依赖 OpenClaw 自身
   - 即使 Gateway 挂了也能工作

3. 本地存储
   - 日志文件在用户目录
   - 配置不提交到 git
```

## 性能

```
┌─────────────────────────────────────────────────────────────────────┐
│                        性能指标                                      │
└─────────────────────────────────────────────────────────────────────┘

- 内存占用: ~30MB
- CPU 使用: < 1%
- 检查间隔: 60 秒 (可配置)
- 启动时间: < 2 秒
- 网络请求: 每次 5 秒超时
```

---

🦞 小溪 - 2026-03-18


path: CHANGELOG.md
content: # Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.1] - 2026-03-18

### Fixed
- 重启逻辑改用 `--force` 强制重启
- 添加 .gitignore 忽略敏感配置

## [1.0.0] - 2026-03-18

### Added
- 初始版本发布
- 支持 Windows / Linux / macOS
- 自动检测 Gateway 状态
- 掉了自动重启
- 钉钉机器人通知
- 每日报平安功能

### Features
- ✅ 跨平台支持
- ✅ 钉钉加签验证
- ✅ 多种通知场景
- ✅ 开机自启配置

### Components
- `gateway_monitor.py` - 主程序
- `config.example.py` - 配置示例
- `README.md` - 使用说明
- `CHANGELOG.md` - 更新日志
- `ARCHITECTURE.md` - 架构文档

---

## 版本历史

| 版本 | 日期 | 说明 |
|------|------|------|
| 1.0.0 | 2026-03-18 | 初始版本 |

## 计划功能

- [ ] 支持企业微信通知
- [ ] 支持 Telegram 通知
- [ ] 支持邮件通知
- [ ] Web UI 管理界面
- [ ] 支持多 Gateway 监控

---

🦞 小溪 - 2026-03-18


path: README.md
content: # OpenClaw Gateway Watchdog

> 🦞 OpenClaw Gateway 24/7 稳定运行监控工具

[![GitHub Stars](https://img.shields.io/github/stars/adminlove520/gateway-watchdog)](https://github.com/adminlove520/gateway-watchdog)
[![Python Version](https://img.shields.io/badge/python-3.7%2B-blue)](https://www.python.org/)
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-orange)](https://github.com/adminlove520/gateway-watchdog)

## 为什么需要

**不要用 OpenClaw 自己的 cron 监控 Gateway。** Gateway 挂了，cron job 根本收不到 wake event，形成死锁。

本工具用独立进程监控 Gateway，掉了自动重启，还能发送钉钉通知！

## 特性

- ✅ 自动检测 Gateway 状态
- ✅ 掉了自动重启
- ✅ 钉钉机器人通知
- ✅ 支持 Windows / Linux / macOS
- ✅ 每天报平安
- ✅ 开机自启

## 快速开始

### 1. 安装

```bash
# 克隆仓库
git clone https://github.com/adminlove520/gateway-watchdog.git
cd gateway-watchdog

# 配置钉钉（可选）
cp config.example.py config.py
# 编辑 config.py 填入你的钉钉 Webhook 和加签密钥
```

### 2. 配置钉钉机器人

1. 钉钉群 → 设置 → 智能群助手 → 添加机器人
2. 选择「自定义」机器人
3. 设置名字（如 Gateway Watchdog）
4. 开启「加签」安全设置
5. 复制 `Webhook 地址` 和 `加签密钥`

### 3. 运行

```bash
# 方式1: 直接运行
python gateway_monitor.py

# 方式2: 后台运行（Linux/macOS）
nohup python gateway_monitor.py &

# 方式3: 使用安装脚本（推荐）
python install.py
```

### 4. 开机自启

运行 `python install.py` 选择安装即可：

```
==================================================
OpenClaw Gateway Watchdog 安装向导
==================================================

检测到系统: Windows

1. 安装开机自启
2. 卸载开机自启
3. 直接运行（测试用）
4. 查看状态
0. 退出
```

**手动安装（Windows）**:
```powershell
schtasks /create /tn "OpenClawGatewayWatchdog" /tr "python C:\path\to\gateway_monitor.py" /sc minute /mo 1 /rl limited /f
```

## 配置说明

编辑 `config.py` 或在运行目录创建配置文件：

```python
# 钉钉配置
WEBHOOK = "https://oapi.dingtalk.com/robot/send?access_token=xxx"
[REDACTED_SECRET]"

# 监控配置
CHECK_INTERVAL = 60      # 检查间隔（秒）
GATEWAY_PORT = 18789    # Gateway 端口

# 通知配置
NOTIFY_ON_STARTUP = True    # 启动时报平安
NOTIFY_ON_DOWN = True       # 掉线时通知
NOTIFY_ON_RECOVERY = True   # 恢复时通知
NOTIFY_ON_FAILED = True     # 重启失败时通知
NOTIFY_DAILY = True         # 每天报平安
```

## 通知逻辑

| 场景 | 是否通知 |
|------|----------|
| 启动时 | ✅ 报平安 |
| Gateway 掉线 | ✅ 报警 |
| 重启成功 | ✅ 报平安 |
| 重启失败 | ✅ 报警 |
| 正常运行 | ❌ 不通知 |
| 每天 8-10 点 | ✅ 报平安 |

## 重启逻辑

**掉了直接用 `--force` 强制重启**

掉线说明 Gateway 有问题，常规重启很可能起不来。所以本工具使用 `openclaw gateway --force start` 直接强制杀掉旧进程再启动。

## 开机自启

### Windows

```powershell
# 创建任务计划
schtasks /create /tn "OpenClaw Gateway Watchdog" /tr "python C:\path\to\gateway_monitor.py" /sc minute /mo 1 /rl limited /f
```

### Linux (systemd)

```ini
# /etc/systemd/system/gateway-watchdog.service
[Unit]
Description=OpenClaw Gateway Watchdog
After=network.target

[Service]
Type=simple
User=yourusername
WorkingDirectory=/path/to/gateway-watchdog
ExecStart=/usr/bin/python3 /path/to/gateway-watchdog/gateway_monitor.py
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
```

```bash
sudo systemctl enable gateway-watchdog
sudo systemctl start gateway-watchdog
```

### Linux (crontab)

```bash
# 编辑 crontab
crontab -e

# 添加（每分钟检查一次）
* * * * * cd /path/to/gateway-watchdog && python gateway_monitor.py >> /tmp/gateway-watchdog.log 2>&1
```

### macOS (launchd)

```xml
<!-- ~/Library/LaunchAgents/com.openclaw.gateway-watchdog.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.openclaw.gateway-watchdog</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/python3</string>
        <string>/path/to/gateway-watchdog/gateway_monitor.py</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
</dict>
</plist>
```

```bash
launchctl load ~/Library/LaunchAgents/com.openclaw.gateway-watchdog.plist
```

## 项目结构

```
gateway-watchdog/
├── gateway_monitor.py    # 主程序
├── config.example.py     # 配置示例
├── README.md             # 说明文档
├── CHANGELOG.md         # 更新日志
├── ARCHITECTURE.md      # 架构图
└── install.py           # 安装脚本
```

## 监控流程图

```
┌─────────────────────────────────────────────────────────┐
│                    Watchdog 启动                          │
└─────────────────────┬───────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────┐
│              每 60 秒检查一次 Gateway                      │
└─────────────────────┬───────────────────────────────────┘
                      │
           ┌──────────┴──────────┐
           │                     │
           ▼                     ▼
    ┌─────────────┐       ┌─────────────┐
    │  Gateway    │       │  Gateway    │
    │  运行正常   │       │   掉线      │
    └──────┬──────┘       └──────┬──────┘
           │                     │
           ▼                     ▼
    ┌─────────────┐       ┌─────────────┐
    │  记录日志   │       │  钉钉通知   │
    │  (不通知)   │       │  掉线报警   │
    └─────────────┘       └──────┬──────┘
                                 │
                                 ▼
                    ┌─────────────────────────┐
                    │  杀掉 node 进程         │
                    │  等待 2 秒              │
                    │  启动 Gateway           │
                    └──────────┬──────────────┘
                               │
                    ┌──────────┴──────────────┐
                    │                         │
                    ▼                         ▼
             ┌─────────────┐          ┌─────────────┐
             │  重启成功   │          │  重启失败   │
             └──────┬──────┘          └──────┬──────┘
                    │                         │
                    ▼                         ▼
             ┌─────────────┐          ┌─────────────┐
             │  钉钉通知   │          │  钉钉通知   │
             │  恢复成功   │          │  需要人工   │
             └─────────────┘          └─────────────┘
```

## 常见问题

### Q: 为什么不用 OpenClaw 自带的 cron？

A: Gateway 挂了之后，cron job 也收不到通知，形成死锁。独立监控更可靠。

### Q: 钉钉通知显示乱码怎么办？

A: 请使用 Python 3.7+，并确保使用 `config.py` 而非直接修改 `gateway_monitor.py`。

### Q: 如何查看日志？

A: 日志文件在 `~/.openclaw/gateway-watchdog.log`（Linux/macOS）或 `%USERPROFILE%\.openclaw\gateway-watchdog.log`（Windows）

## 更新日志

详见 [CHANGELOG.md](CHANGELOG.md)

## 贡献者

- 小溪 🦞 - 作者

## 许可证

MIT License

---

🦞 小溪的作品 - 帮哥哥守护 Gateway！


path: config.example.py
content: # -*- coding: utf-8 -*-
"""
配置示例文件
复制此文件为 config.py 并填入你的配置
"""

# ==================== 钉钉配置 ====================
# 钉钉 Webhook 地址（从钉钉机器人设置中获取）
WEBHOOK = "https://oapi.dingtalk.com/robot/send?access_token=YOUR_ACCESS_TOKEN"

# 钉钉加签密钥（以 SEC 开头，从钉钉机器人安全设置中获取）
[REDACTED_SECRET]"
# =================================================

# ==================== 监控配置 ====================
# 检查间隔（秒），默认 60 秒检查一次
CHECK_INTERVAL = 60

# Gateway 端口，默认 18789
GATEWAY_PORT = 18789

# Gateway URL，一般不需要修改
GATEWAY_URL = f"http://127.0.0.1:{GATEWAY_PORT}/"
# =================================================

# ==================== 通知配置 ====================
# 启动时发送报平安通知
NOTIFY_ON_STARTUP = True

# Gateway 掉线时发送通知
NOTIFY_ON_DOWN = True

# Gateway 恢复后发送通知
NOTIFY_ON_RECOVERY = True

# Gateway 重启失败时发送通知
NOTIFY_ON_FAILED = True

# 每天发送报平安通知（仅在 8-10 点发送）
NOTIFY_DAILY = True
# =================================================

# ==================== 日志配置 ====================
# 日志文件路径
# Windows: %USERPROFILE%\.openclaw\gateway-watchdog.log
# Linux/macOS: ~/.openclaw/gateway-watchdog.log
import os
LOG_FILE = os.path.join(os.path.expanduser("~"), ".openclaw", "gateway-watchdog.log")
# =================================================


path: gateway_monitor.py
content: # -*- coding: utf-8 -*-
"""
OpenClaw Gateway Watchdog
Gateway 监控脚本，支持自动重启和钉钉通知
"""

import os
import sys
import time
import json
import hmac
import base64
import urllib.parse
import urllib.request
import subprocess
import platform
from datetime import datetime, timezone, timedelta

# ==================== 配置区域 ====================
# 优先使用同目录下的 config.py
import os
import sys

_config_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.py")
if os.path.exists(_config_file):
    sys.path.insert(0, os.path.dirname(_config_file))
    try:
        import config
        WEBHOOK = getattr(config, 'WEBHOOK', "")
        [REDACTED_SECRET], 'SECRET', "")
        CHECK_INTERVAL = getattr(config, 'CHECK_INTERVAL', 60)
        GATEWAY_PORT = getattr(config, 'GATEWAY_PORT', 18789)
        GATEWAY_URL = getattr(config, 'GATEWAY_URL', f"http://127.0.0.1:{GATEWAY_PORT}/")
        NOTIFY_ON_STARTUP = getattr(config, 'NOTIFY_ON_STARTUP', True)
        NOTIFY_ON_DOWN = getattr(config, 'NOTIFY_ON_DOWN', True)
        NOTIFY_ON_RECOVERY = getattr(config, 'NOTIFY_ON_RECOVERY', True)
        NOTIFY_ON_FAILED = getattr(config, 'NOTIFY_ON_FAILED', True)
        NOTIFY_DAILY = getattr(config, 'NOTIFY_DAILY', True)
        LOG_FILE = getattr(config, 'LOG_FILE', os.path.join(os.path.expanduser("~"), ".openclaw", "gateway-watchdog.log"))
    except Exception as e:
        print(f"加载配置文件失败: {e}")
        WEBHOOK = ""
        SECRET = ""
        CHECK_INTERVAL = 60
        GATEWAY_PORT = 18789
        GATEWAY_URL = f"http://127.0.0.1:{GATEWAY_PORT}/"
        NOTIFY_ON_STARTUP = True
        NOTIFY_ON_DOWN = True
        NOTIFY_ON_RECOVERY = True
        NOTIFY_ON_FAILED = True
        NOTIFY_DAILY = True
        LOG_FILE = os.path.join(os.path.expanduser("~"), ".openclaw", "gateway-watchdog.log")
else:
    # 钉钉配置
    WEBHOOK = ""  # 钉钉 Webhook URL
    SECRET = ""   # 钉钉加签密钥

    # 监控配置
    CHECK_INTERVAL = 60      # 检查间隔（秒）
    GATEWAY_PORT = 18789    # Gateway 端口
    GATEWAY_URL = f"http://127.0.0.1:{GATEWAY_PORT}/"

    # 通知配置
    NOTIFY_ON_STARTUP = True    # 启动时报平安
    NOTIFY_ON_DOWN = True       # 掉线时通知
    NOTIFY_ON_RECOVERY = True   # 恢复时通知
    NOTIFY_ON_FAILED = True     # 重启失败时通知
    NOTIFY_DAILY = True         # 每天报平安

    # 日志文件
    LOG_FILE = os.path.join(os.path.expanduser("~"), ".openclaw", "gateway-watchdog.log")
# =================================================

# 北京时间
def beijing_now():
    return datetime.now(timezone(timedelta(hours=8)))

def log(msg):
    """记录日志"""
    timestamp = beijing_now().strftime("%Y-%m-%d %H:%M:%S")
    line = f"{timestamp} - {msg}"
    print(line)
    try:
        os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
        with open(LOG_FILE, "a", encoding="utf-8") as f:
            f.write(line + "\n")
    except Exception:
        pass

def send_dingtalk(text):
    """发送钉钉通知"""
    if not WEBHOOK or not SECRET:
        log("钉钉未配置，跳过通知")
        return
    
    timestamp = int(time.time() * 1000)
    sign_str = f"{timestamp}\n{SECRET}"
    
    # HMAC-SHA256 签名
    hmac_obj = hmac.new(SECRET.encode("utf-8"), sign_str.encode("utf-8"), digestmod="sha256")
    sign = base64.b64encode(hmac_obj.digest()).decode("utf-8")
    sign = urllib.parse.quote(sign)
    
    url = f"{WEBHOOK}&timestamp={timestamp}&sign={sign}"
    
    data = {
        "msgtype": "text",
        "text": {
            "content": text
        }
    }
    
    req = urllib.request.Request(
        url,
        data=json.dumps(data).encode("utf-8"),
        headers={"Content-Type": "application/json"}
    )
    
    try:
        with urllib.request.urlopen(req, timeout=10) as response:
            result = json.loads(response.read().decode("utf-8"))
            if result.get("errcode") == 0:
                log("钉钉通知发送成功")
            else:
                log(f"钉钉通知发送失败: {result.get('errmsg')}")
    except Exception as e:
        log(f"钉钉通知发送失败: {e}")

def check_gateway():
    """检查 Gateway 是否在运行"""
    try:
        req = urllib.request.Request(GATEWAY_URL)
        with urllib.request.urlopen(req, timeout=5) as response:
            if response.status == 200:
                return True
    except Exception:
        pass
    return False

def kill_gateway_processes():
    """杀掉 Gateway 相关进程"""
    system = platform.system()
    
    if system == "Windows":
        try:
            subprocess.run(["taskkill", "/F", "/IM", "node.exe"], 
                          capture_output=True, timeout=10)
        except Exception:
            pass
    else:  # Linux/Mac
        try:
            # 杀掉所有 node 进程（谨慎使用）
            subprocess.run(["pkill", "-f", "openclaw"], 
                          capture_output=True, timeout=10)
        except Exception:
            pass

def start_gateway():
    """启动 Gateway（使用 --force 强制重启）"""
    system = platform.system()
    
    try:
        if system == "Windows":
            # 使用 --force 强制重启
            subprocess.Popen(
                ["openclaw", "gateway", "--force"],
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
                creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0
            )
        else:
            subprocess.Popen(
                ["openclaw", "gateway", "--force"],
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
                start_new_session=True
            )
        return True
    except Exception as e:
        log(f"启动 Gateway 失败: {e}")
        return False

def get_last_check_time():
    """获取上次检查时间（用于每日报平安）"""
    config_file = os.path.join(os.path.expanduser("~"), ".openclaw", "watchdog-config.json")
    try:
        if os.path.exists(config_file):
            with open(config_file, "r") as f:
                return json.load(f).get("last_daily_notify", 0)
    except Exception:
        pass
    return 0

def save_last_check_time():
    """保存检查时间"""
    config_file = os.path.join(os.path.expanduser("~"), ".openclaw", "watchdog-config.json")
    try:
        os.makedirs(os.path.dirname(config_file), exist_ok=True)
        with open(config_file, "w") as f:
            json.dump({"last_daily_notify": int(time.time())}, f)
    except Exception:
        pass

def should_send_daily():
    """判断是否应该发送每日报平安"""
    last = get_last_check_time()
    now = int(time.time())
    
    # 每天 9 点发送
    current_hour = beijing_now().hour
    
    # 距离上次通知超过 20 小时 且 当前是 8-10 点
    if now - last > 20 * 3600 and 8 <= current_hour <= 10:
        return True
    return False

def main():
    """主函数"""
    log("=" * 50)
    log("OpenClaw Gateway Watchdog 启动")
    log("=" * 50)
    
    # 启动时发送通知
    if NOTIFY_ON_STARTUP:
        send_dingtalk("🦞 [小溪] Gateway Watchdog 已启动！监控中...")
    
    consecutive_failures = 0
    
    while True:
        try:
            if check_gateway():
                log("Gateway is running - OK")
                consecutive_failures = 0
                
                # 每日报平安
                if NOTIFY_DAILY and should_send_daily():
                    send_dingtalk("☀️ [小溪] Gateway 今天也在正常运行！")
                    save_last_check_time()
            else:
                consecutive_failures += 1
                log(f"Gateway is DOWN! (连续失败: {consecutive_failures})")
                
                if NOTIFY_ON_DOWN:
                    send_dingtalk("⚠️ [报警] Gateway 掉线了！正在尝试重启...")
                
                # 杀掉旧进程
                kill_gateway_processes()
                time.sleep(2)
                
                # 启动 Gateway
                start_gateway()
                time.sleep(5)
                
                # 检查是否成功
                if check_gateway():
                    log("Gateway 重启成功")
                    if NOTIFY_ON_RECOVERY:
                        send_dingtalk("✅ [小溪] Gateway 重启成功！")
                else:
                    log("Gateway 重启失败")
                    if NOTIFY_ON_FAILED:
                        send_dingtalk("❌ [报警] Gateway 重启失败！需要人工干预！")
                    
                    # 失败后等待更长时间再重试
                    time.sleep(60)
            
            time.sleep(CHECK_INTERVAL)
            
        except KeyboardInterrupt:
            log("收到退出信号，Watchdog 停止")
            break
        except Exception as e:
            log(f"发生错误: {e}")
            time.sleep(CHECK_INTERVAL)

if __name__ == "__main__":
    main()


path: install.py
content: # -*- coding: utf-8 -*-
"""
OpenClaw Gateway Watchdog 安装脚本
自动配置开机自启
"""

import os
import sys
import subprocess
import platform

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
MAIN_SCRIPT = os.path.join(SCRIPT_DIR, "gateway_monitor.py")
PYTHON_CMD = sys.executable

def install_windows():
    """Windows 开机自启"""
    task_name = "OpenClawGatewayWatchdog"
    
    # 检查是否已存在
    result = subprocess.run(
        ["schtasks", "/query", "/tn", task_name],
        capture_output=True,
        text=True
    )
    
    if result.returncode == 0:
        print(f"❌ 任务 '{task_name}' 已存在，要重新安装请先卸载")
        return False
    
    # 创建任务计划
    cmd = [
        "schtasks", "/create",
        "/tn", task_name,
        "/tr", f'"{PYTHON_CMD}" "{MAIN_SCRIPT}"',
        "/sc", "minute",
        "/mo", "1",
        "/rl", "limited",
        "/f"
    ]
    
    result = subprocess.run(cmd, capture_output=True, text=True)
    
    if result.returncode == 0:
        print("✅ Windows 开机自启安装成功！")
        print(f"   任务名: {task_name}")
        print(f"   检查频率: 每 1 分钟")
        return True
    else:
        print(f"❌ 安装失败: {result.stderr}")
        return False

def uninstall_windows():
    """Windows 卸载"""
    task_name = "OpenClawGatewayWatchdog"
    
    result = subprocess.run(
        ["schtasks", "/delete", "/tn", task_name, "/f"],
        capture_output=True,
        text=True
    )
    
    if result.returncode == 0:
        print("✅ 卸载成功！")
        return True
    else:
        print(f"❌ 卸载失败: {result.stderr}")
        return False

def install_linux():
    """Linux 开机自启 (systemd)"""
    service_name = "gateway-watchdog"
    service_file = f"/etc/systemd/system/{service_name}.service"
    
    if os.path.exists(service_file):
        print(f"❌ 服务已存在，要重新安装请先卸载")
        return False
    
    service_content = f"""[Unit]
Description=OpenClaw Gateway Watchdog
After=network.target

[Service]
Type=simple
User={os.getenv('USER', 'root')}
WorkingDirectory={SCRIPT_DIR}
ExecStart={PYTHON_CMD} {MAIN_SCRIPT}
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
"""
    
    try:
        # 写入服务文件（需要 sudo）
        print(f"请手动创建服务文件: {service_file}")
        print("内容如下:")
        print("-" * 40)
        print(service_content)
        print("-" * 40)
        print("然后运行:")
        print(f"  sudo systemctl enable {service_name}")
        print(f"  sudo systemctl start {service_name}")
        return True
    except Exception as e:
        print(f"❌ 安装失败: {e}")
        return False

def install_macos():
    """macOS 开机自启 (launchd)"""
    plist_name = "com.openclaw.gateway-watchdog.plist"
    plist_path = os.path.expanduser(f"~/Library/LaunchAgents/{plist_name}")
    
    if os.path.exists(plist_path):
        print(f"❌ plist 已存在，要重新安装请先卸载")
        return False
    
    plist_content = f"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>{plist_name}</string>
    <key>ProgramArguments</key>
    <array>
        <string>{PYTHON_CMD}</string>
        <string>{MAIN_SCRIPT}</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
</dict>
</plist>
"""
    
    try:
        os.makedirs(os.path.dirname(plist_path), exist_ok=True)
        with open(plist_path, "w") as f:
            f.write(plist_content)
        
        print(f"✅ macOS 开机自启安装成功！")
        print(f"   plist: {plist_path}")
        print("然后运行:")
        print(f"  launchctl load {plist_path}")
        return True
    except Exception as e:
        print(f"❌ 安装失败: {e}")
        return False

def install_crontab():
    """Linux/macOS 备选方案：crontab"""
    print("添加 crontab 开机自启...")
    print(f"请运行: @reboot {PYTHON_CMD} {MAIN_SCRIPT}")
    return True

def main():
    print("=" * 50)
    print("OpenClaw Gateway Watchdog 安装向导")
    print("=" * 50)
    
    if len(sys.argv) > 1:
        command = sys.argv[1].lower()
        
        if command in ["uninstall", "remove", "delete"]:
            system = platform.system()
            if system == "Windows":
                uninstall_windows()
            else:
                print("❌ 仅支持 Windows 卸载")
            return
    
    system = platform.system()
    print(f"\n检测到系统: {system}")
    print()
    
    print("1. 安装开机自启")
    print("2. 卸载开机自启")
    print("3. 直接运行（测试用）")
    print("4. 查看状态")
    print("0. 退出")
    print()
    
    choice = input("请选择 [1-4, 0]: ").strip()
    
    if choice == "1":
        if system == "Windows":
            install_windows()
        elif system == "Linux":
            install_linux()
        elif system == "Darwin":
            install_macos()
        else:
            print(f"❌ 不支持的系统: {system}")
    elif choice == "2":
        if system == "Windows":
            uninstall_windows()
        else:
            print("❌ 仅支持 Windows 卸载")
    elif choice == "3":
        print("运行 Watchdog（按 Ctrl+C 停止）...")
        print("提示: 使用 nohup python gateway_monitor.py & 后台运行")
        import gateway_monitor
        try:
            gateway_monitor.main()
        except KeyboardInterrupt:
            print("\n已停止")
    elif choice == "4":
        if system == "Windows":
            result = subprocess.run(
                ["schtasks", "/query", "/tn", "OpenClawGatewayWatchdog"],
                capture_output=True,
                text=True
            )
            if result.returncode == 0:
                print("✅ Watchdog 已安装")
                print(result.stdout)
            else:
                print("❌ Watchdog 未安装")
        else:
            print("请使用 ps aux | grep gateway_monitor 查看")
    elif choice == "0":
        print("退出")
    else:
        print("无效选择")

if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)