package daemon import ( "context" "fmt" "net" "net/http" "os" "os/signal" "syscall" "time" "task/internal/command" "task/internal/config" "task/internal/daemon/api" "task/internal/project" "task/internal/ticket" ) // Run starts the foreground daemon for cfg until signal or context cancel. func Run(cfg *config.Config) error { abs, err := AbsoluteDataDir(cfg.DataDir) if err != nil { return err } cfg.DataDir = abs p, err := PathsForDataDir(cfg.DataDir) if err != nil { return err } cleanupStale(p) store := ticket.NewJSONStore(cfg.DataDir) timeStore := ticket.NewJSONTimeStore(cfg.DataDir) projects := project.NewDirStore(cfg.DataDir) history, err := command.OpenHistory(command.HistoryPath(cfg.DataDir), store, timeStore) if err != nil { return fmt.Errorf("history: %w", err) } srvAPI := api.New(store, timeStore, projects, history) server := &http.Server{Handler: srvAPI.Handler()} ln, err := net.Listen("unix", p.Socket) if err != nil { return fmt.Errorf("listen %s: %w", p.Socket, err) } if err := os.Chmod(p.Socket, 0o600); err != nil { _ = ln.Close() return fmt.Errorf("chmod socket: %w", err) } if err := writePID(p.PIDFile, os.Getpid()); err != nil { _ = ln.Close() return fmt.Errorf("pid file: %w", err) } defer cleanupStale(p) errCh := make(chan error, 1) go func() { errCh <- server.Serve(ln) }() sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) select { case <-sigCh: ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() _ = server.Shutdown(ctx) return nil case err := <-errCh: if err == http.ErrServerClosed { return nil } return err } }