internal/daemon/systemd.go (114 lines of code) (raw):
//go:build linux
package daemon
import (
"context"
"fmt"
"github.com/coreos/go-systemd/v22/dbus"
)
var _ DaemonManager = &systemdDaemonManager{}
type systemdDaemonManager struct {
conn *dbus.Conn
}
const (
ModeReplace = "replace"
TypeSymlink = "symlink"
TypeUnlink = "unlink"
)
func NewDaemonManager() (DaemonManager, error) {
conn, err := dbus.NewWithContext(context.Background())
if err != nil {
return nil, err
}
return &systemdDaemonManager{
conn: conn,
}, nil
}
// DaemonReload instructs systemd to scan and reload all unit files
func (m *systemdDaemonManager) DaemonReload() error {
return m.conn.ReloadContext(context.TODO())
}
func (m *systemdDaemonManager) StartDaemon(name string) error {
unitName := getServiceUnitName(name)
_, err := m.conn.StartUnitContext(context.TODO(), unitName, ModeReplace, nil)
return err
}
func (m *systemdDaemonManager) StopDaemon(name string) error {
unitName := getServiceUnitName(name)
status, err := m.GetDaemonStatus(name)
if err != nil {
return err
}
if status == DaemonStatusRunning {
_, err := m.conn.StopUnitContext(context.TODO(), unitName, ModeReplace, nil)
return err
}
return nil
}
func (m *systemdDaemonManager) RestartDaemon(ctx context.Context, name string, opts ...OperationOption) error {
o := &OperationOptions{
Mode: ModeReplace,
}
for _, opt := range opts {
opt(o)
}
resultChan := prepareResultChan(o)
unitName := getServiceUnitName(name)
_, err := m.conn.RestartUnitContext(ctx, unitName, o.Mode, resultChan)
return err
}
func (m *systemdDaemonManager) GetDaemonStatus(name string) (DaemonStatus, error) {
// TODO(g-gaston): this should take a context to it can be cancelled from the caller
unitName := getServiceUnitName(name)
status, err := m.conn.GetUnitPropertyContext(context.TODO(), unitName, "ActiveState")
if err != nil {
return DaemonStatusUnknown, err
}
switch status.Value.String() {
case "\"active\"":
return DaemonStatusRunning, nil
case "\"inactive\"":
return DaemonStatusStopped, nil
default:
return DaemonStatusUnknown, nil
}
}
// EnableDaemon enables the daemon with the given name.
// If the daemon is already enabled, this is a no-op.
func (m *systemdDaemonManager) EnableDaemon(name string) error {
unitName := getServiceUnitName(name)
_, changes, err := m.conn.EnableUnitFilesContext(context.TODO(), []string{unitName}, false, false)
if err != nil {
return err
}
if len(changes) != 0 && changes[0].Type != TypeSymlink {
return fmt.Errorf("unexpected unit file change type: %s", changes[0].Type)
}
return nil
}
func (m *systemdDaemonManager) DisableDaemon(name string) error {
unitName := getServiceUnitName(name)
changes, err := m.conn.DisableUnitFilesContext(context.TODO(), []string{unitName}, false)
if err != nil {
return err
}
if len(changes) != 1 {
return fmt.Errorf("unexpected number of unit file changes: %d", len(changes))
}
if changes[0].Type != TypeUnlink {
return fmt.Errorf("unexpected unit file change type: %s", changes[0].Type)
}
return nil
}
func (m *systemdDaemonManager) Close() {
m.conn.Close()
}
func getServiceUnitName(name string) string {
return fmt.Sprintf("%s.service", name)
}
func prepareResultChan(o *OperationOptions) chan string {
var resultChan chan string
if o.Result != nil {
resultChan = make(chan string)
go func() {
r := <-resultChan
o.Result <- OperationResult(r)
close(resultChan)
}()
}
return resultChan
}