Commit Graph
18 Commits
Author SHA1 Message Date
opsx f3733f0bfc feat: 品牌统一 LokiStack + 全站中英 i18n + 用户批量删除 + 主题定时切换与浅色侧栏
- 品牌: Go 模块/数据文件/二进制/构建部署资产全部 opsx→lokistack;前端 localStorage 命名空间迁移(读旧写新,含按用户 id 前缀迁移);修复 nginx upstream 与 Makefile 引用已改名服务的断裂
- i18n: react-i18next 落地,common + 11 个页面域命名空间全站中英(菜单/顶栏/登录/列表页/表单/确认弹窗/状态徽章);设置页语言即时切换;修复词典自注册导致的循环导入白屏
- 用户管理: 批量删除(POST /users/batch-delete,不删自己/最后管理员,部分失败不阻断)+ 行复选/全选/批量工具条
- 主题: 浅色侧栏(中性灰激活态)、四态模式(浅色/深色/跟随系统/定时切换,按时区判定)、顶栏太阳月亮二态、设置页紧凑分段选择器
- UI: 登录记住用户名、CardActionButton 统一五页卡片操作钮、shadcn Badge 替换手写徽章、语义色修正
- 清理: 移除主机 Web 终端(前后端)、k8s-helm/Helm 遗留代码与字段、README 重写精简
- 修复: 侧栏与日志告警 order 重复、路由标题 i18n key 未翻译、词典双逗号等
2026-10-27 17:11:38 +08:00
opsx 02d1bd87a7 fix(promtail): 修 Sprintf 缺参导致清单非法 + Ingress 模板固定命名空间 + 资源名去 opsxloki
=== 一、致命 bug:Promtail 清单第 10 个 %s 缺参数(导致日志采集中断)===
go vet 报:"format %s reads arg #10, but call has 9 args"。
模板里 DaemonSet 的 `namespace: %s`(第 9 个)和 `image: %s`(第 10 个)
是两个独立占位符,但 Sprintf 只传了 9 个参数,第 10 个变成 %!s(MISSING),
整个多文档 YAML 解析失败:
  "解析 Kubernetes YAML: yaml: line 23: found character that cannot start any token"

而 installDaemonset 是「先删 daemonset/configmap/serviceaccount 再 apply」,
所以每次失败都会把上一份 Promtail 删掉却不重建 → 日志采集中断。

修复:
1) Sprintf 补上 DaemonSet 命名空间参数(第 9 位)
2) 新增 validateManifest():在删旧资源之前先本地解析校验清单,
   校验不过直接中止、不碰任何现有资源 —— 让模板错误永远不会再打断采集
3) 新增回归测试 promtail_manifest_test.go:用平台同一套
   YAMLOrJSONDecoder 解析 3 种参数组合,断言各解析出 5 个对象且无 TAB

=== 二、快捷 LogQL:Ingress 访问日志不再跟随所选命名空间 ===
原实现:next = applyLabelFilter(next,'namespace', namespace.trim() || 'ingress-nginx')
→ 选了 monitor 就生成 {namespace="monitor"} |= "HTTP/",查不到任何日志
  (ingress controller 的 Pod 只在 ingress-nginx 命名空间里)

修复:ingress 模板独立处理
  - 固定用 ingress controller 命名空间(ingress-nginx,缺失则回退 ingress)
  - 不叠加当前选中的 app / pod 过滤(那些属于业务服务)
  - 结果是 {namespace="ingress-nginx"} |= "HTTP/"

=== 三、K8s 资源名去掉 opsxloki ===
ClusterRole / ClusterRoleBinding:opsxloki-promtail → loki-promtail
- PromtailDaemonsetYAML 里 3 处 name 全部改名
- uninstallDaemonset 同步改名,并保留对旧名 opsxloki-promtail 的兼容清理,
  避免升级后留下孤儿 ClusterRole
- 集群侧已实测:新建 loki-promtail,旧的 opsxloki-promtail 已删除

保留的 opsxloki(有兼容性约束,改动会丢数据/掉登录):
- Go module 导入路径 opsxloki/xxx(149 处,纯内部包名)
- 数据文件 opsxloki.json(改了读不到历史数据)
- 前端 localStorage key opsxloki.auth.* / opsxloki.theme.* 等(改了在线用户掉登录)
- Dockerfile 里的 opsxloki 系统用户/二进制名

