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)、环境注意事项与编码约定
This commit is contained in:
2026-08-30 17:30:16 +08:00
parent 70c084e7e9
commit 24430f1867
31 changed files with 111 additions and 156 deletions
+110
View File
@@ -0,0 +1,110 @@
# 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`。