2026-10-24 09:49:16 +02:00
|
|
|
package daemon
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"os"
|
|
|
|
|
"os/exec"
|
|
|
|
|
)
|
|
|
|
|
|
2026-10-24 11:15:35 +02:00
|
|
|
// 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).
|
2026-10-24 09:49:16 +02:00
|
|
|
//
|
2026-10-24 11:15:35 +02:00
|
|
|
// 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).
|
2026-10-24 09:49:16 +02:00
|
|
|
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 {
|
2026-10-24 11:15:35 +02:00
|
|
|
return nil, fmt.Errorf("daemon log %s: %w", logPath, err)
|
2026-10-24 09:49:16 +02:00
|
|
|
}
|
2026-10-24 11:15:35 +02:00
|
|
|
defer logFile.Close() // the child holds its own handle
|
2026-10-24 09:49:16 +02:00
|
|
|
|
|
|
|
|
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 {
|
2026-10-24 11:15:35 +02:00
|
|
|
return nil, fmt.Errorf("starting daemon (%s daemon run): %w", exe, err)
|
2026-10-24 09:49:16 +02:00
|
|
|
}
|
|
|
|
|
exited := make(chan error, 1)
|
|
|
|
|
go func() { exited <- cmd.Wait() }()
|
|
|
|
|
return exited, nil
|
|
|
|
|
}
|