镜像:lghost/lokistack:latest + v0.1 = sha256:4a5590580cbb
2026-10-27 04:16:55 +00:00
opsx 41633f0b38 feat(collector): 采集命名空间多选 + app 标签来源可自定义 + app 白名单
=== 一、采集命名空间改为多选(原来只有文本输入)===
- CollectorEditDialog 重写:勾选式多选面板,从
  GET /clusters/:id/namespaces 拉取真实命名空间列表
- "所有命名空间(全集群采集)"一键全选(targetNamespaces 置空即全集群)
- 支持搜索过滤命名空间;已选数量实时显示
- 保存时为空数组 → Promtail 不加 namespaces 白名单

=== 二、app 标签取值来源可配置 ===
新增 Collector.AppLabelSource:
  留空(默认)= app.kubernetes.io/name → app → controller 名(去hash) → pod 名 四级回退
  可填任意 Pod 标签,如 app.kubernetes.io/name / app / k8s.kuboard.cn/name
  也可直接写 __meta_kubernetes_pod_label_xxx
  → 作为最高优先级插到回退链最前,其余仍兜底

模板侧新增:
  sanitizePromLabel()      标签名 → Prometheus meta label(. / 等转 _)
  promtailAppSourceLabel() 解析用户配置
  promtailAppIncludeFilter() 生成白名单 keep 规则

=== 三、app 白名单(控制采集量)===
新增 Collector.AppInclude []string:
  填了之后生成
    - source_labels: [app]
      regex: ^(app1|app2)$
      action: keep
  只保留匹配的日志流 —— 这是收敛日志量最直接的手段

service.Update 同步支持 appLabelSource / appInclude(兼容 []string 与逗号分隔字符串)。

前端 CollectorEditDialog:
  采集命名空间 = 勾选多选 + 全选
  app 标签取值来源 = 输入框 + 5 个常用值 datalist 提示
  只采集这些 app = 逗号分隔输入

镜像:lghost/lokistack:latest + v0.1 = sha256:43369d06cb28
2026-10-27 03:55:13 +00:00
opsx e006fc1a0e fix(promtail): app 标签通用回退 + 跳过僵尸 Pod + SecretKey 不下发 + 采集命名空间可选
=== 一、astralx 只采集到 2 个 app(核心问题)===
实证:用户的 Deployment Pod 模板只有 k8s.kuboard.cn/name 标签,
     没有 app / app.kubernetes.io/name。原模板的 app 只从这两个标签取,
     所以 115 个服务的 app 全是空值,Loki 里 app 只剩零星 2 个。

修复:app 四级回退,全部基于 Kubernetes 原生元数据(跨集群通用):
  1) app.kubernetes.io/name   标准约定
  2) app                      常见简写
  3) __tmp_controller_name    Pod owner(ReplicaSet) 去 hash → Deployment 名
                              (__meta_kubernetes_pod_controller_name 由
                                Promtail 直接从 K8s API 读取,任何集群都有,
                                不依赖 Kuboard/Rancher 等第三方工具注入的标签)
  4) pod name                 兜底,保证 app 永不为空

同时修复 PromtailHelmValues 模板同样的 app 逻辑,
并修正其 __meta_kubernetes__pod_name 双下划线笔误。

验证:astralx 的 app 值从 2 个 → 73 个。

=== 二、15 分钟 27 万条日志的真相 ===
排查结论:不是重复采集(positions 存 hostPath /run/promtail,Pod 重启不丢)。
真实原因:cex-openapi-gateway 残留 347 个 Evicted + 37 个 Error 的僵尸 Pod,
        只有 1 个 Running。这些 Pod 的日志文件仍在 /var/log/pods/ 下,
        Promtail 会把它们的历史异常堆栈(Reactor at reactor.core.publisher...)
        全量采集,造成 27 万条的假象并污染存储。

修复:relabel_configs 首条加 drop 规则:
  - source_labels: [__meta_kubernetes_pod_phase]
    regex: (Succeeded|Failed)
    action: drop
(两个模板都加)

