package webui
// 数据目录一致性端点。
//
// ## 为什么需要它
//
// 内核有**两处**数据目录来源,且不等价:
//
// -data
← systemd 单元,决定 ks.DataDir
// core.daemon.data_dir ← 数据库,决定 ks.Cfg.Daemon.DataDir
//
// 不一致时数据会**分裂**:图记忆(memory/graph.db)在 -data 那个目录,
// 而文本/知识/文档在 DB 那个目录 ⇒ 检索跨目录、备份会漏。
//
// 生产当前两者一致,所以从没暴露;但 WebUI 允许改 core.daemon.data_dir
// 而**没有任何提示** —— 用户改完重启就会发现"数据丢了"。
//
// ## 只读 + 给命令,绝不自动改
//
// 本端点只返回诊断与**可直接粘贴执行的修复命令**(里面已内联 sed 替换)。
// 不代用户改 systemd 单元:静默改单元比不提示更危险 —— 改错了服务起不来,
// 而用户不知道是谁改的。修复必须由人执行。
import (
"net/http"
"github.com/JianFeeeee/HomeAgent/internal/systemd"
)
// dataDirs 取两个数据目录来源。
//
// runtime ← SettingsAPI.DataDir()(= -data,systemd 单元给的那个)
// config ← ConfigAPI.Get().Daemon.DataDir(= DB 里 core.daemon.data_dir)
//
// 任一不可用就返回空串 —— 调用方据此判断"无法判定",而不是拿空串
// 去比出"不一致"的假警报。
func (h *Handler) dataDirs() (runtimeDir, configDir string) {
if h.settings != nil {
runtimeDir = h.settings.DataDir()
}
if h.config != nil {
if cfg := h.config.Get(); cfg != nil {
configDir = cfg.Daemon.DataDir
}
}
return runtimeDir, configDir
}
// handleDataDirStatus 处理 GET /api/v1/system/data-dir
//
// 返回:
//
// runtime_dir 当前进程实际用的(-data)
// config_dir 数据库里 core.daemon.data_dir
// in_sync 两者是否一致
// describe 人读的一句话说明
// sync_command 不一致时的修复命令(内联 sed,可直接复制执行)
func (h *Handler) handleDataDirStatus(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeJSONError(w, http.StatusMethodNotAllowed, "只接受 GET")
return
}
runtimeDir, configDir := h.dataDirs()
st := systemd.State{
RuntimeDataDir: runtimeDir,
ConfigDataDir: configDir,
}
out := map[string]interface{}{
"runtime_dir": runtimeDir,
"config_dir": configDir,
"in_sync": st.InSync(),
"describe": st.Describe(),
"under_systemd": systemd.IsUnderSystemd(),
}
if !st.InSync() {
// 只在真正不一致时给命令 —— 一致时给命令会让人以为该做点什么
out["sync_command"] = st.SyncCommand(configDir)
out["warning"] = "数据目录不一致:图记忆在 runtime_dir,文本/知识/文档在 config_dir。" +
"不修复则检索跨目录、备份会漏。请执行 sync_command(勿手改单元,生产用 drop-in 覆盖 ExecStart)。"
}
writeJSON(w, http.StatusOK, out)
}