Skip to main content

agent_procs/daemon/
spawn.rs

1use crate::paths;
2use std::fs;
3use std::path::Path;
4use std::process::Command;
5
6/// Spawns the daemon as a detached background process by re-executing the
7/// current binary with the `--run-daemon SESSION` internal flag.
8/// This avoids the fork-inside-tokio-runtime problem.
9pub fn spawn_daemon(session: &str) -> std::io::Result<()> {
10    // Create socket base dir with restricted permissions
11    let socket_dir = paths::socket_base_dir();
12    fs::create_dir_all(&socket_dir)?;
13    // Set permissions to 0700 (owner only)
14    #[cfg(unix)]
15    {
16        use std::os::unix::fs::PermissionsExt;
17        fs::set_permissions(&socket_dir, fs::Permissions::from_mode(0o700))?;
18    }
19
20    let state = paths::state_dir(session);
21    fs::create_dir_all(state.join("logs"))?;
22
23    let socket_path = paths::socket_path(session);
24    let pid_path = paths::pid_path(session);
25
26    // Remove stale socket if present (ignore ENOENT)
27    let _ = fs::remove_file(&socket_path);
28
29    // Re-exec self with a hidden flag to run as daemon
30    let exe = std::env::current_exe()?;
31
32    // Use double-fork via shell to fully detach
33    let child = Command::new(&exe)
34        .args(["--run-daemon", session])
35        .stdin(std::process::Stdio::null())
36        .stdout(std::process::Stdio::null())
37        .stderr(std::process::Stdio::null())
38        .spawn()?;
39
40    // Detach: we don't want to wait for it (let OS reap it as orphan → init)
41    // Drop the child handle without waiting — it becomes a daemon
42    drop(child);
43
44    wait_for_daemon_ready(&pid_path, &socket_path)
45}
46
47fn wait_for_daemon_ready(pid_path: &Path, socket_path: &Path) -> std::io::Result<()> {
48    // Poll for socket existence, then try to connect to verify it's accepting
49    for _ in 0..100 {
50        if pid_path.exists() && socket_path.exists() {
51            // Try connecting to confirm the server is actually listening
52            if std::os::unix::net::UnixStream::connect(socket_path).is_ok() {
53                return Ok(());
54            }
55        }
56        std::thread::sleep(std::time::Duration::from_millis(50));
57    }
58    Err(std::io::Error::new(
59        std::io::ErrorKind::TimedOut,
60        "daemon did not start within 5s",
61    ))
62}
63
64/// Entry point called when running as daemon (via --run-daemon flag in main.rs).
65/// This runs in a fresh process with no tokio runtime yet.
66pub async fn run_daemon(session: &str) {
67    // Initialize file-based tracing subscriber before any other operations.
68    // The daemon's stdout/stderr are redirected to /dev/null, so structured
69    // logging to a file is the only way to capture diagnostics.
70    {
71        use tracing_subscriber::{EnvFilter, fmt};
72
73        let log_file = paths::state_dir(session).join("daemon.log");
74        // Ensure the parent directory exists before creating the log file.
75        let _ = std::fs::create_dir_all(paths::state_dir(session));
76        if let Ok(file) = std::fs::File::create(&log_file) {
77            let subscriber = fmt()
78                .with_env_filter(
79                    EnvFilter::from_default_env()
80                        .add_directive("agent_procs=info".parse().unwrap()),
81                )
82                .with_writer(file)
83                .with_ansi(false)
84                .finish();
85            let _ = tracing::subscriber::set_global_default(subscriber);
86        }
87    }
88
89    let socket_path = paths::socket_path(session);
90    let pid_path = paths::pid_path(session);
91
92    // Ensure dirs exist
93    let socket_dir = paths::socket_base_dir();
94    let _ = std::fs::create_dir_all(&socket_dir);
95    #[cfg(unix)]
96    {
97        use std::os::unix::fs::PermissionsExt;
98        let _ = std::fs::set_permissions(&socket_dir, std::fs::Permissions::from_mode(0o700));
99    }
100    let state = paths::state_dir(session);
101    let _ = std::fs::create_dir_all(state.join("logs"));
102
103    // Write PID file
104    if let Ok(mut f) = std::fs::File::create(&pid_path) {
105        use std::io::Write;
106        let _ = writeln!(f, "{}", std::process::id());
107    }
108
109    super::server::run(session, &socket_path).await;
110
111    let _ = std::fs::remove_file(&socket_path);
112    let _ = std::fs::remove_file(&pid_path);
113}