=== 三、SecretKey 不再下发给前端 ===
安全要求:避免任何登录用户点「显示」按钮就能拿到对象存储 SK。
- StorageConfig.Sanitized(): secretKey 返回空串,仅保留 hasSecretKey 标记
  (accessKey 仍明文返回,便于管理员核对用的是哪个 AK)
- 测试连接:StorageTestRequest 新增 id 字段,secretKey 留空时后端用
  库里保存的凭据解密补齐(新建存储仍强制填写)
- 前端编辑时 SecretKey 一律留空 = 不修改,placeholder 显示
  「已保存 · 留空则不修改」,测试时带上 id

=== 四、采集器支持指定命名空间 ===
- Collector 新增 TargetNamespaces []string,对应 promtail 的
  kubernetes_sd_configs.namespaces.names(为空=全集群)
- PromtailDaemonsetYAML 新增 promtailNamespaceFilter 生成白名单
- service.Update 支持 targetNamespaces(兼容 []string 与逗号分隔字符串)
- 前端 CollectorEditDialog 增加「采集命名空间」输入(逗号分隔)

镜像:lghost/lokistack:latest + v0.1 = sha256:05b1505aedae
2026-10-27 03:30:25 +00:00
opsx 24caeefb3a fix(deployments): 卡片按钮布局拥挤 + 去掉冗余的日志查询/采集器/跳转入口
用户反馈 /deployments 卡片按钮布局乱,且「日志查询」「采集器」和 endpoint 跳转按钮多余。

问题根因:
  1. ResourceCard 的操作区是 flex 但没有 flex-wrap,
     而 DeploymentCard 在部署/停止/重启按钮上用了 flex-1,
     窄卡片里 7 个按钮互相挤压、换行错位、宽度不均
  2. endpoint 在 K8s 模式下是 http://loki.log.svc.cluster.local:3100
     这类集群内地址,浏览器点不开,展示出来纯属噪音

修复:
  - ResourceCard 操作区加 flex-wrap + items-center(按钮多时自动规整换行)
  - DeploymentCard 去掉 4 处 flex-1(按钮按内容宽度排布)
  - 去掉「访问」字段(不再展示 cluster-local endpoint)
  - 去掉「日志查询」跳转按钮(左下导航已有入口)
  - 去掉「采集器」跳转按钮(同上)
  - 去掉 endpoint 的 ExternalLink 跳转按钮
  - 清理不再使用的 import:Link(react-router)、ExternalLink(lucide)

卡片保留的按钮:配置 / 部署(或 停止+重启)/ 日志 / 删除

镜像:lghost/lokistack:latest + v0.1 = sha256:d89b9cfa0054
2026-10-27 03:10:42 +00:00
opsx 188d9460fe fix(logs): 「所有人」策略组无法新建(被误判为已属于其他策略组)
现象:userIds:[0] 新建策略组时报
  「用户 0 已属于此数据源的另一个策略组」HTTP 400

根因:validatePolicyGroup 的跨组冲突检查把 EveryoneUserID(0) 当真实用户,
     只要已有任何一个「所有人」策略组(如内置默认策略),
     再新建含 0 的组必然冲突。

修复:跨组冲突检查跳过 AnyoneUserID(0) —— 它是哨兵值不是真实用户,
     允许多个策略组同时包含「所有人」。

验证:
  POST /logs/policy-groups {userIds:[0]}  → 200,id=2 正常创建
  POST userIds:[0,2](混选)               → 400「「所有人」不能与具体用户同时选择」
  GET  /logs/policy-groups                → 默认策略(所有人 · default 命名空间)

镜像:lghost/lokistack:latest + v0.1 = sha256:3222a8716fdd
2026-10-27 03:02:50 +00:00
opsx cf54be3f7d fix(logs): 「所有人」策略保存被拒 + 快捷 LogQL namespace 空且不认默认策略
=== 一、保存策略组报「用户ID无效或重复」===
validatePolicyGroup 里 `if id == 0 || seen[id]` 直接把「所有人」哨兵当非法。
修复:
  - 新增常量 AnyoneUserID = 0(与 policyGroupAccess、前端 ANYONE 对齐)
  - 0 跳过用户存在性校验,但必须独占:与具体用户混选时报
    「「所有人」不能与具体用户同时选择」
  - 重复检查独立出来,报错带具体 ID

