refactor: simplify service installation process

This commit is contained in:
2026-07-19 20:07:26 +08:00
parent a2630df9e0
commit dcdb54b76c
3 changed files with 75 additions and 9 deletions

View File

@ -116,16 +116,10 @@ ss service reload [--name <name>] # reload config via mihomo's API, no restart
ss service status [--name <name>] ss service status [--name <name>]
``` ```
Linux / macOS (installing the service needs root): `install`/`uninstall`/`start`/`stop`/`restart` need root/admin privileges:
```bash
sudo env SF_CONFIG_DIR=$HOME/basicfiles/cli/ss ss service install
```
Windows (run from an elevated/Administrator shell): - **Linux / macOS**: just run the command directly — `ss` re-execs itself under `sudo` automatically, prompting for your password if needed.
```powershell - **Windows**: run from an elevated/Administrator shell — `ss.exe service install`.
$env:SF_CONFIG_DIR = "$HOME\basicfiles\cli\ss"
ss.exe service install
```
## Development ## Development

57
internal/cli/elevate.go Normal file
View File

@ -0,0 +1,57 @@
package cli
import (
"fmt"
"os"
"os/exec"
"runtime"
"gitea.epss.net.cn/klesh/ss/internal/storage"
)
// ensureRoot re-execs the current command under sudo if it isn't already
// running as root. It resolves the config directory *before* elevating and
// passes it through explicitly as SF_CONFIG_DIR, because sudo resets $HOME
// to /root's — without this, the elevated process would silently resolve
// the default config directory under /root instead of the invoking user's
// home.
//
// Windows is handled separately: UAC elevation launches a new process that
// can't transparently inherit the current console, so internal/service's
// runAsAdmin instead prints manual elevation instructions rather than
// auto-elevating.
func ensureRoot() error {
if runtime.GOOS == "windows" {
return nil
}
if os.Geteuid() == 0 {
return nil
}
s, err := storage.New()
if err != nil {
return fmt.Errorf("failed to resolve config directory: %w", err)
}
self, err := os.Executable()
if err != nil {
return fmt.Errorf("failed to determine executable path: %w", err)
}
sudoPath, err := exec.LookPath("sudo")
if err != nil {
return fmt.Errorf("this command requires root privileges, but sudo was not found on PATH: %w", err)
}
args := append([]string{"env", "SF_CONFIG_DIR=" + s.ConfigDir, self}, os.Args[1:]...)
cmd := exec.Command(sudoPath, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
runErr := cmd.Run()
if cmd.ProcessState != nil {
os.Exit(cmd.ProcessState.ExitCode())
}
return fmt.Errorf("failed to re-exec under sudo: %w", runErr)
}

View File

@ -40,6 +40,9 @@ func newServiceInstallCmd() *cobra.Command {
Short: "Install mihomo as a system service", Short: "Install mihomo as a system service",
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if err := ensureRoot(); err != nil {
return err
}
m, err := newManagers("", "", "") m, err := newManagers("", "", "")
if err != nil { if err != nil {
return err return err
@ -62,6 +65,9 @@ func newServiceUninstallCmd() *cobra.Command {
Short: "Uninstall mihomo system service", Short: "Uninstall mihomo system service",
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if err := ensureRoot(); err != nil {
return err
}
m, err := newManagers("", "", "") m, err := newManagers("", "", "")
if err != nil { if err != nil {
return err return err
@ -83,6 +89,9 @@ func newServiceStartCmd() *cobra.Command {
Short: "Start mihomo system service", Short: "Start mihomo system service",
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if err := ensureRoot(); err != nil {
return err
}
m, err := newManagers("", "", "") m, err := newManagers("", "", "")
if err != nil { if err != nil {
return err return err
@ -104,6 +113,9 @@ func newServiceStopCmd() *cobra.Command {
Short: "Stop mihomo system service", Short: "Stop mihomo system service",
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if err := ensureRoot(); err != nil {
return err
}
m, err := newManagers("", "", "") m, err := newManagers("", "", "")
if err != nil { if err != nil {
return err return err
@ -125,6 +137,9 @@ func newServiceRestartCmd() *cobra.Command {
Short: "Restart mihomo system service", Short: "Restart mihomo system service",
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if err := ensureRoot(); err != nil {
return err
}
m, err := newManagers("", "", "") m, err := newManagers("", "", "")
if err != nil { if err != nil {
return err return err