forked from ryyst/kalzu-value-store
Add systemd-style subcommands for managing KVS instances: - start <config> - Daemonize and run in background - stop <config> - Gracefully stop daemon - restart <config> - Restart daemon - status [config] - Show status of all or specific instances Key features: - PID files stored in ~/.kvs/pids/ (global across all directories) - Logs stored in ~/.kvs/logs/ - Config names support both 'node1' and 'node1.yaml' formats - Backward compatible: 'kvs config.yaml' still runs in foreground - Proper stale PID detection and cleanup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
88 lines
2.2 KiB
Go
88 lines
2.2 KiB
Go
package daemon
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"syscall"
|
|
)
|
|
|
|
// GetLogFilePath returns the log file path for a given config file
|
|
func GetLogFilePath(configPath string) (string, error) {
|
|
logDir, err := getLogDir()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
absConfigPath, err := filepath.Abs(configPath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get absolute config path: %w", err)
|
|
}
|
|
|
|
basename := filepath.Base(configPath)
|
|
name := filepath.Base(filepath.Dir(absConfigPath)) + "_" + basename
|
|
return filepath.Join(logDir, name+".log"), nil
|
|
}
|
|
|
|
// Daemonize spawns the process as a daemon and returns
|
|
func Daemonize(configPath string) error {
|
|
// Get absolute path to the current executable
|
|
executable, err := os.Executable()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get executable path: %w", err)
|
|
}
|
|
|
|
// Get absolute path to config
|
|
absConfigPath, err := filepath.Abs(configPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get absolute config path: %w", err)
|
|
}
|
|
|
|
// Check if already running
|
|
_, running, err := ReadPID(configPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check if instance is running: %w", err)
|
|
}
|
|
if running {
|
|
return fmt.Errorf("instance is already running")
|
|
}
|
|
|
|
// Spawn the process in background with --daemon flag
|
|
cmd := exec.Command(executable, "--daemon", absConfigPath)
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
|
Setsid: true, // Create new session
|
|
}
|
|
|
|
// Redirect stdout/stderr to log file
|
|
logDir, err := getLogDir()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get log directory: %w", err)
|
|
}
|
|
if err := os.MkdirAll(logDir, 0755); err != nil {
|
|
return fmt.Errorf("failed to create log directory: %w", err)
|
|
}
|
|
|
|
basename := filepath.Base(configPath)
|
|
name := filepath.Base(filepath.Dir(absConfigPath)) + "_" + basename
|
|
logFile := filepath.Join(logDir, name+".log")
|
|
|
|
f, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open log file: %w", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
cmd.Stdout = f
|
|
cmd.Stderr = f
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
return fmt.Errorf("failed to start daemon: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Started KVS instance '%s' (PID will be written by daemon)\n", filepath.Base(configPath))
|
|
fmt.Printf("Logs: %s\n", logFile)
|
|
|
|
return nil
|
|
}
|