=== 二、快捷 LogQL 的 namespace 下拉总是空的 ===
两个叠加原因:
  1. 用的是原生 <datalist>,只在输入时才提示、样式弱,看着像"下拉是空的"
  2. 候选只来自 Loki 实际值 —— 而默认策略配的 default 命名空间里没有业务 Pod,
     Loki 里根本没有这个 label 值,所以永远不出现;前端也不会拿策略值当默认选中

修复:
  后端新增 GET /api/v1/logs/my-scope?sourceId= → {labels, unrestricted}
    (services.UserAccessLabels 暴露当前用户在数据源上的授权 labels;
      admin 返回 unrestricted=true 但仍带上策略 labels 供 UI 做默认值)
  前端:
    - logsApi.myScope()
    - QuickQueryTemplates 用策略里的 namespace 作为初始选中值
    - namespaceOptions = 策略值 ∪ Loki 实际值(去重,策略值优先)
    - namespace/app/pod 三个 <datalist> 全部换成 SearchableValueSelect
      (点击即展开、可搜索,placeholder 显示「请选择(共 N 个)」)

验证:
  PUT /logs/policy-groups/1 {userIds:[0]}         → 200 ✅
  POST userIds:[0,2](混选)                       → 400「「所有人」不能与具体用户同时选择」✅
  GET /logs/my-scope?sourceId=1                   → {"labels":{"namespace":"default"},"unrestricted":true} ✅
  bundle: my-scope 1 处、datalist 残留 0 处 ✅

镜像:lghost/lokistack:latest + v0.1 = sha256:ad659f564665
2026-10-27 02:59:33 +00:00
opsx 4dda6e5ba8 fix(logs): 快捷 LogQL null 崩溃 + app 标签错位 + 查询授权「所有人」与默认策略
=== 一、快捷 LogQL 报 Cannot read properties of null (reading 'map') ===
根因:Loki 对无值的 label 返回 {"data":null},Go nil slice 序列化成 JSON null,
     前端 setState(null) 后再 .map() / [...null] / .filter() 直接抛错。
     表现为「不稳定」——切到某个没有该 label 值的范围就崩。

修复(前后端双保险):
  后端 LokiLabels / LokiLabelValues 归一化 nil → []string{}
    (LokiHistogram 原本就返回空切片,无需改)
  前端 6 处调用点加 Array.isArray 防御:
    QuickQueryTemplates ×3、QueryInput ×2、LokiLabelBrowser ×2

=== 二、选 astralx 只显示 2 个 app(不是权限问题)===
实测:
  astralx 117 个 Deployment / 497 个 Pod
  带 app 标签的 Pod 只有 2 个(cex-promotion-web / cex-web-h5)
  其余 115 个 Deployment 的 Pod 模板没打 app,Kuboard 打的是 k8s.kuboard.cn/name
  Loki 里 service_name / container 各有 79 个值(Promtail 从容器名生成)

修复:QuickQueryTemplates 的「app」候选改为三级回退 app → service_name → container,
     并用 appLabel 状态记住实际生效的标签名,pod 收窄与 generate() 都用它,
     保证生成的 LogQL selector 真能查到数据。

=== 三、查询授权:支持「所有人」+ 内置默认策略 ===
1. 后端 policyGroupAccess:userIds 里的 0 作为哨兵值表示「所有人」(anyone / *),
   匹配任意用户(原来只能逐个指定用户 ID)
2. 种子 seedDefaultPolicy():首次启动且尚无任何策略组时,创建
   「默认策略(所有人 · default 命名空间)」(labels: namespace=default,userIds: [0])。
   管理员可在「查询授权」里把命名空间改成实际业务命名空间(如 astralx),
   对该组所有用户立即生效。
3. 前端 PolicyGroupsPage:新增「所有人(anyone)」一键按钮(与具体用户互斥),
   列表与编辑区显示「所有人」,校验文案同步更新。

验证:
  GET /logs/label/nonexistent_xyz/values            → {"data":[]}
  GET /logs/label/service_name/values?query={namespace="astralx"} → 79 个
  GET /logs/policy-groups → 1 条:默认策略(所有人 · default 命名空间)所有人

