Files
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

8.5 KiB
Raw Permalink Blame History

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 嵌入
配置 TOMLconfig.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。

# 一次性:构建前端 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 测试/静态检查可用宿主机 gomake test / make vet,仅本地开发验证用;正式产物以容器构建为准)。

  • 前端构建链web/distgitignore)→ internal/webui/distgit 跟踪,必须随改动提交,否则 index.html 引用的新 hash 文件不入库)。

  • 重建前清空 internal/webui/distmake 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):

    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-argHTTP_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 均 gitignoreinternal/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.cssCSS 变量覆盖 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