- .gitignore 增加 internal/webui/dist/,构建产物不再纳入版本控制 (与 web/dist 一致;go:embed 仍依赖磁盘产物,不依赖 git 跟踪) - 从索引移除已跟踪的 internal/webui/dist(历史提交仍保留旧产物) - 新增 AGENTS.md:项目目的、架构、容器构建流程(前端 node:20-alpine 与 Go golang:1.22-alpine 均走 podman)、环境注意事项与编码约定
111 lines
8.5 KiB
Markdown
111 lines
8.5 KiB
Markdown
# 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 + GORM(SQLite 开发 / 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/ CLI:serve / 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 了 proxychains,podman 拉镜像会失败,需:`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`。
|