package shared import ( "context" "errors" "fmt" "os" "os/signal" "sync" "syscall" "time" "git.nevets.tech/Keys/certman/common" "github.com/spf13/cobra" ) type Daemon interface { Init() Tick() Reload() Stop() } var ( ctx context.Context cancel context.CancelFunc wg sync.WaitGroup DaemonCmd = &cobra.Command{ Use: "daemon", Short: "Daemon management", RunE: func(cmd *cobra.Command, args []string) error { return cmd.Help() }, } ) func init() { DaemonCmd.AddCommand(&cobra.Command{ Use: "stop", Short: "stop the daemon", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { return stopDaemonCmd() }, }) DaemonCmd.AddCommand(&cobra.Command{ Use: "reload", Short: "reload daemon configs", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { return reloadDaemonCmd() }, }) DaemonCmd.AddCommand(&cobra.Command{ Use: "tick", Short: "Manually triggers daemon tick", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { return tickDaemonCmd() }, }) DaemonCmd.AddCommand(&cobra.Command{ Use: "status", Short: "Show daemon status", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { return daemonStatusCmd() }, }) } func RunDaemonCmd(daemon Daemon) error { err := common.CreateOrUpdatePIDFile("/var/run/certman.pid") if err != nil { if errors.Is(err, common.ErrorPIDInUse) { return fmt.Errorf("daemon process is already running") } return fmt.Errorf("error creating pidfile: %v", err) } ctx, cancel = context.WithCancel(context.Background()) // Check if main config exists if _, err := os.Stat("/etc/certman/certman.conf"); os.IsNotExist(err) { return fmt.Errorf("main config file not found, please run 'certman --install', then properly configure /etc/certman/certman.conf") } else if err != nil { return fmt.Errorf("error opening /etc/certman/certman.conf: %v", err) } err = LoadConfig() if err != nil { return fmt.Errorf("error loading configuration: %v", err) } localConfig := Config() // Setup SIGINT and SIGTERM listeners sigChannel := make(chan os.Signal, 1) signal.Notify(sigChannel, syscall.SIGINT, syscall.SIGTERM) defer signal.Stop(sigChannel) reloadSigChan := make(chan os.Signal, 1) signal.Notify(reloadSigChan, syscall.SIGHUP) defer signal.Stop(reloadSigChan) tickSigChan := make(chan os.Signal, 1) signal.Notify(tickSigChan, syscall.SIGUSR1) defer signal.Stop(tickSigChan) tickRate := localConfig.App.TickRate ticker := time.NewTicker(time.Duration(tickRate) * time.Hour) defer ticker.Stop() wg.Add(1) go func() { daemon.Init() defer wg.Done() for { select { case <-ctx.Done(): daemon.Stop() return case <-reloadSigChan: daemon.Reload() case <-ticker.C: daemon.Tick() case <-tickSigChan: daemon.Tick() } } }() // Cleanup on stop sig := <-sigChannel fmt.Printf("Program terminated with %v\n", sig.String()) stop() wg.Wait() return nil } func stop() { cancel() common.ClearPIDFile() } func stopDaemonCmd() error { proc, err := common.DaemonProcess() if err != nil { return fmt.Errorf("error getting daemon process: %v", err) } err = proc.Signal(syscall.SIGTERM) if err != nil { return fmt.Errorf("error sending SIGTERM to daemon PID: %v", err) } return nil } func reloadDaemonCmd() error { proc, err := common.DaemonProcess() if err != nil { return fmt.Errorf("error getting daemon process: %v", err) } err = proc.Signal(syscall.SIGHUP) if err != nil { return fmt.Errorf("error sending SIGHUP to daemon PID: %v", err) } return nil } func tickDaemonCmd() error { proc, err := common.DaemonProcess() if err != nil { return fmt.Errorf("error getting daemon process: %v", err) } err = proc.Signal(syscall.SIGUSR1) if err != nil { return fmt.Errorf("error sending SIGUSR1 to daemon PID: %v", err) } return nil } func daemonStatusCmd() error { fmt.Println("Not implemented :/") return nil }