镜像:lghost/lokistack:latest + v0.1 = sha256:88a4ba626f62
2026-10-27 01:19:06 +00:00
opsx 46e73d3d17 fix(loki): COS 存储全链路修复 —— Secret 生成/env 注入/凭据解密/endpoint 规范化
API 端到端跑通中暴露 4 个串联问题(腾讯云 COS + K8s 场景):

1. loki_manifest.go: switch 缺少 cos / obs
   → 不生成 loki-storage-credentials Secret、不注入 LOKI_STORAGE_* 环境变量、
     也不给 Loki 加 -config.expand-env=true
   → 配置里 ${LOKI_STORAGE_ACCESS_KEY} 无法展开 → AK 为空
   → COS 返回 403 InvalidAccessKeyId(ruler 每分钟报错)
   修复:case 补上 cos / obs

2. loki_manifest.go: 生成 Secret 时用了 ENC: 密文原始值
   → Secret data 里是 ENC:xxx 而非真实 AK/SK
   修复:crypto.DecryptField 解密后再 base64

3. loki_config.go: host-docker 模式同样把密文写进配置
   修复:解密后返回

4. loki_config.go: cos/oss/obs 的 endpoint 未剥离桶名前缀
   → 用户填虚拟主机式 endpoint(mybucket-1250000000.cos.<region>...)时,
     Loki 的 S3 客户端又拼一次 bucketnames,变成 <bucket>.<bucket>.cos...
   → TLS 证书校验失败(x509: certificate is valid for ... not <bucket>.<bucket>.cos...)
   修复:新增 stripBucketPrefix,s3/cos/oss/obs/minio 分支统一剥离桶名前缀

验证(log namespace,API 端到端):
  - Loki statefulset 1/1 Running,ruler 无 InvalidAccessKeyId/x509
  - 写入 + 查询日志成功(持久化到 COS)
  - Promtail DaemonSet 12/12 全节点就绪
  - collector status=active

运维侧附加动作(非代码,见部署文档):
  - 存储配置改用有效 AK/SK + 区域端点 cos.ap-hongkong.myqcloud.com
  - 平台 ServiceAccount log:lokistack 绑定 cluster-admin
    (否则无法创建 Promtail 所需的 ClusterRole/ClusterRoleBinding)
2026-10-26 19:32:38 +00:00
opsx 004375e0ce fix(ns): 命名空间固定 log —— 平台/Loki 实例/Promtail 全部收敛到 log
按需求:所有 lokistack 相关资源(管理平台副本、Loki 实例、Promtail 采集器)
固定使用 log 命名空间,不提供覆盖开关。

改动:
1. 后端 services/deployment.go Create():k8s-yaml 模式下 namespace 为空时
   兜底为 "log",前端漏传也不会跑偏。
2. 前端 DeploymentCard.tsx:命名空间展示 fallback 'loki' → 'log'
3. 前端 DeploymentFormDialog.tsx:输入框 placeholder 'loki' → 'log'
   (form-schema.ts 的 DEFAULT_VALUES.namespace 原本已是 'log')

未动:
  kubernetes/install.sh 的 NS="log" 早就是固定值
  collector_deployer.go 已自动跟随 loki.Namespace(collector.Namespace 为空时)

资源名不冲突:
  平台        lokistack / lokistack-data / lokistack-secrets / lokistack-nodeport
  Loki 实例    loki / loki-config / loki-data / loki-headless / loki-storage-credentials
  Promtail    promtail-* (DaemonSet)

镜像:lghost/lokistack:latest + v0.1 = sha256:9d15b7ffbb3d
2026-10-26 18:55:06 +00:00
opsx cfe1ccd154 fix(storage): 按 endpoint 形态解析响应 + 凭据解密返回 + 无建桶权限友好提示
【真实根因】COS 桶明明存在却报"桶不存在/创建失败"

用户配置:endpoint=loki-log-1394668510.cos.ap-hongkong.myqcloud.com(虚拟主机式,已含桶名)
旧代码:始终 GET / 并按 ListBuckets 的 <Buckets><Bucket><Name> 解析。
但在虚拟主机式 endpoint 下,GET / 返回的是 ListObjects(<ListBucketResult>)——
桶内对象列表,不是桶列表。解析必然找不到桶名 → 误判"桶不存在"
→ 触发建桶 PUT → 用户的 AK 无 cos:PutBucket 权限 → 403
→ 最终报"创建后校验失败"。

