mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-09-04 13:36:35 +02:00
170 lines
4.8 KiB
Go
170 lines
4.8 KiB
Go
//go:build windows
|
|
|
|
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"golang.org/x/sys/windows/svc/mgr"
|
|
)
|
|
|
|
// serviceConfigFileExists returns true if the ctrld Windows service is registered.
|
|
func serviceConfigFileExists() bool {
|
|
m, err := mgr.Connect()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
defer m.Disconnect()
|
|
s, err := m.OpenService(ctrldServiceName)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
s.Close()
|
|
return true
|
|
}
|
|
|
|
// appendServiceFlag appends a CLI flag (e.g., "--intercept-mode") to the installed
|
|
// Windows service's BinPath arguments. This is used when upgrading an existing
|
|
// installation to intercept mode without losing the existing --cd flag.
|
|
//
|
|
// The function is idempotent: if the flag already exists, it's a no-op.
|
|
func appendServiceFlag(flag string) error {
|
|
m, err := mgr.Connect()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to connect to Windows SCM: %w", err)
|
|
}
|
|
defer m.Disconnect()
|
|
|
|
s, err := m.OpenService(ctrldServiceName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open service %q: %w", ctrldServiceName, err)
|
|
}
|
|
defer s.Close()
|
|
|
|
config, err := s.Config()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read service config: %w", err)
|
|
}
|
|
|
|
// Check exact arguments so a short mode such as "off" is not confused with
|
|
// an unrelated path or value.
|
|
if binaryPathArgumentPresent(config.BinaryPathName, flag) {
|
|
mainLog.Load().Debug().Msgf("Service flag %q already present in BinPath, skipping", flag)
|
|
return nil
|
|
}
|
|
|
|
// Append the flag to BinPath.
|
|
config.BinaryPathName = strings.TrimSpace(config.BinaryPathName) + " " + flag
|
|
|
|
if err := s.UpdateConfig(config); err != nil {
|
|
return fmt.Errorf("failed to update service config with %q: %w", flag, err)
|
|
}
|
|
|
|
mainLog.Load().Info().Msgf("Appended %q to service BinPath", flag)
|
|
return nil
|
|
}
|
|
|
|
// verifyServiceRegistration opens the Windows Service Control Manager and verifies
|
|
// that the ctrld service is correctly registered: logs the BinaryPathName, checks
|
|
// that --intercept-mode is present if expected, and verifies SERVICE_AUTO_START.
|
|
func verifyServiceRegistration() error {
|
|
m, err := mgr.Connect()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to connect to Windows SCM: %w", err)
|
|
}
|
|
defer m.Disconnect()
|
|
|
|
s, err := m.OpenService(ctrldServiceName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open service %q: %w", ctrldServiceName, err)
|
|
}
|
|
defer s.Close()
|
|
|
|
config, err := s.Config()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read service config: %w", err)
|
|
}
|
|
|
|
mainLog.Load().Debug().Msgf("Service registry: BinaryPathName = %q", config.BinaryPathName)
|
|
|
|
// If intercept mode is set, verify the flag is present in BinPath.
|
|
if interceptMode == "dns" || interceptMode == "hard" {
|
|
if !strings.Contains(config.BinaryPathName, "--intercept-mode") {
|
|
return fmt.Errorf("service registry: --intercept-mode flag missing from BinaryPathName (expected mode %q)", interceptMode)
|
|
}
|
|
mainLog.Load().Debug().Msgf("Service registry: --intercept-mode flag present in BinaryPathName")
|
|
}
|
|
|
|
// Verify auto-start. mgr.StartAutomatic == 2 == SERVICE_AUTO_START.
|
|
if config.StartType != mgr.StartAutomatic {
|
|
return fmt.Errorf("service registry: StartType is %d, expected SERVICE_AUTO_START (%d)", config.StartType, mgr.StartAutomatic)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// removeServiceFlag removes both "--flag value" and "--flag=value" forms from the
|
|
// installed Windows service's BinPath. The function is idempotent.
|
|
func removeServiceFlag(flag string) error {
|
|
m, err := mgr.Connect()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to connect to Windows SCM: %w", err)
|
|
}
|
|
defer m.Disconnect()
|
|
|
|
s, err := m.OpenService(ctrldServiceName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open service %q: %w", ctrldServiceName, err)
|
|
}
|
|
defer s.Close()
|
|
|
|
config, err := s.Config()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read service config: %w", err)
|
|
}
|
|
|
|
updatedPath, removed := removeBinaryPathFlag(config.BinaryPathName, flag)
|
|
if !removed {
|
|
mainLog.Load().Debug().Msgf("Service flag %q not present in BinPath, skipping removal", flag)
|
|
return nil
|
|
}
|
|
config.BinaryPathName = updatedPath
|
|
|
|
if err := s.UpdateConfig(config); err != nil {
|
|
return fmt.Errorf("failed to update service config: %w", err)
|
|
}
|
|
|
|
mainLog.Load().Info().Msgf("Removed %q from service BinPath", flag)
|
|
return nil
|
|
}
|
|
|
|
func binaryPathArgumentPresent(binaryPath, argument string) bool {
|
|
for _, part := range strings.Fields(binaryPath) {
|
|
if part == argument {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func removeBinaryPathFlag(binaryPath, flag string) (string, bool) {
|
|
parts := strings.Fields(binaryPath)
|
|
newParts := make([]string, 0, len(parts))
|
|
removed := false
|
|
for i := 0; i < len(parts); i++ {
|
|
switch {
|
|
case parts[i] == flag:
|
|
removed = true
|
|
if i+1 < len(parts) && !strings.HasPrefix(parts[i+1], "-") {
|
|
i++
|
|
}
|
|
case strings.HasPrefix(parts[i], flag+"="):
|
|
removed = true
|
|
default:
|
|
newParts = append(newParts, parts[i])
|
|
}
|
|
}
|
|
return strings.Join(newParts, " "), removed
|
|
}
|