
玲珑机括上手教程:从安装到用 Gogs 自动构建 Docker 镜像
> 玲珑机括(linglong-webhook)是一个轻量级 Webhook 管理平台,兼容 Gogs / Gitea / GitHub / GitLab。
> 本文带你从零开始:在懒猫微服上安装 → 配置一个测试仓库 → 写一个脚本拉取代码并用 Docker 编译 →
> 创建 Webhook 把它和 Gogs 推送事件连起来,最终实现「推一下代码就自动构建」。
整个过程大约 20 分钟。
---
## 你需要准备什么
- 一台懒猫微服设备
- 跑在懒猫上的一个 Gogs 实例(本文以 Gogs 为例,Gitea 同理)
---
## 第一步:从懒猫商店安装
在懒猫微服的「应用商店」中搜索 **玲珑机括**,点击安装。安装完成后打开应用。
https://appstore.lazycat.cloud/#/shop/detail/ink.akawa.ety001.webhook
---
## 第二步:保存你的 API Key(只显示一次!)
第一次打开应用时,你会看到一个初始化页面,系统会自动生成一个 **API Key**。
> ⚠️ **这个 Key 只显示这一次,请务必立即保存!**
> 页面上会有醒目的黄色提示。一旦关闭页面,明文就再也看不到了。
> 如果日后丢失,只能手动清空数据库里的 `api_key_hash` 记录让程序重新初始化。

点击「复制」把它存到你的密码管理器里,然后点 **「我已保存,进入系统」**。
进入系统后会看到主面板。之后每次重新打开应用,都会要求你输入这个 API Key 登录。
---
## 第三步:在文件管理中准备目录
进入左侧菜单的 **「文件管理」**。这里的根目录是 `/data/workspace`,
它是持久化目录(懒猫上对应宿主机 `/lzcapp/var/workspace`),容器重建后文件不会丢。
你的脚本、克隆下来的代码都应该放在这里。
点击右上角 **「新建文件夹」**,输入 `test`,回车。
现在你的文件树应该是这样:
```
/data/workspace/
└── test/
```
> web 界面没有做删除和修改功能,如果需要可以去懒猫网盘=>应用数据修改
---
## 第四步:在 Gogs 上准备测试仓库
### 4.1 创建仓库
在 Gogs 上新建一个仓库,命名为 `test`(可以私有)。初始化时勾选生成 README,方便克隆。
### 4.2 准备克隆地址
我们走 git 协议克隆,地址形如:
```
git@gogs.xxx.heiyu.space:ety001/test.git
```
#### 4.2.1 准备密钥
本机生成一对密钥(若还没有):
```bash
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N ""
```
在玲珑机括的文件管理里,于 `/data/workspace` 下新建 `sshkey` 目录,里面新建文件 `id_ed25519`,
把**私钥**完整内容粘贴进去并保存。

再把**公钥**(`id_ed25519.pub`)添加到 Gogs:
头像 → **设置** → **SSH 密钥** → 添加。

### 4.3 添加一个简单的 Dockerfile
在 `test` 仓库根目录创建 `Dockerfile`,内容如下:
```dockerfile
FROM alpine:3.20
# 这两个 ARG 接收脚本在 docker build 时通过 --build-arg 传入的值
ARG APP_VERSION=unknown
ARG BUILD_ENV=unknown
# 写进环境变量,方便容器运行时验证它们确实传进来了
ENV APP_VERSION=${APP_VERSION}
ENV BUILD_ENV=${BUILD_ENV}
# 启动时打印这两个值,用来证明「从 webhook 注入的环境变量」成功传到了镜像里
CMD ["sh", "-c", "echo \"APP_VERSION=$APP_VERSION BUILD_ENV=$BUILD_ENV\""]
```
提交并推送。这个 Dockerfile 的作用很单纯:把构建时传入的参数打印出来,
好让我们一眼确认「环境变量是不是真的从 webhook 一路传到了镜像内部」。

