diff --git a/service/internal/config/config.go b/service/internal/config/config.go index fadb024..95f2312 100644 --- a/service/internal/config/config.go +++ b/service/internal/config/config.go @@ -147,6 +147,7 @@ type Config struct { DefaultIconForDirectories string DefaultIconForBack string AdditionalNavigationLinks []*NavigationLink + ServiceHostMode string usedConfigDir string } @@ -245,6 +246,7 @@ func DefaultConfigWithBasePort(basePort int) *Config { config.DefaultIconForDirectories = "📁" config.DefaultIconForBack = "«" config.ThemeCacheDisabled = false + config.ServiceHostMode = "" config.ListenAddressSingleHTTPFrontend = fmt.Sprintf("0.0.0.0:%d", basePort) config.ListenAddressRestActions = fmt.Sprintf("localhost:%d", basePort+1) diff --git a/service/internal/servicehost/servicehost_nonwin.go b/service/internal/servicehost/servicehost_nonwin.go new file mode 100644 index 0000000..8ecd407 --- /dev/null +++ b/service/internal/servicehost/servicehost_nonwin.go @@ -0,0 +1,17 @@ +//go:build !windows +// +build !windows + +package servicehost + +import ( + log "github.com/sirupsen/logrus" +) + +func Start(mode string) { + log.Debugf("servicehost nonwin") +} + +func GetConfigFilePath() string { + log.Debugf("servicehost nonwin") + return "../" +} diff --git a/service/internal/servicehost/servicehost_windows.go b/service/internal/servicehost/servicehost_windows.go new file mode 100644 index 0000000..607ddb0 --- /dev/null +++ b/service/internal/servicehost/servicehost_windows.go @@ -0,0 +1,138 @@ +//go:build windows +// +build windows + +package servicehost + +import ( + log "github.com/sirupsen/logrus" + + "fmt" + "golang.org/x/sys/windows/svc" + "golang.org/x/sys/windows/svc/debug" + "os" + "path" + "path/filepath" + "time" +) + +type otWindowsService struct{} + +//gocyclo:ignore +func (m *otWindowsService) Execute(args []string, r <-chan svc.ChangeRequest, status chan<- svc.Status) (svcSpecificExitCode bool, exitCode uint32) { + const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue + + tick := time.Tick(1 * time.Minute) + + status <- svc.Status{State: svc.StartPending} + status <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} + + log.Info("Servicehost started") + + for { + select { + case <-tick: + log.Debug("servicehost Tick") + case c := <-r: + switch c.Cmd { + case svc.Interrogate: + status <- c.CurrentStatus + case svc.Stop, svc.Shutdown: + log.Info("Stopping service") + return false, 0 + case svc.Pause: + status <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted} + case svc.Continue: + status <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} + default: + log.Infof("Unexpected control request: %d", c.Cmd) + } + } + } +} + +func setupLogging() { + logsDir := path.Join(GetConfigFilePath(), "logs") + + os.MkdirAll(logsDir, 0755) + + timestamp := time.Now().Format("2006-01-02_15-04-05") + + filename := path.Join(logsDir, fmt.Sprintf("OliveTin-service-%v.log", timestamp)) + + log.Infof("Setting up logging to file: %v", filename) + + f, err := os.Create(filename) + + if err != nil { + log.Infof("Failed to open log file: %v", err) + } else { + log.Infof("Switching to log file: %v", f.Name()) + log.SetOutput(f) + log.Infof("Opened log file: %v", f.Name()) + } +} + +func GetConfigFilePath() string { + programDataDir := path.Join(os.Getenv("ProgramData"), "OliveTin") + + _, err := os.Stat(programDataDir) + + if os.IsNotExist(err) { + os.MkdirAll(programDataDir, 0755) + } + + return programDataDir +} + +func cdToExecutableDir() { + ex, err := os.Executable() + if err != nil { + panic(fmt.Sprintf("Failed to get executable path: %s", err)) + } + + exPath := filepath.Dir(ex) + + err = os.Chdir(exPath) + + if err != nil { + panic(fmt.Sprintf("Failed to change directory to executable path: %s", err)) + } +} + +func startServiceHandler(mode string) { + cdToExecutableDir() + + const serviceName = "OliveTin" + + var err error + + switch mode { + case "winsvc-debug": + log.Infof("Running Windows service in debug mode") + + err = debug.Run(serviceName, &otWindowsService{}) + case "winsvc-standard": + log.Infof("Running Windows service in standard mode") + + err = svc.Run(serviceName, &otWindowsService{}) + case "": + return + default: + log.Fatalf("Unknown servicehost service mode: %s", mode) + } + + if err != nil { + log.Fatalf("Failed to run service: %v", err) + } + + log.Infof("Servicehost handler completed") + + os.Exit(0) + +} + +func Start(mode string) { + setupLogging() + + go startServiceHandler(mode) +} diff --git a/service/main.go b/service/main.go index 3e65289..326de69 100644 --- a/service/main.go +++ b/service/main.go @@ -14,6 +14,7 @@ import ( "github.com/OliveTin/OliveTin/internal/oncron" "github.com/OliveTin/OliveTin/internal/onfileindir" "github.com/OliveTin/OliveTin/internal/onstartup" + "github.com/OliveTin/OliveTin/internal/servicehost" updatecheck "github.com/OliveTin/OliveTin/internal/updatecheck" "github.com/OliveTin/OliveTin/internal/websocket" @@ -108,7 +109,7 @@ func initViperConfig(configDir string) { viper.SetConfigName("config.yaml") viper.SetConfigType("yaml") viper.AddConfigPath(configDir) - viper.AddConfigPath("../") + viper.AddConfigPath(servicehost.GetConfigFilePath()) viper.AddConfigPath("/config") // For containers. viper.AddConfigPath("/etc/OliveTin/") @@ -154,6 +155,8 @@ func warnIfPuidGuid() { } func main() { + servicehost.Start(cfg.ServiceHostMode) + log.WithFields(log.Fields{ "configDir": cfg.GetDir(), }).Infof("OliveTin started")