# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## 项目概览 LokiStack 是一个基于 **Gin (Go) + React/TypeScript + JSON 文件存储** 的平台,用于把 Grafana Loki 日志聚合系统部署到 Kubernetes 集群(通过 client-go 直连 Kubernetes API Server)或裸金属 Linux 主机(通过 SSH)。 Go 二进制同时提供 REST API 服务和已构建的前端(SPA)静态资源。 默认登录账号: `admin` / `admin123`(首次启动时由 `backend/seed/seed.go` 写入数据库)。 ## 常用命令 ### 启动密钥(JWT_SECRET / ENCRYPTION_KEY) 显式设置优先;**未设置时自动生成随机密钥并持久化**到 `$DATA_DIR/.secrets.env`(0600,重启不变),无法持久化才拒绝启动(`backend/config/config.go` resolveSecrets)。`./run.sh` 仍会注入开发默认值。 ```bash export JWT_SECRET=$(openssl rand -hex 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/lokistack,并把 frontend/dist 复制到 backend/dist cd backend && go build -o bin/lokistack . # 仅构建后端 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/`,模块路径 `lokistack`,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-yaml` / `host-binary` / `host-docker`)分发,Kubernetes 操作由 client-go 完成,主机操作通过 SSH。 其它 service:`K8sClient`、`ssh`(+ `ssh_errors`)、`storage`、`cluster_info`, 以及较新的 **collector(日志采集器)** 模块(`collector*.go` — 部署客户端日志采集器)。 - `middleware/jwt.go` — `JWTAuth()`(解析后对照 JsonDB 校验 `TokenVersion`,并以数据库最新 role 为准)、`AdminAuth()`;`ratelimit.go` — 登录接口 IP 滑动窗口限流(20 次/分钟)。 - 用户管理(简单两级,无 RBAC):`GET/POST /users`、`DELETE /users/:id`、 `POST /users/:id/reset-password`,全部仅 admin。改密/重置密码会递增用户 `TokenVersion`, 旧 access+refresh token 立即失效(会话吊销)。 - `utils/` — `response.go`(响应封装助手)、`crypto.go`(Argon2 + AES-GCM)、`jwt.go`。 - `seed/` — 数据库为空时写入默认 admin 用户。 ### 数据层 — JSON 文件数据库(唯一数据源) 持久化是 **单个 JSON 文件**,位于 `$DATA_DIR/lokistack.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) > 前端开发标准:`docs/web-ui-guide/`(9 章,含分章 AI 检查清单)。编码、Review、重构 > 时以该标准为基准;与标准冲突的既有代码即为待改进项。 - 技术栈:Tailwind、**shadcn/ui**(Radix 基础组件在 `src/components/ui/`,只保留实际 使用的,需要新组件 `npx shadcn add `)、react-router 7、react-hook-form + zod、 sonner(通知)、next-themes(暗色)。 - **页面按域组织**(`src/pages//`):`index.tsx` 编排(≤400 行)+ `components/`(表单弹窗/卡片/详情)+ `hooks/`(私有数据逻辑)。跨页复用第三次才晋升 到 `src/components/`。 - `src/api/` — API 客户端,一域一模块。`http.ts` 提供:结构化 `ApiError`(status/code)、 30s 超时、`silent` 请求选项、**401 单飞刷新 + 队列重放**(refreshToken 走裸 fetch, 登录/轮询接口静默)、`buildQuery` 编码 query。组件**禁止**绕过 api 层直接 fetch。 - 凭证存取统一走 `src/lib/auth-storage.ts`,key 带命名空间(`lokistack.auth.*`); redirect 回跳校验在 `src/lib/redirect.ts`(防开放重定向)。 - 路由单源:`src/router/routes.tsx`(meta: title/icon/order)驱动菜单、顶栏标题与 `document.title`;守卫 `RequireAuth` 带来源路径回跳;404 走 `pages/not-found`。 - 部署向导 `/wizard`(preflight 状态推导 + Grafana 同环境部署 + 统一 log 命名空间)。 - LDAP 接入(`/settings` 配置+`/users` 批量导入+LDAP 登录)与日志查询(`/logs`,数据源自动注册+kube proxy)。 - 用户管理页(`/users`,meta `adminOnly: true` — 菜单按角色过滤,后端 AdminAuth 兜底); 部署表单的 Loki 版本下拉来自 `GET /deployments/versions`(失败回退兜底列表)。 - 列表数据一律 `useListQuery`(AbortController 取消 + 版本号防错序 + 分页);反馈分级: 成功静默(禁 toast.success)、失败 toast.error、危险操作 ConfirmDialog、加载失败渲染 ErrorState + 重试(与空态严格区分)。 - 列表数据一律 `useListQuery`(AbortController 取消 + 版本号防错序 + 分页);反馈分级: 成功静默(禁 toast.success)、失败 toast.error、危险操作 ConfirmDialog、加载失败渲染 ErrorState + 重试(与空态严格区分)。 - 主题:语义 Token 单源(`src/index.css`,品牌色 = `--primary`,状态色 = success/warning/destructive/info);暗色 `.dark` class 由 next-themes 驱动 (key `lokistack.theme.mode`),禁止硬编码调色板类(如 `bg-emerald-600`)。 - 开发服务器跑在 `:3000`(strictPort),把 `/api` 代理到 `:8080`;生产环境下由 Go 二进制直接提供 `dist/` 静态服务。构建指纹(`__APP_VERSION__`/`__COMMIT_HASH__`) 在 Settings「关于」展示。 ## Loki 部署相关细节 - 合法的部署模式:`k8s-yaml`、`host-binary`、`host-docker`。Kubernetes 模式需要一个 Cluster(kubeconfig); `host-binary` 需要一个 SSH Host,会安装 Loki + systemd。 - Loki 版本会对照 `services.LokiVersionList` 校验(最低支持主版本:2) — 见 `services/deployment.go` 中的 `ValidateLokiVersion`。 - Docker 镜像无需安装 `kubectl` 或 `helm`。