【实测确认(用该 AK 直连 COS)】
  GET / (vhost)         → 200  <ListBucketResult>         桶存在,能列对象
  GET / (区域端点)       → 200  <ListAllMyBucketsResult>   AK 有 cos:GetService
  HEAD /                → 403  AK 无 cos:HeadBucket
  PUT /  (CreateBucket) → 403  AK 无 cos:PutBucket
即 QcloudCOSDataFullControl = 数据面全控(读写/列桶),不含管控面(建桶/HeadBucket)。

【修复】
1. 探测统一用 GET /,再按 endpoint 形态解释响应:
   - 虚拟主机式 → 按 ListObjects(<ListBucketResult>)判断,200 即桶存在
   - 区域端点   → 按 ListBuckets(<Buckets><Bucket><Name>)匹配桶名
   桶存在就直接返回"桶已存在",不再走建桶流程。
2. 建桶二次校验同样改用 GET /(不再用 HEAD,避免 HeadBucket 403 误判)。
3. 建桶返回 403 时给出明确友好提示:
   "当前 Access Key 无建桶权限。腾讯云 QcloudCOSDataFullControl 只授予数据读写/列出,
    不含 CreateBucket。请到 COS 控制台手动创建桶,或在 CAM 追加 cos:PutBucket"
4. models/storage.go Sanitized() 用 crypto.DecryptField 解密后再返回 AK/SK
   (原来直接返回 ENC:xxx 密文,前端编辑框显示密文)。
5. 前端访问地址输入框补充说明:推荐填区域端点(不带桶名)。

镜像:lghost/lokistack:latest + v0.1 = sha256:350478003870
2026-10-26 18:38:00 +00:00
opsx ff69ea2ce8 fix(storage): 凭证明文返回便于编辑测试 + 建桶后二次校验防"假成功"
问题(log.astx.club/storage):
  1. 编辑存储时 Access Key / Secret Key 都是空的,点「测试连接」提示
     请先填写 Bucket 与 Access Key / Secret Key,无法测试也无法保存;
  2. 提示"桶不存在,已自动创建"但疑似没有真正创建;
  3. 测试失败提示用 toast 弹到右下角,不在弹框内。

修复:
  1. models/storage.go Sanitized() 返回 accessKey / secretKey 明文
     (内网运维工具,管理员需要在编辑弹框里看到并测试连接;脱敏会导致
      编辑时字段为空、校验失败)
  2. 前端 StorageConfig 类型补 accessKey / secretKey 字段;
     编辑弹框打开时回填这两个字段(原来只有 secretKey 且强制置空);
     Secret Key 标签去掉「留空保持不变」
  3. services/storage_check.go 建桶后二次 ListBuckets 校验:
     不同对象存储 CreateBucket 语义不同(腾讯云 COS 要求桶名放 Host、
     路径为 /),直接 PUT /<bucket> 可能返回 200 但未真正创建。
     现在创建后重新列举桶,找不到就报"创建后校验失败"并提示检查
     Endpoint / Region / 桶名(COS 需带 APPID)
  4. 前端「请先填写 Bucket 与 Access Key / Secret Key」改为在弹框内
     testResult 区域显示(原来 toast 弹右下角)

镜像:lghost/lokistack:latest + v0.1 = sha256:6b6cc9769e79
2026-10-26 18:30:47 +00:00
opsx d0b9b6b71d fix(ui): 前端用户可见文案 opsx-loki → lokistack
修正用户能看到/会复制走的旧名(你反馈的 K8s 示例 token 命令在内):

1. 界面标题(4 处)
   App.tsx            浏览器标题栏
   Layout.tsx         侧边栏 logo / 页面标题 fallback / 移动端导航
   login/index.tsx    登录页大标题
   settings/index.tsx 「关于 lokistack」

2. 集群接入示例(用户直接复制到集群执行,5 处)
   ClusterFormDialog.tsx:
     Role / ServiceAccount 名: opsx-loki       → lokistack
     token Secret 名:         opsx-loki-token  → lokistack-token
     取 token 命令:           get secret opsx-loki-token → lokistack-token

