feat: initial commit for HookPilot
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
# 宿主机部署说明
|
||||
|
||||
HookPilot 直接部署在 Linux 宿主机上,由 systemd 管理,通过 HTTPS 反向代理对外提供服务。脚本以服务账户身份运行,可以操作该账户有权访问的文件和命令。
|
||||
|
||||
## 1. 环境要求
|
||||
|
||||
- Linux(systemd),x86_64 或 arm64
|
||||
- Node.js **22.13 及以上**(使用内置的 `node:sqlite`,无需编译原生模块)
|
||||
- Bash
|
||||
- Nginx 或 Caddy(提供 HTTPS)
|
||||
|
||||
```bash
|
||||
node -v # 应输出 v22.13.0 或更高
|
||||
```
|
||||
|
||||
## 2. 构建
|
||||
|
||||
可以在服务器上直接构建,也可以在其他机器构建后复制整个目录。
|
||||
|
||||
```bash
|
||||
git clone <仓库地址> /opt/hookpilot # 或将项目目录上传到 /opt/hookpilot
|
||||
cd /opt/hookpilot
|
||||
npm ci
|
||||
npm run build # 构建前端(web/dist)与后端(server/dist)
|
||||
npm prune --omit=dev # 可选:移除开发依赖
|
||||
```
|
||||
|
||||
## 3. 创建服务账户与目录
|
||||
|
||||
默认使用专用账户 `hookpilot`,数据目录 `/var/lib/hookpilot`:
|
||||
|
||||
```bash
|
||||
sudo useradd --system --home-dir /var/lib/hookpilot --create-home --shell /usr/sbin/nologin hookpilot
|
||||
sudo chmod 700 /var/lib/hookpilot
|
||||
sudo chown -R root:root /opt/hookpilot # 程序目录只读,服务账户无需写权限
|
||||
```
|
||||
|
||||
数据目录结构(服务启动时自动创建):
|
||||
|
||||
| 路径 | 内容 |
|
||||
|---|---|
|
||||
| `hookpilot.db` | SQLite 数据库:管理员、会话、接口、脚本版本、任务与输出、系统设置 |
|
||||
| `scripts/<接口ID>/v<版本>.sh` | 各脚本版本的文件,执行时由数据库内容校验并重写 |
|
||||
| `runs/<任务ID>/request.json` | 任务执行期间的请求文件(`HOOK_REQUEST_FILE`),结束后删除 |
|
||||
| `workspace/` | 未配置工作目录时的默认工作目录 |
|
||||
|
||||
脚本需要访问的其他目录,请用常规的文件权限授予 `hookpilot` 账户,例如:
|
||||
|
||||
```bash
|
||||
sudo chown -R hookpilot:hookpilot /srv/my-app
|
||||
```
|
||||
|
||||
## 4. 配置
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /etc/hookpilot
|
||||
sudo cp deploy/hookpilot.env.example /etc/hookpilot/hookpilot.env
|
||||
sudo chmod 600 /etc/hookpilot/hookpilot.env
|
||||
sudo nano /etc/hookpilot/hookpilot.env
|
||||
```
|
||||
|
||||
建议设置 `HOOKPILOT_ADMIN_PASSWORD` 作为初始密码(只在首次启动、数据库中还没有管理员时生效)。未设置时会随机生成,并打印在服务日志中。
|
||||
|
||||
## 5. 安装 systemd 服务
|
||||
|
||||
```bash
|
||||
sudo cp deploy/hookpilot.service /etc/systemd/system/hookpilot.service
|
||||
# 如果 node 不在 /usr/bin/node,请修改 ExecStart(可用 `which node` 查看)
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now hookpilot
|
||||
sudo systemctl status hookpilot
|
||||
journalctl -u hookpilot -f # 查看日志,未设置初始密码时在这里找到随机密码
|
||||
```
|
||||
|
||||
服务停止或重启时:
|
||||
|
||||
- 运行中的任务会被终止(整个进程组),状态记为“中断”,**不会自动重跑**;
|
||||
- 排队中的任务保留在数据库中,服务启动后按原顺序继续执行;
|
||||
- 服务异常退出(如被 `kill -9`、断电)后再启动,上次运行中的任务同样标记为“中断”。
|
||||
|
||||
## 6. 配置 HTTPS 反向代理
|
||||
|
||||
服务默认只监听 `127.0.0.1:8787`,由反向代理提供统一域名和 HTTPS。
|
||||
|
||||
- Nginx:参考 [`deploy/nginx.conf.example`](../deploy/nginx.conf.example)
|
||||
- Caddy:参考 [`deploy/Caddyfile.example`](../deploy/Caddyfile.example)(自动申请证书)
|
||||
|
||||
注意事项:
|
||||
|
||||
1. **同步模式**的接口会等脚本执行完才响应,代理的读取超时要大于最长脚本超时加排队时间。
|
||||
2. 代理的请求体上限应不小于 `HOOKPILOT_BODY_LIMIT`(默认 1 MiB)。
|
||||
3. 代理与服务不在同一台机器时,把 `HOOKPILOT_TRUST_PROXY` 设置为代理的 IP,否则执行记录中的请求来源会显示为代理地址。
|
||||
4. 使用 HTTPS 访问时登录 Cookie 自动带 `Secure` 标记(`HOOKPILOT_COOKIE_SECURE=auto`)。
|
||||
|
||||
## 7. 按命令授予提权
|
||||
|
||||
脚本默认只有 `hookpilot` 账户的权限。确实需要重启系统服务或修改受保护文件时,由部署者在 sudoers 中**按具体命令**授权,不要授予全部权限:
|
||||
|
||||
```bash
|
||||
sudo visudo -f /etc/sudoers.d/hookpilot
|
||||
```
|
||||
|
||||
```text
|
||||
# 只允许无密码执行这两条命令
|
||||
hookpilot ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx
|
||||
hookpilot ALL=(root) NOPASSWD: /usr/bin/systemctl reload caddy
|
||||
```
|
||||
|
||||
脚本中使用 `sudo -n`(不交互询问密码,未授权时立即失败):
|
||||
|
||||
```bash
|
||||
sudo -n /usr/bin/systemctl restart nginx
|
||||
```
|
||||
|
||||
平台页面不提供切换系统用户的功能。启用提权后请保持 systemd 单元中 `NoNewPrivileges` 处于关闭状态。
|
||||
|
||||
## 8. 安全要点
|
||||
|
||||
- 管理端使用登录会话,Webhook 使用每个接口独立的 Token,两种凭证互不通用;Token 只保存哈希。
|
||||
- 执行子进程使用显式构造的环境(`PATH`、`HOME`、`USER`、`LANG` 与 `HOOK_` 变量),不继承服务的环境变量与凭证。
|
||||
- 请求数据只通过环境变量和请求文件传给脚本,不会替换进脚本文本或拼接成 Shell 命令。脚本中请始终用双引号引用变量(`"$HOOK_IP"`)。
|
||||
- 平台面向可信管理员:任何能登录管理界面的人都能以服务账户身份执行任意命令,请使用强密码并限制管理界面的访问来源(如在代理层对 `/` 做 IP 白名单、仅放行 `/hooks/`)。
|
||||
- 连续登录失败 10 次后,该来源 IP 锁定 15 分钟。
|
||||
|
||||
## 9. 升级、备份与维护
|
||||
|
||||
**升级:**
|
||||
|
||||
```bash
|
||||
cd /opt/hookpilot
|
||||
git pull # 或替换为新版本文件
|
||||
npm ci && npm run build
|
||||
sudo systemctl restart hookpilot
|
||||
```
|
||||
|
||||
数据库结构变更会在启动时自动迁移。
|
||||
|
||||
**备份:** 停止服务后复制整个数据目录;或在运行中使用 SQLite 在线备份:
|
||||
|
||||
```bash
|
||||
sudo -u hookpilot sqlite3 /var/lib/hookpilot/hookpilot.db ".backup '/var/backups/hookpilot.db'"
|
||||
```
|
||||
|
||||
**忘记管理员密码:**
|
||||
|
||||
```bash
|
||||
cd /opt/hookpilot
|
||||
sudo -u hookpilot env HOOKPILOT_DATA_DIR=/var/lib/hookpilot node server/dist/cli.js reset-password
|
||||
# 或指定新密码:
|
||||
sudo -u hookpilot env HOOKPILOT_DATA_DIR=/var/lib/hookpilot HOOKPILOT_NEW_PASSWORD='新密码' node server/dist/cli.js reset-password
|
||||
```
|
||||
|
||||
重置后所有登录会话失效。
|
||||
|
||||
**日志清理:** 超过“日志保留天数”(默认 30 天)的已结束执行记录及其输出每小时自动清理一次,可在“系统设置”中调整。
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
# Lucky 对接示例:DDNS 更新后同步家庭 IP
|
||||
|
||||
本示例演示设计文档中的验收场景:Lucky 检测到公网 IP 变化后调用 HookPilot,脚本把新 IP 写入指定文件。你可以在此基础上改为更新防火墙白名单、重写 Nginx 配置等。
|
||||
|
||||
## 1. 在 HookPilot 中创建接口
|
||||
|
||||
进入“接口管理 → 新建接口”:
|
||||
|
||||
**基本配置**
|
||||
|
||||
| 配置项 | 值 |
|
||||
|---|---|
|
||||
| 接口名称 | 更新家庭 IP |
|
||||
| 接口标识 | `home-ip`(调用地址为 `/hooks/home-ip`) |
|
||||
| 请求方式 | POST |
|
||||
|
||||
**参数**
|
||||
|
||||
| 环境变量 | 来源 | 字段 | 类型 | 必填 |
|
||||
|---|---|---|---|---|
|
||||
| `HOOK_IP` | JSON 请求体 | `ip` | IP 地址 | ✓ |
|
||||
|
||||
**脚本**:在“插入示例模板”中选择“更新家庭 IP”,按需修改 `TARGET_FILE`:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
TARGET_FILE="/var/lib/hookpilot/workspace/home-ip.txt"
|
||||
|
||||
old_ip=""
|
||||
if [[ -f "$TARGET_FILE" ]]; then
|
||||
old_ip="$(cat "$TARGET_FILE")"
|
||||
fi
|
||||
|
||||
if [[ "$old_ip" == "$HOOK_IP" ]]; then
|
||||
echo "IP 未变化:$HOOK_IP"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
printf '%s\n' "$HOOK_IP" > "$TARGET_FILE.tmp"
|
||||
mv "$TARGET_FILE.tmp" "$TARGET_FILE"
|
||||
echo "IP 已更新:${old_ip:-无} -> $HOOK_IP"
|
||||
```
|
||||
|
||||
**执行策略**
|
||||
|
||||
| 配置项 | 值 | 说明 |
|
||||
|---|---|---|
|
||||
| 响应模式 | 同步 | Lucky 可以根据响应判断脚本是否成功 |
|
||||
| 超时时间 | 30 秒 | |
|
||||
| 接口并发数 | 1 | 连续多次调用按接收顺序执行 |
|
||||
|
||||
点击“创建接口”,**立即复制弹窗中的 Token**(只显示一次)。
|
||||
|
||||
创建后切到“测试”标签,点击“按参数生成示例”→“预览参数映射”确认映射正确,再点击“执行(真实运行)”验证脚本。
|
||||
|
||||
## 2. 在 Lucky 中配置 Webhook
|
||||
|
||||
在 Lucky 的“动态域名”任务中打开 Webhook 设置(不同版本界面位置和名称可能略有差异,以实际界面为准):
|
||||
|
||||
| Lucky 配置项 | 值 |
|
||||
|---|---|
|
||||
| 接口地址 | `https://hooks.example.com/hooks/home-ip` |
|
||||
| 请求方法 | POST |
|
||||
| 请求头 | `Authorization: Bearer hp_xxxxxxxx`<br>`Content-Type: application/json` |
|
||||
| 请求主体 | `{"ip":"#{ipAddr}"}` |
|
||||
| 接口调用成功包含的字符串 | `"ok":true` |
|
||||
|
||||
- `#{ipAddr}` 是 Lucky 提供的“当前 IP”变量,具体变量名请以你所用 Lucky 版本 Webhook 设置页的提示为准。
|
||||
- 如果 Lucky 版本不支持自定义请求头,可以把 Token 放在地址中:`https://hooks.example.com/hooks/home-ip?token=hp_xxxxxxxx`。这种方式的 Token 可能出现在代理访问日志中,示例 Nginx 配置已去掉日志中的 query。
|
||||
|
||||
## 3. 响应说明
|
||||
|
||||
同步模式下 HookPilot 的响应:
|
||||
|
||||
| 情况 | HTTP 状态 | 响应体示例 |
|
||||
|---|---|---|
|
||||
| 脚本成功(退出码 0) | 200 | `{"ok":true,"executionId":12,"status":"success","exitCode":0,"durationMs":25}` |
|
||||
| 脚本失败(非零退出码) | 500 | `{"ok":false,"executionId":13,"status":"failed","exitCode":1,...}` |
|
||||
| 脚本超时 | 504 | `{"ok":false,...,"status":"timeout"}` |
|
||||
| 被手动终止 / 取消 | 409 | `{"ok":false,...,"status":"killed"}` |
|
||||
| Token 错误 | 401 | `{"ok":false,"error":"unauthorized","message":"Token 无效"}` |
|
||||
| 参数缺失或 IP 非法 | 400 | `{"ok":false,"error":"invalid_params","details":["HOOK_IP:不是合法的 IP 地址"]}` |
|
||||
| 接口已停用 | 403 | `{"ok":false,"error":"disabled",...}` |
|
||||
| 请求方式不符 | 405 | `{"ok":false,"error":"method_not_allowed",...}` |
|
||||
|
||||
Token 错误、参数校验失败等情况**不会执行脚本**,也不会生成执行记录。脚本输出只在管理界面查看,不返回给 Lucky。
|
||||
|
||||
异步模式下只要任务入队就返回 `202 {"ok":true,"executionId":12,"status":"queued"}`,表示已接收,不代表脚本执行成功。
|
||||
|
||||
## 4. 用 curl 手动验证
|
||||
|
||||
```bash
|
||||
curl -i -X POST 'https://hooks.example.com/hooks/home-ip' \
|
||||
-H 'Authorization: Bearer hp_xxxxxxxx' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"ip":"203.0.113.10"}'
|
||||
```
|
||||
|
||||
然后在“执行记录”中查看本次执行的参数、脚本版本、stdout / stderr 与退出码。
|
||||
Reference in New Issue
Block a user