package main12import (3"context"4"io"5"os/exec"67"github.com/go-kit/log"8"github.com/go-kit/log/level"9)1011// serviceManager manages an individual binary.12type serviceManager struct {13log log.Logger14cfg serviceManagerConfig15}1617// serviceManagerConfig configures a service.18type serviceManagerConfig struct {19// Path to the binary to run.20Path string2122// Args of the binary to run, not including the command itself.23Args []string2425// Dir specifies the working directory to run the binary from. If Dir is26// empty, the working directory of the current process is used.27Dir string2829// Stdout and Stderr specify where the process' stdout and stderr will be30// connected.31//32// If Stdout or Stderr are nil, they will default to os.DevNull.33Stdout, Stderr io.Writer34}3536// newServiceManager creates a new, unstarted serviceManager. Call37// [service.Run] to start the serviceManager.38//39// Logs from the serviceManager will be sent to w. Logs from the managed40// service will be written to cfg.Stdout and cfg.Stderr as appropriate.41func newServiceManager(l log.Logger, cfg serviceManagerConfig) *serviceManager {42if l == nil {43l = log.NewNopLogger()44}4546return &serviceManager{47log: l,48cfg: cfg,49}50}5152// Run starts the serviceManager. The binary associated with the serviceManager53// will be run until the provided context is canceled or the binary exits.54//55// Intermediate restarts will increase with an exponential backoff, which56// resets if the binary has been running for longer than the maximum57// exponential backoff period.58func (svc *serviceManager) Run(ctx context.Context) {59cmd := svc.buildCommand(ctx)6061level.Info(svc.log).Log("msg", "starting program", "command", cmd.String())62err := cmd.Run()6364// Handle the context being canceled before processing whether cmd.Run65// exited with an error.66if ctx.Err() != nil {67return68}6970exitCode := cmd.ProcessState.ExitCode()7172if err != nil {73level.Error(svc.log).Log("msg", "service exited with error", "err", err, "exit_code", exitCode)74} else {75level.Info(svc.log).Log("msg", "service exited", "exit_code", exitCode)76}77}7879func (svc *serviceManager) buildCommand(ctx context.Context) *exec.Cmd {80cmd := exec.CommandContext(ctx, svc.cfg.Path, svc.cfg.Args...)81cmd.Dir = svc.cfg.Dir82cmd.Stdout = svc.cfg.Stdout83cmd.Stderr = svc.cfg.Stderr84return cmd85}868788