sing-box/experimental/daemon/daemon.go
2022-08-25 11:07:41 +08:00

166 lines
3.3 KiB
Go
Executable file

package daemon
import (
"io"
"os"
"path/filepath"
"runtime"
"strings"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw"
"github.com/kardianos/service"
C "github.com/sagernet/sing-box/constant"
)
const (
DefaultDaemonName = "sing-box-daemon"
DefaultDaemonPort = 9091
)
var defaultDaemonOptions = Options{
Listen: "127.0.0.1",
ListenPort: DefaultDaemonPort,
WorkingDirectory: workingDirectory(),
}
func workingDirectory() string {
switch runtime.GOOS {
case "linux":
return filepath.Join("/usr/local/lib", DefaultDaemonName)
default:
configDir, err := os.UserConfigDir()
if err == nil {
return filepath.Join(configDir, DefaultDaemonName)
} else {
return DefaultDaemonName
}
}
}
const systemdScript = `[Unit]
Description=sing-box service
Documentation=https://sing-box.sagernet.org
After=network.target nss-lookup.target
[Service]
User=root
ExecStart={{.Path|cmdEscape}}{{range .Arguments}} {{.|cmd}}{{end}}
WorkingDirectory={{.WorkingDirectory|cmdEscape}}
Restart=on-failure
RestartSec=10s
LimitNOFILE=infinity
[Install]
WantedBy=multi-user.target`
type Daemon struct {
service service.Service
workingDirectory string
executable string
}
func New() (*Daemon, error) {
daemonInterface := NewInterface(defaultDaemonOptions)
executable := filepath.Join(defaultDaemonOptions.WorkingDirectory, "sing-box")
if C.IsWindows {
executable += ".exe"
}
daemonService, err := service.New(daemonInterface, &service.Config{
Name: DefaultDaemonName,
Description: "The universal proxy platform.",
Arguments: []string{"daemon", "run"},
Executable: executable,
Option: service.KeyValue{
"SystemdScript": systemdScript,
},
})
if err != nil {
return nil, E.New(strings.ToLower(err.Error()))
}
return &Daemon{
service: daemonService,
workingDirectory: defaultDaemonOptions.WorkingDirectory,
executable: executable,
}, nil
}
func (d *Daemon) Install() error {
_, err := d.service.Status()
if err != service.ErrNotInstalled {
d.service.Stop()
err = d.service.Uninstall()
if err != nil {
return err
}
}
executablePath, err := os.Executable()
if err != nil {
return err
}
if !rw.FileExists(d.workingDirectory) {
err = os.MkdirAll(d.workingDirectory, 0o755)
if err != nil {
return err
}
}
outputFile, err := os.OpenFile(d.executable, os.O_CREATE|os.O_WRONLY, 0o755)
if err != nil {
return err
}
inputFile, err := os.Open(executablePath)
if err != nil {
outputFile.Close()
return err
}
_, err = io.Copy(outputFile, inputFile)
inputFile.Close()
outputFile.Close()
if err != nil {
return err
}
err = d.service.Install()
if err != nil {
return err
}
return d.service.Start()
}
func (d *Daemon) Uninstall() error {
_, err := d.service.Status()
if err != service.ErrNotInstalled {
d.service.Stop()
err = d.service.Uninstall()
if err != nil {
return err
}
}
return os.RemoveAll(d.workingDirectory)
}
func (d *Daemon) Run() error {
d.chdir()
return d.service.Run()
}
func (d *Daemon) chdir() error {
executable, err := os.Executable()
if err != nil {
return err
}
return os.Chdir(filepath.Dir(executable))
}
func (d *Daemon) Start() error {
return d.service.Start()
}
func (d *Daemon) Stop() error {
return d.service.Stop()
}
func (d *Daemon) Restart() error {
return d.service.Restart()
}