mirror of
https://github.com/lukaszraczylo/lolcathost.git
synced 2026-06-05 23:29:18 +00:00
129 lines
3.0 KiB
Go
129 lines
3.0 KiB
Go
// Package daemon provides the main daemon loop and lifecycle management.
|
|
package daemon
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/lukaszraczylo/lolcathost/internal/config"
|
|
"github.com/lukaszraczylo/lolcathost/internal/protocol"
|
|
)
|
|
|
|
// Daemon represents the lolcathost daemon.
|
|
type Daemon struct {
|
|
server *Server
|
|
config *config.Manager
|
|
stopCh chan struct{}
|
|
cleanupCh chan struct{}
|
|
}
|
|
|
|
// New creates a new daemon instance.
|
|
func New(configPath string) (*Daemon, error) {
|
|
cfgManager := config.NewManager(configPath)
|
|
|
|
// Try to load config, create default if it doesn't exist
|
|
if err := cfgManager.Load(); err != nil {
|
|
if os.IsNotExist(err) {
|
|
if err := config.CreateDefault(configPath); err != nil {
|
|
return nil, fmt.Errorf("failed to create default config: %w", err)
|
|
}
|
|
if err := cfgManager.Load(); err != nil {
|
|
return nil, fmt.Errorf("failed to load default config: %w", err)
|
|
}
|
|
} else {
|
|
return nil, fmt.Errorf("failed to load config: %w", err)
|
|
}
|
|
}
|
|
|
|
// Ensure at least one group exists
|
|
cfg := cfgManager.Get()
|
|
if cfg != nil {
|
|
cfg.EnsureDefaultGroup()
|
|
// Save if we added a default group
|
|
if len(cfg.Groups) == 1 && cfg.Groups[0].Name == "default" && len(cfg.Groups[0].Hosts) == 0 {
|
|
_ = cfgManager.Save()
|
|
}
|
|
}
|
|
|
|
server := NewServer(protocol.SocketPath, cfgManager)
|
|
|
|
return &Daemon{
|
|
server: server,
|
|
config: cfgManager,
|
|
stopCh: make(chan struct{}),
|
|
cleanupCh: make(chan struct{}),
|
|
}, nil
|
|
}
|
|
|
|
// Run starts the daemon and blocks until stopped.
|
|
func (d *Daemon) Run() error {
|
|
// Verify we're running as root
|
|
if os.Geteuid() != 0 {
|
|
return fmt.Errorf("daemon must run as root")
|
|
}
|
|
|
|
// Start the server
|
|
if err := d.server.Start(); err != nil {
|
|
return fmt.Errorf("failed to start server: %w", err)
|
|
}
|
|
|
|
// Watch config for changes
|
|
if err := d.config.Watch(d.onConfigChange); err != nil {
|
|
fmt.Fprintf(os.Stderr, "warning: failed to watch config: %v\n", err)
|
|
}
|
|
|
|
// Start cleanup goroutine
|
|
go d.cleanupLoop()
|
|
|
|
// Wait for shutdown signal
|
|
sigCh := make(chan os.Signal, 1)
|
|
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
select {
|
|
case <-sigCh:
|
|
fmt.Println("Received shutdown signal")
|
|
case <-d.stopCh:
|
|
fmt.Println("Shutdown requested")
|
|
}
|
|
|
|
return d.shutdown()
|
|
}
|
|
|
|
func (d *Daemon) shutdown() error {
|
|
close(d.cleanupCh)
|
|
d.config.Stop()
|
|
|
|
if err := d.server.Stop(); err != nil {
|
|
return fmt.Errorf("failed to stop server: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Daemon) onConfigChange(cfg *config.Config) {
|
|
fmt.Println("Config changed, syncing hosts file...")
|
|
// The server will use the updated config on next request
|
|
// We could trigger a sync here if autoApply is enabled
|
|
if cfg != nil && cfg.Settings.AutoApply {
|
|
// Sync hosts file with new config
|
|
// This is handled by the server internally
|
|
}
|
|
}
|
|
|
|
func (d *Daemon) cleanupLoop() {
|
|
ticker := time.NewTicker(5 * time.Minute)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
d.server.rateLimiter.Cleanup()
|
|
case <-d.cleanupCh:
|
|
return
|
|
}
|
|
}
|
|
}
|