//go:build with_daemon package main import ( "bytes" "io" "net" "net/http" "net/url" "os" "github.com/sagernet/sing-box/common/json" "github.com/sagernet/sing-box/experimental/daemon" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/spf13/cobra" ) var commandDaemon = &cobra.Command{ Use: "daemon", } func init() { commandDaemon.AddCommand(commandDaemonInstall) commandDaemon.AddCommand(commandDaemonUninstall) commandDaemon.AddCommand(commandDaemonStart) commandDaemon.AddCommand(commandDaemonStop) commandDaemon.AddCommand(commandDaemonRestart) commandDaemon.AddCommand(commandDaemonRun) mainCommand.AddCommand(commandDaemon) mainCommand.AddCommand(commandStart) mainCommand.AddCommand(commandStop) mainCommand.AddCommand(commandStatus) } var commandDaemonInstall = &cobra.Command{ Use: "install", Short: "Install daemon", Run: func(cmd *cobra.Command, args []string) { err := installDaemon() if err != nil { log.Fatal(err) } }, Args: cobra.NoArgs, } var commandDaemonUninstall = &cobra.Command{ Use: "uninstall", Short: "Uninstall daemon", Run: func(cmd *cobra.Command, args []string) { err := uninstallDaemon() if err != nil { log.Fatal(err) } }, Args: cobra.NoArgs, } var commandDaemonStart = &cobra.Command{ Use: "start", Short: "Start daemon", Run: func(cmd *cobra.Command, args []string) { err := startDaemon() if err != nil { log.Fatal(err) } }, Args: cobra.NoArgs, } var commandDaemonStop = &cobra.Command{ Use: "stop", Short: "Stop daemon", Run: func(cmd *cobra.Command, args []string) { err := stopDaemon() if err != nil { log.Fatal(err) } }, Args: cobra.NoArgs, } var commandDaemonRestart = &cobra.Command{ Use: "restart", Short: "Restart daemon", Run: func(cmd *cobra.Command, args []string) { err := restartDaemon() if err != nil { log.Fatal(err) } }, Args: cobra.NoArgs, } var commandDaemonRun = &cobra.Command{ Use: "run", Short: "Run daemon", Run: func(cmd *cobra.Command, args []string) { err := runDaemon() if err != nil { log.Fatal(err) } }, Args: cobra.NoArgs, } func installDaemon() error { instance, err := daemon.New() if err != nil { return err } return instance.Install() } func uninstallDaemon() error { instance, err := daemon.New() if err != nil { return err } return instance.Uninstall() } func startDaemon() error { instance, err := daemon.New() if err != nil { return err } return instance.Start() } func stopDaemon() error { instance, err := daemon.New() if err != nil { return err } return instance.Stop() } func restartDaemon() error { instance, err := daemon.New() if err != nil { return err } return instance.Restart() } func runDaemon() error { instance, err := daemon.New() if err != nil { return err } return instance.Run() } var commandStart = &cobra.Command{ Use: "start", Short: "Start service", Run: func(cmd *cobra.Command, args []string) { err := startService() if err != nil { log.Fatal(err) } }, Args: cobra.NoArgs, } var commandStop = &cobra.Command{ Use: "stop", Short: "Stop service", Run: func(cmd *cobra.Command, args []string) { err := stopService() if err != nil { log.Fatal(err) } }, Args: cobra.NoArgs, } var commandStatus = &cobra.Command{ Use: "status", Short: "Check service", Run: func(cmd *cobra.Command, args []string) { err := checkService() if err != nil { log.Fatal(err) } }, Args: cobra.NoArgs, } func doRequest(method string, path string, params url.Values, body io.ReadCloser) ([]byte, error) { requestURL := url.URL{ Scheme: "http", Path: path, Host: net.JoinHostPort("127.0.0.1", F.ToString(daemon.DefaultDaemonPort)), } if params != nil { requestURL.RawQuery = params.Encode() } request, err := http.NewRequest(method, requestURL.String(), body) if err != nil { return nil, err } response, err := http.DefaultClient.Do(request) if err != nil { return nil, err } defer response.Body.Close() var content []byte if response.StatusCode != http.StatusNoContent { content, err = io.ReadAll(response.Body) if err != nil { return nil, err } } if response.StatusCode != http.StatusOK && response.StatusCode != http.StatusNoContent { return nil, E.New(string(content)) } return content, nil } func ping() error { response, err := doRequest("GET", "/ping", nil, nil) if err != nil || string(response) != "pong" { return E.New("daemon not running") } return nil } func startService() error { if err := ping(); err != nil { return err } configContent, err := os.ReadFile(configPath) if err != nil { return E.Cause(err, "read config") } return common.Error(doRequest("POST", "/run", nil, io.NopCloser(bytes.NewReader(configContent)))) } func stopService() error { if err := ping(); err != nil { return err } return common.Error(doRequest("GET", "/stop", nil, nil)) } func checkService() error { if err := ping(); err != nil { return err } response, err := doRequest("GET", "/status", nil, nil) if err != nil { return err } var statusResponse daemon.StatusResponse err = json.Unmarshal(response, &statusResponse) if err != nil { return err } if statusResponse.Running { log.Info("service running") } else { log.Info("service stopped") } return nil }