package daemon import ( "fmt" "os" "os/exec" ) // Spawn starts `exe daemon run` as an independent background process (own // session or own process group, detached from the terminal), with // stdout/stderr going to the log file logPath (created fresh per start; // empty = discard). env is the child's environment (keys, TALK_* // overrides). // // The return value is a channel that delivers the Wait result exactly once // when the child exits; the caller uses it to detect a failed start (e.g. // port in use) without waiting for a timeout. If the caller exits first, // the daemon keeps running (that is the point). func Spawn(exe, logPath string, env []string) (<-chan error, error) { var logFile *os.File if logPath == "" { logPath = os.DevNull } logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644) if err != nil { return nil, fmt.Errorf("daemon log %s: %w", logPath, err) } defer logFile.Close() // the child holds its own handle cmd := exec.Command(exe, "daemon", "run") cmd.Env = env cmd.Stdin = nil cmd.Stdout = logFile cmd.Stderr = logFile detach(cmd) if err := cmd.Start(); err != nil { return nil, fmt.Errorf("starting daemon (%s daemon run): %w", exe, err) } exited := make(chan error, 1) go func() { exited <- cmd.Wait() }() return exited, nil }