From 788c7207c6ac7fcdf0621739cce7dc9a642e7f88 Mon Sep 17 00:00:00 2001 From: Kevin McConnell Date: Mon, 3 Aug 2026 17:28:43 +0100 Subject: [PATCH] Add `once background status` --- internal/command/background.go | 1 + internal/command/background_status.go | 62 +++++++++++++++++++++++++++ internal/service/launchd.go | 4 ++ internal/service/service.go | 1 + internal/service/systemd.go | 4 ++ 5 files changed, 72 insertions(+) create mode 100644 internal/command/background_status.go diff --git a/internal/command/background.go b/internal/command/background.go index 53b1f8f..d269416 100644 --- a/internal/command/background.go +++ b/internal/command/background.go @@ -15,6 +15,7 @@ func newBackgroundCommand() *backgroundCommand { b.cmd.AddCommand(newBackgroundInstallCommand().cmd) b.cmd.AddCommand(newBackgroundUninstallCommand().cmd) + b.cmd.AddCommand(newBackgroundStatusCommand().cmd) b.cmd.AddCommand(newBackgroundRunCommand().cmd) return b diff --git a/internal/command/background_status.go b/internal/command/background_status.go new file mode 100644 index 0000000..ed1dfb3 --- /dev/null +++ b/internal/command/background_status.go @@ -0,0 +1,62 @@ +package command + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + + "github.com/basecamp/once/internal/service" +) + +type backgroundStatusCommand struct { + cmd *cobra.Command +} + +func newBackgroundStatusCommand() *backgroundStatusCommand { + b := &backgroundStatusCommand{} + b.cmd = &cobra.Command{ + Use: "status", + Short: "Show whether the background tasks service is installed and running", + Args: cobra.NoArgs, + RunE: b.run, + } + return b +} + +// Private + +func (b *backgroundStatusCommand) run(cmd *cobra.Command, args []string) error { + ctx := context.Background() + namespace := namespaceFlag(cmd) + + svc, err := service.New() + if err != nil { + return err + } + + serviceName := namespace + backgroundServiceSuffix + + switch { + case !svc.IsInstalled(serviceName): + fmt.Printf("Service %s is not installed\n", svc.ServiceName(serviceName)) + case svc.IsRunning(ctx, serviceName): + fmt.Printf("Service %s is installed and running\n", svc.ServiceName(serviceName)) + return nil + default: + fmt.Printf("Service %s is installed but not running\n", svc.ServiceName(serviceName)) + } + + cmd.SilenceErrors = true + return notRunningError{} +} + +type notRunningError struct{} + +func (notRunningError) Error() string { + return "service is not running" +} + +func (notRunningError) ExitCode() int { + return 3 +} diff --git a/internal/service/launchd.go b/internal/service/launchd.go index 565ab0d..0d20935 100644 --- a/internal/service/launchd.go +++ b/internal/service/launchd.go @@ -46,6 +46,10 @@ func (l *Launchd) IsInstalled(name string) bool { return err == nil } +func (l *Launchd) IsRunning(ctx context.Context, name string) bool { + return exec.CommandContext(ctx, "launchctl", "print", "system/"+l.label(name)).Run() == nil +} + func (l *Launchd) Install(ctx context.Context, name, execPath, namespace string) error { label := l.label(name) path := l.plistPath(name) diff --git a/internal/service/service.go b/internal/service/service.go index 12e7a4c..b285c77 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -8,6 +8,7 @@ import ( type Service interface { IsInstalled(name string) bool + IsRunning(ctx context.Context, name string) bool Install(ctx context.Context, name, execPath, namespace string) error Remove(ctx context.Context, name string) error ServiceName(name string) string diff --git a/internal/service/systemd.go b/internal/service/systemd.go index 81a40b8..74eaa09 100644 --- a/internal/service/systemd.go +++ b/internal/service/systemd.go @@ -33,6 +33,10 @@ func (s *Systemd) IsInstalled(name string) bool { return err == nil } +func (s *Systemd) IsRunning(ctx context.Context, name string) bool { + return exec.CommandContext(ctx, "systemctl", "is-active", "--quiet", name).Run() == nil +} + func (s *Systemd) Install(ctx context.Context, name, execPath, namespace string) error { path := s.unitFilePath(name)