feat: migrate to golang
This commit is contained in:
137
internal/service/wrapper_windows.go
Normal file
137
internal/service/wrapper_windows.go
Normal file
@ -0,0 +1,137 @@
|
||||
//go:build windows
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/windows/svc"
|
||||
)
|
||||
|
||||
// windowsServiceHandler implements svc.Handler, hosting the mihomo
|
||||
// subprocess directly inside the ss binary. Replaces
|
||||
// windows_service_wrapper.py + pywin32 + the per-service JSON config file:
|
||||
// the equivalent configuration (bin path + args) is now passed as command
|
||||
// line flags to `ss service run-windows`, and this same binary registers
|
||||
// itself as the service via golang.org/x/sys/windows/svc.
|
||||
type windowsServiceHandler struct {
|
||||
name string
|
||||
binPath string
|
||||
args string
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func (h *windowsServiceHandler) Execute(_ []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) {
|
||||
const accepted = svc.AcceptStop | svc.AcceptShutdown
|
||||
|
||||
changes <- svc.Status{State: svc.StartPending}
|
||||
|
||||
var cmdArgs []string
|
||||
if h.args != "" {
|
||||
split, err := shlexSplit(h.args)
|
||||
if err != nil {
|
||||
h.logger.Printf("failed to parse service args %q: %v", h.args, err)
|
||||
return false, 1
|
||||
}
|
||||
cmdArgs = split
|
||||
}
|
||||
|
||||
cmd := exec.Command(h.binPath, cmdArgs...)
|
||||
cmd.Dir = filepath.Dir(h.binPath)
|
||||
cmd.Stdout = &logWriter{h.logger, "stdout"}
|
||||
cmd.Stderr = &logWriter{h.logger, "stderr"}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
h.logger.Printf("failed to launch %s: %v", h.binPath, err)
|
||||
return false, 1
|
||||
}
|
||||
h.logger.Printf("started process PID %d", cmd.Process.Pid)
|
||||
|
||||
changes <- svc.Status{State: svc.Running, Accepts: accepted}
|
||||
|
||||
exited := make(chan error, 1)
|
||||
go func() { exited <- cmd.Wait() }()
|
||||
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case err := <-exited:
|
||||
h.logger.Printf("process exited: %v", err)
|
||||
break loop
|
||||
case req := <-r:
|
||||
switch req.Cmd {
|
||||
case svc.Interrogate:
|
||||
changes <- req.CurrentStatus
|
||||
case svc.Stop, svc.Shutdown:
|
||||
h.logger.Printf("stop requested")
|
||||
changes <- svc.Status{State: svc.StopPending}
|
||||
h.terminate(cmd)
|
||||
<-exited
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changes <- svc.Status{State: svc.Stopped}
|
||||
return false, 0
|
||||
}
|
||||
|
||||
func (h *windowsServiceHandler) terminate(cmd *exec.Cmd) {
|
||||
if cmd.Process == nil {
|
||||
return
|
||||
}
|
||||
if err := cmd.Process.Kill(); err != nil {
|
||||
h.logger.Printf("error terminating process: %v", err)
|
||||
return
|
||||
}
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
cmd.Wait()
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
h.logger.Printf("process terminated")
|
||||
case <-time.After(30 * time.Second):
|
||||
h.logger.Printf("process did not terminate within timeout")
|
||||
}
|
||||
}
|
||||
|
||||
type logWriter struct {
|
||||
logger *log.Logger
|
||||
stream string
|
||||
}
|
||||
|
||||
func (w *logWriter) Write(p []byte) (int, error) {
|
||||
for _, line := range strings.Split(strings.TrimRight(string(p), "\r\n"), "\n") {
|
||||
if strings.TrimSpace(line) != "" {
|
||||
w.logger.Printf("[%s] %s", w.stream, line)
|
||||
}
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// RunWindowsService registers this process as the named Windows service and
|
||||
// blocks until the service control manager stops it. Invoked via the hidden
|
||||
// `ss service run-windows --name <name> --bin <binPath> --args <args>`
|
||||
// command that Install() points sc.exe's binPath= at.
|
||||
func RunWindowsService(name, binPath, args string) error {
|
||||
logPath := filepath.Join(filepath.Dir(binPath), name+"_service.log")
|
||||
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open service log file %s: %w", logPath, err)
|
||||
}
|
||||
defer logFile.Close()
|
||||
|
||||
logger := log.New(logFile, "", log.LstdFlags)
|
||||
logger.Printf("service %q starting, bin=%s args=%q", name, binPath, args)
|
||||
|
||||
handler := &windowsServiceHandler{name: name, binPath: binPath, args: args, logger: logger}
|
||||
return svc.Run(name, handler)
|
||||
}
|
||||
Reference in New Issue
Block a user