A per-DataDir background process owns ticket JSON, history, and sync so multiple TUI instances stay consistent; MCP token APIs are ready for phase 2. Co-authored-by: Cursor <cursoragent@cursor.com>
92 lines
1.9 KiB
Go
92 lines
1.9 KiB
Go
package daemon
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
)
|
|
|
|
const (
|
|
sockName = "daemon.sock"
|
|
pidName = "daemon.pid"
|
|
logName = "daemon.log"
|
|
)
|
|
|
|
// Paths holds filesystem locations for one DataDir's daemon instance.
|
|
type Paths struct {
|
|
RuntimeDir string
|
|
Socket string
|
|
PIDFile string
|
|
LogFile string
|
|
}
|
|
|
|
// PathsForDataDir returns runtime paths unique to dataDir.
|
|
func PathsForDataDir(dataDir string) (Paths, error) {
|
|
dataDir = filepath.Clean(dataDir)
|
|
sum := sha256.Sum256([]byte(dataDir))
|
|
id := hex.EncodeToString(sum[:8])
|
|
|
|
dir, err := mkdirRuntime(id, dataDir)
|
|
if err != nil {
|
|
return Paths{}, err
|
|
}
|
|
return Paths{
|
|
RuntimeDir: dir,
|
|
Socket: filepath.Join(dir, sockName),
|
|
PIDFile: filepath.Join(dir, pidName),
|
|
LogFile: filepath.Join(dir, logName),
|
|
}, nil
|
|
}
|
|
|
|
func mkdirRuntime(id, dataDir string) (string, error) {
|
|
candidates := []string{}
|
|
if r := strings.TrimSpace(os.Getenv("XDG_RUNTIME_DIR")); r != "" {
|
|
candidates = append(candidates, filepath.Join(r, "task-master-"+id))
|
|
}
|
|
candidates = append(candidates,
|
|
filepath.Join(os.TempDir(), fmt.Sprintf("task-master-%d-%s", os.Getuid(), id)),
|
|
filepath.Join(dataDir, ".runtime"),
|
|
)
|
|
var last error
|
|
for _, dir := range candidates {
|
|
if err := os.MkdirAll(dir, 0o700); err != nil {
|
|
last = err
|
|
continue
|
|
}
|
|
return dir, nil
|
|
}
|
|
if last == nil {
|
|
last = fmt.Errorf("no runtime dir candidate")
|
|
}
|
|
return "", fmt.Errorf("create runtime dir: %w", last)
|
|
}
|
|
|
|
func writePID(path string, pid int) error {
|
|
return os.WriteFile(path, []byte(strconv.Itoa(pid)+"\n"), 0o600)
|
|
}
|
|
|
|
func readPID(path string) (int, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
s := strings.TrimSpace(string(data))
|
|
if s == "" {
|
|
return 0, fmt.Errorf("empty pid file")
|
|
}
|
|
return strconv.Atoi(s)
|
|
}
|
|
|
|
func processAlive(pid int) bool {
|
|
if pid <= 0 {
|
|
return false
|
|
}
|
|
err := syscall.Kill(pid, 0)
|
|
return err == nil
|
|
}
|