mirror of
https://github.com/MetaCubeX/Clash.Meta.git
synced 2025-04-20 01:00:56 +00:00
Basic support system service mode
todo: Log redirection and parameter forwarding
This commit is contained in:
parent
d2d8c0115c
commit
9bb52164ed
7 changed files with 235 additions and 5 deletions
1
go.mod
1
go.mod
|
@ -81,6 +81,7 @@ require (
|
|||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/kardianos/service v1.2.2 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
|
|
3
go.sum
3
go.sum
|
@ -86,6 +86,8 @@ github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtL
|
|||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60=
|
||||
github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||
|
@ -246,6 +248,7 @@ golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
|
25
main.go
25
main.go
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/metacubex/mihomo/service"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
|
@ -23,9 +24,12 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
version bool
|
||||
testConfig bool
|
||||
geodataMode bool
|
||||
version bool
|
||||
testConfig bool
|
||||
geodataMode bool
|
||||
|
||||
serviceMode string
|
||||
|
||||
homeDir string
|
||||
configFile string
|
||||
externalUI string
|
||||
|
@ -44,11 +48,18 @@ func init() {
|
|||
flag.BoolVar(&geodataMode, "m", false, "set geodata mode")
|
||||
flag.BoolVar(&version, "v", false, "show current version of mihomo")
|
||||
flag.BoolVar(&testConfig, "t", false, "test configuration and exit")
|
||||
flag.StringVar(&serviceMode, "service", "run", "control mihomo service, available options: [run |install |uninstall | start | stop | restart | status]")
|
||||
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
service.Parser(serviceMode, RealMain)
|
||||
}
|
||||
|
||||
func RealMain() {
|
||||
_, _ = maxprocs.Set(maxprocs.Logger(func(string, ...any) {}))
|
||||
|
||||
if version {
|
||||
fmt.Printf("Mihomo Meta %s %s %s with %s %s\n",
|
||||
C.Version, runtime.GOOS, runtime.GOARCH, runtime.Version(), C.BuildTime)
|
||||
|
@ -56,7 +67,7 @@ func main() {
|
|||
fmt.Printf("Use tags: %s\n", strings.Join(tags, ", "))
|
||||
}
|
||||
|
||||
return
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if homeDir != "" {
|
||||
|
@ -85,6 +96,10 @@ func main() {
|
|||
log.Fatalln("Initial configuration directory error: %s", err.Error())
|
||||
}
|
||||
|
||||
if service.Noninteractive {
|
||||
service.SetLogger(C.Path.HomeDir())
|
||||
}
|
||||
|
||||
if testConfig {
|
||||
if _, err := executor.Parse(); err != nil {
|
||||
log.Errorln(err.Error())
|
||||
|
@ -92,7 +107,7 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("configuration file %s test is successful\n", C.Path.Config())
|
||||
return
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
var options []hub.Option
|
||||
|
|
30
service/control.go
Normal file
30
service/control.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"github.com/kardianos/service"
|
||||
"github.com/metacubex/mihomo/hub/executor"
|
||||
"os"
|
||||
)
|
||||
|
||||
func (p *program) Start(s service.Service) error {
|
||||
// Start should not block. Do the actual work async.
|
||||
go mainFunc()
|
||||
return nil
|
||||
}
|
||||
|
||||
//
|
||||
//func (p *program) run() {
|
||||
// // Do work here
|
||||
//
|
||||
//}
|
||||
|
||||
func (p *program) Stop(s service.Service) error {
|
||||
// Stop should not block. Return with a few seconds.
|
||||
go p.exit()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *program) exit() {
|
||||
executor.Shutdown()
|
||||
os.Exit(0)
|
||||
}
|
69
service/parser.go
Normal file
69
service/parser.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"github.com/kardianos/service"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Parser parses the command line arguments and runs as a service.
|
||||
// 如果需要程序退出(包括对服务的操作:install、uninstall、start、stop)则返回真
|
||||
// 不需要退出程序(run、noninteractive(服务中启动))返回假
|
||||
// 其他命令则以 cmd == "run" 处理
|
||||
// 注意:本函数需要处理的非常快(毫秒级别)
|
||||
// 代码以空间换时间
|
||||
func Parser(cmd string, runFunc func()) {
|
||||
status, err := Init(runFunc)
|
||||
|
||||
switch cmd {
|
||||
case "install":
|
||||
err = SysService.Install()
|
||||
|
||||
case "uninstall":
|
||||
err = SysService.Uninstall()
|
||||
|
||||
case "start":
|
||||
if status != service.StatusRunning {
|
||||
err = SysService.Start()
|
||||
}
|
||||
|
||||
case "stop":
|
||||
if status != service.StatusStopped {
|
||||
err = SysService.Stop()
|
||||
}
|
||||
|
||||
case "restart":
|
||||
err = SysService.Restart()
|
||||
|
||||
case "status":
|
||||
if err != nil {
|
||||
log.Errorln("Failed to get service status: %s", err)
|
||||
return
|
||||
}
|
||||
log.Infoln("Service status:%d (0:Unknown 1:Running 2:Stopped)", status)
|
||||
|
||||
case "noninteractive":
|
||||
// 在服务中启动
|
||||
Noninteractive = true
|
||||
log.Infoln("Running in service.")
|
||||
err = SysService.Run()
|
||||
if err != nil {
|
||||
log.Errorln("Failed to run service: %s", err)
|
||||
}
|
||||
default:
|
||||
// 直接运行
|
||||
log.Infoln("Running in terminal.")
|
||||
err = SysService.Run()
|
||||
if err != nil {
|
||||
log.Errorln("Failed to run service: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actionLog(cmd, err)
|
||||
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
} else {
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
36
service/privilege.go
Normal file
36
service/privilege.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// isAdmin 检查当前用户是否具有管理员权限
|
||||
func isAdmin() bool {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
return isAdminWindows()
|
||||
case "linux":
|
||||
return isAdminLinux()
|
||||
case "darwin":
|
||||
return isAdminMacOS()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// isAdminWindows 检查当前用户是否具有管理员权限(Windows)
|
||||
func isAdminWindows() bool {
|
||||
_, err := os.Open("\\\\.\\PHYSICALDRIVE0")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// isAdminLinux 检查当前用户是否具有管理员权限(Linux)
|
||||
func isAdminLinux() bool {
|
||||
return os.Geteuid() == 0
|
||||
}
|
||||
|
||||
// isAdminMacOS 检查当前用户是否具有管理员权限(macOS)
|
||||
func isAdminMacOS() bool {
|
||||
return os.Geteuid() == 0
|
||||
}
|
76
service/service.go
Normal file
76
service/service.go
Normal file
|
@ -0,0 +1,76 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"github.com/kardianos/service"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type program struct{}
|
||||
|
||||
var (
|
||||
SvcConfig *service.Config
|
||||
Program *program
|
||||
SysService service.Service
|
||||
Elevated bool
|
||||
mainFunc func()
|
||||
Noninteractive bool
|
||||
)
|
||||
|
||||
func Init(runFunc func()) (service.Status, error) {
|
||||
Elevated = isAdmin()
|
||||
mainFunc = runFunc
|
||||
Noninteractive = false
|
||||
|
||||
err := error(nil)
|
||||
svcHomeDir, err := filepath.Abs(C.Path.HomeDir())
|
||||
SvcConfig = &service.Config{
|
||||
Name: "mihomo-kernel",
|
||||
DisplayName: "Mihomo Kernel",
|
||||
Description: "Another Mihomo Kernel.",
|
||||
Arguments: []string{
|
||||
"--service", "noninteractive",
|
||||
"-d", svcHomeDir,
|
||||
},
|
||||
//Dependencies: []string{
|
||||
// "Requires=network.target",
|
||||
// "After=network-online.target"},
|
||||
Option: service.KeyValue{
|
||||
"Restart": "on-success",
|
||||
"SuccessExitStatus": "1 2 8 SIGKILL",
|
||||
},
|
||||
}
|
||||
|
||||
Program = &program{}
|
||||
SysService, err = service.New(Program, SvcConfig)
|
||||
if err != nil {
|
||||
log.Errorln("Fatal: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return SysService.Status()
|
||||
}
|
||||
|
||||
func SetLogger(fileDir string) {
|
||||
filePath := filepath.Join(fileDir, "service.log")
|
||||
|
||||
f, err := os.OpenFile(filePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0777)
|
||||
if err != nil {
|
||||
os.Stdout = f
|
||||
os.Stderr = f
|
||||
}
|
||||
}
|
||||
|
||||
func actionLog(action string, err error) {
|
||||
if err == nil {
|
||||
log.Infoln("Successfully to %s a service.", action)
|
||||
return
|
||||
}
|
||||
|
||||
if !Elevated {
|
||||
log.Errorln("Service control action needs elevated privileges. Please run with administrator privileges.")
|
||||
}
|
||||
log.Errorln("Failed to %s a service: %s", action, err)
|
||||
|
||||
}
|
Loading…
Add table
Reference in a new issue