Files
workpod/SPECS.md
T

578 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# WorkPod Alpine 技术规格
> 版本:1.1.0 | 更新日期:2026-04-07 | 镜像版本:workpod-alpine:latest (1.1.0)
---
## 1. 项目概述
WorkPod Alpine 是一个**轻量级 Docker 开发环境容器**,基于 Alpine Linux 3.23 构建,提供 Web 终端(ttyd)和 SSH 双入口,支持多实例部署、开发工具自动检测、tmux 多会话管理。
### 核心特性
| 特性 | 说明 |
|------|------|
| **镜像大小** | ~436MB(多阶段构建,Ubuntu 版的 1/6 |
| **基础镜像** | alpine:3.23 (~7MB) |
| **运行时内存** | 80-120MB 基线 + 应用 |
| **启动时间** | ~2.5 秒 |
| **Web 终端** | ttyd + tmux 会话管理 |
| **AI 工具集成** | Claude Code + coding-helper + 智谱 GLM |
### 适用场景
- 远程开发环境(浏览器直接访问,无需本地配置)
- 团队成员统一开发环境
- 多项目隔离部署(每个项目独立容器实例)
---
## 2. 架构设计
```mermaid
graph TB
subgraph "Docker Host"
DC["docker-compose.yml<br/>基础实例"]
DCF["docker-compose.flux.yml<br/>Flux 项目"]
DCA["docker-compose-alpine.yml<br/>备用/测试"]
end
subgraph "Images"
BASE["workpod-alpine:latest<br/>Alpine 3.23 + Node 24<br/>~436MB"]
FLUX["ws-flux-dev:latest<br/>BASE + JDK17/Maven 挂载"]
end
subgraph "Containers"
W1["workpod-alpine<br/>SSH :2222 / Web :7681<br/>network: workpod-alpine-network"]
W2["ws-flux-dev<br/>SSH :2201 / Web :7701<br/>network: workpod-flux-network"]
end
subgraph "Host Mounts"
WS["E:/wk-flux → /workspace"]
JDK["D:/Java/jdk-musl-17 → /opt/jdk-musl-17 :ro"]
MVN["D:/Java/maven-mvnd → /opt/maven-mvnd :ro"]
end
subgraph "Optional"
AP["auth-proxy.js<br/>认证代理 :8080<br/>Basic Auth + Token"]
NX["nginx (wk.1216.top)<br/>SSL 终止 + 反向代理"]
end
DC -->|"build"| BASE
DCF -->|"build"| FLUX
DCA -->|"image"| W1
BASE --> W1
FLUX --> W2
W2 --> WS
W2 --> JDK
W2 --> MVN
AP -.->|"proxy"| W1
AP -.->|"proxy"| W2
NX -.->|"ssl termination"| AP
```
### 组件职责
| 组件 | 职责 | 状态 |
|------|------|------|
| `Dockerfile` | 基础镜像构建(Node + Claude Code + coding-helper | 活跃 |
| `Dockerfile.flux` | Flux 项目扩展(基于基础镜像,JDK 外挂) | 活跃 |
| `entrypoint.sh` | 容器入口:信号处理、PATH 检测、服务启停 | 活跃 |
| `ttyd-session.sh` | tmux 会话管理(URL 参数 / 交互菜单) | 活跃 |
| `docker-compose.yml` | 基础实例编排 | 活跃 |
| `docker-compose.flux.yml` | Flux 项目实例编排 | 活跃 |
| `docker-compose-alpine.yml` | 备用编排(多端口 /root 挂载) | 归档备用 |
| `auth-proxy.js` | 认证代理(Basic Auth + Token + WS 代理) | 可选组件 |
| `download-packages.sh` | 离线包下载脚本 | 归档备用 |
| `entrypoint-test.sh` | Test 入口(developer 用户 + 全权限 Claude | 已归档 |
---
## 3. 镜像构建
### 3.1 基础镜像 (Dockerfile)
```dockerfile
# 多阶段构建
FROM alpine:3.23 AS builder # 阶段1: 构建
# → 安装 xz, libstdc++
# → 解压 Node.js v24.14.1 (musl)
# → npm install -g claude-code@latest, coding-helper@latest
# → npm cache clean
FROM alpine:3.23 # 阶段2: 运行时
# → apk add: curl git openssh-server bash ttyd tmux libstdc++
# → ssh-keygen + chpasswd + PermitRootLogin yes
# COPY --from=builder /opt/node /usr/local
# → npm config set registry npmmirror.com
# COPY --chmod=755 entrypoint.sh /entrypoint.sh
# COPY --chmod=755 ttyd-session.sh /opt/ttyd-session.sh
```
**构建产物**
| 层 | 内容 | 大小 |
|----|------|------|
| alpine:3.23 | 基础系统 | ~7MB |
| runtime tools | curl/git/ssh/ttyd/tmux/bash | ~80MB |
| /usr/local | Node.js + npm 全局包 | ~200MB |
| scripts | entrypoint + ttyd-session | <10KB |
| **总计** | | **~436MB (压缩后)** |
### 3.2 Flux 扩展镜像 (Dockerfile.flux)
```dockerfile
FROM workpod-alpine:latest
# 无额外 RUN 指令
# JDK 通过 docker-compose volume 从宿主机挂载
# JAVA_HOME 由 environment 设置
EXPOSE 22 7681
ENTRYPOINT ["/entrypoint.sh"]
```
Flux 镜像与基础镜像几乎等大(仅增加 LABEL 元数据),JDK/Maven 不占用镜像空间。
### 3.3 构建命令
```bash
# 基础镜像
docker build -t workpod-alpine:latest .
# Flux 镜像(依赖基础镜像先构建好)
docker build -t ws-flux-dev:latest -f Dockerfile.flux .
```
---
## 4. 容器编排
### 4.1 三份 Compose 文件对比
| 配置项 | docker-compose.yml (基础) | docker-compose.flux.yml (Flux) | docker-compose-alpine.yml (备用) |
|--------|--------------------------|-------------------------------|--------------------------------|
| **Service 名** | workpod-alpine | ws-flux-dev | workpod-alpine |
| **Container 名** | workpod-alpine | ws-flux-dev | workpod-alpine |
| **SSH 端口** | 2222 → 22 | 2201 → 22 | 2223 → 22 |
| **Web 端口** | 7681 → 7681 | 7701 → 7681 | 7683→7681, 7684→7682 |
| **工作空间挂载** | ./data/workspace → /workspace | E:/wk-flux → /workspace | data/workpod-alpine/workspace → /workspace |
| **JDK 挂载** | 无 | D:/Java/jdk-musl-17:ro → /opt/jdk-musl-17 | 无 |
| **Maven 挂载** | 无 | D:/Java/maven-mvnd:ro → /opt/maven-mvnd | 无 |
| **智谱 GLM env** | 无 | ANTHROPIC_* 5 项传递 | 无 |
| **privileged** | true | false | true |
| **内存限制** | 4G limit / 1G reserve | 4G limit / 1G reserve | 无 |
| **健康检查** | 有 | 有 | 无 |
| **日志轮转** | 10m × 3 | 10m × 3 | 无 |
| **网络** | workpod-alpine-network | workpod-flux-network | workpod-network |
### 4.2 启动命令
```bash
# 基础实例
docker compose up -d
# Flux 项目实例
docker compose -f docker-compose.flux.yml up -d
# 备用实例
docker compose -f docker-compose-alpine.yml up -d
```
---
## 5. 运行时
### 5.1 启动流程 (entrypoint.sh)
```
trap SIGTERM/SIGINT/SIGQUIT → cleanup()
┌─ PATH 自动检测 ─────────────────────────┐
│ JAVA_HOME → ${JAVA_HOME}/bin │
│ MAVEN_HOME → ${MAVEN_HOME}/bin │
│ .cargo/bin → Rust (cargo/rustup) │
│ gvm/gos → Go (gvm 版本管理) │
│ .pyenv → Python (pyenv) │
│ go/bin → Go (自定义安装) │
│ .local/bin → 本地工具 │
│ rust-* → Rust 独立安装 │
└──────────────────────────────────────────┘
┌─ 写入 /etc/profile.d/dev-tools.sh ─────┐│ (新 shell 会话也生效)
│ (同样逻辑,确保持久化) │
└──────────────────────────────────────────┘
┌─ 启动服务 ────────────────────────────────┐
│ /usr/sbin/sshd │
│ ttyd -W -c "${TTYD_CREDENTIALS}" \ │
│ -t fontSize=16 \ │
│ -t theme='{"background":"#1a1a2e"}' │
│ /opt/ttyd-session.sh & │
└──────────────────────────────────────────┘
┌─ 健康检查 ────────────────────────────────┐
│ kill -0 $TTYD_PID → ttyd 存活? │
│ pidof sshd → sshd 存活? │
│ 任一失败 → exit 1 │
└──────────────────────────────────────────┘
┌─ 显示环境信息 ──────────────────────────┐
│ Node/npm/Claude/Rust/Go/Python 版本 │
│ 连接方式 (Web URL + SSH 命令) │
└──────────────────────────────────────────┘
exec sleep infinity (PID 1)
```
### 5.2 信号处理
```bash
cleanup() {
kill $TTYD_PID # ttyd 后台进程
kill $SSHD_PID # sshd (注意: 当前 PID 未追踪,实际用 pkill)
wait # 等待子进程退出
exit 0
}
trap cleanup SIGTERM SIGINT SIGQUIT
```
### 5.3 ttyd 会话管理 (ttyd-session.sh)
```
用户访问 http://host:7681
┌─ URL 带 ?session=xxx ? ──────┐
│ 是 → 解析 session 名称 │
│ ↓ │
│ 过滤非法字符 (仅 a-zA-Z0-9_-) │
│ ↓ │
│ tmux attach (存在) 或 new (不存在)│
│ │
│ 否 → 显示交互式菜单 │
│ ┌─────────────────────────┐ │
│ │ [1] session_a │ │
│ │ [2] session_b │ │
│ │ 输入编号或新名称 │ │
│ └─────────────────────────┘ │
│ ↓ │
│ 数字 → 映射到已有 session │
│ 字符串 → 新建 session │
│ ↓ │
│ exec tmux attach/new │
└─────────────────────────────────────┘
```
**安全设计**
- Session 名过滤:`tr -cd 'a-zA-Z0-9_-'` 防止注入
- 共享模式:不带 `-d` 参数,多人可同时查看同一 session
- 默认值:空输入默认为 `default`
---
## 6. 服务端口与连接方式
### 6.1 端口分配表
| 实例 | SSH | Web 终端 (ttyd) | 网络 |
|------|-----|---------------|------|
| **workpod-alpine** (基础) | 2222 | 7681 | workpod-alpine-network |
| **ws-flux-dev** (Flux) | 2201 | 7701 | workpod-flux-network |
| **workpod-test** (旧测试) | 2223 | 7682 | — |
### 6.2 连接速查
```bash
# 基础实例
Web: http://localhost:7681 # 用户 jc / 密码 1234567
SSH: ssh root@localhost -p 2222 # 密码 workpod123 (或 ROOT_PASSWORD)
# Flux 项目实例
Web: http://localhost:7701 # 用户 jc / 密码 1234567
SSH: ssh root@localhost -p 2201 # 密码 workpod123 (或 ROOT_PASSWORD)
# 进入容器 (调试用)
MSYS_NO_PATHCONV=1 docker exec -it workpod-alpine bash
MSYS_NO_PATHCONV=1 docker exec -it ws-flux-dev bash
```
### 6.3 环境变量速查
| 变量 | 默认值 | 用途 |
|------|--------|------|
| `ROOT_PASSWORD` | workpod123 | SSH root 密码 |
| `TTYD_CREDENTIALS` | jc:1234567 | ttyd Web 终端认证 (格式 `用户:密码`) |
| `TZ` | Asia/Shanghai | 时区 |
| `JAVA_HOME` | (无) | JDK 路径 (Flux 实例设置) |
| `MAVEN_HOME` | (无) | Maven 路径 (Flux 实例设置) |
| `ANTHROPIC_AUTH_TOKEN` | (从宿主机继承) | 智谱 API 密钥 |
| `ANTHROPIC_BASE_URL` | https://open.bigmodel.cn/api/anthropic | 智谱 API 地址 |
| `ANTHROPIC_DEFAULT_SONNET_MODEL` | glm-5v-turbo | Sonnet 模型 |
| `ANTHROPIC_DEFAULT_OPUS_MODEL` | glm-5.1 | Opus 模型 |
| `ANTHROPIC_DEFAULT_HAIKU_MODEL` | glm-4.5-air | Haiku 模型 |
---
## 7. 开发工具外挂机制
### 7.1 支持的工具链
entrypoint.sh 启动时自动检测以下路径并加入 PATH:
| 工具 | 检测路径 | 环境变量 |
|------|---------|---------|
| Java (JDK) | `$JAVA_HOME/bin` | JAVA_HOME |
| Maven | `$MAVEN_HOME/bin` | MAVEN_HOME |
| Rust (rustup) | `/root/.cargo/bin` | RUSTUP_HOME, CARGO_HOME |
| Rust (独立) | `/root/rust-*/bin` | — |
| Go (gvm) | `/root/gvm/gos/current/bin` | — |
| Python (pyenv) | `/root/.pyenv/shims`, `.pyenv/bin` | — |
| Go (自定义) | `/root/go/bin` | GOROOT |
| 本地工具 | `/root/.local/bin` | — |
### 7.2 PATH 生效范围
- **当前 entrypoint 进程**:立即 export
- **后续 shell 登录**:通过 `/etc/profile.d/dev-tools.sh` 自动加载
- **docker exec**:需要 `bash -lc 'command'` 或手动 source profile
### 7.3 使用方式
开发者只需将工具安装到对应路径(通过挂载 /root 或在容器内安装),重启或重新登录即可生效。无需修改 Dockerfile 或 entrypoint。
---
## 8. 多实例部署策略
### 8.1 推荐方案
```
项目 A → docker-compose.A.yml (端口 220x / 770x)
项目 B → docker-compose.B.yml (端口 221x / 771x)
通用 → docker-compose.yml (端口 2222 / 7681)
```
每个项目一份 compose 文件,基于同一个基础镜像 `workpod-alpine:latest`,通过 Dockerfile 扩展或 volume 挂载添加项目特定依赖。
### 8.2 扩展新实例步骤
1. 创建 `Dockerfile.xxx`(如需额外包)或直接用基础镜像
2. 创建 `docker-compose.xxx.yml`(配置端口、挂载、环境变量)
3. `docker compose -f docker-compose.xxx.yml up -d`
4. 确认端口不冲突
### 8.3 网络隔离
每个 compose 文件创建独立的 bridge network,容器间默认不可互通。如需互通可在同一 compose 中定义多 service。
---
## 9. 安全模型
### 9.1 当前状态
| 安全项 | 配置 | 风险等级 | 备注 |
|--------|------|---------|------|
| **特权模式** | 基础版 `privileged: true` | **高** | Flux 版已移除 |
| **运行用户** | 全部 root | 中 | 开发环境可接受 |
| **SSH 登录** | PermitRootLogin yes + 密码 | 中 | 建议生产环境禁用 |
| **ttyd 认证** | Basic Auth (jc:1234567) | 低-中 | 可配置强密码 |
| **密码管理** | 环境变量注入 | 低 | 已修复硬编码 |
| **API Key** | docker-compose env 传递 | 低 | 不写入文件系统 |
| **镜像源** | alpine:3.23 + npmmirror | 低 | 第三方信任链 |
| **npm 包** | @latest (不确定版本) | 低 | 用户决策,接受风险 |
### 9.2 已修复的问题
- ~~密码硬编码在源码中~~ → ROOT_PASSWORD / TTYD_CREDENTIALS 环境变量
- ~~API Key 写入 .bashrc~~ → docker-compose environment 直接传递
- ~~healthcheck CMD 数组管道错误~~ → CMD-SHELL + netstat
- ~~entrypoint 外挂 bind mount~~ → COPY --chmod 内置镜像
- ~~构建上下文 ~520MB~~ → .dockerignore 优化到 275 bytes
### 9.3 待改进项
| 优先级 | 改进项 | 建议 |
|--------|--------|------|
| P0 | 移除 privileged: true | 基础版改用具体 capability 或确认是否真需要 |
| P1 | SSH 禁用密码登录 | 改为密钥认证,或限制来源 IP |
| P2 | ttyd 加密传输 | 前置 nginx/caddy 做 HTTPS 终止 |
| P3 | npm 包固定版本 | 将 @latest 改为具体版本号,构建可重现 |
---
## 10. 认证代理 (auth-proxy.js)
> 状态:可选组件,已补充到主目录但未默认启用
### 10.1 架构
```
浏览器 → :8080 auth-proxy.js
├── GET /login → 登录页面 (static/login.html)
├── POST /auth/check → Basic Auth → Token (1h 过期)
├── GET /api/workspaces → 工作空间列表
├── GET/WS /* → Token 验证 → 代理到 ttyd :7681
└── WebSocket upgrade → 双向管道 (终端 I/O)
```
### 10.2 配置
| 配置项 | 值 |
|--------|-----|
| 监听端口 | 8080 |
| 认证方式 | Basic Auth (`wk:1234567`, `admin:admin123`) |
| Token 格式 | `wk_{用户名}_{时间戳}` |
| Token 过期 | 1 小时 (内存存储) |
| 工作区路由 | 默认(/) → :7681, hszd → :7682 |
### 10.3 生产部署
```bash
# 1. 修改密码(环境变量或配置文件)
# 2. 前置 Nginx 反向代理 (wk.1216.conf)
# 3. SSL 证书 (wk.1216.top)
# 4. 启动
node auth-proxy.js
```
---
## 11. 离线包管理
> 状态:归档备用,Dockerfile 直接从 registry 安装
### 11.1 包清单
| 包名 | 版本 | 用途 | 来源 |
|------|------|------|------|
| node-v24.14.1-linux-x64-musl.tar.gz | v24.14.1 | Node.js 运行时 | unofficial-builds.nodejs.org |
| claude-code-*.tgz | @latest | AI 编程助手 | registry.npmmirror.com |
| z_ai-coding-helper-*.tgz | @latest | AI 编程助手 | registry.npmmirror.com |
| openclaw-*.tgz | 2026.3.28 | (未使用) | registry.npmmirror.com |
| go1.26.1.linux-amd64.tar.gz | 1.26.1 | (外挂预留) | Go 官方 |
| rust-1.94.1-x86_64-unknown-linux-musl.tar.xz | 1.94.1 | (外挂预留) | Rust 官方 |
### 11.2 下载脚本
```bash
./download-packages.sh
# 自动下载到 packages/ 目录
# 支持断点续传(文件存在则跳过)
# 使用国内镜像加速
```
---
## 12. 决策记录
以下是在项目演进过程中做出的关键架构决策及其原因:
### DEC-01: Alpine vs Ubuntu 作为基础镜像
- **决策**: 选择 Alpine 3.23
- **原因**: 镜像小 6 倍(436MB vs 2.6GB)、内存基线低 5 倍、启动快 6 倍
- **代价**: musl libc 兼容性需注意(JDK 必须用 musl 版本)
### DEC-02: JDK/Maven 宿主机挂载 vs 镜像内安装
- **决策**: 选择宿主机目录 ro 挂载(`D:/Java/jdk-musl-17`, `D:/Java/maven-mvnd`
- **原因**: 升级 JDK/Maven 无需 rebuild 镜像;本地多项目可共享同一套工具链
- **代价**: 依赖宿主机路径存在;容器不可脱离该主机单独分发
### DEC-03: BellSoft Liberica JDK vs 其他 musl JDK
- **决策**: BellSoft Liberica JDK 17.0.16+12 (musl)
- **原因**: Adoptium "musl" 标签实际仍链接 glibcAlpine openjdk 无法外部挂载;gcompat 缺少符号
- **参考**: 经 ldd 验证解释器为 `/lib/ld-musl-x86_64.so.1`
### DEC-04: npm 包 @latest vs 固定版本
- **决策**: 保持 @latestclaude-code 和 coding-helper
- **原因**: 用户选择每次构建自动获取最新版本,接受不确定性
- **风险**: 构建结果可能不一致;回归问题排查困难
### DEC-05: auto-upgrade 功能废弃
- **决策**: 不实现容器启动时自动检测升级
- **原因**: 维护负担 > 收益;增加启动复杂度和网络依赖;set -e 下失败处理困难
- **替代**: 手动 `docker build` 时自然获取最新
### DEC-06: workpod-alpine 目录合并到 workpod
- **决策**: 删除 `E:/wk-lab/workpod-alpine/`,全部内容归入 `E:/wk-lab/workpod/`
- **原因**: 单一目录管理;避免两份代码漂移;旧版完整归档于 `_archive/`
### DEC-07: entrypoint/ttyd-session 内置镜像 vs bind mount
- **决策**: COPY --chmod=755 内置到镜像
- **原因**: 避免 execvp failedbind mount 源目录删除后容器失效);符合 immutable 镜像最佳实践
- **触发事件**: 2026-04-04 因删除 workpod-alpine 目录导致 workpod 容器 ttyd 子进程全部 crash (exit code 254)
---
## 附录 A: 常用运维命令
```bash
# 构建
docker build -t workpod-alpine:latest .
docker build -t ws-flux-dev:latest -f Dockerfile.flux .
# 启动/停止
docker compose up -d
docker compose down
docker compose -f docker-compose.flux.yml up -d
# 查看日志
docker logs -f workpod-alpine
docker logs --tail 50 ws-flux-dev
# 进入容器
MSYS_NO_PATHCONV=1 docker exec -it workpod-alpine bash
# 验证工具链 (需 login shell)
MSYS_NO_PATHCONV=1 docker exec -it workpod-alpine bash -lc '
echo "Java: $(java -version 2>&1 | head -1)"
echo "Maven: $(mvn -version 2>&1 | head -1)"
echo "Node: $(node -v)"
echo "Claude: $(claude --version)"
'
# 清理
docker system prune -f # 清理悬停资源
docker image prune -f # 清理未使用镜像
```
## 附录 B: 文件总览
```
workpod/
├── Dockerfile # 基础镜像 (多阶段构建)
├── Dockerfile.flux # Flux 扩展镜像
├── entrypoint.sh # 容器入口 (信号/PATH/服务/信息)
├── ttyd-session.sh # tmux 会话管理
├── docker-compose.yml # 基础实例 (:2222/:7681)
├── docker-compose.flux.yml # Flux 实例 (:2201/:7701)
├── docker-compose-alpine.yml # 备用编排
├── auth-proxy.js # 认证代理 (可选)
├── download-packages.sh # 离线包下载 (归档)
├── entrypoint-test.sh # Test 入口 (归档)
├── wk.1216.conf # Nginx 配置
├── nginx-map-patch.sh # Nginx WS 补丁
├── connection_upgrade.map # Nginx map 片段
├── .dockerignore # 构建忽略
├── ISSUES.md # 问题记录
├── SPECS.md # 本文档 ← 你在这里
├── docs/
│ └── 05-问题处理/ # 5 份审核报告
├── config/ # 配置模板
├── static/ # auth-proxy 登录页
├── data/ # 运行时数据
└── _archive/ # 历史版本归档
```