File size: 10,478 Bytes
985a50e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# Kimina Lean Server 离线镜像构建与部署指南

本文档说明如何将 `kimina-lean-server` 打包为完全离线的 Docker 镜像,部署到无公网访问的内部服务器上。

---

## 一、构建思路

### 1.1 为什么要做离线镜像

kimina-lean-server 运行时依赖以下组件,它们在首次启动或运行过程中会尝试联网:

| 组件 | 联网行为 | 离线处理方式 |
|---|---|---|
| **elan** | 安装时下载 Lean 工具链;运行时会检查更新 | 构建时预装工具链;运行时禁用更新检查 |
| **lake** | `lake build` 时从 git 拉取依赖;`lake exe cache get` 下载编译缓存 | 构建时完成所有编译;修改 manifest 为本地 path 类型 |
| **mathlib4** | 依赖众多 Lean 包,需从 GitHub 下载 | 构建时完整克隆并编译,预装入镜像 |
| **Python 依赖** | `pip install` 从 PyPI 下载 | 构建时全部安装到镜像中 |

### 1.2 核心策略:构建时联网,运行时零依赖

整个方案遵循"有网构建、无网运行"的原则:

1. **构建阶段**(在有网络的机器上):安装 elan、下载 Lean 工具链、克隆 repl 和 mathlib4、编译所有依赖、安装 Python 包
2. **镜像封装**:将编译产物、缓存、依赖全部打入 Docker 镜像
3. **离线阶段**(内部服务器):仅通过 `docker load` 加载镜像,无需任何外部网络

### 1.3 关键处理点

- **lake-manifest.json 改写**:构建完成后,将 mathlib4 的 `lake-manifest.json` 中所有包的 `type``"git"` 改为 `"path"`,并删除 `url` 字段。这样 lake 在运行时不会再尝试从 git 拉取依赖
- **elan 自更新禁用**:通过 `ELAN_NO_UPDATE_CHECK=1``elan config --disable-self-update` 双重保证
- **工具链预装**:构建时通过 `elan-init.sh --default-toolchain v4.26.0` 将工具链固定安装到镜像中

---

## 二、文件说明

### 2.1 `Dockerfile`(仓库根目录)

定义镜像的构建流程,关键步骤:

```dockerfile
# 基础镜像:Python 3.13 slim
FROM python:3.13-slim

# 构建参数:Lean 版本、REPL 源、mathlib 源等
ARG LEAN_SERVER_LEAN_VERSION=v4.26.0
ARG REPL_REPO_URL=https://github.com/leanprover-community/repl.git
ARG REPL_BRANCH=${LEAN_SERVER_LEAN_VERSION}
ARG MATHLIB_REPO_URL=https://github.com/leanprover-community/mathlib4.git
ARG MATHLIB_BRANCH=${LEAN_SERVER_LEAN_VERSION}

# 运行时环境变量
ENV LEAN_SERVER_LEAN_VERSION=${LEAN_SERVER_LEAN_VERSION} \
    LEAN_SERVER_REPL_PATH=/repl/.lake/build/bin/repl \
    LEAN_SERVER_PROJECT_DIR=/mathlib4 \
    LEAN_SERVER_MAX_REPL_MEM=12G \          # 关键:内存限制(见下文注意事项)
    LEAN_SERVER_INIT_REPLS='{"import Mathlib": 1}' \
    ELAN_NO_UPDATE_CHECK=1                   # 禁用 elan 更新检查

# 1. 安装系统依赖(curl, git, jq, build-essential 等)
RUN apt-get update && apt-get install -y ...

# 2. 复制并执行 setup.sh(安装 elan、lean、repl、mathlib4)
COPY setup.sh /usr/local/bin/
RUN sed -i 's/\r$//' /usr/local/bin/setup.sh \   # 修复 Windows CRLF
 && chmod +x /usr/local/bin/setup.sh \
 && /usr/local/bin/setup.sh

# 3. 安装 Python 依赖
RUN pip install ... && prisma generate

# 4. 启动服务
CMD ["python", "-m", "server"]
```

### 2.2 `setup.sh`(仓库根目录)

在容器构建阶段执行,完成 Lean 生态的预装:

