package daemon import ( "bufio" "fmt" "log" "os/exec" "sync" "syscall" "time" "git.k7n.net/mats/go-telldus/pkg/telldus" ) // Manager handles the telldusd daemon lifecycle type Manager struct { cmd *exec.Cmd mu sync.Mutex } // New creates a new daemon manager func New() *Manager { return &Manager{} } // Start starts the telldusd daemon and captures its output func (m *Manager) Start() error { m.mu.Lock() defer m.mu.Unlock() if m.cmd != nil && m.cmd.Process != nil { log.Println("Telldusd already running") return nil } log.Println("Starting telldusd...") cmd := exec.Command("/usr/local/sbin/telldusd", "--nodaemon") // Capture stdout stdout, err := cmd.StdoutPipe() if err != nil { return fmt.Errorf("failed to get stdout pipe: %v", err) } // Capture stderr stderr, err := cmd.StderrPipe() if err != nil { return fmt.Errorf("failed to get stderr pipe: %v", err) } // Start the command if err := cmd.Start(); err != nil { return fmt.Errorf("failed to start telldusd: %v", err) } m.cmd = cmd // Log stdout in a goroutine go func() { scanner := bufio.NewScanner(stdout) for scanner.Scan() { log.Printf("[telldusd] %s", scanner.Text()) } }() // Log stderr in a goroutine go func() { scanner := bufio.NewScanner(stderr) for scanner.Scan() { log.Printf("[telldusd] ERROR: %s", scanner.Text()) } }() // Monitor process in a goroutine go func() { err := cmd.Wait() m.mu.Lock() m.cmd = nil m.mu.Unlock() if err != nil { log.Printf("Telldusd exited with error: %v", err) } else { log.Println("Telldusd exited normally") } }() // Give telldusd a moment to start time.Sleep(500 * time.Millisecond) log.Println("Telldusd started successfully") return nil } // Stop stops the telldusd daemon func (m *Manager) Stop() error { m.mu.Lock() defer m.mu.Unlock() if m.cmd == nil || m.cmd.Process == nil { log.Println("Telldusd not running") return nil } log.Println("Stopping telldusd...") // Send SIGTERM if err := m.cmd.Process.Signal(syscall.SIGTERM); err != nil { log.Printf("Failed to send SIGTERM to telldusd: %v", err) // Try SIGKILL as fallback if err := m.cmd.Process.Kill(); err != nil { return fmt.Errorf("failed to kill telldusd: %v", err) } } // Wait for process to exit (with timeout) done := make(chan error, 1) go func() { done <- m.cmd.Wait() }() select { case <-done: log.Println("Telldusd stopped successfully") case <-time.After(5 * time.Second): log.Println("Telldusd did not stop gracefully, killing...") m.cmd.Process.Kill() } m.cmd = nil return nil } // Restart restarts the telldusd daemon func (m *Manager) Restart() error { log.Println("Restarting telldusd due to configuration change...") if err := m.Stop(); err != nil { log.Printf("Error stopping telldusd: %v", err) } // Give it a moment to fully stop time.Sleep(1 * time.Second) // Close and reinitialize telldus library telldus.Close() time.Sleep(500 * time.Millisecond) if err := m.Start(); err != nil { return err } // Reinitialize telldus library telldus.Init() log.Println("Telldusd restarted successfully") return nil }