---
## 第五步:编写构建脚本
回到玲珑机括的文件管理,进入 `test` 目录,**新建文件** `test.sh`,填入下面内容。
> 把脚本里的 `REPO_URL` 换成你自己的 Gogs 克隆地址(见 4.2)。
```bash
#!/bin/bash
set -euo pipefail
# ============================================
# 测试构建脚本(子容器方案)
# 被 webhook 触发,通过子容器完成 Git 拉取和 Docker 构建
# 环境变量 APP_VERSION / BUILD_ENV 由 webhook pass_environment 注入
# ============================================
WORKSPACE_DIR="/data/workspace"
TEST_DIR="${WORKSPACE_DIR}/test"
SSH_KEY_DIR="${WORKSPACE_DIR}/sshkey"
REPO_DIR="${TEST_DIR}/repo"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
log "========== 开始构建 =========="
# 验证 SSH 密钥存在
if [ ! -f "${SSH_KEY_DIR}/id_ed25519" ]; then
log "ERROR: SSH 密钥不存在: ${SSH_KEY_DIR}/id_ed25519"
exit 1
fi
# ---- 1. 通过子容器拉取最新代码 ----
# 关键点:
# --network host —— 玲珑跑在懒猫容器里,子容器默认访问不到内网 Gogs,必须用 host 网络
# -v .ssh:/root/.ssh —— 整个 .ssh 目录读写挂载,便于写入 known_hosts
# ssh-keyscan —— 预填 known_hosts,免去首次交互确认
# chmod 600 —— ssh 要求私钥权限必须 600,否则拒绝使用
# alpine/git —— 同时带 git 和 ssh 客户端
log ">>> 通过子容器拉取代码..."
# 把 Gogs 主机和你的仓库地址换成实际的
GOGS_HOST="gogs.<你的懒猫微服名称>.heiyu.space"
REPO_URL="git@${GOGS_HOST}:ety001/test.git"
if [ ! -d "${REPO_DIR}/.git" ]; then
log " 首次 clone..."
mkdir -p "${REPO_DIR}"
docker run --rm \
--network host \
--entrypoint sh \
-v "${SSH_KEY_DIR}:/root/.ssh" \
-v "${REPO_DIR}:/repo" \
alpine/git:latest \
-c ' \
chmod 600 /root/.ssh/id_ed25519; \
ssh-keyscan -t ed25519 '"${GOGS_HOST}"' >> /root/.ssh/known_hosts 2>/dev/null; \
GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=accept-new" \
git clone --depth 1 '"${REPO_URL}"' /repo'
else
log " 已有仓库,执行 pull..."
docker run --rm \
--network host \
--entrypoint sh \
-v "${SSH_KEY_DIR}:/root/.ssh" \
-v "${REPO_DIR}:/repo" \
alpine/git:latest \
-c ' \
chmod 600 /root/.ssh/id_ed25519; \
ssh-keyscan -t ed25519 '"${GOGS_HOST}"' >> /root/.ssh/known_hosts 2>/dev/null; \
cd /repo && GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=accept-new" git pull'
fi
log " Git 操作完成"
# ---- 2. 把 webhook 注入的环境变量传给 docker build ----
log "APP_VERSION=${APP_VERSION:-(未设置)}"
log "BUILD_ENV=${BUILD_ENV:-(未设置)}"
# ---- 2. 构建 Docker 镜像 ----
# ⚠️ 关键:docker build 默认用 BuildKit,它解析 FROM 基础镜像(load metadata)时
# 不会自动走 dockerd 的代理,所以连 auth.docker.io 拿 token 会超时。
# 解决:把「设置 → Docker 代理设置」里配的代理加载进当前 shell,
# 这样 BuildKit 就能走代理拉取基础镜像了。
# (/etc/docker/proxy.env 由后端写入,与本脚本在同一容器内,可直接读取)
if [ -f /etc/docker/proxy.env ]; then
set -a; . /etc/docker/proxy.env; set +a
log "已加载 Docker 代理: ${HTTPS_PROXY:-${HTTP_PROXY:-(未配置)}}"
else
log "提示:未检测到代理配置。若拉取基础镜像超时,请到「设置 → Docker 代理设置」配置代理。"
fi
log ">>> 构建 Docker 镜像..."
docker build \
--build-arg APP_VERSION="${APP_VERSION}" \
--build-arg BUILD_ENV="${BUILD_ENV}" \
-t test-app:latest \
"${REPO_DIR}"
# ---- 3. 跑一下镜像,验证环境变量确实进去了 ----
log ">>> 构建完成,运行验证..."
docker run --rm test-app:latest
log "========== 全部完成 =========="
```
点 **「保存」**。