```bash
#!/usr/bin/env bash
set -euxo pipefail

# 安装 elan(Lean 工具链管理器)
curl https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -sSf \
  | sh -s -- --default-toolchain "${LEAN_SERVER_LEAN_VERSION}" -y

# 禁用 elan 自动更新(离线环境必须)
elan config --disable-self-update

# 克隆并编译 REPL
install_repo repl "$REPL_REPO_URL" "$REPL_BRANCH" false

# 克隆并编译 mathlib4
install_repo mathlib4 "$MATHLIB_REPO_URL" "$MATHLIB_BRANCH" true
```

`install_repo` 函数的核心逻辑:

1. `git clone --single-branch --depth 1` 浅克隆仓库
2. `lake exe cache get` 下载 mathlib4 的预编译缓存(加速构建,失败则回退到源码编译)
3. `lake build` 编译项目
4. 对 mathlib4 执行 **manifest 改写**```bash
   jq '.packages |= map(.type="path"|del(.url)|.dir=".lake/packages/"+.name)' \
      lake-manifest.json
   ```
5. 构建完成后删除 `.git` 目录减小镜像体积

### 2.3 `deploy/offline/build-and-save.sh`

一键构建脚本,执行以下流程:

```
构建镜像
    |
    v
启动容器(--network=none)进行离线冒烟测试
    |
    v
等待 "Initialized REPLs" 日志(最多 10 分钟)
    |
    v
测试通过 → docker save | gzip 导出 tar.gz
```

可覆盖的环境变量:

| 变量 | 默认值 | 说明 |
|---|---|---|
| `LEAN_VERSION` | `v4.26.0` | Lean 版本 |
| `IMAGE` | `kimina-lean-server:${LEAN_VERSION}-offline` | 输出镜像名 |
| `PLATFORM` | `linux/amd64` | 目标平台架构 |
| `APT_MIRROR` | "" | APT 源镜像(国内网络可设阿里云) |
| `PIP_INDEX_URL` | "" | pip 源镜像 |
| `SKIP_UV` | "" | 非空则跳过 uv 安装 |

### 2.4 `deploy/offline/compose.offline.yaml`

离线服务器上的 Docker Compose 配置:

```yaml
services:
  server:
    image: kimina-lean-server:v4.26.0-offline
    ports:
      - "80:8000"
    environment:
      LEAN_SERVER_ENVIRONMENT: prod
      LEAN_SERVER_LEAN_VERSION: v4.26.0
      LEAN_SERVER_MAX_REPL_MEM: 12G       # 与 Dockerfile 保持一致
      LEAN_SERVER_INIT_REPLS: '{"import Mathlib": 1}'
    restart: unless-stopped
```

---

## 三、构建步骤

### 3.1 前置要求

