前端: - src/srv/api → src/api,按域分文件夹(auth/cluster/host/storage/deployment/collector) - 新增 http.ts 引擎(http.get/post/put/delete),域模块改用语义化调用 - index.html 增加 favicon(匹配应用 logo 的 emerald + Activity 脉冲) 后端分层增强(保留包名): - 新增 repository/:从 models 拆出 JsonDB 持久化层(InitDB/GetDB/Shutdown/RequestSave) - 新增 dto/:从 controllers 抽出各域请求契约(带 binding 校验) - 新增 pkg/crypto、pkg/jwt:从 utils 拆出安全原语(外部 jwt 别名 jwtv5 避免冲突) - models 只留领域实体 + sanitizer;utils 只留 gin/应用相关(response/errors) v2 优化移植与缺陷修复: - 移植密钥分离/SSH TOFU/并发写安全/Loki 生产级配置/Docker 加固 - 修复 deployment.go 并发丢更新、Helm values 重复键、EncryptField 哨兵、loki-gateway 端点等审计缺陷 - 实现真正的优雅关停(main.go http.Server + signal + repository.Shutdown) - 修复登录契约:后端返回 accessToken,前端 auth.ts/Login.tsx 对齐 - 补 crypto/jwt/repository 单元测试(含并发写回归) 验证: go build/vet/test 全绿;npm run build 通过;登录→鉴权→优雅关停冒烟通过 Co-Authored-By: Claude <noreply@anthropic.com>
127 lines
7.3 KiB
Markdown
127 lines
7.3 KiB
Markdown
# AGENTS.md
|
|
|
|
本文件为各类 AI 编码代理(Cursor、Aider、Claude Code、Copilot 等)在本仓库中工作时的通用指引。
|
|
内容与 `CLAUDE.md` 保持同步;两份文件描述的是同一套约定,修改其一请同步另一份。
|
|
|
|
## 项目概览
|
|
|
|
LokiDeploy 是一个基于 **Gin (Go) + React/TypeScript + JSON 文件存储** 的平台,用于把 Grafana
|
|
Loki 日志聚合系统部署到 Kubernetes 集群(通过 `kubectl`/`helm`)或裸金属 Linux 主机(通过 SSH)。
|
|
Go 二进制同时提供 REST API 服务和已构建的前端(SPA)静态资源。
|
|
|
|
默认登录账号: `admin` / `admin123`(首次启动时由 `backend/seed/seed.go` 写入数据库)。
|
|
|
|
## 常用命令
|
|
|
|
### 启动后端前的必备环境变量
|
|
后端在缺少以下变量时会 **拒绝启动**(由 `backend/config/config.go` 通过 `log.Fatal` 强制)。
|
|
`./run.sh` 会自动注入开发用默认值;直接 `go run` 或 Docker 则不会。
|
|
|
|
```bash
|
|
export JWT_SECRET=$(openssl rand -hex 32) # >= 32 字符
|
|
export ENCRYPTION_KEY=$(openssl rand -hex 32) # >= 32 字节(不是 16,详见「安全」一节)
|
|
```
|
|
|
|
### 开发模式(两个终端,热重载)
|
|
```bash
|
|
# 终端 1 — 后端(端口 8080)
|
|
cd backend && go run .
|
|
|
|
# 终端 2 — 前端(端口 3000,会把 /api 代理到 :8080,见 vite.config.ts)
|
|
cd frontend && npm install && npm run dev
|
|
```
|
|
也可以用 `./run.sh`,它会同时拉起前后端并注入开发用默认密钥。
|
|
|
|
### 构建单体二进制(前端 + 后端)
|
|
```bash
|
|
bash build.sh # 产物:backend/bin/lokideploy,并把 frontend/dist 复制到 backend/dist
|
|
cd backend && go build -o bin/lokideploy . # 仅构建后端
|
|
cd frontend && npm run build # 仅构建前端(tsc -b && vite build)
|
|
```
|
|
|
|
### 测试与 lint
|
|
```bash
|
|
cd backend && go test ./... # 所有 Go 测试(utils、repository)
|
|
cd backend && go test ./utils/ -run TestName -v # 运行单个测试
|
|
cd frontend && npm run lint # ESLint
|
|
```
|
|
注意:`models/` 目录目前 **没有** 测试,测试集中在 `utils/` 和 `repository/`。
|
|
|
|
### Docker
|
|
```bash
|
|
export JWT_SECRET=... ENCRYPTION_KEY=...
|
|
docker compose up -d # 开发用 compose(docker-compose.yml)
|
|
make build start stop logs status clean # Makefile 快捷命令
|
|
docker compose -f docker-compose.prod.yml up -d # 生产环境(读取 ./secrets/*.txt;先运行 `make secrets`)
|
|
```
|
|
|
|
## 架构
|
|
|
|
### 后端分层(`backend/`,模块路径 `lokideploy`,Go 1.24)
|
|
请求流转:**router → middleware(JWT)→ controller → service → repository(JsonDB)**。
|
|
|
|
- `config/` — `GetConfig()` 单例。读取环境变量;密钥缺失或长度不足时直接 `log.Fatal`。
|
|
- `routers/router.go` — 所有路由注册在 `/api/v1` 下;注册 SPA 静态资源服务,并用 `NoRoute`
|
|
回退到 `index.html` 以支持前端路由。`findDistPath()` 负责相对于可执行文件定位 `dist/`
|
|
(兼容 `go run` 的临时目录和打包后的二进制两种情况)。
|
|
- `controllers/` — 薄薄的 Gin handler,每个资源一个文件。职责:绑定/校验入参、调用 service、
|
|
用 `models.SanitizedJSON(...)` 包装返回结果。
|
|
- `services/` — 业务逻辑层。部署流水线就在这里:
|
|
`DeploymentService.Deploy()` → `LokiDeployer.Deploy()`,按部署模式
|
|
(`k8s-helm` / `k8s-yaml` / `host-binary`)分发,实际通过 shell 调用 `kubectl`/`helm` 或 SSH。
|
|
其它 service:`K8sClient`、`ssh`(+ `ssh_errors`)、`storage`、`cluster_info`,
|
|
以及较新的 **collector(日志采集器)** 模块(`collector*.go` — 部署客户端日志采集器)。
|
|
- `middleware/jwt.go` — `JWTAuth()`(`/authorized` 路由组必用)、`AdminAuth()`。
|
|
- `utils/` — `response.go`(响应封装助手)、`crypto.go`(Argon2 + AES-GCM)、`jwt.go`。
|
|
- `seed/` — 数据库为空时写入默认 admin 用户。
|
|
|
|
### 数据层 — JSON 文件数据库(唯一数据源)
|
|
持久化是 **单个 JSON 文件**,位于 `$DATA_DIR/lokideploy.json`(默认 `./data/`)。
|
|
**没有 SQL 数据库**。`repository.JsonDB` 是一个与文件对应的内存结构:
|
|
|
|
- **并发模型**(重要):写操作由 `Lock()/RLock()` 保护,随后 `RequestSave()` 会通过一个
|
|
单一的、串行化的 `saveWorker` goroutine 调度一次 **防抖(500ms)原子写**
|
|
(写临时文件 → `fsync` → rename)。worker 可能仍在运行时 **绝对不要** 直接调用 `Save()`;
|
|
常规写入请用 `RequestSave()`。
|
|
- **优雅关停的顺序很关键**(`main.go` + `repository.Shutdown`):停防抖定时器 → 关闭 worker 并
|
|
等待退出 → 最后一次 `doSave`。顺序颠倒会导致更新丢失。
|
|
- 这意味着应用 **只能单实例运行** — 不可以水平扩展。
|
|
- `NextID(table)` 为每个集合分配单调递增的整数 ID。
|
|
|
|
> **存在两份 `JsonDB` 实现。** 真正在用的是 `repository/db.go`,被 `main.go`、`seed` 以及所有
|
|
> service 通过 `repository.GetDB()` 调用。`models/db.go` 是一份 **正在迁移、尚未清理的旧副本**
|
|
> — 它仍然会被编译(属于 `package models`),但已经没有任何代码调用
|
|
> `models.GetDB/InitDB/Shutdown`。新增数据库代码请写到 `repository`,不要写到 `models`。
|
|
> 在旧副本被删除前,修改其中一份时要保证另一份也能通过编译。
|
|
|
|
### 响应封装与脱敏
|
|
- 所有 API 响应都是 `{ code, message, data }`。`code == 0` 表示成功;前端
|
|
(`frontend/src/api/http.ts`)在 `code !== 0` 时抛错。`utils.Success/Error/...` 负责构造。
|
|
- **永远不要直接返回原始 model。** 必须用 `models.SanitizedJSON(...)` 包装。各 model 实现
|
|
`Sanitized()` 方法(`models/sanitizer.go`),用于剔除敏感字段(密码、kubeconfig、私钥、token)。
|
|
新增一个 model = 新增一个 `Sanitized()` 方法 + 在 `SanitizedJSON` 中补一个 case(单对象 + 切片各一个)。
|
|
|
|
### 凭证安全(`utils/crypto.go`)
|
|
- 密码用 **Argon2** 哈希。
|
|
- 凭证(kubeconfig、SSH 密码/密钥、存储密钥)用 **AES-GCM** 加密;AES 密钥由 `ENCRYPTION_KEY`
|
|
经 **HKDF 派生** 得到(原始密钥永远不会被直接使用)。
|
|
- `ENCRYPTION_KEY` 必须 ≥ 32 字节。(较早的文档/脚本写的是 16 — 现在这样会导致启动失败。)
|
|
|
|
## 前端(`frontend/`,React 19 + Vite 7 + TypeScript)
|
|
|
|
- 技术栈:Tailwind、**shadcn/ui**(Radix 基础组件在 `src/components/ui/`)、react-router 7、
|
|
react-hook-form + zod、recharts、sonner(通知)。
|
|
- `src/api/` 是 **当前在用** 的 API 客户端(取代了已删除的 `src/srv/api/`)。`http.ts` 是
|
|
fetch 封装:注入 `Authorization: Bearer`、30s 超时、401 → 清除 token 并跳转 `/login`、可选重试。
|
|
每个资源一个模块(`auth`、`cluster` ……)。
|
|
- `src/App.tsx` — 路由通过 `React.lazy` 懒加载,`RequireAuth` 守卫在 localStorage 无 `token`
|
|
时重定向到 `/login`。`@` → `src/` 别名(tsconfig + vite)。
|
|
- 开发服务器跑在 `:3000`,把 `/api` 代理到 `:8080`;生产环境下由 Go 二进制直接提供 `dist/` 静态服务。
|
|
|
|
## Loki 部署相关细节
|
|
- 合法的部署模式:`k8s-helm`、`k8s-yaml`、`host-binary`。K8s 模式需要一个 Cluster(kubeconfig);
|
|
`host-binary` 需要一个 SSH Host,会安装 Loki + systemd。
|
|
- Loki 版本会对照 `services.LokiVersionList` 校验(最低支持主版本:2) —
|
|
见 `services/deployment.go` 中的 `ValidateLokiVersion`。
|
|
- `kubectl`/`helm` 在 Docker 镜像中安装时做了 SHA256 校验(见 `Dockerfile`)。
|