脚本做的事:用 git 协议拉取/更新 Gogs 代码 → 用 Docker 编译镜像
(并把 webhook 注入的环境变量通过 `--build-arg` 交给 Dockerfile)→ 运行镜像打印出这些值。
> 💡 **拉取基础镜像超时?配代理!**
> 上面的 `docker build` 解析 `FROM alpine:3.20` 时要连 `auth.docker.io`,国内网络容易超时
>(报错形如 `failed to fetch anonymous token ... i/o timeout`)。
> 先到 **「设置」→「Docker 代理设置」** 填好 HTTP/HTTPS 代理并保存——保存后系统会重启内层 Docker Daemon。
>
> **注意**:代理设置主要对 `docker pull` 生效;而 `docker build` 默认走 **BuildKit**,
> 它拉基础镜像时不会自动用 dockerd 的代理。**这正是本脚本在构建前
> `source /etc/docker/proxy.env` 的原因**——把代理加载进 shell,BuildKit 才能继承并走代理。
> 如果配了代理仍超时,确认代理地址正确,并检查「不走代理的地址」没误拦 docker.io。

---
## 第六步:创建 Webhook
进入左侧 **「Webhook」** 菜单,点 **「新建」**,按下面的内容填写:
| 字段 | 填写内容 | 说明 |
|------|----------|------|
| **Hook ID** | `test-build` | URL 里的标识,只能用字母/数字/`-`/`_` |
| **名称** | `测试构建` | 显示名,随便起 |
| **执行命令** | `sh /data/workspace/test/test.sh` | 上一步创建的脚本路径 |
| **工作目录** | `/data/workspace` | 脚本运行的工作目录 |
| **超时(秒)** | `600` | 构建可能较慢,留足时间 |
| **并发策略** | `skip` | 已有构建在跑就跳过本次,避免冲突 |
| **环境变量** | 见下方 | 传给脚本的环境变量 |
**环境变量**(每行一个,格式 `KEY=值`):
```
APP_VERSION=1.0.0
BUILD_ENV=production
```
> 📌 **关于环境变量**:这里的值是**固定注入**的——每次触发都会把 `APP_VERSION=1.0.0`、
> `BUILD_ENV=production` 注入到脚本环境里。脚本再通过 `docker build --build-arg`
> 把它们交进 Dockerfile。这就是「读取环境变量并赋给 Docker 编译过程」的实现方式。

