Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ sudo wx init
wx init
```

**重新扫描密钥**(`sudo wx init --force` 或 `wx init --force`)时,会在写入新密钥前**停止正在运行的 wx-daemon**,并**清空 `~/.wx-cli/cache` 解密缓存**(含 `_mtimes.json`),避免仅因 mtime 未变而继续复用按旧密钥解出的缓存文件。

验证安装:

```bash
Expand Down
57 changes: 17 additions & 40 deletions src/cli/daemon_cmd.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use anyhow::Result;
use crate::config;
use crate::cli::DaemonCommands;
use crate::cli::transport;
use crate::cli::DaemonCommands;
use crate::config;
use anyhow::Result;

pub fn cmd_daemon(cmd: DaemonCommands) -> Result<()> {
match cmd {
Expand All @@ -25,42 +25,13 @@ fn cmd_status() -> Result<()> {
}

fn cmd_stop() -> Result<()> {
let pid_path = config::pid_path();
if !pid_path.exists() {
println!("daemon 未运行");
return Ok(());
}

let pid_str = std::fs::read_to_string(&pid_path)?;
let pid: u32 = pid_str.trim().parse()
.map_err(|_| anyhow::anyhow!("PID 文件格式错误"))?;

#[cfg(unix)]
{
let ret = unsafe { libc::kill(pid as libc::pid_t, libc::SIGTERM) };
if ret != 0 {
let errno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
if errno == libc::ESRCH {
println!("wx-daemon (PID {}) 已不在运行,清理残留文件", pid);
} else {
anyhow::bail!("发送 SIGTERM 失败 (errno {})", errno);
}
} else {
println!("已停止 wx-daemon (PID {})", pid);
match transport::stop_daemon()? {
transport::StopDaemonOutcome::NoPidFile => println!("daemon 未运行"),
transport::StopDaemonOutcome::Stopped(pid) => println!("已停止 wx-daemon (PID {})", pid),
transport::StopDaemonOutcome::StalePid(pid) => {
println!("wx-daemon (PID {}) 已不在运行,清理残留文件", pid);
}
}

#[cfg(windows)]
{
std::process::Command::new("taskkill")
.args(["/PID", &pid.to_string(), "/F"])
.output()?;
println!("已停止 wx-daemon (PID {})", pid);
}

let _ = std::fs::remove_file(config::sock_path());
let _ = std::fs::remove_file(&pid_path);

Ok(())
}

Expand Down Expand Up @@ -89,19 +60,25 @@ fn cmd_logs(follow: bool, lines: usize) -> Result<()> {
file.read_to_string(&mut content)?;
let all_lines: Vec<&str> = content.lines().collect();
let show = &all_lines[all_lines.len().saturating_sub(lines)..];
for line in show { println!("{}", line); }
for line in show {
println!("{}", line);
}
loop {
std::thread::sleep(std::time::Duration::from_millis(500));
let mut buf = String::new();
file.read_to_string(&mut buf)?;
if !buf.is_empty() { print!("{}", buf); }
if !buf.is_empty() {
print!("{}", buf);
}
}
}
} else {
let content = std::fs::read_to_string(&log_path)?;
let all_lines: Vec<&str> = content.lines().collect();
let show = &all_lines[all_lines.len().saturating_sub(lines)..];
for line in show { println!("{}", line); }
for line in show {
println!("{}", line);
}
}

Ok(())
Expand Down
68 changes: 55 additions & 13 deletions src/cli/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use anyhow::{Context, Result};
use serde_json::json;
use std::collections::HashMap;

use crate::cli::transport::{stop_daemon, StopDaemonOutcome};
use crate::config;
use crate::scanner;

Expand All @@ -14,14 +15,20 @@ pub fn cmd_init(force: bool) -> Result<()> {
if let Ok(content) = std::fs::read_to_string(&config_path) {
if let Ok(cfg) = serde_json::from_str::<serde_json::Value>(&content) {
let db_dir = cfg.get("db_dir").and_then(|v| v.as_str()).unwrap_or("");
let keys_file = cfg.get("keys_file").and_then(|v| v.as_str()).unwrap_or("all_keys.json");
let keys_file = cfg
.get("keys_file")
.and_then(|v| v.as_str())
.unwrap_or("all_keys.json");
let keys_path = if std::path::Path::new(keys_file).is_absolute() {
std::path::PathBuf::from(keys_file)
} else {
config_path.parent().unwrap_or(std::path::Path::new("."))
config_path
.parent()
.unwrap_or(std::path::Path::new("."))
.join(keys_file)
};
if !db_dir.is_empty() && !db_dir.contains("your_wxid")
if !db_dir.is_empty()
&& !db_dir.contains("your_wxid")
&& std::path::Path::new(db_dir).exists()
&& keys_path.exists()
{
Expand Down Expand Up @@ -50,22 +57,40 @@ pub fn cmd_init(force: bool) -> Result<()> {
#[cfg(unix)]
drop_privileges_if_sudo()?;

// --force:先停 daemon 并清空解密缓存,避免旧缓存与新密钥 mtime 一致仍被复用
if force {
println!("(--force) 停止 wx-daemon 并清空解密缓存…");
match stop_daemon()? {
StopDaemonOutcome::NoPidFile => {}
StopDaemonOutcome::Stopped(pid) => println!("已停止 wx-daemon (PID {})", pid),
StopDaemonOutcome::StalePid(pid) => {
println!("wx-daemon (PID {}) 已不在运行,已清理残留文件", pid);
}
}
clear_decrypt_cache()?;
println!("已清空 {}", config::cache_dir().display());
}

// 确保父目录存在(如 ~/.wx-cli/),必须在任何写入之前
if let Some(parent) = config_path.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("创建目录失败: {}", parent.display()))?;
}

// Step 3: 保存 all_keys.json
let keys_file_path = config_path.parent()
let keys_file_path = config_path
.parent()
.unwrap_or(std::path::Path::new("."))
.join("all_keys.json");

let mut keys_json = serde_json::Map::new();
for entry in &entries {
keys_json.insert(entry.db_name.clone(), json!({
"enc_key": entry.enc_key,
}));
keys_json.insert(
entry.db_name.clone(),
json!({
"enc_key": entry.enc_key,
}),
);
}
std::fs::write(&keys_file_path, serde_json::to_string_pretty(&keys_json)?)
.context("写入 all_keys.json 失败")?;
Expand All @@ -85,21 +110,35 @@ pub fn cmd_init(force: bool) -> Result<()> {
}
}
cfg.insert("db_dir".into(), json!(db_dir.to_string_lossy()));
cfg.entry("keys_file".into()).or_insert_with(|| json!("all_keys.json"));
cfg.entry("decrypted_dir".into()).or_insert_with(|| json!("decrypted"));
cfg.entry("keys_file".into())
.or_insert_with(|| json!("all_keys.json"));
cfg.entry("decrypted_dir".into())
.or_insert_with(|| json!("decrypted"));

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();
let _ = stop_daemon();

println!("初始化完成,可以使用 wx sessions / wx history 等命令了");

Ok(())
}

/// 删除 `~/.wx-cli/cache`(含 `_mtimes.json` 与已解密 DB),并重建空目录。
fn clear_decrypt_cache() -> Result<()> {
let dir = config::cache_dir();
if dir.exists() {
std::fs::remove_dir_all(&dir)
.with_context(|| format!("删除解密缓存目录失败: {}", dir.display()))?;
}
std::fs::create_dir_all(&dir)
.with_context(|| format!("创建解密缓存目录失败: {}", dir.display()))?;
Ok(())
}

/// 如果当前以 root 身份运行且是通过 sudo 启动的,drop 到调用用户身份,
/// 并迁移旧版本遗留的 root 属主 `~/.wx-cli/`。
///
Expand Down Expand Up @@ -133,7 +172,9 @@ fn drop_privileges_if_sudo() -> Result<()> {
}

// 设置 umask,让后续 create 出来的文件/目录默认是 0600 / 0700。
unsafe { libc::umask(0o077); }
unsafe {
libc::umask(0o077);
}

// 必须先 setgid 再 setuid:一旦 uid 降下来就没法再改 gid 了。
unsafe {
Expand All @@ -157,8 +198,9 @@ fn drop_privileges_if_sudo() -> Result<()> {
Ok(())
}
fn chown_one(path: &Path, uid: u32, gid: u32) -> std::io::Result<()> {
let c = CString::new(path.as_os_str().as_bytes())
.map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidInput, "path contains NUL"))?;
let c = CString::new(path.as_os_str().as_bytes()).map_err(|_| {
std::io::Error::new(std::io::ErrorKind::InvalidInput, "path contains NUL")
})?;
if unsafe { libc::chown(c.as_ptr(), uid, gid) } != 0 {
return Err(std::io::Error::last_os_error());
}
Expand Down
Loading