Files
theta-agent/main.go
T
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

105 lines
2.8 KiB
Go

package main
import (
"fmt"
"log"
"os"
"os/signal"
"strings"
"sync"
"sync/atomic"
"syscall"
)
// wsConnected is flipped atomically by connectWebSocket as the connection
// comes up and drops, so StartHomeMonitor can read it without a mutex.
var wsConnected atomic.Bool
// agentStopCh closes once the agent should exit: a SIGINT/SIGTERM in the
// foreground, or the SCM stop request when running as a Windows service. Both
// runAgent (foreground) and the service handler wait on it.
var (
agentStopOnce sync.Once
agentStopCh = make(chan struct{})
)
// currentCM lets the tray IPC server persist preferences (auto_vpn) and reset
// enrollment into the live config file. Set once in runAgent.
var currentCM *ConfigManager
// stopAgent signals the running agent to shut down. Idempotent.
func stopAgent() {
agentStopOnce.Do(func() { close(agentStopCh) })
}
func main() {
if len(os.Args) > 1 && handleCLI(os.Args[1:]) {
return
}
// Windows: when installed as a service the SCM starts us and svc.Run takes
// over the process lifecycle. Foreground (or any other OS) falls through to
// runAgent, which blocks until a signal arrives.
if maybeRunAsService() {
return
}
runAgent()
}
// runAgent runs the agent daemon until stopAgent is called.
func runAgent() {
log.Println("Starting Theta Agent...")
// Attempt to load configuration
configPath := defaultConfigPath()
if len(os.Args) > 1 && !strings.HasPrefix(os.Args[1], "-") {
configPath = os.Args[1]
}
cm, err := NewConfigManager(configPath)
if err != nil {
log.Fatalf("Error loading configuration from %s: %v", configPath, err)
}
cfg := cm.Get()
log.Printf("Connecting to Theta Directory at %s", cfg.ServerURL)
log.Printf("Loaded capabilities: Telemetry=%v, LDAP=%v, Reboot=%v, Bash=%v",
cfg.Capabilities.Telemetry,
cfg.Capabilities.ConfigureLDAP,
cfg.Capabilities.Reboot,
cfg.Capabilities.ArbitraryBash,
)
// Initialize system executor and pin the platform ops the command
// dispatcher runs behind.
exec := &SystemExecutor{}
defaultPlatformOps = NewPlatformOps(cfg, exec)
currentCM = cm
// Seed the auto-VPN preference from disk; the tray checkbox updates it.
SetAutoVPN(cfg.AutoVPN)
// Tray IPC server — desktop tray connects here for status updates.
go globalTrayServer.Start()
// WebSocket connection to Theta Directory
go connectWebSocket(cm, exec)
// Home detection + tray status push (polls public IP every 60s).
go StartHomeMonitor(cfg, func() bool { return wsConnected.Load() })
// Foreground: exit on SIGINT/SIGTERM. A Windows service ignores these and
// is driven by its own handler.
go func() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
<-sigs
stopAgent()
}()
<-agentStopCh
fmt.Println("Shutting down Theta Agent...")
}