3. 文档
   docs/deployment/README.md, host-nginx.md  旧名全量修正
   README.md 迁移章节之外的示例命令修正;
             迁移章节(从 opsx-loki 升级)保留旧名作历史对照

【刻意不改】
  a) localStorage key(9 个)
     opsxloki.auth.{token,refresh-token,user}
     opsxloki.theme.mode / layout.collapsed / chunk-reloaded
     opsxloki.logs.{query-history,range-history}
     原因:改了所有在线用户立即掉登录,主题/折叠/查询历史全丢。
     要改必须配套迁移(参考 main.tsx 中 lokideploy.* → opsxloki.* 的写法)。

  b) 数据文件名 opsxloki.json(后端硬编码,改了读不到 DB)

  c) Go module 路径 opsxloki/...(159 处 import)
     属编译期内部标识,最终用户不可见;改名需全仓重构 + go.mod 变更。
2026-10-26 17:30:30 +00:00
opsx 3c9388658b refactor: 资源名 opsx-loki/opsxloki → lokistack(线上已迁移完成)
【已在线完成,零数据丢失】
K8s ns=log 资源全部改名:
  Deployment  opsx-loki          → lokistack
  Service     opsx-loki          → lokistack
  Service     opsx-loki-nodeport → lokistack-nodeport
  PVC         opsxloki-data      → lokistack-data
  Secret      opsxloki-secrets   → lokistack-secrets
  Label       app: opsx-loki     → app: lokistack
  Ingress     log.astx.club 后端  → lokistack-nodeport:8080
  镜像        lghost/opsxloki     → lghost/lokistack
  docker-compose 卷/容器名同步更名

【保留不变(改了会出事)】
  namespace: log
  数据文件名 opsxloki.json(代码硬编码)
  容器内路径 /app/data

【迁移过程】
  1. 备份 PVC 数据(32KB) + Secret 原值 + 旧 yaml → /root/lokistack-migration-backup-20261026-171141
  2. scale 旧 Deployment → 0,等 Pod 消失
  3. 复制 Secret(JWT sha256 校验一致)
  4. 新建 PVC lokistack-data(Bound 30Gi cbs)
  5. 临时 Pod 恢复数据(chown 1000:1000,opsxloki.json 保留 0600)
  6. apply 新 Deployment → 1/1 Running
  7. 修复 Ingress 后端(TKE qcloud 要求 NodePort)→ CLB 恢复 200
  8. 删除旧 Deployment / 失效 Service

【验证】
  lokistack 1/1,0 重启,镜像 lghost/lokistack:latest(statically linked)
  /api/v1/health → {"status":"ok","version":"2.0.0"}
  /app/data/opsxloki.json 610B 完整(admin 用户 + 配置)
  http://log.astx.club/ → HTTP 200
  镜像加固:admin123=0、/app/backend/=0、initial-admin-password.txt 存在

【回滚兜底(建议 7 天后删)】
  pvc/opsxloki-data、secret/opsxloki-secrets

【文档】
  README 新增「从 opsx-loki 升级」章节:
  K8s 七步迁移脚本 + docker-compose 卷迁移命令
2026-10-26 17:16:15 +00:00
opsx b65dcc9677 refactor: 镜像名 lghost/opsxloki → lghost/lokistack(跟随项目更名)
只改镜像引用(8 处),K8s 资源名一律不动 —— 改了会丢数据/断服务:

改:
  kubernetes/deployment.yaml        image
  docker-compose.yml                image
  docker-compose.prod.yml           image ×2
  kubernetes/README.md              文档 ×2
  docs/deployment/kubernetes.md     文档 ×2

不改(重要):
  opsxloki-secrets    K8s Secret —— 改名会导致 JWT_SECRET/ENCRYPTION_KEY 重置,
                      历史加密的实例密码无法解密
  opsxloki-data       PVC —— 改名 = 新建空卷,数据全丢
  container_name      docker 容器名 —— 改名会让 docker exec/logs 命令失效
  app: opsx-loki      Pod label —— 改名要同步改 Service selector
  deployment 名称 opsx-loki —— 改名要重建 Deployment

