Files
theta-agent/service_cli_windows.go
wmantly 5013148ffe fix(installer): Theta Directory branding, visible URL/join-key fields, GUI tray, service autostart
User-reported install fixes:

- Branding: every user-facing 'SSO Manager' string now says 'Theta Directory'
  (agent logs, CLI usage, agent.yml.example, installer wizard).
- Wizard page: the URL/join-key text boxes were never shown. The layout used
  Surface.Width (0 at wizard init) instead of SurfaceWidth and combined
  WordWrap with AutoSize (mutually exclusive in VCL). Rewritten with the
  canonical Inno pattern (SurfaceWidth + ScaleY + explicit label height).
- No console window after install: the tray and helper now build as
  GUI-subsystem binaries (-H=windowsgui) in build_all.sh and
  scripts/setup-build-env.ps1. The agent stays a console app for foreground
  debugging (as a service it never shows a console).
- The daemon never came up after install: install-service now starts the
  service immediately, so the tray IPC socket exists right away and the tray
  connects instead of logging 'actively refused' until a reboot.

Verified: go build/vet/test green; tray+helper PE subsystem = GUI (2), agent =
console (3); installer compiles; tray runs silently.
2026-08-09 20:33:06 -07:00

95 lines
2.1 KiB
Go

//go:build windows
package main
// Service management CLI (theta-agent install-service / remove-service). The
// Inno installer also drives this rather than hand-rolling `sc create`.
import (
"fmt"
"os"
"path/filepath"
"golang.org/x/sys/windows/svc/mgr"
)
func handleServiceCommand(args []string) {
action := "install"
if len(args) > 0 {
action = args[0]
}
switch action {
case "install":
installService()
case "remove":
removeService()
default:
fmt.Fprintf(os.Stderr, "usage: theta-agent %s [install|remove]\n", action)
os.Exit(1)
}
}
func installService() {
exe, err := os.Executable()
if err != nil {
logFatal("cannot resolve agent path: %v", err)
}
m, err := mgr.Connect()
if err != nil {
logFatal("cannot connect to service manager: %v", err)
}
defer m.Disconnect()
s, err := m.OpenService("theta-agent")
if err == nil {
s.Close()
fmt.Println("[+] theta-agent service already exists")
return
}
cfg := mgr.Config{
DisplayName: "Theta Agent",
Description: "Theta42 unified endpoint management agent (telemetry, remote ops, LDAP logon, WireGuard mesh)",
ServiceStartName: "LocalSystem",
StartType: mgr.StartAutomatic,
}
s, err = m.CreateService("theta-agent", exe, cfg, "is", "auto")
if err != nil {
logFatal("cannot create service: %v", err)
}
defer s.Close()
// Start it immediately so the daemon binds the tray IPC socket and the
// credential provider is live without waiting for a reboot.
if err := s.Start(); err != nil {
logFatal("cannot start service: %v", err)
}
fmt.Printf("[+] Registered and started theta-agent service (%s)\n", filepath.Base(exe))
}
func removeService() {
m, err := mgr.Connect()
if err != nil {
logFatal("cannot connect to service manager: %v", err)
}
defer m.Disconnect()
s, err := m.OpenService("theta-agent")
if err != nil {
fmt.Println("[!] theta-agent service not found")
return
}
defer s.Close()
if err := s.Delete(); err != nil {
logFatal("cannot delete service: %v", err)
}
fmt.Println("[+] Removed theta-agent service")
}
func logFatal(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, "[!] "+format+"\n", args...)
os.Exit(1)
}