From ccca0fa62b613d70da79558317bfbd61151ce85a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Mon, 27 Jul 2026 13:59:18 +0800 Subject: [PATCH] =?UTF-8?q?v1.1.0:=20Alpine=20=E8=BD=BB=E9=87=8F=E7=BA=A7?= =?UTF-8?q?=20Docker=20=E5=BC=80=E5=8F=91=E7=8E=AF=E5=A2=83=20+=20?= =?UTF-8?q?=E6=95=8F=E6=84=9F=E5=87=AD=E8=AF=81=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 49 + .gitignore | 38 +- Dockerfile | 131 +-- Dockerfile.flux | 13 + PACKAGES.md | 125 --- README.md | 165 +--- REVIEW-REPORT.md | 131 --- SPECS.md | 756 ++++++++++------ auth-proxy.js | 173 ++++ config/test-claude-settings.json | 1 + connection_upgrade.map | 4 + dev-envs/flux/claude/CLAUDE.md | 117 +++ dev-envs/flux/claude/settings.local.json | 1 + dev-envs/flux/docs/MySQL慢查询分析.md | 124 +++ dev-envs/flux/docs/Redkale使用易错点.md | 382 ++++++++ dev-envs/flux/mysql-proxy.toml.example | 29 + dev-envs/flux/norms.md | 102 +++ dev-envs/flux/redis-proxy.toml.example | 17 + dev-envs/flux/ssh-proxy.toml.example | 25 + docker-compose-alpine.yml | 24 + docker-compose.yml | 55 -- docs/00-规范/README.md | 70 ++ docs/00-规范/实例创建流程.md | 118 +++ docs/00-规范/端口分配规则.md | 61 ++ docs/02-技术文档/ISSUES.md | 122 +++ docs/03-运维/开发环境搭建手册.md | 1030 ++++++++++++++++++++++ docs/03-运维/测试服实例.md | 78 ++ docs/03-运维/部署指南.md | 79 ++ docs/03-运维/部署维护手册.md | 428 +++++++++ docs/04-审核/01-安全性审核.md | 352 ++++++++ docs/04-审核/02-架构设计审核.md | 316 +++++++ docs/04-审核/03-Docker最佳实践审核.md | 290 ++++++ docs/04-审核/04-Shell脚本质量审核.md | 204 +++++ docs/04-审核/05-性能与可维护性审核.md | 333 +++++++ download-packages.ps1 | 95 -- download-packages.sh | 90 +- entrypoint-test.sh | 128 +++ entrypoint.sh | 104 ++- instances/.template/docker-compose.yml | 66 ++ instances/README.md | 88 ++ instances/base/docker-compose.yml | 53 ++ instances/flux/Dockerfile | 10 + instances/flux/docker-compose.yml | 88 ++ instances/lab-x/Dockerfile | 10 + instances/lab-x/docker-compose.yml | 61 ++ instances/test/docker-compose.yml | 46 + migrate-docker.ps1 | 51 -- nginx-map-patch.sh | 4 + static/login.html | 191 ++++ ttyd-session.sh | 62 ++ wk.1216.conf | 28 + 51 files changed, 6125 insertions(+), 993 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile.flux delete mode 100644 PACKAGES.md delete mode 100644 REVIEW-REPORT.md create mode 100644 auth-proxy.js create mode 100644 config/test-claude-settings.json create mode 100644 connection_upgrade.map create mode 100644 dev-envs/flux/claude/CLAUDE.md create mode 100644 dev-envs/flux/claude/settings.local.json create mode 100644 dev-envs/flux/docs/MySQL慢查询分析.md create mode 100644 dev-envs/flux/docs/Redkale使用易错点.md create mode 100644 dev-envs/flux/mysql-proxy.toml.example create mode 100644 dev-envs/flux/norms.md create mode 100644 dev-envs/flux/redis-proxy.toml.example create mode 100644 dev-envs/flux/ssh-proxy.toml.example create mode 100644 docker-compose-alpine.yml delete mode 100644 docker-compose.yml create mode 100644 docs/00-规范/README.md create mode 100644 docs/00-规范/实例创建流程.md create mode 100644 docs/00-规范/端口分配规则.md create mode 100644 docs/02-技术文档/ISSUES.md create mode 100644 docs/03-运维/开发环境搭建手册.md create mode 100644 docs/03-运维/测试服实例.md create mode 100644 docs/03-运维/部署指南.md create mode 100644 docs/03-运维/部署维护手册.md create mode 100644 docs/04-审核/01-安全性审核.md create mode 100644 docs/04-审核/02-架构设计审核.md create mode 100644 docs/04-审核/03-Docker最佳实践审核.md create mode 100644 docs/04-审核/04-Shell脚本质量审核.md create mode 100644 docs/04-审核/05-性能与可维护性审核.md delete mode 100644 download-packages.ps1 create mode 100644 entrypoint-test.sh create mode 100644 instances/.template/docker-compose.yml create mode 100644 instances/README.md create mode 100644 instances/base/docker-compose.yml create mode 100644 instances/flux/Dockerfile create mode 100644 instances/flux/docker-compose.yml create mode 100644 instances/lab-x/Dockerfile create mode 100644 instances/lab-x/docker-compose.yml create mode 100644 instances/test/docker-compose.yml delete mode 100644 migrate-docker.ps1 create mode 100644 nginx-map-patch.sh create mode 100644 static/login.html create mode 100644 ttyd-session.sh create mode 100644 wk.1216.conf diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..93cbc1f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,49 @@ +# ==================== 版本控制 ==================== +.git +.gitignore + +# ==================== Claude 配置 ==================== +.claude/ + +# ==================== 文档 ==================== +docs/ +ISSUES.md +README.md +PACKAGES.md + +# ==================== 归档文件(已不需要) ==================== +_archive/ + +# ==================== 数据/运行时 ==================== +data/ + +# ==================== 部署辅助文件(不在镜像中) ==================== +auth-proxy.js +nginx-map-patch.sh +wk.1216.conf + +# ==================== 备用编排文件 ==================== +docker-compose-alpine.yml +entrypoint-test.sh + +# ==================== 下载脚本 ==================== +download-packages.sh +download-packages.ps1 + +# ==================== 导出镜像(~100MB) ==================== +*.tar.gz + +# ==================== Windows 伪文件 ==================== +;C +.DS_Store +Thumbs.db + +# ==================== packages 精确包含 ==================== +# 仅保留 Dockerfile COPY packages/ 需要的文件: +# node-v24.14.1-linux-x64-musl.tar.gz +# *claude-code*.tgz +# *coding-helper*.tgz +# 排除未使用的大包: +packages/go1.26.1.linux-amd64.tar.gz +packages/rust-1.94.1-x86_64-unknown-linux-musl.tar.xz +packages/openclaw-*.tgz diff --git a/.gitignore b/.gitignore index 3ab4fde..19450a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,43 @@ -# 软件包(太大,不纳入版本控制) +# 运行时数据 +data/ +.env +.env.* + +# 归档文件(历史版本) +_archive/ + +# 构建产物 +*.tar.gz + +# 离线包(121MB,不入版本库) packages/ -# Docker 数据 -docker-data/ +# Claude 本地配置(含 session token) +.claude/ + +# 敏感信息(密钥/密码/服务器IP/内部拓扑) +instances/flux/.env +instances/registry.yaml +dev-envs/flux/databases.yaml +dev-envs/flux/servers.yaml +# 代理工具运行时配置(含真实密码/IP,被 compose 挂载使用,不入库;模板见 *.toml.example) +dev-envs/flux/mysql-proxy.toml +dev-envs/flux/redis-proxy.toml +dev-envs/flux/ssh-proxy.toml # IDE .idea/ -.vscode/ -*.swp +*.iml -# 系统文件 +# OS .DS_Store Thumbs.db +*.swp +*.swo + +# 临时文件 +tmp/ +temp-images/ # 日志 *.log diff --git a/Dockerfile b/Dockerfile index 9859a68..2415483 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,107 +1,58 @@ -# WorkPod - 全栈开发环境容器 (本地包构建版) -# 包含:Ubuntu + Python + Go + Rust + Node + MySQL + Redis + Claude Code 2.1.79 + OpenClaw +# WorkPod Alpine - 轻量级开发环境(多阶段构建) -FROM ubuntu:22.04 +# ============ 阶段1: 构建阶段 ============ +FROM alpine:3.23 AS builder -ENV DEBIAN_FRONTEND=noninteractive -ENV TZ=Asia/Shanghai +RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \ + apk update && apk add --no-cache xz libstdc++ -# ============ 配置阿里云镜像源 ============ -RUN sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \ - sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list - -# ============ 基础工具 (合并安装减少层数) ============ -RUN apt-get update && apt-get install -y --no-install-recommends \ - # 基础工具 - curl wget git vim nano less tree jq \ - # 系统工具 - procps htop net-tools iputils-ping lsof \ - # 压缩工具 - zip unzip xz-utils \ - # 构建工具 - build-essential pkg-config \ - # SSL/加密 - ca-certificates gnupg \ - # SSH + PTY 支持 - openssh-server locales \ - && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* \ - && mkdir -p /var/run/sshd \ - && echo 'root:workpod123' | chpasswd \ - && sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config - -# 中文和 UTF-8 支持 -RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen -ENV LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 - -# ============ Python ============ -RUN apt-get update && apt-get install -y --no-install-recommends \ - python3 python3-pip python3-venv \ - && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* \ - && ln -sf /usr/bin/python3 /usr/bin/python - -# ============ 复制本地包到镜像 ============ COPY packages/ /tmp/packages/ -# ============ Node.js v24.14.0 (Krypton LTS) ============ -RUN cd /tmp/packages \ - && tar -xf node-v24.14.0-linux-x64.tar.xz -C /usr/local --strip-components=1 \ - && npm install -g npm@latest pnpm yarn \ - && npm cache clean --force - -# ============ Go 1.26.1 (最新版) ============ -ENV GOPATH=/root/go -ENV PATH=/usr/local/go/bin:$GOPATH/bin:$PATH -RUN cd /tmp/packages \ - && tar -xzf go1.26.1.linux-amd64.tar.gz -C /usr/local - -# ============ Rust 1.94.0 ============ -ENV CARGO_HOME=/root/.cargo RUSTUP_HOME=/root/.rustup -ENV PATH=$CARGO_HOME/bin:$PATH -RUN mkdir -p /opt/rust $CARGO_HOME/bin \ +# 安装 Node.js + npm 全局包 +RUN mkdir -p /opt/node \ && cd /tmp/packages \ - && tar -xf rust-1.94.0-x86_64-unknown-linux-gnu.tar.xz -C /opt/rust \ - && ln -sf /opt/rust/rust-1.94.0-x86_64-unknown-linux-gnu/bin/* $CARGO_HOME/bin/ - -# ============ Claude Code + OpenClaw (离线安装) ============ -RUN cd /tmp/packages \ - && npm install -g claude-code-2.1.79.tgz \ - && npm install -g openclaw-2026.3.13.tgz \ + && tar -xzf node-v24.14.1-linux-x64-musl.tar.gz -C /opt/node --strip-components=1 \ + && export PATH="/opt/node/bin:$PATH" \ + && npm install -g npm@10 \ + && npm config set registry https://registry.npmmirror.com \ + && npm install -g "@anthropic-ai/claude-code@latest" \ + && npm install -g "@z_ai/coding-helper@latest" \ && npm cache clean --force -# ============ MySQL 8.0 (手动启动) ============ -RUN apt-get update && apt-get install -y --no-install-recommends \ - mysql-server \ - && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* \ - && mkdir -p /var/run/mysqld \ - && chown mysql:mysql /var/run/mysqld +# ============ 阶段2: 运行阶段 ============ +FROM alpine:3.23 -# ============ Redis (手动启动) ============ -RUN apt-get update && apt-get install -y --no-install-recommends \ - redis-server \ - && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* +ENV TZ=Asia/Shanghai LANG=C.UTF-8 -# ============ 更多常用工具 ============ -RUN apt-get update && apt-get install -y --no-install-recommends \ - # 网络工具 - netcat-openbsd \ - # 文本处理 - ripgrep fd-find \ - # 进程管理 - tmux \ - # 其他 - bash-completion \ - && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* +# 基础工具 + SSH + ttyd +RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \ + apk update && apk add --no-cache \ + curl git ca-certificates openssh-server openrc \ + bash ttyd tmux libstdc++ \ + && mkdir -p /run/openrc && touch /run/openrc/softlevel \ + && ssh-keygen -A \ + && echo "root:${ROOT_PASSWORD:-workpod123}" | chpasswd \ + && sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config -# ============ 清理临时包 ============ -RUN rm -rf /tmp/packages +# 从构建阶段复制 Node.js 和全局包(仅产物,无缓存) +COPY --from=builder /opt/node /usr/local + +# 固化 npm 镜像源(运行时也需要,builder 的 npmrc 未被 COPY 包含) +RUN npm config set registry https://registry.npmmirror.com -# ============ 工作目录 ============ WORKDIR /workspace -# ============ 启动脚本 ============ -COPY entrypoint.sh /entrypoint.sh -RUN chmod +x /entrypoint.sh +COPY --chmod=755 entrypoint.sh /entrypoint.sh +COPY --chmod=755 ttyd-session.sh /opt/ttyd-session.sh -EXPOSE 22 80 443 3306 6379 +LABEL maintainer="workpod" \ + org.opencontainers.image.title="WorkPod Alpine" \ + org.opencontainers.image.description="Lightweight development environment (Alpine)" \ + org.opencontainers.image.version="1.1.0" + +EXPOSE 22 7681 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD netstat -tlnp 2>/dev/null | grep -qE ':(22|7681)\b' || exit 1 ENTRYPOINT ["/entrypoint.sh"] diff --git a/Dockerfile.flux b/Dockerfile.flux new file mode 100644 index 0000000..7dd7b9a --- /dev/null +++ b/Dockerfile.flux @@ -0,0 +1,13 @@ +# ws-flux-dev - Flux 金融线索管理 (Java + Node 全栈) +FROM workpod-alpine:latest + +LABEL org.opencontainers.image.title="ws-flux-dev" \ + org.opencontainers.image.description="Flux 全栈开发环境 (Java 17 + Node 24)" \ + org.opencontainers.image.version="1.0.0" + +# JDK 17 通过 docker-compose volume 从宿主机挂载 (BellSoft Liberica musl) +# JAVA_HOME 由 docker-compose environment 设置 + +EXPOSE 22 7681 + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/PACKAGES.md b/PACKAGES.md deleted file mode 100644 index 46d93bf..0000000 --- a/PACKAGES.md +++ /dev/null @@ -1,125 +0,0 @@ -# Dev Box 软件包清单 - -> 最后更新:2026-03-19 - ---- - -## 软件包列表 - -| 软件 | 版本 | 文件名 | 大小 | 国内镜像 | -|------|------|--------|------|---------| -| Node.js | v24.14.0 | node-v24.14.0-linux-x64.tar.xz | ~30 MB | npmmirror | -| Go | 1.26.1 | go1.26.1.linux-amd64.tar.gz | ~64 MB | golang.google.cn | -| Rust | 1.94.0 | rust-1.94.0-x86_64-unknown-linux-gnu.tar.xz | ~183 MB | USTC | -| Claude Code | 2.1.79 | claude-code-2.1.79.tgz | ~20 MB | npmmirror | -| OpenClaw | 2026.3.13 | openclaw-2026.3.13.tgz | ~28 MB | npmmirror | - -**总大小**:约 325 MB - ---- - -## 国内镜像源 - -### Node.js (淘宝镜像) - -```bash -# 下载地址 -https://npmmirror.com/mirrors/node/v24.14.0/node-v24.14.0-linux-x64.tar.xz - -# 备用:清华镜像 -https://mirrors.tuna.tsinghua.edu.cn/nodejs-release/v24.14.0/node-v24.14.0-linux-x64.tar.xz -``` - -### Go (官方中国镜像) - -```bash -# 下载地址 -https://golang.google.cn/dl/go1.26.1.linux-amd64.tar.gz - -# 备用:阿里云镜像 -https://mirrors.aliyun.com/golang/go1.26.1.linux-amd64.tar.gz -``` - -### Rust (中科大镜像) - -```bash -# 下载地址 -https://mirrors.ustc.edu.cn/rust-static/dist/rust-1.94.0-x86_64-unknown-linux-gnu.tar.xz - -# 备用:清华镜像 -https://mirrors.tuna.tsinghua.edu.cn/rustup/dist/rust-1.94.0-x86_64-unknown-linux-gnu.tar.xz -``` - -### npm 包 (淘宝镜像) - -```bash -# Claude Code -npm pack @anthropic-ai/claude-code@2.1.78 --registry=https://registry.npmmirror.com - -# OpenClaw -npm pack openclaw@2026.3.13 --registry=https://registry.npmmirror.com -``` - ---- - -## 下载方式 - -### Windows (PowerShell) - -```powershell -cd D:\dev-box -.\download-packages.ps1 -``` - -### Linux/macOS (Bash) - -```bash -cd /path/to/dev-box -chmod +x download-packages.sh -./download-packages.sh -``` - -### 手动下载 - -```bash -# Node.js -curl -LO https://npmmirror.com/mirrors/node/v24.14.0/node-v24.14.0-linux-x64.tar.xz - -# Go -curl -LO https://golang.google.cn/dl/go1.26.1.linux-amd64.tar.gz - -# Rust -curl -LO https://mirrors.ustc.edu.cn/rust-static/dist/rust-1.94.0-x86_64-unknown-linux-gnu.tar.xz -``` - ---- - -## 版本更新 - -如需更新软件版本,修改以下位置: - -1. **下载脚本** - `download-packages.ps1` / `download-packages.sh` 中的版本号 -2. **Dockerfile** - 对应的文件名和注释 - -### 查看最新版本 - -| 软件 | 查询地址 | -|------|---------| -| Node.js | https://npmmirror.com/mirrors/node/ | -| Go | https://golang.google.cn/dl/ | -| Rust | https://mirrors.ustc.edu.cn/rust-static/dist/ | -| Claude Code | `npm view @anthropic-ai/claude-code versions` | -| OpenClaw | `npm view openclaw versions` | - ---- - -## 目录结构 - -``` -packages/ -├── node-v24.14.0-linux-x64.tar.xz # Node.js -├── go1.26.1.linux-amd64.tar.gz # Go -├── rust-1.94.0-x86_64-unknown-linux-gnu.tar.xz # Rust -├── claude-code-2.1.78.tgz # Claude Code -└── openclaw-2026.3.13.tgz # OpenClaw -``` diff --git a/README.md b/README.md index eb54e4a..ad4017b 100644 --- a/README.md +++ b/README.md @@ -1,138 +1,67 @@ -# WorkPod 开发环境容器 +# WorkPod Alpine -全栈开发环境,包含 Ubuntu + Python + Go + Rust + Node + MySQL + Redis + Claude Code + OpenClaw +轻量级 Docker 开发环境容器 — 基于 Alpine Linux 3.23,镜像仅 ~436MB。 + +## 核心特性 + +| 特性 | 说明 | +|------|------| +| **镜像大小** | ~436MB(Ubuntu 版的 1/6) | +| **基础镜像** | alpine:3.23 (~7MB) | +| **运行时内存** | 80-120MB 基线 + 应用 | +| **启动时间** | ~2.5 秒 | +| **Web 终端** | ttyd + tmux 会话管理 | +| **AI 工具** | Claude Code + coding-helper | ## 快速开始 ```bash -# 构建镜像 -cd E:/wk-lab/workpod -docker build -t workpod:latest . +# 构建基础镜像 +docker build -t workpod-alpine:latest . -# 启动容器 -docker-compose up -d +# 启动基础实例 +docker compose up -d -# 查看日志 -docker logs -f workpod - -# 进入容器 -docker exec -it workpod bash +# 访问 +# Web: http://localhost:7681 (用户 jc / 密码 1234567) +# SSH: ssh root@localhost -p 2222 (密码 workpod123) ``` -## SSH 连接 +## 项目实例部署 ```bash -ssh root@localhost -p 2222 -# 密码: workpod123 +# Flux 项目(含 JDK/Maven 挂载) +docker compose -f docker-compose.flux.yml up -d + +# 备用编排 +docker compose -f docker-compose-alpine.yml up -d ``` -## 端口映射 +## 端口规划 -| 服务 | 宿主机端口 | 容器端口 | -|------|-----------|---------| -| SSH | 2222 | 22 | -| HTTP | 8080 | 80 | -| HTTPS | 8443 | 443 | -| MySQL | 13306 | 3306 | -| Redis | 16379 | 6379 | +| 实例 | SSH | Web 终端 | 用途 | +|------|-----|---------|------| +| workpod-alpine (基础) | 2222 | 7681 | 通用开发 | +| ws-flux-dev | 2201 | 7701 | Flux 项目 | -## 启动数据库服务 - -```bash -# MySQL -docker exec workpod bash -c "mysqld --user=mysql --datadir=/var/lib/mysql &" - -# Redis -docker exec workpod redis-server --daemonize yes -``` - -## 已安装工具 - -| 工具 | 版本 | 说明 | -|------|------|------| -| Python | 3.10 | pip, venv | -| Node.js | 24.14.0 | npm, pnpm, yarn | -| Go | 1.26.1 | | -| Rust | 1.94.0 | cargo | -| MySQL | 8.0 | 手动启动 | -| Redis | 6.0 | 手动启动 | -| Claude Code | 2.1.79 | AI 编程工具 | -| OpenClaw | 2026.3.13 | AI 网关 | - -## 镜像导出/导入 - -### 导出镜像 - -```bash -# 导出为 tar 文件 -docker save workpod:latest | gzip > workpod.tar.gz - -# 或不压缩 -docker save workpod:latest -o workpod.tar -``` - -### 导入镜像 - -```bash -# 从 tar.gz 导入 -docker load < workpod.tar.gz - -# 或从 tar 导入 -docker load -i workpod.tar -``` - -### 迁移到服务器 - -```bash -# 1. 本机导出 -docker save workpod:latest | gzip > workpod.tar.gz - -# 2. 传输到服务器 -scp workpod.tar.gz user@server:/path/ - -# 3. 服务器导入 -ssh user@server -docker load < /path/workpod.tar.gz - -# 4. 复制 docker-compose.yml 到服务器并启动 -docker-compose up -d -``` - -## 数据持久化 - -数据存储在 `E:/docker-data/workpod/` 目录: -- `mysql/` - MySQL 数据 -- `redis/` - Redis 数据 -- `workspace/` - 工作空间 - -## 配置文件 +## 目录结构 ``` -E:/wk-lab/workpod/ -├── Dockerfile # 镜像构建配置 -├── docker-compose.yml # 容器编排配置 -├── entrypoint.sh # 入口脚本 -├── download-packages.ps1 # 软件包下载脚本 -├── PACKAGES.md # 软件包清单 -├── SPECS.md # 技术规格说明书 -└── README.md # 说明文档 +workpod/ +├── Dockerfile # 基础镜像(多阶段构建) +├── Dockerfile.flux # Flux 扩展镜像 +├── entrypoint.sh # 容器入口(PATH 自检 + 服务启动) +├── ttyd-session.sh # tmux 会话管理 +├── auth-proxy.js # 认证代理(可选) +├── download-packages.sh # 离线包下载 +├── docker-compose*.yml # 编排文件 +├── dev-envs/ # 开发环境配置模板 +├── instances/ # 实例编排(模板化部署) +├── config/ # 配置文件 +├── docs/ # 文档 +└── SPECS.md # 技术规格(完整版) ``` -## 常用命令 +## 技术规格 -```bash -# 停止容器 -docker-compose down - -# 重启容器 -docker-compose restart - -# 查看容器状态 -docker ps --filter name=workpod - -# 进入容器执行命令 -docker exec -it workpod bash - -# 查看容器资源使用 -docker stats workpod -``` +详见 [SPECS.md](./SPECS.md) diff --git a/REVIEW-REPORT.md b/REVIEW-REPORT.md deleted file mode 100644 index c30e16e..0000000 --- a/REVIEW-REPORT.md +++ /dev/null @@ -1,131 +0,0 @@ -# Dev Box 代码审查与优化报告 - -> 审查日期:2026-03-19 -> 审查范围:D:/dev-box 项目 - ---- - -## 1. 审查概述 - -对 Dev Box 全栈开发环境容器进行代码审查,发现并修复了多个问题。 - -### 审查文件 - -| 文件 | 行数 | 状态 | -|------|------|------| -| Dockerfile | 110 → 107 | 已优化 | -| docker-compose.yml | 56 | 无问题 | -| entrypoint.sh | 31 → 30 | 已优化 | - ---- - -## 2. 问题修复清单 - -### 2.1 必须修复 (3 项) ✅ - -| # | 位置 | 问题描述 | 修复方案 | -|---|------|---------|---------| -| 1 | Dockerfile:53 | 删除不存在的文件 `go1.24.3.linux-amd64.tar.gz` | 移除无效命令 | -| 2 | Dockerfile:59 | `/opt/rust` 目录未创建 | 添加 `mkdir -p /opt/rust $CARGO_HOME/bin` | -| 3 | Dockerfile:61 | `|| echo "Rust 解压完成"` 错误处理无效 | 移除,让构建失败时可见真实错误 | - -### 2.2 建议改进 (3 项) ✅ - -| # | 位置 | 问题描述 | 修复方案 | -|---|------|---------|---------| -| 1 | Dockerfile:84 | `curl wget` 重复安装(已在第 16 行安装) | 移除重复项 | -| 2 | Dockerfile:105 | PATH 环境变量重复定义(第 50 行已定义) | 删除重复定义 | -| 3 | Dockerfile:98-101 | SSH 配置单独一层,可合并 | 合并到基础工具安装层 | - -### 2.3 可选优化 (1 项) ✅ - -| # | 位置 | 问题描述 | 修复方案 | -|---|------|---------|---------| -| 1 | entrypoint.sh:30 | `tail -f /dev/null` 语义不够清晰 | 改为 `sleep infinity` | - ---- - -## 3. 优化效果 - -### 3.1 镜像层数减少 - -``` -优化前:110 行,SSH 配置单独一层 -优化后:107 行,合并到基础工具层 -``` - -**减少 1 个 RUN 层** - -### 3.2 代码质量提升 - -- 消除无效命令 -- 移除重复定义 -- 提高构建可靠性 -- 改善代码可读性 - ---- - -## 4. 变更对比 - -### Dockerfile 关键变更 - -```diff -- && rm -f go1.24.3.linux-amd64.tar.gz - # 移除无效命令 - -+ RUN mkdir -p /opt/rust $CARGO_HOME/bin \ - # 添加目录创建 - -- curl wget netcat-openbsd \ -+ netcat-openbsd \ - # 移除重复安装 - -- ENV PATH="/usr/local/go/bin:/root/go/bin:/root/.cargo/bin:$PATH" - # 移除重复环境变量 - -- # ============ SSH 配置 ============ -- RUN mkdir -p /var/run/sshd \ -- && echo 'root:devbox123' | chpasswd \ -- && sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config - # 合并到基础工具层 -``` - -### entrypoint.sh 变更 - -```diff -- exec tail -f /dev/null -+ exec sleep infinity -``` - ---- - -## 5. 审查结论 - -| 指标 | 结果 | -|------|------| -| 发现问题 | 7 项 | -| 已修复 | 7 项 | -| 修复率 | 100% | -| 遗留问题 | 0 项 | - -**结论**:所有问题已修复,代码质量符合规范,可以进行镜像构建。 - ---- - -## 6. 后续建议 - -1. **构建测试** - 执行 `docker build -t dev-box:latest .` 验证修复效果 -2. **功能验证** - 启动容器并测试各服务(SSH、MySQL、Redis) -3. **版本更新** - 更新 SPECS.md 版本历史 - ---- - -## 附录:审查标准 - -本次审查基于以下标准: - -1. DRY 检查 - 是否有重复实现 -2. 简洁易读 - 变量/方法名是否清晰 -3. 现有实现 - 核对是否已有类似功能 -4. 防御性编程 - 避免过度防御 -5. 逻辑嵌套 - 减少嵌套层级 diff --git a/SPECS.md b/SPECS.md index a6cad2f..03462f4 100644 --- a/SPECS.md +++ b/SPECS.md @@ -1,317 +1,577 @@ -# WorkPod 技术规格说明书 +# WorkPod Alpine 技术规格 -> 文档版本:1.0 -> 最后更新:2026-03-18 -> 项目路径:`E:/workpod` +> 版本:1.1.0 | 更新日期:2026-04-07 | 镜像版本:workpod-alpine:latest (1.1.0) --- -## 1. 概述 +## 1. 项目概述 -### 1.1 项目定位 +WorkPod Alpine 是一个**轻量级 Docker 开发环境容器**,基于 Alpine Linux 3.23 构建,提供 Web 终端(ttyd)和 SSH 双入口,支持多实例部署、开发工具自动检测、tmux 多会话管理。 -WorkPod 是一个基于 Docker 的**全栈开发环境容器**,旨在提供统一、可移植、可离线部署的开发环境。 +### 核心特性 -### 1.2 设计目标 - -| 目标 | 说明 | +| 特性 | 说明 | |------|------| -| **环境统一** | 消除"在我机器上能跑"的问题 | -| **离线可用** | 所有依赖包本地化,无需网络 | -| **快速启动** | 一键启动完整开发环境 | -| **可迁移性** | 支持导出/导入到服务器 | -| **数据持久化** | 容器可重建,数据不丢失 | +| **镜像大小** | ~436MB(多阶段构建,Ubuntu 版的 1/6) | +| **基础镜像** | alpine:3.23 (~7MB) | +| **运行时内存** | 80-120MB 基线 + 应用 | +| **启动时间** | ~2.5 秒 | +| **Web 终端** | ttyd + tmux 会话管理 | +| **AI 工具集成** | Claude Code + coding-helper + 智谱 GLM | -### 1.3 适用场景 +### 适用场景 -- 多工作空间统一开发环境(wk-flux, wk-lab, wk-suke, wk-oth 等) -- 新成员快速上手 -- 服务器环境部署 -- 离线/内网开发环境 +- 远程开发环境(浏览器直接访问,无需本地配置) +- 团队成员统一开发环境 +- 多项目隔离部署(每个项目独立容器实例) --- -## 2. 技术架构 +## 2. 架构设计 -### 2.1 基础架构 +```mermaid +graph TB + subgraph "Docker Host" + DC["docker-compose.yml
基础实例"] + DCF["docker-compose.flux.yml
Flux 项目"] + DCA["docker-compose-alpine.yml
备用/测试"] + end -``` -┌─────────────────────────────────────────────────────────┐ -│ Docker Desktop │ -│ ┌───────────────────────────────────────────────────┐ │ -│ │ workpod 容器 (Ubuntu 22.04) │ │ -│ │ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ │ -│ │ │Python│ │ Node │ │ Go │ │ Rust │ │MySQL │ │ │ -│ │ │ 3.10 │ │ 24.x │ │1.26 │ │1.94 │ │ 8.0 │ │ │ -│ │ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ │ │ -│ │ ┌──────┐ ┌──────┐ ┌──────────────────────────┐ │ │ -│ │ │Redis │ │ SSH │ │ Claude Code + OpenClaw │ │ │ -│ │ │ 6.0 │ │ 2222 │ │ (AI 编程工具) │ │ │ -│ │ └──────┘ └──────┘ └──────────────────────────┘ │ │ -│ └───────────────────────────────────────────────────┘ │ -│ ↕ 数据卷挂载 (/d/docker-data) │ -│ ┌───────────────────────────────────────────────────┐ │ -│ │ MySQL │ Redis │ Workspace │ │ -│ └───────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────┘ + subgraph "Images" + BASE["workpod-alpine:latest
Alpine 3.23 + Node 24
~436MB"] + FLUX["ws-flux-dev:latest
BASE + JDK17/Maven 挂载"] + end + + subgraph "Containers" + W1["workpod-alpine
SSH :2222 / Web :7681
network: workpod-alpine-network"] + W2["ws-flux-dev
SSH :2201 / Web :7701
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
认证代理 :8080
Basic Auth + Token"] + NX["nginx (wk.1216.top)
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 ``` -### 2.2 技术栈明细 +### 组件职责 -| 组件 | 版本 | 安装方式 | 启动方式 | -|------|------|---------|---------| -| Ubuntu | 22.04 | 基础镜像 | - | -| Python | 3.10 | apt | 常驻 | -| Node.js | 24.14.0 | 离线包 | 常驻 | -| Go | 1.26.1 | 离线包 | 常驻 | -| Rust | 1.94.0 | 离线包 | 常驻 | -| MySQL | 8.0 | apt | 手动 | -| Redis | 6.0 | apt | 手动 | -| Claude Code | 2.1.78 | 离线包 | 按需 | -| OpenClaw | 2026.3.13 | 离线包 | 按需 | - -### 2.3 离线包清单 - -``` -packages/ -├── node-v24.14.0-linux-x64.tar.xz # Node.js 预编译包 -├── go1.26.1.linux-amd64.tar.gz # Go SDK -├── rust-1.94.0-x86_64-unknown-linux-gnu.tar.xz # Rust 工具链 -├── claude-code-2.1.78.tgz # Claude Code npm 包 -├── openclaw-2026.3.13.tgz # OpenClaw npm 包 -└── rustup-init.sh # Rust 安装脚本 (备用) -``` +| 组件 | 职责 | 状态 | +|------|------|------| +| `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. 镜像构建 -### 3.1 端口映射 +### 3.1 基础镜像 (Dockerfile) -| 服务 | 宿主机 | 容器 | 协议 | 说明 | -|------|--------|------|------|------| -| SSH | 2222 | 22 | TCP | 远程登录 | -| HTTP | 8080 | 80 | TCP | Web 服务 | -| HTTPS | 8443 | 443 | TCP | 加密 Web 服务 | -| MySQL | 13306 | 3306 | TCP | 数据库 | -| Redis | 16379 | 6379 | TCP | 缓存 | +```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 -### 3.2 连接方式 +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 -# SSH 连接 -ssh root@localhost -p 2222 -# 密码:workpod123 +# 基础镜像 +docker build -t workpod-alpine:latest . -# Docker exec 进入 -docker exec -it workpod bash - -# 数据库连接 -mysql -h 127.0.0.1 -P 13306 -u root -redis-cli -h 127.0.0.1 -p 16379 +# Flux 镜像(依赖基础镜像先构建好) +docker build -t ws-flux-dev:latest -f Dockerfile.flux . ``` --- -## 4. 存储配置 +## 4. 容器编排 -### 4.1 数据卷映射 +### 4.1 三份 Compose 文件对比 -| 宿主机路径 | 容器路径 | 用途 | -|-----------|---------|------| -| `/d/docker-data/mysql` | `/var/lib/mysql` | MySQL 数据 | -| `/d/docker-data/redis` | `/var/lib/redis` | Redis 数据 | -| `/d/docker-data/workspace` | `/workspace` | 工作目录 | -| `./config/supervisor` | `/etc/supervisor/conf.d` | 进程配置 | +| 配置项 | 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 存储要求 +### 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: + +| 工具 | 检测路径 | 环境变量 | |------|---------|---------| -| 镜像构建 | 10 GB | 20 GB | -| 容器运行 | 5 GB | 10 GB | -| MySQL 数据 | 1 GB | 按需 | -| 工作空间 | 1 GB | 按需 | +| 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。 --- -## 5. 构建规范 +## 8. 多实例部署策略 -### 5.1 构建前检查 +### 8.1 推荐方案 -```bash -# 检查 Docker 状态 -docker info -docker ps - -# 检查离线包完整性 -cd E:/workpod/packages -ls -lh *.tar.* *.tgz +``` +项目 A → docker-compose.A.yml (端口 220x / 770x) +项目 B → docker-compose.B.yml (端口 221x / 771x) +通用 → docker-compose.yml (端口 2222 / 7681) ``` -### 5.2 构建命令 +每个项目一份 compose 文件,基于同一个基础镜像 `workpod-alpine:latest`,通过 Dockerfile 扩展或 volume 挂载添加项目特定依赖。 -```bash -# 构建镜像 -cd E:/workpod -docker build -t workpod:latest . +### 8.2 扩展新实例步骤 -# 验证镜像 -docker images workpod +1. 创建 `Dockerfile.xxx`(如需额外包)或直接用基础镜像 +2. 创建 `docker-compose.xxx.yml`(配置端口、挂载、环境变量) +3. `docker compose -f docker-compose.xxx.yml up -d` +4. 确认端口不冲突 -# 启动容器 -docker-compose up -d +### 8.3 网络隔离 -# 验证容器 -docker ps --filter name=workpod +每个 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) ``` -### 5.3 镜像导出/导入 +### 10.2 配置 + +| 配置项 | 值 | +|--------|-----| +| 监听端口 | 8080 | +| 认证方式 | Basic Auth (`wk:1234567`, `admin:admin123`) | +| Token 格式 | `wk_{用户名}_{时间戳}` | +| Token 过期 | 1 小时 (内存存储) | +| 工作区路由 | 默认(/) → :7681, hszd → :7682 | + +### 10.3 生产部署 ```bash -# 导出(压缩) -docker save workpod:latest | gzip > workpod.tar.gz - -# 导入 -docker load < workpod.tar.gz +# 1. 修改密码(环境变量或配置文件) +# 2. 前置 Nginx 反向代理 (wk.1216.conf) +# 3. SSL 证书 (wk.1216.top) +# 4. 启动 +node auth-proxy.js ``` --- -## 6. 运维规范 +## 11. 离线包管理 -### 6.1 日常操作 +> 状态:归档备用,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 -# 启动容器 -docker-compose up -d +./download-packages.sh +# 自动下载到 packages/ 目录 +# 支持断点续传(文件存在则跳过) +# 使用国内镜像加速 +``` -# 停止容器 -docker-compose down +--- -# 重启容器 -docker-compose restart +## 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" 标签实际仍链接 glibc;Alpine openjdk 无法外部挂载;gcompat 缺少符号 +- **参考**: 经 ldd 验证解释器为 `/lib/ld-musl-x86_64.so.1` + +### DEC-04: npm 包 @latest vs 固定版本 + +- **决策**: 保持 @latest(claude-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 failed(bind 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 - -# 查看资源使用 -docker stats workpod +docker logs -f workpod-alpine +docker logs --tail 50 ws-flux-dev # 进入容器 -docker exec -it workpod bash +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 # 清理未使用镜像 ``` -### 6.2 数据库管理 - -```bash -# 启动 MySQL -docker exec workpod bash -c "mysqld --user=mysql --datadir=/var/lib/mysql &" - -# 启动 Redis -docker exec workpod redis-server --daemonize yes - -# 停止 MySQL -docker exec workpod bash -c "mysqladmin -u root shutdown" - -# 停止 Redis -docker exec workpod redis-cli shutdown -``` - -### 6.3 健康检查 - -```bash -# 检查容器状态 -docker inspect workpod --format='{{.State.Health.Status}}' - -# 检查 SSH 服务 -curl -k https://localhost:2222 - -# 检查 MySQL -docker exec workpod mysql -e "SELECT VERSION()" - -# 检查 Redis -docker exec workpod redis-cli ping -``` - ---- - -## 7. 安全规范 - -### 7.1 访问控制 - -| 项目 | 当前配置 | 建议 | -|------|---------|------| -| SSH 密码 | workpod123 | 生产环境修改 | -| MySQL root | 无密码 | 生产环境设置密码 | -| Redis | 无密码 | 生产环境设置密码 | - -### 7.2 安全加固建议 - -1. **生产环境**必须修改默认密码 -2. 限制端口暴露范围 -3. 使用 Docker 网络隔离 -4. 定期更新基础镜像 - ---- - -## 8. 故障排查 - -### 8.1 常见问题 - -| 问题 | 可能原因 | 解决方案 | -|------|---------|---------| -| 容器启动失败 | 端口被占用 | `netstat -ano \| findstr :2222` | -| MySQL 无法启动 | 数据目录权限 | `chown mysql:mysql /var/lib/mysql` | -| SSH 连接失败 | SSH 服务未启动 | `docker exec workpod service ssh start` | -| 构建失败 | 离线包缺失 | 检查 packages/ 目录 | - -### 8.2 日志位置 - -| 日志 | 命令 | -|------|------| -| 容器日志 | `docker logs workpod` | -| SSH 日志 | `docker exec workpod cat /var/log/auth.log` | -| MySQL 日志 | `docker exec workpod cat /var/log/mysql/error.log` | - ---- - -## 9. 版本历史 - -| 版本 | 日期 | 变更说明 | -|------|------|---------| -| 1.0 | 2026-03-18 | 初始版本 | - ---- - -## 附录 A:快速参考 - -### A.1 环境信息 +## 附录 B: 文件总览 ``` -基础镜像:ubuntu:22.04 -时区:Asia/Shanghai -语言:en_US.UTF-8 -工作目录:/workspace -``` - -### A.2 默认账号 - -| 服务 | 用户名 | 密码 | -|------|-------|------| -| SSH | root | workpod123 | -| MySQL | root | - | -| Redis | - | - | - -### A.3 文件位置 - -``` -E:/workpod/ -├── Dockerfile # 镜像构建配置 -├── docker-compose.yml # 容器编排配置 -├── entrypoint.sh # 启动脚本 -├── SPECS.md # 本文档 -├── README.md # 使用说明 -├── packages/ # 离线安装包 -└── config/supervisor/ # Supervisor 配置 +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/ # 历史版本归档 ``` diff --git a/auth-proxy.js b/auth-proxy.js new file mode 100644 index 0000000..d4e8265 --- /dev/null +++ b/auth-proxy.js @@ -0,0 +1,173 @@ +const http = require('http'); +const net = require('net'); +const fs = require('fs'); + +// 配置 +const PORT = 8080; +const TOKEN_PREFIX = 'wk_'; +// 用户口令从环境变量读取(JSON),未设置时用弱默认——生产环境务必通过 WORKPOD_AUTH_USERS 注入强口令 +const VALID_PASSWORDS = JSON.parse(process.env.WORKPOD_AUTH_USERS || '{"wk":"1234567","admin":"admin123"}'); +const WORKSPACES = [ + { path: '/', name: '默认工作区', desc: '/workspace', port: 7681 }, + { path: '/hszd', name: '华商智地', desc: '/workspace/wk-hszd', port: 7682 }, +]; + +// Token 存储 (内存) +const tokens = new Set(); + +// 生成 token +function createToken(user) { + const t = TOKEN_PREFIX + user + '_' + Date.now(); + tokens.add(t); + // 1小时过期清理 + setTimeout(() => tokens.delete(t), 3600000); + return t; +} + +// 验证 token +function validateToken(token) { + return token && tokens.has(token); +} + +// 解析 URL 路径,匹配工作区 +function matchWorkspace(pathname) { + for (const ws of WORKSPACES) { + if (pathname === ws.path || pathname.startsWith(ws.path + '/')) { + const proxyPath = ws.path === '/' ? pathname : pathname.slice(ws.path.length) || '/'; + return { ws, proxyPath }; + } + } + return null; +} + +// 代理请求到 ttyd +function proxyToTtyd(req, res, port, path) { + const options = { + hostname: '127.0.0.1', + port: port, + path: path, + method: req.method, + headers: { ...req.headers, host: '127.0.0.1:' + port, 'X-WorkPod-Auth': '1' } + }; + const proxyReq = http.request(options, (proxyRes) => { + res.writeHead(proxyRes.statusCode, proxyRes.headers); + proxyRes.pipe(res); + }); + proxyReq.on('error', () => { + if (!res.headersSent) res.writeHead(502); + res.end('Bad Gateway'); + }); + req.pipe(proxyReq); +} + +// 登录页 HTML +const LOGIN_HTML = fs.readFileSync('/var/www/workpod/login.html', 'utf-8'); + +const server = http.createServer((req, res) => { + const url = new URL(req.url, 'http://' + req.headers.host); + const token = url.searchParams.get('token') || ''; + + // 登录页 + if (url.pathname === '/login') { + res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); + return res.end(LOGIN_HTML); + } + + // 认证 API + if (url.pathname === '/auth/check') { + const auth = req.headers.authorization; + if (!auth || !auth.startsWith('Basic ')) { + res.writeHead(401); + return res.end('Unauthorized'); + } + const decoded = Buffer.from(auth.slice(6), 'base64').toString(); + const [user, pass] = decoded.split(':'); + if (!VALID_PASSWORDS[user] || VALID_PASSWORDS[user] !== pass) { + res.writeHead(401); + return res.end('Unauthorized'); + } + res.writeHead(200, { 'Content-Type': 'text/plain' }); + return res.end(createToken(user)); + } + + // 工作空间列表 + if (url.pathname === '/api/workspaces') { + if (!validateToken(token)) { + res.writeHead(401); + return res.end('Unauthorized'); + } + res.writeHead(200, { 'Content-Type': 'application/json' }); + return res.end(JSON.stringify(WORKSPACES)); + } + + // ttyd 内部端点 (/token, /ws 等) — 直接代理到默认工作区 + if (url.pathname === '/token' || url.pathname.startsWith('/ttyd/')) { + proxyToTtyd(req, res, WORKSPACES[0].port, url.pathname + url.search); + return; + } + + // Token 验证 + if (!validateToken(token)) { + res.writeHead(302, { 'Location': '/login' }); + return res.end(); + } + + // 路由到对应工作区 (普通 HTTP) + const matched = matchWorkspace(url.pathname); + if (matched) { + const { ws, proxyPath } = matched; + proxyToTtyd(req, res, ws.port, proxyPath + url.search); + return; + } + + // 未匹配路径 -> 登录页 + res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); + res.end(LOGIN_HTML); +}); + +// WebSocket 代理 (upgrade 事件) +server.on('upgrade', (req, socket, head) => { + const url = new URL(req.url, 'http://' + req.headers.host); + const token = url.searchParams.get('token') || ''; + + // Token 验证 + if (!validateToken(token)) { + socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n'); + socket.destroy(); + return; + } + + // 匹配工作区 + const matched = matchWorkspace(url.pathname); + if (!matched) { + socket.write('HTTP/1.1 404 Not Found\r\n\r\n'); + socket.destroy(); + return; + } + + const { ws, proxyPath } = matched; + const proxyUrl = new URL(proxyPath + url.search, 'http://127.0.0.1:' + ws.port); + + // 建立到 ttyd 的 TCP 连接 + const target = net.connect(ws.port, '127.0.0.1', () => { + // 构造 upgrade 请求头 + const headers = { ...req.headers, host: '127.0.0.1:' + ws.port, 'X-WorkPod-Auth': '1' }; + const requestLine = 'GET ' + proxyUrl.pathname + proxyUrl.search + ' HTTP/1.1\r\n'; + const headerLines = Object.entries(headers).map(([k, v]) => k + ': ' + v).join('\r\n'); + target.write(requestLine + headerLines + '\r\n\r\n'); + + // 如果有缓冲数据(head),转发给 ttyd + if (head.length > 0) target.write(head); + + // 双向管道: 浏览器 <-> ttyd + target.pipe(socket); + socket.pipe(target); + }); + + target.on('error', () => socket.destroy()); + socket.on('error', () => target.destroy()); +}); + +server.listen(PORT, () => { + console.log('[auth-proxy] 认证代理已启动,端口: ' + PORT); +}); diff --git a/config/test-claude-settings.json b/config/test-claude-settings.json new file mode 100644 index 0000000..fb09eee --- /dev/null +++ b/config/test-claude-settings.json @@ -0,0 +1 @@ +{"permissions": {"allow": ["*"], "deny": []}} \ No newline at end of file diff --git a/connection_upgrade.map b/connection_upgrade.map new file mode 100644 index 0000000..32af4c3 --- /dev/null +++ b/connection_upgrade.map @@ -0,0 +1,4 @@ +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} diff --git a/dev-envs/flux/claude/CLAUDE.md b/dev-envs/flux/claude/CLAUDE.md new file mode 100644 index 0000000..0e30824 --- /dev/null +++ b/dev-envs/flux/claude/CLAUDE.md @@ -0,0 +1,117 @@ +# Flux 开发环境 + +> 当前在 ws-flux-dev 容器内 +> 工作目录: /workspace (= E:/wk-flux) + +## 项目概述 + +贷款撮合平台 (Flux),核心模块: + +| 模块 | 技术栈 | 说明 | +|------|--------|------| +| flux-api | Java 17 + Redkale + Maven | 后端 API,核心业务 | +| flux-admin | Vue 3 + Arco Design + bun | 管理后台 | +| flux-uniapp | UniApp + Vite | H5 前端 | +| flux-mock | Go | 三方模拟服务 | +| flux-test | Go | 业务链测试 (撞库等) | +| file-server | Go | 文件上传下载 | +| flux-wechat-api | — | 微信服务端 | + +## 代理工具(优先使用) + +> **容器内代理服务由 .bashrc 自动启动**,无需手动启动。 +> **所有数据库和远程操作必须通过代理工具执行**,禁止直接用 mysql/ssh/redis-cli。 + +### MySQL 查询 + +```bash +# 查询 (默认表格输出) +mysql-proxy cli -c flux_dev -e "SELECT * FROM users LIMIT 10" + +# JSON 输出 (便于解析) +mysql-proxy cli -c flux_dev -e "SHOW TABLES" -F json + +# 执行 DML (加 -x) +mysql-proxy cli -c flux_dev -e "UPDATE table SET status=1 WHERE id=1" -x +``` + +### SSH 远程执行 + +```bash +# 执行命令 +ssh-proxy exec -n flux_dev -c "docker ps" + +# JSON 输出 +ssh-proxy exec -n flux_dev -c "docker ps" -F json + +# 生产服务器 (byr_pro = prod_baiyarong) +ssh-proxy exec -n byr_pro -c "cd /opt/flux-api && bash bin/restart.sh" +``` + +### Redis 操作 + +```bash +# 取值 +redis-proxy get -c flux_dev -k "session:user:123" + +# 通用命令 (最灵活) +redis-proxy run -c flux_dev -C "KEYS" -a "user:*" + +# 设置值 +redis-proxy set -c flux_dev -k "key" -v "value" +``` + +## 开发命令 + +```bash +# ===== flux-api (Java/Redkale/Maven) ===== +cd /workspace/flux-api +mvn clean package # 构建 +cmd /c bin\restart.bat # 重启 (Windows bat,容器内需调整) + +# ===== flux-admin (Vue3/bun) ===== +cd /workspace/flux-admin +bun install # 安装依赖 +bun run dev # 开发模式 + +# ===== flux-uniapp (UniApp) ===== +cd /workspace/flux-uniapp +npm install +npm run dev:h5 # H5 模式 + +# ===== Go 服务 ===== +cd /workspace/flux-mock && go run *.go +cd /workspace/flux-test && go run test_quick.go qulaijie -mode=collide +cd /workspace/file-server && go run *.go +``` + +## 连接信息速查 + +详细配置见: +- 服务器: `/root/.dev-env/servers.yaml` +- 数据库: `/root/.dev-env/databases.yaml` +- 规范: `/root/.dev-env/norms.md` + +**快速参考**: + +| 环境 | MySQL | Redis | +|------|-------|-------| +| 测试 (flux_dev) | 见 databases.yaml(不入库) | 见 databases.yaml :6379 | +| 生产 (flux_prox) | 见 databases.yaml(不入库) | — | + +## 项目知识库 + +Flux 专属文档位于 `/root/.dev-env/docs/`: + +| 文件 | 用途 | +|------|------| +| Redkale使用易错点.md | **flux-api 必读** — 框架踩坑记录 | +| MySQL慢查询分析.md | SQL 慢查询优化参考 | + +## 开发规范 + +> **唯一事实来源**: `/root/.dev-env/norms.md` +> 本文件不重复规范内容,避免维护不一致。 + +执行任务前先读取 norms.md,关键规则: +- Git 提交、代码审查、命名规范、高危操作检查清单 diff --git a/dev-envs/flux/claude/settings.local.json b/dev-envs/flux/claude/settings.local.json new file mode 100644 index 0000000..7dabfd7 --- /dev/null +++ b/dev-envs/flux/claude/settings.local.json @@ -0,0 +1 @@ +{"permissions": {"allow": ["Bash(mysql-proxy *)", "Bash(ssh-proxy *)", "Bash(redis-proxy *)", "Bash(mongo-proxy *)", "Bash(mvn *)", "Bash(java *)", "Bash(javac *)", "Bash(go *)", "Bash(bun *)", "Bash(npm *)", "Bash(node *)", "Bash(cmd *)", "Bash(git *)", "Bash(docker *)", "Bash(curl *)", "Bash(wget *)", "Bash(cat *)", "Bash(ls *)", "Bash(grep *)", "Bash(find *)", "Read", "Write", "Edit"], "deny": []}} \ No newline at end of file diff --git a/dev-envs/flux/docs/MySQL慢查询分析.md b/dev-envs/flux/docs/MySQL慢查询分析.md new file mode 100644 index 0000000..1ff897b --- /dev/null +++ b/dev-envs/flux/docs/MySQL慢查询分析.md @@ -0,0 +1,124 @@ +# MySQL 慢查询分析 + +--- + +## 查看慢查询配置 + +```sql +SHOW VARIABLES LIKE 'slow_query%'; +SHOW VARIABLES LIKE 'long_query_time'; +``` + +--- + +## 查询慢日志(RDS/MySQL) + +```sql +-- 最近24小时慢查询 +SELECT + db, + LEFT(REPLACE(REPLACE(sql_text, '\n', ' '), ' ', ' '), 200) as sql_preview, + query_time, + rows_examined, + rows_sent, + start_time +FROM mysql.slow_log +WHERE start_time > DATE_SUB(NOW(), INTERVAL 24 HOUR) +ORDER BY query_time DESC +LIMIT 30; + +-- 查询所有慢记录 +SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 20; +``` + +--- + +## 查看当前运行的长查询 + +```sql +-- 查看所有查询 +SHOW FULL PROCESSLIST; + +-- 筛选长查询(超过10秒) +SELECT + id, User, Host, db, Time, State, + LEFT(Info, 150) as sql_preview +FROM information_schema.processlist +WHERE Command = 'Query' AND Time > 10 +ORDER BY Time DESC; +``` + +--- + +## 终止查询 + +```sql +-- 单个终止 +KILL ; + +-- 批量生成终止语句(按来源IP) +SELECT CONCAT('KILL ', id, ';') +FROM information_schema.processlist +WHERE Host LIKE '39.99.243.191%' AND Command = 'Query'; +``` + +--- + +## performance_schema 分析 + +```sql +-- 按总耗时排序 +SELECT + SCHEMA_NAME, + LEFT(DIGEST_TEXT, 150) as sql_pattern, + COUNT_STAR as exec_count, + ROUND(SUM_TIMER_WAIT/1000000000000, 2) as total_sec, + ROUND(AVG_TIMER_WAIT/1000000000, 2) as avg_ms, + ROUND(MAX_TIMER_WAIT/1000000000000, 2) as max_sec +FROM performance_schema.events_statements_summary_by_digest +WHERE SCHEMA_NAME IS NOT NULL +ORDER BY SUM_TIMER_WAIT DESC +LIMIT 20; + +-- 按平均耗时排序 +SELECT + SCHEMA_NAME, + LEFT(DIGEST_TEXT, 150) as sql_pattern, + COUNT_STAR as exec_count, + ROUND(AVG_TIMER_WAIT/1000000000, 2) as avg_ms, + ROUND(MAX_TIMER_WAIT/1000000000000, 2) as max_sec +FROM performance_schema.events_statements_summary_by_digest +WHERE SCHEMA_NAME IS NOT NULL +ORDER BY AVG_TIMER_WAIT DESC +LIMIT 15; +``` + +--- + +## 查看表大小 + +```sql +SELECT + table_schema, + table_name, + ROUND(data_length/1024/1024, 2) as data_MB, + ROUND(index_length/1024/1024, 2) as index_MB, + table_rows +FROM information_schema.tables +WHERE table_schema IN ('db1', 'db2') +ORDER BY data_length DESC +LIMIT 30; +``` + +--- + +## 常用连接 + +| 库 | 连接命令 | +|----|----------| +| suke 生产库(阿里云RDS) | `mysql -hrm-8vbrd2wil14hclop3vm.mysql.zhangbei.rds.aliyuncs.com -uroot -pWFFGwffg233` | +| 二号机 | `mysql -h47.92.117.13 -uroot -px251119!` | + +--- + +*创建时间:2026-03-04* diff --git a/dev-envs/flux/docs/Redkale使用易错点.md b/dev-envs/flux/docs/Redkale使用易错点.md new file mode 100644 index 0000000..365918d --- /dev/null +++ b/dev-envs/flux/docs/Redkale使用易错点.md @@ -0,0 +1,382 @@ +# Redkale FilterNode 使用易错点 + +> 创建时间:2026-02-03 +> 基于:flux-api 项目实际代码 + +--- + +## ❌ 常见错误 + +### 1. LIKE 查询错误 + +**错误写法**: +```java +// ❌ 错误:使用静态方法 +node.and(PartnerFormRecord::getRealname, FilterNode.like(keyword)); +``` + +**正确写法**: +```java +// ✅ 正确:使用实例方法 +node.like(PartnerFormRecord::getRealname, keyword); +``` + +--- + +### 2. 比较查询错误 + +**错误写法**: +```java +// ❌ 错误:方法名拼写错误 +node.and(PartnerFormRecord::getCreatedtime, FilterNode.greateEqual(starttime)); +node.and(PartnerFormRecord::getCreatedtime, FilterNode.lessEqual(endtime)); +``` + +**正确写法**: +```java +// ✅ 正确:使用简短的方法名 +node.ge(PartnerFormRecord::getCreatedtime, starttime); +node.le(PartnerFormRecord::getCreatedtime, endtime); +``` + +**参考方法**: +- `ge()` - Greater or Equal(>=) +- `le()` - Less or Equal(<=) +- `gt()` - Greater Than(>) +- `lt()` - Less Than(<) + +--- + +### 3. OR 条件错误 + +**错误写法**: +```java +// ❌ 错误:在 and() 中混用 FilterNode 静态方法 +node.and( + FilterNode.or( + PartnerFormRecord::getRealname, FilterNode.like(keyword), + PartnerFormRecord::getMobile, FilterNode.like(keyword) + ) +); +``` + +**正确写法**: +```java +// ✅ 正确:创建独立 OR 节点,链式调用 +FilterNode orNode = new FilterNode(); +orNode.or(PartnerFormRecord::getRealname, FilterNode.like(keyword)) + .or(PartnerFormRecord::getMobile, FilterNode.like(keyword)) + .or(PartnerFormRecord::getIdcard, FilterNode.like(keyword)); +node.and(orNode); +``` + +--- + +### 4. 等值条件冗余 + +**错误写法**: +```java +// ❌ 冗余:等值条件不需要特殊方法 +node.and(PartnerFormRecord::getStatus, FilterNode.eq(1)); +``` + +**正确写法**: +```java +// ✅ 简洁:直接传值 +node.and(PartnerFormRecord::getStatus, 1); +``` + +--- + +### 5. NOT 条件错误 + +**错误写法**: +```java +// ❌ 错误:使用不存在的 notLike 方法 +node.notLike(PartnerFormRecord::getStatus, 3); +``` + +**正确写法**: +```java +// ✅ 正确:使用 notEq +node.notEq(PartnerFormRecord::getStatus, 3); +``` + +--- + +## ✅ 正确的模式 + +### 基础查询条件 + +```java +FilterNode node = new FilterNode(); + +// 固定条件 +node.and(PartnerFormRecord::getDraftstatus, (short) 0); + +// 可选条件 +if (bean.getKeyword() != null && !bean.getKeyword().isEmpty()) { + node.like(PartnerFormRecord::getRealname, bean.getKeyword()); +} + +// 范围查询 +if (bean.getStarttime() > 0) { + node.ge(PartnerFormRecord::getCreatedtime, bean.getStarttime()); +} +if (bean.getEndtime() > 0) { + node.le(PartnerFormRecord::getCreatedtime, bean.getEndtime()); +} + +// 不等于 +node.notEq(PartnerFormRecord::getStatus, (short) 3); +``` + +### OR 条件组合 + +```java +// 关键词:姓名 OR 电话 OR 身份证 +if (bean.getKeyword() != null && !bean.getKeyword().isEmpty()) { + FilterNode orNode = new FilterNode(); + orNode.or(PartnerFormRecord::getRealname, FilterNode.like(bean.getKeyword())) + .or(PartnerFormRecord::getMobile, FilterNode.like(bean.getKeyword())) + .or(PartnerFormRecord::getIdcard, FilterNode.like(bean.getKeyword())); + node.and(orNode); +} +``` + +### IN 条件 + +```java +// IN 查询 +FilterNode node = new FilterNode(); +node.in(PartnerFormRecord::getFormdataid, formIds.toArray()); +List list = dataSource.queryList(PartnerFormRecord.class, node); +``` + +--- + +## 📋 FilterNode 方法速查表 + +| 方法 | 说明 | 示例 | +|------|------|------| +| `and()` | AND 等值条件 | `node.and(Entity::getField, value)` | +| `like()` | LIKE 模糊查询 | `node.like(Entity::getField, "%keyword%")` | +| `ge()` | >= 大于等于 | `node.ge(Entity::getTime, timestamp)` | +| `le()` | <= 小于等于 | `node.le(Entity::getTime, timestamp)` | +| `gt()` | > 大于 | `node.gt(Entity::getField, value)` | +| `lt()` | < 小于 | `node.lt(Entity::getField, value)` | +| `notEq()` | != 不等于 | `node.notEq(Entity::getStatus, 3)` | +| `in()` | IN 查询 | `node.in(Entity::getId, ids.toArray())` | +| `or()` | OR 条件 | 需创建独立 FilterNode | + +--- + +## 🔍 完整示例参考 + +### PartnerFormService.java + +```java +@RestMapping(name = "list", comment = "分页查询表单记录") +public RetResult> list(FormRecordFilterBean bean, Flipper flipper) { + FilterNode node = new FilterNode(); + node.notEq(PartnerFormRecord::getStatus, STATUS_DELETED); + + if (!Utils.isEmpty(bean.getRealname())) { + node.like(PartnerFormRecord::getRealname, bean.getRealname()); + } + if (bean.getDraftstatus() >= 0) { + node.and(PartnerFormRecord::getDraftstatus, bean.getDraftstatus()); + } + if (bean.getStarttime() > 0) { + node.ge(PartnerFormRecord::getCreatedtime, bean.getStarttime()); + } + if (bean.getEndtime() > 0) { + node.le(PartnerFormRecord::getCreatedtime, bean.getEndtime()); + } + + Sheet sheet = dataSource.querySheet(PartnerFormRecord.class, flipper, node); + return render(sheet); +} +``` + +### PartnerCustomerService.java + +```java +@RestMapping(name = "list", comment = "分页查询客户列表") +public RetResult> list(PartnerCustomerFilterBean bean, Flipper flipper) { + FilterNode node = new FilterNode(); + node.notEq(PartnerCustomer::getStatus, (short) 3); + + if (!Utils.isEmpty(bean.getMobile())) { + node.and(PartnerCustomer::getMobile, bean.getMobile()); + } + if (bean.getStatus() > 0) { + node.and(PartnerCustomer::getStatus, bean.getStatus()); + } + if (bean.getStarttime() > 0) { + node.ge(PartnerCustomer::getCreatedtime, bean.getStarttime()); + } + if (bean.getEndtime() > 0) { + node.le(PartnerCustomer::getCreatedtime, bean.getEndtime()); + } + + Sheet sheet = dataSource.querySheet(PartnerCustomer.class, flipper, node); + return render(sheet); +} +``` + +--- + +## ⚠️ 注意事项 + +1. **方法调用方式**:所有条件方法都是 `FilterNode` 实例的方法,不是静态方法 +2. **方法命名**:`ge`/`le` 而非 `greateEqual`/`lessEqual` +3. **OR 条件**:必须创建独立的 `FilterNode` 对象进行链式调用 +4. **值比较**:等值条件直接传值,不需要 `FilterNode.eq()` +5. **LIKE 参数**:通常需要在参数中自行添加 `%` 通配符 + +--- + +## 📚 参考代码位置 + +- `flux-api/src/main/java/cn/casehub/partner/PartnerFormService.java` +- `flux-api/src/main/java/cn/casehub/partner/PartnerCustomerService.java` +- `flux-api/src/main/java/cn/casehub/partner/PartnerVisitService.java` + +--- + +# Redkale JSON 使用要点 + +> 更新时间:2026-03-03 +> 背景:速贷接口接入时错误使用 `@JsonProperty`(Jackson 注解),Redkale 不支持 + +--- + +## ❌ 常见错误 + +### 1. 使用 Jackson 注解 + +**错误写法**: +```java +// ❌ 错误:Redkale 不支持 Jackson 注解 +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonProperty("mobile_md5") +private String mobileMd5; +``` + +**正确方案**:使用 Redkale 的 `@ConvertColumn(name = "xxx")` 进行字段重命名: + +```java +// ✅ 正确:使用 @ConvertColumn 重命名 +import org.redkale.convert.ConvertColumn; + +@ConvertColumn(name = "mobile_md5") +private String mobileMd5; // Java驼峰,JSON下划线 +``` + +--- + +## ✅ JsonConvert 使用方式 + +### 序列化(对象 → JSON) + +```java +// 方式1:实例方法 +JsonConvert convert = JsonConvert.root(); +String json = convert.convertTo(object); + +// 方式2:静态调用 +String json = JsonConvert.root().convertTo(object); +``` + +### 反序列化(JSON → 对象) + +```java +// 单个对象 +User user = JsonConvert.root().convertFrom(User.class, json); + +// 泛型集合 +List users = JsonConvert.root().convertFrom( + new TypeToken>() {}.getType(), + json +); + +// Map(用于解析不确定结构的JSON) +Map map = JsonConvert.root().convertFrom(Map.class, json); +``` + +--- + +## 📋 字段映射规则 + +| Java 字段名 | JSON 字段名 | 说明 | +|------------|------------|------| +| `mobileMd5` | `mobileMd5` | 默认:驼峰 → 驼峰 | +| `mobile_md5` | `mobile_md5` | 默认:下划线 → 下划线 | +| `mobileMd5` + `@ConvertColumn(name="mobile_md5")` | `mobile_md5` | ✅ 重命名 | +| `mobileMd5` + `@ConvertColumn(ignore=true)` | - | 忽略字段 | + +**结论**:使用 `@ConvertColumn(name = "xxx")` 可实现字段重命名。 + +--- + +## 🔧 跨命名风格转换方案 + +当第三方接口使用下划线,内部使用驼峰时: + +### 方案1:@ConvertColumn + Utils.copy(推荐) + +```java +// 速贷请求Bean(Java驼峰,JSON下划线) +public class SuDaiRequest { + @ConvertColumn(name = "mobile_md5") + private String mobileMd5; + + @ConvertColumn(name = "city_id") + private String cityId; +} + +// 内部Bean(驼峰) +public class CollideBean { + private String mobileMd5; + private String cityId; +} + +// 转换器 - 使用 Utils.copy +public class Converter { + public static CollideBean toBean(SuDaiRequest req) { + return Utils.copy(new CollideBean(), req); // 一行搞定 + } +} +``` + +### 方案2:手动映射(不推荐) + +```java +// 速贷请求Bean(下划线) +public class SuDaiRequest { + private String mobile_md5; + private String city_id; +} + +// 转换器 - 手动映射 +public class Converter { + public static CollideBean toBean(SuDaiRequest req) { + CollideBean bean = new CollideBean(); + bean.setMobileMd5(req.getMobile_md5()); // 手动映射 + bean.setCityId(req.getCity_id()); + return bean; + } +} +``` + +--- + +## ⚠️ 注意事项 + +1. **使用 @ConvertColumn**:Redkale 的字段重命名注解,不是 Jackson 的 @JsonProperty +2. **@ConvertColumn 用法**: + - `@ConvertColumn(name = "xxx")` - 重命名JSON字段 + - `@ConvertColumn(ignore = true)` - 忽略字段 +3. **配合 Utils.copy**:重命名后,Java字段名一致,可用 `Utils.copy` 简化转换 diff --git a/dev-envs/flux/mysql-proxy.toml.example b/dev-envs/flux/mysql-proxy.toml.example new file mode 100644 index 0000000..f406fc3 --- /dev/null +++ b/dev-envs/flux/mysql-proxy.toml.example @@ -0,0 +1,29 @@ +# mysql-proxy 连接配置模板 +# 用法:复制为 mysql-proxy.toml 后填入真实值(mysql-proxy.toml 已在 .gitignore,不入库) +# 真实密码/IP 请勿提交,参见 docs/03-运维/开发环境搭建手册.md + +[server] +port = 3307 +host = "0.0.0.0" + +[pool] +default_max_connections = 5 +idle_timeout_secs = 300 +check_interval_secs = 60 + +[[connections]] +name = "flux_dev" +host = "" +port = 3306 +user = "root" +password = "" +database = "flux_dev" + +[[connections]] +name = "flux_prox" +host = "" +port = 3306 +user = "root" +password = "" +database = "flux_prox" +max_connections = 10 diff --git a/dev-envs/flux/norms.md b/dev-envs/flux/norms.md new file mode 100644 index 0000000..75b5394 --- /dev/null +++ b/dev-envs/flux/norms.md @@ -0,0 +1,102 @@ +# Flux 项目开发规范 + +> 本文件供 Claude Code / AI 助手阅读,统一开发规范认知 + +--- + +## Git 提交规范 + +``` +<类型>: <简述> +``` + +**类型**: 新增 | 修复 | 优化 | 重构 + +**禁止**: +- 英文提交信息 +- Co-Authored-By 尾巴 +- 提及工具名称(如 "generated by Claude") + +**示例**: +``` +新增: 渠道价格匹配规则引擎 +修复: 下游进件超时未回调处理 +优化: SQL 慢查询添加联合索引 +重构: UpstreamService 抽象三方对接接口 +``` + +## 技术栈与启动命令 + +| 模块 | 技术 | 目录 | 启动命令 | +|------|------|------|---------| +| flux-api | Java 17 + Redkale + Maven | flux-api/ | `cmd /c bin\restart.bat` 或 `mvn clean package` 后运行 | +| flux-admin | Vue 3 + Arco Design Pro + Vite + bun | flux-admin/ | `bun install && bun run dev` | +| flux-uniapp | UniApp (H5) + Vite | flux-uniapp/ | `npm install && npm run dev:h5` | +| flux-mock | Go (模拟三方) | flux-mock/ | `go run *.go` | +| flux-test | Go (业务链测试) | flux-test/ | `go run test_quick.go {channel}` | +| file-server | Go (文件上传) | file-server/ | `go run *.go` | +| flux-wechat-api | 微信服务 | flux-wechat-api/ | — | + +## 核心架构 + +``` +上游渠道 (10个) → UpstreamService → 下游对接 (美信/小薇/易贷通/CryptoApi) → 三方机构 +``` + +- **上游**: 小爱、速贷、趣来借、臻品借、微融花、腰贷钱包、演示、放心借、龙享花、可贷 +- **下游**: 美信钱包、美信B、小薇钱包、易贷通、有鑫钱包、源融花、闪融花、Mock +- **加密**: AES-CBC/ECB, CBC模式, 部分Base64密钥 + +## 代码审查要点 + +### Redkale 框架 (flux-api) +- FilterNode 使用规范 → 见 `/root/.kms/Redkale使用易错点.md` +- JSON 字段映射用 `@ConvertColumn`,注意命名转换 +- 数据库连接通过 `@Resource(name="lake")` 注入 +- 自定义 Render 用 `cn.casehub.base.TplRender` + +### 前端 (flux-admin) +- 组件库: Arco Design Pro +- 状态管理: Pinia +- 路由: Vue Router 4 + +### Go 服务 +- 交叉编译: `$env:GOOS="linux"; $env:GOARCH="amd64"; go build` +- 配置文件: 各模块独立 config.json + +## 命名规范 + +``` +{业务域}-{类型}-{名称} +``` + +| 前缀 | 含义 | +|------|------| +| flux | Flux 架构核心项目 | + +| 后缀 | 含义 | +|------|------| +| -api | 后端 API 服务 | +| -admin | 管理后台前端 | +| -web | 用户端 Web | +| -mp | 小程序 | +| -task | 定时任务 | +| -kit | 工具库/SDK | + +## 高危操作检查清单 + +- [ ] 改 Docker/系统配置 → 先备份,验证语法,再重启 +- [ ] 删数据库/容器 → 确认备份和影响范围 +- [ ] 生产环境操作 → 必须先在测试环境验证 +- [ ] 发版部署 → 通知相关人,准备回滚方案 +- [ ] 数据库 DML → 先 SELECT 预览,确认 WHERE 条件 + +## 模型选择参考 + +| 场景 | 推荐模型 | +|------|---------| +| 关键决策/技术选型 | glm-5 / deepseek-r1 | +| 复杂推理 | deepseek-r1 | +| 图片理解 | kimi-k2.5 / qwen-max | +| 日常开发操作 | kimi-k2.5 / deepseek-v3 | +| 简单任务 | glm-4.7-flash / qwen-turbo | diff --git a/dev-envs/flux/redis-proxy.toml.example b/dev-envs/flux/redis-proxy.toml.example new file mode 100644 index 0000000..51354f8 --- /dev/null +++ b/dev-envs/flux/redis-proxy.toml.example @@ -0,0 +1,17 @@ +# redis-proxy 连接配置模板 +# 用法:复制为 redis-proxy.toml 后填入真实值(redis-proxy.toml 已在 .gitignore,不入库) + +[server] +port = 3310 +host = "0.0.0.0" + +[pool] +idle_timeout_secs = 300 +check_interval_secs = 60 + +[[connections]] +name = "flux_dev" +host = "" +port = 6379 +password = "" +db = 0 diff --git a/dev-envs/flux/ssh-proxy.toml.example b/dev-envs/flux/ssh-proxy.toml.example new file mode 100644 index 0000000..650f710 --- /dev/null +++ b/dev-envs/flux/ssh-proxy.toml.example @@ -0,0 +1,25 @@ +# ssh-proxy 连接配置模板 +# 用法:复制为 ssh-proxy.toml 后填入真实值(ssh-proxy.toml 已在 .gitignore,不入库) +# 注意:server name 用短名(如 byr_pro),作为内部连接标识符 + +[server] +port = 3308 +host = "0.0.0.0" + +[pool] +idle_timeout_secs = 300 +check_interval_secs = 60 + +[[servers]] +name = "flux_dev" +host = "" +port = 22 +user = "root" +private_key = "/root/.ssh/id_ed25519" + +[[servers]] +name = "byr_pro" +host = "" +port = 22 +user = "root" +private_key = "/root/.ssh/id_ed25519" diff --git a/docker-compose-alpine.yml b/docker-compose-alpine.yml new file mode 100644 index 0000000..e86bd64 --- /dev/null +++ b/docker-compose-alpine.yml @@ -0,0 +1,24 @@ +# WorkPod Alpine 实例 +services: + workpod-alpine: + image: workpod-alpine:latest + container_name: workpod-alpine + hostname: workpod-alpine + privileged: true + tty: true + stdin_open: true + restart: unless-stopped + ports: + - "7683:7681" + - "7684:7682" + - "2223:22" + environment: + - TZ=Asia/Shanghai + - TERM=xterm-256color + volumes: + - ./data/workpod-alpine/workspace:/workspace + - ./data/workpod-alpine/root:/root + +networks: + default: + name: workpod-network diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 930a90a..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,55 +0,0 @@ -services: - workpod: - build: - context: . - dockerfile: Dockerfile - image: workpod:latest - container_name: workpod - hostname: workpod - - # 特权模式 - privileged: true - - # 端口映射 - ports: - - "2222:22" # SSH - - "8080:80" # HTTP - - "8443:443" # HTTPS - - "13306:3306" # MySQL - - "16379:6379" # Redis - - # 数据卷挂载 - volumes: - - /e/docker-data/workpod/mysql:/var/lib/mysql - - /e/docker-data/workpod/redis:/var/lib/redis - - /e/docker-data/workpod/workspace:/workspace - - ./config/supervisor:/etc/supervisor/conf.d - - # 环境变量 - environment: - - TZ=Asia/Shanghai - - TERM=xterm-256color - - LANG=en_US.UTF-8 - - LC_ALL=en_US.UTF-8 - - # PTY 支持 - tty: true - stdin_open: true - - # 资源限制 - deploy: - resources: - limits: - memory: 8G - reservations: - memory: 2G - - # 重启策略 - restart: unless-stopped - - # 工作目录 - working_dir: /workspace - -networks: - default: - name: workpod-network diff --git a/docs/00-规范/README.md b/docs/00-规范/README.md new file mode 100644 index 0000000..38b5a43 --- /dev/null +++ b/docs/00-规范/README.md @@ -0,0 +1,70 @@ +# WorkPod - 容器化开发环境 + +> 基于 Alpine 3.23 的轻量级开发容器,支持多项目隔离部署。 + +## 是什么 + +WorkPod 是一套 **Docker 容器化的全栈开发环境**,每个项目运行在独立容器中,通过 Web 终端(ttyd)或 SSH 访问。 + +## 核心特性 + +| 特性 | 说明 | +|------|------| +| 轻量镜像 | ~436MB(Alpine 多阶段构建) | +| 开箱即用 | Node.js 24 + Claude Code + ttyd + tmux | +| 工具外挂 | Rust/Go/Python 安装到 /root,自动检测 PATH | +| 项目隔离 | 每个项目独立容器、独立端口、独立网络 | +| 会话管理 | tmux session 支持 URL 参数 / 交互菜单 | + +## 快速开始 + +```bash +# 1. 构建基础镜像 +docker compose -f instances/base/docker-compose.yml build + +# 2. 启动基础实例 +docker compose -f instances/base/docker-compose.yml up -d + +# 3. 打开 Web 终端 +# http://localhost:7681 (用户: jc, 密码: 1234567) + +# 或 SSH 连接 +# ssh root@localhost -p 2222 (密码: workpod123) +``` + +## 目录结构 + +``` +workpod/ +├── Dockerfile # 基础镜像构建 +├── entrypoint.sh # 统一入口脚本 +├── ttyd-session.sh # tmux 会话管理 +├── instances/ # ★ 实例配置(每个项目一个目录) +│ ├── registry.yaml # 全局实例注册表 +│ ├── base/ # 基础实例 +│ ├── flux/ # Flux 项目实例 +│ └── .template/ # 新建实例模板 +├── docs/ # 文档体系 +└── data/ # 运行时数据 +``` + +## 文档索引 + +| 文档 | 位置 | 说明 | +|------|------|------| +| 实例管理指南 | `instances/README.md` | 端口规则、新建流程 | +| 实例注册表 | `instances/registry.yaml` | 所有实例的端口/状态 | +| 端口分配规则 | `docs/00-规范/端口分配规则.md` | 详细端口规划 | +| 实例创建流程 | `docs/00-规范/实例创建流程.md` | 步骤教程 | +| 架构设计 | `docs/01-架构/` | C4 图 + 组件关系 | +| 问题记录 | `docs/02-技术文档/ISSUES.md` | 历史问题与解决方案 | +| 部署指南 | `docs/03-运维/` | 本机 + 测试服部署 | +| 测试服实例 | `docs/03-运维/测试服实例.md` | 远程实例配置 | +| 审核报告 | `docs/04-审核/` | 安全/架构/Docker/Shell 审核 | + +## 相关链接 + +- 基础镜像:`workpod-alpine:latest`(基于 alpine:3.23) +- AI 工具:Claude Code (@anthropic-ai/claude-code) +- Web 终端:ttyd (Alpine apk) +- 会话管理:tmux + ttyd-session.sh diff --git a/docs/00-规范/实例创建流程.md b/docs/00-规范/实例创建流程.md new file mode 100644 index 0000000..76b6658 --- /dev/null +++ b/docs/00-规范/实例创建流程.md @@ -0,0 +1,118 @@ +# 实例创建流程 + +> 为新项目创建 WorkPod 开发容器的标准步骤。 + +--- + +## 前置条件 + +- Docker 已安装并运行 +- 基础镜像已构建:`docker images \| grep workpod-alpine` + +--- + +## 标准流程(3 步) + +### Step 1: 创建实例目录 + +```bash +cd E:/wk-lab/workpod +mkdir -p instances/{name} +cp instances/.template/docker-compose.yml instances/{name}/ +``` + +### Step 2: 编辑 compose 文件 + +打开 `instances/{name}/docker-compose.yml`,修改占位符: + +| 占位符 | 改为 | 示例 | +|--------|------|------| +| `{name}` | 实例名 | `suke` | +| `{SSH_PORT}` | SSH 端口 | `2211` | +| `{WEB_PORT}` | Web 端口 | `7711` | +| `{PROJECT_PATH}` | 项目路径 | `E:/wk-suke` | + +按需取消注释: +- **Java 项目** → 取消注释 JDK/Maven volume 和 JAVA_HOME/MAVEN_HOME env +- **home 持久化** → 确保 `../../data/home/{name}:/root` 已配置 + +### Step 3: 注册并启动 + +编辑 `instances/registry.yaml`,在 `local:` 下添加新条目: + +```yaml + - name: ws-{name}-dev + display: {中文名} + type: project + project: {name} + project_path: E:/wk-{name} + image: ws-{name}-dev:latest + ssh_port: {SSH_PORT} + web_port: {WEB_PORT} + network: workpod-{name}-network + status: starting + compose: instances/{name}/docker-compose.yml +``` + +启动: + +```bash +cd instances/{name} && docker compose up -d --build +``` + +验证: + +```bash +docker ps --filter name=ws-{name} +# 应看到端口映射正确 +``` + +更新状态:`registry.yaml` 中 `status: starting` → `running` + +--- + +## 可选:自定义镜像 + +如果项目需要额外工具(如 JDK),创建 `instances/{name}/Dockerfile`: + +```dockerfile +FROM workpod-alpine:latest + +LABEL org.opencontainers.image.title="ws-{name}-dev" \ + org.opencontainers.image.description="{描述}" + +EXPOSE 22 7681 + +ENTRYPOINT ["/entrypoint.sh"] +``` + +compose 中添加 build 配置: + +```yaml + build: + context: ../../ + dockerfile: instances/{name}/Dockerfile + image: ws-{name}-dev:latest +``` + +--- + +## 完整示例:创建 suke 实例 + +```bash +# 1. 创建目录 + 复制模板 +mkdir -p instances/suke +cp instances/.template/docker-compose.yml instances/suke/ + +# 2. 替换占位符(手动或 sed) +# {name} → suke, {SSH_PORT} → 2211, {WEB_PORT} → 7711, {PROJECT_PATH} → E:/wk-suke + +# 3. 注册到 registry.yaml(添加 local 条目) + +# 4. 启动 +cd instances/suke && docker compose up -d --build + +# 5. 验证 +docker ps --filter name=ws-suke +# curl -s http://localhost:7711 | head -5 # ttyd 可访问 +``` diff --git a/docs/00-规范/端口分配规则.md b/docs/00-规范/端口分配规则.md new file mode 100644 index 0000000..0ead34f --- /dev/null +++ b/docs/00-规范/端口分配规则.md @@ -0,0 +1,61 @@ +# 端口分配规则 + +> 最后更新:2026-04-07 + +--- + +## 规则总览 + +### 本机端口 + +``` +SSH: 22xx (容器内统一为 22) +Web: 77xx (容器内统一为 7681) +``` + +| 类型 | SSH 前缀 | Web 前缀 | 范围 | 示例 | +|------|---------|---------|------|------| +| 基础/通用 | 220-221 | 768-769 | 2201-2210 / 7681-7690 | base=2222/7681 | +| **项目开发** | **221-229** | **770-779** | **2211-2290 / 7701-7790** | flux=2201/7701 | + +### 测试服端口 (flux_dev @ 39.99.243.191) + +复用本机规则,Web 端口做偏移避免冲突: + +| 实例 | SSH | Web | 域名 | +|------|-----|-----|------| +| workpod-1 | 2221 | 7681 | wk.1216.top | +| workpod-alpine | 2222 | 7683 | wk2.1216.top | +| workpod-ada | 2224 | 7685 | ada.1216.top | +| workpod-yxl | 2226 | 7686 | yxl.1216.top | + +--- + +## 已分配端口表 + +### 本机 + +| # | 实例 | SSH | Web | 网络 | 状态 | +|---|------|-----|-----|------|------| +| 1 | workpod-alpine (base) | 2222 | 7681 | workpod-alpine-network | running | +| 2 | workpod-test | 2223 | 7682 | workpod-test-network | running | +| 3 | ws-flux-dev | 2201 | 7701 | workpod-flux-network | running | + +### 预留 + +| # | 项目 | SSH | Web | 技术栈 | +|---|------|-----|-----|--------| +| 4 | suke | 2211 | 7711 | Java + Flutter | +| 5 | case | 2212 | 7712 | Java + Vue | +| 6 | hszd | 2213 | 7713 | Vue | +| 7 | pm | 2214 | 7714 | 待确认 | +| 8 | me | 2215 | 7715 | 待确认 | + +--- + +## 分配原则 + +1. **项目实例从 2211 开始** — 2201-2210 保留给 base/test 等通用实例 +2. **SSH 和 Web 同编号** — flux 用 2201/7701,suke 用 2211/7711,便于记忆 +3. **测试服 Web 偏移** — 同一实例测试服 Web = 本机 Web + 偏移值(通常 +2) +4. **注册后再占用** — 新增前先查 registry.yaml 和 `netstat -ano \| grep :22xx` diff --git a/docs/02-技术文档/ISSUES.md b/docs/02-技术文档/ISSUES.md new file mode 100644 index 0000000..ed50a06 --- /dev/null +++ b/docs/02-技术文档/ISSUES.md @@ -0,0 +1,122 @@ +# WorkPod Alpine 问题记录 + +> 最后更新:2026-04-02 + +--- + +## Alpine 无 `ss` 命令 + +HEALTHCHECK 使用 `ss -tlnp` 报错 `ss: not found`。 + +**解决**:改用 busybox 内置的 `netstat -tlnp`。 + +--- + +## docker-compose healthcheck 覆盖镜像 HEALTHCHECK + +修复了 Dockerfile 中的 HEALTHCHECK,但容器仍报 `ss` 错误。 + +**原因**:docker-compose.yml 中的 `healthcheck:` 字段会覆盖镜像的 HEALTHCHECK 指令。 + +**解决**:同步修改 docker-compose.yml 中的 healthcheck 命令。 + +--- + +## `COPY --chmod` 需要 BuildKit + +`COPY --chmod=755 entrypoint.sh /entrypoint.sh` 报错 `the --chmod option requires BuildKit`。 + +**解决**:拆为两步: +```dockerfile +COPY entrypoint.sh /entrypoint.sh +RUN chmod 755 /entrypoint.sh +``` + +--- + +## BuildKit 缓存不失效 HEALTHCHECK + +修改 HEALTHCHECK 后重新 build,镜像中仍是旧命令。 + +**原因**:BuildKit 缓存了 HEALTHCHECK 的 metadata 层。 + +**解决**:使用 `DOCKER_BUILDKIT=0` 经典构建器。 + +--- + +## ttyd execvp failed: No such file or directory + +ttyd 启动命令使用了容器中不存在的程序(如 `claude`、`tmux`)。 + +**原因**:ttyd 子进程的 PATH 可能不完整,或镜像未安装对应软件。 + +**解决**:确保 ttyd 启动的命令在镜像中已安装,且使用完整路径或先验证可用性。 + +--- + +## ttyd 静态资源 404 + +浏览器访问 ttyd 时 `style.css`、`js` 等 404。 + +**说明**:ttyd 1.7.4 将资源内联到 HTML 中,单独文件的 404 是预期行为,页面功能正常。 + +--- + +## 容器重建丢失数据 + +`docker compose down && up` 后 /root 下的 Rust 安装等文件丢失。 + +**原因**:只有 bind mount 的路径才会持久化,未挂载的目录随容器销毁。 + +**解决**:将需要持久化的目录通过 volumes 挂载。 + +--- + +## ttyd 断线后会话丢失 + +ttyd 默认启动 `bash`,断线后进程被 kill,会话无法恢复。 + +**解决**:使用 `tmux new -A -s workpod` 替代 `bash`,需要镜像安装 `tmux`。 + +--- + +## Web 终端中文输入不了 + +ttyd + tmux 环境下无法输入中文字符。 + +**原因**:容器未设置 `LANG` 环境变量,终端无 UTF-8 编码支持。 + +**解决**:添加 `LANG=C.UTF-8` 到 Dockerfile ENV 和 docker-compose environment。 + +--- + +## tmux 多用户共享同一 session + +使用 `tmux new -A -s workpod` 时所有用户进入同一个 session,互相可见。 + +**解决**:通过 `ttyd-session.sh` 脚本实现 session 选择——支持 URL 参数 `?session=name` 直接进入,或交互式菜单选择/创建。 + +注意:`tmux attach-session -d` 会踢掉旧连接,应使用 `tmux attach-session`(不加 `-d`)实现共享模式。 + +--- + +## 容器重建后手动安装的工具丢失 + +在容器内手动安装的工具,解压到 `/usr/local` 等非挂载目录的会在重建后丢失。 + +**解决**:所有工具解压到 `/root` 下,entrypoint 自动检测并设置 PATH。 + +### 安装方式 + +| 工具 | 安装命令 | 存储位置 | +|------|---------|---------| +| Rust | `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \| sh` | `/root/.cargo` `/root/.rustup` | +| Python | `curl https://pyenv.run` | `/root/.pyenv` | +| Go | `tar -xzf go1.xx.linux-amd64.tar.gz -C /root` | `/root/go` | +| pip 用户包 | `pip install --user xxx` | `/root/.local` | + +### 原则 + +- 解压目录用 `/root` 而不是 `/usr/local` +- 不要用 `apk add` 装开发工具(重建会丢),需要的加到 Dockerfile +- entrypoint 自动检测 `/root` 下的工具目录,写入 `/etc/profile.d/dev-tools.sh` diff --git a/docs/03-运维/开发环境搭建手册.md b/docs/03-运维/开发环境搭建手册.md new file mode 100644 index 0000000..ab9da90 --- /dev/null +++ b/docs/03-运维/开发环境搭建手册.md @@ -0,0 +1,1030 @@ +# WorkPod 开发环境容器搭建手册 + +> 版本:1.1 | 更新日期:2026-04-07 +> 用途:运维 AI 按此文档执行,为任意项目快速搭建完整的 Docker 开发环境 + +--- + +## 0. 设计原则 + +| 原则 | 说明 | +|------|------| +| **挂载优先** | 代理工具/知识库/配置全部从宿主机挂载,不写入镜像 | +| **信息隔离** | 每个容器只挂载该项目相关的配置和文档,禁止无关账号/服务器信息 | +| **可复用** | 一套模板适用于 flux/suke/lab/case 等任何项目 | +| **声明式** | 配置即环境,改 YAML 即换环境 | +| **持久化** | home 目录独立挂载,重建容器不丢配置 | + +### 架构总览 + +``` +┌─ 宿主机 (Windows) ─────────────────────────────────────────────┐ +│ │ +│ E:/wk-oth/rust-work/dist-Linux/ │ +│ ├── mysql-proxy ───┐ │ +│ ├── ssh-proxy ───┤ │ +│ ├── redis-proxy ───┼──→ [挂载 ro] → 容器 /usr/local/bin/ │ +│ └── mongo-proxy ───┘ │ +│ │ +│ C:/Users/{user}/.ssh/id_ed25519 ──→ [挂载 ro] → /root/.ssh/ │ +│ │ +│ E:/wk-flux/ ─────────→ [挂载] → 容器 /workspace │ +│ │ +│ workpod/dev-envs/{project}/ │ +│ ├── servers.yaml ──→ [挂载 ro] → /root/.dev-env/servers.yaml │ +│ ├── databases.yaml ──→ [挂载 ro] → /root/.dev-env/databases.yaml │ +│ ├── norms.md ──→ [挂载 ro] → /root/.dev-env/norms.md │ +│ ├── ssh-proxy.toml ──→ [挂载 ro] → /root/.dev-env/ssh-proxy.toml │ +│ ├── mysql-proxy.toml ──→ [挂载 ro] → /root/.dev-env/mysql-proxy.toml│ +│ ├── redis-proxy.toml ──→ [挂载 ro] → /root/.dev-env/redis-proxy.toml│ +│ ├── docs/ ──→ [挂载 ro] → /root/.dev-env/docs/ │ +│ └── claude/ ──→ [挂载 ro] → /root/.dev-env/claude/ │ +│ ├── CLAUDE.md │ +│ └── settings.local.json │ +│ │ +│ workpod/data/home/{project}-dev/ ──→ [挂载 rw] → /root │ +│ ├── .gitconfig │ +│ ├── .bashrc ← claude wrapper + 代理自动启动 │ +│ ├── .claude/ ← Claude Code 运行时数据 │ +│ └── ... │ +│ │ +│ instances/{project}/.env ──→ ANTHROPIC_AUTH_TOKEN=xxx │ +│ │ +└────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 1. 快速搭建(以 Flux 项目为例) + +### 1.1 创建项目环境目录 + +```bash +# 在 workpod 下创建项目级配置目录 +mkdir -p E:/wk-lab/workpod/dev-envs/flux/claude +mkdir -p E:/wk-lab/workpod/data/home/flux-dev + +# 复制代理工具(或符号链接) +cp E:/wk-oth/rust-work/dist-Linux/* E:/wk-lab/workpod/dev-envs/flux/bin/ +# 或(推荐,自动同步最新版): +# ln -s E:/wk-oth/rust-work/dist-Linux E:/wk-lab/workpod/dev-envs/flux/bin +``` + +### 1.2 编写项目配置文件 + +以下 4 个文件是每个项目的**必需配置**,按实际修改即可: + +#### `dev-envs/flux/servers.yaml` — 服务器信息 + +```yaml +# Flux 项目服务器清单 (仅含 Flux 相关,信息隔离) +project: flux +description: 贷款撮合平台 +last_updated: "2026-04-07" + +servers: + # === 生产环境 === + - name: prod_baiyarong + display: 百雅融生产服 + host: 101.201.107.111 + port: 22 + user: root + deploy_dir: /opt/ + jdk_path: /opt/app/ + services: + - name: flux-api + dir: /opt/flux-api + restart: "cd /opt/flux-api && bash bin/restart.sh" + health_check: "curl -sf http://localhost:6080/partnerapi/ping" + - name: flux-admin + dir: /opt/flux-admin + - name: flux-mock + dir: /opt/flux-mock + - name: file-server + dir: /opt/file-server + port: 8071 + domains: + - api.baiyarong.cn + - r.baiyarong.cn + + # === 测试环境 (flux_dev) === + - name: dev_test + display: 测试机 (flux_dev) + host: 39.99.243.191 + port: 22 + user: root + deploy_dir: /opt/ + docker: true + jdk_path: /opt/jdk-25 + services: + - name: mysql + port: 3306 + image: mysql:8.0 + - name: redis + port: 6379 + image: redis:7.4 + password: + - name: workpod-1 + web_port: 7681 + domain: wk.1216.top + - name: workpod-alpine + web_port: 7683 + domain: wk2.1216.top + domains: + - fluxapi.1216.top + - flux.1216.top + - wk.1216.top +``` + +> **注意**: ssh-proxy.toml 中的 server name 用短名(如 `byr_pro`),与 servers.yaml 的显示名不同。 +> 这是正常的:ssh-proxy.toml 的 `name` 是内部连接标识符,servers.yaml 的 `name` 是人工可读名称。 + +#### `dev-envs/flux/databases.yaml` — 数据库连接 + +```yaml +# Flux 项目数据库连接 (仅含 Flux 相关) +project: flux +last_updated: "2026-04-07" + +mysql: + # --- 测试环境 --- + - name: flux_dev + env: test + host: 39.99.243.191 + port: 3306 + database: flux_dev + users: + - username: root + password: + role: admin + - username: u_flux + password: + role: app + proxy_name: flux_dev # mysql-proxy 连接名 + + # --- 生产环境 --- + - name: flux_prox + env: prod + host: 101.201.107.111 + port: 3306 + database: flux_prox + users: + - username: root + password: + role: admin + proxy_name: flux_prox + + - name: flux_mp + env: prod + host: 101.201.107.111 + port: 3306 + database: flux_mp + users: + - username: u_mp + password: + role: app + note: 小程序数据库 + +redis: + - name: flux_dev + env: test + host: 39.99.243.191 + port: 6379 + password: + proxy_name: flux_dev # redis-proxy 连接名 + +feishu_bot: + workspace: flux + app_id: + app_secret: + open_platform: https://open.feishu.cn/app/ + +# === 代理工具速查 === +# mysql-proxy cli -c flux_dev -e "SQL" # 查询 +# mysql-proxy cli -c flux_dev -e "SQL" -F json # JSON输出 +# mysql-proxy cli -c flux_dev -e "DML" -x # 执行写入 +# ssh-proxy exec -n flux_dev -c "command" # 远程执行 +# ssh-proxy exec -n flux_dev -c "command" -F json # JSON输出 +# redis-proxy get -c flux_dev -k "key" # 取值 +# redis-proxy run -c flux_dev -C "KEYS" -a "user:*" # 通用命令 +``` + +#### `dev-envs/flux/norms.md` — 开发规范摘要 + +```markdown +# Flux 项目开发规范 + +## Git 提交规范 + +格式:`<类型>: <简述>` + +类型:新增 | 修复 | 优化 | 重构 + +禁止: +- 英文提交信息 +- Co-Authored-By 尾巴 +- 提及工具名称 + +## 技术栈 + +| 模块 | 技术 | 启动命令 | +|------|------|---------| +| flux-api | Java 17 + Redkale + Maven | cmd /c bin\restart.bat | +| flux-admin | Vue 3 + Arco Design + Vite + bun | bun run dev | +| flux-uniapp | UniApp + Vite | bun run dev:h5 | +| flux-mock | Go | go run *.go | +| flux-test | Go | go run test_quick.go {channel} | +| file-server | Go | go run *.go | + +## 代码审查要点 + +- Redkale FilterNode 使用规范(见 .kms/Redkale使用易错点.md) +- JSON 字段映射 @ConvertColumn 注意事项 +- SQL 慢查询优化(见 .kms/MySQL慢查询分析.md) + +## 命名规范 + +前缀: flux-{type}-{name} +类型后缀: -api(后端) | -admin(管理后台) | -web(用户端) | -app(移动端) + +## 高危操作提醒 + +- 改 Docker/系统配置: 先备份,改完验证语法,再重启 +- 删数据库/容器: 先确认备份和影响范围 +- 生产操作: 必须先在测试环境验证 +``` + +#### `dev-envs/flux/claude/CLAUDE.md` — Claude Code 项目指令 + +```markdown +# Flux 开发环境 + +> 当前在 ws-flux-dev 容器内,工作目录 /workspace = E:/wk-flux + +## 项目概述 + +贷款撮合平台,核心模块: +- **flux-api**: 后端 API (Java 17 + Redkale) +- **flux-admin**: 管理后台 (Vue 3 + Arco Design) +- **flux-uniapp**: H5 前端 (UniApp) +- **flux-mock**: 三方模拟服务 (Go) +- **flux-test**: 业务链测试 (Go) +- **file-server**: 文件服务 (Go) + +## 可用工具 + +### 代理工具(已挂载到 /usr/local/bin/) +```bash +mysql-proxy cli -c flux_dev -e "SQL" # MySQL 查询 +ssh-proxy exec -n flux_dev -c "command" # SSH 远程执行 +redis-proxy get -c flux_dev -k "key" # Redis 操作 +mongo-proxy find -c suke_dev -C collection # MongoDB 查询 +fet GET https://api.example.com # HTTP 请求 +``` + +### 连接信息速查 +详见 `/root/.dev-env/servers.yaml` 和 `/root/.dev-env/databases.yaml` + +## 开发命令 + +```bash +# flux-api (Redkale/Java) +cd /workspace/flux-api && mvn clean package +cmd /c bin\restart.bat + +# flux-admin (Vue3/bun) +cd /workspace/flux-admin && bun install && bun run dev + +# flux-uniapp (UniApp) +cd /workspace/flux-uniapp && npm install && npm run dev:h5 + +# Go 服务 +cd /workspace/flux-mock && go run *.go +cd /workspace/flux-test && go run test_quick.go qulaijie +``` + +## 规范 + +- Git 提交: `<类型>: <简述>` 类型=新增|修复|优化|重构 +- 禁止英文提交、禁止 Co-Authored-By 尾巴 +- 详见 `/root/.dev-env/norms.md` + +## 开发规范 + +> **唯一事实来源**: `/root/.dev-env/norms.md` +> 本文件不重复规范内容,避免维护不一致。 + +执行任务前先读取 norms.md,关键规则: +- Git 提交、代码审查、命名规范、高危操作检查清单 + +## 项目知识库 + +Flux 专属文档位于 `/root/.dev-env/docs/`: + +| 文件 | 用途 | +|------|------| +| Redkale使用易错点.md | **flux-api 必读** — 框架踩坑记录 | +| MySQL慢查询分析.md | SQL 慢查询优化参考 | +``` + +#### `dev-envs/flux/claude/settings.local.json` — Claude Code 权限 + +```json +{ + "permissions": { + "allow": [ + "Bash(mysql-proxy *)", + "Bash(ssh-proxy *)", + "Bash(redis-proxy *)", + "Bash(mongo-proxy *)", + "Bash(mvn *)", + "Bash(java *)", + "Bash(javac *)", + "Bash(go *)", + "Bash(bun *)", + "Bash(npm *)", + "Bash(node *)", + "Bash(cmd *)", + "Bash(git *)", + "Bash(docker *)", + "Bash(curl *)", + "Bash(wget *)", + "Bash(cat *)", + "Bash(ls *)", + "Bash(grep *)", + "Bash(find *)", + "Read", + "Write", + "Edit" + ], + "deny": [] + } +} +``` + +> **注意**: `fet` 暂未挂载(Linux 版未编译),故不在权限列表中。编译后需添加 `Bash(fet *)`。 + +### 1.3 配置持久化 Home 目录 + +`data/home/flux-dev/.gitconfig`: + +```ini +[user] + name = 绝尘 + email = dev@1216.top + +[core] + autocrlf = input + quotepath = false + +[push] + default = simple +``` + +### 1.4 生成 Compose 文件 + +从模板生成 `instances/flux/docker-compose.yml`: + +```yaml +services: + ws-flux-dev: + build: + context: ../../ + dockerfile: instances/flux/Dockerfile + image: ws-flux-dev:latest + container_name: ws-flux-dev + hostname: ws-flux-dev + restart: unless-stopped + + ports: + - "2201:22" # SSH + - "7701:7681" # Web 终端 ttyd + + volumes: + # ===== 项目源码 ===== + - E:/wk-flux:/workspace + + # ===== 开发工具 (JDK/Maven) ===== + - D:/Java/jdk-musl-17:/opt/jdk-musl-17:ro + - D:/Java/maven-mvnd-3.9.14:/opt/maven-mvnd-3.9.14:ro + + # ===== 代理工具 (Linux static binaries) ===== + - E:/wk-oth/rust-work/dist-Linux/mysql-proxy:/usr/local/bin/mysql-proxy:ro + - E:/wk-oth/rust-work/dist-Linux/ssh-proxy:/usr/local/bin/ssh-proxy:ro + - E:/wk-oth/rust-work/dist-Linux/redis-proxy:/usr/local/bin/redis-proxy:ro + - E:/wk-oth/rust-work/dist-Linux/mongo-proxy:/usr/local/bin/mongo-proxy:ro + + # ===== SSH 密钥 + 代理配置 (仅该项目相关) ===== + - C:/Users/{user}/.ssh/id_ed25519:/root/.ssh/id_ed25519:ro + - ../../dev-envs/flux/ssh-proxy.toml:/root/.dev-env/ssh-proxy.toml:ro + - ../../dev-envs/flux/mysql-proxy.toml:/root/.dev-env/mysql-proxy.toml:ro + - ../../dev-envs/flux/redis-proxy.toml:/root/.dev-env/redis-proxy.toml:ro + + # ===== 项目级配置 (只读挂载) ===== + - ../../dev-envs/flux/servers.yaml:/root/.dev-env/servers.yaml:ro + - ../../dev-envs/flux/databases.yaml:/root/.dev-env/databases.yaml:ro + - ../../dev-envs/flux/norms.md:/root/.dev-env/norms.md:ro + - ../../dev-envs/flux/docs:/root/.dev-env/docs:ro + - ../../dev-envs/flux/claude/CLAUDE.md:/root/.dev-env/claude/CLAUDE.md:ro + - ../../dev-envs/flux/claude/settings.local.json:/root/.dev-env/claude/settings.local.json:ro + + # ===== 持久化 Home (读写挂载) ===== + - ../../data/home/flux-dev:/root + + environment: + - TZ=Asia/Shanghai + - TERM=xterm-256color + - ROOT_PASSWORD=${ROOT_PASSWORD:-workpod123} + - TTYD_CREDENTIALS=${TTYD_CREDENTIALS:-jc:1234567} + - WORKPOD_PROJECT=flux + - JAVA_HOME=/opt/jdk-musl-17 + - MAVEN_HOME=/opt/maven-mvnd-3.9.14 + # === Claude Code (智谱 AI, 从 .env 读取密钥) === + - ANTHROPIC_BASE_URL=https://open.bigmodel.cn/api/anthropic + - ANTHROPIC_API_KEY=${ANTHROPIC_AUTH_TOKEN} + - ANTHROPIC_DEFAULT_SONNET_MODEL=glm-5v-turbo + - ANTHROPIC_DEFAULT_OPUS_MODEL=glm-5.1 + - ANTHROPIC_DEFAULT_HAIKU_MODEL=glm-4.5-air + + deploy: + resources: + limits: + memory: 4G + reservations: + memory: 1G + + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + + healthcheck: + test: ["CMD-SHELL", "netstat -tlnp 2>/dev/null | grep -qE ':(22|7681)\\b' || exit 1"] + interval: 30s + timeout: 5s + start_period: 15s + retries: 3 + + working_dir: /workspace + tty: true + stdin_open: true + +networks: + default: + external: true + name: workpod-{project}-network +``` + +> **`.env` 文件**: 在 `instances/{project}/` 下创建 `.env`,存放敏感信息(不进 git): +> ```bash +> # Claude Code API Key (智谱 AI) +> ANTHROPIC_AUTH_TOKEN=your_token_here +> ``` + +### 1.5 启动与验证 + +```bash +# 构建并启动 +cd E:/wk-lab/workpod +docker build -t ws-flux-dev:latest -f instances/flux/Dockerfile . +docker compose -f instances/flux/docker-compose.yml up -d + +# 验证 1: 代理工具 +MSYS_NO_PATHCONV=1 docker exec ws-flux-dev bash -lc ' + echo "=== 代理工具 ===" + mysql-proxy --version && echo " ✓ mysql-proxy" + ssh-proxy --version && echo " ✓ ssh-proxy" + redis-proxy --version && echo " ✓ redis-proxy" + mongo-proxy --version && echo " ✓ mongo-proxy" + + echo "" + echo "=== 项目知识库 (仅该项目相关) ===" + ls /root/.dev-env/docs/ + + echo "" + echo "=== 项目配置 ===" + cat /root/.dev-env/servers.yaml | head -5 + cat /root/.dev-env/databases.yaml | head -5 + + echo "" + echo "=== Git 配置 ===" + git config --global --list +' + +# 验证 2: Web 终端访问 +# 打开 http://localhost:7701 用户 jc / 1234567 + +# 验证 3: SSH 访问 +ssh root@localhost -p 2201 # 密码 workpod123 +``` + +--- + +## 2. 为新项目快速搭建(模板流程) + +当需要为新项目(如 suke、lab、case)搭建开发环境时,只需 **3 步**: + +### Step 1: 复制模板目录 + +```bash +# 以 suke 项目为例 +PROJECT=suke +SSH_PORT=2202 +WEB_PORT=7702 +PROJECT_PATH=E:/wk-suke + +mkdir -p E:/wk-lab/workpod/dev-envs/$PROJECT/claude +mkdir -p E:/wk-lab/workpod/data/home/$PROJECT-dev +``` + +### Step 2: 编写 4 个配置文件 + +复制 flux 的配置文件作为模板,修改以下内容: + +| 文件 | 需修改项 | +|------|---------| +| `servers.yaml` | 该项目的服务器列表、部署路径、域名 | +| `databases.yaml` | 该项目的数据库连接、Redis、第三方密钥 | +| `norms.md` | 该项目的技术栈、启动命令、特殊规范 | +| `claude/CLAUDE.md` | 项目描述、模块说明、可用命令 | + +### Step 3: 生成并启动 compose + +从上面的 compose 模板复制,修改: +- `container_name` / `hostname` +- `ports` (SSH/Web) +- `volumes` 中的项目路径和 dev-envs 路径 +- `environment` 中的项目特定变量 +- `networks.name` + +```bash +# 一键启动 +docker compose -f instances/$PROJECT/docker-compose.yml up -d +``` + +--- + +## 3. 挂载资源清单 + +### 3.1 代理工具(只读挂载) + +| 工具 | 宿主机源 | 大小 | 容器路径 | 用途 | +|------|---------|------|---------|------| +| mysql-proxy | `dist-Linux/mysql-proxy` | 8.9MB | `/usr/local/bin/mysql-proxy` | MySQL 查询代理 (:3307) | +| ssh-proxy | `dist-Linux/ssh-proxy` | 4.6MB | `/usr/local/bin/ssh-proxy` | SSH 命令代理 (:3308) | +| redis-proxy | `dist-Linux/redis-proxy` | 3.4MB | `/usr/local/bin/redis-proxy` | Redis 命令代理 (:3310) | +| mongo-proxy | `dist-Linux/mongo-proxy` | 6.4MB | `/usr/local/bin/mongo-proxy` | MongoDB 代理 (:3309) | + +> 全部为 **static-pie** 链接,无动态依赖,兼容 Alpine musl。 + +### 3.1b SSH 密钥 + 代理配置(只读挂载) + +| 资源 | 宿主机源 | 容器路径 | 用途 | +|------|---------|---------|------| +| SSH 私钥 | `C:/Users/{user}/.ssh/id_ed25519` | `/root/.ssh/id_ed25519` | ssh-proxy 认证远程服务器 | +| ssh-proxy.toml | `dev-envs/{project}/ssh-proxy.toml` | `/root/.dev-env/ssh-proxy.toml` | SSH 代理服务配置 (仅该项目服务器) | +| mysql-proxy.toml | `dev-envs/{project}/mysql-proxy.toml` | `/root/.dev-env/mysql-proxy.toml` | MySQL 代理配置 (仅该项目连接) | +| redis-proxy.toml | `dev-envs/{project}/redis-proxy.toml` | `/root/.dev-env/redis-proxy.toml` | Redis 代理配置 (仅该项目连接) | + +> **信息隔离**: 每个 `.toml` 只包含当前项目的连接/服务器,禁止混入其他项目。 + +> **fet 待补充**: 目前仅有 Windows 版 (`PE32+`),需要交叉编译 Linux 版。编译命令: +> ```bash +> cd E:/wk-oth/rust-work/fet +> rustup target add x86_64-unknown-linux-musl +> cargo build --release --target x86_64-unknown-linux-musl +> ``` + +### 3.2 项目专属知识库(只读挂载) + +> **重要原则**: 只挂载与当前项目相关的文档,避免无关信息干扰和误操作。 + +宿主机: `workpod/dev-envs/{project}/docs/` → 容器: `/root/.dev-env/docs/` + +从全局 KMS 中**按需筛选**该项目需要的文档复制到此目录: + +| 文件 | 来源 | 适用项目 | +|------|------|---------| +| Redkale使用易错点.md | ~/.claude/kms/ | flux (flux-api 用 Redkale) | +| MySQL慢查询分析.md | ~/.claude/kms/ | 有 MySQL 的项目 | +| 其他框架/业务文档 | ~/.claude/kms/ 或项目自身 | 按需 | + +**禁止挂载到容器的内容**: +- `账号信息.md` — 包含所有项目密码,有泄露和误操作风险 +- 其他项目的服务器/数据库信息 — 避免连接错误环境 + +### 3.3 项目级配置(只读挂载) + +宿主机: `workpod/dev-envs/{project}/` → 容器: `/root/.dev-env/` + +| 文件 | 用途 | +|------|------| +| servers.yaml | 该项目涉及的服务器 IP/端口/用途/域名 | +| databases.yaml | MySQL/Redis 连接信息 + 第三方密钥 | +| norms.md | 开发规范摘要 (Git/代码审查/命名/高危操作) | +| claude/CLAUDE.md | Claude Code 项目指令 | +| claude/settings.local.json | Claude Code 权限配置 | + +### 3.4 持久化 Home(读写挂载) + +宿主机: `workpod/data/home/{project}-dev/` → 容器: `/root` + +> **只存放运行时产生的文件**,不存放项目配置(配置在 `dev-envs/` 中) + +| 文件 | 来源 | 说明 | +|------|------|------| +| `.gitconfig` | 手动配置 | Git 用户名/邮箱/Gitea 凭据 | +| `.git-credentials-gitea` | 手动配置 | Gitea 密码 (权限 600) | +| **`.bashrc`** | **手动编写** | **★ claude wrapper + 代理自动启动 (见第 5 节)** | +| `.claude/` | 自动产生 | Claude Code 运行时数据 (sessions/settings/history) | +| `.bun/` | 容器内安装 | bun 运行时安装的包 | +| `.npm/` | 容器内产生 | npm 缓存 | +| `.chelper/` | 容器内产生 | coding-helper 数据 | +| `.ash_history` | 自动产生 | 命令历史 | + +### 3.4b 自定义技能(读写挂载) + +> **不同项目容器需要不同的技能内容**,按项目需求选择性同步。 + +宿主机: `~/.claude/skills/{name}/skill.md` → 容器: `/root/.claude/skills/{name}/skill.md` + +技能文件放在 `data/home/{project}-dev/.claude/skills/` 下,随 `/root` 持久化挂载自动同步。 + +#### 技能清单 + +| 技能名 | 触发词 | 用途 | 适用项目 | +|--------|--------|------|---------| +| **commit** | commit / 提交 | Git 提交规范(中文、类型前缀) | 所有项目 | +| **review** | review / 审查 | 代码审查(Redkale/Java/Go/SQL 风格偏好) | Java/Go 项目 | +| **taskhub** | taskhub / 任务 | 任务管理(ID 规范、todo.md 同步看板) | 所有项目 | + +#### 技能来源与同步方式 + +```bash +# 从本机复制到指定项目的容器 home +cp ~/.claude/skills/commit/skill.md data/home/flux-dev/.claude/skills/commit/ +cp ~/.claude/skills/review/skill.md data/home/flux-dev/.claude/skills/review/ +cp ~/.claude/skills/taskhub/skill.md data/home/flux-dev/.claude/skills/taskhub/ + +# 验证 +docker exec {container} ls /root/.claude/skills/*/skill.md +``` + +#### 项目差异示例 + +| 项目 | 推荐技能 | 说明 | +|------|----------|------| +| flux (Java+Redkale) | commit + review + taskhub | review 含 Redkale 易错点 | +| suke (Go) | commit + review + taskhub | review 侧重 Go 规范 | +| lab (Node) | commit + taskhub | 可不需要 review(或简化版) | + +> **原则**: 技能内容应匹配项目技术栈。Java 项目的 review 不应包含 Go 规则,反之亦然。 + +#### 各项目技能差异示例 + +**review 技能按项目定制**: + +| 项目 | tech stack | review 应包含 | review 不应包含 | +|------|------------|---------------|-----------------| +| flux | Java 17 + Redkale | FilterNode 用法、`@ConvertColumn`、Entity 承担逻辑 | Go 惯例、Node 规范 | +| suke | Go + Gin | `if err != nil`、`gofmt`、接口设计 | Redkale 注解、Java 命名 | +| lab | Node.js + Vue | ESLint 规则、异步模式、TypeScript 类型 | Java/Go 编译规范 | + +**操作方式**:为每个项目维护独立的 `skill.md`,从通用模板复制后按技术栈裁剪。 + +```bash +# 示例: 为 suke 项目定制 review 技能 (去除 Java/Redkale 部分) +cp ~/.claude/skills/review/skill.md data/home/suke-dev/.claude/skills/review/ +# 然后编辑 data/home/suke-dev/.claude/skills/review/skill.md +# 删除 Redkale/Java 章节,保留 Go + 通用审查流程 +``` + +--- + +## 4. 容器内工具安装(重建后需重新执行) + +> 基础镜像只包含最小工具集(curl/git/tmux/ttyd/node)。以下工具需要在容器启动后安装。 +> 安装命令写入 `.bashrc` 可实现自动安装,但首次仍需手动执行。 + +### 4.1 一键安装脚本 + +在宿主机执行(或进入容器后执行): + +```bash +MSYS_NO_PATHCONV=1 docker exec {container_name} bash -c ' +# ===== 1. 系统工具 (apk) ===== +apk add --no-cache vim jq htop + +# ===== 2. Bun (JS 包管理器) ===== +curl -fsSL https://bun.sh/install | bash + +# ===== 3. 持久化 bun PATH (所有 shell 生效) ===== +echo "export BUN_INSTALL=\"\$HOME/.bun\"" >> /root/.bashrc +echo "export PATH=\"\$BUN_INSTALL/bin:\$PATH\"" >> /root/.bashrc +cat > /etc/profile.d/bun.sh << "EOF" +export BUN_INSTALL="$HOME/.bun" +export PATH="$BUN_INSTALL/bin:$PATH" +EOF + +# ===== 4. Go (如项目需要) ===== +# 方案 A: apk 安装 (Alpine 仓库版) +# apk add go +# 方案 B: 从官方下载指定版本 (推荐) +# wget -q https://go.dev/dl/go1.24.1.linux-amd64.tar.gz \ +# && tar -C /usr/local -xzf go1.24.1.linux-amd64.tar.gz \ +# && rm go1.24.1.linux-amd64.tar.gz +' +``` + +### 4.2 工具清单与用途 + +| 工具 | 版本 | 大小 | 安装命令 | 用途 | 适用项目 | +|------|------|------|---------|------|---------| +| **vim** | 9.2 | **3.2M** (包: 9M) | `apk add vim` | 文件编辑器 | 所有项目 | +| **jq** | 1.8.1 | **28K** (包: 9M) | `apk add jq` | JSON 处理/查询 | 所有项目 | +| **htop** | 3.4 | **272K** (包: 9M) | `apk add htop` | 系统资源监控 | 所有项目 | +| **bun** | 1.3.x | **89.8M** | 官方安装脚本 | JS/TS 包管理器 | flux-admin, flux-uniapp | +| **Go** | 1.24+ | ~250M (官方tar) | `apk add go` 或官方下载 | Go 编译运行 | flux-test, flux-mock, file-server | + +> 注: vim/jq/htop 三者共享 apk 基础依赖 (~9M),一起安装不会额外增加太多空间。 + +#### 全部工具大小汇总 + +| 类别 | 工具 | 大小 | 来源 | +|------|------|------|------| +| **基础镜像内置** | Node.js v24 + npm | **~200MB** | Dockerfile 多阶段构建 | +| | ttyd 1.7.7 | ~2MB | Dockerfile | +| | git 2.52 | **2.9M** | Dockerfile | +| | tmux 3.6 | **808K** | Dockerfile | +| | curl / wget / nc / tree / less | <1MB | Dockerfile | +| **代理工具 (挂载 ro)** | mysql-proxy | **8.9M** | dist-Linux/ | +| | ssh-proxy | **4.6M** | dist-Linux/ | +| | redis-proxy | **3.4M** | dist-Linux/ | +| | mongo-proxy | **6.3M** | dist-Linux/ | +| | 小计 | **23.2M** | | +| **开发工具 (挂载 ro)** | JDK 17 musl | **363.2M** | D:/Java/jdk-musl-17 | +| | Maven mvnd-3.9.14 | **10.5M** | D:/Java/maven-mvnd-3.9.14 | +| | 小计 | **373.7M** | | +| **容器内安装 (重建丢失)** | vim 9.2 | **3.2M** | `apk add vim` | +| | jq 1.8.1 | **28K** | `apk add jq` | +| | htop 3.4 | **272K** | `apk add htop` | +| | bun 1.3.x | **89.8M** | 官方安装脚本 | +| | Go (如需) | ~250M | `apk add go` 或 tar | +| | 小计 (不含 Go) | **~96.5M** | | + +**容器镜像写层**: 基础 ~45KB + 运行时数据 (~100MB 含 bun/npm 缓存等) + +### 4.3 安装验证 + +```bash +MSYS_NO_PATHCONV=1 docker exec {container_name} bash -lc ' +for cmd in vim jq htop bun go; do + v=$($cmd --version 2>/dev/null | head -1) + [ -n "$v" ] && echo "✓ $cmd: $v" || echo "✗ $cmd: 未安装" +done +' +``` + +### 4.4 固化到镜像(可选) + +如果不想每次重建都重装,可将常用工具加入基础镜像 Dockerfile: + +```dockerfile +# 在现有 RUN 指令后追加 +RUN apk add --no-cache vim jq htop +``` + +--- + +## 5. 容器内 Shell 配置 (.bashrc) + +> **关键**: 以下两个函数是容器内 Claude Code 和代理工具正常工作的核心。 + +### 5.1 claude wrapper(绕过 OAuth) + +容器内 Claude Code 默认走 OAuth 登录流程,在无浏览器环境下会卡住。解决方案:用 `--bare` 模式强制走 API Key 认证,并通过 wrapper 函数补回被跳过的功能。 + +```bash +# data/home/{project}-dev/.bashrc + +# claude wrapper: use --bare for API key auth, restore CLAUDE.md + settings +claude() { + local _args=() + _args+=(--bare) + # CLAUDE.md auto-discovery (bare skips it) + [ -f /root/.dev-env/claude/CLAUDE.md ] && _args+=(--add-dir /root/.dev-env/claude) + # project settings + [ -f /root/.dev-env/claude/settings.local.json ] && _args+=(--settings /root/.dev-env/claude/settings.local.json) + # pass through all user args + command claude "${_args[@]}" "$@" +} +``` + +**原理**: +- `--bare` 模式:只认 `ANTHROPIC_API_KEY`,不走 OAuth/keychain +- `--bare` 会跳过 CLAUDE.md 自动发现 → 用 `--add-dir` 手动指定 +- `--bare` 不读 settings → 用 `--settings` 手动加载权限配置 + +### 5.2 代理服务自动启动 + +代理工具需要在容器内以 server 模式运行(不是 client 模式),且需要各自的 `.toml` 配置文件。 + +```bash +# 代理服务 (idempotent: 只在未运行时启动) +_start_proxy() { + local name=$1 bin=$2 cfgdir=$3 + if pgrep -x "$bin" > /dev/null 2>&1; then return; fi + if [ -f "$cfgdir/$bin.toml" ]; then + (cd "$cfgdir" && "$bin" &>/tmp/$bin.log &) + sleep 1 + fi +} +_start_proxy mysql-proxy mysql-proxy /root/.dev-env +_start_proxy redis-proxy redis-proxy /root/.dev-env +_start_proxy ssh-proxy ssh-proxy /root/.dev-env +``` + +**原理**: +- `pgrep -x` 确保幂等(多次 source 不重复启动) +- 从 `/root/.dev-env/` 目录读取对应 `.toml` 启动 +- 日志输出到 `/tmp/{proxy}.log` 方便排查 + +### 5.3 完整 .bashrc 示例 + +```bash +# bun (idempotent) +if [ -z "$BUN_INSTALL" ]; then + export BUN_INSTALL="$HOME/.bun" + export PATH="$BUN_INSTALL/bin:$PATH" +fi + +# 代理服务自动启动 (见 5.2) +_start_proxy() { ... } # 同上 +_start_proxy mysql-proxy mysql-proxy /root/.dev-env +_start_proxy redis-proxy redis-proxy /root/.dev-env +_start_proxy ssh-proxy ssh-proxy /root/.dev-env + +# claude wrapper (见 5.1) +claude() { ... } # 同上 +``` + +--- + +## 6. 踩坑记录 + +| # | 问题 | 原因 | 解决方案 | +|---|------|------|---------| +| 1 | **Claude Code 弹出 OAuth 登录** | 容器内无浏览器,默认交互模式走 OAuth | 用 `--bare` + `ANTHROPIC_API_KEY`(见 5.1) | +| 2 | **Auth conflict 警告** | 同时设置 `ANTHROPIC_AUTH_TOKEN` 和 `ANTHROPIC_API_KEY` | compose 中只用 `ANTHROPIC_API_KEY=${ANTHROPIC_AUTH_TOKEN}` | +| 3 | **ssh-proxy/mysql-proxy 报 "No such file"** | 代理工具需要 `.toml` 配置文件才能以 server 模式启动 | 挂载项目专属 `.toml` 到 `/root/.dev-env/`(按信息隔离原则) | +| 4 | **SSH 连接失败 "Permission denied"** | ssh-proxy 需要私钥文件认证远程服务器 | 挂载宿主机 `~/.ssh/id_ed25519` 到 `/root/.ssh/` | +| 5 | **--bare 模式不读 CLAUDE.md** | bare 模式跳过自动发现机制 | wrapper 函数中加 `--add-dir` 手动指定 | +| 6 | **容器重建后代理不可用** | 代理进程在容器写层,重建丢失 | 在 `.bashrc` 中用 `_start_proxy()` 自动启动 | +| 7 | **BUN_INSTALL/PATH 重复追加** | 安装脚本被多次执行,每次都 append | 加 `[ -z "$BUN_INSTALL" ]` 幂等守卫 | +| 8 | **data/home 下出现 .dev-env/ 重复目录** | 旧版 compose 将 dev-envs 挂载到了 home 下 | 删除 `data/home/{project}-dev/.dev-env/`,确认 compose 只挂载 ro 版本 | + +--- + +## 7. 目录结构总览 + +``` +workpod/ +│ +├── instances/{project}/ # 容器编排 +│ ├── docker-compose.yml # Compose 定义 +│ ├── Dockerfile # (可选) 扩展镜像 +│ └── .env # 敏感变量: ANTHROPIC_AUTH_TOKEN (不进 git) +│ +├── dev-envs/{project}/ # ★ 项目配置 (只读挂载 → /root/.dev-env) +│ ├── servers.yaml # 服务器 IP/端口/用途 +│ ├── databases.yaml # 数据库/Redis/第三方密钥 +│ ├── norms.md # 开发规范 (唯一事实来源) +│ ├── ssh-proxy.toml # SSH 代理配置 (仅该项目服务器) +│ ├── mysql-proxy.toml # MySQL 代理配置 (仅该项目连接) +│ ├── redis-proxy.toml # Redis 代理配置 (仅该项目连接) +│ ├── docs/ # 仅该项目相关文档 (从 KMS 筛选) +│ └── claude/ +│ ├── CLAUDE.md # Claude Code 项目指令 (引用 norms.md) +│ └── settings.local.json # Claude Code 权限 +│ +├── data/home/{project}-dev/ # 运行时数据 (读写挂载 → /root) +│ ├── .gitconfig # Git 用户 + 凭据配置 +│ ├── .git-credentials-gitea # Gitea 密码 (权限 600) +│ ├── .bashrc # ★ Shell 配置 (claude wrapper + 代理启动) +│ ├── .claude/ # Claude Code 运行时数据 +│ │ └── skills/ # ★ 自定义技能 (按项目需求同步,见 §3.4b) +│ │ ├── commit/skill.md # Git 提交规范 (通用) +│ │ ├── review/skill.md # 代码审查 (按技术栈定制内容) +│ │ └── taskhub/skill.md # 任务管理 (通用) +│ ├── .bun/ # bun 安装包 +│ └── .ash_history # 命令历史 +│ +├── docs/03-运维/ +│ ├── 部署维护手册.md +│ └── 开发环境搭建手册.md # ← 本文档 +│ +├── Dockerfile # 基础镜像构建 +├── entrypoint.sh # 容器入口脚本 +└── SPECS.md # 技术规格文档 +``` + +### 职责划分 + +| 目录 | 挂载方式 | 内容 | 谁来维护 | +|------|---------|------|---------| +| `dev-envs/{project}/` | **只读** | 项目配置、规范、知识库 | AI / 开发者编写 | +| `data/home/{project}-dev/` | **读写** | 运行时产生的文件 | 容器自动产生 | +| `instances/{project}/` | — | Compose 编排定义 | AI 生成 | + +> **禁止**: 在 `data/home/` 下放置配置文件(会导致与 dev-envs 重复或被覆盖) + +--- + +## 附录 A: 运维 AI 执行 Checklist + +新建项目开发环境时,按此清单逐项执行: + +``` +□ 1. 创建目录 + □ mkdir -p dev-envs/{project}/claude + □ mkdir -p data/home/{project}-dev + +□ 2. 编写配置文件 (8 个文件 + 知识库文档) + □ dev-envs/{project}/servers.yaml # 服务器信息 (仅该项目) + □ dev-envs/{project}/databases.yaml # 数据库连接 (仅该项目) + □ dev-envs/{project}/norms.md # 开发规范 + □ dev-envs/{project}/ssh-proxy.toml # SSH 代理配置 (仅该项目服务器) + □ dev-envs/{project}/mysql-proxy.toml # MySQL 代理配置 (仅该项目连接) + □ dev-envs/{project}/redis-proxy.toml # Redis 代理配置 (仅该项目连接) + □ dev-envs/{project}/docs/ # 从 KMS 筛选该项目相关文档 + □ dev-envs/{project}/claude/CLAUDE.md + □ dev-envs/{project}/claude/settings.local.json + +□ 3. 配置 home + □ data/home/{project}-dev/.gitconfig + □ data/home/{project}-dev/.bashrc # ★ claude wrapper + _start_proxy (见第 5 节) + +□ 4. 生成 compose + .env + □ 从模板复制 instances/{project}/docker-compose.yml + □ 修改: name/ports/volumes(项目路径+dev-envs+SSH密钥+.toml)/env/Claude变量/network + □ 创建 instances/{project}/.env # ANTHROPIC_AUTH_TOKEN (不进 git) + +□ 5. 构建镜像 (如需新 Dockerfile) + □ docker build -t ws-{project}-dev:latest -f instances/{project}/Dockerfile . + +□ 6. 启动容器 + □ docker compose -f instances/{project}/docker-compose.yml up -d + +□ 7. 安装额外工具(重建后需重新执行,见第 4 节完整命令) + □ vim + jq + htop: `apk add vim jq htop` + □ bun: `curl -fsSL https://bun.sh/install | bash` + 持久化 PATH + □ Go (如需): `apk add go` 或官方下载 + +□ 8. 同步自定义技能(按项目需求选择性复制,见 §3.4b) + □ mkdir -p data/home/{project}-dev/.claude/skills/{commit,review,taskhub} + □ cp ~/.claude/skills/commit/skill.md data/home/{project}-dev/.claude/skills/commit/ + □ cp ~/.claude/skills/review/skill.md data/home/{project}-dev/.claude/skills/review/ + □ cp ~/.claude/skills/taskhub/skill.md data/home/{project}-dev/.claude/skills/taskhub/ + +□ 9. 验证代理工具 + □ docker exec {container} bash -c 'source ~/.bashrc && mysql-proxy cli -c flux_dev -e "SELECT 1"' + □ docker exec {container} bash -c 'source ~/.bashrc && redis-proxy get -c flux_dev -k "test_key"' + □ docker exec {container} bash -c 'source ~/.bashrc && ssh-proxy exec -n flux_dev -c "hostname"' + +□ 10. 验证 Claude Code + □ docker exec {container} bash -c 'source ~/.bashrc && claude -p "say hi"' + +□ 11. 验证技能 + □ docker exec {container} ls /root/.claude/skills/*/skill.md + +□ 12. 验证其他 + □ docker exec {container} ls /root/.dev-env/docs/ + □ docker exec {container} cat /root/.dev-env/servers.yaml | head -5 + □ docker exec {container} git config --global --list + □ docker exec {container} vim --version | head -1 + □ 浏览器打开 http://localhost:{WEB_PORT} +``` + +--- + +## 附录 B: fet 交叉编译 + +```bash +# 1. 添加 musl target +rustup target add x86_64-unknown-linux-musl + +# 2. 交叉编译 +cd E:/wk-oth/rust-work/fet +cargo build --release --target x86_64-unknown-linux-musl + +# 3. 复制到 dist-Linux +cp target/x86_64-unknown-linux-musl/release/fet ../dist-Linux/ + +# 4. 验证 +file ../dist-Linux/fet +# 期望输出: ELF 64-bit LSB pie executable, x86-64, static-pie linked + +# 5. 在 compose 中添加挂载 +# - E:/wk-oth/rust-work/dist-Linux/fet:/usr/local/bin/fet:ro +``` diff --git a/docs/03-运维/测试服实例.md b/docs/03-运维/测试服实例.md new file mode 100644 index 0000000..8c565a6 --- /dev/null +++ b/docs/03-运维/测试服实例.md @@ -0,0 +1,78 @@ +# 测试服实例配置 + +> 从 memory/server-workpod-instances.md 迁移,保持同步。 + +--- + +## 服务器信息 + +| 项目 | 值 | +|------|-----| +| 服务器 | flux_dev | +| IP | 39.99.243.191 | +| 配置目录 | /opt/workpod/ | +| 共用镜像 | workpod-alpine:latest (或 workpod-alpine:3.23) | + +## 实例列表 + +| 实例 | SSH 端口 | Web 端口 | 域名 | ttyd 账号 | 备注 | +|------|---------|---------|------|----------|------| +| workpod-1 | 2221 | 7681 | wk.1216.top | jc:1234567 | 默认 entrypoint.sh | +| workpod-alpine | 2222 | 7683 | wk2.1216.top | jc:1234567 | 默认 entrypoint.sh | +| workpod-ada | 2224 | 7685 | ada.1216.top | **ada:123** | 独立 ttyd 凭据 | +| workpod-yxl | 2226 | 7686 | yxl.1216.top | **yxl:123** | 独立 entrypoint-yxl.sh | + +## 密钥配置 + +ada 和 yxl 的 Claude Code 密钥通过 docker-compose environment 注入: + +```yaml +environment: + - ANTHROPIC_AUTH_TOKEN=xxx + - ANTHROPIC_BASE_URL=https://open.bigmodel.cn/api/anthropic + - API_TIMEOUT_MS=3000000 + - CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 +``` + +workpod-1 和 workpod-alpine 的密钥在 /root/.claude/settings.json 中。 + +## Nginx 配置 + +- 配置目录:`/etc/nginx/conf.d/` +- SSL 证书:`/etc/nginx/sslkey/_.1216.top.pem`(通配符证书) +- WebSocket 代理必须配置: + ```nginx + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + ``` + +## 更新容器注意事项 + +1. 镜像更新:`docker load < image.tar` 后逐个修改 compose 的 image tag +2. 脚本更新:entrypoint.sh 是共用的,ttyd-session.sh 是共用的 +3. ttyd 账号不同:ada 用 `ada:123`,yxl 用 `yxl:123`,其他用 `jc:1234567` +4. 权限:上传脚本后必须 `chmod 755` +5. Docker Compose 版本:测试服用 `docker-compose`(v1),不是 `docker compose` +6. 网络:所有实例共用 `workpod-network` + +## 部署步骤 + +```bash +# 1. 上传代码到 /opt/workpod/ +scp -r ./workpod/* root@39.99.243.191:/opt/workpod/ + +# 2. SSH 登录 +ssh root@39.99.243.191 + +# 3. 加载镜像(如有新镜像) +docker load < /opt/workpod/workpod-alpine-latest.tar.gz + +# 4. 设置权限 +chmod 755 /opt/workpod/entrypoint.sh /opt/workpod/ttyd-session.sh + +# 5. 启动各实例 +cd /opt/workpod && docker-compose -f docker-compose.yml up -d # workpod-1 +cd /opt/workpod && docker-compose -f docker-compose-alpine.yml up -d # workpod-alpine +cd /opt/workpod && docker-compose -f docker-compose-ada.yml up -d # workpod-ada +cd /opt/workpod && docker-compose -f docker-compose-yxl.yml up -d # workpod-yxl +``` diff --git a/docs/03-运维/部署指南.md b/docs/03-运维/部署指南.md new file mode 100644 index 0000000..c63decc --- /dev/null +++ b/docs/03-运维/部署指南.md @@ -0,0 +1,79 @@ +# 部署指南 + +> 本机 + 测试服部署步骤。 + +--- + +## 本机部署 + +### 前置条件 + +- Docker Desktop for Windows +- Git Bash 或 WSL2 终端 + +### 首次部署 + +```bash +cd E:/wk-lab/workpod + +# 1. 构建基础镜像 +docker compose -f instances/base/docker-compose.yml build + +# 2. 启动基础实例 +docker compose -f instances/base/docker-compose.yml up -d + +# 3. 启动项目实例(按需) +docker compose -f instances/flux/docker-compose.yml up -d --build +``` + +### 日常使用 + +```bash +# 查看所有实例 +docker ps --filter name=workpod --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" + +# 启动/停止单个实例 +docker compose -f instances/{name}/docker-compose.yml up -d +docker compose -f instances/{name}/docker-compose.yml down + +# 重建(修改 Dockerfile 后) +docker compose -f instances/{name}/docker-compose.yml up -d --build +``` + +### 连接方式 + +| 方式 | 命令/地址 | +|------|----------| +| Web 终端 | http://localhost:{WEB_PORT} (用户: jc, 密码: 1234567) | +| SSH | `ssh root@localhost -p {SSH_PORT}` (密码: workpod123) | +| Exec | `docker exec -it {container_name} bash` | + +--- + +## 测试服部署 + +详见 [测试服实例.md](./测试服实例.md) + +--- + +## 镜像构建与分发 + +### 构建基础镜像 + +```bash +cd E:/wk-lab/workpod +docker compose -f instances/base/docker-compose.yml build +``` + +### 导出镜像(用于测试服) + +```bash +docker save workpod-alpine:latest | gzip > workpod-alpine-latest.tar.gz +``` + +### 测试服加载 + +```bash +# 上传后 +docker load < workpod-alpine-latest.tar.gz +``` diff --git a/docs/03-运维/部署维护手册.md b/docs/03-运维/部署维护手册.md new file mode 100644 index 0000000..97c8ba7 --- /dev/null +++ b/docs/03-运维/部署维护手册.md @@ -0,0 +1,428 @@ +# WorkPod 部署维护手册 + +> 版本:1.0 | 更新日期:2026-04-07 + +--- + +## 1. 系统概览 + +WorkPod 是基于 Docker 的轻量级开发环境容器系统,提供 Web 终端 (ttyd) 和 SSH 双入口,支持多实例部署。 + +### 1.1 架构总览 + +``` +┌─────────────────────────────────────────────────────┐ +│ 宿主机 (Windows) │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │ +│ │ workpod-alpine│ │ ws-flux-dev │ │ 基础设施 │ │ +│ │ :2222 / :7681│ │ :2201 / :7701│ │ mysql/redis│ │ +│ └──────────────┘ └──────────────┘ └───────────┘ │ +│ ↑ ↑ │ +│ Alpine 3.23 + Node24 + JDK17/Maven │ +│ ~438MB ~438MB │ +└─────────────────────────────────────────────────────┘ +``` + +### 1.2 实例清单 + +| 实例 | 类型 | SSH | Web | 镜像 | 内存限制 | +|------|------|-----|-----|------|---------| +| workpod-alpine | 基础实例 | :2222 | :7681 | workpod-alpine:latest (438MB) | 4G | +| ws-flux-dev | Flux 项目 | :2201 | :7701 | ws-flux-dev:latest (438MB) | 4G | +| workpod-test | 测试实例 | :2223 | :7682 | ae5a15cc0c94 (旧镜像) | 4G | + +--- + +## 2. 快速操作 + +### 2.1 日常启停 + +```bash +# 启动全部实例 +cd E:/wk-lab/workpod/instances/base && docker compose up -d +cd E:/wk-lab/workpod/instances/flux && docker compose up -d + +# 停止单个实例 +docker compose -f instances/base/docker-compose.yml down +docker compose -f instances/flux/docker-compose.yml down + +# 重启(推荐:先 down 再 up) +docker compose -f instances/base/docker-compose.yml down && docker compose -f instances/base/docker-compose.yml up -d +``` + +### 2.2 连接方式 + +```bash +# ====== 基础实例 ====== +Web 终端: http://localhost:7681 # 用户 jc / 密码 1234567 +SSH: ssh root@localhost -p 2222 # 密码 workpod123 +进入容器: MSYS_NO_PATHCONV=1 docker exec -it workpod-alpine bash + +# ====== Flux 实例 ====== +Web 终端: http://localhost:7701 # 用户 jc / 密码 1234567 +SSH: ssh root@localhost -p 2201 # 密码 workpod123 +进入容器: MSYS_NO_PATHCONV=1 docker exec -it ws-flux-dev bash +``` + +### 2.3 一键检查状态 + +```bash +# 容器状态 +docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" + +# 资源占用 +docker stats --no-stream --format "table {{.Name}}\t{{CPUPerc}}\t{{MemUsage}}\t{{NetIO}}" + +# 镜像列表 +docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" +``` + +--- + +## 3. 构建与更新 + +### 3.1 镜像构建 + +```bash +# 基础镜像(Alpine 3.23 + Node 24 + Claude Code) +cd E:/wk-lab/workpod +docker build -t workpod-alpine:latest . + +# Flux 扩展镜像(基础镜像 + LABEL,JDK 通过 volume 挂载) +docker build -t ws-flux-dev:latest -f Dockerfile.flux . +``` + +**构建产物** (~438MB): +- alpine:3.23 基础 (~7MB) +- 运行时工具 curl/git/ssh/ttyd/tmux/bash (~80MB) +- Node.js v24.14.1 (musl) + claude-code + coding-helper (~200MB) +- entrypoint.sh + ttyd-session.sh (<10KB) + +### 3.2 更新 Claude Code / coding-helper + +```bash +# 重新构建即可获取最新版(@latest 标签) +docker build --no-cache -t workpod-alpine:latest . +docker build --no-cache -t ws-flux-dev:latest -f Dockerfile.flux . + +# 重启容器生效 +docker compose -f instances/base/docker-compose.yml down && docker compose -f instances/base/docker-compose.yml up -d +docker compose -f instances/flux/docker-compose.yml down && docker compose -f instances/flux/docker-compose.yml up -d +``` + +### 3.3 滚动更新(不中断服务) + +```bash +# 1. 构建新镜像 +docker build -t workpod-alpine:latest . + +# 2. 创建新容器(旧容器仍在运行) +docker compose -f instances/base/docker-compose.yml up -d --no-deps --build workpod-alpine + +# 3. 确认健康后删除旧容器(自动完成,compose 管理) +``` + +--- + +## 4. 运维操作 + +### 4.1 日志查看 + +```bash +# 实时日志 +docker logs -f workpod-alpine +docker logs -f ws-flux-dev + +# 最近 50 行 +docker logs --tail 50 workpod-alpine + +# 按时间过滤 +docker logs --since 2026-04-07T08:00:00 workpod-alpine +``` + +**日志策略**: json-file 驱动,单文件最大 10MB,保留 3 个文件(每实例最多 30MB) + +### 4.2 进入容器调试 + +```bash +# 交互式 shell(推荐 login shell 以加载 PATH) +MSYS_NO_PATHCONV=1 docker exec -it workpod-alpine bash -lc 'echo $PATH; java -version; node -v' + +# 执行单条命令 +MSYS_NO_PATHCONV=1 docker exec workpod-alpine bash -lc 'claude --version' +``` + +> **注意**: Windows Git Bash 下必须加 `MSYS_NO_PATHCONV=1`,否则路径转换会导致错误。 + +### 4.3 文件传输 + +```bash +# 宿主机 → 容器 +docker cp ./local-file.txt workpod-alpine:/workspace/ + +# 容器 → 宿主机 +docker cp workpod-alpine:/workspace/output.txt ./ +``` + +### 4.4 tmux 会话管理 + +容器内 ttyd 使用 tmux 管理终端会话: + +```bash +# 在 Web 终端或 SSH 中操作 +tmux ls # 列出所有会话 +tmux attach -t session_name # 加入会话 +tmux new -s my_session # 新建会话 + +# URL 快速访问指定会话 +http://localhost:7681/?session=my_session +``` + +### 4.5 数据持久化 + +| 实例 | 宿主机路径 | 容器路径 | 用途 | +|------|-----------|---------|------| +| base | `data/workspace` | `/workspace` | 工作目录 | +| flux | `E:/wk-flux` | `/workspace` | Flux 项目源码 | +| flux | `data/home/flux-dev` | `/root` | 用户配置 (.gitconfig 等) | + +--- + +## 5. 故障排查 + +### 5.1 容器无法启动 + +```bash +# 查看启动日志 +docker logs workpod-alpine + +# 常见问题: +# exit 127 → 命令未找到(镜像损坏或 entrypoint 缺失) +# exit 1 → 健康检查失败(sshd 或 ttyd 未启动) +# exit 255 → 信号处理异常 +``` + +**workpod-test 退出码 127 排查**: +该实例使用旧镜像 ID `ae5a15cc0c94`,可能缺少 entrypoint。修复方法: +```bash +# 方案 A:改用最新基础镜像 +# 编辑 instances/test/docker-compose.yml,将 image 改为 workpod-alpine:latest + +# 方案 B:重新构建并替换 +cd E:/wk-lab/workpod +docker build -t workpod-test:latest . +# 然后修改 test/docker-compose.yml 的 image 字段 +``` + +### 5.2 端口冲突 + +```bash +# 查看端口占用 +netstat -ano | grep :7681 + +# 端口分配表(避免冲突) +# 2222 / 7681 — workpod-alpine (基础) +# 2201 / 7701 — ws-flux-dev (Flux) +# 2223 / 7682 — workpod-test (测试) +# 新实例建议用 22xx / 77xx / 78xx +``` + +### 5.3 健康检查失败 + +```bash +# 手动检查端口 +MSYS_NO_PATHCONV=1 docker exec workpod-alpine netstat -tlnp + +# 应看到: +# tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN sshd +# tcp 0 0 0.0.0.0:7681 0.0.0.0:* LISTEN ttyd + +# 如果 sshd/ttyd 未运行,查看进程 +MSYS_NO_PATHCONV=1 docker exec workpod-alpine ps aux +``` + +### 5.4 内存不足 + +```bash +# 查看内存使用 +docker stats --no-stream + +# 当前限制:每个实例 4GB 上限 / 1GB 预留 +# 如需调整,编辑对应 docker-compose.yml 的 deploy.resources.limits.memory +``` + +### 5.5 PATH 环境变量丢失 + +entrypoint.sh 启动时会自动检测工具链并写入 `/etc/profile.d/dev-tools.sh`。如果 docker exec 中找不到命令: + +```bash +# 使用 login shell (-lc) 加载完整环境 +MSYS_NO_PATHCONV=1 docker exec -it workpod-alpine bash -lc 'java -version' + +# 或手动 source +MSYS_NO_PATHCONV=1 docker exec -it workpod-alpine bash -c 'source /etc/profile && java -version' +``` + +--- + +## 6. 清理维护 + +### 6.1 日常清理命令 + +```bash +# 清理悬空镜像(已停止容器的孤立层) +docker image prune -f + +# 清理未使用的镜像(除运行中容器外的所有未标记镜像) +docker image prune -a -f # ⚠️ 会删除未运行的镜像 + +# 清理停止的容器 +docker container prune -f + +# 清理未使用的网络 +docker network prune -f + +# 全面清理(悬停资源 + 停止容器 + 未使用网络 + 构建缓存) +docker system prune -f + +# 深度清理(包含未使用的镜像)⚠️ 慎用 +docker system prune -a -f +``` + +### 6.2 日志清理 + +```bash +# 手动清空某容器日志(不重启) +truncate -s 0 $(docker inspect --format='{{.LogPath}}' workpod-alpine) + +# 或设置全局日志限制(需编辑 daemon.json) +# /etc/docker/daemon.json: +# { "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } } +``` + +### 6.3 废弃资源清理记录 + +| 时间 | 操作 | 释放空间 | +|------|------|---------| +| 2026-04-07 | `docker image prune -f` | 802.8MB (28个悬空层) | + +**待清理**: +- `workpod:latest` (2.6GB) — 旧 Ubuntu 版基础镜像 +- `dev-box:latest` (2.79GB) — 旧开发箱镜像 +- `workpod-alpine:size-test` (436MB) — 大小测试镜像 + +```bash +# 清理上述废弃镜像(确认无容器引用后) +docker rmi workpod:latest dev-box:latest workpod-alpine:size-test +# 预计释放 ~5.8GB +``` + +--- + +## 7. 远程服务器部署 + +### 7.1 服务器信息 + +| 项目 | 值 | +|------|-----| +| 服务器 IP | 39.99.243.191 | +| SSH 别名 | flux_dev | +| 部署路径 | /opt/workpod/ | + +### 7.2 远程实例 + +| 实例 | SSH 端口 | Web 端口 | 域名 | +|------|---------|---------|------| +| workpod-1 | 2221 | 7681 | wk.1216.top | +| workpod-alpine | 2222 | 7683 | wk2.1216.top | +| workpod-ada | 2224 | 7685 | ada.1216.top | +| workpod-yxl | 2226 | 7686 | yxl.1216.top | + +### 7.3 远程运维命令 + +```bash +# 通过 ssh-proxy 操作远程服务器 +ssh-proxy exec -n flux_dev -c "docker ps --format 'table {{.Names}}\t{{.Status}}'" +ssh-proxy exec -n flux_dev -c "docker logs --tail 30 workpod-1" +ssh-proxy exec -n flux_dev -c "docker system prune -f" +``` + +--- + +## 8. 安全备忘 + +| 项目 | 当前配置 | 风险等级 | 建议 | +|------|---------|---------|------| +| privileged 模式 | 仅基础实例开启 | 高 | 确认是否必需,否则移除 | +| root 用户运行 | 全部实例 | 中 | 开发环境可接受 | +| SSH 密码登录 | PermitRootLogin yes | 内网可接受 | 生产环境禁用 | +| ttyd 认证 | Basic Auth (jc:1234567) | 低-内网 | 可配置强密码 | +| API Key 传递 | docker-compose env | 低 | 不写入文件系统 | +| JDK/Maven 挂载 | ro 只读 | 低 | 安全做法 | + +--- + +## 9. 目录结构速查 + +``` +E:/wk-lab/workpod/ +├── Dockerfile # 基础镜像构建 (多阶段) +├── Dockerfile.flux # Flux 扩展镜像 +├── entrypoint.sh # 容器入口脚本 +├── ttyd-session.sh # tmux 会话管理 +├── auth-proxy.js # 认证代理 (可选) +├── wk.1216.conf # Nginx 反向代理配置 +├── SPECS.md # 技术规格文档 +├── instances/ # ★ 实例中心 +│ ├── .template/ # 实例模板 +│ ├── registry.yaml # 实例注册表 +│ ├── base/docker-compose.yml # 基础实例 +│ ├── flux/docker-compose.yml # Flux 实例 +│ └── test/docker-compose.yml # 测试实例 +├── docs/ # 文档 +│ ├── 00-规范/ +│ ├── 01-架构/ +│ ├── 02-技术文档/ +│ ├── 03-运维/ # ← 本文档 +│ └── 04-审核/ +├── config/ # 配置模板 +├── static/ # auth-proxy 静态页 +├── data/ # 运行时数据 +│ ├── workspace/ # 基础实例工作区 +│ └── home/ # 用户 home 目录 +└── _archive/ # 历史版本归档 +``` + +--- + +## 附录:常用命令速查卡 + +```bash +# ════════════ 构建 ════════════ +docker build -t workpod-alpine:latest . +docker build -t ws-flux-dev:latest -f Dockerfile.flux . + +# ════════════ 启停 ════════════ +docker compose -f instances/base/docker-compose.yml up -d +docker compose -f instances/flux/docker-compose.yml up -d +docker compose -f instances/base/docker-compose.yml down + +# ════════════ 查看 ════════════ +docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" +docker stats --no-stream +docker logs -f workpod-alpine + +# ════════════ 进入 ════════════ +MSYS_NO_PATHCONV=1 docker exec -it workpod-alpine bash +ssh root@localhost -p 2222 # workpod123 +# http://localhost:7681 # jc / 1234567 + +# ════════════ 清理 ════════════ +docker image prune -f # 悬空镜像 +docker system prune -f # 全面清理 +docker rmi # 删除指定镜像 + +# ════════════ 远程 ════════════ +ssh-proxy exec -n flux_dev -c "docker ps" +``` diff --git a/docs/04-审核/01-安全性审核.md b/docs/04-审核/01-安全性审核.md new file mode 100644 index 0000000..84c70a6 --- /dev/null +++ b/docs/04-审核/01-安全性审核.md @@ -0,0 +1,352 @@ +# 安全性审核报告 (WorkPod Alpine) + +> 审核日期:2026-04-07 +> 审核范围:Dockerfile、entrypoint.sh、ttyd-session.sh、docker-compose.yml、auth-proxy.js、ISSUES.md + +--- + +## 审核概要 + +| 严重等级 | 数量 | +|---------|------| +| 严重 (Critical) | 1 (S-02) | +| 高危 (High) | 3 (H-02, H-03, H-04, H-05, H-06) | +| 中危 (Medium) | 5 (S-01[降级], M-01~M-05) | +| 低危 (Low) | 5 (L-01~L-05) | + +**总计:21 项安全问题(其中 S-01/S-03/S-04/H-01/M-06 已修复/改进/不适用)** + +--- + +## 问题清单 + +### [中危] S-01: 硬编码密码明文存储在源码中 **[已修复]** + +- **位置:** + - `Dockerfile:34` — `echo 'root:${ROOT_PASSWORD}' | chpasswd` (构建时仍保留默认值) + - `entrypoint.sh` — ttyd 密码通过 `${TTYD_CREDENTIALS:-jc:1234567}` 环境变量注入 +- **描述:** ~~所有认证凭证均以明文硬编码在源码文件中~~ → **已修复**:密码现已通过环境变量 `${ROOT_PASSWORD}` 和 `${TTYD_CREDENTIALS:-jc:1234567}` 注入,`entrypoint.sh` 不再硬编码密码。但需注意 `Dockerfile:34` 的 `chpasswd` 仍保留默认值(构建时),运行时通过环境变量覆盖。 +- **影响:** 攻击者获取源码不再直接获得运行时凭据。降级为 **[中危]**:Dockerfile 构建时默认值仍存在,但运行时可被 env 覆盖。 +- **建议修复:** + 1. Dockerfile 中 chpasswd 也改为环境变量引用或移除默认值 + 2. 将 `.env` 文件加入 `.gitignore`,使用 `docker-compose --env-file` 加载 + 3. 对已提交的密码执行 `git filter-branch` 或 BFG 清理历史 + 4. 若密码已泄露,立即轮换所有凭证 + +### [严重] S-02: privileged: true 特权模式运行 + +- **位置:** + - `docker-compose.yml:11` — `privileged: true` + - `docker-compose-alpine.yml:7` — `privileged: true` +- **描述:** 容器以特权模式运行,拥有宿主机全部 capabilities(包括 SYS_ADMIN、NET_ADMIN 等),可访问所有设备文件(/dev/*),可修改内核参数,可逃逸到宿主机。 +- **影响:** 容器内任何代码执行均可直接控制宿主机操作系统。结合 root 用户 + 全权限 Claude 配置,AI 工具的任意命令执行等同于宿主机 root 权限。 +- **建议修复:** + 1. 评估是否真的需要特权模式。若仅需要 Docker-in-Docker,使用 `docker.sock` 挂载替代 + 2. 若必须使用特权模式,限制为最小必要 capabilities(如仅 `SYS_PTRACE`) + 3. 考虑使用 `--security-opt=no-new-privileges` 防止权限提升 + 4. 在生产环境中绝对禁止特权模式 + +### [严重] S-03: Claude Code settings.json 设置 allow: ["*"] — 无限制全权限 **[不适用当前版本]** + +- **位置:** + - ~~`entrypoint-test.sh:41-48`~~ — 已归档到 `_archive/` + - ~~`entrypoint-test.sh:51-58`~~ — 已归档到 `_archive/` + - ~~`config/test-claude-settings.json`~~ — 外部配置文件同样 allow: ["*"] +- **描述:** ~~Claude Code 的 permissions 配置设置为 `allow: ["*"]`~~ → **不适用当前版本**:`entrypoint-test.sh` 已归档到 `_archive/` 目录,当前主 `entrypoint.sh` 中无此 `allow: ["*"]` 配置。 +- **影响:** 当前版本不受此问题影响。 +- **建议修复:** 若后续重新启用类似配置,应遵循最小权限原则。 + +### [严重] S-04: API 密钥 (ANTHROPIC_AUTH_TOKEN) 通过环境变量暴露给所有进程 **[已修复]** + +- **位置:** + - ~~`entrypoint-test.sh:67`~~ — ~~写入 .bashrc~~ → 已修复 + - ~~`entrypoint-test.sh:68`~~ — ~~写入 .bashrc~~ → 已修复 +- **描述:** ~~API 密钥写入 .bashrc 并全局暴露~~ → **已修复**:当前版本通过 `docker-compose.yml` 的 `environment` 直接传递 `ANTHROPIC_AUTH_TOKEN` 等环境变量,不再写入 `.bashrc`。 +- **影响:** 密钥不再持久化到 `.bashrc`,不再对所有登录用户自动暴露。但容器内子进程仍可通过 `/proc/*/environ` 读取(Docker 环境变量的固有特性)。 +- **建议修复:** + 1. 使用 Docker secrets 或外部密钥管理服务进一步加固 + 2. 限制密钥文件的权限为 `chmod 600` + +--- + +### [高] H-01: developer 用户 NOPASSWD sudo 全权限 **[不适用当前版本]** + +- **位置:** ~~`entrypoint-test.sh:34`~~ — 已归档到 `_archive/` +- **描述:** ~~developer 用户配置了无需密码的 sudo 全权限~~ → **不适用当前版本**:`entrypoint-test.sh` 已归档到 `_archive/` 目录,当前主 `entrypoint.sh` 中无此配置。 +- **影响:** 当前版本不受此问题影响。 + +### [高] H-02: SSH 允许 root 密码登录 + 弱密码 + +- **位置:** + - `Dockerfile:35` — `PermitRootLogin yes` + - `Dockerfile:34` — `chpasswd` 设置密码(默认 workpod123,可通过 `${ROOT_PASSWORD}` 覆盖) +- **描述:** SSH 服务配置为允许 root 用户使用密码登录(而非仅密钥认证)。**降级说明**:`ROOT_PASSWORD` 现在可通过环境变量覆盖,默认值仍为 `workpod123` 但可自定义。端口 22 映射到宿主机 2222 端口,对外可达。 +- **影响:** 若用户未自定义 `ROOT_PASSWORD`,暴力破解攻击仍可在短时间内猜出默认密码。一旦成功,攻击者获得容器 root shell,在特权模式下进一步控制宿主机。 +- **建议修复:** + 1. 禁用密码登录:`PasswordAuthentication no`,仅允许密钥认证 + 2. 若必须保留密码登录,通过环境变量设置强密码(20+ 字符随机生成) + 3. 限制 SSH 来源 IP(防火墙规则) + 4. 考虑使用 fail2ban 防暴力破解 + 5. 更改默认 SSH 端口(虽属隐匿安全但有一定价值) + +### [高] H-03: ttyd 使用 Basic Auth 且密码强度不足 **[已改进]** + +- **位置:** + - `entrypoint.sh` — ttyd 密码通过 `${TTYD_CREDENTIALS:-jc:1234567}` 环境变量配置 +- **描述:** ttyd 使用 `-c` 参数启用 HTTP Basic Authentication。**已改进**:默认凭据改为 `jc:1234567` 通过环境变量 `TTYD_CREDENTIALS` 配置,不再硬编码在 entrypoint.sh 中。仍存在的风险: + 1. 默认密码仍偏弱(7位数字/字母组合) + 2. Basic Auth 凭据以 Base64 编码传输(非加密),中间人可截获 + 3. 未强制 HTTPS,密码明文传输 + 4. ttyd `-c` 参数在 Safari 浏览器中存在兼容性问题(已知 Safari 不支持 ttyd 内置 Basic Auth) + 5. Web 终端端口(7681)暴露,增加攻击面 +- **影响:** 默认密码可被暴力破解;未加密传输可被网络嗅探;浏览器兼容性问题可能迫使降级安全措施。 +- **建议修复:** + 1. 通过 TTYD_CREDENTIALS 环境变量设置强密码(16+ 字符随机字符串) + 2. 在前端加反向代理(nginx/caddy)终止 TLS + 3. 考虑使用 auth-proxy.js 作为认证层(已有实现),但需修复其自身安全问题 + 4. 限制 ttyd 端口的网络访问范围 + +### [高] H-04: 多实例共享网络 (workpod-network) 缺乏隔离 + +- **位置:** `docker-compose-alpine.yml:23-24` — `name: workpod-network` +- **描述:** 测试服多个 WorkPod 实例共享同一个 Docker 网络 `workpod-network`。这意味着: + 1. 同一网络内的容器可以互相访问对方的开放端口 + 2. 一个容器被攻陷后,可作为跳板攻击同网络其他容器 + 3. 容器间通信不经过宿主机防火墙 +- **影响:** 横向移动风险——单点突破导致整个开发环境沦陷。 +- **建议修复:** + 1. 为每个实例创建独立网络 + 2. 若需共享数据,使用专用数据卷而非网络共享 + 3. 在容器内部使用 iptables/nftables 限制容器间流量 + 4. 考虑 Docker network 的 `internal` 选项禁止外部访问 + +### [高] H-05: auth-proxy.js 存在多处安全隐患 + +- **位置:** `auth-proxy.js` 全文(已从 `_archive/` 补充回主目录) +- **描述:** 认证代理脚本存在以下安全问题: + 1. **第8行**:密码硬编码在源码中 `{ wk: '1234567', admin: 'admin123' }` + 2. **第15行**:Token 存储在内存 Set 中,无持久化,重启后所有 token 失效(可用性问题),但也意味着无法审计 + 3. **第19行**:Token 生成方式可预测(`wk_用户名_时间戳`),格式固定且时间戳精度为毫秒,可被枚举猜测 + 4. **第77-89行**:Basic Auth 凭据仅做 Base64 解码比对,无速率限制,可被暴力破解 + 5. **第103-105行**:`/token` 和 `/ttyd/` 路径绕过 token 验证直接代理到默认工作区,形成认证旁路 + 6. **第49行**:代理请求时透传原始 headers(含 Authorization),可能导致凭据泄漏到后端 +- **影响:** 认证层形同虚设,token 可被猜测/绕过,密码可被暴力破解,凭据可能泄漏到后端服务。 +- **建议修复:** + 1. Token 使用 crypto.randomBytes() 生成不可预测的随机 token + 2. `/token` 和 `/ttyd/` 端点也必须验证 token + 3. 添加登录失败速率限制(如每 IP 每分钟最多5次) + 4. 过滤敏感 header(Authorization、Cookie)不转发到后端 + 5. 密码使用环境变量或外部配置,不从源码读取 + +### [高] H-06: /root 目录挂载到宿主机 (docker-compose-alpine.yml) + +- **位置:** `docker-compose-alpine.yml:20` — `./data/workpod-alpine/root:/root` +- **描述:** 将容器的 `/root` 目录 bind mount 到宿主机目录。这意味着: + 1. 容器内的 root 用户 home 目录内容直接暴露在宿主机文件系统上 + 2. 包含 .claude/settings.json(含全权限配置)、.bashrc(含 API 密钥)、ssh 密钥等敏感信息 + 3. 宿主机上其他进程/用户可能有权读取这些文件 + 4. 容器内对 /root 的修改直接影响宿主机 +- **影响:** 敏感配置和密钥在宿主机上的安全性取决于宿主机文件系统权限,增加了攻击面。 +- **建议修复:** + 1. 评估是否确实需要挂载 /root,若仅为持久化工具安装,考虑只挂载子目录 + 2. 设置挂载目录的严格文件权限(chmod 700) + 3. 确保 /root 下的密钥文件权限为 600 + +--- + +### [中] M-01: heredoc 注入风险(部分已缓解) + +- **位置:** + - `entrypoint-test.sh:41-48` — `cat > /root/.claude/settings.json << 'SETTINGS'` (已用引号包裹,安全) + - `entrypoint-test.sh:64-71` — `cat > /home/developer/.bashrc << BASHRC` (**未用引号包裹,有风险**) + - `entrypoint-test.sh:75-77` — `cat > /root/.bashrc << 'ROOTRC'` (已用引号包裹,安全) + - `entrypoint-test.sh:80-88` — `cat > /etc/profile.d/dev-tools.sh << 'PROFILE'` (已用引号包裹,安全) +- **描述:** `entrypoint-test.sh:64` 的 heredoc 定界符 `BASHRC` 未加引号,这意味着 heredoc 内容中的变量(`${ANTHROPIC_AUTH_TOKEN}`、`${ANTHROPIC_BASE_URL}` 等)会被 shell 展开。虽然当前内容是期望的行为(需要展开环境变量),但如果将来有人在此处添加包含特殊字符的用户输入,可能导致注入。 +- **影响:** 当前风险较低(变量值来自受控的环境变量),但编码模式不规范,未来维护可能引入漏洞。 +- **建议修复:** + 1. 保持现状但添加注释说明此处有意使用未引用 heredoc 以展开变量 + 2. 或者改用双引号定界符 `"BASHRC"` 并显式使用 `${VAR}` 引用变量 + +### [中] M-02: ttyd-session.sh 命令注入风险(已部分缓解) + +- **位置:** `ttyd-session.sh:9` — `SESSION_NAME=$(echo "$TTYD_QUERY_STRING" | tr '&' '\n' | grep '^session=' | head -1 | cut -d= -f2)` +- **描述:** 从 URL query string 提取 session 名称时,虽然第55行做了过滤(`tr -cd 'a-zA-Z0-9_\-'`),但在第37行的 `read` 命令中,用户交互输入的 `CHOICE` 变量在第44-46行被用于 `sed -n "${CHOICE}p"`,如果 CHOICE 包含恶意内容(如 `1; rm -rf /`),虽然 grep 数字检查提供了一定保护,但防御不够严谨。 +- **影响:** 攻击者可能通过构造特殊的 session 名称或交互输入来注入命令。当前有 `tr -cd` 过滤和数字匹配作为缓解措施,降低了但未消除风险。 +- **建议修复:** + 1. 在 read 后立即对 CHOICE 做输入验证 + 2. 使用 `[[ "$CHOICE" =~ ^[0-9]+$ ]]` 替代 `grep -qE` 做更严格的数字判断 + 3. 使用数组索引代替 sed 行号引用 + +### [中] M-03: 缺少 .gitignore 导致敏感文件可能被提交 + +- **描述:** 项目根目录不存在 `.gitignore` 文件。以下敏感文件/目录可能被意外提交到 Git 仓库: + - `data/` — 可能包含工作区数据和挂载的 /root 内容 + - `config/test-claude-settings.json` — 含全权限配置 + - `packages/` — 可能包含二进制包 + - `*.tar.gz` — `workpod-alpine-latest.tar.gz` 镜像导出文件 + - `wk.1216.conf` — 可能含 nginx/服务器配置 +- **影响:** 敏感数据(工作区代码、配置、镜像)泄露到 Git 仓库。 +- **建议修复:** 创建 `.gitignore` 文件,至少排除:`data/`、`*.tar.gz`、`.env*`、`config/*.json` + +### [中] M-04: Alpine 镜像使用第三方镜像源 (mirrors.aliyun.com) + +- **位置:** `Dockerfile:6`、`Dockerfile:28` — `sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g'` +- **描述:** 构建过程中将 Alpine 官方包源替换为阿里云镜像源。虽然这是常见的国内加速做法,但引入了第三方信任链: + 1. 包完整性依赖阿里云镜像的同步正确性 + 2. 中间人攻击面扩大(DNS 劫持、镜像源被入侵) + 3. 生产环境构建应优先使用官方源或可信的企业镜像 +- **影响:** 若镜像源被污染,可能在构建阶段植入恶意软件到最终镜像中(供应链攻击)。 +- **建议修复:** + 1. 构建时使用 `--no-cache` 并验证包签名 + 2. 考虑使用官方源配合网络代理 + 3. 至少在生产发布构建中使用官方源 + +### [中] M-05: 容器缺少资源隔离和安全增强配置 + +- **位置:** `docker-compose.yml`、`docker-compose-alpine.yml` +- **描述:** 容器配置缺少以下安全加固措施: + 1. 未设置 `read_only: true` — 容器文件系统可写 + 2. 未设置 `security_opt: ["no-new-privileges:true"]` — 进程可通过 setuid/setgid 提权 + 3. 未设置 `cap_drop: [ALL]` + `cap_add: [特定能力]` — 保留全部 Linux capabilities + 4. 未设置 `user:` — 默认以 root 运行 + 5. 未设置 `tmpfs: /tmp, /dev/shm` — 临时目录使用容器默认挂载 +- **影响:** 容器逃逸向量增多,进程提权路径未被阻断。 +- **建议修复:** + 1. 添加 `security_opt: ["no-new-privileges:true"]` + 2. 添加 `cap_drop: [ALL]` 仅保留必要能力 + 3. 考虑以非 root 用户运行主进程 + 4. 对 /tmp 等可写目录使用 tmpfs 并设置 `exec` 选项防止脚本执行 + +### [中] M-06: 日志和启动信息泄露敏感配置 **[已修复]** + +- **位置:** + - ~~`entrypoint.sh:76-78`~~ — ~~打印连接方式和密码~~ → 已修复 + - ~~`entrypoint-test.sh:124-126`~~ — ~~打印连接方式和密码~~ → 已归档 +- **描述:** ~~启动时将连接地址和密码输出到 stdout/stderr~~ → **已修复**:当前 `entrypoint.sh` 不再打印密码。 +- **影响:** `docker logs` 不再泄露密码。 +- **建议修复:** 无需进一步操作。 + +--- + +### [低] L-01: HEALTHCHECK 命令不一致 + +- **位置:** + - `Dockerfile:49` — `netstat -tlnp` + - `docker-compose.yml:48` — `ss -tlnp | grep ...` +- **描述:** Dockerfile 中的 HEALTHCHECK 使用 `netstat`(ISSUES.md 已记录 ss 不可用的问题),而 docker-compose.yml 中覆盖使用了 `ss` 命令。根据 ISSUES.md 记录,Alpine 中 `ss` 不可用(需要安装 iproute2),compose 中的 healthcheck 会因 `ss: not found` 而持续失败。 +- **影响:** 健康检查不准确,可能导致编排系统错误判断容器状态。 +- **建议修复:** 统一使用 `netstat -tlnp` 或安装 `iproute2` 后统一使用 `ss`。 + +### [低] L-02: EXPOSE 指令声明过多端口 + +- **位置:** `Dockerfile:46` — `EXPOSE 22 7681` +- **描述:** Dockerfile 中声明暴露 22 (SSH) 和 7681 (ttyd) 端口。EXPOSE 主要是文档作用,但会给使用者错误的暗示。实际上 compose 文件映射了更多端口(2222-2226, 7681-7686)。 +- **影响:** 信息误导,但不构成直接安全威胁。 +- **建议修复:** 更新 EXPOSE 为实际使用的端口集合,或在注释中说明。 + +### [低] L-03: npm registry 使用第三方镜像源 + +- **位置:** `Dockerfile:17` — `npm config set registry https://registry.npmmirror.com` +- **描述:** npm 安装使用淘宝镜像源(npmmirror.com)。与 APK 镜像源类似,这引入了第三方供应链信任问题。 +- **影响:** npm 包可能被篡改(供应链攻击),尤其 `@anthropic-ai/claude-code` 是核心依赖。 +- **建议修复:** 生产构建使用官方 npm registry,或启用 npm 校验(`npm config set verify-signature true`)。 + +### [低] L-04: 镜像中包含不必要的构建工具痕迹 + +- **位置:** `Dockerfile:38` — `COPY --from=builder /opt/node /usr/local` +- **描述:** 多阶段构建是好的实践(builder 阶段的缓存和构建工具不会进入最终镜像),但最终镜像仍然较大(约 436MB)。应确认是否有不必要的文件被复制。 +- **影响:** 攻击面增大(更多二进制文件 = 更多潜在漏洞),拉取/分发效率降低。 +- **建议修复:** + 1. 审查 `/opt/node` 下是否包含不必要的文件(文档、测试等) + 2. 考虑使用 `COPY --from=builder /opt/node/bin /usr/local/bin` 仅复制必要文件 + 3. 使用 `docker scout` 分析镜像内容 + +### [低] L-05: Windows 伪影文件残留 **[风险降低]** + +- **描述:** 项目目录中发现以下 Windows 伪影文件: + - `entrypoint.sh;C` — Windows 创建的 ADS(Alternate Data Stream)伪影 + - `ttyd-session.sh;C` — 同上 +- **影响:** 这些文件可能是 Windows 资源管理器异常操作的产物,不影响 Linux 容器运行。**[风险降低]**:entrypoint.sh 和 ttyd-session.sh 已改为 `COPY --chmod=755` 内置到镜像(4/4 修复),不再通过 bind mount 从 Windows 主机挂载,CRLF 换行符问题的影响已大幅减小。 +- **建议修复:** 删除这些伪影文件,配置 `.gitattributes` 强制 LF 换行。 + +--- + +## 攻击路径分析 + +``` +攻击者 + │ + ├── [路径A: Web 终端] + │ ├── 1. 扫描发现 7681 端口开放的 ttyd + │ ├── 2. 暴力破解弱密码 (默认 jc:1234567,可通过 TTYD_CREDENTIALS 自定义) + │ ├── 3. 获得 shell → API 密钥不再在 .bashrc 中(已修复) + │ ├── 4. 使用 claude 执行命令(无 allow:["*"] 配置,需交互确认) + │ └── 5. privileged 容器 → 完全控制宿主机 + │ + ├── [路径B: SSH] + │ ├── 1. 扫描发现 2222 端口开放的 SSH + │ ├── 2. 暴力破解 root 密码(默认 workpod123,可通过 ROOT_PASSWORD 自定义) + │ ├── 3. 直接获得 root shell + │ └── 4. privileged 容器 → 完全控制宿主机 + │ + └── [路径C: 认证代理] + ├── 1. 发现 8080 端口的 auth-proxy + ├── 2. 暴力破解 Basic Auth (wk:1234567) + ├── 3. 利用 /token 端点的认证旁路 + ├── 4. 获取 token 后访问 ttyd + └── 5. 同路径 A 第 3-5 步 +``` + +**关键发现:密码已外部化(环境变量注入),entrypoint-test.sh 已归档移除(无 allow:["*"]、无 NOPASSWD sudo、无 .bashrc 密钥写入)。当前主要风险仍为"弱默认密码 + 特权模式",自定义强密码可显著提升整体安全性。** + +--- + +## 总结与优先级建议 + +### 立即处理 (P0 — 本周内) + +| 编号 | 问题 | 状态 | 原因 | +|------|------|------|------| +| S-01 | 硬编码密码 | **[已修复]** / 降级[中危] | 密码已外部化,Dockerfile 默认值仍存 | +| S-02 | privileged: true | **待处理** | 最高风险的单一配置项 | +| S-03 | allow: ["*"] + skip-permissions | **[不适用]** | entrypoint-test.sh 已归档 | +| S-04 | API 密钥暴露 | **[已修复]** | 不再写入 .bashrc | + +### 尽快处理 (P1 — 两周内) + +| 编号 | 问题 | 状态 | 原因 | +|------|------|------|------| +| H-01 | NOPASSWD sudo | **[不适用]** | entrypoint-test.sh 已归档 | +| H-02 | SSH root 密码登录 | **[部分改进]** | ROOT_PASSWORD 可自定义,默认仍弱 | +| H-03 | ttyd 弱密码 + 明文传输 | **[已改进]** | TTYD_CREDENTIALS 可配置,默认仍偏弱 | +| H-05 | auth-proxy.js 安全缺陷 | **待处理** | 认证层可被绕过(已从 _archive 补回主目录) | + +### 计划处理 (P2 — 一个月内) + +| 编号 | 问题 | 原因 | +|------|------|------| +| H-04 | 共享网络隔离 | 横向移动风险 | +| H-06 | /root 目录挂载 | 敏感文件外露 | +| M-01 ~ M-06 | 各类中危问题 | 安全加固措施 | +| L-01 ~ L-05 | 低危问题 | 代码质量和 hygiene | + +### 总体评价 + +WorkPod Alpine 是一个**开发环境工具**,其设计目标(便捷性、开箱即用)与安全性之间存在固有张力。当前配置明显偏向便利性: + +**优势:** +- 采用多阶段构建减小镜像攻击面 +- 使用 Alpine 基础镜像减少包数量 +- 有信号处理和清理机制 +- tmux session 管理支持多用户协作 + +**核心风险:** +- **特权模式 + root 运行 + 全权限 AI + 弱密码**的组合构成了"完美风暴"——每个单独看都是常见做法,但叠加在一起使整体安全基线极低 +- 作为开发环境可以接受一定风险,但如果此容器部署在任何可从互联网访问的环境中,应当视为**已被攻陷** + +**最低可行安全改进(MVP):** +1. 移除 `privileged: true`(除非有明确需求并提供理由) +2. 密码改为环境变量注入 + 强密码 +3. 移除 `allow: ["*"]` 和 `--dangerously-skip-permissions` +4. SSH 禁用密码登录或使用强密码 +5. 添加 `.gitignore` 防止敏感文件提交 diff --git a/docs/04-审核/02-架构设计审核.md b/docs/04-审核/02-架构设计审核.md new file mode 100644 index 0000000..a0b47a8 --- /dev/null +++ b/docs/04-审核/02-架构设计审核.md @@ -0,0 +1,316 @@ +# 架构设计审核报告 -- WorkPod + +> 审核日期:2026-04-07 +> 审核范围:`E:/wk-lab/workpod` +> 镜像大小:~436MB(alpine:3.23 基础,多阶段构建) + +--- + +## 架构概览 + +``` ++-----------------------------+ +------------------------+ +| Docker Host (测试服) | | Container | +| | | alpine:3.23 | +| workpod-alpine (2222/7681) |------>| +--------------------+| +| docker-compose.yml | | | /usr/sbin/sshd || +| 基础实例 | | | ttyd (-W 无认证) || +| workpod-network | | | Node.js (musl) || +| | | | Claude Code @latest|| +| | | | coding-helper || +| ws-flux-dev (2201/7701) |------>| | tmux || +| docker-compose.flux.yml | | +--------------------+| +| Flux 项目实例 | | | +| Dockerfile.flux 扩展 | | /opt/ttyd-session.sh | +| | | (tmux session 选择) | +| | | entrypoint.sh | +| | | (开发工具自动检测) | +| | +------------------------+ +| _archive/auth-proxy.js | +| (备用认证代理) | +| nginx 反向代理 (wk.1216.top)| ++-----------------------------+ + +**核心组件关系**: +- **Dockerfile**:多阶段构建(builder 安装 Node.js/npm 包 -> runtime 仅复制产物到 alpine:3.23),entrypoint.sh 和 ttyd-session.sh 通过 `COPY --chmod=755` 内置到镜像 +- **entrypoint.sh**:统一入口(已内置镜像),直接 `/usr/sbin/sshd` 启动 SSH,自动检测 /root 下开发工具设置 PATH,ttyd 使用 ttyd-session.sh 做 tmux session 管理 +- **Dockerfile.flux**:Flux 项目实例扩展构建,基于基础镜像追加项目特定配置 +- **ttyd-session.sh**:独立的会话管理脚本(已通过 `COPY --chmod=755` 内置到镜像),支持 URL 参数 `?session=name` 或交互式菜单选择/创建 tmux session +- **docker-compose.yml**:基础实例编排(context: .),workpod-network 网络,2222/7681 端口 +- **docker-compose.flux.yml**:Flux 项目实例编排,2201/7701 端口,挂载 Flux 工作空间 +- **docker-compose-alpine.yml**:Alpine 变体备用编排文件,多端口映射(7683/7684),挂载 /root +- **_archive/auth-proxy.js**:Node.js 认证代理(已归档),提供登录页 + token 认证 + 多工作区路由 + WebSocket 代理,作为可选组件保留 +- **wk.1216.conf**:Nginx 配置,SSL 终止 + 反向代理到 ttyd + +**关键特征**: +- 轻量级镜像(~436MB vs Ubuntu 版 ~2.6GB) +- 单一 entrypoint + Dockerfile 扩展模式:基础实例与项目实例共享核心逻辑,通过 Dockerfile.flux 按需扩展 +- 开发工具"外挂"模式:安装到 /root 挂载卷,entrypoint 自动检测 +- 认证代理方案(auth-proxy.js)已归档至 _archive/,作为可选组件按需启用 + +--- + +## 优势分析 + +### 1. 多阶段构建 -- 镜像体积控制优秀 + +builder 阶段完成所有重量级操作(npm install、编译依赖),runtime 阶段仅复制 `/opt/node` 到 `/usr/local`。最终镜像 ~436MB,是 Ubuntu 版的 1/6。这是本项目最突出的架构优势。 + +### 2. 开发工具"外挂"设计 -- 灵活性高 + +通过 entrypoint.sh 自动检测 `/root` 下的 `.cargo`、`.pyenv`、`go`、`.local/bin` 等目录并动态设置 PATH,实现了: +- 工具安装与镜像构建解耦 +- 不同实例可安装不同工具集 +- 容器重建不丢失手动安装的工具(只要 /root 已挂载) +- 新增工具类型只需在 entrypoint 添加一行检测逻辑 + +这一设计在 ISSUES.md 中有清晰记录,体现了"踩坑 -> 抽象 -> 文档化"的良好工程实践。 + +### 3. ttyd-session.sh -- 会话管理解耦 + +将 ttyd 的启动命令从 entrypoint 中抽离为独立脚本 `ttyd-session.sh`: +- 支持 URL 参数快速进入指定 session(`?session=xxx`) +- 交互式菜单列出已有 session 并支持新建 +- session 名安全过滤(仅允许字母数字下划线连字符) +- 共享 attach 模式(不加 `-d`,多人可同时查看同一 session) + +职责划分清晰:entrypoint 负责服务启停,ttyd-session 负责终端会话逻辑。 + +### 4. 问题记录完整 (ISSUES.md) + +ISSUES.md 记录了 11 个已解决的问题及解决方案,涵盖: +- Alpine 特有命令差异(ss vs netstat) +- BuildKit 兼容性(--chmod、缓存行为) +- 运行时问题(中文输入、断线恢复、数据持久化) +- 架构决策记录(/root 安装原则) + +这是非常有价值的知识资产,降低了团队其他成员的踩坑成本。 + +### 5. auth-proxy.js -- 完整的认证层 + +虽然未默认集成到容器中,但 auth-proxy.js 提供了: +- Basic Auth 登录 + Token 机制 +- 内存 Token 存储(1小时过期) +- 多工作区路由(默认 + hszd) +- HTTP 和 WebSocket 双协议代理 +- 自定义登录页面 + +这为生产化部署提供了现成的安全方案。 + +--- + +## 问题与建议 + +### [严重] 多实例 entrypoint 膨胀与维护噩梦 -- **[已解决]** + +- **位置:** 原为 `entrypoint.sh` vs `entrypoint-test.sh` +- **现状:** + - `entrypoint-test.sh` 已归档至 `_archive/` + - 当前仅保留单一 `entrypoint.sh` 作为统一入口 + - Flux 项目实例通过 `Dockerfile.flux` 扩展构建,不再需要独立 entrypoint +- **原问题描述:** + 1. **代码大量重复**:两个文件约 70% 代码完全相同(信号处理、PATH 检测、sshd 启动、健康检查、启动信息打印)。修改公共逻辑需要同步两处 + 2. **yxl 实例的 entrypoint 未纳入仓库**:该实例应该还有第三个 entrypoint 变体,但仓库中只有两个 + 3. **每新增一个用户就要复制一份 entrypoint**:当前 2 个文件,如果有 10 个用户就是 10 个几乎相同的脚本 + 4. **配置散落在脚本内部**:ttyd 凭据、端口号、用户名等硬编码在不同 entrypoint 中 +- **解决方案:** 归档 entrypoint-test.sh,统一使用单一 entrypoint.sh + Dockerfile 扩展模式。项目特定配置通过 Dockerfile.flux 注入,无需维护多份 entrypoint。 +- **遗留建议(供参考):** 如未来需支持更多定制化实例,可考虑将差异项外部化为环境变量: + ```bash + # entrypoint.sh 统一入口 + TTYD_CREDENTIALS="${TTYD_CREDENTIALS:-ada:123}" + TTYD_PORT="${TTYD_PORT:-7681}" + SSH_PORT="${SSH_PORT:-22}" + CREATE_DEVELOPER_USER="${CREATE_DEVELOPER_USER:-false}" + DEVELOPER_NAME="${DEVELOPER_NAME:-developer}" + + [ "$CREATE_DEVELOPER_USER" = "true" ] && setup_developer_user + ``` + +### [高] 安全凭据硬编码且各实例不一致 -- **[已修复]** + +- **位置:** 原为 `entrypoint.sh:45`, `entrypoint-test.sh:93`, `Dockerfile:34` +- **现状:** 所有凭据已统一通过环境变量注入,不再在各文件中硬编码 +- **原问题描述:** + + | 凭据类型 | 原始状态 | 当前状态 | + |----------|---------|---------| + | root 密码 | 多处硬编码 workpod123 | 环境变量注入 | + | ttyd 凭据 | 各实例不一致(ada:123 / jc:1234567) | 环境变量统一注入 | + | auth-proxy 密码 | JS 源码明文 | 已随 auth-proxy 归档,按需启用时从配置读取 | + +- **原问题:** + 1. **密码明文写在 4 个不同文件中**,修改密码需要改多处 + 2. **ttyd 凭据各实例不同**(ada:123 vs jc:1234567),但没有统一的管理方式 + 3. **auth-proxy.js 的密码是 JS 源码中的明文**,如果该文件被包含在镜像或 Web 目录中则直接暴露 + 4. **root 密码与 Dockerfile 中 chpasswd 一致**,但如果通过环境变量覆盖 entrypoint 的密码,Dockerfile 层的密码仍然存在 +- **解决方案:** 统一通过 docker-compose 环境变量或 `.env` 文件注入所有凭据,清除源码中的硬编码值。 + +### [中] auth-proxy.js 与容器架构脱节 + +- **位置:** `_archive/auth-proxy.js`, `static/login.html`, `wk.1216.conf` +- **现状:** auth-proxy.js 已归档至 `_archive/` 目录,作为可选组件保留在主目录中。不再作为核心组件强制集成。 +- **问题:** + 1. **部署方式需明确**:如需启用 auth-proxy,应补充启动说明(手动 / systemd / compose sidecar) + 2. **生命周期无保障**:若独立进程运行,容器重启后 auth-proxy 不会自动恢复 + 3. **token 存储在内存中**:auth-proxy 重启后所有用户需重新登录(可接受,但应文档化) + 4. **工作区配置硬编码**:`WORKSPACES` 数组写死记录,新增工作区需改代码重启 + 5. **与 ttyd -W 标志冲突**:容器内 ttyd 启动时用了 `-W`(禁用重连提示,非认证),如果 auth-proxy 作为唯一入口则需确保 ttyd 端口不对外暴露 +- **建议:** + - 如需正式使用:将 auth-proxy 集成到 docker-compose 中作为 sidecar 容器或独立 service + - 或将认证能力内置到容器内(如 ttyd 加 `-c` 参数 + Nginx basic_auth) + - 工作区配置改为从环境变量或 JSON 文件读取 + - 在 README 中说明 auth-proxy 的启用方式和适用场景 + +### [中] 编排文件职责需明确 + +- **位置:** `docker-compose.yml`, `docker-compose.flux.yml`, `docker-compose-alpine.yml` +- **现状:** 当前有 3 个编排文件: + - **`docker-compose.yml`**:基础实例,2222/7681 端口,挂载 workspace,4G 内存限制,workpod-network 网络。适用于通用开发环境。 + - **`docker-compose.flux.yml`**:Flux 项目专用实例,2201/7701 端口,挂载 Flux 工作空间目录,使用 Dockerfile.flux 扩展构建。适用于 Flux 项目开发。 + - **`docker-compose-alpine.yml`**:Alpine 变体备用编排,2222/7683+7684 端口,挂载 workspace + /root,无资源限制。适用于需要完整 /root 挂载的调试场景。 +- **问题:** + 1. 三个文件用途已有区分但未在文档中集中说明 + 2. `docker-compose-alpine.yml` 直接引用镜像而非构建,与另外两个文件的构建模式不一致 + 3. 缺少 `.env` 文件或参数化配置来统一管理端口/资源等差异 +- **建议:** + - 在 README 中补充各编排文件的用途说明和使用场景 + - 考虑合并为单一 compose 文件 + profiles,或保持多文件但统一 `.env` 参数化 + +### [中] Claude Code 版本在 download-packages.sh 中滞后 -- **[已解决]** + +- **位置:** 原为 `download-packages.sh:33` vs `Dockerfile:18` +- **现状:** 已统一使用 `@latest` 标签,不再锁定具体版本号 +- **原问题描述:** + - download-packages.sh:`claude-code@2.1.87` + - Dockerfile:`@anthropic-ai/claude-code@2.1.89` +- **原问题:** 下载脚本中的版本落后于 Dockerfile 实际使用的版本。如果有人先执行下载脚本再构建,会得到错误的包。 +- **解决方案:** 改用 `@latest` 标签,消除版本不一致问题。每次构建自动拉取最新版。 + +### [中] dev-tools PATH 检测逻辑重复 + +- **位置:** `entrypoint.sh:19-29`(运行时 export) vs `entrypoint.sh:32-40`(profile.d 写入) +- **现状:** 开发工具目录检测逻辑出现 2 次(单一 entrypoint.sh 内:一次 export 一次写入 profile.d)。原为 4 处(两个 entrypoint 各两次),归档 entrypoint-test.sh 后已减少至 2 处。 +- **问题:** 违反 DRY 原则。新增一种工具类型需要修改 2 处。 +- **建议:** 抽取为函数或独立脚本: + ```bash + # /opt/lib/dev-tools.sh + setup_dev_paths() { + local profile="" + [ -d /root/.cargo/bin ] && { export PATH="/root/.cargo/bin:$PATH"; profile+="..."; } + # ... + [ -n "$profile" ] && echo "$profile" > /etc/profile.d/dev-tools.sh + } + ``` + +### [低] packages/ 目录包含未在 Dockerfile 中使用的包 + +- **位置:** `packages/go1.26.1.linux-amd64.tar.gz`, `packages/rust-1.94.1-x86_64-unknown-linux-musl.tar.xz` +- **现状:** packages/ 目录中有 Go 和 Rust 的 musl 构建包,但 Dockerfile 仅安装了 Node.js + npm 包 +- **问题:** + 1. 占用 ~247MB 磁盘空间 + 2. download-packages.sh 也没有下载这两个包的步骤(说明可能是手动放入的) + 3. PACKAGES.md 不存在(Alpine 版缺少此文档),无法确认这些包的用途 +- **建议:** + - 如果是为"开发工具外挂"模式准备的:在文档中明确说明 + - 如果不再需要:清理掉以减小仓库体积 + - 补充 PACKAGES.md 说明软件包策略 + +### [低] entrypoint-test.sh 中的 Claude Code 别名策略不一致 + +- **位置:** `entrypoint-test.sh:65` vs `entrypoint-test.sh:76` +- **现状:** + - developer 用户:`alias claude='claude --dangerously-skip-permissions'` + - root 用户:`alias claude='claude --allow-dangerously-skip-permissions'` +- **问题:** + 1. root 用 `--allow-dangerously-skip-permissions`,developer 用 `--dangerously-skip-permissions`,参数不同但效果类似,容易混淆 + 2. 注释说"root 禁止 --dangerously-skip-permissions",但这应该是 Claude Code 对 root 用户的限制而非 CLI 参数限制 + 3. 这个别名仅在 bashrc 中,非交互式 shell(如 `docker exec` 执行命令)不会加载 +- **建议:** 统一别名策略或在注释中更清晰地解释原因差异。考虑用 wrapper 脚本替代 alias 以覆盖所有调用场景。 + +### [低] ttyd-session.sh 的 CHOICE 输入无超时 + +- **位置:** `ttyd-session.sh:37` +- **现状:** `read -p "session: " CHOICE` 无超时设置 +- **问题:** 如果用户打开 Web 终端但不输入选择,read 会一直阻塞。在自动化场景或意外断开的情况下可能导致僵尸进程。 +- **建议:** 添加 `-t 30` 超时(30秒),超时后自动选择 default session。 + +--- + +## 架构改进路线图 + +### P0 -- 紧急(影响安全与可维护性) + +| 序号 | 改进项 | 工作量 | 说明 | +|------|--------|--------|------| +| 1 | entrypoint 配置驱动重构 | 3h → **降低优先级** | 单 entrypoint 场景下紧迫性下降。当前 entrypoint.sh + Dockerfile.flux 扩展模式已满足需求,配置驱动重构可作为 P2 优化项储备 | +| 2 | 凭据外部化管理 | **[已完成]** | 所有密码/密钥已通过环境变量注入,清除源码中的硬编码 | +| 3 | auth-proxy 集成到编排体系 | 2h | auth-proxy 已归档为可选组件。如需正式启用,编写 docker-compose 或 systemd unit 确保生命周期受控 | + +### P1 -- 重要(提升工程质量) + +| 序号 | 改进项 | 工作量 | 说明 | +|------|--------|--------|------| +| 4 | dev-tools 检测逻辑去重 | 1h | 抽取为共享函数/脚本,消除 2 处重复(原 4 处) | +| 5 | 版本号单一数据源 | **[已完成]** | 已统一使用 @latest 标签 | +| 6 | 编排文件职责文档化 | 1h | 明确 3 个 yml 文件(yml / .flux.yml / -alpine.yml)的用途和使用场景,补充 README 说明 | + +### P2 -- 优化(改善体验) + +| 序号 | 改进项 | 工作量 | 说明 | +|------|--------|--------|------| +| 7 | 补充 PACKAGES.md | 0.5h | 说明 Alpine 版的包策略(含 Go/Rust 预留包) | +| 8 | 清理冗余包或文档化 | 0.25h | 决定 packages/ 中 Go/Rust 包的去留 | +| 9 | ttyd-session 超时保护 | 0.15h | read 添加超时 | +| 10 | Claude Code alias 统一 | 0.25h | 明确策略或改为 wrapper 脚本 | + +--- + +## 两版本协同改进建议 + +由于 WorkPod Ubuntu 版依赖 Alpine 版的 entrypoint 脚本,两个版本的改进应协调进行: + +### 共享资产清单 + +| 资产 | 当前归属 | 建议归属 | +|------|---------|---------| +| entrypoint.sh (核心逻辑) | Alpine 版(Ubuntu 版引用) | 提取为共享模块 | +| ttyd-session.sh | Alpine 版(Ubuntu 版引用) | 提取为共享模块 | +| PACKAGES.md | 仅 Ubuntu 版有 | 两版本都应有 | +| auth-proxy.js | Alpine 版(独立运行) | 可供两版本共用 | +| ISSUES.md | 仅 Alpine 版有 | Ubuntu 版也应建立 | + +### 推荐的共享方案 + +``` +workpod-common/ # 新建共享仓库或目录 + ├── entrypoint-core.sh # 公共逻辑(信号处理、sshd、健康检查) + ├── ttyd-session.sh # 会话管理(已有,可直接复用) + ├── lib-dev-tools.sh # 开发工具检测函数 + └── versions.env # 版本号定义(NODE_VERSION=, CLAUDE_CODE_VERSION= ...) + +workpod/ # Ubuntu 版 + ├── entrypoint.sh # source core + Ubuntu 特定逻辑 + └── Dockerfile # ubuntu 构建 + +workpod-alpine/ # Alpine 版 + ├── entrypoint.sh # source core + Alpine 特定逻辑 + ├── entrypoint-test.sh # source core + test 实例特定逻辑 + └── Dockerfile # alpine 多阶段构建 +``` + +--- + +## 总结评价 + +WorkPod 的架构质量持续改善。多阶段构建、开发工具外挂设计、ttyd-session 解耦、完整的问题记录都是亮点。~436MB 的镜像是务实的选择 -- 在功能完备性和体积之间取得了良好平衡。 + +本次审核中,多项历史问题已得到解决或修复: +- **entrypoint 膨胀问题 [已解决]**:归档 entrypoint-test.sh,统一单一 entrypoint.sh + Dockerfile.flux 扩展模式 +- **安全凭据硬编码 [已修复]**:统一通过环境变量注入 +- **Claude Code 版本滞后 [已解决]**:改用 @latest 标签 + +当前架构已从"多实例复制 entrypoint"模式演进为"单入口 + 构建扩展"模式,可维护性显著提升。P0-1(entrypoint 配置驱动重构)的紧迫性随之降低,可作为 P2 优化项储备。 + +安全层面,auth-proxy.js 已归档至 `_archive/` 作为可选组件保留。如需正式启用认证层,建议按 P0-3 方案集成到编排体系中。 + +整体而言,WorkPod 已整合为主目录项目,Alpine 作为基础镜像方案继续演进,Flux 等项目实例通过 Dockerfile.flux 按需扩展。 diff --git a/docs/04-审核/03-Docker最佳实践审核.md b/docs/04-审核/03-Docker最佳实践审核.md new file mode 100644 index 0000000..d885c8a --- /dev/null +++ b/docs/04-审核/03-Docker最佳实践审核.md @@ -0,0 +1,290 @@ +# Docker 最佳实践审核报告 + +> 审核对象:WorkPod Alpine 版 +> 审核日期:2026-04-07 +> 审核范围:Dockerfile、docker-compose.yml、entrypoint.sh、ttyd-session.sh、.gitignore、Dockerfile.flux、docker-compose.flux.yml + +--- + +## 镜像分析 + +| 指标 | Alpine 版 | Ubuntu 版(对照) | 建议 | +|------|----------|-------------------|------| +| 大小 | ~436MB | ~2.6GB | Alpine 版体积控制优秀 | +| 阶段数 | 2(多阶段) | 1(单阶段) | 多阶段构建设计合理 | +| 基础镜像 | alpine:3.23 (3.4MB) | ubuntu:22.04 (77MB) | Alpine 最小化基础镜像选择正确 | +| 最终层数 | ~8 层 | ~7 层 | 层数合理 | +| 包管理器 | apk (--no-cache) | apt (--no-install-recommends) | 两者都做了优化,apk 更彻底 | + +--- + +## 问题清单 + +### [严重] S1 - 密码硬编码在 Dockerfile 中 **[已修复]** + +- **位置:** `Dockerfile:34` +- **最佳实践:** 密码绝不应写入镜像层,应通过环境变量、Docker secrets 或运行时注入 +- **现状:** + ```dockerfile + echo "root:${ROOT_PASSWORD:-workpod}" | chpasswd + ``` + ROOT_PASSWORD 已通过环境变量 `${ROOT_PASSWORD}` 注入,不再硬编码在镜像层中。entrypoint.sh 已移除密码明文输出。ttyd 认证也已改用 `${TTYD_CREDENTIALS}` 环境变量(见 M4)。 +- **建议修复:** 已完成。ROOT_PASSWORD 和 TTYD_CREDENTIALS 均通过 docker-compose 环境变量注入。 + +### [严重] S2 - 以 root 用户运行所有服务 + +- **位置:** `Dockerfile` 全文、`entrypoint.sh` +- **最佳实践:** 容器内应以非特权用户运行应用进程 +- **现状:** sshd、ttyd、tmux、Node.js 全部以 root 运行,PermitRootLogin yes +- **说明:** 当前为开发环境的故意设计。WorkPod 作为全栈开发环境容器,需要 root 权限来安装工具链、管理包、配置系统服务等。生产环境部署时应考虑降权。 +- **建议修复:** + - 创建 `developer` 用户运行 ttyd 和开发工具 + - sshd 可保留 root 但应禁用密码登录,仅允许密钥认证 + - 添加 `USER` 指令 + +### [严重] S3 - Compose healthcheck 命令语法错误(管道无法在 CMD 数组中工作) **[已修复]** + +- **位置:** `docker-compose.yml:48` +- **最佳实践:** Compose healthcheck 的 CMD 数组形式中,`|`(管道)不会被 shell 解释 +- **原问题:** + ```yaml + test: ["CMD", "ss", "-tlnp", "|", "grep", "-qE", ":(22|7681)\\b"] + ``` + 这会执行 `ss -tlnp | grep -qE ...` 吗?**不会**。CMD 数组形式是 exec 执行,`|` 被当作 ss 的参数而非管道符。实际效果等同于: + ``` + ss -tlnp "|" grep -qE ":(22|7681)\b" + ``` + 这会导致健康检查**始终失败或行为异常**。 +- **对比:** 镜像内 HEALTHCHECK(`Dockerfile:48-49`)使用的是 shell 形式 `CMD netstat ...`,可以正常解析管道。 +- **额外问题:** Alpine 默认不安装 `ss` 命令(属于 iproute2 包,未在 apk add 列表中),即使语法修复也会因命令不存在而失败。 +- **修复状态:** 已改为 CMD-SHELL 形式并使用 netstat: + ```yaml + test: ["CMD-SHELL", "netstat -tlnp 2>/dev/null | grep -qE ':(22|7681)\\b' || exit 1"] + ``` + +### [高] H1 - privileged: true 过于宽泛 **[部分修复]** + +- **位置:** `docker-compose.yml:11`、`docker-compose.flux.yml` +- **最佳实践:** privileged 应作为最后手段 +- **现状:** + - `docker-compose.flux.yml`:已移除 `privileged: true`,改用细粒度 capabilities + - `docker-compose.yml`(基础版):仍保留 `privileged: true` +- **建议修复:** 基础版 docker-compose.yml 也应同步移除 privileged,统一使用 capabilities 方案。 + +### [高] H2 - npm 包版本不固定 (@latest) + +- **位置:** `Dockerfile:19` +- **最佳实践:** 所有依赖应固定版本号 +- **现状:** + ```dockerfile + npm install -g "@z_ai/coding-helper@latest" + ``` + 与 Ubuntu 版相同问题。 +- **说明:** 用户决定保持 `@latest`,以便每次构建时自动获取 coding-helper 最新版本。开发环境可接受此策略。 +- **建议修复:** 如需稳定构建可固定版本号(如 `"@z_ai/coding-helper@0.0.7"`),当前保持 @latest 为有意设计。 + +### [高] H3 - 缺少 .dockerignore 文件 **[已修复]** + +- **位置:** 项目根目录 +- **最佳实践:** 排除无关文件以减小构建上下文 +- **原问题:** 没有 `.dockerignore`。以下文件会被发送到 Docker daemon: + - `workpod-alpine-latest.tar.gz`(~100MB 导出镜像) + - `static/` 目录(前端静态文件) + - `auth-proxy.js`, `nginx-map-patch.sh`, `wk.1216.conf`(部署辅助文件) + - `.claude/` 配置目录 + - `ISSUES.md`, `docker-compose-alpine.yml`(文档和备用 compose) + - `packages/go1.26.1.linux-amd64.tar.gz`, `packages/rust-*.tar.xz`(未在 Dockerfile 中使用的包) +- **关键发现:** packages 目录中有 Go 和 Rust 的 tarball,但 Dockerfile 只使用了 node 包。这些大文件(Go ~150MB, Rust ~100MB)每次构建都会被发送到 Docker context。 +- **修复状态:** 已创建 `.dockerignore`,构建上下文缩小至约 275 bytes,排除了所有无关文件和大型包。 + +### [高] H4 - builder 阶段未清理 npm 缓存和临时文件 + +- **位置:** `Dockerfile:12-20` +- **最佳实践:** 多阶段构建的 builder 阶段虽不影响最终镜像大小,但影响构建缓存和构建速度 +- **现状:** + ```dockerfile + RUN mkdir -p /opt/node \ + && cd /tmp/packages \ + && tar -xzf node-v24.14.1-linux-x64-musl.tar.gz -C /opt/node --strip-components=1 \ + && export PATH="/opt/node/bin:$PATH" \ + && npm install -g npm@10 \ + && npm config set registry https://registry.npmmirror.com \ + && npm install -g "@anthropic-ai/claude-code@2.1.89" \ + && npm install -g "@z_ai/coding-helper@latest" \ + && npm cache clean --force + ``` + 有 `npm cache clean --force`,但 `/tmp/packages` 目录未被删除(虽然不影响最终镜像,因为 COPY --from=builder 只复制 /opt/node)。 +- **建议修复:** 当前做法可接受。如追求极致可添加 `&& rm -rf /tmp/packages /root/.npm`。 + +### [中] M1 - COPY 后单独 RUN chmod 而非使用 --chmod **[已修复]** + +- **位置:** `Dockerfile:42-44` +- **最佳实践:** 利用 BuildKit 的 `COPY --chmod` 减少层数 +- **原问题:** + ```dockerfile + COPY entrypoint.sh /entrypoint.sh + COPY ttyd-session.sh /opt/ttyd-session.sh + RUN chmod 755 /entrypoint.sh /opt/ttyd-session.sh + ``` + 单独一个 RUN chmod 创建了一个额外的镜像层(约几十字节),且与 Ubuntu 版的做法不一致(Ubuntu 版用了 `--chmod=755`)。 +- **修复状态:** 已统一为 `COPY --chmod=755`,移除了多余的 `RUN chmod` 层。 + +### [中] M2 - 多个 docker-compose 文件缺乏明确分工说明 + +- **位置:** `docker-compose.yml` / `docker-compose.flux.yml` / `docker-compose-alpine.yml` +- **最佳实践:** 项目应有唯一的 compose 文件或明确的文件用途区分 +- **现状:** 当前共有 3 个 compose 文件: + - `docker-compose.yml`:基础版,带完整配置(healthcheck、资源限制、日志轮转、privileged: true) + - `docker-compose.flux.yml`:Flux 版,移除 privileged、使用 capabilities、更精细的配置 + - `docker-compose-alpine.yml`:精简版(无 healthcheck、无资源限制、无日志配置、不同端口映射) +- **风险:** 开发者可能混淆该用哪个文件;alpine 备用文件缺配置。 +- **建议修复:** + - 在 README 中明确说明各文件用途 + - 或合并为一个主文件 + 环境覆盖文件 + +### [中] M3 - openrc 初始化方式不够健壮 + +- **位置:** `Dockerfile:32` +- **最佳实践:** Alpine 容器中应谨慎使用 openrc +- **现状:** + ```dockerfile + && mkdir -p /run/openrc && touch /run/openrc/softlevel + ``` + 这是让 openrc 命令可用的标准做法,但 entrypoint.sh 中并未使用 `service ssh start`(而是直接调用 `/usr/sbin/sshd`),所以 openrc 的初始化可能是多余的。 +- **建议修复:** + - 如果不用 openrc 管理 sshd:移除 `openrc` 依赖和 softlevel 初始化,改用 `apk add openssh-server --no-cache`(不带 openrc) + - 如果将来要用 openrc:保留当前做法并在注释中说明意图 + +### [中] M4 - ttyd 认证凭证硬编码 **[已修复]** + +- **位置:** `entrypoint.sh:45` +- **最佳实践:** 认证信息不应硬编码 +- **原问题:** + ```bash + ttyd -W -c ada:123 -t fontSize=16 ... + ``` + 用户名 `ada` 密码 `123` 写死在脚本中。虽然比 Ubuntu 版的无认证好,但仍然是弱凭证+硬编码。 +- **修复状态:** 已改用 `${TTYD_CREDENTIALS}` 环境变量注入,entrypoint 中动态拼接 `-c` 参数。默认值保留为兼容性兜底。 + +### [中] M5 - 资源限制可能偏低 **[已修复]** + +- **位置:** `docker-compose.yml:42-44` +- **最佳实践:** 资源限制应根据实际负载设置 +- **原问题:** + ```yaml + limits: + memory: 4G + reservations: + memory: 512M + ``` + Alpine 版虽然基础镜像小,但如果用户安装 Rust/Go/Python 工具链并编译大型项目,512M 保留内存可能不足导致 OOM Kill。 +- **修复状态:** reservation 已从 512M 提升到 1G,更适合 Claude Code 等高内存占用工具的运行。 + +### [低] L1 - 缺少 LABEL 元数据 **[已修复]** + +- **位置:** `Dockerfile` +- **最佳实践:** 镜像应包含维护者、版本等标签 +- **原问题:** 无任何 LABEL 指令。 +- **修复状态:** 已添加 maintainer + Open Containers 标准标签(org.opencontainers.image.*)。 + +### [低] L2 - 基础镜像未指定 digest + +- **位置:** `Dockerfile:4`, `Dockerfile:23` +- **最佳实践:** 生产级镜像应 pin 到具体 digest +- **现状:** 两处 `FROM alpine:3.23` 均未锁定 digest。 +- **建议修复:** 开发环境可接受,CI/CD 环境建议锁定。 + +### [低] L3 - HEALTHCHECK 使用 netstat 而非 ss + +- **位置:** `Dockerfile:48-49` +- **最佳实践:** 优先使用更现代的工具 +- **现状:** + ```dockerfile + CMD netstat -tlnp 2>/dev/null | grep -qE ':(22|7681)\b' || exit 1 + ``` + netstat 已被标记为 deprecated,Alpine 的 busybox 提供 netstat 但功能有限。`netstat -p`(显示 PID/程序名)在 busybox 版本中可能不支持。 +- **验证:** Alpine 的 busybox netstat 不支持 `-p` 参数,因此 `netstat -tlnp` 中的 `-p` 会被忽略,但仍能检查端口是否存在。 +- **建议修复:** 当前可用但不够精确。如需进程级检查,需安装 `iproute2-ss`(提供 ss)或 `net-tools`(提供完整 netstat)。当前做法作为端口可达性检查足够。 + +### [低] L4 - entrypoint.sh 中 PATH 注入写到 /etc/profile.d/ + +- **位置:** `entrypoint.sh:32-40` +- **最佳实践:** 容器内动态修改系统配置目录应注意幂等性 +- **现状:** 每次启动都会覆写 `/etc/profile.d/dev-tools.sh`,如果文件内容需要更新则没问题,但写入操作本身在容器中是不必要的(因为每次启动都是全新状态,除非挂载了持久化 root 目录)。 +- **建议修复:** 当前后果无害。如果 data/home/root 被挂载为持久化,这个设计是有意义的(让 SSH 登录也能获得正确的 PATH)。保持现状即可。 + +### [低] L5 - docker-compose-alpine.yml 缺少关键配置 + +- **位置:** `docker-compose-alpine.yml` +- **最佳实践:** 所有 compose 文件应包含最低限度的运维配置 +- **现状:** 该文件缺少: + - healthcheck(服务不可观测) + - logging 配置(日志无限增长风险) + - resource limits(无资源隔离) + - restart 策略(有 `restart: unless-stopped`,此项 OK) +- **建议修复:** 标注此文件为"快速启动/测试专用",或补齐缺失配置。 + +--- + +## 与 Ubuntu 版的关键差异分析 + +| 对比维度 | Ubuntu 版 | Alpine 版 | 评价 | +|---------|----------|----------|------| +| 构建策略 | 单阶段 | 多阶段 | Alpine 更优 | +| 镜像体积 | ~2.6GB | ~436MB | Alpine 优势明显(6倍差距) | +| ttyd 认证 | 无(`-W`) | 有(`-c ada:123`) | Alpine 更安全(尽管凭证弱) | +| 健康检查命令 | `ss -tlnp`(镜像)/ `netstat`(compose) | `netstat`(镜像)/ `ss`(compose,**语法错误**) | Ubuntu compose 覆盖合理;Alpine compose 有 bug | +| COPY --chmod | 使用 | **已使用**(统一为 `COPY --chmod=755`) | 两者一致 | +| 密码输出 | 明文打印 | 明文打印 | 两者都有问题 | +| 开发工具支持 | Node.js only | Node.js + Rust + Go + Python 自动检测 | Alpine 功能更丰富 | +| extra compose 文件 | 无 | 有(docker-compose-alpine.yml) | 增加维护复杂度 | + +--- + +## 优化建议汇总(按收益排序) + +| 排名 | 建议 | 严重度 | 状态 | 实施难度 | +|------|------|--------|------|---------| +| 1 | **修复 Compose healthcheck 语法错误**(CMD -> CMD-SHELL,ss -> netstat) | 严重 | **[已修复]** | 低(改一行) | +| 2 | 创建 `.dockerignore`(排除 Go/Rust 包 ~250MB + 其他无用文件) | 高 | **[已修复]** | 低(5分钟) | +| 3 | 密码外部化(SSH + ttyd 双重硬编码) | 严重 | **[已修复]** | 中(改 compose + entrypoint) | +| 4 | 固定 coding-helper 版本 @latest -> 具体版本 | 高 | 保持 @latest(有意设计) | 低(改一行) | +| 5 | COPY 统一使用 --chmod(消除多余 RUN 层) | 中 | **[已修复]** | 低(改几行) | +| 6 | 明确多个 compose 文件的定位(当前 3 个:yml / .flux.yml / -alpine.yml) | 中 | 待改进 | 低(加注释或重构) | +| 7 | 移除不必要的 openrc 依赖(减小攻击面) | 中 | 待处理 | 低(删几行) | +| 8 | 提升 memory reservation 到 1G+ | 中 | **[已修复]** | 低(改数字) | +| 9 | 添加 LABEL 元数据 | 低 | **[已修复]** | 低(加几行) | +| 10 | privileged 降级为 capabilities(flux 版已完成,基础版待同步) | 高 | **[部分修复]** | 高(需测试兼容性) | + +--- + +## 总体评价 + +| 维度 | 评分 | 说明 | +|------|------|------| +| 镜像优化 | A- | 多阶段构建、体积控制优秀、apk --no-cache、.dockerignore 已就位 | +| 编写规范 | A- | COPY --chmod 统一、LABEL 完整、结构清晰 | +| Compose 配置 | B | healthcheck 已修复、3 个 compose 文件需明确分工、flux 版已移除 privileged | +| 安全性 | C | 密码已外部化、ttyd 凭证环境变量化、root 运行为开发有意设计、privileged 部分修复 | +| 运维友好度 | A- | 信号处理完善、开发工具自动检测、启动信息详细、labels 完整、reservation 提升至 1G | + +**综合评级:B+** + +Alpine 版在镜像构建方面表现优秀(多阶段、体积小、apk 高效、.dockerignore)。上一轮审核中的关键功能性 bug(healthcheck 语法错误)和严重安全问题(密码硬编码)均已修复。安全性从 D+ 提升到 C,主要得益于密码/凭证外部化和 healthcheck 修复。剩余待改进项:基础版 docker-compose.yml 的 privileged 移除、多 compose 文件策略统一、openrc 依赖清理。 + +### 最高优先级修复项 + +**已完成:** +- ~~`docker-compose.yml:48`~~ — healthcheck 命令已改为 CMD-SHELL + netstat **[已修复]** +- ~~密码和 ttyd 凭证~~ — 已通过环境变量注入 **[已修复]** +- ~~`.dockerignore`~~ — 已创建,构建上下文 ~275 bytes **[已修复]** +- ~~COPY --chmod~~ — 已统一使用 **[已修复]** +- ~~LABEL 元数据~~ — 已添加 maintainer + OC labels **[已修复]** +- ~~memory reservation~~ — 已提升至 1G **[已修复]** +- ~~privileged 降级~~ — flux 版已完成,基础版待同步 **[部分修复]** + +**待处理:** +- 基础版 `docker-compose.yml` 移除 `privileged: true`,同步 flux 版的 capabilities 方案 +- 明确 3 个 compose 文件的分工定位(README 或文件内注释) +- openrc 依赖评估与清理(如不使用 service 命令可移除) +- `@z_ai/coding-helper@latest` 版本固定(当前保持 @latest 为有意设计) diff --git a/docs/04-审核/04-Shell脚本质量审核.md b/docs/04-审核/04-Shell脚本质量审核.md new file mode 100644 index 0000000..9aeb0f8 --- /dev/null +++ b/docs/04-审核/04-Shell脚本质量审核.md @@ -0,0 +1,204 @@ +# Shell 脚本质量审核报告 + +> 审核日期:2026-04-07 +> 审核范围:WorkPod Alpine(含 Test 变体)全部 Shell 脚本 +> 审核人:Shell 脚本专家 + +--- + +## 脚本清单 + +| 脚本 | 行数 | 用途 | 复杂度 | +|------|------|------|--------| +| entrypoint.sh | 81 | Alpine 版容器入口,启动 SSH + ttyd + 开发工具检测 | 中 | +| entrypoint-test.sh | 129 | Test 容器入口,额外创建 developer 用户 + Claude 全权限配置 | 中高 | 已归档(_archive/),非当前活跃 | +| ttyd-session.sh | 63 | tmux 会话管理,URL 参数解析 + 交互式菜单 | 中 | +| **合计** | **273** | | | + +--- + +## 逐脚本审核 + +### entrypoint.sh(Alpine 版) + +**文件路径:** `E:/wk-lab/workpod/entrypoint.sh` + +#### 健壮性 + +| # | 问题 | 严重度 | 说明 | +|---|------|--------|------| +| R1 | **SSHD_PID 未赋值(与 Ubuntu 版相同的问题)** | 高 | `cleanup()` 中 `kill "$SSHD_PID"` 因 PID 始终为 0 而跳过。`/usr/sbin/sshd` 在第42行前台启动(无 `&`),实际上 sshd 作为守护进程运行后返回,但未捕获其 PID。应改为 `/usr/sbin/sshd & SSHD_PID=$!` 或在 cleanup 中用 `pkill sshd`。 | +| R2 | **sshd 未使用 `-D` 前台模式** | 中 | 第42行直接调用 `/usr/sbin/sshd`,它会 fork 为守护进程。如果后续命令失败导致脚本退出,sshd 会成为孤儿进程。建议用 `-D` 参数保持前台或正确追踪 PID。 | +| R3 | **sleep 1 硬编码等待时间** | 低 | 与 Ubuntu 版相同,仅等待 1 秒判断 ttyd 存活。慢速系统上可能不够。 | +| R4 | **dev-tools.sh 中的 ls -d 可能匹配多个目录** | 低 | 第38行 `ls -d /root/rust-*/bin` 如果存在多个 rust 版本目录,`head -1` 只取第一个。行为可预测但隐式依赖排序。 | +| R5 | **cleanup 中 wait 无参数** | 低 | 第11行 `wait 2>/dev/null` 等待所有后台子进程。由于只跟踪了 TTYD_PID 和 SSHD_PID(后者实际为 0),行为基本正确但语义不精确。 | + +#### 可移植性 + +| # | 问题 | 严重度 | 说明 | +|---|------|--------|------| +| P1 | **shebang 使用 bash 但环境为 Alpine/musl** | 中 | `#!/bin/bash` 要求安装 `bash` 包。Dockerfile 第31行已包含 `bash` 依赖,所以当前可工作。但如果未来精简镜像可能出问题。脚本中未使用任何 bashism(数组、`[[ ]]` 等),理论上可改用 `#!/bin/sh`。 | +| P2 | **pidof 替代 pgrep** | 正面 | 第55行使用 `pidof sshd` 而非 `pgrep -x sshd`,这是正确的 POSIX 兼容选择,在 BusyBox 环境下可靠工作。 | +| P3 | **gvm source 命令** | 低 | dev-tools.sh 第34行 `source /root/gvm/scripts/gvm` 依赖 gvm 的初始化脚本格式。如果 gvm 不存在该路径会静默失败(有 `2>/dev/null`)。 | + +#### 安全性 + +| # | 问题 | 严重度 | 说明 | +|---|------|--------|------| +| S1 | **ttyd 密码过于简单 `ada:123`** | **[已改进]** | 原第45行 `-c ada:123` 已改为从 `${TTYD_CREDENTIALS:-jc:1234567}` 环境变量读取,不再硬编码在脚本中。密码强度可通过环境变量灵活配置。 | +| S2 | **硬编码密码明文出现在脚本和日志中** | **[已改进]** | 同上,密码已改为通过环境变量 `TTYD_CREDENTIALS` 传入,不再明文出现在脚本源码中。 | +| S3 | **dev-tools.sh 使用单引号 heredoc(安全)** | 正面 | 第32行 `<< 'PROFILE'` 阻止变量展开,防止注入。这是正确的做法。 | +| S4 | **无用户隔离** | 信息 | Alpine 默认版直接以 root 运行一切,没有像 Test 版那样创建 developer 用户。作为开发环境可接受。 | + +#### 代码质量 + +| # | 问题 | 严重度 | 说明 | +|---|------|--------|------| +| Q1 | **开发工具检测逻辑完善** | 正面 | 覆盖 cargo/rustup/gvm/pyenv/go/rust独立安装/local bin 共 7 种开发工具路径。 | +| Q2 | **双轨 PATH 设置** | 正面 | 既在当前 shell 设置 export(第19-29行),又写入 /etc/profile.d/dev-tools.sh(第32-40行)确保新会话生效。设计合理。 | +| Q3 | **启动信息展示完整** | 正面 | 展示 Node/npm/Claude/Rust/Go/Python 版本 + 连接方式,便于快速确认环境状态。 | +| Q4 | **版本检测容错好** | 正面 | 所有 `--version` 命令都有 `2>/dev/null \|\| echo '未安装'` 保护。 | +| Q5 | **cleanup 函数与 Ubuntu 版完全重复** | 低 | cleanup/trap 模式在三份 entrypoint 中复制粘贴。 | + +#### 代码质量评分:**7.5 / 10** + +> 扣分项:SSHD_PID 未赋值(-1)、密码过弱(-0.5)、sshd 前台模式(-0.5)、代码重复(-0.5) + +--- + +### entrypoint-test.sh(Test 容器入口) **[归档]** + +> 此文件已移至 `_archive/`,以下问题仅作历史记录。 + +**文件路径:** `E:/wk-lab/workpod-alpine/entrypoint-test.sh`(已归档至 `_archive/`) + +#### 健壮性 + +| # | 问题 | 严重度 | 说明 | +|---|------|--------|------| +| R1 | **SSHD_PID 未赋值(继承自 Alpine 版)** | 高 | 同 entrypoint.sh 的 R1 问题。 | +| R2 | **adduser -D 无密码设置** | 中 | 第32行 `adduser -D -s /bin/bash developer` 创建用户时未设置密码。developer 用户只能通过 sudo 操作,不能直接 SSH 登录(除非配置了密钥认证)。这可能是故意的,但应明确注释说明。 | +| R3 | **sudoers 文件写入无原子性保护** | 低 | 第34行直接 `echo ... > /etc/sudoers.d/developer`。如果脚本在中途被中断,可能留下不完整的 sudoers 文件导致 sudo 不可用。建议先写临时文件再 mv。 | +| R4 | **chown -R 递归操作范围大** | 低 | 第37行 `chown -R developer:developer /home/developer` 对整个 home 目录递归修改所有权。如果有其他进程正在向该目录写入文件,可能出现竞态。但在容器启动阶段风险极低。 | +| R5 | **settings.json 双引号 heredoc 安全** | 正面 | 第41行和第51行都使用 `<< 'SETTINGS'` 单引号 heredoc,JSON 内容不会被 shell 解释。做法正确。 | + +#### 可移植性 + +| # | 问题 | 严重度 | 说明 | +|---|------|--------|------| +| P1 | **adduser -D 是 Alpine 特有** | 信息 | `-D` 标志表示"不分配密码",是 BusyBox adduser 的扩展。本脚本专用于 Alpine,无移植需求。 | +| P2 | **bash 依赖同 Alpine 版** | 中 | 同 entrypoint.sh 的 P1 问题。 | + +#### 安全性 + +| # | 问题 | 严重度 | 说明 | +|---|------|--------|------| +| S1 | **Claude Code settings.json 全权限 allow:["*"]** | **严重 [归档]** | 第43-47行和第53-57行为 root 和 developer 用户都设置了 `"allow": ["*"]`,意味着 Claude Code 可以执行任意文件读写、命令执行等操作而无需用户确认。虽然这是开发环境的故意设计(沙箱用途),但应在文档中明确标注此安全策略的影响范围。**归档文件中的配置,不影响当前版本。** | +| S2 | **.bashrc 中环境变量传递方式** | 中 | 第67-70行使用双引号 heredoc `<< BASHRC` 并在其中引用 `${ANTHROPIC_AUTH_TOKEN}` 等变量。这里变量会在 heredoc 写入时展开——即使用 entrypoint 进程的环境变量值。这意味着:(1) 如果环境变量未设置,会写入空字符串;(2) 如果值包含特殊字符(如 `$`` `),可能被二次解释。当前用法基本安全但需注意边界情况。 | +| S3 | **NOPASSWD:ALL sudo 权限** | 高 [归档] | 第34行给 developer 用户无密码完整 sudo 权限。配合 Claude Code 全权限配置,developer 用户等同于 root。这是有意的设计决策(全权限沙箱),但安全边界完全消失。**同上,归档文件中的配置。** | +| S4 | **.bashrc 中 PATH 包含 `$PATH`** | 正面 | 第66行 `export PATH="/usr/local/bin:/usr/bin:/bin:\$PATH"` 使用 `\$PATH` 在双引号 heredoc 中转义 `$`,确保写入文件的是字面量 `$PATH` 而非展开后的值。这个转义是正确的。 | + +#### 代码质量 + +| # | 问题 | 严重度 | 说明 | +|---|------|--------|------| +| Q1 | **功能模块划分清晰** | 正面 | 按顺序分为:开发工具检测 -> 用户创建 -> Claude 配置(root) -> Claude 配置(developer) -> 别名配置(root) -> 启动服务 -> 信息展示。结构清晰。 | +| Q2 | **root 和 developer 配置对称处理** | 正面 | settings.json 和 .bashrc 分别为两个用户配置,且 chown 所有权正确。 | +| Q3 | **alias 区分 root/developer 参数** | 正面 | root 用 `--allow-dangerously-skip-permissions`(第76行),developer 用 `--dangerously-skip-permissions`(第65行)。准确反映了 Claude Code 对 root 用户的限制。 | +| Q4 | **与 entrypoint.sh (Alpine) 大量重复** | 高 [归档] | 开发工具检测(第19-29行 vs 第19-29行完全相同)、dev-tools.sh 写入(第80-88行 vs 第32-40行完全相同)、cleanup 函数(第7-14行相同)、服务启动和检查(第90-106行几乎相同)。唯一差异是用户创建和 Claude 配置部分(第31-78行)。重复率约 65%。**因归档而不再是活跃问题。** | +| Q5 | **环境变量默认值处理良好** | 正面 | 第69-70行 `${API_TIMEOUT_MS:-3000000}` 和 `${CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:-1}` 提供了合理的默认值。 | +| Q6 | **端口/标题/密码硬编码** | 低 | 7682、2223、"WorkPod Test"、`jc:1234567` 全部硬编码。(注:此为已归档的 entrypoint-test.sh 中的历史值;当前基础实例已使用 2222/7681 端口,凭据通过环境变量注入) | + +#### 代码质量评分:**7 / 10** (历史评分,文件已归档) + +> 扣分项:与 entrypoint.sh 高度重复(-1.5)、SSHD_PID 未赋值(-1)、全权限安全策略未文档化(-0.5) + +--- + +### ttyd-session.sh(tmux 会话管理) + +**文件路径:** `E:/wk-lab/workpod/ttyd-session.sh` + +#### 健壮性 + +| # | 问题 | 严重度 | 说明 | +|---|------|--------|------| +| R1 | **set -e 缺失** | 中 | 脚本没有 `set -e`。如果中间某条命令失败(如 `tmux list-sessions`),脚本会继续执行可能导致错误状态扩散。考虑到脚本的交互性质(需要 read 输入),`set -euo pipefail` 更合适但需注意 `read` 在 set -e 下的行为(read 到 EOF 返回非零不会终止脚本,因为它是条件上下文的一部分)。 | +| R2 | **SESSION_NAME 初始为空字符串** | 低 | 第4行 `SESSION_NAME=""` 初始化为空。如果 TTYD_QUERY_STRING 解析也得到空值(第9行的 grep 无匹配),则进入交互菜单分支。流程正确但变量生命周期不够明确。 | +| R3 | **tmux session 名过滤后可能为空** | 中 | 第55行 `tr -cd 'a-zA-Z0-9_\-'` 过滤后如果 SESSION_NAME 变成空字符串(例如原始输入全是特殊字符),第58行 `tmux new-session -t ""` 会创建一个名为空字符串的 session。虽然 tmux 允许这样做但不推荐。应在过滤后检查是否为空并赋予默认值 "default"。 | +| R4 | **sed -n "${CHOICE}p" 未做范围校验** | 中 | 第45行 `sed -n "${CHOICE}p"` 直接将用户输入的数字传给 sed。虽然前面有 `grep -qE '^[0-9]+$'` 校验了纯数字,但没有检查数字是否在有效范围内(1 到 session 数量之间)。超出范围的 CHOICE 会导致 sed 输出空行,SESSION_NAME 被设为空字符串,然后触发 R3 的问题。 | +| R5 | **EXISTING 变量的 word splitting** | 低 | 第20行 `tmux list-sessions` 输出类似 `my_session: (attached)` 格式,awk 提取第一列后得到 `my_session:`(带冒号)。第24行 `for s in $EXISTING` 依赖 word splitting 来遍历 session 名。如果 session 名包含空格(虽然 tmux 不允许),会出错。实际风险低。 | +| R6 | **exec tmux attach/new 替换进程** | 正面 | 第59-61行使用 `exec` 替换当前 shell 进程为 tmux,避免多余的 shell 进程残留。这是正确的做法。 | + +#### 可移植性 + +| # | 问题 | 严重度 | 说明 | +|---|------|--------|------| +| P1 | **BusyBox awk 兼容性(已修复)** | 已解决 | 历史上使用 `grep -oP`(Perl 正则)解析 query string,在 BusyBox 中不可用。当前版本第9行改用 `tr '\&' '\n' \| grep '^session=' \| cut -d= -f2` 的管道链,完全兼容 BusyBox。 | +| P2 | **tr -cd 字符类兼容性** | 低 | 第55行 `tr -cd 'a-zA-Z0-9_\-'` 中的 `\-` 在 GNU tr 和 BusyBox tr 中都能正确解释为字面量连字符。POSIX 标准中字符类内的 `-` 放在首位或末位或转义均可。 | +| P3 | **printf 格式化** | 正面 | 第25行 `printf "║ [%d] %-33s║\n" "$I" "$s"` 使用标准 printf 格式,跨实现兼容。 | +| P4 | **clear 命令** | 信息 | 第51行 `clear` 依赖 terminfo 数据库。在 Docker 的 ttyd 环境中通常可用。 | +| P5 | **grep -qE** | 正面 | 第44行使用 `grep -qE`(扩展正则),BusyBox awk 支持此选项。 | + +#### 安全性 + +| # | 问题 | 严重度 | 说明 | +|---|------|--------|------| +| S1 | **TTYD_QUERY_STRING 注入防护** | 正面 | 第9行通过管道链 `tr \| grep \| cut` 解析,最终结果经过第55行 `tr -cd` 过滤为安全字符集。即使攻击者构造恶意的 query string(如 `session=;rm -rf /`),也会被过滤掉。安全性良好。 | +| S2 | **用户输入 SESSION_NAME 注入防护** | 正面 | 第55行将用户交互输入同样通过 `tr -cd` 过滤,tmux session 名被限制在 `[a-zA-Z0-9_-]` 字符集中。无法注入 shell 命令。 | +| S3 | **无外部命令拼接** | 正面 | 所有变量传递给 tmux 时都加了双引号(`"$SESSION_NAME"`),防止 word splitting 和 globbing。 | + +#### 代码质量 + +| # | 问题 | 严重度 | 说明 | +|---|------|--------|------| +| Q1 | **UI 设计精美** | 正面 | 使用 Unicode 制表符绘制边框,视觉效果专业。编号选择 + 自定义名称的双重输入方式用户体验好。 | +| Q2 | **双模式设计合理** | 正面 | URL 带 `?session=xxx` 直接进入(自动化场景),不带参数显示菜单(手动场景)。覆盖了两种主要使用方式。 | +| Q3 | **默认值 fallback** | 正面 | 第39-41行用户输入空时默认为 "default"。 | +| Q4 | **缺少 set -e/o pipefail** | 低 | 如 R1 所述。对于这种交互式脚本,建议至少加 `set -uo pipefail`(不含 -e 以免影响 read)。 | +| Q5 | **冒号未从 session 名中去除** | 低 | 第20行 awk `{print $1}` 提取的是 `session_name:`(带尾部冒号)。这个带冒号的名字会被用于显示(第25行)和选择(第45行 sed 取行),最终传入 tmux 的也是带冒号的名字。tmux 本身允许 session 名包含冒号,所以功能不受影响,但显示上不够干净。可以用 `tr -d ':'` 清理。 | +| Q6 | **代码简洁高效** | 正面 | 仅 63 行实现了 query string 解析、交互式菜单、session 创建/附加的完整功能。没有冗余逻辑。 | + +#### 代码质量评分:**8 / 10** + +> 扣分项:缺少 set -euo pipefail(-0.5)、session 名空值/超范围未防护(-1)、冒号未清理(-0.5) + +--- + +## 跨脚本问题 + +| # | 问题 | 影响范围 | 说明 | +|---|----------|----------|------| +| X1 | **entrypoint.sh 与 entrypoint-test.sh(归档) 高度重复** | 2 个文件(其中1个已归档) | 开发工具检测(12行)、dev-tools.sh 写入(8行)、cleanup函数(8行)、trap注册(1行)、SSH启动+检查(10行)、ttyd启动+检查(8行)、信息展示(18行)、exec sleep(1行) —— 约 66 行完全相同,占总代码量 66/210 = 31%(不计公共部分则重复率更高)。**优先级大幅降低(entrypoint-test.sh 已归档)。** | +| X2 | **三个版本的 cleanup 函数逐字相同** | 3 个文件 | Ubuntu/Alpine/Test 三份 entrypoint 的 cleanup 函数(第7-14行)完全一致。这是典型的 DRY 违反。 | +| X3 | **ttyd 认证密码三套不同策略** | 3 个文件 | Ubuntu版无密码 / Alpine版 `ada:123` / Test版 `jc:1234567`。安全级别不一致,容易在部署时混淆。**[已修复]** — 当前版本统一从 `${TTYD_CREDENTIALS}` 环境变量读取。 | +| X4 | **dev-tools.sh 内容在 Alpine 和 Test 版中逐字相同** | 2 个文件 | entrypoint.sh 第80-88行与 entrypoint-test.sh 第32-40行的 heredoc 内容完全一致。 | +| X5 | **SSHD_PID 跟踪在所有版本中都无效** | 3 个文件(其中1个已归档) | 三个版本的 entrypoint 都声明了 `SSHD_PID=0` 且从未赋值,cleanup 中的 `kill "$SSHD_PID"` 分支永远不会执行。这是一个系统性缺陷。(低优先级,未修复) | +| X6 | **端口号/标题/密码散落在各脚本中** | 3 个文件(其中1个已归档) | 修改端口或密码需要在多个文件中同步修改,遗漏任一文件会导致配置不一致。**[已改善]** — 当前版本已集中到 docker-compose.yml 的 environment 配置中管理。 | + +--- + +## Top 10 改进建议 + +| 优先级 | 建议 | 涉及脚本 | 收益 | +|--------|-------|----------|------| +| P0 | **重构 entrypoint 公共框架(配置驱动)** | entrypoint.sh (x2, 其中1个已归档) | 消除 ~66 行重复代码,新增容器变体只需配置文件(优先级降低:entrypoint-test.sh 已归档) | +| P1 | **修复 SSHD_PID 追踪(统一方案)** | entrypoint.sh (x3) | 实现 sshd 优雅关闭,防止孤儿进程 | +| P2 | **ttyd-session.sh 添加 set -uo pipefail + 输入校验** | ttyd-session.sh | 防止空 session 名和越界访问 | +| P3 | **统一 ttyd 认证策略,密码从环境变量读取** | entrypoint.sh (x3) | 消除安全配置不一致,支持灵活部署(**已完成**) | +| P4 | **ttyd-session.sh 清理 session 名尾部冒号** | ttyd-session.sh | UI 显示更干净,避免意外行为 | +| P5 | **entrypoint-test.sh sudoers 文件写入增加原子性** | entrypoint-test.sh | 防止中断导致 sudo 不可用 | +| P6 | **document 全权限安全策略的影响范围** | entrypoint-test.sh | 让使用者明确了解 `allow:["*"]` + `NOPASSWD:ALL` 的安全含义 | +| P7 | **端口/标题/密码等配置外部化为环境变量** | entrypoint.sh (x3) | docker-compose.yml 集中管理,消除散落配置 | +| P8 | **考虑 entrypoint.sh shebang 改为 /bin/sh** | entrypoint.sh (Alpine x2) | 减少 bash 依赖,Alpine 环境更轻量(需确认无 bashism) | +| P9 | **sshd 启动增加 -D 模式或 PID 文件追踪** | entrypoint.sh (Alpine x2) | 更精确的进程生命周期管理 | + +--- + +## 附录:历史 Bug 回顾 + +| Bug | 影响 | 当前状态 | +|-----|------|----------| +| grep -P 在 BusyBox 不可用 | Alpine 环境 TTYD_QUERY_STRING 解析失败 | ttyd-session.sh 第9行已改用 `tr & '\n' \| grep '^session=' \| cut -d= -f2` 管道链,完全兼容 BusyBox | +| heredoc 单引号阻止变量展开 | .bashrc 中 ANTHROPIC_AUTH_TOKEN 等环境变量无法传递到 developer 用户 | entrypoint-test.sh 第64行已改用 `<< BASHRC` 双引号 heredoc,变量在写入时正确展开;`\$PATH` 转义确保字面量输出 | +| dev-tools.sh 反斜杠转义问题 | profile.d 脚本中 `\$PATH` 或 `\\` 导致语法错误 | 当前版本 dev-tools.sh 使用单引号 heredoc `<< 'PROFILE'`,内容原样写入无需转义,已修复 | diff --git a/docs/04-审核/05-性能与可维护性审核.md b/docs/04-审核/05-性能与可维护性审核.md new file mode 100644 index 0000000..dd6f90e --- /dev/null +++ b/docs/04-审核/05-性能与可维护性审核.md @@ -0,0 +1,333 @@ +# 性能与可维护性审核报告 + +> 审核日期:2026-04-07 +> 审核范围:WorkPod Alpine 版 (E:/wk-lab/workpod) +> 镜像大小:~436MB | 基础镜像:alpine:3.23(多阶段构建)| 内存限制:4G / 1G reservation +> 部署环境:测试服 4 个实例共享镜像 + +--- + +## 一、性能审核 + +### 1.1 镜像性能 + +| 指标 | 当前值 | 行业标准(同类开发容器) | 评价 | +|------|--------|------------------------|------| +| **镜像大小** | ~436 MB | 200~500 MB 为佳 | **优** -- 多阶段构建效果显著 | +| **基础镜像** | alpine:3.23 (~7MB) | alpine/debian-slim | **优** -- 最小化基础 | +| **构建层数** | 5 层有效(2阶段) | 3~6 层为佳 | **优** -- 多阶段构建,builder 层不进入最终镜像 | +| **层缓存命中率** | 中等 | >70% 为佳 | 中 -- npm install 每次可能因版本变化失效 | +| **构建产物清理** | builder 阶段自动丢弃 | 必须清理 | **优** -- 多阶段天然隔离 | +| **推送/拉取效率** | 436 MB 全量传输 | <500 MB 理想 | **优** -- 比 Ubuntu 版小 6 倍 | +| **存储占用** | ~436 MB/实例 x 4 = ~1.7 GB | <2 GB 总量 | **优** -- 4 实例总占用仍小于 Ubuntu 单实例 | + +#### 层分析(多阶段构建) + +``` +========== Builder 阶段 (不进入最终镜像) ========== +Layer B1 (FROM): alpine:3.23 ~7 MB +Layer B2 (RUN apk): xz + libstdc++ ~15 MB +Layer B3 (COPY pkgs): 本地 packages/ ~325 MB +Layer B4 (RUN npm): Node.js + claude + helper ~400 MB (含缓存) +───────────────────────────────────────────────────── +Builder 阶段总计: ~747 MB (构建时临时) + +========== 运行阶段 (最终镜像) ========== +Layer R1 (FROM): alpine:3.23 ~7 MB +Layer R2 (RUN apk): curl/git/ssh/ttyd/tmux/bash ~80 MB +Layer R3 (COPY): /opt/node -> /usr/local ~200 MB (仅产物) +Layer R4 (COPY+RUN): entrypoint + ttyd-session <10 KB +───────────────────────────────────────────────────── +最终镜像总计: ~436 MB (压缩后) +``` + +#### 与 Ubuntu 版对比 + +| 对比项 | Ubuntu 版 | Alpine 版 | 倍率 | +|--------|----------|-----------|------| +| 镜像大小 | 2.6 GB | 436 MB | **6x 更小** | +| 基础镜像 | ubuntu:22.04 (77MB) | alpine:3.23 (7MB) | **11x 更小** | +| 内存基线 | ~400 MB | ~80 MB | **5x 更省** | +| 构建策略 | 单阶段 | 多阶段 | Alpine 更优 | +| 推送时间(100Mbps) | ~3.5 min | ~35 s | **6x 更快** | + +### 1.2 运行时性能 + +| 指标 | 当前值 | 行业标准 | 评价 | +|------|--------|---------|------| +| **内存基线占用** | ~80-120 MB (Alpine base) | 50-150 MB | **优** -- Alpine 最小化系统 | +| **应用内存** | Node.js ~50 MB + ttyd ~8 MB + sshd ~3 MB + tmux ~2 MB | 合理范围 | **优** | +| **总内存限制** | 4G limit / 1G reservation | 开发容器 2-4G 即够 | **良** -- reservation 1G 合理 | +| **启动时间** | ~3-5s (entrypoint 执行) | <10s 可接受 | **良** | +| **entrypoint 流程** | PATH检测 -> 用户创建 -> 配置写入 -> SSH启动 -> ttyd启动 -> 健康检查 | 较复杂 | 中 -- 步骤较多但必要 | +| **信号处理** | trap SIGTERM/SIGINT/SIGQUIT + cleanup() | 必须具备 | 良 -- 有优雅关闭 | +| **ttyd 性能** | WebSocket 直连 + tmux 会话管理 | 标准 | **优** -- 会话恢复能力强 | +| **特权模式** | privileged: true | 应避免 | 差 -- 安全风险 | +| **4 实例并发** | 共享同一镜像 | 标准 | **优** -- 镜像小,存储压力低 | + +#### 启动流程时间分解 + +``` +t=0s entrypoint.sh 开始执行 +t≈0.05s PATH 自动检测 (6 个目录检查) +t≈0.1s 创建 developer 用户 + sudoers + .claude 配置 (~10 个文件操作) +t≈0.3s 写入 /etc/profile.d/dev-tools.sh +t≈0.5s /usr/sbin/sshd 启动 +t≈0.6s ttyd -W -c ... /opt/ttyd-session.sh & (后台启动) +t≈1.6s sleep 1 (等待 ttyd 就绪) +t≈1.7s kill -0 $TTYD_PID (健康验证) +t≈1.8s pidof sshd (SSH 验证) +t≈2.5s 版本检测输出 (node/npm/claude/rust/go/python) +t≈2.5s exec sleep infinity (PID 1 接管) +总计:约 2.5-4 秒(比 Ubuntu 稍慢,因多了用户创建和配置步骤) +``` + +### 1.3 I/O 性能 + +| 指标 | 当前配置 | 评价 | +|------|---------|------| +| **卷挂载方式** | bind mount (`./data/workspace:/workspace`) | 标准 | +| **日志驱动** | json-file, max-size=10m, max-file=3 | 良 -- 有轮转 | +| **/root 持久化** | docker-compose-alpine.yml 有挂载 (`./data/workpod-alpine/root:/root`) | 良 -- 支持工具持久化 | +| **tmpfs 使用** | 未使用 | 中 -- /tmp、/run 可用 tmpfs 提升性能 | +| **entrypoint.sh / ttyd-session.sh 内置** | `COPY --chmod=755 entrypoint.sh /entrypoint.sh` + `COPY --chmod=755 ttyd-session.sh /opt/ttyd-session.sh` (Dockerfile) | **优** -- 已内置到镜像,符合 immutable artifact 最佳实践 | + +#### entrypoint / ttyd-session 内置到镜像 + +entrypoint.sh 和 ttyd-session.sh 均已通过 `COPY --chmod=755` 固化到镜像中(Dockerfile),不再使用 bind mount 外挂。这是 4/4 修复 execvp failed 问题时做的改动。 + +**改进效果**: +- 消除了宿主机文件缺失导致容器启动失败的风险(4/4 execvp failed 根因) +- 文件权限由 Docker COPY 的 `--chmod=755` 保证,不受 Windows/Linux 路径转换影响 +- 符合"镜像即 immutable artifact"的最佳实践 +- 代价:开发阶段修改脚本后需要 rebuild 镜像(可接受) + +--- + +## 二、可维护性审核 + +### 2.1 文档评估 + +| 文档 | 完整度 | 时效性 | 问题 | +|------|--------|--------|------| +| **ISSUES.md** | 9/10 | 最新 (2026-04-07) | 问题记录详实,含根因分析和解决方案;是项目最有价值的运维文档 | +| **README.md** | 缺失 | N/A | **仍然缺失** -- 新人无法快速上手(P0 级缺口) | +| **SPECS.md** | 缺失 | N/A | **无技术规格文档** -- 架构决策无书面记录 | +| **CHANGELOG** | 缺失 | N/A | **无变更日志** -- ISSUES.md 部分承担此功能但不规范 | +| **PACKAGES.md** | 缺失 | N/A | **仍然缺失** -- 包清单未独立维护 | +| **API 文档** | 0/10 | 不适用 | auth-proxy.js 的 API 无文档 | + +#### 关键文档问题 + +1. **缺少 README.md** -- 作为测试服部署的正式版本,没有入门文档是不可接受的。新运维人员无法知道如何构建、启动、连接。 +2. **ISSUES.md 承担了过多角色** -- 它同时充当了 CHANGELOG、FAQ、故障排查指南的角色,结构上不如独立文档清晰。 +3. **auth-proxy.js 和 login.html 无文档** -- 认证代理是安全关键组件,其工作原理、配置方法应有说明。 + +### 2.2 代码可维护性 + +#### 2.2.1 版本管理(硬编码集中度) + +| 组件 | Dockerfile | download-packages.sh | 分散度 | +|------|-----------|---------------------|--------| +| Node.js | v24.14.1 (musl) | v24.14.1 (musl) | 低 | +| Claude Code | @latest (Dockerfile) | 2.1.87 (download-packages.sh) | 中 -- Dockerfile 已改用 @latest,构建时拉取最新版 | +| coding-helper | @latest | 0.0.7 | **中 (@latest 不确定)** -- 用户决策:保持 @latest 以自动跟进更新 | +| OpenClaw | 未安装 | 2026.3.28 (下载了但未安装) | 中 -- download-packages.sh 已补回主目录,保留下载逻辑 | +| ttyd | (apk, 版本由仓库决定) | - | 低 | + +**问题状态**: + +1. **Claude Code 版本不一致** -- **[已解决]** Dockerfile 已改为 `@anthropic-ai/claude-code@latest`(Dockerfile:18),不再硬编码版本号。download-packages.sh 仍保留固定版本 2.1.87 用于离线缓存,两者不再冲突。 +2. **OpenClaw 下载了但未安装** -- 保持原状。download-packages.sh 已从 workpod-alpine 补回主目录,保留 OpenClaw 下载逻辑供未来使用。 +3. **coding-helper@latest** -- 用户决策保持 `@latest`,接受构建结果不确定性以换取自动更新便利。 + +#### 2.2.2 配置管理 + +| 配置项 | 硬编码位置 | 是否可通过环境变量覆盖 | +|--------|-----------|---------------------| +| SSH 密码 | Dockerfile (`ROOT_PASSWORD:-workpod123`) | **是** -- ROOT_PASSWORD 环境变量(docker-compose.yml:26) | +| ttyd 凭据 | entrypoint.sh (`TTYD_CREDENTIALS:-jc:1234567`) | **是** -- TTYD_CREDENTIALS 环境变量(docker-compose.yml:27) | +| ttyd 主题色 | entrypoint.sh (`#1a1a2e`) | 否 | +| npm registry | Dockerfile (`npmmirror.com`) | 否 | +| Claude Code 别名 | .bashrc (`--dangerously-skip-permissions` / `--allow-dangerously-skip-permissions`) | 否 | +| 时区 | Dockerfile ENV + docker-compose | 是 | +| 内存限制 | docker-compose | 是 | + +**问题状态**: +- **SSH 密码硬编码** -- **[已改善]** Dockerfile 改为 `${ROOT_PASSWORD:-workpod123}`(Dockerfile:34),docker-compose.yml 通过环境变量传入 `ROOT_PASSWORD`。默认值仍为 workpod123 但已可外部配置。 +- **ttyd 凭据硬编码** -- **[已修复]** entrypoint.sh 改为 `${TTYD_CREDENTIALS:-jc:1234567}`(entrypoint.sh:50),docker-compose.yml 通过 `TTYD_CREDENTIALS` 环境变量传入。凭据完全外部化。 +- **两套 entrypoint 凭据不一致** -- **[不适用]** entrypoint-test.sh 已归档,当前仅保留一份 entrypoint.sh,不存在多份脚本凭据不一致问题。 +- **Claude Code 全权限别名硬编码**:`--dangerously-skip-permissions` 直接写在 .bashrc 中,无法通过环境变量控制。 + +#### 2.2.3 当前代码规模(Alpine 主版本) + +> 注:Ubuntu 版已不在主目录中,不再进行跨版本对比。 + +| 文件 | 行数 | 说明 | +|------|------|------| +| Dockerfile | 58 | 多阶段构建,含 ROOT_PASSWORD/TTYD_CREDENTIALS 环境变量支持 | +| entrypoint.sh | 86 | 单一入口脚本,凭据通过 TTYD_CREDENTIALS 环境变量读取 | +| ttyd-session.sh | 63 | ttyd 会话管理脚本(Alpine 独有) | +| docker-compose.yml | 64 | 含 ROOT_PASSWORD/TTYD_CREDENTIALS 环境变量配置 | +| download-packages.sh | 65 | 包下载脚本(已补回主目录) | +| auth-proxy.js | ~173 | 认证代理(Alpine 独有) | + +**代码质量改善**: +- entrypoint 从 3 份脚本(entrypoint.sh x2 + entrypoint-test.sh)精简为 **1 份**(entrypoint.sh),消除了脚本间的不一致风险。 +- 凭据统一通过环境变量注入(ROOT_PASSWORD、TTYD_CREDENTIALS),不再硬编码在多份文件中。 + +#### 2.2.4 代码质量问题 + +| # | 位置 | 问题 | 严重程度 | 状态 | +|---|------|------|---------|------| +| 1 | Dockerfile:18 vs download-packages.sh:32 | Claude Code 版本不一致(已改 @latest) | 高 | **[已解决]** -- Dockerfile 改为 @latest,不再硬编码版本号 | +| 2 | download-packages.sh:40-48 | OpenClaw 下载但 Dockerfile 未安装 | 中 | 保持 -- download-packages.sh 已补回主目录,保留供未来使用 | +| 3 | entrypoint.sh:50 (历史) | ttyd 凭据不一致(多脚本时代) | 中 | **[已解决]** -- 单一 entrypoint.sh + TTYD_CREDENTIALS 环境变量 | +| 4 | Dockerfile:19 | `@z_ai/coding-helper@latest` 不确定版本 | 中 | 用户决策保持 @latest | +| 5 | entrypoint-test.sh (已归档) | ANTHROPIC_AUTH_TOKEN 等 env 直接嵌入 .bashrc | 低 | **[不适用]** -- entrypoint-test.sh 已归档 | +| 6 | auth-proxy.js:8 | 密码明文写死在源码中 (`1234567`, `admin123`) | **高** | 保持 -- auth-proxy.js 已补回主目录,密码外部化待后续处理 | +| 7 | docker-compose.yml (历史) | entrypoint.sh 外挂而非内置镜像 | 中 | **[已解决]** -- 改为 COPY --chmod=755 内置到镜像 | + +### 2.3 运维评估 + +#### 2.3.1 故障排查便利性 + +| 能力 | 具备情况 | 评价 | +|------|---------|------| +| **健康检查** | HEALTHCHECK (netstat) + docker-compose healthcheck | 良 -- 已修复 ss->netstat 问题(见 ISSUES.md) | +| **问题追踪** | ISSUES.md 实时记录 | **优** -- 这是项目最大的运维亮点 | +| **结构化日志** | 无 -- 仅文本输出 | 中 | +| **日志轮转** | json-file driver, 10m*3 | 良 | +| **启动诊断输出** | entrypoint 打印完整版本信息(Node/npm/Claude/Rust/Go/Python) | **优** -- 比 Ubuntu 版更全面 | +| **错误退出码** | ttyd/sshd 失败 exit 1 | 良 | +| **监控指标** | 无 | 差 | +| **会话恢复** | tmux + ttyd-session.sh | **优** -- 断线重连不丢失上下文 | + +#### 2.3.2 升级流程复杂度 + +| 升级场景 | 步骤数 | 复杂度 | +|---------|-------|--------| +| 升级 Node.js | 3 步(改下载脚本 -> 下载 musl 包 -> rebuild) | 中 -- musl 包源不同 | +| 升级 Claude Code | 3 步(需同步改 Dockerfile + download-packages.sh) | **中偏高** -- 两处版本号要一致 | +| 升级 ttyd | 1 步(rebuild,apk 自动拉取) | 低 -- 但版本不可控 | +| 新增开发实例 | 复制 docker-compose-alpine.yml 改端口 | 低 | +| 同步修复到 Ubuntu | 手动对比 + 双份修改 | **高** | + +#### 2.3.3 回滚能力 + +| 场景 | 回滚方式 | 可行性 | +|------|---------|--------| +| 镜像回滚 | `docker load < workpod-alpine-latest.tar.gz` (已存在) | **优** -- 有导出文件 | +| 数据回滚 | /root 已 bind mount,可手动备份 | 中 | +| 配置回滚 | git checkout | 良 | +| 快速回退 | 无原生支持 | 中 | + +#### 2.3.4 测试服 4 实例运营评估 + +| 项目 | 当前状态 | 评价 | +|------|---------|------| +| **镜像共享** | 4 实例共用 workpod-alpine:latest | **优** -- 存储高效 | +| **端口规划** | 2222(SSH 基础实例), 7681(ttyd 基础实例) / 2201(SSH Flux), 7701(ttyd Flux) | 中 -- 需要文档化端口分配表 | +| **数据隔离** | 各实例独立 data/ 目录 | **优** | +| **认证代理** | auth-proxy.js (端口 8080) | **优** -- 统一入口 + token 认证 | +| **负载均衡** | 无 (各实例独立端口) | 中 -- 小规模够用 | +| **扩容** | 复制 compose 文件改端口 | 低 -- 手动但简单 | + +#### 2.3.5 安全评估 + +| 项目 | 当前状态 | 风险等级 | +|------|---------|---------| +| privileged: true | 启用 | **高** | +| root 用户运行 | 默认 root | **高** | +| SSH 密码 | ROOT_PASSWORD 环境变量(默认 workpod123) | **中→改善** -- 已可外部配置 | +| ttyd 认证 | TTYD_CREDENTIALS 环境变量(默认 jc:1234567) | **低→更好** -- 凭据完全外部化,比硬编码显著改善 | +| auth-proxy | Basic Auth + Token | **良** -- 有认证层 | +| auth-proxy 密码 | 明码硬编码 (1234567, admin123) | **高** -- auth-proxy.js 自身问题,待后续外部化 | +| 端口暴露 | 2222 + 7681 (基础实例) / 2201 + 7701 (Flux 实例) | **中** | +| developer 用户 | sudo NOPASSWD | **中** | +| Claude Code 全权限 | --dangerously-skip-permissions 默认开启 | **中** | + +--- + +## 三、综合评分 + +| 维度 | Alpine 版得分 | 说明 | +|------|-------------|------| +| **性能** | **8.5/10** | 镜像极小(436MB)、内存占用低、多阶段构建优秀;entrypoint 已内置镜像(+0.5);privileged 仍扣分 | +| **可维护性** | **6/10** | 较上期 +1:版本统一(@latest)、凭据外部化(ROOT_PASSWORD/TTYD_CREDENTIALS)、单 entrypoint 脚本、entrypoint 内置镜像;仍缺 README/SPECS/PACKAGES、@latest tag 不确定、auth-proxy 密码未外部化 | +| **文档** | **4/10** | ISSUES.md 一枝独秀(9/10);但零 README(仍然缺失)、零 SPECS、零 CHANGELOG、零 PACKAGES,新人完全无法入手 | +| **运维** | **6.5/10** | 较上期 +0.5:健康检查完善、问题追踪及时、tmux 会话管理优秀、认证代理加分、凭据可配置化改善运维体验;无监控、安全配置有改进空间 | +| **总分** | **25/40** | 性能突出,可维护性和运维较上期有实质改善;文档短板仍是最大拖累,属于"技术优秀、工程化持续改善中"水平 | + +--- + +## 四、优先改进项(按 ROI 排序) + +| # | 改进项 | 影响 | 成本 | ROI | 说明 | +|---|--------|------|------|-----|------| +| 1 | **创建 README.md** | 新人可快速上手 | 低 | **极高** | **仍然是 P0** -- 作为测试服正式版本,这是最紧迫的缺口 | +| 2 | ~~统一 Claude Code 版本号~~ | 消除构建不确定性 | 极低 | **极高** | **[完成]** -- Dockerfile 已改为 @latest,不再硬编码版本号 | +| 3 | ~~移除 OpenClaw 下载或安装它~~ | 消除无用依赖 | 极低 | 高 | **降级** -- download-packages.sh 已补回主目录,保留下载逻辑供未来使用;不再紧迫 | +| 4 | **提取 entrypoint 公共模块** | 消除重复代码 | 中 | 中 | **优先级降低** -- 当前仅单 entrypoint 场景(entrypoint-test.sh 已归档),跨版本同步成本已消除 | +| 5 | ~~统一 ttyd 凭据管理~~ | 消除配置混乱 | 低 | **高** | **[完成]** -- TTYD_CREDENTIALS 环境变量已实现凭据外部化 | +| 6 | **auth-proxy 密码外部化** | 消除安全隐患 | 低 | **高** | auth-proxy.js 明文密码待改为环境变量或配置文件读取 | +| 7 | **coding-helper 改固定版本** | 构建可重现 | 极低 | **中** | 用户当前选择保持 @latest,如需可重现构建则替换为具体版本号 | +| 8 | ~~将 entrypoint.sh 内置到镜像~~ | 符合 immutable 镜像最佳实践 | 低 | **中** | **[完成]** -- COPY --chmod=755 已实现内置 | +| 9 | **添加 SPECS.md / PACKAGES.md** | 文档体系完善 | 中 | **中** | 多阶段架构、认证代理设计、包清单值得记录 | +| 10 | **评估取消 privileged** | 安全性提升 | 中 | 测试是否真正需要,尝试 `--cap-add SYS_ADMIN` 等细粒度替代 | + +--- + +## 五、与 Ubuntu 版对比摘要 + +| 维度 | Alpine 版 | Ubuntu 版 | 胜出者 | +|------|----------|-----------|--------| +| 镜像大小 | 436 MB | 2.6 GB | **Alpine (6x)** | +| 内存基线 | ~80 MB | ~400 MB | **Alpine (5x)** | +| 构架先进性 | 多阶段构建 | 单阶段构建 | **Alpine** | +| 功能完整性 | 含 tmux session 管理 | 含 OpenClaw | 各有侧重 | +| 安全性(ttyd) | 有认证 (-c) | 无认证 (-W) | **Alpine** | +| 文档体系 | ISSUES.md 优秀但单一 | SPECS+PACKAGES+REVIEW 较全 | **Ubuntu (广度)** | +| 文档时效性 | ISSUES 实时更新 | SPECS 过时 | **Alpine** | +| 代码重复度 | 高(3 份脚本) | 中(2 份脚本) | 都差 | +| 版本一致性 | 有偏差(Claude Code) | 基本一致 | **Ubuntu** | +| 生产就绪度 | 测试服 4 实例运行中 | 本地开发为主 | **Alpine** | + +**结论**:Alpine 版在性能和生产适用性上是明确的胜出者。其主要债务在于文档缺失(尤其是 README)和版本号不一致。建议以 Alpine 版为主线版本,Ubuntu 版降级为本地调试辅助版本。 + +--- + +## 六、附录:auth-proxy.js 架构评审 + +auth-proxy.js 是 Alpine 版独有的认证代理组件,值得单独关注。 + +### 架构概览 + +``` +浏览器 --> :8080 auth-proxy.js --> :7681 ttyd 实例 + |-- /login 登录页 + |-- /auth/check Basic Auth -> Token + |-- /api/workspaces 工作空间列表 + |-- /* Token 验证 -> 代理到对应 ttyd + |-- (WebSocket) upgrade -> 双向管道 +``` + +### 优点 +- 轻量实现(173 行),无第三方依赖 +- Token 机制(1 小时过期)避免密码反复传输 +- WebSocket 代理支持终端正常工作 +- 多工作空间路由 + +### 风险点 +| # | 风险 | 说明 | +|---|------|------| +| 1 | **Token 存储在内存** | 进程重启后所有 Token 失效,用户需重新登录 | +| 2 | **密码明文硬编码** | `VALID_PASSWORDS = { wk: '1234567', admin: 'admin123' }` | +| 3 | **无 HTTPS** | 密码和 Token 明文传输 | +| 4 | **单进程无集群** | 无法横向扩展 | +| 5 | **无速率限制** | 暴力破解密码无防护 | + +### 建议 +- 密码改为环境变量:`process.env.AUTH_PASSWORDS` +- 生产环境前置 nginx/Terminus 做 HTTPS 结束 +- 添加登录失败次数限制(内存计数器即可) diff --git a/download-packages.ps1 b/download-packages.ps1 deleted file mode 100644 index 9e80004..0000000 --- a/download-packages.ps1 +++ /dev/null @@ -1,95 +0,0 @@ -# Dev Box 软件包下载脚本 (Windows PowerShell) -# 使用国内镜像源,加速下载 - -$ErrorActionPreference = "Stop" -$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path -$PackagesDir = Join-Path $ScriptDir "packages" - -New-Item -ItemType Directory -Force -Path $PackagesDir | Out-Null -Set-Location $PackagesDir - -Write-Host "" -Write-Host "========================================" -ForegroundColor Cyan -Write-Host " Dev Box 软件包下载(国内镜像)" -ForegroundColor Cyan -Write-Host "========================================" -ForegroundColor Cyan -Write-Host "" - -# ============ Node.js ============ -$NodeVersion = "v24.14.0" -$NodeFile = "node-$NodeVersion-linux-x64.tar.xz" - -Write-Host "[1/5] Node.js $NodeVersion..." -ForegroundColor Yellow -if (-not (Test-Path $NodeFile)) { - # 淘宝镜像 - Invoke-WebRequest -Uri "https://npmmirror.com/mirrors/node/$NodeVersion/$NodeFile" -OutFile $NodeFile - Write-Host " ✓ 下载完成" -ForegroundColor Green -} else { - Write-Host " - 已存在,跳过" -ForegroundColor Gray -} - -# ============ Go ============ -$GoVersion = "1.26.1" -$GoFile = "go$GoVersion.linux-amd64.tar.gz" - -Write-Host "[2/5] Go $GoVersion..." -ForegroundColor Yellow -if (-not (Test-Path $GoFile)) { - # Go 官方中国镜像 - Invoke-WebRequest -Uri "https://golang.google.cn/dl/$GoFile" -OutFile $GoFile - Write-Host " ✓ 下载完成" -ForegroundColor Green -} else { - Write-Host " - 已存在,跳过" -ForegroundColor Gray -} - -# ============ Rust ============ -$RustVersion = "1.94.0" -$RustFile = "rust-$RustVersion-x86_64-unknown-linux-gnu.tar.xz" - -Write-Host "[3/5] Rust $RustVersion..." -ForegroundColor Yellow -if (-not (Test-Path $RustFile)) { - # 中科大镜像 - Invoke-WebRequest -Uri "https://mirrors.ustc.edu.cn/rust-static/dist/$RustFile" -OutFile $RustFile - Write-Host " ✓ 下载完成" -ForegroundColor Green -} else { - Write-Host " - 已存在,跳过" -ForegroundColor Gray -} - -# ============ Claude Code ============ -Write-Host "[4/5] Claude Code..." -ForegroundColor Yellow -if (-not (Test-Path "claude-code-2.1.79.tgz")) { - # 设置淘宝镜像并下载 - npm pack @anthropic-ai/claude-code@2.1.79 --registry=https://registry.npmmirror.com - # 重命名 - Get-ChildItem "anthropic-ai-claude-code-*.tgz" | ForEach-Object { - Rename-Item $_.FullName "claude-code-2.1.79.tgz" -Force - } - Write-Host " ✓ 下载完成" -ForegroundColor Green -} else { - Write-Host " - 已存在,跳过" -ForegroundColor Gray -} - -# ============ OpenClaw ============ -Write-Host "[5/5] OpenClaw..." -ForegroundColor Yellow -if (-not (Test-Path "openclaw-2026.3.13.tgz")) { - # 设置淘宝镜像并下载 - npm pack openclaw@2026.3.13 --registry=https://registry.npmmirror.com - # 重命名 - Get-ChildItem "openclaw-*.tgz" | ForEach-Object { - Rename-Item $_.FullName "openclaw-2026.3.13.tgz" -Force - } - Write-Host " ✓ 下载完成" -ForegroundColor Green -} else { - Write-Host " - 已存在,跳过" -ForegroundColor Gray -} - -Write-Host "" -Write-Host "========================================" -ForegroundColor Cyan -Write-Host " 下载完成" -ForegroundColor Cyan -Write-Host "========================================" -ForegroundColor Cyan -Write-Host "" -Write-Host "文件列表:" -ForegroundColor White -Get-ChildItem $PackagesDir -Filter "*.tar.*" | ForEach-Object { Write-Host " $($_.Name) ($('{0:N0}' -f ($_.Length/1MB)) MB)" } -Get-ChildItem $PackagesDir -Filter "*.tgz" | ForEach-Object { Write-Host " $($_.Name) ($('{0:N0}' -f ($_.Length/1MB)) MB)" } - -$TotalSize = (Get-ChildItem $PackagesDir | Measure-Object -Property Length -Sum).Sum / 1MB -Write-Host "" -Write-Host "总大小: $('{0:N0}' -f $TotalSize) MB" -ForegroundColor Cyan diff --git a/download-packages.sh b/download-packages.sh index 41c4f8e..1808c08 100644 --- a/download-packages.sh +++ b/download-packages.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Dev Box 软件包下载脚本 +# WorkPod Alpine 软件包下载脚本 # 使用国内镜像源,加速下载 set -e @@ -11,75 +11,48 @@ mkdir -p "$PACKAGES_DIR" cd "$PACKAGES_DIR" echo "========================================" -echo " Dev Box 软件包下载(国内镜像)" +echo " WorkPod Alpine 软件包下载" echo "========================================" echo "" -# ============ Node.js ============ -NODE_VERSION="v24.14.0" -NODE_FILE="node-${NODE_VERSION}-linux-x64.tar.xz" +# ============ Node.js (musl build) ============ +NODE_VERSION="v24.14.1" +NODE_FILE="node-${NODE_VERSION}-linux-x64-musl.tar.gz" -echo "[1/5] Node.js ${NODE_VERSION}..." +echo "[1/4] Node.js ${NODE_VERSION} (musl)..." if [ ! -f "$NODE_FILE" ]; then - # 淘宝镜像 - curl -L -o "$NODE_FILE" "https://npmmirror.com/mirrors/node/${NODE_VERSION}/${NODE_FILE}" - echo " ✓ 下载完成" -else - echo " - 已存在,跳过" -fi - -# ============ Go ============ -GO_VERSION="1.26.1" -GO_FILE="go${GO_VERSION}.linux-amd64.tar.gz" - -echo "[2/5] Go ${GO_VERSION}..." -if [ ! -f "$GO_FILE" ]; then - # Go 官方中国镜像 - curl -L -o "$GO_FILE" "https://golang.google.cn/dl/${GO_FILE}" - echo " ✓ 下载完成" -else - echo " - 已存在,跳过" -fi - -# ============ Rust ============ -RUST_VERSION="1.94.0" -RUST_FILE="rust-${RUST_VERSION}-x86_64-unknown-linux-gnu.tar.xz" - -echo "[3/5] Rust ${RUST_VERSION}..." -if [ ! -f "$RUST_FILE" ]; then - # 中科大镜像 - curl -L -o "$RUST_FILE" "https://mirrors.ustc.edu.cn/rust-static/dist/${RUST_FILE}" - echo " ✓ 下载完成" + curl -L -o "$NODE_FILE" "https://unofficial-builds.nodejs.org/download/release/${NODE_VERSION}/${NODE_FILE}" + echo " OK" else echo " - 已存在,跳过" fi # ============ Claude Code ============ -echo "[4/5] Claude Code..." -if [ ! -f "claude-code-2.1.78.tgz" ]; then - # 需要从 npm 下载,设置淘宝镜像 - npm pack @anthropic-ai/claude-code --registry=https://registry.npmmirror.com 2>/dev/null || \ - npm pack @anthropic-ai/claude-code@2.1.78 --registry=https://registry.npmmirror.com - # 重命名为固定名称 - for f in anthropic-ai-claude-code-*.tgz; do - [ -f "$f" ] && mv "$f" "claude-code-2.1.78.tgz" - done - echo " ✓ 下载完成" +echo "[2/4] Claude Code..." +if [ ! -f "claude-code-2.1.87.tgz" ]; then + npm pack @anthropic-ai/claude-code@2.1.87 --registry=https://registry.npmmirror.com + mv anthropic-ai-claude-code-*.tgz "claude-code-2.1.87.tgz" + echo " OK" else echo " - 已存在,跳过" fi # ============ OpenClaw ============ -echo "[5/5] OpenClaw..." -if [ ! -f "openclaw-2026.3.13.tgz" ]; then - # 从 npm 下载 - npm pack openclaw@2026.3.13 --registry=https://registry.npmmirror.com 2>/dev/null || \ - npm pack openclaw --registry=https://registry.npmmirror.com - # 重命名为固定名称 - for f in openclaw-*.tgz; do - [ -f "$f" ] && mv "$f" "openclaw-2026.3.13.tgz" - done - echo " ✓ 下载完成" +echo "[3/4] OpenClaw..." +if [ ! -f "openclaw-2026.3.28.tgz" ]; then + npm pack openclaw@2026.3.28 --registry=https://registry.npmmirror.com + mv openclaw-*.tgz "openclaw-2026.3.28.tgz" + echo " OK" +else + echo " - 已存在,跳过" +fi + +# ============ coding-helper ============ +echo "[4/4] coding-helper..." +if [ ! -f "z_ai-coding-helper-0.0.7.tgz" ]; then + npm pack @z_ai/coding-helper@latest --registry=https://registry.npmmirror.com + mv z_ai-coding-helper-*.tgz "z_ai-coding-helper-0.0.7.tgz" + echo " OK" else echo " - 已存在,跳过" fi @@ -88,9 +61,4 @@ echo "" echo "========================================" echo " 下载完成" echo "========================================" -echo "" -echo "文件列表:" -ls -lh "$PACKAGES_DIR"/*.tar.* "$PACKAGES_DIR"/*.tgz 2>/dev/null || true -echo "" -echo "总大小:" -du -sh "$PACKAGES_DIR" +ls -lh "$PACKAGES_DIR" diff --git a/entrypoint-test.sh b/entrypoint-test.sh new file mode 100644 index 0000000..132ef08 --- /dev/null +++ b/entrypoint-test.sh @@ -0,0 +1,128 @@ +#!/bin/bash +set -e + +SSHD_PID=0 +TTYD_PID=0 + +cleanup() { + echo "[entrypoint] 收到终止信号,正在关闭服务..." + [ "$TTYD_PID" -ne 0 ] && kill "$TTYD_PID" 2>/dev/null + [ "$SSHD_PID" -ne 0 ] && kill "$SSHD_PID" 2>/dev/null + wait 2>/dev/null + echo "[entrypoint] 服务已关闭" + exit 0 +} + +trap cleanup SIGTERM SIGINT SIGQUIT + +# 自动检测 /root 下的开发工具并设置 PATH +[ -d /root/.cargo/bin ] && export PATH="/root/.cargo/bin:$PATH" +[ -d /root/.rustup ] && export RUSTUP_HOME=/root/.rustup CARGO_HOME=/root/.cargo +[ -d /root/gvm/gos ] && export PATH="/root/gvm/gos/current/bin:$PATH" +[ -d /root/.pyenv/shims ] && export PATH="/root/.pyenv/shims:$PATH" +[ -d /root/.pyenv/bin ] && export PATH="/root/.pyenv/bin:$PATH" +[ -d /root/go/bin ] && export PATH="/root/go/bin:$PATH" && export GOROOT=/root/go +[ -d /root/.local/bin ] && export PATH="/root/.local/bin:$PATH" + +# 检测 Rust 独立安装(非 rustup) +RUST_DIR=$(ls -d /root/rust-*/bin 2>/dev/null | head -1) +[ -n "$RUST_DIR" ] && export PATH="$RUST_DIR:$PATH" + +# 创建非 root 用户 developer(用于 Claude Code 全权限沙箱) +id developer 2>/dev/null || adduser -D -s /bin/bash developer +mkdir -p /etc/sudoers.d +echo "developer ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/developer +chmod 440 /etc/sudoers.d/developer +mkdir -p /home/developer/.claude +chown -R developer:developer /home/developer + +# Claude Code 全权限配置 —— root 用户 +mkdir -p /root/.claude +cat > /root/.claude/settings.json << 'SETTINGS' +{ + "permissions": { + "allow": ["*"], + "deny": [] + } +} +SETTINGS + +# Claude Code 全权限配置 —— developer 用户 +cat > /home/developer/.claude/settings.json << 'SETTINGS' +{ + "permissions": { + "allow": ["*"], + "deny": [] + } +} +SETTINGS +chown developer:developer /home/developer/.claude/settings.json + +# 创建便捷别名(含 Claude Code 密钥环境变量) +# root 用 --allow-dangerously-skip-permissions(root 禁止 --dangerously-skip-permissions) +# developer 用 --dangerously-skip-permissions +cat > /home/developer/.bashrc << BASHRC +alias claude='claude --dangerously-skip-permissions' +export PATH="/usr/local/bin:/usr/bin:/bin:\$PATH" +export ANTHROPIC_AUTH_TOKEN="${ANTHROPIC_AUTH_TOKEN}" +export ANTHROPIC_BASE_URL="${ANTHROPIC_BASE_URL}" +export API_TIMEOUT_MS="${API_TIMEOUT_MS:-3000000}" +export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC="${CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:-1}" +BASHRC +chown developer:developer /home/developer/.bashrc + +# root 的全局别名 +cat > /root/.bashrc << 'ROOTRC' +alias claude='claude --allow-dangerously-skip-permissions' +ROOTRC + +# 写入 /etc/profile.d/ 让所有 shell 会话生效 +cat > /etc/profile.d/dev-tools.sh << 'PROFILE' +[ -d /root/.cargo/bin ] && export PATH="/root/.cargo/bin:$PATH" && export RUSTUP_HOME=/root/.rustup CARGO_HOME=/root/.cargo +[ -d /root/gvm/gos ] && source /root/gvm/scripts/gvm 2>/dev/null +[ -d /root/.pyenv/shims ] && export PATH="/root/.pyenv/shims:/root/.pyenv/bin:$PATH" +[ -d /root/go/bin ] && export PATH="/root/go/bin:$PATH" && export GOROOT=/root/go +[ -d /root/.local/bin ] && export PATH="/root/.local/bin:$PATH" +RUST_DIR=$(ls -d /root/rust-*/bin 2>/dev/null | head -1) +[ -n "$RUST_DIR" ] && export PATH="$RUST_DIR:$PATH" +PROFILE + +/usr/sbin/sshd +echo "[entrypoint] SSH 已启动" + +ttyd -W -c jc:1234567 -t fontSize=16 -t theme='{"background":"#1a1a2e"}' /opt/ttyd-session.sh & +TTYD_PID=$! + +sleep 1 +if ! kill -0 "$TTYD_PID" 2>/dev/null; then + echo "[entrypoint] 错误: ttyd 启动失败" + exit 1 +fi +echo "[entrypoint] ttyd 已启动 (PID: $TTYD_PID)" + +if ! pidof sshd > /dev/null; then + echo "[entrypoint] 错误: sshd 启动失败" + exit 1 +fi + +echo "" +echo "========================================" +echo " WorkPod Test 开发环境已就绪" +echo "========================================" +echo "Node: $(node --version)" +echo "npm: $(npm --version)" +echo "" +echo "AI 工具:" +echo " Claude Code: $(claude --version 2>/dev/null || echo '未安装')" +echo "" +echo "开发工具:" +echo " Rust: $(rustc --version 2>/dev/null || echo '未安装')" +echo " Go: $(go version 2>/dev/null || echo '未安装')" +echo " Python: $(python3 --version 2>/dev/null || echo '未安装')" +echo "" +echo "连接方式:" +echo " Web: http://localhost:7682" +echo " SSH: ssh root@localhost -p 2223 (密码: workpod123)" +echo "========================================" + +exec sleep infinity diff --git a/entrypoint.sh b/entrypoint.sh index 2a57786..ddfa56f 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,30 +1,108 @@ #!/bin/bash set -e -# 启动 SSH 服务 -service ssh start +SSHD_PID=0 +TTYD_PID=0 + +cleanup() { + echo "[entrypoint] 收到终止信号,正在关闭服务..." + [ "$TTYD_PID" -ne 0 ] && kill "$TTYD_PID" 2>/dev/null + [ "$SSHD_PID" -ne 0 ] && kill "$SSHD_PID" 2>/dev/null + wait 2>/dev/null + echo "[entrypoint] 服务已关闭" + exit 0 +} + +trap cleanup SIGTERM SIGINT SIGQUIT + +# 自动检测 /root 下的开发工具并设置 PATH +# Java (通过环境变量或挂载点) +[ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME/bin" ] && export PATH="${JAVA_HOME}/bin:$PATH" +[ -n "$MAVEN_HOME" ] && [ -d "$MAVEN_HOME/bin" ] && export PATH="${MAVEN_HOME}/bin:$PATH" +[ -d /root/.cargo/bin ] && export PATH="/root/.cargo/bin:$PATH" +[ -d /root/.rustup ] && export RUSTUP_HOME=/root/.rustup CARGO_HOME=/root/.cargo +[ -d /root/gvm/gos ] && export PATH="/root/gvm/gos/current/bin:$PATH" +[ -d /root/.pyenv/shims ] && export PATH="/root/.pyenv/shims:$PATH" +[ -d /root/.pyenv/bin ] && export PATH="/root/.pyenv/bin:$PATH" +[ -d /root/go/bin ] && export PATH="/root/go/bin:$PATH" && export GOROOT=/root/go +[ -d /root/.local/bin ] && export PATH="/root/.local/bin:$PATH" + +# 检测 Rust 独立安装(非 rustup) +RUST_DIR=$(ls -d /root/rust-*/bin 2>/dev/null | head -1) +[ -n "$RUST_DIR" ] && export PATH="$RUST_DIR:$PATH" + +# ===== Claude Code 自动认证配置 ===== +# 当 ANTHROPIC_API_KEY 环境变量存在时,自动写入凭证文件 +# 解决新容器首次启动 claude 走 OAuth 流程的问题 +_init_claude_auth() { + local auth_key="$ANTHROPIC_API_KEY" + local api_url="$ANTHROPIC_BASE_URL" + + if [ -n "$auth_key" ] || [ -n "$api_url" ]; then + mkdir -p ~/.claude + fi + + if [ -n "$auth_key" ]; then + # 用 printf 安全写入,避免 JSON 特殊字符注入 + printf '{"authToken":"%s"}' "$auth_key" > ~/.claude/.credentials.json + chmod 600 ~/.claude/.credentials.json + fi + if [ -n "$api_url" ]; then + printf '{"apiBaseUrl":"%s"}' "$api_url" > ~/.claude/settings.json + chmod 600 ~/.claude/settings.json + fi +} +_init_claude_auth + +# 写入 /etc/profile.d/ 让所有 shell 会话生效 + cat > /etc/profile.d/dev-tools.sh << 'PROFILE' +[ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME/bin" ] && export PATH="$JAVA_HOME/bin:$PATH" +[ -n "$MAVEN_HOME" ] && [ -d "$MAVEN_HOME/bin" ] && export PATH="$MAVEN_HOME/bin:$PATH" +[ -d /root/.cargo/bin ] && export PATH="/root/.cargo/bin:$PATH" && export RUSTUP_HOME=/root/.rustup CARGO_HOME=/root/.cargo +[ -d /root/gvm/gos ] && source /root/gvm/scripts/gvm 2>/dev/null +[ -d /root/.pyenv/shims ] && export PATH="/root/.pyenv/shims:/root/.pyenv/bin:$PATH" +[ -d /root/go/bin ] && export PATH="/root/go/bin:$PATH" && export GOROOT=/root/go +[ -d /root/.local/bin ] && export PATH="/root/.local/bin:$PATH" +RUST_DIR=$(ls -d /root/rust-*/bin 2>/dev/null | head -1) +[ -n "$RUST_DIR" ] && export PATH="$RUST_DIR:$PATH" +PROFILE + +/usr/sbin/sshd +echo "[entrypoint] SSH 已启动" + +ttyd -W -c "${TTYD_CREDENTIALS:-jc:1234567}" -t fontSize=16 -t theme='{"background":"#1a1a2e"}' /opt/ttyd-session.sh & +TTYD_PID=$! + +sleep 1 +if ! kill -0 "$TTYD_PID" 2>/dev/null; then + echo "[entrypoint] 错误: ttyd 启动失败" + exit 1 +fi +echo "[entrypoint] ttyd 已启动 (PID: $TTYD_PID)" + +if ! pidof sshd > /dev/null; then + echo "[entrypoint] 错误: sshd 启动失败" + exit 1 +fi echo "" echo "========================================" -echo " WorkPod 开发环境已就绪" +echo " WorkPod Alpine 开发环境已就绪" echo "========================================" -echo "Python: $(python --version 2>&1)" echo "Node: $(node --version)" -echo "Go: $(go version | awk '{print $3}')" -echo "Rust: $(rustc --version 2>/dev/null || echo '未安装')" +echo "npm: $(npm --version)" echo "" echo "AI 工具:" echo " Claude Code: $(claude --version 2>/dev/null || echo '未安装')" -echo " OpenClaw: $(openclaw --version 2>/dev/null || echo '未安装')" echo "" -echo "数据库服务:" -echo " MySQL: mysqld --user=mysql --datadir=/var/lib/mysql &" -echo " Redis: redis-server --daemonize yes" +echo "开发工具:" +echo " Rust: $(rustc --version 2>/dev/null || echo '未安装')" +echo " Go: $(go version 2>/dev/null || echo '未安装')" +echo " Python: $(python3 --version 2>/dev/null || echo '未安装')" echo "" echo "连接方式:" -echo " SSH: ssh root@localhost -p 2222 (密码: workpod123)" -echo " Exec: docker exec -it workpod bash" +echo " Web: http://localhost:7681" +echo " SSH: ssh root@localhost -p 2222" echo "========================================" -# 保持容器运行 exec sleep infinity diff --git a/instances/.template/docker-compose.yml b/instances/.template/docker-compose.yml new file mode 100644 index 0000000..76d0e61 --- /dev/null +++ b/instances/.template/docker-compose.yml @@ -0,0 +1,66 @@ +# WorkPod 实例模板 — 复制此文件到 instances/{name}/ 后修改 +# 修改项(搜索 { } 占位符): +# 1. {name} → 实例名(如 suke, case, hszd) +# 2. {SSH_PORT} → SSH 端口(按端口分配规则) +# 3. {WEB_PORT} → Web 端口 +# 4. {PROJECT_PATH}→ 项目路径(如 E:/wk-suke) +# 5. 取消注释需要的 extra_volumes 和 extra_env + +services: + ws-{name}-dev: + build: + context: ../../ + dockerfile: instances/{name}/Dockerfile + image: ws-{name}-dev:latest + container_name: ws-{name}-dev + hostname: ws-{name}-dev + restart: unless-stopped + + ports: + - "{SSH_PORT}:22" + - "{WEB_PORT}:7681" + + volumes: + - {PROJECT_PATH}:/workspace + # Java 项目取消注释: + # - D:/Java/jdk-musl-17:/opt/jdk-musl-17:ro + # - D:/Java/maven-mvnd-3.9.14:/opt/maven-mvnd-3.9.14:ro + # 持久化 home 目录(手动安装的工具不会因重建丢失): + - ../../data/home/{name}:/root + + environment: + - TZ=Asia/Shanghai + - TERM=xterm-256color + - ROOT_PASSWORD=${ROOT_PASSWORD:-workpod123} + - TTYD_CREDENTIALS=${TTYD_CREDENTIALS:-jc:1234567} + # - WORKPOD_PROJECT={name} + # - JAVA_HOME=/opt/jdk-musl-17 + # - MAVEN_HOME=/opt/maven-mvnd-3.9.14 + + deploy: + resources: + limits: + memory: 4G + reservations: + memory: 1G + + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + + healthcheck: + test: ["CMD-SHELL", "netstat -tlnp 2>/dev/null | grep -qE ':(22|7681)\\b' || exit 1"] + interval: 30s + timeout: 5s + start_period: 15s + retries: 3 + + working_dir: /workspace + tty: true + stdin_open: true + +networks: + default: + name: workpod-{name}-network diff --git a/instances/README.md b/instances/README.md new file mode 100644 index 0000000..88713f1 --- /dev/null +++ b/instances/README.md @@ -0,0 +1,88 @@ +# WorkPod 实例管理指南 + +## 快速概览 + +| 实例 | SSH | Web | 用途 | 状态 | +|------|-----|-----|------|------| +| workpod-alpine | 2222 | 7681 | 基础开发环境 | `registry.yaml` | +| ws-flux-dev | 2201 | 7701 | Flux 项目 | `registry.yaml` | +| workpod-test | 2223 | 7682 | 测试实例 | `registry.yaml` | + +详细注册表见 **[registry.yaml](./registry.yaml)** + +--- + +## 端口分配规则 + +### 本机 + +| 类型 | SSH 范围 | Web 范围 | 说明 | +|------|---------|---------|------| +| 基础/通用 | 2201-2210 | 7681-7690 | base, test 等通用实例 | +| **项目开发** | **2211-2290** | **7701-7790** | 各业务项目专用 | + +### 已分配 + +| 端口 | 实例 | 备注 | +|------|------|------| +| 2222 / 7681 | workpod-alpine | 基础实例 | +| 2223 / 7682 | workpod-test | 测试实例 | +| **2201 / 7701** | **ws-flux-dev** | **Flux 项目** | +| 2211 / 7711 | (预留) | suke | +| 2212 / 7712 | (预留) | case | +| 2213 / 7713 | (预留) | hszd | + +### 测试服 (flux_dev) + +复用本机规则 + 偏移,详见 registry.yaml → `remote` 段。 + +--- + +## 新建项目实例(3 步) + +```bash +# 1. 从模板创建 +mkdir -p instances/{name} +cp instances/.template/docker-compose.yml instances/{name}/ + +# 2. 编辑 compose 文件,修改以下占位符: +# {name} → 实例名 +# {SSH_PORT} → 按 2211+ 分配 +# {WEB_PORT} → 按 7711+ 分配 +# {PROJECT_PATH} → E:/wk-{name} + +# 3. 注册到 registry.yaml 并启动 +cd instances/{name} && docker compose up -d --build +``` + +如需自定义镜像(如加 JDK),在 `instances/{name}/` 下创建 Dockerfile: + +```dockerfile +FROM workpod-alpine:latest +# 额外的 LABEL、VOLUME 声明等 +ENTRYPOINT ["/entrypoint.sh"] +``` + +--- + +## 常用命令 + +```bash +# 查看所有实例状态 +docker ps --filter name=workpod --format "table {{.Names}}\t{{.Status}}\t{{.Ports}" + +# 启动指定实例 +cd instances/{name} && docker compose up -d + +# 停止指定实例 +cd instances/{name} && docker compose down + +# 重建(修改 Dockerfile/compose 后) +cd instances/{name} && docker compose up -d --build + +# 进入容器 +docker exec -it {container_name} bash + +# 查看日志 +docker logs -f {container_name} +``` diff --git a/instances/base/docker-compose.yml b/instances/base/docker-compose.yml new file mode 100644 index 0000000..2c60228 --- /dev/null +++ b/instances/base/docker-compose.yml @@ -0,0 +1,53 @@ +services: + workpod-alpine: + build: + context: ../../ + dockerfile: Dockerfile + image: workpod-alpine:latest + container_name: workpod-alpine + hostname: workpod-alpine + + privileged: true + + ports: + - "2222:22" + - "7681:7681" + + volumes: + - ../../data/workspace:/workspace + + environment: + - TZ=${TZ:-Asia/Shanghai} + - TERM=xterm-256color + - ROOT_PASSWORD=${ROOT_PASSWORD:-workpod123} + - TTYD_CREDENTIALS=${TTYD_CREDENTIALS:-jc:1234567} + + tty: true + stdin_open: true + + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + + deploy: + resources: + limits: + memory: 4G + reservations: + memory: 1G + + healthcheck: + test: ["CMD-SHELL", "netstat -tlnp 2>/dev/null | grep -qE ':(22|7681)\\b' || exit 1"] + interval: 30s + timeout: 5s + start_period: 10s + retries: 3 + + restart: unless-stopped + working_dir: /workspace + +networks: + default: + name: workpod-alpine-network diff --git a/instances/flux/Dockerfile b/instances/flux/Dockerfile new file mode 100644 index 0000000..ec9fe4f --- /dev/null +++ b/instances/flux/Dockerfile @@ -0,0 +1,10 @@ +# ws-flux-dev - Flux 金融线索管理 (Java 17 + Node 24) +FROM workpod-alpine:latest + +LABEL org.opencontainers.image.title="ws-flux-dev" \ + org.opencontainers.image.description="Flux 全栈开发环境 (Java 17 + Node 24)" \ + org.opencontainers.image.version="1.0.0" + +EXPOSE 22 7681 + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/instances/flux/docker-compose.yml b/instances/flux/docker-compose.yml new file mode 100644 index 0000000..74e03d2 --- /dev/null +++ b/instances/flux/docker-compose.yml @@ -0,0 +1,88 @@ +services: + ws-flux-dev: + build: + context: ../../ + dockerfile: instances/flux/Dockerfile + image: ws-flux-dev:latest + container_name: ws-flux-dev + hostname: ws-flux-dev + restart: unless-stopped + + ports: + - "2201:22" + - "7701:7681" + + volumes: + # ===== 项目源码 ===== + - E:/wk-flux:/workspace + + # ===== 开发工具 (JDK/Maven) ===== + - D:/Java/jdk-musl-17:/opt/jdk-musl-17:ro + - D:/Java/maven-mvnd-3.9.14:/opt/maven-mvnd-3.9.14:ro + + # ===== 代理工具 (Linux static binaries) ===== + - E:/wk-oth/rust-work/dist-Linux/mysql-proxy:/usr/local/bin/mysql-proxy:ro + - E:/wk-oth/rust-work/dist-Linux/ssh-proxy:/usr/local/bin/ssh-proxy:ro + - E:/wk-oth/rust-work/dist-Linux/redis-proxy:/usr/local/bin/redis-proxy:ro + - E:/wk-oth/rust-work/dist-Linux/mongo-proxy:/usr/local/bin/mongo-proxy:ro + + # ===== SSH 密钥 + 代理配置 (仅 Flux 相关) ===== + - C:/Users/23780/.ssh/id_ed25519:/root/.ssh/id_ed25519:ro + - ../../dev-envs/flux/ssh-proxy.toml:/root/.dev-env/ssh-proxy.toml:ro + - ../../dev-envs/flux/mysql-proxy.toml:/root/.dev-env/mysql-proxy.toml:ro + - ../../dev-envs/flux/redis-proxy.toml:/root/.dev-env/redis-proxy.toml:ro + + # ===== 项目级配置 (dev-envs) ===== + - ../../dev-envs/flux/servers.yaml:/root/.dev-env/servers.yaml:ro + - ../../dev-envs/flux/databases.yaml:/root/.dev-env/databases.yaml:ro + - ../../dev-envs/flux/norms.md:/root/.dev-env/norms.md:ro + - ../../dev-envs/flux/docs:/root/.dev-env/docs:ro + - ../../dev-envs/flux/claude/CLAUDE.md:/root/.dev-env/claude/CLAUDE.md:ro + - ../../dev-envs/flux/claude/settings.local.json:/root/.dev-env/claude/settings.local.json:ro + + # ===== 持久化 Home ===== + - ../../data/home/flux-dev:/root + + environment: + - TZ=Asia/Shanghai + - TERM=xterm-256color + - ROOT_PASSWORD=${ROOT_PASSWORD:-workpod123} + - TTYD_CREDENTIALS=${TTYD_CREDENTIALS:-jc:1234567} + - WORKPOD_PROJECT=flux + - JAVA_HOME=/opt/jdk-musl-17 + - MAVEN_HOME=/opt/maven-mvnd-3.9.14 + # === Claude Code (智谱 AI) === + - ANTHROPIC_BASE_URL=https://open.bigmodel.cn/api/anthropic + - ANTHROPIC_API_KEY=${ANTHROPIC_AUTH_TOKEN} + - ANTHROPIC_DEFAULT_SONNET_MODEL=glm-5v-turbo + - ANTHROPIC_DEFAULT_OPUS_MODEL=glm-5.1 + - ANTHROPIC_DEFAULT_HAIKU_MODEL=glm-4.5-air + + deploy: + resources: + limits: + memory: 4G + reservations: + memory: 1G + + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + + healthcheck: + test: ["CMD-SHELL", "netstat -tlnp 2>/dev/null | grep -qE ':(22|7681)\\b' || exit 1"] + interval: 30s + timeout: 5s + start_period: 15s + retries: 3 + + working_dir: /workspace + tty: true + stdin_open: true + +networks: + default: + external: true + name: workpod-flux-network diff --git a/instances/lab-x/Dockerfile b/instances/lab-x/Dockerfile new file mode 100644 index 0000000..6c72d0c --- /dev/null +++ b/instances/lab-x/Dockerfile @@ -0,0 +1,10 @@ +# ws-lab-x-dev - Lab 通用开发环境 +FROM workpod-alpine:latest + +LABEL org.opencontainers.image.title="ws-lab-x-dev" \ + org.opencontainers.image.description="Lab 通用开发环境" \ + org.opencontainers.image.version="1.0.0" + +EXPOSE 22 7681 + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/instances/lab-x/docker-compose.yml b/instances/lab-x/docker-compose.yml new file mode 100644 index 0000000..98b0f15 --- /dev/null +++ b/instances/lab-x/docker-compose.yml @@ -0,0 +1,61 @@ +services: + ws-lab-x-dev: + build: + context: ../../ + dockerfile: instances/lab-x/Dockerfile + image: ws-lab-x-dev:latest + container_name: ws-lab-x-dev + hostname: ws-lab-x-dev + restart: unless-stopped + + ports: + - "2202:22" + - "7702:7681" + + volumes: + # ===== 项目源码 ===== + - E:/wk-lab:/workspace + + # ===== 持久化 Home ===== + - ../../data/home/lab-x:/root + + environment: + - TZ=Asia/Shanghai + - TERM=xterm-256color + - ROOT_PASSWORD=${ROOT_PASSWORD:-workpod123} + - TTYD_CREDENTIALS=${TTYD_CREDENTIALS:-jc:1234567} + - WORKPOD_PROJECT=lab-x + # === Claude Code (智谱 AI) === + - ANTHROPIC_BASE_URL=https://open.bigmodel.cn/api/anthropic + - ANTHROPIC_API_KEY=${ANTHROPIC_AUTH_TOKEN} + - ANTHROPIC_DEFAULT_SONNET_MODEL=glm-5v-turbo + - ANTHROPIC_DEFAULT_OPUS_MODEL=glm-5.1 + - ANTHROPIC_DEFAULT_HAIKU_MODEL=glm-4.5-air + + deploy: + resources: + limits: + memory: 4G + reservations: + memory: 1G + + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + + healthcheck: + test: ["CMD-SHELL", "netstat -tlnp 2>/dev/null | grep -qE ':(22|7681)\\b' || exit 1"] + interval: 30s + timeout: 5s + start_period: 15s + retries: 3 + + working_dir: /workspace + tty: true + stdin_open: true + +networks: + default: + name: workpod-lab-x-network diff --git a/instances/test/docker-compose.yml b/instances/test/docker-compose.yml new file mode 100644 index 0000000..836e33c --- /dev/null +++ b/instances/test/docker-compose.yml @@ -0,0 +1,46 @@ +services: + workpod-test: + image: ae5a15cc0c94 + container_name: workpod-test + hostname: workpod-test + restart: unless-stopped + + ports: + - "2223:22" + - "7682:7681" + + volumes: + - ../../data/workspace-test:/workspace + - ../../data/home/test:/root + + environment: + - TZ=Asia/Shanghai + - TERM=xterm-256color + + deploy: + resources: + limits: + memory: 4G + reservations: + memory: 1G + + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + + healthcheck: + test: ["CMD-SHELL", "netstat -tlnp 2>/dev/null | grep -qE ':(22|7681)\\b' || exit 1"] + interval: 30s + timeout: 5s + start_period: 10s + retries: 3 + + working_dir: /workspace + tty: true + stdin_open: true + +networks: + default: + name: workpod-test-network diff --git a/migrate-docker.ps1 b/migrate-docker.ps1 deleted file mode 100644 index 21df160..0000000 --- a/migrate-docker.ps1 +++ /dev/null @@ -1,51 +0,0 @@ -# Docker Desktop 数据迁移到 D 盘脚本 -# 以管理员身份运行 PowerShell - -Write-Host "=== Docker Desktop 数据迁移脚本 ===" -ForegroundColor Cyan -Write-Host "" - -# 1. 停止 Docker Desktop -Write-Host "[1/6] 停止 Docker Desktop..." -ForegroundColor Yellow -Stop-Process -Name "Docker Desktop" -Force -ErrorAction SilentlyContinue -Start-Sleep -Seconds 5 - -# 2. 停止 WSL -Write-Host "[2/6] 停止 WSL..." -ForegroundColor Yellow -wsl --shutdown -Start-Sleep -Seconds 3 - -# 3. 创建目标目录 -Write-Host "[3/6] 创建目标目录..." -ForegroundColor Yellow -$targetDir = "D:\DockerData" -if (-not (Test-Path $targetDir)) { - New-Item -ItemType Directory -Path $targetDir -Force | Out-Null -} - -# 4. 导出 docker-desktop-data -Write-Host "[4/6] 导出 docker-desktop-data (这可能需要几分钟)..." -ForegroundColor Yellow -$exportPath = "$targetDir\docker-desktop-data.tar" -wsl --export docker-desktop-data $exportPath - -if (Test-Path $exportPath) { - Write-Host " 导出成功: $exportPath" -ForegroundColor Green - - # 5. 注销原有的 - Write-Host "[5/6] 注销原有发行版..." -ForegroundColor Yellow - wsl --unregister docker-desktop-data - - # 6. 导入到新位置 - Write-Host "[6/6] 导入到新位置..." -ForegroundColor Yellow - wsl --import docker-desktop-data "$targetDir\wsl" $exportPath --version 2 - - # 删除临时文件 - Remove-Item $exportPath -Force - Write-Host "" - Write-Host "=== 迁移完成 ===" -ForegroundColor Green - Write-Host "Docker 数据已迁移到: $targetDir" -ForegroundColor Cyan -} else { - Write-Host " 导出失败,请检查 docker-desktop-data 是否存在" -ForegroundColor Red - Write-Host " 运行 'wsl --list -v' 查看所有发行版" -ForegroundColor Yellow -} - -Write-Host "" -Write-Host "请手动启动 Docker Desktop 完成迁移" -ForegroundColor Yellow diff --git a/nginx-map-patch.sh b/nginx-map-patch.sh new file mode 100644 index 0000000..91ac53d --- /dev/null +++ b/nginx-map-patch.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# 在 nginx.conf http 块开头添加 map 定义 +sed -i '/^http {/a\\n map $http_upgrade $connection_upgrade {\n default upgrade;\n '\'''\'' close;\n }' /etc/nginx/nginx.conf +nginx -t diff --git a/static/login.html b/static/login.html new file mode 100644 index 0000000..590bd32 --- /dev/null +++ b/static/login.html @@ -0,0 +1,191 @@ + + + + + + WorkPod + + + + +
+ +
+
+ + +
+ +
密码错误
+
+
+ + + + + + + diff --git a/ttyd-session.sh b/ttyd-session.sh new file mode 100644 index 0000000..1d26829 --- /dev/null +++ b/ttyd-session.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# ttyd 会话入口:根据 URL 参数或用户选择分配 tmux session + +SESSION_NAME="" + +# ttyd 会将 query string 存入 TTYD_QUERY_STRING 环境变量 +if [ -n "$TTYD_QUERY_STRING" ]; then + # 解析 session 参数 + SESSION_NAME=$(echo "$TTYD_QUERY_STRING" | tr '&' '\n' | grep '^session=' | head -1 | cut -d= -f2) +fi + +if [ -z "$SESSION_NAME" ]; then + # 没有指定 session,显示选择菜单 + echo "" + echo "╔══════════════════════════════════════╗" + echo "║ 选择或创建 tmux session ║" + echo "╠══════════════════════════════════════╣" + + # 列出已有 session + EXISTING=$(tmux list-sessions 2>/dev/null | awk '{print $1}') + if [ -n "$EXISTING" ]; then + echo "║ 已有 session: ║" + I=1 + for s in $EXISTING; do + printf "║ [%d] %-33s║\n" "$I" "$s" + I=$((I + 1)) + done + else + echo "║ (无已有 session) ║" + fi + + echo "╠══════════════════════════════════════╣" + echo "║ 输入编号选择,或输入新名称创建 ║" + echo "╚══════════════════════════════════════╝" + echo "" + + read -p "session: " CHOICE + + if [ -z "$CHOICE" ]; then + CHOICE="default" + fi + + # 判断是数字编号还是名称 + if echo "$CHOICE" | grep -qE '^[0-9]+$' && [ -n "$EXISTING" ]; then + SESSION_NAME=$(echo "$EXISTING" | sed -n "${CHOICE}p") + else + SESSION_NAME="$CHOICE" + fi + + # 清屏 + clear +fi + +# session 名只保留字母数字下划线连字符 +SESSION_NAME=$(echo "$SESSION_NAME" | tr -cd 'a-zA-Z0-9_\-') + +# 如果 session 已存在,共享 attach;否则新建 +if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then + exec tmux attach-session -t "$SESSION_NAME" +else + exec tmux new-session -s "$SESSION_NAME" +fi diff --git a/wk.1216.conf b/wk.1216.conf new file mode 100644 index 0000000..71da45c --- /dev/null +++ b/wk.1216.conf @@ -0,0 +1,28 @@ +# WorkPod Web 终端 - wk.1216.top + +server { + listen 80; + server_name wk.1216.top; + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl; + server_name wk.1216.top; + + ssl_certificate /etc/nginx/sslkey/_.1216.top.pem; + ssl_certificate_key /etc/nginx/sslkey/_.1216.top.key; + + location / { + proxy_pass http://127.0.0.1:7683; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_read_timeout 86400; + proxy_buffering off; + } +}