125 lines
4.3 KiB
Go
125 lines
4.3 KiB
Go
//go:build windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
clientwindows "github.com/rvbox/rvbox/internal/client/supervisor/windows"
|
|
"github.com/rvbox/rvbox/internal/client/windowsservice"
|
|
"github.com/rvbox/rvbox/internal/client/windowstray"
|
|
"golang.org/x/sys/windows/svc"
|
|
)
|
|
|
|
func defaultClientConfigPath() string {
|
|
root := os.Getenv("ProgramData")
|
|
if root == "" {
|
|
root = `C:\ProgramData`
|
|
}
|
|
return filepath.Join(root, "RVBox", "client.toml")
|
|
}
|
|
|
|
func runService(configPath string, diagnostics io.Writer) error {
|
|
inService, err := svc.IsWindowsService()
|
|
if err != nil {
|
|
return fmt.Errorf("detect service control manager context: %w", err)
|
|
}
|
|
if !inService {
|
|
return errors.New("--service is reserved for the installed Windows service")
|
|
}
|
|
return runWindowsService(configPath, diagnostics)
|
|
}
|
|
|
|
func runWindowsService(configPath string, diagnostics io.Writer) error {
|
|
return windowsservice.Run(func(ctx context.Context) error {
|
|
// The tray endpoint lives in the same LocalSystem service process. It
|
|
// has no store access; the handler below returns only bounded status/path
|
|
// data and rechecks SCM authorization in the native pipe adapter.
|
|
go func() {
|
|
if err := windowstray.Serve(ctx, func(requestContext context.Context, _ windowstray.Peer, request windowstray.Frame) (windowstray.Frame, error) {
|
|
return handleTrayRequest(requestContext, configPath, request)
|
|
}); err != nil && ctx.Err() == nil && diagnostics != nil {
|
|
_, _ = fmt.Fprintf(diagnostics, "rvbox tray endpoint stopped: %v\n", err)
|
|
}
|
|
}()
|
|
return runClientDaemon(ctx, configPath, diagnostics)
|
|
})
|
|
}
|
|
|
|
func runTray(configPath string, diagnostics io.Writer) error {
|
|
_ = configPath // the tray obtains the canonical paths from the service.
|
|
return windowstray.Run(context.Background(), diagnostics)
|
|
}
|
|
|
|
func runLauncher(channel string, _ io.Writer) error {
|
|
return clientwindows.RunLauncher(context.Background(), channel)
|
|
}
|
|
|
|
func runSignalHelper(channel string, _ io.Writer) error {
|
|
return clientwindows.RunSignalHelper(context.Background(), channel)
|
|
}
|
|
|
|
func handleTrayRequest(ctx context.Context, configPath string, request windowstray.Frame) (windowstray.Frame, error) {
|
|
response := windowstray.Frame{Action: windowstray.ActionStatus}
|
|
switch request.Action {
|
|
case windowstray.ActionStatus:
|
|
response.Payload = []byte("RVBox service=running")
|
|
case windowstray.ActionOpenConfig:
|
|
response.Payload = []byte(configPath)
|
|
case windowstray.ActionOpenLog:
|
|
configured, err := loadClientConfig(configPath)
|
|
if err != nil {
|
|
return response, err
|
|
}
|
|
logPath := configured.Observability.LogFile
|
|
if logPath == "" {
|
|
logPath = filepath.Join(filepath.Dir(configPath), "logs", "rvbox.log")
|
|
}
|
|
response.Payload = []byte(logPath)
|
|
case windowstray.ActionStartService:
|
|
if err := windowsservice.Start(); err != nil {
|
|
return response, err
|
|
}
|
|
response.Payload = []byte("RVBox service start requested")
|
|
case windowstray.ActionStopService:
|
|
// A service cannot synchronously wait for its own stop request from the
|
|
// pipe handler: the SCM callback must return so the process can unwind.
|
|
go func() { _ = windowsservice.Stop(30) }()
|
|
response.Payload = []byte("RVBox service stop requested")
|
|
case windowstray.ActionRestartService:
|
|
// Restart must be performed by the external UAC helper. If the tray is
|
|
// already elevated, its native fallback still launches the canonical
|
|
// --restart-service mode outside this service process.
|
|
return response, errors.New("restart requires the external service helper")
|
|
case windowstray.ActionSetAutomatic:
|
|
if err := windowsservice.Configure(windowsservice.StartupAutomatic); err != nil {
|
|
return response, err
|
|
}
|
|
response.Payload = []byte("RVBox service startup set to automatic")
|
|
case windowstray.ActionSetManual:
|
|
if err := windowsservice.Configure(windowsservice.StartupManual); err != nil {
|
|
return response, err
|
|
}
|
|
response.Payload = []byte("RVBox service startup set to manual")
|
|
case windowstray.ActionExitTray:
|
|
response.Payload = []byte("tray exit acknowledged")
|
|
default:
|
|
return response, fmt.Errorf("unknown tray action %d", request.Action)
|
|
}
|
|
if len(response.Payload) > 4<<10 {
|
|
response.Payload = []byte(strings.TrimSpace(string(response.Payload[:4<<10])))
|
|
}
|
|
select {
|
|
case <-ctx.Done():
|
|
return response, ctx.Err()
|
|
default:
|
|
return response, nil
|
|
}
|
|
}
|