Files
usernode/AGENTS.md
T
cao.wangrenbo 24430f1867 chore: internal/webui/dist 改为不追踪(构建产物),新增 AGENTS.md
- .gitignore 增加 internal/webui/dist/,构建产物不再纳入版本控制
  (与 web/dist 一致;go:embed 仍依赖磁盘产物,不依赖 git 跟踪)
- 从索引移除已跟踪的 internal/webui/dist(历史提交仍保留旧产物)
- 新增 AGENTS.md:项目目的、架构、容器构建流程(前端 node:20-alpine
  与 Go golang:1.22-alpine 均走 podman)、环境注意事项与编码约定
2026-08-30 17:30:16 +08:00

111 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AGENTS.md — ws_usernode 项目指南
本文件供开发/编码 agent 快速上手:项目目的、架构、构建流程、环境注意事项与编码约定。
详细设计见 `docs/PLAN.md`,接口见 `docs/API.md`,部署见 `docs/DEPLOY.md`
## 项目目的
**服务器用户管理节点**:对服务器上的**外部用户**(非本机管理员)做集中化管理,交付形态为**单个 Go 二进制**(前端产物 `go:embed` 嵌入,单文件分发)。核心能力:
- 系统账号生命周期:创建、禁用、删除、过期与回收;统一 `ext_` 前缀、`external` 组、口令锁定(仅 SSH 密钥登录)
- SSH 公钥自助管理:用户上传/重命名/吊销,实时同步 `authorized_keys`
- 新账号申请 + 管理员审批流(通过后自动建号并邮件通知)
- SMTP 邮件通知:OTP 登录验证码、审批结果、到期提醒、密码重置
- 管理操作审计(append-only,保留 30 天 + 手动/每日定时归档)
部署形态:单机节点 + RESTful API v1;系统账号操作经 sudoers 白名单命令,为后期"控制面 + 多节点 agent"预留。
## 技术栈与目录
| 层 | 技术 |
|---|---|
| 后端 | Go ≥ 1.22 + Gin + GORMSQLite 开发 / MySQL 生产)+ log/slog |
| 前端 | Vue 3 + Vite + TypeScript + Element Plus + Pinia + Vue Router + Axios |
| 构建 | podman 容器构建前端(node:20-alpine),Go 二进制 go:embed 嵌入 |
| 配置 | TOML`config.example.toml`),环境变量覆盖 `USERNODE_<SECTION>_<FIELD>` |
```
cmd/usernode/ CLIserve / migrate / admin / user / help
internal/api/ HTTP handler + 审计 helper
internal/service/ 业务逻辑(user/approval/audit/key/auth/settings/lifecycle
internal/model/ GORM 模型(User/Approval/AuditLog/MailLog/Session/Setting
internal/pkg/ 校验等公共工具(validate.go:用户名/邮箱规则)
internal/auth/ OTP、验证码、会话、密码重置
internal/system/ 系统账号操作层(useradd/usermod/...sudoers 白名单)
internal/webui/ go:embed 前端产物(dist/ 被 git 跟踪)
internal/router/ 路由装配
web/ 前端源码(Vue3)
docs/ PLAN.md / API.md / DEPLOY.md
deploy/ Containerfile(多阶段镜像)、sudoers、systemd 服务
```
## 开发构建流程
**一切构建操作必须在 podman 容器内进行**,不要尝试在宿主机 install / build。
```bash
# 一次性:构建前端 dev 镜像 + 初始化 node_modules volume
make web-install
# 前端热开发(容器挂载源码,5173 端口,API 代理到 Go 后端 8080
make web-dev # 依赖宿主机 Go 后端已启动(make run)
# 标准构建(每次改前端后走这套):
make web-build # 容器内 npm run build → web/dist → 复制到 internal/webui/dist
make build-embed # 重新编译含前端产物的 Go 二进制 → bin/usernode
# 后端
make build # 仅编译 Go(不含前端)
make run # 本地运行(默认 config.toml
make test # go test ./...
make vet
make migrate # 数据库迁移
make image # 多阶段容器镜像(deploy/Containerfile,内含 golang:1.22-alpine 构建阶段)
make clean # 清 bin/ 和 web/dist
```
### 关键细节
- **Go 容器构建方式**:项目标准做法是 `deploy/Containerfile` 多阶段构建(前端 node:20-alpine → Go golang:1.22-alpine → 运行镜像),`make image` 一次产出含前端与后端的镜像。本地开发若要单独产 Go 二进制,应**用与容器一致的 golang:1.22-alpine 环境**`podman run --rm -v "$PWD":/src -w /src golang:1.22-alpine sh -c "CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o /out/usernode ./cmd/usernode"`),避免宿主机 go 1.26.7 版本漂移。
- **`make build-embed` 直接用宿主机 go 编译**(当前 1.26.7),与项目基线(go.mod `go 1.22`)不一致;仅在确认宿主机 go 版本与目标一致时使用,否则改走容器构建。
- **Go 测试/静态检查可用宿主机 go**(`make test` / `make vet`,仅本地开发验证用;正式产物以容器构建为准)。
- **前端构建链**`web/dist`gitignore)→ `internal/webui/dist`(**git 跟踪,必须随改动提交**,否则 index.html 引用的新 hash 文件不入库)。
- **重建前清空 `internal/webui/dist`**`make web-build` 内是 `cp -r` 合并复制,不清旧 hash 文件会导致 `go:embed` 把新旧产物一起打进二进制。标准做法:`rm -rf internal/webui/dist && mkdir -p internal/webui/dist && make web-build`
- **新增 npm 依赖**(在容器内安装,同步宿主机 package.json / package-lock.json):
```bash
podman run --rm --network=host \
-v "$PWD/web:/app" -v ws_usernode_node_modules:/app/node_modules \
ws-usernode-web-dev sh -c "npm install <pkg> --save"
```
- **前端类型检查**(容器内):`podman run --rm --network=host -v "$PWD/web:/app" -v ws_usernode_node_modules:/app/node_modules ws-usernode-web-dev sh -c "npx vue-tsc --noEmit"`。
- **代理环境**:容器构建无法继承宿主机 proxychains。Makefile 经 `--build-arg` 传 `HTTP_PROXY/HTTPS_PROXY/ALL_PROXY/NO_PROXY`。若宿主机 LD_PRELOAD 了 proxychainspodman 拉镜像会失败,需:`env -u LD_PRELOAD -u PROXYCHAINS_CONF_FILE make web-build`。
- **git 跟踪约定**`bin/`、`web/dist/`、`web/node_modules/`、`data/`、`config.toml` 均 gitignore`internal/webui/dist/` 例外(跟踪)。
## 环境注意事项(本开发机)
- 环境可能存在代理(proxychains node 等),本地回环可能会被拒绝,尝试绕过。
- 不要在本机装 Node 工具链,一律走 podman。
- 正式 Go 产物请走 `golang:1.22-alpine` 容器(见"Go 容器构建方式"),测试/静态检查可用宿主机 go。
- podman 有镜像 `localhost/ws-usernode-web-dev` 与 volume `ws_usernode_node_modules`(缓存依赖,勿删)。
- 无法访问本地浏览器后端(browser-use 不可用),视觉验证交给用户。
## 编码约定与易错点
- **外部用户名校验**`internal/pkg/validate.go`):申请/创建时传**不含前缀**的用户名,须匹配 `^[a-z][a-z0-9]{1,31}$`(小写开头、2~32 位);最终系统账号 = `user_prefix` + 用户名,总长 ≤ 32。
- **申请页(ApplyView)已改为中文姓名**:前端校验 2~10 个汉字,用 `pinyin-pro``{ toneType:'none', type:'array', surname:'head' }`,surname 处理姓氏多音字如 曾→zeng、单→shan)转拼音作为用户名提交;alert 实时预览 `ext_<username>`。改这里时保持"姓名→拼音→提交"链路与 `surname:'head'` 选项。
- **同名处理**:提交申请时后端 `usernameAvailable` 拦截"用户表已存在 + 同名待审批",返回 `ErrUsernameTaken` → HTTP 409;审批通过时复查用户表(防审批期间被手动创建)。前端 axios 拦截器统一弹出后端 error 文案,失败也进审计。
- **审计 append-only**:业务代码只允许 `AuditService.Record` 写入(禁 Update/Delete);`Result` ∈ success/failed。`AuditFilter.Result` 支持按结果筛选(管理员"错误日志"= 审计页选"失败"),导出 CSV 仅带时间范围(有意为之,勿随意改)。
- **审计清理**`audit.archive_dir` 留空 = 不归档也不清理(防丢审计);生产配置目录后超期审计先归档 CSV 再删除。
- **系统账号操作**`system.dry_run=true` 时只打印命令;生产 `sudo=true` 走 sudoers 白名单(deploy/sudoers),节点以专有用户运行。
- **邮件**:SMTP 留空则禁用邮件;OTP 双通道(邮件 + `usernode user otp --username ext_xxx`)同验证码、同 10 分钟有效期、同 60s 冷却。
- **前端主题**:主题定制集中在 `web/src/styles/theme.css`CSS 变量覆盖 Element Plus+ `web/src/stores/theme.ts`(暗色切换,localStorage key `ws-usernode-theme`);`index.html` 有防暗色闪烁内联脚本。改主题优先改变量而非逐页样式。
- **测试**:后端 `go test ./...`;前端无测试框架,靠 vue-tsc + 构建。
## 其他补充
- 版本号在 Makefile `VERSION`(当前 `0.3.0-m5`),编译期 `-X main.version` 注入。
- 新模型必须注册进 `internal/model` 的模型列表以触发 AutoMigrate。
- git 暂存时仅暂存已知改动,不要直接全部暂存。提交前必须和用户确认,即使在完全访问(YOLO)的模式下也需要先询问用户再提交。
- 数据库迁移:开发可直接 `make migrate`;生产 MySQL 切换参考 `deploy/migrate-sqlite2mysql.sh`。