mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2025-12-20 01:11:03 +08:00
Pull request 2490: AGDNS-2966-ossvc
Some checks failed
build / test (macOS-latest) (push) Has been cancelled
build / test (ubuntu-latest) (push) Has been cancelled
build / test (windows-latest) (push) Has been cancelled
build / build-release (push) Has been cancelled
build / notify (push) Has been cancelled
lint / go-lint (push) Has been cancelled
lint / eslint (push) Has been cancelled
lint / notify (push) Has been cancelled
Some checks failed
build / test (macOS-latest) (push) Has been cancelled
build / test (ubuntu-latest) (push) Has been cancelled
build / test (windows-latest) (push) Has been cancelled
build / build-release (push) Has been cancelled
build / notify (push) Has been cancelled
lint / go-lint (push) Has been cancelled
lint / eslint (push) Has been cancelled
lint / notify (push) Has been cancelled
Squashed commit of the following: commit 444c1d8fba64271f0348eaa5d979086b0f383eca Merge: 79acd1e3e68fac0147Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Tue Oct 7 16:31:18 2025 +0300 Merge branch 'master' into AGDNS-2966-ossvc commit 79acd1e3e7958ab7c697b4726ec9e92d83186170 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Tue Oct 7 16:04:14 2025 +0300 ossvc: imp log, doc commitbd030f01a3Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Mon Oct 6 18:31:15 2025 +0300 ossvc: imp docs commitff41521552Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Mon Oct 6 14:03:19 2025 +0300 ossvc: imp docs, code commitb097b19d0aAuthor: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Thu Oct 2 19:52:45 2025 +0300 ossvc: imp code, docs commitaf5c1b61c5Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Mon Sep 29 15:35:11 2025 +0300 ossvc: introduce package
This commit is contained in:
28
internal/ossvc/action.go
Normal file
28
internal/ossvc/action.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package ossvc
|
||||
|
||||
// ActionName is the type for actions' names. It has the following valid
|
||||
// values:
|
||||
// - [ActionNameInstall]
|
||||
// - [ActionNameReload]
|
||||
// - [ActionNameStart]
|
||||
// - [ActionNameStop]
|
||||
// - [ActionNameUninstall]
|
||||
type ActionName string
|
||||
|
||||
const (
|
||||
ActionNameInstall ActionName = "install"
|
||||
ActionNameReload ActionName = "reload"
|
||||
ActionNameStart ActionName = "start"
|
||||
ActionNameStop ActionName = "stop"
|
||||
ActionNameUninstall ActionName = "uninstall"
|
||||
)
|
||||
|
||||
// Action is the interface for actions that can be performed by [Manager].
|
||||
type Action interface {
|
||||
// Name returns the name of the action.
|
||||
Name() (name ActionName)
|
||||
|
||||
// isAction is a marker method to prevent types from other packages from
|
||||
// implementing this interface.
|
||||
isAction()
|
||||
}
|
||||
80
internal/ossvc/defaultaction.go
Normal file
80
internal/ossvc/defaultaction.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package ossvc
|
||||
|
||||
import "github.com/kardianos/service"
|
||||
|
||||
// TODO(e.burkov): Declare actions for each OS.
|
||||
|
||||
// ActionInstall is the implementation of the [Action] interface.
|
||||
type ActionInstall struct {
|
||||
// ServiceConf is the configuration for the service to control.
|
||||
//
|
||||
// TODO(e.burkov): Get rid of github.com/kardianos/service dependency and
|
||||
// replace with the actual configuration.
|
||||
ServiceConf *service.Config
|
||||
}
|
||||
|
||||
// Name implements the [Action] interface for *ActionInstall.
|
||||
func (a *ActionInstall) Name() (name ActionName) { return ActionNameInstall }
|
||||
|
||||
// isAction implements the [Action] interface for *ActionInstall.
|
||||
func (a *ActionInstall) isAction() {}
|
||||
|
||||
// ActionReload is the implementation of the [Action] interface.
|
||||
type ActionReload struct {
|
||||
// ServiceConf is the configuration for the service to control.
|
||||
//
|
||||
// TODO(e.burkov): Get rid of github.com/kardianos/service dependency and
|
||||
// replace with the actual configuration.
|
||||
ServiceConf *service.Config
|
||||
}
|
||||
|
||||
// Name implements the [Action] interface for *ActionReload.
|
||||
func (a *ActionReload) Name() (name ActionName) { return ActionNameReload }
|
||||
|
||||
// isAction implements the [Action] interface for *ActionReload.
|
||||
func (a *ActionReload) isAction() {}
|
||||
|
||||
// ActionStart is the implementation of the [Action] interface.
|
||||
type ActionStart struct {
|
||||
// ServiceConf is the configuration for the service to control.
|
||||
//
|
||||
// TODO(e.burkov): Get rid of github.com/kardianos/service dependency and
|
||||
// replace with the actual configuration.
|
||||
ServiceConf *service.Config
|
||||
}
|
||||
|
||||
// Name implements the [Action] interface for *ActionStart.
|
||||
func (a *ActionStart) Name() (name ActionName) { return ActionNameStart }
|
||||
|
||||
// isAction implements the [Action] interface for *ActionStart.
|
||||
func (a *ActionStart) isAction() {}
|
||||
|
||||
// ActionStop is the implementation of the [Action] interface.
|
||||
type ActionStop struct {
|
||||
// ServiceConf is the configuration for the service to control.
|
||||
//
|
||||
// TODO(e.burkov): Get rid of github.com/kardianos/service dependency and
|
||||
// replace with the actual configuration.
|
||||
ServiceConf *service.Config
|
||||
}
|
||||
|
||||
// Name implements the [Action] interface for *ActionStop.
|
||||
func (a *ActionStop) Name() (name ActionName) { return ActionNameStop }
|
||||
|
||||
// isAction implements the [Action] interface for *ActionStop.
|
||||
func (a *ActionStop) isAction() {}
|
||||
|
||||
// ActionUninstall is the implementation of the [Action] interface.
|
||||
type ActionUninstall struct {
|
||||
// ServiceConf is the configuration for the service to control.
|
||||
//
|
||||
// TODO(e.burkov): Get rid of github.com/kardianos/service dependency and
|
||||
// replace with the actual configuration.
|
||||
ServiceConf *service.Config
|
||||
}
|
||||
|
||||
// Name implements the [Action] interface for *ActionUninstall.
|
||||
func (a *ActionUninstall) Name() (name ActionName) { return ActionNameUninstall }
|
||||
|
||||
// isAction implements the [Action] interface for *ActionUninstall.
|
||||
func (a *ActionUninstall) isAction() {}
|
||||
139
internal/ossvc/defaultmanager.go
Normal file
139
internal/ossvc/defaultmanager.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package ossvc
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/kardianos/service"
|
||||
)
|
||||
|
||||
// TODO(e.burkov): Declare managers for each OS.
|
||||
|
||||
// manager is the implementation of [Manager] that wraps [service.Service].
|
||||
type manager struct {
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
// newManager creates a new [Manager] that wraps [service.Service].
|
||||
//
|
||||
// TODO(e.burkov): Return error.
|
||||
func newManager(_ context.Context, conf *ManagerConfig) (mgr *manager) {
|
||||
return &manager{
|
||||
logger: conf.Logger,
|
||||
}
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ Manager = (*manager)(nil)
|
||||
|
||||
// Perform implements the [Manager] interface for *manager.
|
||||
func (m *manager) Perform(ctx context.Context, action Action) (err error) {
|
||||
switch action := action.(type) {
|
||||
case *ActionInstall:
|
||||
return m.install(ctx, action)
|
||||
case *ActionReload:
|
||||
return m.reload(ctx, action)
|
||||
case *ActionStart:
|
||||
return m.start(ctx, action)
|
||||
case *ActionStop:
|
||||
return m.stop(ctx, action)
|
||||
case *ActionUninstall:
|
||||
return m.uninstall(ctx, action)
|
||||
default:
|
||||
return fmt.Errorf("action: %w: %T(%[2]v)", errors.ErrBadEnumValue, action)
|
||||
}
|
||||
}
|
||||
|
||||
// install installs the service in the service manager.
|
||||
func (m *manager) install(ctx context.Context, action *ActionInstall) (err error) {
|
||||
m.logger.InfoContext(ctx, "installing service", "name", action.ServiceConf.Name)
|
||||
|
||||
s, err := service.New(nil, action.ServiceConf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating service: %w", err)
|
||||
}
|
||||
|
||||
return s.Install()
|
||||
}
|
||||
|
||||
// reload stops, if not yet, and starts the configured service in the service
|
||||
// manager.
|
||||
func (m *manager) reload(ctx context.Context, action *ActionReload) (err error) {
|
||||
m.logger.InfoContext(ctx, "reloading service", "name", action.ServiceConf.Name)
|
||||
|
||||
s, err := service.New(nil, action.ServiceConf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating service: %w", err)
|
||||
}
|
||||
|
||||
return s.Restart()
|
||||
}
|
||||
|
||||
// start starts the configured service in the service manager.
|
||||
func (m *manager) start(ctx context.Context, action *ActionStart) (err error) {
|
||||
m.logger.InfoContext(ctx, "starting service", "name", action.ServiceConf.Name)
|
||||
|
||||
s, err := service.New(nil, action.ServiceConf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating service: %w", err)
|
||||
}
|
||||
|
||||
return s.Start()
|
||||
}
|
||||
|
||||
// Status implements the [Manager] interface for *manager.
|
||||
func (m *manager) Status(ctx context.Context, name ServiceName) (status Status, err error) {
|
||||
m.logger.InfoContext(ctx, "getting service status", "name", name)
|
||||
|
||||
s, err := service.New(nil, &service.Config{
|
||||
Name: string(name),
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("creating service: %w", err)
|
||||
}
|
||||
|
||||
svcStatus, err := s.Status()
|
||||
if err != nil {
|
||||
if errors.Is(err, service.ErrNotInstalled) {
|
||||
return StatusNotInstalled, nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("getting service status: %w", err)
|
||||
}
|
||||
|
||||
switch svcStatus {
|
||||
case service.StatusRunning:
|
||||
return StatusRunning, nil
|
||||
case service.StatusStopped:
|
||||
return StatusStopped, nil
|
||||
default:
|
||||
return "", fmt.Errorf("service status: %w: %v", errors.ErrBadEnumValue, svcStatus)
|
||||
}
|
||||
}
|
||||
|
||||
// stop stops the service in the service manager.
|
||||
func (m *manager) stop(ctx context.Context, action *ActionStop) (err error) {
|
||||
m.logger.InfoContext(ctx, "stopping service", "name", action.ServiceConf.Name)
|
||||
|
||||
s, err := service.New(nil, action.ServiceConf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating service: %w", err)
|
||||
}
|
||||
|
||||
return s.Stop()
|
||||
}
|
||||
|
||||
// uninstall uninstalls the service from the service manager.
|
||||
func (m *manager) uninstall(ctx context.Context, action *ActionUninstall) (err error) {
|
||||
m.logger.InfoContext(ctx, "uninstalling service", "name", action.ServiceConf.Name)
|
||||
|
||||
s, err := service.New(nil, action.ServiceConf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating service: %w", err)
|
||||
}
|
||||
|
||||
return s.Uninstall()
|
||||
}
|
||||
52
internal/ossvc/manager.go
Normal file
52
internal/ossvc/manager.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package ossvc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
"github.com/AdguardTeam/golibs/osutil/executil"
|
||||
)
|
||||
|
||||
// Manager is the interface for communication with the OS service manager.
|
||||
//
|
||||
// TODO(e.burkov): Move to golibs.
|
||||
//
|
||||
// TODO(e.burkov): Use.
|
||||
type Manager interface {
|
||||
// Perform performs the specified action.
|
||||
Perform(ctx context.Context, action Action) (err error)
|
||||
|
||||
// Status returns the status of the service with the given name.
|
||||
Status(ctx context.Context, name ServiceName) (status Status, err error)
|
||||
}
|
||||
|
||||
// ManagerConfig contains the configuration for [Manager].
|
||||
type ManagerConfig struct {
|
||||
// Logger is the logger to use.
|
||||
Logger *slog.Logger
|
||||
|
||||
// CommandConstructor is the constructor to use for creating commands.
|
||||
CommandConstructor executil.CommandConstructor
|
||||
}
|
||||
|
||||
// NewManager returns a new properly initialized [Manager], appropriate for the
|
||||
// current platform.
|
||||
func NewManager(ctx context.Context, conf *ManagerConfig) (mgr Manager, err error) {
|
||||
return newManager(ctx, conf), nil
|
||||
}
|
||||
|
||||
// EmptyManager is an empty implementation of [Manager] that does nothing.
|
||||
type EmptyManager struct{}
|
||||
|
||||
// type check
|
||||
var _ Manager = EmptyManager{}
|
||||
|
||||
// Perform implements the [Manager] interface for EmptyManager.
|
||||
func (EmptyManager) Perform(_ context.Context, _ Action) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Status implements the [Manager] interface for EmptyManager.
|
||||
func (EmptyManager) Status(_ context.Context, _ ServiceName) (status Status, err error) {
|
||||
return StatusNotInstalled, nil
|
||||
}
|
||||
24
internal/ossvc/ossvc.go
Normal file
24
internal/ossvc/ossvc.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// Package ossvc contains abstractions and utilities for platform-independent
|
||||
// service management.
|
||||
//
|
||||
// TODO(e.burkov): Add tests.
|
||||
package ossvc
|
||||
|
||||
// ServiceName is the name of a service.
|
||||
//
|
||||
// TODO(e.burkov): Validate for each platform.
|
||||
type ServiceName string
|
||||
|
||||
// Status represents the status of a service.
|
||||
type Status string
|
||||
|
||||
const (
|
||||
// StatusNotInstalled means that the service is not installed.
|
||||
StatusNotInstalled Status = "not installed"
|
||||
|
||||
// StatusStopped means that the service is stopped.
|
||||
StatusStopped Status = "stopped"
|
||||
|
||||
// StatusRunning means that the service is running.
|
||||
StatusRunning Status = "running"
|
||||
)
|
||||
Reference in New Issue
Block a user