main
Raw Download raw file
  1package cmd
  2
  3import (
  4	"context"
  5	"fmt"
  6	"os"
  7	"text/template"
  8	"time"
  9
 10	"github.com/coreos/go-systemd/v22/dbus"
 11	"github.com/spf13/cobra"
 12)
 13
 14func InstallCmd() *cobra.Command {
 15	return &cobra.Command{
 16		Use:   "install",
 17		Short: "install as a service",
 18		RunE:  install,
 19	}
 20}
 21
 22func install(cmd *cobra.Command, args []string) error {
 23
 24	const (
 25		_binaryName     = "mm"
 26		_installDir     = "/usr/local/sbin/"
 27		_installPath    = _installDir + _binaryName
 28		_installDirMode = 0o755
 29		_installExeMode = 0o755
 30		_configDir      = "/etc/"
 31		_configPath     = _configDir + _binaryName + ".conf"
 32		_configMode     = 0o400
 33	)
 34
 35	exePath, err := os.Executable()
 36	if err != nil {
 37		return fmt.Errorf("failed to find self binary: %w", err)
 38	}
 39	exeBytes, err := os.ReadFile(exePath)
 40	if err != nil {
 41		return fmt.Errorf("failed to read self binary: %w", err)
 42	}
 43
 44	err = os.MkdirAll(_installDir, _installDirMode)
 45	if err != nil {
 46		return fmt.Errorf(
 47			"failed to create install dir=%q: %w",
 48			_installDir, err)
 49	}
 50	err = os.WriteFile(_installPath, exeBytes, _installExeMode)
 51	if err != nil {
 52		return fmt.Errorf(
 53			"failed to write binary path=%q: %w",
 54			_installPath, err)
 55	}
 56
 57	const (
 58		_serviceDir  = "/etc/systemd/system/"
 59		_serviceName = _binaryName + ".service"
 60		_servicePath = _serviceDir + _serviceName
 61	)
 62
 63	unitFile, err := os.Create(_servicePath)
 64	if err != nil {
 65		return fmt.Errorf(
 66			"failed to create service file path=%q: %w",
 67			_servicePath, err)
 68	}
 69	tmpl := template.Must(template.New("unit").Parse(unitTemplate))
 70	err = tmpl.Execute(unitFile, struct {
 71		Name     string
 72		ExecPath string
 73		EnvPath  string
 74	}{
 75		Name:     _binaryName,
 76		ExecPath: _installPath,
 77		EnvPath:  _configPath,
 78	})
 79	if err != nil {
 80		return fmt.Errorf(
 81			"failed to template service file path=%q: %w",
 82			_servicePath, err)
 83	}
 84
 85	ctx, cancel := context.WithTimeout(
 86		context.Background(), 10*time.Second)
 87	defer cancel()
 88
 89	conn, err := dbus.NewSystemdConnectionContext(ctx)
 90	if err != nil {
 91		return fmt.Errorf("failed to connect to systemd: %w", err)
 92	}
 93	defer conn.Close()
 94
 95	err = conn.ReloadContext(ctx)
 96	if err != nil {
 97		return fmt.Errorf("failed to reload systemd: %w", err)
 98	}
 99
100	runtimeOnly := false
101	replaceExisting := true
102	_, _, err = conn.EnableUnitFilesContext(
103		ctx, []string{_serviceName},
104		runtimeOnly, replaceExisting)
105	if err != nil {
106		return fmt.Errorf("failed to enable service name=%q: %w",
107			_serviceName, err)
108	}
109
110	startChan := make(chan string, 1)
111	_, err = conn.StartUnitContext(ctx, _serviceName, "replace", startChan)
112	if err != nil {
113		return fmt.Errorf("failed to start service name=%q: %w",
114			_serviceName, err)
115	}
116
117	select {
118	case <-startChan:
119		break
120	case <-ctx.Done():
121		return fmt.Errorf("timed out starting service")
122	}
123
124	err = os.Remove(exePath)
125	if err != nil {
126		return fmt.Errorf("failed to delete self binary: %w", err)
127	}
128	return nil
129}
130
131var unitTemplate = `
132[Unit]
133Description={{.Name}} service
134After=network.target
135
136[Service]
137Type=simple
138ExecStart={{.ExecPath}}
139EnvironmentFile={{.EnvPath}}
140Restart=on-failure
141RestartSec=5
142
143[Install]
144WantedBy=multi-user.target
145`