This commit is contained in:
Mats Tornberg
2025-11-22 17:35:24 +01:00
parent 0596fe60fa
commit 87660c7d1d
12 changed files with 34 additions and 39 deletions

View File

@@ -0,0 +1,156 @@
package daemon
import (
"bufio"
"fmt"
"log"
"os/exec"
"sync"
"syscall"
"time"
"app/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
}

View File

@@ -0,0 +1,63 @@
package daemon
import (
"log"
"path/filepath"
"github.com/fsnotify/fsnotify"
)
// Watcher watches for configuration file changes
type Watcher struct {
configPath string
onReloadFunc func() error
}
// NewWatcher creates a new config file watcher
func NewWatcher(configPath string, onReload func() error) *Watcher {
return &Watcher{
configPath: configPath,
onReloadFunc: onReload,
}
}
// Watch starts watching for changes to the configuration file
func (w *Watcher) Watch() error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}
defer watcher.Close()
// Watch the parent directory since file operations might replace the file
configDir := filepath.Dir(w.configPath)
if err := watcher.Add(configDir); err != nil {
return err
}
log.Printf("Watching for changes to %s", w.configPath)
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return nil
}
// Check if the event is for our config file
if event.Name == w.configPath && (event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create) {
log.Printf("Configuration file changed: %s", event.Op.String())
// Call reload callback if provided
if w.onReloadFunc != nil {
if err := w.onReloadFunc(); err != nil {
log.Printf("Failed to reload devices: %v", err)
}
}
}
case err, ok := <-watcher.Errors:
if !ok {
return nil
}
log.Printf("File watcher error: %v", err)
}
}
}