Files
ss/internal/cli/service.go

238 lines
5.4 KiB
Go

package cli
import (
"fmt"
"github.com/spf13/cobra"
svc "gitea.epss.net.cn/klesh/ss/internal/service"
)
func newServiceCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "service",
Short: "Manage mihomo as a system service",
}
cmd.AddCommand(
newServiceInstallCmd(),
newServiceUninstallCmd(),
newServiceStartCmd(),
newServiceStopCmd(),
newServiceRestartCmd(),
newServiceReloadCmd(),
newServiceStatusCmd(),
newServiceRunMacOSCmd(),
newServiceRunWindowsCmd(),
)
return cmd
}
func withNameFlag(c *cobra.Command, name *string) {
c.Flags().StringVar(name, "name", "mihomo", "Service name (default: mihomo)")
}
func newServiceInstallCmd() *cobra.Command {
var name, description string
c := &cobra.Command{
Use: "install",
Short: "Install mihomo as a system service",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
if err := ensureRoot(); err != nil {
return err
}
m, err := newManagers("", "", "")
if err != nil {
return err
}
if !m.Core.InstallService(name, description) {
return errSilentFailure
}
return nil
},
}
withNameFlag(c, &name)
c.Flags().StringVar(&description, "description", "Mihomo proxy service", "Service description")
return c
}
func newServiceUninstallCmd() *cobra.Command {
var name string
c := &cobra.Command{
Use: "uninstall",
Short: "Uninstall mihomo system service",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
if err := ensureRoot(); err != nil {
return err
}
m, err := newManagers("", "", "")
if err != nil {
return err
}
if !m.Core.UninstallService(name) {
return errSilentFailure
}
return nil
},
}
withNameFlag(c, &name)
return c
}
func newServiceStartCmd() *cobra.Command {
var name string
c := &cobra.Command{
Use: "start",
Short: "Start mihomo system service",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
if err := ensureRoot(); err != nil {
return err
}
m, err := newManagers("", "", "")
if err != nil {
return err
}
if !m.Core.StartService(name) {
return errSilentFailure
}
return nil
},
}
withNameFlag(c, &name)
return c
}
func newServiceStopCmd() *cobra.Command {
var name string
c := &cobra.Command{
Use: "stop",
Short: "Stop mihomo system service",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
if err := ensureRoot(); err != nil {
return err
}
m, err := newManagers("", "", "")
if err != nil {
return err
}
if !m.Core.StopService(name) {
return errSilentFailure
}
return nil
},
}
withNameFlag(c, &name)
return c
}
func newServiceRestartCmd() *cobra.Command {
var name string
c := &cobra.Command{
Use: "restart",
Short: "Restart mihomo system service",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
if err := ensureRoot(); err != nil {
return err
}
m, err := newManagers("", "", "")
if err != nil {
return err
}
if !m.Core.RestartService(name) {
return errSilentFailure
}
return nil
},
}
withNameFlag(c, &name)
return c
}
func newServiceReloadCmd() *cobra.Command {
var name string
c := &cobra.Command{
Use: "reload",
Short: "Reload mihomo service configuration (via API)",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
m, err := newManagers("", "", "")
if err != nil {
return err
}
if !m.Core.ReloadService(name) {
return errSilentFailure
}
return nil
},
}
withNameFlag(c, &name)
return c
}
func newServiceStatusCmd() *cobra.Command {
var name string
c := &cobra.Command{
Use: "status",
Short: "Check mihomo system service status",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
m, err := newManagers("", "", "")
if err != nil {
return err
}
status := m.Core.GetServiceStatus(name)
fmt.Printf("Service '%s' status: %s\n", name, status)
return nil
},
}
withNameFlag(c, &name)
return c
}
// newServiceRunMacOSCmd is the hidden command the generated launchd plist
// invokes, replacing the standalone macos_service_wrapper.py script.
func newServiceRunMacOSCmd() *cobra.Command {
c := &cobra.Command{
Use: "run-macos <executable> <config-dir> [args...]",
Hidden: true,
Args: cobra.MinimumNArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
return svc.RunMacOSWrapper(args[0], args[1], args[2:])
},
}
return c
}
// newServiceRunWindowsCmd is the hidden command sc.exe's binPath= invokes,
// replacing windows_service_wrapper.py + pywin32.
func newServiceRunWindowsCmd() *cobra.Command {
var name, bin, args string
c := &cobra.Command{
Use: "run-windows",
Hidden: true,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, cmdArgs []string) error {
return svc.RunWindowsService(name, bin, args)
},
}
c.Flags().StringVar(&name, "name", "mihomo", "Service name")
c.Flags().StringVar(&bin, "bin", "", "Path to the wrapped executable")
c.Flags().StringVar(&args, "args", "", "Command line arguments for the wrapped executable")
return c
}
// errSilentFailure signals a command failure whose message has already been
// printed by the underlying manager (matching cli.py's pattern of printing
// "❌ ..." then sys.exit(1) without an additional error message).
var errSilentFailure = &silentError{}
type silentError struct{}
func (e *silentError) Error() string { return "" }