fix(cli,config): 修复 sudo 下初始化失败 + daemon 不重载问题

- cli/transport: 新增 stop_daemon(),init 后自动停止旧 daemon
  - config: cli_dir() 优先读 SUDO_USER 环境变量,避免写到 /root/.wx-cli
  - config: auto_detect_db_dir() 按 .db 文件最新 mtime 排序,正确选最新目录
  - daemon/server: dispatch 新增 ReloadConfig 命令(预留)
  - ipc: Request 新增 ReloadConfig 变体
  - scanner/linux: 移除调试日志,清理 unused bail import
pull/37/head
cjliu 2026-05-12 12:36:43 +08:00
parent 6659f48984
commit 880dabed64
6 changed files with 65 additions and 7 deletions

View File

@ -91,6 +91,10 @@ pub fn cmd_init(force: bool) -> Result<()> {
std::fs::write(&config_path, serde_json::to_string_pretty(&cfg)?)
.context("写入 config.json 失败")?;
println!("配置已保存: {}", config_path.display());
// init 之后必须停掉旧 daemon它用的是旧 config下次调用会自动重启
let _ = crate::cli::transport::stop_daemon();
println!("初始化完成,可以使用 wx sessions / wx history 等命令了");
Ok(())

View File

@ -62,6 +62,31 @@ pub fn ensure_daemon() -> Result<()> {
Ok(())
}
/// 停止 daemon如果正在运行
pub fn stop_daemon() -> Result<()> {
let pid_path = config::pid_path();
if let Ok(pid_str) = std::fs::read_to_string(&pid_path) {
if let Ok(pid) = pid_str.trim().parse::<u32>() {
#[cfg(unix)]
{
let _ = std::process::Command::new("kill")
.arg("-TERM")
.arg(pid.to_string())
.spawn();
}
#[cfg(windows)]
{
let _ = std::process::Command::new("taskkill")
.args(["/F", "/PID", &pid.to_string()])
.spawn();
}
}
}
let _ = std::fs::remove_file(config::sock_path());
let _ = std::fs::remove_file(&pid_path);
Ok(())
}
/// 启动 daemon 前检查 `~/.wx-cli/` 可写,给出比"超时"更明确的错误。
///
/// 典型坑:旧版本 `sudo wx init` 把目录留成 root 属主,非 root 的 daemon

View File

@ -87,9 +87,14 @@ fn find_config_file() -> Result<PathBuf> {
}
pub fn cli_dir() -> PathBuf {
dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("/tmp"))
.join(".wx-cli")
let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("/tmp"));
// 支持 sudo 环境:如果以 root 运行但有 SUDO_USER切换到该用户的 home
let home = std::env::var("SUDO_USER")
.ok()
.filter(|s| !s.is_empty())
.map(|u| PathBuf::from("/home").join(u))
.unwrap_or(home);
home.join(".wx-cli")
}
pub fn sock_path() -> PathBuf {
@ -213,13 +218,32 @@ fn detect_db_dir_impl() -> Option<PathBuf> {
}
}
candidates.sort_by_key(|p| {
std::fs::metadata(p)
.and_then(|m| m.modified())
.unwrap_or(std::time::SystemTime::UNIX_EPOCH)
// 排序:取 db_storage 目录下所有 .db 文件的最新 mtime而非目录自身的 mtime
// 这样当收到新消息时(只有 .db 文件被更新),能正确识别最新目录
latest_db_mtime(p).unwrap_or(std::time::SystemTime::UNIX_EPOCH)
});
candidates.into_iter().next_back()
}
/// 递归查找 db_storage 目录下所有 .db 文件的最新 mtime
fn latest_db_mtime(dir: &Path) -> Option<std::time::SystemTime> {
let mut latest = None;
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
let mtime = if path.is_dir() {
latest_db_mtime(&path).unwrap_or(std::time::SystemTime::UNIX_EPOCH)
} else if path.extension().and_then(|s| s.to_str()) == Some("db") {
entry.metadata().and_then(|m| m.modified()).unwrap_or(std::time::SystemTime::UNIX_EPOCH)
} else {
continue;
};
latest = Some(latest.map_or(mtime, |cur| if mtime > cur { mtime } else { cur }));
}
}
latest
}
#[cfg(target_os = "windows")]
fn detect_db_dir_impl() -> Option<PathBuf> {
let appdata = std::env::var("APPDATA").ok()?;

View File

@ -231,5 +231,8 @@ async fn dispatch(
Err(e) => Response::err(e.to_string()),
}
}
ReloadConfig => {
Response::ok(serde_json::json!({ "reloading": true }))
}
}
}

View File

@ -114,6 +114,8 @@ pub enum Request {
#[serde(skip_serializing_if = "Option::is_none")]
user: Option<String>,
},
/// 重新加载配置和密钥init --force 后 daemon 不会自动重读)
ReloadConfig,
}

View File

@ -3,7 +3,7 @@
/// 通过 /proc/<pid>/maps 枚举内存区域,
/// 通过 /proc/<pid>/mem 读取内存内容,
/// 搜索 x'<64hex><32hex>' 格式的 SQLCipher 密钥
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result};
use std::io::{Read, Seek, SeekFrom};
use std::path::Path;