Files
Nukumizu/README.zh-CN.md
NanamiAdmin 03b67dc3c0
Build / ubuntu-latest (push) Failing after 0s
Build / windows-latest (push) Canceled after 1m39s
docs(readme): update readme and add zh_cn version
2026-11-01 20:43:59 +08:00

37 KiB
Raw Permalink Blame History

Nukumizu 后端

English | 简体中文

面向 Komari 的远程服务器监控与命令执行子系统。

Nukumizu 会连接 Komari 面板,在内存中维护所有被监控服务器的状态,并把告警推送到多个通知渠道。同时它还在 QQ(通过 NapCat 或其他 OneBot 11 实现)和 Telegram 上运行交互式机器人,让你可以直接在聊天里控制服务器。前端控制台(Vue 3)编译后内嵌进二进制,浏览器里也能完成同样的监控与配置。

功能

  • Komari 集成 — 登录 Komari 面板,启动时及之后每 5 分钟刷新节点列表,并通过 Komari WebSocket 每 5 秒拉取一次各节点最新状态。
  • 上游请求重试 — 登录与节点获取在遇到传输层错误、429 或 5xx 时最多重试 6 次,采用线性退避(0.5s × 第几次)。凭据错误、4xx 响应和 JSON-RPC 错误会立即失败,不会反复冲击面板。
  • 状态跟踪 — 线程安全的节点跟踪器保存每个服务器的最新报告与静态信息,并检测 Online / Offline 状态切换。启动后的第一份快照被视为基线,因此重启不会产生一堆误报的“离线”告警。
  • 单节点通知控制 — bot_node_config.json 可以单独关闭某个节点的状态推送,也可以把 Offline 通知暂缓一段可配置的时间:节点在窗口内恢复时,这条告警完全不会发出(抖动抑制)。
  • 远程命令执行 — 通过 Komari 任务接口下发命令,并轮询结果(每秒一次,最长 60 秒超时)。
  • 交互式机器人 — QQ(OneBot 11,例如 NapCat)与 Telegram 机器人,支持 /list、/status、/info、/run、/shutdown、/reboot 等命令,并带有管理员 / 可信群组的权限模型。
  • 通知渠道 — 状态变更会推送到所有已启用的渠道:QQ、Telegram、邮件(SMTP)、ntfy 和 Webhook。
  • 入站 Webhook API — 外部应用可以通过 POST /api/webhook/post/<name> 推送自己的告警,Nukumizu 会转发给该端点配置的渠道。每个端点有独立的 token 和目标渠道,且该 API 运行在独立监听端口上,可以只对外开放它而不暴露管理 API。
  • 网络代理 — 可配置全局代理地址,并在每个渠道上通过 networkUseProxy 单独开启;支持 HTTP、WebSocket,甚至 SMTP(通过 HTTP CONNECT 隧道)。
  • 可自定义的消息模板 — 所有机器人 / 通知消息都由 config.json 中的模板渲染,Markdown 格式按渠道单独开关。
  • 热重载 — 保存设置后绝大部分配置立即生效;少数只在启动时读取的键会通过 restartRequired 返回给调用方。
  • 存储 — user.db 与 log.db 使用 SQLite(纯 Go 驱动);采用回滚日志而非 WAL,避免在网络共享盘上损坏数据库。
  • 管理 API — 基于 token 的 REST API,以及仅管理员可用的实时日志 WebSocket。
  • Web 控制台 — Vue 3 管理界面,可浏览节点及其单节点选项、编辑设置、管理机器人信任与 Webhook 端点、实时查看日志。构建产物内嵌在二进制中,单个可执行文件即可同时提供 API 与控制台。
  • 测试 — 各 Go 包带有单元测试(go test ./...),覆盖重试、抖动抑制和设置合并等路径。

工作原理

  1. 启动时 Nukumizu 加载 config.json、bot_user_config.json 和(可选的)bot_node_config.json,然后登录 Komari 面板。登录遇到传输错误 / 429 / 5xx 时最多重试 6 次;仍然失败则进程退出。
  2. 获取节点列表(名称 → UUID、静态信息)并存入内存,随后连接 /api/rpc2 的 WebSocket,每 5 秒请求一次最新状态。连接断开后按指数退避重连(1s、2s、4s……最大 30s);连续 5 次失败会记录错误日志,并继续每 30 秒重试。
  3. 节点跟踪器会等待覆盖所有已知节点的首份快照后再启用通知(另有两分钟兜底),并且始终把第一份快照当作启动基线 —— 不会因为启动时的状态而产生告警。
  4. 真实的在线状态切换会通过 SERVER_STATUS_CHANGED 模板渲染,并发送给所有已启用的渠道。在此之前会先经过 bot_node_config.json 的单节点开关:enableStatusNotify 可以直接丢弃这次变更,flap_tolerant 会把 Offline 暂缓 N 秒 —— 节点在窗口内恢复时,Offline 和随后的 Online 都不会发送。
  5. 首次状态刷新完成后,QQ / Telegram 还会收到启动欢迎消息和初始服务器列表,并遵循每个成员的 event_bot_started 偏好。
  6. 每 12 小时重新登录一次 Komari;节点列表每 5 分钟刷新一次。

