4a619f7adc
First Windows parity milestone (DESIGN-WINDOWS.md §13 build order item 1).
- Add a PlatformOps abstraction so command dispatch is OS-neutral:
- linuxPlatformOps keeps today's systemctl/journalctl/bash behavior (deliberately
untagged so shared dispatch tests run on Windows CI)
- windowsPlatformOps maps reboot/shutdown to shutdown.exe, service control to
sc.exe (stop+start for restart), fetch_logs to Get-WinEvent, arbitrary_bash to
powershell -EncodedCommand (byte-exact under arbitrary quoting), and declines
configure_ldap (Windows logon goes through OpenCredential)
- Run theta-agent as a Windows service (x/sys/windows/svc): SYSTEM auto-start,
SCM stop/shutdown handling; CLI install-service/remove-service via svc/mgr
- Add theta-agent-helper (session-0 companion): lock/display_off/logout via
user32/wtsapi32, and staged self-update (wait for service stop, swap the
locked exe, sc start)
- Self-update becomes platform-aware: Linux renames over the running binary;
Windows stages .new and hands the swap to the helper (running exe is locked)
- Platform paths: agent.yml and tray.sock under %ProgramData%\Theta42 (the
service runs as SYSTEM while the tray runs as the user, so the per-user temp
dir no longer works for tray IPC); LDAP byte-pump falls back to TCP loopback
- config: service_name, desktop_helper, public_ip_detect (air-gap: skips
external public-IP lookups in telemetry + home monitor), wireguard block
- cli: platform-aware config path + self-update artifact name + service restart
- tests: dispatch tests pin linuxPlatformOps; 0600 mode assertions gated to
POSIX so the suite is green on Windows
Rebuilds all tracked dist binaries (v2.1.0).
66 lines
1.6 KiB
Go
66 lines
1.6 KiB
Go
//go:build windows
|
|
|
|
package main
|
|
|
|
// Windows service wrapper. The installer registers theta-agent as a SYSTEM
|
|
// auto-start service; when the SCM launches the process this file takes over.
|
|
// The service is up before any user session, which is what allows the
|
|
// credential provider to validate LDAP logins at Ctrl+Alt+Del and what makes
|
|
// the tray IPC socket path (shared data dir) correct.
|
|
|
|
import (
|
|
"log"
|
|
"os"
|
|
|
|
"golang.org/x/sys/windows/svc"
|
|
)
|
|
|
|
// maybeRunAsService returns true (and runs the service loop) when launched by
|
|
// the Service Control Manager; false when run in a console.
|
|
func maybeRunAsService() bool {
|
|
isSvc, err := svc.IsWindowsService()
|
|
if err != nil || !isSvc {
|
|
return false
|
|
}
|
|
if err := svc.Run("theta-agent", &agentService{}); err != nil {
|
|
log.Printf("service: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
return true
|
|
}
|
|
|
|
type agentService struct{}
|
|
|
|
func (s *agentService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) {
|
|
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
|
|
|
|
changes <- svc.Status{State: svc.StartPending}
|
|
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
runAgent()
|
|
}()
|
|
|
|
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
|
|
|
|
for {
|
|
select {
|
|
case c := <-r:
|
|
switch c.Cmd {
|
|
case svc.Interrogate:
|
|
changes <- c.CurrentStatus
|
|
case svc.Stop, svc.Shutdown:
|
|
changes <- svc.Status{State: svc.StopPending}
|
|
stopAgent()
|
|
<-done
|
|
changes <- svc.Status{State: svc.Stopped}
|
|
return false, 0
|
|
}
|
|
case <-done:
|
|
changes <- svc.Status{State: svc.Stopped}
|
|
return false, 0
|
|
}
|
|
}
|
|
}
|