diff --git a/server/cmd/win/debug.go b/server/cmd/win/debug.go new file mode 100644 index 00000000..206ce25b --- /dev/null +++ b/server/cmd/win/debug.go @@ -0,0 +1,31 @@ +// Copyright 2017-2020 The ShadowEditor Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// +// For more information, please visit: https://github.com/tengge1/ShadowEditor +// You can also visit: https://gitee.com/tengge1/ShadowEditor + +// Reference URL: https://github.com/andrewkroh/sys/blob/master/windows/svc/example/main.go + +// +build windows + +package win + +import ( + "github.com/spf13/cobra" + "github.com/tengge1/shadoweditor/cmd" +) + +// debugCmd debug ShadowEditor service on Windows. +var debugCmd = &cobra.Command{ + Use: "debug", + Short: "debug ShadowEditor service", + Long: `Debug ShadowEditor service on Windows.`, + Run: func(cmd *cobra.Command, args []string) { + runService(ServiceName, true) + }, +} + +func init() { + cmd.AddCommand(installCmd) +} diff --git a/server/cmd/win/install.go b/server/cmd/win/install.go new file mode 100644 index 00000000..a4a23a64 --- /dev/null +++ b/server/cmd/win/install.go @@ -0,0 +1,93 @@ +// Copyright 2017-2020 The ShadowEditor Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// +// For more information, please visit: https://github.com/tengge1/ShadowEditor +// You can also visit: https://gitee.com/tengge1/ShadowEditor + +// Reference URL: https://github.com/andrewkroh/sys/blob/master/windows/svc/example/install.go + +// +build windows + +package win + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/spf13/cobra" + "github.com/tengge1/shadoweditor/cmd" + "golang.org/x/sys/windows/svc/eventlog" + "golang.org/x/sys/windows/svc/mgr" +) + +// installCmd install ShadowEditor as a service on Windows. +var installCmd = &cobra.Command{ + Use: "install", + Short: "install ShadowEditor as a service", + Long: `Install ShadowEditor as a service on Windows.`, + Run: func(cmd *cobra.Command, args []string) { + if err := installService(ServiceName, ServiceDisplayName); err != nil { + fmt.Println(err.Error()) + } + }, +} + +func init() { + cmd.AddCommand(installCmd) +} + +func installService(name, desc string) error { + exepath, err := exePath() + if err != nil { + return err + } + m, err := mgr.Connect() + if err != nil { + return err + } + defer m.Disconnect() + s, err := m.OpenService(name) + if err == nil { + s.Close() + return fmt.Errorf("service %s already exists", name) + } + s, err = m.CreateService(name, exepath, mgr.Config{DisplayName: desc}, "is", "auto-started") + if err != nil { + return err + } + defer s.Close() + err = eventlog.InstallAsEventCreate(name, eventlog.Error|eventlog.Warning|eventlog.Info) + if err != nil { + s.Delete() + return fmt.Errorf("SetupEventLogSource() failed: %s", err) + } + return nil +} + +func exePath() (string, error) { + prog := os.Args[0] + p, err := filepath.Abs(prog) + if err != nil { + return "", err + } + fi, err := os.Stat(p) + if err == nil { + if !fi.Mode().IsDir() { + return p, nil + } + err = fmt.Errorf("%s is directory", p) + } + if filepath.Ext(p) == "" { + p += ".exe" + fi, err := os.Stat(p) + if err == nil { + if !fi.Mode().IsDir() { + return p, nil + } + err = fmt.Errorf("%s is directory", p) + } + } + return "", err +} diff --git a/server/cmd/win/remove.go b/server/cmd/win/remove.go new file mode 100644 index 00000000..5f043665 --- /dev/null +++ b/server/cmd/win/remove.go @@ -0,0 +1,59 @@ +// Copyright 2017-2020 The ShadowEditor Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// +// For more information, please visit: https://github.com/tengge1/ShadowEditor +// You can also visit: https://gitee.com/tengge1/ShadowEditor + +// Reference URL: https://github.com/andrewkroh/sys/blob/master/windows/svc/example/install.go + +// +build windows + +package win + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/tengge1/shadoweditor/cmd" + "golang.org/x/sys/windows/svc/eventlog" + "golang.org/x/sys/windows/svc/mgr" +) + +// removeCmd remove ShadowEditor service on Windows. +var removeCmd = &cobra.Command{ + Use: "remove", + Short: "remove ShadowEditor service", + Long: `Remove ShadowEditor service on Windows.`, + Run: func(cmd *cobra.Command, args []string) { + if err := removeService(ServiceName); err != nil { + fmt.Println(err.Error()) + } + }, +} + +func init() { + cmd.AddCommand(removeCmd) +} + +func removeService(name string) error { + m, err := mgr.Connect() + if err != nil { + return err + } + defer m.Disconnect() + s, err := m.OpenService(name) + if err != nil { + return fmt.Errorf("service %s is not installed", name) + } + defer s.Close() + err = s.Delete() + if err != nil { + return err + } + err = eventlog.Remove(name) + if err != nil { + return fmt.Errorf("RemoveEventLogSource() failed: %s", err) + } + return nil +} diff --git a/server/cmd/win/start.go b/server/cmd/win/start.go new file mode 100644 index 00000000..b45c3887 --- /dev/null +++ b/server/cmd/win/start.go @@ -0,0 +1,54 @@ +// Copyright 2017-2020 The ShadowEditor Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// +// For more information, please visit: https://github.com/tengge1/ShadowEditor +// You can also visit: https://gitee.com/tengge1/ShadowEditor + +// Reference URL: https://github.com/andrewkroh/sys/blob/master/windows/svc/example/manage.go + +// +build windows + +package win + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/tengge1/shadoweditor/cmd" + "golang.org/x/sys/windows/svc/mgr" +) + +// startCmd start ShadowEditor service on Windows. +var startCmd = &cobra.Command{ + Use: "start", + Short: "start ShadowEditor service", + Long: `Start ShadowEditor service on Windows.`, + Run: func(cmd *cobra.Command, args []string) { + if err := startService(ServiceName); err != nil { + fmt.Println(err.Error()) + } + }, +} + +func init() { + cmd.AddCommand(startCmd) +} + +func startService(name string) error { + m, err := mgr.Connect() + if err != nil { + return err + } + defer m.Disconnect() + s, err := m.OpenService(name) + if err != nil { + return fmt.Errorf("could not access service: %v", err) + } + defer s.Close() + err = s.Start("is", "manual-started") + if err != nil { + return fmt.Errorf("could not start service: %v", err) + } + return nil +} diff --git a/server/cmd/win/stop.go b/server/cmd/win/stop.go new file mode 100644 index 00000000..ef2546f9 --- /dev/null +++ b/server/cmd/win/stop.go @@ -0,0 +1,67 @@ +// Copyright 2017-2020 The ShadowEditor Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// +// For more information, please visit: https://github.com/tengge1/ShadowEditor +// You can also visit: https://gitee.com/tengge1/ShadowEditor + +// Reference URL: https://github.com/andrewkroh/sys/blob/master/windows/svc/example/manage.go + +// +build windows + +package win + +import ( + "fmt" + "time" + + "github.com/spf13/cobra" + "github.com/tengge1/shadoweditor/cmd" + "golang.org/x/sys/windows/svc" + "golang.org/x/sys/windows/svc/mgr" +) + +// stopCmd stop ShadowEditor service on Windows. +var stopCmd = &cobra.Command{ + Use: "stop", + Short: "stop ShadowEditor service", + Long: `Stop ShadowEditor service on Windows.`, + Run: func(cmd *cobra.Command, args []string) { + if err := controlService(ServiceName, svc.Stop, svc.Stopped); err != nil { + fmt.Println(err.Error()) + } + }, +} + +func init() { + cmd.AddCommand(stopCmd) +} + +func controlService(name string, c svc.Cmd, to svc.State) error { + m, err := mgr.Connect() + if err != nil { + return err + } + defer m.Disconnect() + s, err := m.OpenService(name) + if err != nil { + return fmt.Errorf("could not access service: %v", err) + } + defer s.Close() + status, err := s.Control(c) + if err != nil { + return fmt.Errorf("could not send control=%d: %v", c, err) + } + timeout := time.Now().Add(10 * time.Second) + for status.State != to { + if timeout.Before(time.Now()) { + return fmt.Errorf("timeout waiting for service to go to state=%d", to) + } + time.Sleep(300 * time.Millisecond) + status, err = s.Query() + if err != nil { + return fmt.Errorf("could not retrieve service status: %v", err) + } + } + return nil +} diff --git a/server/cmd/win/win.go b/server/cmd/win/win.go new file mode 100644 index 00000000..8547488b --- /dev/null +++ b/server/cmd/win/win.go @@ -0,0 +1,89 @@ +// Copyright 2017-2020 The ShadowEditor Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// +// For more information, please visit: https://github.com/tengge1/ShadowEditor +// You can also visit: https://gitee.com/tengge1/ShadowEditor + +// Reference URL: https://github.com/andrewkroh/sys/blob/master/windows/svc/example/service.go + +// +build windows + +package win + +import ( + "fmt" + "strings" + "time" + + "golang.org/x/sys/windows/svc" + "golang.org/x/sys/windows/svc/debug" + "golang.org/x/sys/windows/svc/eventlog" +) + +const ( + // ServiceName is the default service name on Windows. + ServiceName = "ShadowEditor" + // ServiceDisplayName is the name show in the Service Manager. + ServiceDisplayName = "Shadow Editor" +) + +var elog debug.Log + +// Service is the ShadowEditor service model. +type Service struct{} + +// Execute execute the ShadowEditor service. +func (m *Service) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) { + const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue + changes <- svc.Status{State: svc.StartPending} + changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} +loop: + for { + select { + case c := <-r: + switch c.Cmd { + case svc.Interrogate: + changes <- c.CurrentStatus + // Testing deadlock from https://code.google.com/p/winsvc/issues/detail?id=4 + time.Sleep(100 * time.Millisecond) + changes <- c.CurrentStatus + case svc.Stop, svc.Shutdown: + // golang.org/x/sys/windows/svc.TestExample is verifying this output. + testOutput := strings.Join(args, "-") + testOutput += fmt.Sprintf("-%d", c.Context) + elog.Info(1, testOutput) + break loop + default: + elog.Error(1, fmt.Sprintf("unexpected control request #%d", c)) + } + } + } + changes <- svc.Status{State: svc.StopPending} + return +} + +func runService(name string, isDebug bool) { + var err error + if isDebug { + elog = debug.New(name) + } else { + elog, err = eventlog.Open(name) + if err != nil { + return + } + } + defer elog.Close() + + elog.Info(1, fmt.Sprintf("starting %s service", name)) + run := svc.Run + if isDebug { + run = debug.Run + } + err = run(name, &Service{}) + if err != nil { + elog.Error(1, fmt.Sprintf("%s service failed: %v", name, err)) + return + } + elog.Info(1, fmt.Sprintf("%s service stopped", name)) +}