项目结构

nukumizu-backend/
├── main.go                       # 入口、启动流程、后台任务、优雅退出
├── router.go                     # HTTP 路由注册(主监听 + webhook 监听)
├── config/
│   ├── config.go                 # 加载配置文件、填充默认值、单节点选项读取
│   ├── variables.go              # 配置结构体 + 原子配置单例
│   ├── settings.go               # 设置文件读取/合并/写入、启动项检测
│   ├── webhook.go                # 入站 Webhook 端点增删改辅助函数
│   ├── reload.go                 # 重载钩子(日志级别、渠道重建)
│   └── *_test.go                 # 设置合并、并发与抖动抑制测试
├── global/
│   └── variables.go              # 构建元信息(名称/版本/开发者)+ 配置路径
├── handler/
│   ├── user.go                   # /api/user/login、/api/user/register
│   ├── server.go                 # /api/server/list、getInfo、getStatus、exec
│   ├── settings.go               # /api/settings/get、set
│   ├── webhook.go                # /api/webhook/post/{name}(入站 Webhook API)
│   ├── webhook_endpoints.go      # /api/webhook/add、modify、delete、list
│   └── health.go                 # /health
├── database/
│   └── user.go                   # user.db(SQLite)用户存储
├── utils/
│   ├── auth.go                   # Token 管理、鉴权中间件、JSON 响应
│   └── middleware.go             # 限流、CORS、安全响应头
├── postLog/                      # 日志子系统
│   ├── postLog.go                # 分级日志(标准输出 + 广播)
│   ├── database.go               # log.db(SQLite,每次运行一张表)
│   ├── logBroadcaster.go         # 向 WebSocket 客户端分发
│   └── logSocketHandler.go       # /api/system/getLogs WebSocket(仅管理员)
├── web/
│   ├── embed.go                  # 将构建好的控制台(web/dist)嵌入二进制
│   ├── dist/                     # Vite 构建输出 —— 生成物,已 gitignore
│   └── handler.go                # 静态文件服务 + SPA 回退
├── internal/
│   ├── komari/
│   │   ├── client.go             # Komari HTTP/JSON-RPC 客户端(登录、节点、任务执行/轮询)
│   │   └── ws.go                 # Komari 状态 WebSocket(轮询 + 重连)
│   ├── node/
│   │   └── tracker.go            # 线程安全节点状态、基线门控、状态切换
│   ├── netproxy/
│   │   └── netproxy.go           # 各渠道统一网络代理
│   ├── template/
│   │   └── template.go           # 消息模板渲染({{ 变量 }})
│   └── controller/
│       ├── controller.go         # Manager、接口、告警路由、抖动抑制
│       ├── trigger.go            # 命令解析、鉴权、路由
│       ├── processor.go          # 命令处理
│       ├── utils.go
│       └── pipes/
│           ├── email.go          # 邮件通知渠道
│           ├── ntfy.go           # ntfy 通知渠道
│           ├── webhook.go        # 出站 Webhook 通知渠道
│           ├── qq_onebot/
│           │   ├── qq.go         # QQ 渠道(OneBot 11,例如 NapCat)
│           │   └── onebot.go     # OneBot 11 WebSocket 客户端(事件 + API)
│           └── telegram/
│               ├── telegram.go   # Telegram 机器人(go-telegram/bot,长轮询)
│               └── send.go       # 消息发送 / 拆分(Telegram Markdown)
└── frontend/                     # Vue 3 管理控制台(Vite)
    ├── index.html
    ├── vite.config.js            # 开发服务器;把 /api 代理到后端
    └── src/
        ├── main.js               # 启动:主题 + 运行时开关
        ├── App.vue               # 根组件 + toast 容器
        ├── api/index.js          # REST 接口封装
        ├── router/index.js       # 路由与登录守卫
        ├── styles/               # theme.css + ui.css
        ├── utils/                # http/auth/theme/toast/format/runtime 辅助
        ├── components/           # Modal、Toggle、编辑器、ConfigSection、顶栏、侧栏
        └── views/                # Login、Layout、Overview、Trusted、Settings、WebHooks、Logs