**触发规则**(可选,但建议加上,限定只在 push 代码时触发):
> 📌 **规则语法遵循 [adnanh/webhook](https://github.com/adnanh/webhook) 格式。**
> 这是社区里最通用的 webhook 规则约定,玲珑机括完全兼容。规则由 `match` / `and` / `or` / `not`
> 组合而成,数据来源(`source`)支持 `header`、`payload`、`url`、`request-query` 等,
> 匹配方式(`type`)支持 `value`(精确匹配)、`regex`(正则)、`payload-hmac-sha256`(签名校验)等。
> 完整语法请参考 adnanh/webhook 的 [Hook 规则文档](https://github.com/adnanh/webhook/blob/master/docs/Hook-Rules.md)。
下面这条规则的含义是:仅当请求头 `X-Gogs-Event` 的值为 `push` 时才触发执行。
```json
{
"match": {
"type": "value",
"value": "push",
"parameter": { "source": "header", "name": "X-Gogs-Event" }
}
}
```
> 💡 **想限定只构建某个分支?** 可以再加一条对 payload 的匹配(Gogs 会在 body 里带 `ref`):
>
> ```json
> {
> "and": [
> { "match": { "type": "value", "value": "push", "parameter": { "source": "header", "name": "X-Gogs-Event" } } },
> { "match": { "type": "value", "value": "refs/heads/main", "parameter": { "source": "payload", "name": "ref" } } }
> ]
> }
> ```
>
> 有了 `and`/`or` 组合,分支过滤、事件过滤、甚至 HMAC 签名校验都能自由拼装。
>
> 🔁 另外,**设置 → 导入/导出**用的就是 adnanh/webhook 的 `hooks.json` 格式,
> 如果你已有现成的 adnanh/webhook 配置,可以直接导进来复用,不必从头重建。
填好后点 **创建**。
创建完成后,详情页会显示一个 **Webhook 地址**,形如:
```
https://webhook.<你的设备名>.heiyu.space/hooks/test-build
```

把这个地址复制下来,下一步要用。
> ✅ 这个 `/hooks/...` 地址是公开端点,**不需要** API Key 也不经过懒猫网关登录鉴权,
> 所以 Gogs 可以直接访问它。
---
## 第七步:在 Gogs 端配置 Webhook
回到 Gogs 的 `test` 仓库:**设置** → **Web 钩子** → **添加 Web 钩子** → 选 **Gogs**。
- **目标 URL**:粘贴上一步复制的 webhook 地址
- **HTTP 方法**:`POST`
- **触发条件**:选「自定义事件」,勾选 **push**
- **激活**:勾选
保存。

---
## 第八步:触发测试
随便在 `test` 仓库做个改动(比如改一下 README)然后 `git push`。
或者直接在配置界面触发测试。

回到玲珑机括,进入 **「日志」** 页面,你会看到一条新的执行记录,状态从
`running` 变成 `success`。点进去能看到实时的 stdout 输出,类似:
```
[2026-06-29 12:58:12] ========== 开始构建 ==========
[2026-06-29 12:58:12] >>> 通过子容器拉取代码...
[2026-06-29 12:58:12] 已有仓库,执行 pull...
Already up to date.
[2026-06-29 12:58:35] Git 操作完成
[2026-06-29 12:58:35] APP_VERSION=1.0.0
[2026-06-29 12:58:35] BUILD_ENV=production
[2026-06-29 12:58:35] 已加载 Docker 代理: http://192.168.199.11:8001
[2026-06-29 12:58:35] >>> 构建 Docker 镜像...
[2026-06-29 12:59:24] >>> 构建完成,运行验证...
APP_VERSION=1.0.0 BUILD_ENV=production
[2026-06-29 12:59:27] ========== 全部完成 ==========
```
最后那行 `APP_VERSION=1.0.0 BUILD_ENV=production` 由容器内打印出来了,这就证明了:**webhook 注入的环境变量,经过脚本,成功传进了 Docker 构建出的镜像。**

🎉 恭喜!你已经打通了完整的自动化链路。
---
## 进阶:把常用 CI 流程封装成 Docker 镜像
上面的脚本是「现场装环境」式的——直接在基础镜像里跑构建。如果你的构建流程比较固定
(比如永远需要 Node + pnpm,或者 Go + 交叉编译工具链),更省时的做法是
**把这些工具一次性打成一个镜像**,之后每次触发只挂载代码运行即可,省去反复安装依赖的时间。
### 1. 准备一个 CI 镜像
在本地或任意能构建的地方,写一个 `Dockerfile`,把常用工具装进去:
```dockerfile
# ci-builder/Dockerfile
FROM alpine:3.20
RUN apk add --no-cache git openssh docker-cli
# 按需追加:nodejs npm pnpm、go、make、rust ……
WORKDIR /workspace
ENTRYPOINT ["sh"]
```
构建并推送到你的镜像仓库,或者直接导入到懒猫的内层 Docker:
```bash
docker build -t my-ci:latest .
```
### 2. 钩子脚本简化为一行 `docker run`
把原来的 `test.sh` 换成在已装好依赖的镜像里跑:
```bash
#!/bin/sh
set -e
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /data/workspace:/workspace \
-e APP_VERSION="${APP_VERSION}" \
-e BUILD_ENV="${BUILD_ENV}" \
-w /workspace/test \
my-ci:latest \
/workspace/test/build.sh # 镜像内真正干活的脚本(构建逻辑放在这里)
```
好处很直接:
- **省时**:依赖只装一次,后续触发可秒级进入构建;
- **一致**:每次都在同一套环境里构建,告别「我这能跑你那不行」;
- **干净**:宿主 workspace 不会被污染,构建产物都留在挂载目录里。
> 挂载 `/var/run/docker.sock` 是为了让镜像内部能调用宿主的 Docker(即所谓的 out-of-Docker 方案)。
> 玲珑机括跑在懒猫容器里,脚本中的 `docker` 命令默认连的是内层 Daemon,挂载这个 socket
> 后镜像内也能复用同一套 Docker,避免再起一套 Docker-in-Docker。
---
## 进阶:让 AI Agent 替你写钩子脚本并自动部署
如果你用 AI Agent(比如 ZCode、Claude Code 等支持 Skill 的 Agent)来管理懒猫微服,
本程序专门提供了一个 **Skill 技能**,让 Agent 学习后,能直接帮你写好钩子脚本、
配置好 Webhook 并部署到位——省去手写脚本和在界面里逐字段填写的麻烦。
### 这个技能是什么
程序自带一份名为 **`linglong-webhook-hook-dev`** 的技能文档(随应用安装时分发)。
它把「玲珑机括的 REST API、Hook 字段规范、触发规则语法、脚本编写约定」全部整理成了
Agent 可读的参考手册。Agent 加载这份技能后,就掌握了:
- 完整的 Hook 字段含义和取值规范(超时、并发策略、环境变量、触发规则……);
- REST API 的调用方式(创建 / 更新 / 删除 Hook、查日志、停任务);
- 懒猫网关鉴权细节(怎么用 `hc` 命令拿 `Lzc-Api-Auth-Token` 绕过 `*.heiyu.space` 登录);
- 一套可直接复用的 shell 请求模板。
### 怎么用
1. **确认 Agent 已加载技能**:在本程序的技能列表里应能看到 `linglong-webhook-hook-dev`。
2. **把信息给 Agent**:告诉它你的 Gogs 地址、要构建的仓库、想注入的环境变量。
3. **Agent 自动完成**:它会用 REST API 创建 Hook、把生成好的脚本放到
`/data/workspace/...` 下、配好触发规则和并发策略,最后把 Webhook 地址交还给你。
4. **你去 Gogs 配 Webhook**:把 Agent 给你的 `/hooks/<hook_id>` 地址填进 Gogs 即可。
举几个适合交给 Agent 的活儿:
- 「帮我做一个 webhook:Gogs 的 `app` 仓库每次 push 到 main 就用 Docker 重新构建并重启服务」;
- 「给 `blog` 仓库加个自动部署,构建时注入 `DEPLOY_TARGET=lzc` 环境变量」;
- 「查一下最近失败的构建日志,定位原因」。
一句话总结:**你提需求,Agent 写脚本 + 调 API 部署,你只管去 Gogs 贴 webhook 地址。**
> 技能里特别说明了「Webhook 触发端点 `/hooks/{hook_id}` 无需认证、由 Gogs 等直接访问」,
> 所以 Agent 配出来的 Hook 你拿到地址就能用,不必再处理鉴权。
---
## 附录:用 SSH 协议拉取(备选)
由于懒猫网关的存在,因此跨应用访问的情况下, Gogs 只能通过 SSH 访问。
### A.2 脚本改用 SSH 克隆
把 `test.sh` 里的克隆段落换成:
```bash
# 修正私钥权限(ssh 要求 600),并用 GIT_SSH_COMMAND 指定密钥
chmod 600 /data/workspace/sshkey/id_ed25519
export GIT_SSH_COMMAND="ssh -i /data/workspace/sshkey/id_ed25519 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
REPO_URL="git@<gogs-host>:<用户名>/test.git"
if [ -d "$WORK/test/.git" ]; then
cd "$WORK/test" && git pull
else
git clone "$REPO_URL" "$WORK/test"
cd "$WORK/test"
fi
```
其余(`docker build`、环境变量等)保持不变。
---
## 常见问题排查
**Q:日志显示 `Permission denied (publickey)`(SSH 方式)**
A:检查 `sshkey/id_ed25519` 里的私钥是否完整(含首尾标记行),以及 Gogs 是否已添加**配对的公钥**。
脚本里已自动 `chmod 600`,一般不用手动改权限。
**Q:构建报错 `failed to fetch anonymous token ... i/o timeout`(卡在 `load metadata`)**
A:这是 BuildKit 解析基础镜像(`FROM alpine:3.20`)时连不上 `auth.docker.io`,国内网络典型问题。
解决:
1. 到 **「设置」→「Docker 代理设置」** 填上可用的 HTTP/HTTPS 代理(如 `http://192.168.1.1:7890`)并保存,
系统会**自动重启内层 Docker Daemon**;
2. 确认脚本里 `docker build` 之前有 `source /etc/docker/proxy.env`——这是让 BuildKit 继承代理的关键
(代理默认只对 `docker pull` 生效,build 需要脚本主动加载进环境);
3. 检查「不走代理的地址」没误拦 docker.io。
若手边没有代理,可临时用国内镜像源:把 Dockerfile 的 `FROM alpine:3.20` 改成
`FROM docker.m.daocloud.io/library/alpine:3.20` 之类。
**Q:日志一直 `running` 最后变成 `timeout`**
A:把 hook 的「超时」调大;网络拉取镜像或克隆大仓库会比较慢。
**Q:Gogs 测试推送后,日志页没有新记录**
A:检查 Gogs 里 webhook 的「最近交付」记录,看返回的状态码。
若返回 307 或 HTML 登录页,说明地址写错(webhook 地址必须是 `/hooks/...` 这个公开路径)。
**Q:忘记保存 API Key 了**
A:只能 SSH 进懒猫微服,清空数据库里的 `api_key_hash` 记录,重启应用让它重新初始化。
所以——**第一次显示时一定要存好。**
---
## 小结
整个流程串起来其实就三件事:
1. **文件管理**里放好脚本(持久化,不会丢);
2. **Webhook** 里把「脚本路径」和「要注入的环境变量」填好;
3. 把生成的 **webhook 地址** 配到 Gogs。
之后每一次 `git push`,玲珑机括都会自动拉代码、跑 Docker 构建——你只管推代码,剩下的交给它。
此 App 尚未收到足够的评分或评论,无法显示评论列表。