win service

This commit is contained in:
tengge 2020-06-22 21:09:36 +08:00
parent 0cb8004ede
commit b4cafc22a6
6 changed files with 393 additions and 0 deletions

31
server/cmd/win/debug.go Normal file
View File

@ -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)
}

93
server/cmd/win/install.go Normal file
View File

@ -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
}

59
server/cmd/win/remove.go Normal file
View File

@ -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
}

54
server/cmd/win/start.go Normal file
View File

@ -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
}

67
server/cmd/win/stop.go Normal file
View File

@ -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
}

89
server/cmd/win/win.go Normal file
View File

@ -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))
}