mirror of
https://github.com/drakkan/sftpgo.git
synced 2025-12-07 06:40:54 +03:00
add Windows Service support
This commit is contained in:
@@ -76,6 +76,13 @@ Alternately you can use distro packages:
|
|||||||
|
|
||||||
For macOS a `launchd` sample [service](https://github.com/drakkan/sftpgo/tree/master/init/com.github.drakkan.sftpgo.plist "launchd plist") can be found inside the source tree. The `launchd` plist assumes that `sftpgo` has `/usr/local/opt/sftpgo` as base directory.
|
For macOS a `launchd` sample [service](https://github.com/drakkan/sftpgo/tree/master/init/com.github.drakkan.sftpgo.plist "launchd plist") can be found inside the source tree. The `launchd` plist assumes that `sftpgo` has `/usr/local/opt/sftpgo` as base directory.
|
||||||
|
|
||||||
|
On Windows you can install and run `SFTPGo` as Windows Service, take a look at the CLI usage to learn how:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sftpgo.exe service --help
|
||||||
|
sftpgo.exe service install --help
|
||||||
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
The `sftpgo` executable can be used this way:
|
The `sftpgo` executable can be used this way:
|
||||||
|
|||||||
52
cmd/install_windows.go
Normal file
52
cmd/install_windows.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/drakkan/sftpgo/service"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
installCmd = &cobra.Command{
|
||||||
|
Use: "install",
|
||||||
|
Short: "Install SFTPGo as Windows Service",
|
||||||
|
Long: `To install the SFTPGo Windows Service with the default values for the command line flags simply use:
|
||||||
|
|
||||||
|
sftpgo service install
|
||||||
|
|
||||||
|
Please take a look at the usage below to customize the startup options`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
s := service.Service{
|
||||||
|
ConfigDir: configDir,
|
||||||
|
ConfigFile: configFile,
|
||||||
|
LogFilePath: logFilePath,
|
||||||
|
LogMaxSize: logMaxSize,
|
||||||
|
LogMaxBackups: logMaxBackups,
|
||||||
|
LogMaxAge: logMaxAge,
|
||||||
|
LogCompress: logCompress,
|
||||||
|
LogVerbose: logVerbose,
|
||||||
|
Shutdown: make(chan bool),
|
||||||
|
}
|
||||||
|
winService := service.WindowsService{
|
||||||
|
Service: s,
|
||||||
|
}
|
||||||
|
serviceArgs := []string{"service", "start"}
|
||||||
|
customFlags := getCustomServeFlags()
|
||||||
|
if len(customFlags) > 0 {
|
||||||
|
serviceArgs = append(serviceArgs, customFlags...)
|
||||||
|
}
|
||||||
|
err := winService.Install(serviceArgs...)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error installing service: %v\r\n", err)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Service installed!\r\n")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
serviceCmd.AddCommand(installCmd)
|
||||||
|
addServeFlags(installCmd)
|
||||||
|
}
|
||||||
133
cmd/root.go
133
cmd/root.go
@@ -3,16 +3,52 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/drakkan/sftpgo/config"
|
||||||
"github.com/drakkan/sftpgo/utils"
|
"github.com/drakkan/sftpgo/utils"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
logSender = "cmd"
|
logSender = "cmd"
|
||||||
|
configDirFlag = "config-dir"
|
||||||
|
configDirKey = "config_dir"
|
||||||
|
configFileFlag = "config-file"
|
||||||
|
configFileKey = "config_file"
|
||||||
|
logFilePathFlag = "log-file-path"
|
||||||
|
logFilePathKey = "log_file_path"
|
||||||
|
logMaxSizeFlag = "log-max-size"
|
||||||
|
logMaxSizeKey = "log_max_size"
|
||||||
|
logMaxBackupFlag = "log-max-backups"
|
||||||
|
logMaxBackupKey = "log_max_backups"
|
||||||
|
logMaxAgeFlag = "log-max-age"
|
||||||
|
logMaxAgeKey = "log_max_age"
|
||||||
|
logCompressFlag = "log-compress"
|
||||||
|
logCompressKey = "log_compress"
|
||||||
|
logVerboseFlag = "log-verbose"
|
||||||
|
logVerboseKey = "log_verbose"
|
||||||
|
defaultConfigDir = "."
|
||||||
|
defaultConfigName = config.DefaultConfigName
|
||||||
|
defaultLogFile = "sftpgo.log"
|
||||||
|
defaultLogMaxSize = 10
|
||||||
|
defaultLogMaxBackup = 5
|
||||||
|
defaultLogMaxAge = 28
|
||||||
|
defaultLogCompress = false
|
||||||
|
defaultLogVerbose = true
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
configDir string
|
||||||
|
configFile string
|
||||||
|
logFilePath string
|
||||||
|
logMaxSize int
|
||||||
|
logMaxBackups int
|
||||||
|
logMaxAge int
|
||||||
|
logCompress bool
|
||||||
|
logVerbose bool
|
||||||
|
|
||||||
rootCmd = &cobra.Command{
|
rootCmd = &cobra.Command{
|
||||||
Use: "sftpgo",
|
Use: "sftpgo",
|
||||||
Short: "Full featured and highly configurable SFTP server",
|
Short: "Full featured and highly configurable SFTP server",
|
||||||
@@ -35,3 +71,98 @@ func Execute() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addServeFlags(cmd *cobra.Command) {
|
||||||
|
viper.SetDefault(configDirKey, defaultConfigDir)
|
||||||
|
viper.BindEnv(configDirKey, "SFTPGO_CONFIG_DIR")
|
||||||
|
cmd.Flags().StringVarP(&configDir, configDirFlag, "c", viper.GetString(configDirKey),
|
||||||
|
"Location for SFTPGo config dir. This directory should contain the \"sftpgo\" configuration file or the configured "+
|
||||||
|
"config-file and it is used as the base for files with a relative path (eg. the private keys for the SFTP server, "+
|
||||||
|
"the SQLite database if you use SQLite as data provider). This flag can be set using SFTPGO_CONFIG_DIR env var too.")
|
||||||
|
viper.BindPFlag(configDirKey, cmd.Flags().Lookup(configDirFlag))
|
||||||
|
|
||||||
|
viper.SetDefault(configFileKey, defaultConfigName)
|
||||||
|
viper.BindEnv(configFileKey, "SFTPGO_CONFIG_FILE")
|
||||||
|
cmd.Flags().StringVarP(&configFile, configFileFlag, "f", viper.GetString(configFileKey),
|
||||||
|
"Name for SFTPGo configuration file. It must be the name of a file stored in config-dir not the absolute path to the "+
|
||||||
|
"configuration file. The specified file name must have no extension we automatically load JSON, YAML, TOML, HCL and "+
|
||||||
|
"Java properties. Therefore if you set \"sftpgo\" then \"sftpgo.json\", \"sftpgo.yaml\" and so on are searched. "+
|
||||||
|
"This flag can be set using SFTPGO_CONFIG_FILE env var too.")
|
||||||
|
viper.BindPFlag(configFileKey, cmd.Flags().Lookup(configFileFlag))
|
||||||
|
|
||||||
|
viper.SetDefault(logFilePathKey, defaultLogFile)
|
||||||
|
viper.BindEnv(logFilePathKey, "SFTPGO_LOG_FILE_PATH")
|
||||||
|
cmd.Flags().StringVarP(&logFilePath, logFilePathFlag, "l", viper.GetString(logFilePathKey),
|
||||||
|
"Location for the log file. Leave empty to write logs to the standard output. This flag can be set using SFTPGO_LOG_FILE_PATH "+
|
||||||
|
"env var too.")
|
||||||
|
viper.BindPFlag(logFilePathKey, cmd.Flags().Lookup(logFilePathFlag))
|
||||||
|
|
||||||
|
viper.SetDefault(logMaxSizeKey, defaultLogMaxSize)
|
||||||
|
viper.BindEnv(logMaxSizeKey, "SFTPGO_LOG_MAX_SIZE")
|
||||||
|
cmd.Flags().IntVarP(&logMaxSize, logMaxSizeFlag, "s", viper.GetInt(logMaxSizeKey),
|
||||||
|
"Maximum size in megabytes of the log file before it gets rotated. This flag can be set using SFTPGO_LOG_MAX_SIZE "+
|
||||||
|
"env var too. It is unused if log-file-path is empty.")
|
||||||
|
viper.BindPFlag(logMaxSizeKey, cmd.Flags().Lookup(logMaxSizeFlag))
|
||||||
|
|
||||||
|
viper.SetDefault(logMaxBackupKey, defaultLogMaxBackup)
|
||||||
|
viper.BindEnv(logMaxBackupKey, "SFTPGO_LOG_MAX_BACKUPS")
|
||||||
|
cmd.Flags().IntVarP(&logMaxBackups, "log-max-backups", "b", viper.GetInt(logMaxBackupKey),
|
||||||
|
"Maximum number of old log files to retain. This flag can be set using SFTPGO_LOG_MAX_BACKUPS env var too. "+
|
||||||
|
"It is unused if log-file-path is empty.")
|
||||||
|
viper.BindPFlag(logMaxBackupKey, cmd.Flags().Lookup(logMaxBackupFlag))
|
||||||
|
|
||||||
|
viper.SetDefault(logMaxAgeKey, defaultLogMaxAge)
|
||||||
|
viper.BindEnv(logMaxAgeKey, "SFTPGO_LOG_MAX_AGE")
|
||||||
|
cmd.Flags().IntVarP(&logMaxAge, "log-max-age", "a", viper.GetInt(logMaxAgeKey),
|
||||||
|
"Maximum number of days to retain old log files. This flag can be set using SFTPGO_LOG_MAX_AGE env var too. "+
|
||||||
|
"It is unused if log-file-path is empty.")
|
||||||
|
viper.BindPFlag(logMaxAgeKey, cmd.Flags().Lookup(logMaxAgeFlag))
|
||||||
|
|
||||||
|
viper.SetDefault(logCompressKey, defaultLogCompress)
|
||||||
|
viper.BindEnv(logCompressKey, "SFTPGO_LOG_COMPRESS")
|
||||||
|
cmd.Flags().BoolVarP(&logCompress, logCompressFlag, "z", viper.GetBool(logCompressKey), "Determine if the rotated "+
|
||||||
|
"log files should be compressed using gzip. This flag can be set using SFTPGO_LOG_COMPRESS env var too. "+
|
||||||
|
"It is unused if log-file-path is empty.")
|
||||||
|
viper.BindPFlag(logCompressKey, cmd.Flags().Lookup(logCompressFlag))
|
||||||
|
|
||||||
|
viper.SetDefault(logVerboseKey, defaultLogVerbose)
|
||||||
|
viper.BindEnv(logVerboseKey, "SFTPGO_LOG_VERBOSE")
|
||||||
|
cmd.Flags().BoolVarP(&logVerbose, logVerboseFlag, "v", viper.GetBool(logVerboseKey), "Enable verbose logs. "+
|
||||||
|
"This flag can be set using SFTPGO_LOG_VERBOSE env var too.")
|
||||||
|
viper.BindPFlag(logVerboseKey, cmd.Flags().Lookup(logVerboseFlag))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCustomServeFlags() []string {
|
||||||
|
result := []string{}
|
||||||
|
if configDir != defaultConfigDir {
|
||||||
|
result = append(result, "--"+configDirFlag)
|
||||||
|
result = append(result, configDir)
|
||||||
|
}
|
||||||
|
if configFile != defaultConfigName {
|
||||||
|
result = append(result, "--"+configFileFlag)
|
||||||
|
result = append(result, configFile)
|
||||||
|
}
|
||||||
|
if logFilePath != defaultLogFile {
|
||||||
|
result = append(result, "--"+logFilePathFlag)
|
||||||
|
result = append(result, logFilePath)
|
||||||
|
}
|
||||||
|
if logMaxSize != defaultLogMaxSize {
|
||||||
|
result = append(result, "--"+logMaxSizeFlag)
|
||||||
|
result = append(result, strconv.Itoa(logMaxSize))
|
||||||
|
}
|
||||||
|
if logMaxBackups != defaultLogMaxBackup {
|
||||||
|
result = append(result, "--"+logMaxBackupFlag)
|
||||||
|
result = append(result, strconv.Itoa(logMaxBackups))
|
||||||
|
}
|
||||||
|
if logMaxAge != defaultLogMaxAge {
|
||||||
|
result = append(result, "--"+logMaxAgeFlag)
|
||||||
|
result = append(result, strconv.Itoa(logMaxAge))
|
||||||
|
}
|
||||||
|
if logVerbose != defaultLogVerbose {
|
||||||
|
result = append(result, "--"+logVerboseFlag+"=false")
|
||||||
|
}
|
||||||
|
if logCompress != defaultLogCompress {
|
||||||
|
result = append(result, "--"+logCompressFlag+"=true")
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
184
cmd/serve.go
184
cmd/serve.go
@@ -1,185 +1,39 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"github.com/drakkan/sftpgo/service"
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/api"
|
|
||||||
"github.com/drakkan/sftpgo/config"
|
|
||||||
"github.com/drakkan/sftpgo/dataprovider"
|
|
||||||
"github.com/drakkan/sftpgo/logger"
|
|
||||||
"github.com/drakkan/sftpgo/sftpd"
|
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
configDirFlag = "config-dir"
|
|
||||||
configDirKey = "config_dir"
|
|
||||||
configFileFlag = "config-file"
|
|
||||||
configFileKey = "config_file"
|
|
||||||
logFilePathFlag = "log-file-path"
|
|
||||||
logFilePathKey = "log_file_path"
|
|
||||||
logMaxSizeFlag = "log-max-size"
|
|
||||||
logMaxSizeKey = "log_max_size"
|
|
||||||
logMaxBackupFlag = "log-max-backups"
|
|
||||||
logMaxBackupKey = "log_max_backups"
|
|
||||||
logMaxAgeFlag = "log-max-age"
|
|
||||||
logMaxAgeKey = "log_max_age"
|
|
||||||
logCompressFlag = "log-compress"
|
|
||||||
logCompressKey = "log_compress"
|
|
||||||
logVerboseFlag = "log-verbose"
|
|
||||||
logVerboseKey = "log_verbose"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
configDir string
|
serveCmd = &cobra.Command{
|
||||||
configFile string
|
|
||||||
logFilePath string
|
|
||||||
logMaxSize int
|
|
||||||
logMaxBackups int
|
|
||||||
logMaxAge int
|
|
||||||
logCompress bool
|
|
||||||
logVerbose bool
|
|
||||||
testVar string
|
|
||||||
serveCmd = &cobra.Command{
|
|
||||||
Use: "serve",
|
Use: "serve",
|
||||||
Short: "Start the SFTP Server",
|
Short: "Start the SFTP Server",
|
||||||
Long: `To start the SFTP Server with the default values for the command line flags simply use:
|
Long: `To start the SFTPGo with the default values for the command line flags simply use:
|
||||||
|
|
||||||
sftpgo serve
|
sftpgo serve
|
||||||
|
|
||||||
Please take a look at the usage below to customize the startup options`,
|
Please take a look at the usage below to customize the startup options`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
startServe()
|
service := service.Service{
|
||||||
|
ConfigDir: configDir,
|
||||||
|
ConfigFile: configFile,
|
||||||
|
LogFilePath: logFilePath,
|
||||||
|
LogMaxSize: logMaxSize,
|
||||||
|
LogMaxBackups: logMaxBackups,
|
||||||
|
LogMaxAge: logMaxAge,
|
||||||
|
LogCompress: logCompress,
|
||||||
|
LogVerbose: logVerbose,
|
||||||
|
Shutdown: make(chan bool),
|
||||||
|
}
|
||||||
|
if err := service.Start(); err == nil {
|
||||||
|
service.Wait()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(serveCmd)
|
rootCmd.AddCommand(serveCmd)
|
||||||
|
addServeFlags(serveCmd)
|
||||||
viper.SetDefault(configDirKey, ".")
|
|
||||||
viper.BindEnv(configDirKey, "SFTPGO_CONFIG_DIR")
|
|
||||||
serveCmd.Flags().StringVarP(&configDir, configDirFlag, "c", viper.GetString(configDirKey),
|
|
||||||
"Location for SFTPGo config dir. This directory should contain the \"sftpgo\" configuration file or the configured "+
|
|
||||||
"config-file and it is used as the base for files with a relative path (eg. the private keys for the SFTP server, "+
|
|
||||||
"the SQLite database if you use SQLite as data provider). This flag can be set using SFTPGO_CONFIG_DIR env var too.")
|
|
||||||
viper.BindPFlag(configDirKey, serveCmd.Flags().Lookup(configDirFlag))
|
|
||||||
|
|
||||||
viper.SetDefault(configFileKey, config.DefaultConfigName)
|
|
||||||
viper.BindEnv(configFileKey, "SFTPGO_CONFIG_FILE")
|
|
||||||
serveCmd.Flags().StringVarP(&configFile, configFileFlag, "f", viper.GetString(configFileKey),
|
|
||||||
"Name for SFTPGo configuration file. It must be the name of a file stored in config-dir not the absolute path to the "+
|
|
||||||
"configuration file. The specified file name must have no extension we automatically load JSON, YAML, TOML, HCL and "+
|
|
||||||
"Java properties. Therefore if you set \"sftpgo\" then \"sftpgo.json\", \"sftpgo.yaml\" and so on are searched. "+
|
|
||||||
"This flag can be set using SFTPGO_CONFIG_FILE env var too.")
|
|
||||||
viper.BindPFlag(configFileKey, serveCmd.Flags().Lookup(configFileFlag))
|
|
||||||
|
|
||||||
viper.SetDefault(logFilePathKey, "sftpgo.log")
|
|
||||||
viper.BindEnv(logFilePathKey, "SFTPGO_LOG_FILE_PATH")
|
|
||||||
serveCmd.Flags().StringVarP(&logFilePath, logFilePathFlag, "l", viper.GetString(logFilePathKey),
|
|
||||||
"Location for the log file. Leave empty to write logs to the standard output. This flag can be set using SFTPGO_LOG_FILE_PATH "+
|
|
||||||
"env var too.")
|
|
||||||
viper.BindPFlag(logFilePathKey, serveCmd.Flags().Lookup(logFilePathFlag))
|
|
||||||
|
|
||||||
viper.SetDefault(logMaxSizeKey, 10)
|
|
||||||
viper.BindEnv(logMaxSizeKey, "SFTPGO_LOG_MAX_SIZE")
|
|
||||||
serveCmd.Flags().IntVarP(&logMaxSize, logMaxSizeFlag, "s", viper.GetInt(logMaxSizeKey),
|
|
||||||
"Maximum size in megabytes of the log file before it gets rotated. This flag can be set using SFTPGO_LOG_MAX_SIZE "+
|
|
||||||
"env var too. It is unused if log-file-path is empty.")
|
|
||||||
viper.BindPFlag(logMaxSizeKey, serveCmd.Flags().Lookup(logMaxSizeFlag))
|
|
||||||
|
|
||||||
viper.SetDefault(logMaxBackupKey, 5)
|
|
||||||
viper.BindEnv(logMaxBackupKey, "SFTPGO_LOG_MAX_BACKUPS")
|
|
||||||
serveCmd.Flags().IntVarP(&logMaxBackups, "log-max-backups", "b", viper.GetInt(logMaxBackupKey),
|
|
||||||
"Maximum number of old log files to retain. This flag can be set using SFTPGO_LOG_MAX_BACKUPS env var too. "+
|
|
||||||
"It is unused if log-file-path is empty.")
|
|
||||||
viper.BindPFlag(logMaxBackupKey, serveCmd.Flags().Lookup(logMaxBackupFlag))
|
|
||||||
|
|
||||||
viper.SetDefault(logMaxAgeKey, 28)
|
|
||||||
viper.BindEnv(logMaxAgeKey, "SFTPGO_LOG_MAX_AGE")
|
|
||||||
serveCmd.Flags().IntVarP(&logMaxAge, "log-max-age", "a", viper.GetInt(logMaxAgeKey),
|
|
||||||
"Maximum number of days to retain old log files. This flag can be set using SFTPGO_LOG_MAX_AGE env var too. "+
|
|
||||||
"It is unused if log-file-path is empty.")
|
|
||||||
viper.BindPFlag(logMaxAgeKey, serveCmd.Flags().Lookup(logMaxAgeFlag))
|
|
||||||
|
|
||||||
viper.SetDefault(logCompressKey, false)
|
|
||||||
viper.BindEnv(logCompressKey, "SFTPGO_LOG_COMPRESS")
|
|
||||||
serveCmd.Flags().BoolVarP(&logCompress, logCompressFlag, "z", viper.GetBool(logCompressKey), "Determine if the rotated "+
|
|
||||||
"log files should be compressed using gzip. This flag can be set using SFTPGO_LOG_COMPRESS env var too. "+
|
|
||||||
"It is unused if log-file-path is empty.")
|
|
||||||
viper.BindPFlag(logCompressKey, serveCmd.Flags().Lookup(logCompressFlag))
|
|
||||||
|
|
||||||
viper.SetDefault(logVerboseKey, true)
|
|
||||||
viper.BindEnv(logVerboseKey, "SFTPGO_LOG_VERBOSE")
|
|
||||||
serveCmd.Flags().BoolVarP(&logVerbose, logVerboseFlag, "v", viper.GetBool(logVerboseKey), "Enable verbose logs. "+
|
|
||||||
"This flag can be set using SFTPGO_LOG_VERBOSE env var too.")
|
|
||||||
viper.BindPFlag(logVerboseKey, serveCmd.Flags().Lookup(logVerboseFlag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func startServe() {
|
|
||||||
logLevel := zerolog.DebugLevel
|
|
||||||
if !logVerbose {
|
|
||||||
logLevel = zerolog.InfoLevel
|
|
||||||
}
|
|
||||||
logger.InitLogger(logFilePath, logMaxSize, logMaxBackups, logMaxAge, logCompress, logLevel)
|
|
||||||
logger.Info(logSender, "", "starting SFTPGo, config dir: %v, config file: %v, log max size: %v log max backups: %v "+
|
|
||||||
"log max age: %v log verbose: %v, log compress: %v", configDir, configFile, logMaxSize, logMaxBackups, logMaxAge,
|
|
||||||
logVerbose, logCompress)
|
|
||||||
config.LoadConfig(configDir, configFile)
|
|
||||||
providerConf := config.GetProviderConf()
|
|
||||||
|
|
||||||
err := dataprovider.Initialize(providerConf, configDir)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(logSender, "", "error initializing data provider: %v", err)
|
|
||||||
logger.ErrorToConsole("error initializing data provider: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
dataProvider := dataprovider.GetProvider()
|
|
||||||
sftpdConf := config.GetSFTPDConfig()
|
|
||||||
httpdConf := config.GetHTTPDConfig()
|
|
||||||
|
|
||||||
sftpd.SetDataProvider(dataProvider)
|
|
||||||
|
|
||||||
shutdown := make(chan bool)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
logger.Debug(logSender, "", "initializing SFTP server with config %+v", sftpdConf)
|
|
||||||
if err := sftpdConf.Initialize(configDir); err != nil {
|
|
||||||
logger.Error(logSender, "", "could not start SFTP server: %v", err)
|
|
||||||
logger.ErrorToConsole("could not start SFTP server: %v", err)
|
|
||||||
}
|
|
||||||
shutdown <- true
|
|
||||||
}()
|
|
||||||
|
|
||||||
if httpdConf.BindPort > 0 {
|
|
||||||
router := api.GetHTTPRouter()
|
|
||||||
api.SetDataProvider(dataProvider)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
logger.Debug(logSender, "", "initializing HTTP server with config %+v", httpdConf)
|
|
||||||
s := &http.Server{
|
|
||||||
Addr: fmt.Sprintf("%s:%d", httpdConf.BindAddress, httpdConf.BindPort),
|
|
||||||
Handler: router,
|
|
||||||
ReadTimeout: 300 * time.Second,
|
|
||||||
WriteTimeout: 300 * time.Second,
|
|
||||||
MaxHeaderBytes: 1 << 20, // 1MB
|
|
||||||
}
|
|
||||||
if err := s.ListenAndServe(); err != nil {
|
|
||||||
logger.Error(logSender, "", "could not start HTTP server: %v", err)
|
|
||||||
logger.ErrorToConsole("could not start HTTP server: %v", err)
|
|
||||||
}
|
|
||||||
shutdown <- true
|
|
||||||
}()
|
|
||||||
} else {
|
|
||||||
logger.Debug(logSender, "", "HTTP server not started, disabled in config file")
|
|
||||||
logger.DebugToConsole("HTTP server not started, disabled in config file")
|
|
||||||
}
|
|
||||||
|
|
||||||
<-shutdown
|
|
||||||
}
|
}
|
||||||
|
|||||||
16
cmd/service_windows.go
Normal file
16
cmd/service_windows.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
serviceCmd = &cobra.Command{
|
||||||
|
Use: "service",
|
||||||
|
Short: "Install, Uninstall, Start, Stop and retrieve status for SFTPGo Windows Service",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(serviceCmd)
|
||||||
|
}
|
||||||
42
cmd/start_windows.go
Normal file
42
cmd/start_windows.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/drakkan/sftpgo/service"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
startCmd = &cobra.Command{
|
||||||
|
Use: "start",
|
||||||
|
Short: "Start SFTPGo Windows Service",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
s := service.Service{
|
||||||
|
ConfigDir: configDir,
|
||||||
|
ConfigFile: configFile,
|
||||||
|
LogFilePath: logFilePath,
|
||||||
|
LogMaxSize: logMaxSize,
|
||||||
|
LogMaxBackups: logMaxBackups,
|
||||||
|
LogMaxAge: logMaxAge,
|
||||||
|
LogCompress: logCompress,
|
||||||
|
LogVerbose: logVerbose,
|
||||||
|
Shutdown: make(chan bool),
|
||||||
|
}
|
||||||
|
winService := service.WindowsService{
|
||||||
|
Service: s,
|
||||||
|
}
|
||||||
|
err := winService.RunService()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error starting service: %v\r\n", err)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Service started!\r\n")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
serviceCmd.AddCommand(startCmd)
|
||||||
|
addServeFlags(startCmd)
|
||||||
|
}
|
||||||
32
cmd/status_windows.go
Normal file
32
cmd/status_windows.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/drakkan/sftpgo/service"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
statusCmd = &cobra.Command{
|
||||||
|
Use: "status",
|
||||||
|
Short: "Retrieve the status for the SFTPGo Windows Service",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
s := service.WindowsService{
|
||||||
|
Service: service.Service{
|
||||||
|
Shutdown: make(chan bool),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
status, err := s.Status()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error querying service status: %v\r\n", err)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Service status: %#v\r\n", status.String())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
serviceCmd.AddCommand(statusCmd)
|
||||||
|
}
|
||||||
32
cmd/stop_windows.go
Normal file
32
cmd/stop_windows.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/drakkan/sftpgo/service"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
stopCmd = &cobra.Command{
|
||||||
|
Use: "stop",
|
||||||
|
Short: "Stop SFTPGo Windows Service",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
s := service.WindowsService{
|
||||||
|
Service: service.Service{
|
||||||
|
Shutdown: make(chan bool),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := s.Stop()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error stopping service: %v\r\n", err)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Service stopped!\r\n")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
serviceCmd.AddCommand(stopCmd)
|
||||||
|
}
|
||||||
32
cmd/uninstall_windows.go
Normal file
32
cmd/uninstall_windows.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/drakkan/sftpgo/service"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
uninstallCmd = &cobra.Command{
|
||||||
|
Use: "uninstall",
|
||||||
|
Short: "Uninstall SFTPGo Windows Service",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
s := service.WindowsService{
|
||||||
|
Service: service.Service{
|
||||||
|
Shutdown: make(chan bool),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := s.Uninstall()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error removing service: %v\r\n", err)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Service uninstalled\r\n")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
serviceCmd.AddCommand(uninstallCmd)
|
||||||
|
}
|
||||||
2
go.mod
2
go.mod
@@ -26,7 +26,7 @@ require (
|
|||||||
go.etcd.io/bbolt v1.3.3
|
go.etcd.io/bbolt v1.3.3
|
||||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7
|
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7
|
||||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 // indirect
|
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 // indirect
|
||||||
golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7 // indirect
|
golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7
|
||||||
google.golang.org/appengine v1.6.2 // indirect
|
google.golang.org/appengine v1.6.2 // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||||
)
|
)
|
||||||
|
|||||||
103
service/service.go
Normal file
103
service/service.go
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/drakkan/sftpgo/api"
|
||||||
|
"github.com/drakkan/sftpgo/config"
|
||||||
|
"github.com/drakkan/sftpgo/dataprovider"
|
||||||
|
"github.com/drakkan/sftpgo/logger"
|
||||||
|
"github.com/drakkan/sftpgo/sftpd"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
logSender = "service"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Service defines the SFTPGo service
|
||||||
|
type Service struct {
|
||||||
|
ConfigDir string
|
||||||
|
ConfigFile string
|
||||||
|
LogFilePath string
|
||||||
|
LogMaxSize int
|
||||||
|
LogMaxBackups int
|
||||||
|
LogMaxAge int
|
||||||
|
LogCompress bool
|
||||||
|
LogVerbose bool
|
||||||
|
Shutdown chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start initializes the service
|
||||||
|
func (s *Service) Start() error {
|
||||||
|
logLevel := zerolog.DebugLevel
|
||||||
|
if !s.LogVerbose {
|
||||||
|
logLevel = zerolog.InfoLevel
|
||||||
|
}
|
||||||
|
logger.InitLogger(s.LogFilePath, s.LogMaxSize, s.LogMaxBackups, s.LogMaxAge, s.LogCompress, logLevel)
|
||||||
|
logger.Info(logSender, "", "starting SFTPGo, config dir: %v, config file: %v, log max size: %v log max backups: %v "+
|
||||||
|
"log max age: %v log verbose: %v, log compress: %v", s.ConfigDir, s.ConfigFile, s.LogMaxSize, s.LogMaxBackups, s.LogMaxAge,
|
||||||
|
s.LogVerbose, s.LogCompress)
|
||||||
|
config.LoadConfig(s.ConfigDir, s.ConfigFile)
|
||||||
|
providerConf := config.GetProviderConf()
|
||||||
|
|
||||||
|
err := dataprovider.Initialize(providerConf, s.ConfigDir)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(logSender, "", "error initializing data provider: %v", err)
|
||||||
|
logger.ErrorToConsole("error initializing data provider: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dataProvider := dataprovider.GetProvider()
|
||||||
|
sftpdConf := config.GetSFTPDConfig()
|
||||||
|
httpdConf := config.GetHTTPDConfig()
|
||||||
|
|
||||||
|
sftpd.SetDataProvider(dataProvider)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
logger.Debug(logSender, "", "initializing SFTP server with config %+v", sftpdConf)
|
||||||
|
if err := sftpdConf.Initialize(s.ConfigDir); err != nil {
|
||||||
|
logger.Error(logSender, "", "could not start SFTP server: %v", err)
|
||||||
|
logger.ErrorToConsole("could not start SFTP server: %v", err)
|
||||||
|
}
|
||||||
|
s.Shutdown <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
if httpdConf.BindPort > 0 {
|
||||||
|
router := api.GetHTTPRouter()
|
||||||
|
api.SetDataProvider(dataProvider)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
logger.Debug(logSender, "", "initializing HTTP server with config %+v", httpdConf)
|
||||||
|
httpServer := &http.Server{
|
||||||
|
Addr: fmt.Sprintf("%s:%d", httpdConf.BindAddress, httpdConf.BindPort),
|
||||||
|
Handler: router,
|
||||||
|
ReadTimeout: 300 * time.Second,
|
||||||
|
WriteTimeout: 300 * time.Second,
|
||||||
|
MaxHeaderBytes: 1 << 20, // 1MB
|
||||||
|
}
|
||||||
|
if err := httpServer.ListenAndServe(); err != nil {
|
||||||
|
logger.Error(logSender, "", "could not start HTTP server: %v", err)
|
||||||
|
logger.ErrorToConsole("could not start HTTP server: %v", err)
|
||||||
|
}
|
||||||
|
s.Shutdown <- true
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
logger.Debug(logSender, "", "HTTP server not started, disabled in config file")
|
||||||
|
logger.DebugToConsole("HTTP server not started, disabled in config file")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait blocks until the service exits
|
||||||
|
func (s *Service) Wait() {
|
||||||
|
<-s.Shutdown
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop terminates the service and unblocks the Wait method
|
||||||
|
func (s *Service) Stop() {
|
||||||
|
close(s.Shutdown)
|
||||||
|
logger.Debug(logSender, "", "Service stopped")
|
||||||
|
}
|
||||||
271
service/service_windows.go
Normal file
271
service/service_windows.go
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/drakkan/sftpgo/logger"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows/svc"
|
||||||
|
"golang.org/x/sys/windows/svc/eventlog"
|
||||||
|
"golang.org/x/sys/windows/svc/mgr"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
serviceName = "SFTPGo"
|
||||||
|
serviceDesc = "Full featured and highly configurable SFTP server"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Status defines service status
|
||||||
|
type Status uint8
|
||||||
|
|
||||||
|
// Supported values for service status
|
||||||
|
const (
|
||||||
|
StatusUnknown Status = iota
|
||||||
|
StatusRunning
|
||||||
|
StatusStopped
|
||||||
|
StatusPaused
|
||||||
|
StatusStartPending
|
||||||
|
StatusPausePending
|
||||||
|
StatusContinuePending
|
||||||
|
StatusStopPending
|
||||||
|
)
|
||||||
|
|
||||||
|
type WindowsService struct {
|
||||||
|
Service Service
|
||||||
|
isInteractive bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Status) String() string {
|
||||||
|
switch s {
|
||||||
|
case StatusRunning:
|
||||||
|
return "running"
|
||||||
|
case StatusStopped:
|
||||||
|
return "stopped"
|
||||||
|
case StatusStartPending:
|
||||||
|
return "start pending"
|
||||||
|
case StatusPausePending:
|
||||||
|
return "pause pending"
|
||||||
|
case StatusPaused:
|
||||||
|
return "paused"
|
||||||
|
case StatusContinuePending:
|
||||||
|
return "continue pending"
|
||||||
|
case StatusStopPending:
|
||||||
|
return "stop pending"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *WindowsService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
|
||||||
|
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
|
||||||
|
changes <- svc.Status{State: svc.StartPending}
|
||||||
|
if err := s.Service.Start(); err != nil {
|
||||||
|
return true, 1
|
||||||
|
}
|
||||||
|
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
c := <-r
|
||||||
|
switch c.Cmd {
|
||||||
|
case svc.Interrogate:
|
||||||
|
logger.Debug(logSender, "", "Received service interrogate request, current status: %v", c.CurrentStatus)
|
||||||
|
changes <- c.CurrentStatus
|
||||||
|
case svc.Stop, svc.Shutdown:
|
||||||
|
logger.Debug(logSender, "", "Received service stop request")
|
||||||
|
changes <- svc.Status{State: svc.StopPending}
|
||||||
|
s.Service.Stop()
|
||||||
|
break loop
|
||||||
|
default:
|
||||||
|
continue loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *WindowsService) RunService() error {
|
||||||
|
exepath, err := s.getExecPath()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
isIntSess, err := svc.IsAnInteractiveSession()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.isInteractive = isIntSess
|
||||||
|
dir := filepath.Dir(exepath)
|
||||||
|
if err = os.Chdir(dir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s.isInteractive {
|
||||||
|
return s.Start()
|
||||||
|
}
|
||||||
|
return svc.Run(serviceName, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *WindowsService) Start() error {
|
||||||
|
m, err := mgr.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer m.Disconnect()
|
||||||
|
service, err := m.OpenService(serviceName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not access service: %v", err)
|
||||||
|
}
|
||||||
|
defer service.Close()
|
||||||
|
err = service.Start()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not start service: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *WindowsService) Install(args ...string) error {
|
||||||
|
exepath, err := s.getExecPath()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m, err := mgr.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer m.Disconnect()
|
||||||
|
service, err := m.OpenService(serviceName)
|
||||||
|
if err == nil {
|
||||||
|
service.Close()
|
||||||
|
return fmt.Errorf("service %s already exists", serviceName)
|
||||||
|
}
|
||||||
|
config := mgr.Config{
|
||||||
|
DisplayName: serviceName,
|
||||||
|
Description: serviceDesc,
|
||||||
|
StartType: mgr.StartAutomatic}
|
||||||
|
service, err = m.CreateService(serviceName, exepath, config, args...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer service.Close()
|
||||||
|
err = eventlog.InstallAsEventCreate(serviceName, eventlog.Error|eventlog.Warning|eventlog.Info)
|
||||||
|
if err != nil {
|
||||||
|
if !strings.Contains(err.Error(), "exists") {
|
||||||
|
service.Delete()
|
||||||
|
return fmt.Errorf("SetupEventLogSource() failed: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recoveryActions := []mgr.RecoveryAction{
|
||||||
|
mgr.RecoveryAction{
|
||||||
|
Type: mgr.ServiceRestart,
|
||||||
|
Delay: 0,
|
||||||
|
},
|
||||||
|
mgr.RecoveryAction{
|
||||||
|
Type: mgr.ServiceRestart,
|
||||||
|
Delay: 60 * time.Second,
|
||||||
|
},
|
||||||
|
mgr.RecoveryAction{
|
||||||
|
Type: mgr.ServiceRestart,
|
||||||
|
Delay: 90 * time.Second,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err = service.SetRecoveryActions(recoveryActions, uint32(86400))
|
||||||
|
if err != nil {
|
||||||
|
service.Delete()
|
||||||
|
return fmt.Errorf("unable to set recovery actions: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *WindowsService) Uninstall() error {
|
||||||
|
m, err := mgr.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer m.Disconnect()
|
||||||
|
service, err := m.OpenService(serviceName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("service %s is not installed", serviceName)
|
||||||
|
}
|
||||||
|
defer service.Close()
|
||||||
|
err = service.Delete()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = eventlog.Remove(serviceName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("RemoveEventLogSource() failed: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *WindowsService) Stop() error {
|
||||||
|
m, err := mgr.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer m.Disconnect()
|
||||||
|
service, err := m.OpenService(serviceName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not access service: %v", err)
|
||||||
|
}
|
||||||
|
defer service.Close()
|
||||||
|
status, err := service.Control(svc.Stop)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not send control=%d: %v", svc.Stop, err)
|
||||||
|
}
|
||||||
|
timeout := time.Now().Add(10 * time.Second)
|
||||||
|
for status.State != svc.Stopped {
|
||||||
|
if timeout.Before(time.Now()) {
|
||||||
|
return fmt.Errorf("timeout waiting for service to go to state=%d", svc.Stopped)
|
||||||
|
}
|
||||||
|
time.Sleep(300 * time.Millisecond)
|
||||||
|
status, err = service.Query()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not retrieve service status: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *WindowsService) Status() (Status, error) {
|
||||||
|
m, err := mgr.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return StatusUnknown, err
|
||||||
|
}
|
||||||
|
defer m.Disconnect()
|
||||||
|
service, err := m.OpenService(serviceName)
|
||||||
|
if err != nil {
|
||||||
|
return StatusUnknown, fmt.Errorf("could not access service: %v", err)
|
||||||
|
}
|
||||||
|
defer service.Close()
|
||||||
|
status, err := service.Query()
|
||||||
|
if err != nil {
|
||||||
|
return StatusUnknown, fmt.Errorf("could not query service status: %v", err)
|
||||||
|
}
|
||||||
|
switch status.State {
|
||||||
|
case svc.StartPending:
|
||||||
|
return StatusStartPending, nil
|
||||||
|
case svc.Running:
|
||||||
|
return StatusRunning, nil
|
||||||
|
case svc.PausePending:
|
||||||
|
return StatusPausePending, nil
|
||||||
|
case svc.Paused:
|
||||||
|
return StatusPaused, nil
|
||||||
|
case svc.ContinuePending:
|
||||||
|
return StatusContinuePending, nil
|
||||||
|
case svc.StopPending:
|
||||||
|
return StatusStopPending, nil
|
||||||
|
case svc.Stopped:
|
||||||
|
return StatusStopped, nil
|
||||||
|
default:
|
||||||
|
return StatusUnknown, fmt.Errorf("unknown status %v", status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *WindowsService) getExecPath() (string, error) {
|
||||||
|
return os.Executable()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user