环境要求

  • Go 1.27.3 或更高版本(见 go.mod)
  • Node.js 22 或更高版本 —— 仅在构建 Web 控制台时需要,预编译的二进制不需要
  • 一台可从本机访问的 Komari 面板
  • QQ:一个提供正向 WebSocket 端点的 OneBot 11 实现,例如 NapCat(/api 与 /event)
  • Telegram:来自 @BotFather 的 bot token

主要依赖:github.com/go-telegram/bot、github.com/gorilla/websocket、gopkg.in/mail.v2、modernc.org/sqlite。

配置

程序从工作目录读取三个 JSON 文件。没有命令行参数:文件名固定为 config.json、bot_user_config.json、bot_node_config.json,因此请在存放这些文件的目录下启动程序。

文件 是否必需 用途
config.json 是 核心配置:系统、调试开关、Komari、Webhook 监听、渠道、消息模板、存储路径
bot_user_config.json 是 各机器人的管理员 / 可信群组及其通知偏好
bot_node_config.json 否 单节点选项(通知开关、抖动抑制窗口)。首次获取节点时自动创建

三个文件都在 .gitignore 中,因为它们包含凭据(Komari 密码、bot token、代理认证)。请参考下面的示例,切勿提交真实密钥。bot_node_config.json 本身不含敏感信息,但也一并被忽略。

config.json

{
    "system": {
        "debugMode": true,
        "listenAddr": "0.0.0.0",
        "listenPort": "8080",
        "networkProxy": "http://127.0.0.1:7890"
    },
    "debug": {
        "showOneBotMsg": false,
        "showOneBotAction": false,
        "showTelegramMsg": false,
        "showTriggerCmdEcho": true,
        "showKomariTaskEcho": false,
        "oneBotIgnoreSelfMsg": false,
        "showFlapTolerantEcho": false
    },
    "komari": {
        "dashboardURL": "https://status.example.com",
        "account": {
            "username": "admin",
            "password": "CHANGE_ME"
        }
    },
    "webhook": {
        "enabled": false,
        "listenAddr": "0.0.0.0",
        "listenPort": "8081",
        "endpoints": {
            "example": {
                "enabled": true,
                "token": "CHANGE_ME",
                "notifyPipes": ["qq_onebot", "telegram", "email", "ntfy"]
            }
        }
    },
    "controllerMethod": {
        "qq_onebot": {
            "enabled": false,
            "markdown": false,
            "networkUseProxy": false,
            "oneBotAddr": "127.0.0.1",
            "oneBotPort": "3000",
            "oneBotToken": "",
            "botQQID": 0,
            "listenMethod": "global"
        },
        "telegram": {
            "enabled": false,
            "markdown": true,
            "networkUseProxy": false,
            "botToken": "",
            "listenMethod": "global"
        },
        "email": {
            "enabled": false,
            "markdown": false,
            "networkUseProxy": false,
            "smtpHost": "",
            "smtpPort": 587,
            "username": "",
            "password": "",
            "from": "",
            "to": [],
            "useTLS": true
        },
        "ntfy": {
            "enabled": false,
            "markdown": false,
            "networkUseProxy": false,
            "server": "https://ntfy.sh",
            "topic": "",
            "token": "",
            "priority": "default"
        },
        "webhook": {
            "enabled": false,
            "markdown": false,
            "networkUseProxy": false,
            "url": "",
            "method": "POST",
            "headers": {},
            "template": ""
        }
    },
    "controllerMessage": {
        "BOT_STARTED": "Nukumizu Alert Bot Started\nTime: {{ time }}\n- Software Version: {{ softwareVersion }}\n- Build Version: {{ softwareBuildVer }}\n- Commit Hash: {{ softwareCommitHash }}\n- Build Type: {{ softwareBuildType }}\n- Build Time: {{ softwareBuildTime }}\n- Developer: {{ softwareDeveloper }}",
        "BOT_HELP": "Nukumizu Alert Bot Ver. {{ softwareVersion }}.{{ softwareBuildVer }}.{{ softwareCommitHash }}\nCommand Lists:\n- /help: Show this help message\n- /list: List all servers and show their status\n- /status <UUID>: Show specific server status\n- /info <UUID>: Show specific server info\n- /run <UUID> <command>: Execute command on specific server. If you type \"all\" in <UUID>, you will run the command on all servers.\n- /shutdown <UUID>: Shutdown specific server.\n- /reboot <UUID>: Reboot specific server.\n- /getip <UUID>: Get specific server IP address.",
        "TG_BOT_START": "Welcome to use Nukumizu Alert Bot!\nUse `/help` to get command list.",
        "SERVER_STATUS_CHANGED": "Server Status Changed Alert\n{{ serverName }} - {{ upStatus }}\n- Event: {{ event }}\n- Server Name: {{ serverName }}\n- Message: {{ message }}\n- Time: {{ time }}",
        "SERVER_LIST": "All server list:\n- Online:\n{{ list.onlineServers }}\n- Offline:\n{{ list.offlineServers }}",
        "SERVER_EXECUTE_RESULT": "Command execute result:\n- Server ID: {{ serverName }}\n- Command: {{ command }}\n-----**Result**-----\n\n{{ result }}\n----------\n\n- Time: {{ time }}"
    },
    "dataPath": "./data",
    "dbPath": "./db"
}

