app-init
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
package winsvc
|
||||
|
||||
const (
|
||||
Name = "mysql-tunnel"
|
||||
DisplayName = "MySQL Tunnel"
|
||||
Description = "Local MySQL proxy that translates MySQL protocol to Navicat HTTP Tunnel for Yingdao RPA."
|
||||
)
|
||||
@@ -0,0 +1,40 @@
|
||||
//go:build !windows
|
||||
|
||||
package winsvc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func IsService() bool { return false }
|
||||
|
||||
func Run(func(ctx context.Context) error) error {
|
||||
return fmt.Errorf("Windows service is only supported on Windows")
|
||||
}
|
||||
|
||||
func Install(string, string) error { return fmt.Errorf("Windows service is only supported on Windows") }
|
||||
func Uninstall() error { return fmt.Errorf("Windows service is only supported on Windows") }
|
||||
func Start() error { return fmt.Errorf("Windows service is only supported on Windows") }
|
||||
func Stop() error { return fmt.Errorf("Windows service is only supported on Windows") }
|
||||
|
||||
func ExePath() (string, error) {
|
||||
p, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.EvalSymlinks(p)
|
||||
}
|
||||
|
||||
func CopyFile(src, dst string) error {
|
||||
in, err := os.ReadFile(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0o750); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(dst, in, 0o600)
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
//go:build windows
|
||||
|
||||
package winsvc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/svc"
|
||||
"golang.org/x/sys/windows/svc/mgr"
|
||||
)
|
||||
|
||||
func IsService() bool {
|
||||
ok, err := svc.IsWindowsService()
|
||||
return err == nil && ok
|
||||
}
|
||||
|
||||
func Run(run func(ctx context.Context) error) error {
|
||||
return svc.Run(Name, &handler{run: run})
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
run func(ctx context.Context) error
|
||||
}
|
||||
|
||||
func (h *handler) Execute(_ []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) {
|
||||
changes <- svc.Status{State: svc.StartPending}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
errCh <- h.run(ctx)
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-errCh:
|
||||
if err != nil {
|
||||
return true, 1
|
||||
}
|
||||
return false, 0
|
||||
case <-time.After(800 * time.Millisecond):
|
||||
changes <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case err := <-errCh:
|
||||
if err != nil {
|
||||
return true, 1
|
||||
}
|
||||
return false, 0
|
||||
case c := <-r:
|
||||
switch c.Cmd {
|
||||
case svc.Interrogate:
|
||||
changes <- c.CurrentStatus
|
||||
case svc.Stop, svc.Shutdown:
|
||||
changes <- svc.Status{State: svc.StopPending}
|
||||
cancel()
|
||||
select {
|
||||
case <-errCh:
|
||||
case <-time.After(8 * time.Second):
|
||||
}
|
||||
return false, 0
|
||||
default:
|
||||
changes <- c.CurrentStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Install(exePath, configPath string) error {
|
||||
m, err := mgr.Connect()
|
||||
if err != nil {
|
||||
return wrapAccess(err, "connect to service manager")
|
||||
}
|
||||
defer m.Disconnect()
|
||||
|
||||
if s, err := m.OpenService(Name); err == nil {
|
||||
s.Close()
|
||||
return fmt.Errorf("service %s already exists; run --uninstall first", Name)
|
||||
}
|
||||
|
||||
s, err := m.CreateService(Name, exePath, mgr.Config{
|
||||
DisplayName: DisplayName,
|
||||
Description: Description,
|
||||
StartType: mgr.StartAutomatic,
|
||||
ErrorControl: mgr.ErrorNormal,
|
||||
}, "--config", configPath)
|
||||
if err != nil {
|
||||
return wrapAccess(err, "create service")
|
||||
}
|
||||
defer s.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func Uninstall() error {
|
||||
m, err := mgr.Connect()
|
||||
if err != nil {
|
||||
return wrapAccess(err, "connect to service manager")
|
||||
}
|
||||
defer m.Disconnect()
|
||||
s, err := m.OpenService(Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("service %s not installed", Name)
|
||||
}
|
||||
defer s.Close()
|
||||
st, err := s.Query()
|
||||
if err == nil && st.State != svc.Stopped {
|
||||
_ = control(s, svc.Stop, svc.Stopped)
|
||||
}
|
||||
if err := s.Delete(); err != nil {
|
||||
return wrapAccess(err, "delete service")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Start() error {
|
||||
m, err := mgr.Connect()
|
||||
if err != nil {
|
||||
return wrapAccess(err, "connect to service manager")
|
||||
}
|
||||
defer m.Disconnect()
|
||||
s, err := m.OpenService(Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("service %s not installed", Name)
|
||||
}
|
||||
defer s.Close()
|
||||
if err := s.Start(); err != nil {
|
||||
if err == windows.ERROR_SERVICE_ALREADY_RUNNING {
|
||||
return nil
|
||||
}
|
||||
return wrapAccess(err, "start service")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Stop() error {
|
||||
m, err := mgr.Connect()
|
||||
if err != nil {
|
||||
return wrapAccess(err, "connect to service manager")
|
||||
}
|
||||
defer m.Disconnect()
|
||||
s, err := m.OpenService(Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("service %s not installed", Name)
|
||||
}
|
||||
defer s.Close()
|
||||
st, err := s.Query()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if st.State == svc.Stopped {
|
||||
return nil
|
||||
}
|
||||
return control(s, svc.Stop, svc.Stopped)
|
||||
}
|
||||
|
||||
func control(s *mgr.Service, c svc.Cmd, to svc.State) error {
|
||||
status, err := s.Control(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deadline := time.Now().Add(10 * time.Second)
|
||||
for status.State != to {
|
||||
if time.Now().After(deadline) {
|
||||
return fmt.Errorf("timeout waiting for service state %v", to)
|
||||
}
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
status, err = s.Query()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func wrapAccess(err error, op string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if err == windows.ERROR_ACCESS_DENIED {
|
||||
return fmt.Errorf("%s: access denied; run as Administrator", op)
|
||||
}
|
||||
return fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
|
||||
func ExePath() (string, error) {
|
||||
p, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if r, err := filepath.EvalSymlinks(p); err == nil {
|
||||
return r, nil
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func CopyFile(src, dst string) error {
|
||||
in, err := os.ReadFile(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0o750); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(dst, in, 0o600)
|
||||
}
|
||||
Reference in New Issue
Block a user