⚠️ 部署顺序(本提交只改代码,尚未 apply 到 K8s):
  1) 推镜像 lghost/lokistack:latest 到 Docker Hub
  2) kubectl apply -f kubernetes/deployment.yaml
  3) 等 Pod 1/1 就绪
  4) 才能删除 lghost/opsxloki
  顺序颠倒会因 imagePullPolicy: Always 拉到不存在的镜像导致服务中断
2026-10-26 17:09:47 +00:00
opsx ec759cd36b build: Dockerfile 编译加 -trimpath(抹除源码绝对路径)
加固前:二进制内可 strings 出 66 处 /app/backend/controllers/*.go 等源码路径,
        泄露完整目录结构与文件名。
加固后:路径被 trimpath 抹除,命中数 0。

注:build.sh / run.sh 已在上个提交加过 -trimpath,本次补上实际部署用的
    Dockerfile 构建路径(K8s / docker-compose 走的就是这里)。
2026-10-26 17:05:11 +00:00
opsx 3c27e58a73 feat(security): 移除 admin123 硬编码默认密码 + 编译加 -trimpath
安全加固(降低二进制泄露/逆向的信息面):

1. 初始密码随机化
   - 首次启动生成 16 位随机密码(大小写+数字,排除 0/O/1/l/I 易混淆字符)
   - 写入数据卷 initial-admin-password.txt(0600)并打印到首次启动日志
   - admin 首次改密后自动删除该文件(seed.CleanupInitPassword)
   - 支持 ADMIN_INIT_PASSWORD 环境变量指定(自动化部署 / 固定密码场景)
   - 生成失败直接 Fatal 退出,不再降级到公开默认口令

2. 移除硬编码 admin123
   - main.go 不再打印 "Default login: admin / admin123"
   - 二进制内 admin123 命中:2 → 0

3. -trimpath
   - build.sh / run.sh 编译加 -trimpath
   - 二进制内 /app/backend/ 源码路径命中:66 → 0

4. 文档
   - README 新增「第 4 步:获取管理员初始密码」
     (docker compose exec / logs / docker cp / named volume 四种方式 + K8s 对应命令)
   - .env.example 补 ADMIN_INIT_PASSWORD
   - kubernetes/deployment.yaml 加 env 注释说明

验证(strings 对比加固前后二进制):
  admin123              2 → 0
  /app/backend/        66 → 0
  initial-admin-password.txt / ADMIN_INIT_PASSWORD / CleanupInitPassword 均存在
2026-10-26 17:01:09 +00:00
opsx 9a46a955d4 feat(k8s): 一键部署脚本 + 跨云适配 + 云相关清单拆分到 examples
新增 kubernetes/install.sh(一条命令部署):
- 云平台识别:读节点 providerID,识别 TKE / EKS / ACK / CCE / GKE / AKS / K3s / 自建
- StorageClass 三级探测:显式 STORAGE_CLASS → is-default-class 注解 → 集群唯一 SC
- 自建集群兜底:无任何 SC 时自动创建静态 local PV
  (hostPath + DirectoryOrCreate + nodeAffinity + WaitForFirstConsumer + Retain)
- Secret 幂等生成:已存在则跳过,绝不覆盖(避免密钥轮换导致历史凭证无法解密)
- 失败分类诊断:ImagePullBackOff / CreateContainerConfigError / PVC Pending
  按云给出对应仓库格式与推荐 SC 名
- 环境变量:WITH_NODEPORT / STORAGE_CLASS / LOCAL_STORAGE / NODE_NAME / PV_PATH / PV_SIZE

云相关清单拆到 kubernetes/examples/:
- ingress.yaml(阿里云 alb)、ingress-tke.yaml(腾讯云 qcloud)、secret.example.yaml
- 原因:留在根目录会导致 kubectl apply -f kubernetes/ 时 ingress 同名互相覆盖,
  且 secret.example.yaml 的占位符会把真实密钥顶掉

README 重写:
- 全部用法速查 + 环境变量一览
- 自建 K8s 存储方案选型(local PV / local-path-provisioner / OpenEBS / Longhorn / NFS)
- hostPath 数据运维:定位、热备份、跨节点迁移、卸载保留、常见坑
2026-10-26 14:03:39 +00:00