diff --git a/cmd/service.go b/cmd/service.go index e0775ca..d12bae2 100644 --- a/cmd/service.go +++ b/cmd/service.go @@ -1,11 +1,9 @@ package cmd import ( - "github.com/darki73/goflaresync/pkg/helpers" "github.com/darki73/goflaresync/pkg/log" + "github.com/darki73/goflaresync/pkg/service" "github.com/spf13/cobra" - "os" - "os/exec" ) // serviceCmd represents the service command. @@ -20,84 +18,13 @@ var serviceInstallCmd = &cobra.Command{ Short: "Installs the service", Long: "Installs the systemd service and enables it", Run: func(cmd *cobra.Command, args []string) { - checkForSystemd() - checkIfRoot() + manager := service.NewManager() + systemManager := manager.GetSystemManager() - servicePath := "/etc/systemd/system/goflaresync.service" - - if _, err := os.Stat(servicePath); err == nil { - log.WarnWithFields( - "service file already exists", - log.FieldsMap{ - "source": "main", - }, - ) - return - } - - err := os.WriteFile(servicePath, []byte(serviceContent()), 0644) - if err != nil { - log.Fatalf( - "failed to write the service file: %s", - log.FieldsMap{ - "source": "main", - }, - err.Error(), - ) - return - } - - if err := exec.Command("systemctl", "daemon-reload").Run(); err != nil { - log.Fatalf( - "failed to reload systemd: %s", - log.FieldsMap{ - "source": "main", - }, - err.Error(), - ) - } - - log.InfoWithFields( - "successfully reloaded systemd", - log.FieldsMap{ - "source": "main", - }, - ) - - if err := exec.Command("systemctl", "enable", "goflaresync").Run(); err != nil { - log.Fatalf( - "failed to enable the service: %s", - log.FieldsMap{ - "source": "main", - }, - err.Error(), - ) - } - - log.InfoWithFields( - "successfully enabled the service", - log.FieldsMap{ - "source": "main", - }, - ) - }, -} - -// serviceUninstallCmd represents the service uninstall command. -var serviceUninstallCmd = &cobra.Command{ - Use: "uninstall", - Short: "Uninstalls the service", - Long: "Disables the the systemd service and uninstalls it", - Run: func(cmd *cobra.Command, args []string) { - checkForSystemd() - checkIfRoot() - - servicePath := "/etc/systemd/system/goflaresync.service" - if _, err := os.Stat(servicePath); err == nil { - - if err := exec.Command("systemctl", "stop", "goflaresync").Run(); err != nil { - log.Fatalf( - "failed to stop the service: %s", + if systemManager != nil { + if err := systemManager.CreateService(); err != nil { + log.FatalfWithFields( + "failed to create the service: %s", log.FieldsMap{ "source": "main", }, @@ -105,33 +32,33 @@ var serviceUninstallCmd = &cobra.Command{ ) } - log.InfoWithFields( - "successfully stopped the service", - log.FieldsMap{ - "source": "main", - }, - ) - - if err := exec.Command("systemctl", "disable", "goflaresync").Run(); err != nil { - log.Fatalf( - "failed to disable the service: %s", + if err := systemManager.EnableService(); err != nil { + log.FatalfWithFields( + "failed to enable the service: %s", log.FieldsMap{ "source": "main", }, err.Error(), ) } + } + + }, +} - log.InfoWithFields( - "successfully disabled the service", - log.FieldsMap{ - "source": "main", - }, - ) +// serviceUninstallCmd represents the service uninstall command. +var serviceUninstallCmd = &cobra.Command{ + Use: "uninstall", + Short: "Uninstalls the service", + Long: "Disables the the systemd service and uninstalls it", + Run: func(cmd *cobra.Command, args []string) { + manager := service.NewManager() + systemManager := manager.GetSystemManager() - if err := os.Remove(servicePath); err != nil { + if systemManager != nil { + if err := systemManager.DisableService(); err != nil { log.FatalfWithFields( - "failed to remove the service file: %s", + "failed to disable the service: %s", log.FieldsMap{ "source": "main", }, @@ -139,29 +66,15 @@ var serviceUninstallCmd = &cobra.Command{ ) } - log.InfoWithFields( - "successfully removed the service file", - log.FieldsMap{ - "source": "main", - }, - ) - - if err := exec.Command("systemctl", "daemon-reload").Run(); err != nil { - log.Fatalf( - "failed to reload systemd: %s", + if err := systemManager.DeleteService(); err != nil { + log.FatalfWithFields( + "failed to remove the service: %s", log.FieldsMap{ "source": "main", }, err.Error(), ) } - - log.InfoWithFields( - "successfully reloaded systemd", - log.FieldsMap{ - "source": "main", - }, - ) } }, } @@ -172,22 +85,20 @@ var serviceStartCmd = &cobra.Command{ Short: "Starts the service", Long: "Starts the service", Run: func(cmd *cobra.Command, args []string) { - if err := exec.Command("systemctl", "start", "goflaresync").Run(); err != nil { - log.Fatalf( - "failed to start the service: %s", - log.FieldsMap{ - "source": "main", - }, - err.Error(), - ) - } + manager := service.NewManager() + systemManager := manager.GetSystemManager() - log.InfoWithFields( - "successfully started the service", - log.FieldsMap{ - "source": "main", - }, - ) + if systemManager != nil { + if err := systemManager.StartService(); err != nil { + log.FatalfWithFields( + "failed to start the service: %s", + log.FieldsMap{ + "source": "main", + }, + err.Error(), + ) + } + } }, } @@ -197,22 +108,20 @@ var serviceStopCmd = &cobra.Command{ Short: "Stops the service", Long: "Stops the service", Run: func(cmd *cobra.Command, args []string) { - if err := exec.Command("systemctl", "stop", "goflaresync").Run(); err != nil { - log.Fatalf( - "failed to stop the service: %s", - log.FieldsMap{ - "source": "main", - }, - err.Error(), - ) - } + manager := service.NewManager() + systemManager := manager.GetSystemManager() - log.InfoWithFields( - "successfully stopped the service", - log.FieldsMap{ - "source": "main", - }, - ) + if systemManager != nil { + if err := systemManager.StopService(); err != nil { + log.FatalfWithFields( + "failed to stop the service: %s", + log.FieldsMap{ + "source": "main", + }, + err.Error(), + ) + } + } }, } @@ -221,22 +130,20 @@ var serviceRestartCmd = &cobra.Command{ Use: "restart", Long: "Restarts the service", Run: func(cmd *cobra.Command, args []string) { - if err := exec.Command("systemctl", "restart", "goflaresync").Run(); err != nil { - log.Fatalf( - "failed to restart the service: %s", - log.FieldsMap{ - "source": "main", - }, - err.Error(), - ) - } + manager := service.NewManager() + systemManager := manager.GetSystemManager() - log.InfoWithFields( - "successfully restarted the service", - log.FieldsMap{ - "source": "main", - }, - ) + if systemManager != nil { + if err := systemManager.RestartService(); err != nil { + log.FatalfWithFields( + "failed to restart the service: %s", + log.FieldsMap{ + "source": "main", + }, + err.Error(), + ) + } + } }, } @@ -246,22 +153,20 @@ var serviceEnableCmd = &cobra.Command{ Short: "Enables the service", Long: "Enables the service", Run: func(cmd *cobra.Command, args []string) { - if err := exec.Command("systemctl", "enable", "goflaresync").Run(); err != nil { - log.Fatalf( - "failed to enable the service: %s", - log.FieldsMap{ - "source": "main", - }, - err.Error(), - ) - } + manager := service.NewManager() + systemManager := manager.GetSystemManager() - log.InfoWithFields( - "successfully enabled the service", - log.FieldsMap{ - "source": "main", - }, - ) + if systemManager != nil { + if err := systemManager.EnableService(); err != nil { + log.FatalfWithFields( + "failed to enable the service: %s", + log.FieldsMap{ + "source": "main", + }, + err.Error(), + ) + } + } }, } @@ -271,49 +176,23 @@ var serviceDisableCmd = &cobra.Command{ Short: "Disables the service", Long: "Disables the service", Run: func(cmd *cobra.Command, args []string) { - if err := exec.Command("systemctl", "disable", "goflaresync").Run(); err != nil { - log.Fatalf( - "failed to disable the service: %s", - log.FieldsMap{ - "source": "main", - }, - err.Error(), - ) - } + manager := service.NewManager() + systemManager := manager.GetSystemManager() - log.InfoWithFields( - "successfully disabled the service", - log.FieldsMap{ - "source": "main", - }, - ) + if systemManager != nil { + if err := systemManager.DisableService(); err != nil { + log.FatalfWithFields( + "failed to disable the service: %s", + log.FieldsMap{ + "source": "main", + }, + err.Error(), + ) + } + } }, } -// checkForSystemd checks if systemd is available. -func checkForSystemd() { - if !helpers.HasSystemd() { - log.FatalfWithFields( - "this system does not have systemd", - log.FieldsMap{ - "source": "main", - }, - ) - } -} - -// checkIfRoot checks if the current user is root. -func checkIfRoot() { - if !helpers.IsRoot() { - log.FatalfWithFields( - "this command must be run as root", - log.FieldsMap{ - "source": "main", - }, - ) - } -} - // init registers the command and flags. func init() { serviceCmd.AddCommand(serviceInstallCmd) @@ -325,19 +204,3 @@ func init() { serviceCmd.AddCommand(serviceDisableCmd) rootCmd.AddCommand(serviceCmd) } - -// serviceContent returns the content of the service file. -func serviceContent() string { - return `[Unit] -Description=GoFlareSync Service -After=network.target - -[Service] -Type=simple -ExecStart=/usr/bin/goflaresync start -Restart=on-failure - -[Install] -WantedBy=multi-user.target -` -} diff --git a/cmd/start.go b/cmd/start.go index 04a9892..7dfb50c 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -3,6 +3,7 @@ package cmd import ( "github.com/darki73/goflaresync/pkg/configuration" "github.com/darki73/goflaresync/pkg/log" + "github.com/darki73/goflaresync/pkg/service" "github.com/darki73/goflaresync/pkg/watcher" "github.com/spf13/cobra" ) @@ -17,6 +18,21 @@ var startCmd = &cobra.Command{ log.Fatal(err.Error()) } + manager := service.NewManager() + systemManager := manager.GetSystemManager() + if systemManager != nil { + if err := systemManager.GetProcessIdentifierHandler().HandleApplicationStart(); err != nil { + log.Fatal(err.Error()) + } + + defer func(handler *service.ProcessIdentifierHandler) { + err := handler.HandleApplicationStop() + if err != nil { + log.Fatal(err.Error()) + } + }(systemManager.GetProcessIdentifierHandler()) + } + instance := watcher.New() if err := instance.Start(); err != nil { log.Fatal(err.Error()) diff --git a/cmd/stop.go b/cmd/stop.go new file mode 100644 index 0000000..d12f5ea --- /dev/null +++ b/cmd/stop.go @@ -0,0 +1,32 @@ +package cmd + +import ( + "github.com/darki73/goflaresync/pkg/log" + "github.com/darki73/goflaresync/pkg/service" + "github.com/spf13/cobra" +) + +// stopCmd represents the stop command. +var stopCmd = &cobra.Command{ + Use: "stop", + Short: "Stops the service", + Long: "Stops the service", + Run: func(cmd *cobra.Command, args []string) { + if err := initializeConfiguration(); err != nil { + log.Fatal(err.Error()) + } + + manager := service.NewManager() + systemManager := manager.GetSystemManager() + if systemManager != nil { + if err := systemManager.GetProcessIdentifierHandler().HandleApplicationStop(); err != nil { + log.Fatal(err.Error()) + } + } + }, +} + +// init initializes the command. +func init() { + rootCmd.AddCommand(stopCmd) +} diff --git a/go.mod b/go.mod index 1d9b926..d96905b 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/darki73/goflaresync go 1.19 require ( + github.com/Code-Hex/dd v1.1.0 github.com/fsnotify/fsnotify v1.6.0 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.7.0 @@ -10,9 +11,13 @@ require ( ) require ( + github.com/alecthomas/chroma v0.10.0 // indirect + github.com/dlclark/regexp2 v1.4.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/spf13/afero v1.9.5 // indirect diff --git a/go.sum b/go.sum index 0eab301..6ae4d5e 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,10 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Code-Hex/dd v1.1.0 h1:VEtTThnS9l7WhpKUIpdcWaf0B8Vp0LeeSEsxA1DZseI= +github.com/Code-Hex/dd v1.1.0/go.mod h1:VaMyo/YjTJ3d4qm/bgtrUkT2w+aYwJ07Y7eCWyrJr1w= +github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= +github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -50,6 +54,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -136,6 +142,10 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= @@ -311,6 +321,8 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= diff --git a/pkg/debug/debug.go b/pkg/debug/debug.go new file mode 100644 index 0000000..796de72 --- /dev/null +++ b/pkg/debug/debug.go @@ -0,0 +1,20 @@ +package debug + +import ( + "github.com/Code-Hex/dd/p" + "github.com/darki73/goflaresync/pkg/log" + "os" +) + +// Dump dumps the arguments. +func Dump(args ...interface{}) { + if _, err := p.P(args...); err != nil { + log.Fatalf("failed to dump: %s", err.Error()) + } +} + +// DieAndDump dumps the arguments and exits. +func DieAndDump(args ...interface{}) { + Dump(args...) + os.Exit(0) +} diff --git a/pkg/helpers/application.go b/pkg/helpers/application.go new file mode 100644 index 0000000..34f39ff --- /dev/null +++ b/pkg/helpers/application.go @@ -0,0 +1,27 @@ +package helpers + +import ( + "os" + "path/filepath" + "strings" +) + +// GetExecutableDirectory returns the directory containing the executable. +func GetExecutableDirectory() (string, error) { + executablePath, err := os.Executable() + if err != nil { + return "", err + } + + executableDirectory := filepath.Dir(executablePath) + return executableDirectory, nil +} + +// GetExecutableName returns the name of the executable. +func GetExecutableName() string { + executableName := filepath.Base(os.Args[0]) + if strings.Contains(executableName, ".") { + executableName = strings.Split(executableName, ".")[0] + } + return executableName +} diff --git a/pkg/helpers/directory.go b/pkg/helpers/directory.go new file mode 100644 index 0000000..b0f394d --- /dev/null +++ b/pkg/helpers/directory.go @@ -0,0 +1,12 @@ +package helpers + +import "os" + +// IsDirectoryExists returns a boolean value indicating whether the directory exists. +func IsDirectoryExists(directoryPath string) bool { + info, err := os.Stat(directoryPath) + if os.IsNotExist(err) { + return false + } + return info.IsDir() +} diff --git a/pkg/service/common.go b/pkg/service/common.go new file mode 100644 index 0000000..f2d03a1 --- /dev/null +++ b/pkg/service/common.go @@ -0,0 +1,120 @@ +package service + +import ( + "github.com/darki73/goflaresync/pkg/helpers" + "github.com/darki73/goflaresync/pkg/log" + "os" + "path" + "strings" +) + +// InitializationSystemManager is the initialization system manager. +type InitializationSystemManager interface { + // GetServiceTemplate returns the service template. + GetServiceTemplate() (string, error) + // GetServicePath returns the service path. + GetServicePath() string + // GetProcessIdentifierHandler returns the process identifier handler. + GetProcessIdentifierHandler() *ProcessIdentifierHandler + // CreateService creates the service. + CreateService() error + // DeleteService deletes the service. + DeleteService() error + // EnableService enables the service. + EnableService() error + // DisableService disables the service. + DisableService() error + // StartService starts the service. + StartService() error + // StopService stops the service. + StopService() error + // RestartService restarts the service. + RestartService() error + // ReloadManager reloads the manager. + ReloadManager() error + // IsRunningAsRoot checks if the application is running as root. + IsRunningAsRoot() + // IsServiceExists checks if the service exists. + IsServiceExists() bool + // IsServiceEnabled checks if the service is enabled. + IsServiceEnabled() (bool, error) + // IsServiceRunning checks if the service is running. + IsServiceRunning() (bool, error) +} + +// mergeLogFields merges the log fields. +func mergeLogFields(fields log.FieldsMap) log.FieldsMap { + defaultFields := log.FieldsMap{ + "source": "service", + } + + if fields == nil { + return defaultFields + } + + for key, value := range defaultFields { + fields[key] = value + } + + return fields +} + +// logDebug logs a debug message. +func logDebug(message string, fields log.FieldsMap) { + log.DebugWithFields(message, mergeLogFields(fields)) +} + +// logDebugf logs a debug message. +func logDebugf(message string, fields log.FieldsMap, args ...interface{}) { + log.DebugfWithFields(message, mergeLogFields(fields), args...) +} + +// logInfo logs an info message. +func logInfo(message string, fields log.FieldsMap) { + log.InfoWithFields(message, mergeLogFields(fields)) +} + +// logInfof logs an info message. +func logInfof(message string, fields log.FieldsMap, args ...interface{}) { + log.InfofWithFields(message, mergeLogFields(fields), args...) +} + +// applicationName returns the name of the application. +func applicationName() string { + return helpers.GetExecutableName() +} + +// applicationFullPath returns the full path of the application. +func applicationFullPath() (string, error) { + directory, err := helpers.GetExecutableDirectory() + if err != nil { + return "", err + } + return path.Join(directory, applicationName()), nil +} + +// combineSlices combines the slices. +func combineSlices(slices ...[]string) string { + var combined []string + + for _, slice := range slices { + combined = append(combined, slice...) + } + + return strings.Join(combined, "\n") +} + +// isRunningAsRoot checks if the application is running as root. +func isRunningAsRoot() { + if !helpers.IsRoot() { + log.Fatal("this command must be run as root") + } +} + +// isServiceExists checks if the service exists. +func isServiceExists(baseDirectory string, serviceName string) bool { + if _, err := os.Stat(path.Join(baseDirectory, serviceName)); err != nil { + return false + } + return true +} diff --git a/pkg/service/initd.go b/pkg/service/initd.go new file mode 100644 index 0000000..96c6902 --- /dev/null +++ b/pkg/service/initd.go @@ -0,0 +1,347 @@ +package service + +import ( + "fmt" + "os" + "os/exec" + "path" + "path/filepath" +) + +// SysVInitConfigurator is the init.d configurator. +type SysVInitConfigurator struct { + // baseDirectory is the base directory for service installation + baseDirectory string + // serviceName is the name of the service + serviceName string + // processIdentifierHandler is the process identifier handler + processIdentifierHandler *ProcessIdentifierHandler +} + +// NewSysVInitConfigurator creates a new initd configurator. +func NewSysVInitConfigurator() *SysVInitConfigurator { + return &SysVInitConfigurator{ + baseDirectory: "/etc/init.d", + serviceName: applicationName(), + processIdentifierHandler: NewProcessIdentifierHandler(), + } +} + +// GetServiceTemplate returns the service template. +func (sysVInitConfigurator *SysVInitConfigurator) GetServiceTemplate() (string, error) { + executablePath, err := applicationFullPath() + if err != nil { + return "", err + } + + scriptInformation := []string{ + "#!/bin/bash", + fmt.Sprintf("# %s/%s", sysVInitConfigurator.baseDirectory, sysVInitConfigurator.serviceName), + "\n", + "### BEGIN INIT INFO", + fmt.Sprintf("# Provides: %s", sysVInitConfigurator.serviceName), + "# Required-Start: $local_fs $network", + "# Required-Stop: $local_fs", + "# Default-Start: 2 3 4 5", + "# Default-Stop: 0 1 6", + "# Short-Description: GoFlareSync Service", + "# Description: Manager GoFlareSync Service", + "### END INIT INFO", + "\n", + fmt.Sprintf("EXEC_PATH=\"%s\"", executablePath), + fmt.Sprintf("PID_FILE=\"%s\"", sysVInitConfigurator.processIdentifierHandler.GetProcessIdentifierFile()), + } + + startFunction := []string{ + "start() {", + " echo \"Starting GoFlareSync Service\"", + " $EXEC_PATH start", + "}", + } + + stopFunction := []string{ + "stop() {", + " echo \"Stopping GoFlareSync Service\"", + " $EXEC_PATH stop", + "}", + } + + restartFunction := []string{ + "restart() {", + " stop", + " start", + "}", + } + + statusFunction := []string{ + "status() {", + " if [ -e $PID_FILE ]; then", + " echo \"GoFlareSync Service is running\"", + " else", + " echo \"GoFlareSync Service is not running\"", + " fi", + "}", + } + + serviceDefinition := []string{ + "case \"$1\" in", + " start)", + " start", + " ;;", + " stop)", + " stop", + " ;;", + " restart)", + " restart", + " ;;", + " status)", + " status", + " ;;", + " *)", + " echo \"Usage: $0 {start|stop|restart|status}\"", + " exit 1", + " ;;", + "esac", + } + + return combineSlices( + scriptInformation, + startFunction, + stopFunction, + restartFunction, + statusFunction, + serviceDefinition, + ), nil +} + +// GetServicePath returns the service path. +func (sysVInitConfigurator *SysVInitConfigurator) GetServicePath() string { + return path.Join(sysVInitConfigurator.baseDirectory, sysVInitConfigurator.serviceName) +} + +// GetProcessIdentifierHandler returns the process identifier handler. +func (sysVInitConfigurator *SysVInitConfigurator) GetProcessIdentifierHandler() *ProcessIdentifierHandler { + return sysVInitConfigurator.processIdentifierHandler +} + +// CreateService creates the service. +func (sysVInitConfigurator *SysVInitConfigurator) CreateService() error { + sysVInitConfigurator.IsRunningAsRoot() + + if sysVInitConfigurator.IsServiceExists() { + logInfo("service already exists", nil) + return nil + } + + logInfo("creating the service", nil) + + template, err := sysVInitConfigurator.GetServiceTemplate() + + if err != nil { + return err + } + + err = os.WriteFile(sysVInitConfigurator.GetServicePath(), []byte(template), 0755) + + if err != nil { + logDebugf("failed to write service file: %s", nil, err.Error()) + return err + } + + logInfo("successfully created the service", nil) + + return sysVInitConfigurator.ReloadManager() +} + +// DeleteService deletes the service. +func (sysVInitConfigurator *SysVInitConfigurator) DeleteService() error { + sysVInitConfigurator.IsRunningAsRoot() + + if !sysVInitConfigurator.IsServiceExists() { + logInfo("service does not exist", nil) + return nil + } + + logInfo("deleting the service", nil) + + isRunning, err := sysVInitConfigurator.IsServiceRunning() + + if err != nil { + return err + } + + if isRunning { + if err := sysVInitConfigurator.StopService(); err != nil { + return err + } + } + + if err := sysVInitConfigurator.DisableService(); err != nil { + return err + } + + if err := os.Remove(sysVInitConfigurator.GetServicePath()); err != nil { + return err + } + + logInfo("successfully deleted the service", nil) + + return sysVInitConfigurator.ReloadManager() +} + +// EnableService enables the service. +func (sysVInitConfigurator *SysVInitConfigurator) EnableService() error { + sysVInitConfigurator.IsRunningAsRoot() + + if !sysVInitConfigurator.IsServiceExists() { + return fmt.Errorf("service does not exist") + } + + isEnabled, err := sysVInitConfigurator.IsServiceEnabled() + if err != nil { + return err + } + + if !isEnabled { + if err := exec.Command("update-rc.d", sysVInitConfigurator.serviceName, "defaults").Run(); err != nil { + logDebugf("failed to enable the service: %s", nil, err.Error()) + return err + } + logInfo("successfully enabled the service", nil) + } else { + logInfo("service is already enabled", nil) + } + + return nil +} + +// DisableService disables the service. +func (sysVInitConfigurator *SysVInitConfigurator) DisableService() error { + sysVInitConfigurator.IsRunningAsRoot() + + if !sysVInitConfigurator.IsServiceExists() { + return fmt.Errorf("service does not exist") + } + + logInfo("disabling the service", nil) + + isEnabled, err := sysVInitConfigurator.IsServiceEnabled() + if err != nil { + return err + } + + if isEnabled { + if err := exec.Command("update-rc.d", "-f", sysVInitConfigurator.serviceName, "remove").Run(); err != nil { + logDebugf("failed to disable the service: %s", nil, err.Error()) + return err + } + logInfo("successfully disabled the service", nil) + } else { + logInfo("service is already disabled", nil) + } + + return nil +} + +// StartService starts the service. +func (sysVInitConfigurator *SysVInitConfigurator) StartService() error { + sysVInitConfigurator.IsRunningAsRoot() + + if !sysVInitConfigurator.IsServiceExists() { + return fmt.Errorf("service does not exist") + } + + isRunning, _ := sysVInitConfigurator.IsServiceRunning() + + if !isRunning { + if err := exec.Command(sysVInitConfigurator.GetServicePath(), "start").Run(); err != nil { + logDebugf("failed to start the service: %s", nil, err.Error()) + return err + } + logInfo("successfully started the service", nil) + } else { + logInfo("service is already running", nil) + } + + return nil +} + +// StopService stops the service. +func (sysVInitConfigurator *SysVInitConfigurator) StopService() error { + sysVInitConfigurator.IsRunningAsRoot() + + if !sysVInitConfigurator.IsServiceExists() { + return fmt.Errorf("service does not exist") + } + + isRunning, _ := sysVInitConfigurator.IsServiceRunning() + + if isRunning { + if err := exec.Command(sysVInitConfigurator.GetServicePath(), "stop").Run(); err != nil { + logDebugf("failed to stop the service: %s", nil, err.Error()) + return err + } + logInfo("successfully stopped the service", nil) + } else { + logInfo("service is already stopped", nil) + } + + return nil +} + +// RestartService restarts the service. +func (sysVInitConfigurator *SysVInitConfigurator) RestartService() error { + sysVInitConfigurator.IsRunningAsRoot() + + logInfo("restarting the service", nil) + + if err := sysVInitConfigurator.StopService(); err != nil { + return err + } + + if err := sysVInitConfigurator.StartService(); err != nil { + return err + } + + logInfo("successfully restarted the service", nil) + + return nil +} + +// ReloadManager reloads the manager. +func (sysVInitConfigurator *SysVInitConfigurator) ReloadManager() error { + return nil +} + +// IsRunningAsRoot checks if the application is running as root. +func (sysVInitConfigurator *SysVInitConfigurator) IsRunningAsRoot() { + isRunningAsRoot() +} + +// IsServiceExists checks if the service exists. +func (sysVInitConfigurator *SysVInitConfigurator) IsServiceExists() bool { + return isServiceExists(sysVInitConfigurator.baseDirectory, sysVInitConfigurator.serviceName) +} + +// IsServiceEnabled checks if the service is enabled. +func (sysVInitConfigurator *SysVInitConfigurator) IsServiceEnabled() (bool, error) { + runLevels := []string{"0", "1", "2", "3", "4", "5", "6", "S"} + + for _, runlevel := range runLevels { + dirPath := filepath.Join("/etc/rc" + runlevel + ".d/") + enabled, err := filepath.Glob(filepath.Join(dirPath, "S*"+sysVInitConfigurator.serviceName)) + if err != nil { + return false, err + } + if len(enabled) > 0 { + return true, nil + } + } + + return false, nil +} + +// IsServiceRunning checks if the service is running. +func (sysVInitConfigurator *SysVInitConfigurator) IsServiceRunning() (bool, error) { + return sysVInitConfigurator.processIdentifierHandler.IsProcessIdentifierFileExists(), nil +} diff --git a/pkg/service/pid_handler.go b/pkg/service/pid_handler.go new file mode 100644 index 0000000..5c6d69e --- /dev/null +++ b/pkg/service/pid_handler.go @@ -0,0 +1,223 @@ +package service + +import ( + "fmt" + "github.com/darki73/goflaresync/pkg/helpers" + "os" + "os/exec" + "path" + "strconv" + "strings" + "syscall" +) + +// ProcessIdentifierHandler is the process identifier handler. +type ProcessIdentifierHandler struct { + // processIdentifier is the process identifier + processIdentifier int + // processIdentifierFileName is the file name containing the process identifier + processIdentifierFileName string + // processIdentifierFilePath is the path to the file containing the process identifier + processIdentifierFilePath string + // isSupported is a flag indicating whether the process identifier handler is supported + isSupported bool +} + +// NewProcessIdentifierHandler returns a new process identifier handler. +func NewProcessIdentifierHandler() *ProcessIdentifierHandler { + handler := &ProcessIdentifierHandler{ + processIdentifier: os.Getpid(), + processIdentifierFileName: fmt.Sprintf("%s.pid", applicationName()), + processIdentifierFilePath: "", + isSupported: false, + } + + return handler.bootstrap() +} + +// HandleApplicationStart handles the application start. +func (processIdentifierHandler *ProcessIdentifierHandler) HandleApplicationStart() error { + if !processIdentifierHandler.IsSupported() { + return nil + } + + if processIdentifierHandler.IsProcessIdentifierFileExists() { + processIdentifier, err := processIdentifierHandler.ReadProcessIdentifierFromFile() + if err != nil { + return err + } + + isRunning := processIdentifierHandler.IsProcessRunning(processIdentifier) + + if isRunning && !processIdentifierHandler.IsThisApplication(processIdentifier) { + logDebug( + "PID file exists, process is running, but does not belong to this application", + nil, + ) + if err := processIdentifierHandler.DeleteProcessIdentifierFile(); err != nil { + return err + } + } + + if !isRunning { + logDebug( + "PID file exists, but process is not running", + nil, + ) + if err := processIdentifierHandler.DeleteProcessIdentifierFile(); err != nil { + return err + } + } + } + return processIdentifierHandler.WriteProcessIdentifierToFile() +} + +// HandleApplicationStop handles the application stop. +func (processIdentifierHandler *ProcessIdentifierHandler) HandleApplicationStop() error { + if !processIdentifierHandler.IsSupported() { + return nil + } + + if processIdentifierHandler.IsProcessIdentifierFileExists() { + processIdentifier, err := processIdentifierHandler.ReadProcessIdentifierFromFile() + if err != nil { + return err + } + + if processIdentifierHandler.IsProcessRunning(processIdentifier) { + logDebug( + "process identifier file exists and the process with the given identifier is running", + nil, + ) + + if processIdentifierHandler.IsThisApplication(processIdentifier) { + logDebug( + "process identifier belongs to this application", + nil, + ) + + process, findErr := os.FindProcess(processIdentifier) + if findErr != nil { + return findErr + } + + if killErr := process.Signal(syscall.SIGTERM); killErr != nil { + return killErr + } + + if err := processIdentifierHandler.DeleteProcessIdentifierFile(); err != nil { + return err + } + } + } else { + logDebug( + "process identifier file exists and the process with the given identifier is not running", + nil, + ) + + if err := processIdentifierHandler.DeleteProcessIdentifierFile(); err != nil { + return err + } + } + } + + return nil +} + +// GetProcessIdentifier returns the process identifier. +func (processIdentifierHandler *ProcessIdentifierHandler) GetProcessIdentifier() int { + return processIdentifierHandler.processIdentifier +} + +// GetProcessIdentifierFileName returns the file name containing the process identifier. +func (processIdentifierHandler *ProcessIdentifierHandler) GetProcessIdentifierFileName() string { + return processIdentifierHandler.processIdentifierFileName +} + +// GetProcessIdentifierFilePath returns the path to the file containing the process identifier. +func (processIdentifierHandler *ProcessIdentifierHandler) GetProcessIdentifierFilePath() string { + return processIdentifierHandler.processIdentifierFilePath +} + +// GetProcessIdentifierFile returns the full path to the file containing the process identifier. +func (processIdentifierHandler *ProcessIdentifierHandler) GetProcessIdentifierFile() string { + return path.Join( + processIdentifierHandler.GetProcessIdentifierFilePath(), + processIdentifierHandler.GetProcessIdentifierFileName(), + ) +} + +// IsSupported returns a flag indicating whether the process identifier handler is supported. +func (processIdentifierHandler *ProcessIdentifierHandler) IsSupported() bool { + return processIdentifierHandler.isSupported +} + +// ReadProcessIdentifierFromFile reads the process identifier from the file. +func (processIdentifierHandler *ProcessIdentifierHandler) ReadProcessIdentifierFromFile() (int, error) { + data, err := os.ReadFile(processIdentifierHandler.GetProcessIdentifierFile()) + if err != nil { + return 0, err + } + return strconv.Atoi(string(data)) +} + +// WriteProcessIdentifierToFile writes the process identifier to the file. +func (processIdentifierHandler *ProcessIdentifierHandler) WriteProcessIdentifierToFile() error { + return os.WriteFile( + processIdentifierHandler.GetProcessIdentifierFile(), + []byte(strconv.Itoa(processIdentifierHandler.GetProcessIdentifier())), + 0644, + ) +} + +// DeleteProcessIdentifierFile deletes the file containing the process identifier. +func (processIdentifierHandler *ProcessIdentifierHandler) DeleteProcessIdentifierFile() error { + return os.Remove(processIdentifierHandler.GetProcessIdentifierFile()) +} + +// IsProcessIdentifierFileExists returns a flag indicating whether the file containing the process identifier exists. +func (processIdentifierHandler *ProcessIdentifierHandler) IsProcessIdentifierFileExists() bool { + if _, err := os.Stat(processIdentifierHandler.GetProcessIdentifierFile()); err == nil { + return true + } + return false +} + +// IsProcessRunning returns a flag indicating whether the process is running. +func (processIdentifierHandler *ProcessIdentifierHandler) IsProcessRunning(processIdentifier int) bool { + if processIdentifier == 0 { + return false + } + + cmd := exec.Command("kill", "-0", strconv.Itoa(processIdentifier)) + err := cmd.Run() + return err == nil +} + +// IsThisApplication returns a flag indicating whether the process identifier belongs to this application. +func (processIdentifierHandler *ProcessIdentifierHandler) IsThisApplication(processIdentifier int) bool { + if processIdentifier == 0 { + return false + } + + cmd := exec.Command("ps", "-p", strconv.Itoa(processIdentifier), "-o", "args=") + out, err := cmd.Output() + if err != nil { + return false + } + + return strings.Contains(string(out), applicationName()) +} + +// bootstrap bootstraps the process identifier handler. +func (processIdentifierHandler *ProcessIdentifierHandler) bootstrap() *ProcessIdentifierHandler { + if helpers.IsDirectoryExists("/var/run") { + processIdentifierHandler.isSupported = true + processIdentifierHandler.processIdentifierFilePath = "/var/run" + } else if helpers.IsDirectoryExists("/run") { + processIdentifierHandler.isSupported = true + processIdentifierHandler.processIdentifierFilePath = "/run" + } + + return processIdentifierHandler +} diff --git a/pkg/service/service.go b/pkg/service/service.go new file mode 100644 index 0000000..dc73716 --- /dev/null +++ b/pkg/service/service.go @@ -0,0 +1,125 @@ +package service + +import ( + "os" + "os/exec" + "runtime" + "strings" +) + +type InitSystem string + +const ( + Systemd InitSystem = "systemd" + Upstart InitSystem = "upstart" + SysVinit InitSystem = "sysvinit" + OpenRC InitSystem = "openrc" + None InitSystem = "none" +) + +// Manager is the service manager. +type Manager struct { + // systemManager is the system manager + systemManager InitializationSystemManager +} + +// NewManager creates a new service manager. +func NewManager() *Manager { + manager := &Manager{ + systemManager: nil, + } + return manager.bootstrap() +} + +// GetSystemManager returns the system manager. +func (manager *Manager) GetSystemManager() InitializationSystemManager { + return manager.systemManager +} + +// HasSystemManager checks if the system manager is available. +func (manager *Manager) HasSystemManager() bool { + return manager.systemManager != nil +} + +// bootstrap bootstraps the service manager. +func (manager *Manager) bootstrap() *Manager { + switch manager.detectInitializationSystem() { + case Systemd: + manager.systemManager = NewSystemdConfigurator() + break + //case Upstart: + // manager.systemManager = NewUpstartConfigurator() + // break + case SysVinit: + manager.systemManager = NewSysVInitConfigurator() + break + //case OpenRC: + // manager.systemManager = NewOpenRCConfigurator() + // break + case None: + manager.systemManager = nil + break + default: + manager.systemManager = nil + break + } + + return manager +} + +// detectInitializationSystem detects the initialization system. +func (manager *Manager) detectInitializationSystem() InitSystem { + if runtime.GOOS == "windows" || manager.isWindowsSubsystemForLinux() { + return None + } + + if manager.isCommandAvailable("systemctl") { + return Systemd + } + + if manager.isCommandAvailable("initctl") { + return Upstart + } + + if manager.isCommandAvailable("service") && manager.isDirectoryExists("/etc/init.d") { + return SysVinit + } + + if manager.isCommandAvailable("rc-service") || manager.isCommandAvailable("rc-update") { + return OpenRC + } + + return None +} + +// isCommandAvailable checks if the command is available. +func (manager *Manager) isCommandAvailable(name string) bool { + cmd := exec.Command("/bin/sh", "-c", "command -v "+name) + if err := cmd.Run(); err != nil { + return false + } + return true +} + +// isDirectoryExists checks if the directory exists. +func (manager *Manager) isDirectoryExists(path string) bool { + if _, err := os.Stat(path); err != nil { + return false + } + return true +} + +// isWindowsSubsystemForLinux checks if application is running on Windows Subsystem for Linux. +func (manager *Manager) isWindowsSubsystemForLinux() bool { + data, err := os.ReadFile("/proc/version") + if err != nil { + return false + } + + versionInfo := string(data) + + if strings.Contains(versionInfo, "Microsoft") || strings.Contains(versionInfo, "microsoft") { + return true + } + return false +} diff --git a/pkg/service/systemd.go b/pkg/service/systemd.go new file mode 100644 index 0000000..da27c3b --- /dev/null +++ b/pkg/service/systemd.go @@ -0,0 +1,330 @@ +package service + +import ( + "bytes" + "errors" + "fmt" + "os" + "os/exec" + "path" +) + +// SystemdConfigurator is the systemd configurator. +type SystemdConfigurator struct { + // baseDirectory is the base directory for service installation + baseDirectory string + // serviceName is the name of the service + serviceName string + // processIdentifierHandler is the process identifier handler + processIdentifierHandler *ProcessIdentifierHandler +} + +// NewSystemdConfigurator creates a new systemd configurator. +func NewSystemdConfigurator() *SystemdConfigurator { + return &SystemdConfigurator{ + baseDirectory: "/etc/systemd/system", + serviceName: fmt.Sprintf("%s.service", applicationName()), + processIdentifierHandler: NewProcessIdentifierHandler(), + } +} + +// GetServiceTemplate returns the service template. +func (systemdConfigurator *SystemdConfigurator) GetServiceTemplate() (string, error) { + unitInformation := []string{ + "[Unit]", + "Description=GoFlareSync Service", + "After=network.target", + } + + executablePath, err := applicationFullPath() + if err != nil { + return "", err + } + + serviceInformation := []string{ + "[Service]", + "Type=simple", + "Restart=on-failure", + fmt.Sprintf("ExecStart=%s start", executablePath), + fmt.Sprintf("ExecStop=%s stop", executablePath), + fmt.Sprintf( + "PIDFile=%s", + systemdConfigurator.processIdentifierHandler.GetProcessIdentifierFile(), + ), + } + + installInformation := []string{ + "[Install]", + "WantedBy=multi-user.target", + } + + return combineSlices(unitInformation, serviceInformation, installInformation), nil +} + +// GetServicePath returns the service path. +func (systemdConfigurator *SystemdConfigurator) GetServicePath() string { + return path.Join(systemdConfigurator.baseDirectory, systemdConfigurator.serviceName) +} + +// GetProcessIdentifierHandler returns the process identifier handler. +func (systemdConfigurator *SystemdConfigurator) GetProcessIdentifierHandler() *ProcessIdentifierHandler { + return systemdConfigurator.processIdentifierHandler +} + +// CreateService creates the service. +func (systemdConfigurator *SystemdConfigurator) CreateService() error { + systemdConfigurator.IsRunningAsRoot() + + if systemdConfigurator.IsServiceExists() { + logInfo("service already exists", nil) + return nil + } + + logInfo("creating the service", nil) + + template, err := systemdConfigurator.GetServiceTemplate() + + if err != nil { + return err + } + + err = os.WriteFile(systemdConfigurator.GetServicePath(), []byte(template), 0644) + + if err != nil { + logDebugf("failed to write service file: %s", nil, err.Error()) + return err + } + + logInfo("successfully created the service", nil) + + return systemdConfigurator.ReloadManager() +} + +// DeleteService deletes the service. +func (systemdConfigurator *SystemdConfigurator) DeleteService() error { + systemdConfigurator.IsRunningAsRoot() + + if !systemdConfigurator.IsServiceExists() { + logInfo("service does not exist", nil) + return nil + } + + logInfo("deleting the service", nil) + + isRunning, err := systemdConfigurator.IsServiceRunning() + if err != nil { + return err + } + + if isRunning { + if err := systemdConfigurator.StopService(); err != nil { + return err + } + } + + if err := os.Remove(systemdConfigurator.GetServicePath()); err != nil { + return err + } + + logInfo("successfully deleted the service", nil) + + return systemdConfigurator.ReloadManager() +} + +// EnableService enables the service. +func (systemdConfigurator *SystemdConfigurator) EnableService() error { + systemdConfigurator.IsRunningAsRoot() + + if !systemdConfigurator.IsServiceExists() { + return fmt.Errorf("service does not exist") + } + + logInfof("enabling the service", nil) + + isEnabled, err := systemdConfigurator.IsServiceEnabled() + + if err != nil { + return err + } + + if !isEnabled { + if err := exec.Command("systemctl", "enable", systemdConfigurator.serviceName).Run(); err != nil { + logDebugf("failed to enable the service: %s", nil, err.Error()) + return err + } + logInfo("successfully enabled the service", nil) + } else { + logInfo("service is already enabled", nil) + } + + return nil +} + +// DisableService disables the service. +func (systemdConfigurator *SystemdConfigurator) DisableService() error { + systemdConfigurator.IsRunningAsRoot() + + if !systemdConfigurator.IsServiceExists() { + return fmt.Errorf("service does not exist") + } + + logInfo("disabling the service", nil) + + isEnabled, err := systemdConfigurator.IsServiceEnabled() + + if err != nil { + return err + } + + if isEnabled { + if err := exec.Command("systemctl", "disable", systemdConfigurator.serviceName).Run(); err != nil { + logDebugf("failed to disable the service: %s", nil, err.Error()) + return err + } + logInfo("successfully disabled the service", nil) + } else { + logInfo("service is already disabled", nil) + } + + return nil +} + +// StartService starts the service. +func (systemdConfigurator *SystemdConfigurator) StartService() error { + systemdConfigurator.IsRunningAsRoot() + + if !systemdConfigurator.IsServiceExists() { + return fmt.Errorf("service does not exist") + } + + logInfo("starting the service", nil) + + isRunning, err := systemdConfigurator.IsServiceRunning() + + if err != nil { + return err + } + + if !isRunning { + if err := exec.Command("systemctl", "start", systemdConfigurator.serviceName).Run(); err != nil { + logDebugf("failed to start the service: %s", nil, err.Error()) + return err + } + + logInfo("successfully started the service", nil) + } else { + logInfo("service is already running", nil) + } + + return nil +} + +// StopService stops the service. +func (systemdConfigurator *SystemdConfigurator) StopService() error { + systemdConfigurator.IsRunningAsRoot() + + if !systemdConfigurator.IsServiceExists() { + return fmt.Errorf("service does not exist") + } + + logInfo("stopping the service", nil) + + isRunning, err := systemdConfigurator.IsServiceRunning() + + if err != nil { + return err + } + + if isRunning { + if err := exec.Command("systemctl", "stop", systemdConfigurator.serviceName).Run(); err != nil { + logDebugf("failed to stop the service: %s", nil, err.Error()) + return err + } + logInfo("successfully stopped the service", nil) + } else { + logInfo("service is already stopped", nil) + } + + return nil +} + +// RestartService restarts the service. +func (systemdConfigurator *SystemdConfigurator) RestartService() error { + systemdConfigurator.IsRunningAsRoot() + + logInfo("restarting the service", nil) + + if err := systemdConfigurator.StopService(); err != nil { + return err + } + + if err := systemdConfigurator.StartService(); err != nil { + return err + } + + logInfo("successfully restarted the service", nil) + + return nil +} + +// ReloadManager reloads the manager. +func (systemdConfigurator *SystemdConfigurator) ReloadManager() error { + logInfo("reloading the manager", nil) + + if err := exec.Command("systemctl", "daemon-reload").Run(); err != nil { + logDebugf("failed to reload systemd: %s", nil, err.Error()) + return err + } + + logInfo("successfully reloaded the manager", nil) + + return nil +} + +// IsRunningAsRoot checks if the application is running as root. +func (systemdConfigurator *SystemdConfigurator) IsRunningAsRoot() { + isRunningAsRoot() +} + +// IsServiceExists checks if the service exists. +func (systemdConfigurator *SystemdConfigurator) IsServiceExists() bool { + return isServiceExists(systemdConfigurator.baseDirectory, systemdConfigurator.serviceName) +} + +// IsServiceEnabled checks if the service is enabled. +func (systemdConfigurator *SystemdConfigurator) IsServiceEnabled() (bool, error) { + cmd := exec.Command("systemctl", "is-enabled", systemdConfigurator.serviceName) + logDebugf("executing command: %s", nil, cmd.String()) + var out bytes.Buffer + cmd.Stdout = &out + + _ = cmd.Run() + + switch out.String() { + case "enabled\n": + return true, nil + case "disabled\n": + return false, nil + default: + return false, errors.New("Unexpected systemctl output: " + out.String()) + } +} + +// IsServiceRunning checks if the service is running. +func (systemdConfigurator *SystemdConfigurator) IsServiceRunning() (bool, error) { + cmd := exec.Command("systemctl", "is-active", systemdConfigurator.serviceName) + logDebugf("executing command: %s", nil, cmd.String()) + var out bytes.Buffer + cmd.Stdout = &out + + _ = cmd.Run() + + switch out.String() { + case "active\n": + return true, nil + case "inactive\n": + return false, nil + default: + return false, errors.New("Unexpected systemctl output: " + out.String()) + } +}