- 一台**有公网访问**的 Linux 机器(架构需与目标服务器一致,通常为 `linux/amd64`- Docker 已安装并运行
- Bash、Git 可用

### 3.2 执行构建

```bash
cd deploy/offline
bash build-and-save.sh
```

构建耗时约 10-30 分钟,取决于网络速度。成功后会输出:

```
==> Offline smoke test PASSED
...
==> Done.
  image:        kimina-lean-server:v4.26.0-offline
  image digest: sha256:...
  tar:          .../deploy/offline/out/kimina-lean-server-v4.26.0.tar.gz
  tar sha256:   8e5d3edcc18ace92aa7252329e0a06a24d1b1506b3263042b0138e56de74761f
```

### 3.3 使用国内镜像加速(可选)

如果构建机器在国内,可设置镜像源:

```bash
export APT_MIRROR=https://mirrors.aliyun.com
export PIP_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/
export SKIP_UV=1
bash build-and-save.sh
```

---

## 四、部署步骤

### 4.1 传输文件到内部服务器

将以下两个文件复制到离线服务器:

```
deploy/offline/out/kimina-lean-server-v4.26.0.tar.gz
deploy/offline/compose.offline.yaml
```

### 4.2 加载镜像

在内部服务器上执行:

```bash
# 加载镜像(约 1-3 分钟,取决于磁盘速度)
gunzip -c kimina-lean-server-v4.26.0.tar.gz | docker load

# 验证镜像已加载
docker images | grep kimina-lean-server
```

### 4.3 启动服务

```bash
docker compose -f compose.offline.yaml up -d
```

服务将在后台运行,监听容器的 8000 端口,映射到宿主机的 80 端口。

### 4.4 查看日志

```bash
docker compose -f compose.offline.yaml logs -f
```

预期输出:

```
INFO  Running Kimina Lean Server 'v2.0.0' in prod mode with Lean version: 'v4.26.0'
INFO  REPL manager initialized with: MAX_REPLS=20, MAX_REPL_USES=-1, MAX_REPL_MEM=12288 MB
INFO  Initialized REPLs with: {"import Mathlib": 1}
INFO  Application startup complete.
INFO  Uvicorn running on http://0.0.0.0:8000
```

---

## 五、测试方法

### 5.1 快速功能测试

服务启动后,在离线服务器上执行:

```bash
curl --request POST \
  --url http://localhost:8000/api/check \
  --header 'Content-Type: application/json' \
  --data '{
    "snippets": [
      {"id": "check-nat-test", "code": "#check Nat"}
    ]
  }'
```

预期返回(类似):

```json
{
  "snippets": [
    {
      "id": "check-nat-test",
      "response": {
        "messages": [
          {
            "severity": "info",
            "pos": {"line": 1, "column": 0},
            "endPos": {"line": 1, "column": 6},
            "data": "Nat : Type"
          }
        ],
        "env": 0
      }
    }
  ]
}
```

### 5.2 离线环境验证

确认容器确实在断网环境下运行:

```bash
# 查看容器网络模式(应为默认 bridge,但无外网访问)
docker inspect <容器名> | grep -i network

# 进入容器内部测试网络连通性
docker exec -it <容器名> bash -c "curl -I https://github.com 2>&1"
# 预期:连接超时或无法解析(证明确实离线)
```

### 5.3 健康检查端点

```bash
curl http://localhost:8000/health
```

预期返回:

```json
{"status": "ok"}
```

---

## 六、注意事项

### 6.1 内存限制(重要)

Lean 4.26.0 + Mathlib 在 REPL 启动时需要大量虚拟内存。测试发现:

| 内存限制 | 结果 |
|---|---|
| 8G | REPL 启动失败(进程被内存限制阻塞) |
| 9G+ | 正常工作 |
| **12G**(推荐) | 稳定运行,留有余量 |

因此 Dockerfile 和 compose 文件中将默认值设为 `12G`。如果你的服务器内存紧张,可尝试降低到 9-10G,但不建议低于 9G。

### 6.2 架构一致性

构建机器的 CPU 架构必须与目标服务器一致。如果不一致,需使用 Docker 的交叉编译:

```bash
# 例如:在 Apple Silicon Mac 上构建 amd64 镜像
export PLATFORM=linux/amd64
bash build-and-save.sh
```

### 6.3 镜像体积

离线镜像体积较大(约 2-3GB),主要来源于:
- mathlib4 预编译产物和依赖包
- Lean 工具链
- REPL 二进制(约 200MB)

### 6.4 版本升级

如需升级 Lean 版本,修改以下位置后重新构建:

1. `Dockerfile` 中的 `LEAN_SERVER_LEAN_VERSION`
2. `compose.offline.yaml` 中的 `LEAN_SERVER_LEAN_VERSION`
3. 重新运行 `build-and-save.sh`

---

## 七、故障排查

| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 构建时 `env: 'bash\r': No such file or directory` | setup.sh 有 Windows CRLF 换行符 | Dockerfile 中已添加 `sed -i 's/\r$//'` 自动修复 |
| 离线启动时 REPL 返回空响应 | 内存限制过低 | 提高 `LEAN_SERVER_MAX_REPL_MEM` 到 12G |
| `lake exe cache get` 失败 | 网络问题或 lake 版本不兼容 | setup.sh 中已添加 `|| true` 容错,会回退到源码编译 |
| 容器启动后立刻退出 | REPL 初始化失败 | 查看 `docker logs` 中的错误信息 |
| elan 尝试联网更新 | 环境变量未生效 | 检查 `ELAN_NO_UPDATE_CHECK=1` 是否设置 |