字段说明:

  • controllerMethod 以渠道名为键:qq_onebot、telegram、email、ntfy、webhook。同样的名称也用于 notifyPipes 和 bot_user_config.json。QQ 渠道使用 OneBot 11 协议,oneBotAddr / oneBotPort / oneBotToken 指向 OneBot 实现(NapCat 默认在 127.0.0.1:3000)。
  • system.networkProxy 是全局代理地址。只有当渠道自身的 networkUseProxy 为 true 时才会使用它,作用于 Telegram HTTP 轮询、QQ OneBot WebSocket、ntfy 与 Webhook 请求,以及邮件 SMTP(通过 HTTP CONNECT 隧道)。
  • webhook 配置的是入站 Webhook API(见 入站 Webhook API);controllerMethod.webhook 配置的是出站 Webhook 通知渠道。两者互不相关。
  • markdown 是五个渠道各自独立的开关。为 false(默认)时所有渲染值都以纯文本插入;为 true 时,需要原样阅读的值(UUID、事件消息、命令、命令结果、告警来源与内容)会被包裹为 Markdown 行内代码 / 代码块。程序不会根据渠道名推断,因此渠道只会得到你明确要求的格式 —— 平台不支持 Markdown 时请关闭它。在 Telegram 上它同时决定 parse_mode:关闭 markdown 时不发送 parse_mode,含 * 或 _ 的文本会原样送达,而不会被 API 以“Markdown 格式错误”拒绝。
  • controllerMethod.qq_onebot.listenMethod / telegram.listenMethod —— 见 机器人响应模式。
  • debug 控制各模块的详细日志,仅在 system.debugMode 为 true 时生效:
    • showOneBotMsg / showOneBotAction —— 收到的 OneBot 消息 / 发出的 OneBot API 调用。
    • showTelegramMsg —— 收到的 Telegram 更新。
    • showTriggerCmdEcho —— 交给触发器解析后的命令。
    • showKomariTaskEcho —— Komari 任务的创建与轮询。
    • oneBotIgnoreSelfMsg —— 忽略 OneBot 回显的机器人自身消息。
    • showFlapTolerantEcho —— 抖动抑制的决策(暂缓 / 释放 / 补发)。
  • email.useTLS 仅为配置兼容而保留。
  • dbPath 默认为 ./db,user.db 与 log.db 会创建在其中。dataPath 默认为 ./data,目前尚未使用,预留给后续的文件存储。
  • 缺失的键会使用内置默认值(地址 0.0.0.0、端口 8080、Webhook API 0.0.0.0:8081、无 Webhook 端点、OneBot 127.0.0.1:3000、ntfy 服务器 https://ntfy.sh、Webhook 方法 POST 等)。消息模板也有内置默认值。markdown 默认为 false,需要 Telegram 格式时请像示例那样显式开启。

bot_user_config.json

管理员与可信群组按机器人渠道分别定义,并把成员 ID 映射到该成员的通知偏好:

{
    "qq_onebot": {
        "admins": {
            "123456789": {
                "event_status_notify": true,
                "event_bot_started": true,
                "event_reply": true
            }
        },
        "trustedGroups": {
            "987654321": {
                "event_status_notify": true,
                "event_bot_started": false,
                "event_reply": true
            }
        }
    },
    "telegram": {
        "admins": {
            "user_handle": {
                "event_status_notify": true,
                "event_bot_started": true,
                "event_reply": true
            }
        },
        "trustedGroups": {
            "-1001234567890": {
                "event_status_notify": true,
                "event_bot_started": false,
                "event_reply": true
            }
        }
    }
}
  • QQ:成员 ID 为 QQ 号;群 ID 为数字群号。
  • Telegram:成员 ID 可以是 @username(该用户给机器人发过消息后会解析为数字用户 ID)或数字用户 ID;群 ID 为数字聊天 ID(超级群为负数)。
  • 每个成员的选项:
    • event_status_notify —— 接收服务器 Online/Offline 推送。
    • event_bot_started —— 接收启动时的自动消息(欢迎语 + 初始服务器列表)。
    • event_reply —— 预留,用于关闭对该成员命令的自动回复。
  • 某渠道的管理员也会出现在机器人发送通知的所有可信群组 / 私聊上下文中。

bot_node_config.json

单节点选项,以 Komari 上报的节点 UUID 为键:

{
    "0f2a9c1e-1234-4a5b-8c9d-000000000001": {
        "enableStatusNotify": false,
        "flap_tolerant": 30
    },
    "7d3b8f20-5678-4c9d-9e0f-000000000002": {}
}
字段 默认值 含义
enableStatusNotify true 为 false 时该节点的状态变更不会推送 —— 聊天机器人和通知渠道都不会发送。省略该字段(或整个条目)即为开启。
flap_tolerant 0 Offline 通知的暂缓秒数(允许小数,上限 86400)。节点在窗口内恢复时,Offline 与 Online 都会被抑制;窗口结束时仍未恢复则补发 Offline。为 0 或省略时立即通知。

该文件由程序自动维护:每次获取节点(FetchNodes —— 启动、重连和每 5 分钟刷新)都会把新出现的 UUID 以 {} 形式加入,且从不覆盖已有条目,因此手写的选项不会被冲掉。文件缺失或为空也没问题 —— 所有节点使用上述默认值。可通过控制台的 Nodes 页面修改这两项,或调用 POST /api/settings/set?type=bot_node_config。

消息模板

controllerMessage 中的模板会在发送前渲染。可用变量如下(markdown: true 的渠道还会对需要原样阅读的值加 Markdown 包裹,见上文):

变量 含义
{{ time }} 服务器当前时间
{{ serverName }} 目标服务器名称
{{ serverUUID }} 目标服务器 UUID
{{ upStatus }} Online / Offline
{{ event }} 状态变更事件(Online / Offline)
{{ message }} 状态事件附带的消息
{{ command }} 被执行的命令
{{ result }} 命令执行结果
{{ list.onlineServers }} 格式化后的在线服务器列表(- 名称 (uuid))
{{ list.offlineServers }} 格式化后的离线服务器列表
{{ softwareVersion }}、{{ softwareBuildVer }}、{{ softwareCommitHash }}、{{ softwareBuildType }}、{{ softwareBuildTime }}、{{ softwareDeveloper }}、{{ softwareDescription }} 构建元信息(commit hash 与构建时间在编译期注入)

无需重启即可生效的配置

保存设置后,大部分配置立即生效:

配置 生效方式
controllerMethod(全部五个渠道) 所有渠道会被重建:停止正在运行的控制器,再按新设置构建一套。持有连接的渠道会重新连接 —— Telegram 重新执行 getMe 握手,OneBot 重新建立 WebSocket —— 因此切换期间的推送会丢失。仅当该配置节确实发生变化时才重建;只保存消息模板不会影响渠道。
controllerMessage、debug、bot_user_config.json、bot_node_config.json、webhook.endpoints 使用时即时读取,不会重启任何东西。若在 Offline 暂缓期间修改通知开关或抖动窗口,补发时会重新读取。
system.debugMode 同时影响行为与日志过滤。
system.networkProxy 每次请求、每次拨号都会重新读取,因此新地址对已在运行的渠道也生效。只有渠道级的 networkUseProxy 在构建渠道时固定,切换它仍需上面的重建。
system.listenAddr / listenPort、webhook.enabled / listenAddr / listenPort、komari.dashboardURL、dataPath、dbPath 仅在启动时生效。 保存会写入文件并更新内存配置,但不会改变正在运行的监听、数据库或 Komari 客户端 —— 需要重启。/api/settings/set 会通过 restartRequired 返回这些键。

API

成功响应统一为 {"success": true, "message": "...", "data": {...}},负载放在单个 data 键下。错误响应为 {"success": false, "message": "..."}。成功且无额外信息时 message 可以省略。

鉴权

请求通过 HTTP 头鉴权:

请求头 含义
X-Token 登录/注册返回的 token。仅保存在内存中(重启后失效)。
X-Timestamp Unix 时间戳(秒);与服务器时间相差超过 ±30 分钟会被拒绝。当 system.debugMode 为 true 时完全跳过检查。

空闲超过 1 小时的 token 会失效(每 10 分钟清理一次);任何已鉴权的调用都会刷新计时。权限级别:None、bot、admin。要求 bot 的接口接受 bot 与 admin 两种 token。目前注册/登录一律签发 admin 级别 token。

浏览器的 WebSocket 握手无法携带自定义请求头,因此 /api/system/getLogs 也接受 ?token= 与 ?timestamp= 查询参数(两者都存在时请求头优先)。该接口仅接受 admin token;时间戳只在握手时检查一次,已建立的连接不受后续容差窗口影响。查询串可能进入代理与访问日志,带 token 的 WebSocket URL 应视为机密。

接口列表

接口 方法 权限 说明
/api/user/login POST None 登录。请求体 {username, password}。返回 data: {token, userID, username, level, registerDate}。
/api/user/register POST None 注册首个用户。请求体 {username, password}。仅在尚无用户时允许,否则 403。返回 data: {token, userID, username, level}。
/api/server/list GET bot / admin 以渲染后的文本列出所有被监控服务器(data.list,与机器人 /list 输出一致)。
/api/server/getInfo GET admin 服务器静态信息(对应机器人 /info)。查询 ?uuid=<uuid>(或 all)。返回 data: {<uuid>: {uuid, name, info}},每个请求的服务器一项。单个未知 uuid 返回 404。
/api/server/getStatus GET admin 服务器实时状态(对应机器人 /status)。查询 ?uuid=<uuid>(或 all)。返回 data: {<uuid>: {uuid, name, online, report}};节点尚未上报时 report 为 null。单个未知 uuid 返回 404。
/api/server/exec POST bot / admin 执行命令。请求体 {uuid: [<uuid>...], command}。下发 Komari 任务并轮询至完成(或超时)。返回 data: {taskID, results}。
/api/settings/get GET admin ?type=global|bot_user_config|bot_node_config —— 返回 data: {config},即对应配置文件的内容(布局与文件一致)。
/api/settings/set POST admin ?type=<同上> + 部分更新的 JSON 请求体,例如 {"system":{"debugMode":true}} —— 深度合并到对应文件、写盘并在内存中重载。只改动给出的键;数组整体替换,null 删除该键。返回 data: {type, restartRequired},其中 restartRequired 列出本次改动中只在启动时读取的键(见 无需重启即可生效的配置)—— 写入始终成功,这个字段只表示哪些修改尚未生效。始终为数组,全部生效时为空。
/api/webhook/add POST admin 新增入站 Webhook 端点。请求体 {name, enabled?, token?, notifyPipes?} —— 只保存给出的字段,其余使用默认值。名称已存在时返回 409。
/api/webhook/modify POST admin 修改已有端点。请求体 {name, ...} —— 给出哪些字段就改哪些(规则同 /api/settings/set,但限定在单个端点)。名称不存在返回 404,未给出其他字段返回 400。
/api/webhook/delete POST admin 删除端点。请求体 {name}。名称不存在返回 404。
/api/webhook/list GET admin 所有已配置的入站 Webhook 端点,以名称为键,位于 data.endpoints。
/health GET None 健康检查。返回 data: {status, database}。
/api/system/getLogs WebSocket admin 推送日志。先发送最近 100 条缓冲,再实时推送 {level, content, timestamp}。凭据可通过 X-Token/X-Timestamp 请求头或 ?token=/?timestamp= 查询参数提供;校验失败会返回 JSON 错误且不升级连接。

作用于整个服务的中间件:

  • 限流 —— 令牌桶,每个客户端 IP 每分钟 100 次请求(超出返回 429)。
  • CORS —— Access-Control-Allow-Origin: *,允许 Content-Type、X-Token、X-Timestamp、Authorization。
  • 安全响应头 —— X-XSS-Protection、X-Content-Type-Options: nosniff、X-Frame-Options: DENY、Referrer-Policy 以及严格的 CSP。
  • WebSocket 鉴权 —— 日志端点通过路由级中间件要求管理员 token,未授权客户端在升级前即被拒绝。

入站 Webhook API

它使用独立监听端口,外部应用可以只访问它而接触不到管理 API。通过 webhook.enabled 开启,监听 webhook.listenAddr:webhook.listenPort(默认 0.0.0.0:8081);这部分配置在启动时生效,而 webhook.endpoints 会在配置重载时重新读取。这里只应用限流与 CORS 中间件,不涉及会话 token。

控制台的 WebHooks 页面负责监听设置与端点管理。出站 Webhook 通知渠道(controllerMethod.webhook)仍位于设置页,与其他通知渠道放在一起,因为它本身就是其中一个渠道。

接口 方法 权限 说明
/api/webhook/post/<name> POST 端点 token 把告警转发给该端点 notifyPipes 中列出的渠道。请求体 {token, subject, content}。返回 data: {endpoint, channels}。

webhook.endpoints 下的每个条目就是一个端点,以键名作为 URL 最后一段:键 example 对应 POST /api/webhook/post/example。post/ 这一段把端点命名空间与管理路由(/api/webhook/add 等,位于管理监听)区分开。端点通过管理 API 增删改,写入的正是 config.json 的 webhook.endpoints 部分;新增端点在配置重载后即可接收请求,无需重启。端点包含:

字段 含义
enabled 是否接受请求。禁用时返回 403。
token 调用方在请求体 token 字段中提供的共享密钥,使用常量时间比较。未配置 token 的端点返回 500,而不是对任何人开放。
notifyPipes 告警投递的渠道,命名与 controllerMethod 一致:qq_onebot、telegram、email、ntfy、webhook。未知或未启用的渠道会被跳过并上报。

管理 API 只接受上述三个字段。出现其他字段,或字段类型错误(enabled 必须是布尔值、token 必须是字符串、notifyPipes 必须是字符串数组)时返回 400,不会写入 config.json —— 程序不认识的字段不应出现在文件里。name 不能为空且不能包含 /,因为它会成为端点 URL 的最后一段。请求体上限为 1 MiB。

告警按渠道渲染为:

{{ subject }}
- Source: {{ source }}
- Content:
{{ content }}

- Time: {{ time }}
Sent by Nukumizu Alert System

{{ source }} 是端点名称,接收方据此判断是哪个应用触发的告警。markdown: true 的渠道会把 source 包裹为行内代码、content 包裹为代码块;{{ subject }} 与 {{ time }} 保持纯文本。

状态码:200 已投递,400 请求体格式错误或 subject/content 为空,401 token 错误,403 端点已禁用,404 端点名称不存在,405 非 POST 请求,500 端点未配置 token,502 没有任何渠道接受该告警。

机器人

QQ(OneBot 11)与 Telegram 机器人共用同一套命令引擎与鉴权流程,实现在 internal/controller/。QQ 渠道通过 WebSocket 使用 OneBot 11(事件流 + API),因此兼容 NapCat 及同类实现;Telegram 使用 go-telegram/bot 长轮询。

权限模型

  • 可信群组:群内发出的命令只有在该群位于 trustedGroups 中时才会得到响应,其他群的消息会被忽略。
  • 管理命令:/shutdown、/reboot 和 /run 还要求发送者位于 admins 中。
  • 私聊:非管理命令(/help、/list、/status、/info、/getip)对任何私聊发送者都响应;管理命令仍要求管理员。

机器人响应模式

  • global —— 机器人监听可信群组中的所有消息,无需被 @ 即可响应已知命令;未知的 / 命令会被静默忽略。
  • at —— 机器人只在自己被提及时响应(QQ @、Telegram @botname)。此模式下未知命令会回复 Unknown command: /…。

命令

命令 权限 说明
/help 所有人 显示帮助消息(BOT_HELP 模板)。
/list 所有人 列出所有服务器及其在线状态(SERVER_LIST 模板)。
/status <uuid> 所有人 服务器实时报告(CPU、内存、磁盘、网络、运行时长、进程)。
/info <uuid> 所有人 服务器静态信息(系统、内核、CPU、内存、交换、磁盘、计费、标签)。
/getip <uuid> 所有人 服务器的 IPv4 / IPv6 地址。
/shutdown <uuid> 管理员 通过 Komari 任务接口关机。
/reboot <uuid> 管理员 通过 Komari 任务接口重启。
/run <uuid|all> <command> 管理员 在单台(all 表示全部)服务器上执行命令并回报结果(SERVER_EXECUTE_RESULT 模板)。
/start 所有人 仅 Telegram —— 发送 TG_BOT_START 欢迎消息。

通知渠道

QQ 与 Telegram 是交互式渠道。邮件、ntfy 与 Webhook 是仅状态渠道 —— 它们只接收状态变更告警,不能执行命令。启动时,欢迎消息与初始服务器列表只发送给机器人渠道(QQ / Telegram),并遵循每个成员的 event_bot_started 偏好。

五个渠道也都能承载外部应用通过入站 Webhook API提交的告警。机器人渠道会发给该渠道配置的群组与管理员;仅状态渠道会发给各自的目的地(收件人、ntfy topic、出站 Webhook URL)。Markdown 格式由各渠道的 markdown 设置决定,与渠道名称无关。

状态变更通知会先经过 bot_node_config.json 的单节点开关(enableStatusNotify、flap_tolerant),因此聊天机器人与仅状态渠道看到的是同一份过滤后的数据流。

构建

需要 Go 1.27.3+,构建 Web 控制台时还需要 Node.js 22+。

仓库根目录的辅助脚本会先构建 Vue 控制台,再把后端与它一起编译;同时通过 -ldflags 把当前 git commit 与构建时间写进二进制。脚本名表示目标平台,每个目标都有 Windows(.bat)和 Linux/macOS(.sh)两种版本:请在当前宿主机上运行对应版本,因为所有脚本都会交叉编译到各自的目标平台。

脚本 产物
build-linux-x86_64.sh / build-linux-x86_64.bat nukumizu-linux-amd64
build-win-x86_64.sh / build-win-x86_64.bat nukumizu-windows-amd64.exe
# Linux / macOS
./build-linux-x86_64.sh
./build-win-x86_64.sh

# Windows
build-linux-x86_64.bat
build-win-x86_64.bat

控制台是内嵌在二进制中的。Vite 将其输出到 web/dist,web/embed.go 通过 go:embed 把它编译进去,因此可执行文件可以独立提供完整前端 —— 复制到任何位置,即使旁边没有 frontend/ 或 web/dist,访问 / 仍会返回控制台。构建脚本会在 frontend/node_modules 缺失时执行 npm ci,并在每次运行时执行 npm run build,因此构建机需要 Node.js 22+(运行二进制的机器不需要)。

也正因如此,编译后端前必须至少构建过一次控制台:web/dist 是生成目录且被 gitignore,在它存在之前 go build 会报 pattern all:dist: no matching files found。任何 build-* 脚本都会替你处理好顺序。

等价的手动构建:

# 1. 控制台(前端改动后执行一次)
cd frontend && npm ci && npm run build && cd ..

# 2. 后端
# Linux / macOS
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
  go build -ldflags "-X main.CommitHash=$(git rev-parse --short HEAD) -X main.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  -o nukumizu-linux-amd64 .

# Windows
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 \
  go build -ldflags "-X main.CommitHash=<commit> -X main.BuildTime=<utc-time>" \
  -o nukumizu-windows-amd64.exe .

main.CommitHash 与 main.BuildTime 会出现在日志和 BOT_STARTED 消息中。

CI(.github/workflows/build.yml)会运行两种版本 —— .sh 在 ubuntu-latest,.bat 在 windows-latest —— 并把两个自包含的二进制作为产物上传。

运行

三个配置文件都从工作目录读取,没有路径参数:

# 开发运行(go run,因此配置文件必须在当前目录)
./run.sh          # Linux / macOS
run.bat           # Windows

# 或先构建,再运行二进制
./nukumizu-linux-amd64

run.sh / run.bat 只运行 Go 后端,不会构建控制台,因此 web/dist 必须已存在(执行过一次任意 build-* 脚本,或在 frontend/ 中执行 npm run build),否则编译会失败。

启动时程序会登录 Komari、加载节点状态、连接状态 WebSocket,然后启动各已启用渠道以及 listenAddr:listenPort 上的 HTTP 服务。按 Ctrl+C 优雅退出。当 webhook.enabled 为 true 时,入站 Webhook 监听会单独启动;绑定失败只会记录日志,不会影响程序其余部分。

前端开发

开发前端时,请分别运行前后端:

# 终端 1 —— 后端(API + WebSocket)监听 :8080
./run.sh

# 终端 2 —— Vite 开发服务器,热更新,监听 :5173
cd frontend
npm install
npm run dev

打开 http://localhost:5173。开发服务器会把 /api(包括日志 WebSocket)代理到 http://127.0.0.1:8080;若后端监听在别处,用 NUKUMIZU_API 指向它:

NUKUMIZU_API=http://192.168.1.10:8080 npm run dev

生产构建时,Go 服务会在正常监听地址上直接提供内嵌的控制台 —— 见构建。运行时与 Vite 无关,因此单独执行 npm run build 不会改变已运行二进制的内容:需要重新构建二进制才能生效。

测试

go test ./...

测试覆盖 config、handler、节点跟踪器、控制器与各管道包,不需要 Komari 实例或网络访问。

更新日志

每个版本的发布说明及对应 commit 见 changeLogs.md。

许可证

见 LICENSE。