From 18daf5b22e5728b6c0da73f6ec455fc5015fc32c Mon Sep 17 00:00:00 2001 From: jackwener Date: Fri, 17 Apr 2026 14:01:04 +0800 Subject: [PATCH] fix: Windows init and daemon startup (issue #5) Three related bugs caused "wx init" and daemon startup to fail on Windows: 1. init.rs: create ~/.wx-cli/ before writing all_keys.json (was created only before config.json, so first write failed with ENOENT) 2. transport.rs (Windows): daemon.log was always empty because stderr was never redirected, and log file open silently fell back to null when parent dir didn't exist. Now mirror the Unix version: create parent dir, try_clone to redirect both stdout and stderr. 3. server.rs (Windows): interprocess GenericNamespaced auto-prepends \\.\pipe\ on Windows. Passing the full path caused a double-prefixed pipe name that clients (using raw \\.\pipe\wx-cli-daemon) could never connect to, leading to the 15s startup timeout. --- src/cli/init.rs | 11 ++++++----- src/cli/transport.rs | 18 ++++++++++++------ src/daemon/server.rs | 7 ++++--- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/cli/init.rs b/src/cli/init.rs index 28775e0..8f802b1 100644 --- a/src/cli/init.rs +++ b/src/cli/init.rs @@ -43,6 +43,12 @@ pub fn cmd_init(force: bool) -> Result<()> { println!("扫描加密密钥(需要 root 权限)..."); let entries = scanner::scan_keys(&db_dir)?; + // 确保父目录存在(如 ~/.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() .unwrap_or(std::path::Path::new(".")) @@ -75,11 +81,6 @@ pub fn cmd_init(force: bool) -> Result<()> { cfg.entry("keys_file".into()).or_insert_with(|| json!("all_keys.json")); cfg.entry("decrypted_dir".into()).or_insert_with(|| json!("decrypted")); - // 确保父目录存在(如 ~/.wx-cli/) - if let Some(parent) = config_path.parent() { - std::fs::create_dir_all(parent) - .with_context(|| format!("创建目录失败: {}", parent.display()))?; - } std::fs::write(&config_path, serde_json::to_string_pretty(&cfg)?) .context("写入 config.json 失败")?; println!("配置已保存: {}", config_path.display()); diff --git a/src/cli/transport.rs b/src/cli/transport.rs index 01590a3..b2e0d43 100644 --- a/src/cli/transport.rs +++ b/src/cli/transport.rs @@ -92,15 +92,21 @@ fn start_daemon() -> Result<()> { #[cfg(windows)] { use std::os::windows::process::CommandExt; - let log_file = std::fs::OpenOptions::new() + let log_path = config::log_path(); + if let Some(parent) = log_path.parent() { + let _ = std::fs::create_dir_all(parent); + } + let (stdout_stdio, stderr_stdio) = std::fs::OpenOptions::new() .create(true).append(true) - .open(config::log_path()) - .ok() - .map(std::process::Stdio::from) - .unwrap_or_else(std::process::Stdio::null); + .open(&log_path) + .and_then(|f| f.try_clone().map(|g| (f, g))) + .map(|(f, g)| (std::process::Stdio::from(f), std::process::Stdio::from(g))) + .unwrap_or_else(|_| (std::process::Stdio::null(), std::process::Stdio::null())); let _ = std::process::Command::new(&exe) .env("WX_DAEMON_MODE", "1") - .stdout(log_file) + .stdin(std::process::Stdio::null()) + .stdout(stdout_stdio) + .stderr(stderr_stdio) .creation_flags(0x00000008) // DETACHED_PROCESS .spawn() .context("无法启动 daemon 进程")?; diff --git a/src/daemon/server.rs b/src/daemon/server.rs index baa3b31..71a1c04 100644 --- a/src/daemon/server.rs +++ b/src/daemon/server.rs @@ -92,12 +92,13 @@ async fn serve_windows( tokio::prelude::*, GenericNamespaced, ListenerOptions, }; - let pipe_name = r"\\.\pipe\wx-cli-daemon"; - let name = pipe_name.to_ns_name::()?; + // interprocess 的 GenericNamespaced 在 Windows 上会自动拼接 `\\.\pipe\` 前缀, + // 这里必须传相对名;client 端用 `\\.\pipe\wx-cli-daemon` 直接打开可以对上 + let name = "wx-cli-daemon".to_ns_name::()?; let opts = ListenerOptions::new().name(name); let listener = opts.create_tokio()?; - eprintln!("[server] 监听 {}", pipe_name); + eprintln!("[server] 监听 \\\\.\\pipe\\wx-cli-daemon"); loop { let conn = listener.accept().await?;