diff --git a/agent.yml.example b/agent.yml.example index acc4606..198df26 100644 --- a/agent.yml.example +++ b/agent.yml.example @@ -26,7 +26,7 @@ public_key: "" location: "default" # Location identifier (e.g., site, datacenter) for naming -# Local LDAP byte-pump socket (DESIGN.md §4). The agent forwards raw LDAP bytes +# Local LDAP byte-pump socket (DESIGN.md ??4). The agent forwards raw LDAP bytes # from this socket to the SSO, which relays them into its OpenLDAP. The agent # never parses LDAP. Point SSSD at it with: # ldap_uri = ldapi://%2frun%2ftheta%2fldap.sock @@ -37,12 +37,12 @@ ldap_socket: "/run/theta/ldap.sock" # directory WebSocket is up. The tray checkbox persists here too. auto_vpn: false -# Windows-specific (DESIGN-WINDOWS.md §11). Ignored on Linux. +# Windows-specific (DESIGN-WINDOWS.md ??11). Ignored on Linux. service_name: "theta-agent" # Windows service name desktop_helper: "" # theta-agent-helper.exe path (session-0 ops) public_ip_detect: true # false = air-gap: never call external IP services -# WireGuard mesh client (DESIGN-WINDOWS.md §5). The signed wireguard_apply +# WireGuard mesh client (DESIGN-WINDOWS.md ??5). The signed wireguard_apply # command pushes the peer config down the WSS channel; these are local paths. wireguard: tunnel_name: "theta-mesh" @@ -54,25 +54,25 @@ capabilities: # Basic Capabilities (Safe, read-only or infrastructure management) # --------------------------------------------------------- - # Push CPU, RAM, GPU, and ZFS metrics to the SSO Manager + # Push CPU, RAM, GPU, and ZFS metrics to Theta Directory telemetry: true - # Allow the SSO Manager to push down SSSD and SSH keys configuration + # Allow Theta Directory to push down SSSD and SSH keys configuration configure_ldap: true - # Serve the local LDAP byte-pump socket for SSSD/PAM (DESIGN.md §4) + # Serve the local LDAP byte-pump socket for SSSD/PAM (DESIGN.md ??4) ldap_tunnel: true - # Render OpenBao secrets to local files from /etc/theta/templates (DESIGN.md §5) + # Render OpenBao secrets to local files from /etc/theta/templates (DESIGN.md ??5) secrets: true - # Apply node IAM (sudo rules, SSH keys, access control, revocation) (DESIGN.md §6) + # Apply node IAM (sudo rules, SSH keys, access control, revocation) (DESIGN.md ??6) iam: true - # Accept signed wireguard_apply/wireguard_remove commands (DESIGN-WINDOWS.md §5) + # Accept signed wireguard_apply/wireguard_remove commands (DESIGN-WINDOWS.md ??5) wireguard: false -# Secret templates to render (DESIGN.md §5). Each maps a local template to a +# Secret templates to render (DESIGN.md ??5). Each maps a local template to a # target file and an optional post-render reload. The template embeds secrets as # {{ bao "secret/data/nodes//#" }}. # secrets: @@ -84,7 +84,7 @@ capabilities: # Advanced Capabilities (High risk, remote operations) # --------------------------------------------------------- - # Allow remote system reboots via the SSO Manager + # Allow remote system reboots via Theta Directory reboot: false # Allow restarting, starting, or stopping specific systemd services. @@ -93,6 +93,6 @@ capabilities: # Setting to true or [] denies all. service_control: [] - # CRITICAL: Allow the execution of raw bash scripts sent from the SSO Manager. + # CRITICAL: Allow the execution of raw bash scripts sent from Theta Directory. # Useful for GitOps deployments, but allows remote code execution. arbitrary_bash: false diff --git a/build_all.sh b/build_all.sh index e549580..00233d6 100755 --- a/build_all.sh +++ b/build_all.sh @@ -7,6 +7,11 @@ DIST_DIR="./dist" mkdir -p "$DIST_DIR" LDFLAGS="-s -w" +# Windows GUI binaries (tray, helper) build as GUI-subsystem so no console +# window pops up when the installer starts the tray or the service spawns the +# helper. The agent stays a console app (handy for foreground debugging; as a +# Windows service it never shows a console anyway). +GUI_LDFLAGS="$LDFLAGS -H=windowsgui" echo "Building Theta Agent binaries..." @@ -26,10 +31,10 @@ echo " -> windows/arm64..." CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -ldflags="$LDFLAGS" -o "$DIST_DIR/theta-agent-windows-arm64.exe" echo " -> windows/amd64 helper..." -CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="$LDFLAGS" -o "$DIST_DIR/theta-agent-helper-windows-amd64.exe" ./cmd/theta-agent-helper/ +CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="$GUI_LDFLAGS" -o "$DIST_DIR/theta-agent-helper-windows-amd64.exe" ./cmd/theta-agent-helper/ echo " -> windows/arm64 helper..." -CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -ldflags="$LDFLAGS" -o "$DIST_DIR/theta-agent-helper-windows-arm64.exe" ./cmd/theta-agent-helper/ +CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -ldflags="$GUI_LDFLAGS" -o "$DIST_DIR/theta-agent-helper-windows-arm64.exe" ./cmd/theta-agent-helper/ echo " -> darwin/amd64 (macOS Intel)..." CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="$LDFLAGS" -o "$DIST_DIR/theta-agent-darwin-amd64" @@ -45,10 +50,10 @@ echo " -> linux/arm64 tray..." CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="$LDFLAGS" -o "$DIST_DIR/theta-agent-tray-linux-arm64" ./cmd/theta-agent-tray/ echo " -> windows/amd64 tray..." -CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="$LDFLAGS" -o "$DIST_DIR/theta-agent-tray-windows-amd64.exe" ./cmd/theta-agent-tray/ +CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="$GUI_LDFLAGS" -o "$DIST_DIR/theta-agent-tray-windows-amd64.exe" ./cmd/theta-agent-tray/ echo " -> windows/arm64 tray..." -CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -ldflags="$LDFLAGS" -o "$DIST_DIR/theta-agent-tray-windows-arm64.exe" ./cmd/theta-agent-tray/ +CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -ldflags="$GUI_LDFLAGS" -o "$DIST_DIR/theta-agent-tray-windows-arm64.exe" ./cmd/theta-agent-tray/ echo "" echo "Build complete! Artifacts in $DIST_DIR:" diff --git a/cli.go b/cli.go index e114254..b5f850a 100644 --- a/cli.go +++ b/cli.go @@ -55,7 +55,7 @@ func printUsage() { fmt.Println(" theta-agent Run agent daemon in foreground") fmt.Println(" theta-agent get-secret Fetch single secret value from OpenBao") fmt.Println(" theta-agent get-secrets [flags] Fetch all host/resource secrets (flags: --json, --env)") - fmt.Println(" theta-agent update Self-update binary from SSO Manager") + fmt.Println(" theta-agent update Self-update binary from Theta Directory") fmt.Println(" theta-agent reinitialize [flags] Reset enrollment credentials & re-register") fmt.Println(" theta-agent install-service (Windows) register the agent as a service") fmt.Println(" theta-agent remove-service (Windows) unregister the agent service") diff --git a/installer/windows/installer.iss b/installer/windows/installer.iss index 4105876..06a737a 100644 --- a/installer/windows/installer.iss +++ b/installer/windows/installer.iss @@ -9,11 +9,11 @@ ; theta-agent-2.1.0-windows-amd64-setup.exe /SILENT ^ ; /SERVER_URL=https://sso.example.com /JOIN_KEY=tjk_... ; -; Interactively, a wizard page asks for the SSO Manager URL and a join key (with -; a button that opens the SSO's Directory -> Install Agent page to mint one). -; In silent mode, /SERVER_URL, /JOIN_KEY, /AUTH_TOKEN, /PUBLIC_KEY and -; /B64_CONFIG (base64 of a full agent.yml) drive the same result. The values are -; written into agent.yml so the installed service enrolls on first start. +; Interactively, a wizard page asks for the Theta Directory URL and a join key +; (with a button that opens the Theta Directory's Directory -> Install Agent page +; to mint one). In silent mode, /SERVER_URL, /JOIN_KEY, /AUTH_TOKEN, /PUBLIC_KEY +; and /B64_CONFIG (base64 of a full agent.yml) drive the same result. The values +; are written into agent.yml so the installed service enrolls on first start. #ifndef MyAppVersion #define MyAppVersion "2.1.0" @@ -128,8 +128,8 @@ begin Result := True; end; -// Opens the SSO's Directory page so the operator can mint a join key right from -// the wizard. Uses the Server URL they just typed. +// Opens the Theta Directory page so the operator can mint a join key right +// from the wizard. Uses the Server URL they just typed. procedure OnOpenSSOClick(Sender: TObject); var Url: String; @@ -137,7 +137,7 @@ var begin Url := Trim(ServerURLEdit.Text); if Url = '' then begin - MsgBox('Enter the SSO Manager URL first (e.g. https://sso.example.com).', + MsgBox('Enter the Theta Directory URL first (e.g. https://directory.example.com).', mbInformation, MB_OK); Exit; end; @@ -150,48 +150,54 @@ var InfoLabel: TNewStaticText; UrlLabel: TNewStaticText; KeyLabel: TNewStaticText; + Y: Integer; begin AgentConfigPage := CreateCustomPage(wpWelcome, - 'SSO Manager connection', - 'Tell the agent which SSO to enroll with.'); + 'Theta Directory connection', + 'Tell the agent which Theta Directory to enroll with.'); InfoLabel := TNewStaticText.Create(AgentConfigPage); InfoLabel.Parent := AgentConfigPage.Surface; InfoLabel.WordWrap := True; - InfoLabel.Caption := 'Paste the SSO Manager URL for this deployment. Then either paste a join key ' + InfoLabel.Caption := 'Paste the Theta Directory URL for this deployment. Then either paste a join key ' + '(mint one with the button below, under Directory -> Install Agent) or leave it blank to ' + 'enroll from the tray / CLI later.'; - InfoLabel.AutoSize := True; - InfoLabel.Width := AgentConfigPage.Surface.Width; + // WordWrap + AutoSize are mutually exclusive in VCL; give the wrapped label a + // fixed height so the fields below it land on screen. + InfoLabel.Width := AgentConfigPage.SurfaceWidth; + InfoLabel.Height := ScaleY(48); + + Y := InfoLabel.Top + InfoLabel.Height + ScaleY(12); UrlLabel := TNewStaticText.Create(AgentConfigPage); UrlLabel.Parent := AgentConfigPage.Surface; - UrlLabel.Caption := 'SSO Manager URL:'; - UrlLabel.Top := InfoLabel.Top + InfoLabel.Height + 16; + UrlLabel.Caption := 'Theta Directory URL:'; + UrlLabel.Top := Y; ServerURLEdit := TNewEdit.Create(AgentConfigPage); ServerURLEdit.Parent := AgentConfigPage.Surface; - ServerURLEdit.Top := UrlLabel.Top + UrlLabel.Height + 4; - ServerURLEdit.Width := AgentConfigPage.Surface.Width; + ServerURLEdit.Top := UrlLabel.Top + UrlLabel.Height + ScaleY(4); + ServerURLEdit.Width := AgentConfigPage.SurfaceWidth; ServerURLEdit.Text := ServerURL; OpenSSOButton := TNewButton.Create(AgentConfigPage); OpenSSOButton.Parent := AgentConfigPage.Surface; - OpenSSOButton.Top := ServerURLEdit.Top + ServerURLEdit.Height + 8; + OpenSSOButton.Top := ServerURLEdit.Top + ServerURLEdit.Height + ScaleY(10); OpenSSOButton.Left := ServerURLEdit.Left; - OpenSSOButton.Caption := 'Open SSO install-agent page...'; - OpenSSOButton.Width := 190; + OpenSSOButton.Caption := 'Open Theta Directory install-agent page...'; + OpenSSOButton.Width := WizardForm.CalculateButtonWidth([OpenSSOButton.Caption]); + OpenSSOButton.Height := ScaleY(23); OpenSSOButton.OnClick := @OnOpenSSOClick; KeyLabel := TNewStaticText.Create(AgentConfigPage); KeyLabel.Parent := AgentConfigPage.Surface; KeyLabel.Caption := 'Join key (optional):'; - KeyLabel.Top := OpenSSOButton.Top + OpenSSOButton.Height + 12; + KeyLabel.Top := OpenSSOButton.Top + OpenSSOButton.Height + ScaleY(12); JoinKeyEdit := TNewEdit.Create(AgentConfigPage); JoinKeyEdit.Parent := AgentConfigPage.Surface; - JoinKeyEdit.Top := KeyLabel.Top + KeyLabel.Height + 4; - JoinKeyEdit.Width := AgentConfigPage.Surface.Width; + JoinKeyEdit.Top := KeyLabel.Top + KeyLabel.Height + ScaleY(4); + JoinKeyEdit.Width := AgentConfigPage.SurfaceWidth; JoinKeyEdit.Text := JoinKey; end; diff --git a/main.go b/main.go index 50026ce..437796b 100644 --- a/main.go +++ b/main.go @@ -63,7 +63,7 @@ func runAgent() { } cfg := cm.Get() - log.Printf("Connecting to SSO Manager at %s", cfg.ServerURL) + 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, @@ -83,7 +83,7 @@ func runAgent() { // Tray IPC server — desktop tray connects here for status updates. go globalTrayServer.Start() - // WebSocket connection to SSO Manager + // WebSocket connection to Theta Directory go connectWebSocket(cm, exec) // Home detection + tray status push (polls public IP every 60s). diff --git a/scripts/setup-build-env.ps1 b/scripts/setup-build-env.ps1 index 51aeb21..483b305 100644 --- a/scripts/setup-build-env.ps1 +++ b/scripts/setup-build-env.ps1 @@ -251,13 +251,16 @@ function Invoke-Build { $dist = Join-Path $RepoRoot 'dist' New-Item -ItemType Directory -Force -Path $dist | Out-Null $flags = '-s -w' + # The tray and helper are GUI-subsystem binaries: no console window pops up + # when the installer starts the tray or the service spawns the helper. + $guiFlags = '-s -w -H=windowsgui' foreach ($arch in @('amd64','arm64')) { $env:GOOS='windows'; $env:GOARCH=$arch; $env:CGO_ENABLED='0' go build "-ldflags=$flags" -o (Join-Path $dist "theta-agent-windows-$arch.exe") $RepoRoot if ($LASTEXITCODE -ne 0) { Write-Fail "build agent windows/$arch failed"; return } - go build "-ldflags=$flags" -o (Join-Path $dist "theta-agent-tray-windows-$arch.exe") (Join-Path $RepoRoot 'cmd\theta-agent-tray') + go build "-ldflags=$guiFlags" -o (Join-Path $dist "theta-agent-tray-windows-$arch.exe") (Join-Path $RepoRoot 'cmd\theta-agent-tray') if ($LASTEXITCODE -ne 0) { Write-Fail "build tray windows/$arch failed"; return } - go build "-ldflags=$flags" -o (Join-Path $dist "theta-agent-helper-windows-$arch.exe") (Join-Path $RepoRoot 'cmd\theta-agent-helper') + go build "-ldflags=$guiFlags" -o (Join-Path $dist "theta-agent-helper-windows-$arch.exe") (Join-Path $RepoRoot 'cmd\theta-agent-helper') if ($LASTEXITCODE -ne 0) { Write-Fail "build helper windows/$arch failed"; return } } Remove-Item Env:GOOS,Env:GOARCH,Env:CGO_ENABLED -ErrorAction SilentlyContinue diff --git a/service_cli_windows.go b/service_cli_windows.go index 7732eb4..7e6d963 100644 --- a/service_cli_windows.go +++ b/service_cli_windows.go @@ -59,7 +59,13 @@ func installService() { logFatal("cannot create service: %v", err) } defer s.Close() - fmt.Printf("[+] Registered theta-agent service (%s)\n", filepath.Base(exe)) + + // 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() { diff --git a/telemetry.go b/telemetry.go index 3917709..edebd86 100644 --- a/telemetry.go +++ b/telemetry.go @@ -663,6 +663,6 @@ func pushDiscovery(c MessageWriter, cfg *Config) { if err := c.WriteMessage(websocket.TextMessage, payload); err != nil { log.Printf("Failed to send discovery data: %v", err) } else { - log.Println("Discovery data pushed to SSO Manager.") + log.Println("Discovery data pushed to Theta Directory.") } } diff --git a/websocket.go b/websocket.go index e073f72..1f1045f 100644 --- a/websocket.go +++ b/websocket.go @@ -146,7 +146,7 @@ func connectWebSocket(cm *ConfigManager, exec Executor) { // credential every 5s just floods the SSO and its audit log // forever, so back off hard and say plainly what is wrong. if resp != nil && (resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden) { - log.Printf("Server rejected our token (HTTP %d). Enroll this agent in the SSO Directory and put the issued token in agent.yml. Retrying in %s.", resp.StatusCode, authRetryInterval) + log.Printf("Server rejected our token (HTTP %d). Enroll this agent in the Theta Directory and put the issued token in agent.yml. Retrying in %s.", resp.StatusCode, authRetryInterval) time.Sleep(authRetryInterval) continue } @@ -155,7 +155,7 @@ func connectWebSocket(cm *ConfigManager, exec Executor) { continue } - log.Println("Successfully connected to SSO Manager.") + log.Println("Successfully connected to Theta Directory.") wsConnected.Store(true) stopCh := make(chan struct{}) @@ -213,7 +213,7 @@ func connectWebSocket(cm *ConfigManager, exec Executor) { // than at Dial. if websocket.IsCloseError(err, closeUnauthorized, closeRevoked, closeTokenRotated) { authRejected = true - log.Printf("Server closed the connection: %v. This agent's token is not valid for that SSO — re-enroll it and update agent.yml.", err) + log.Printf("Server closed the connection: %v. This agent's token is not valid for that Theta Directory — re-enroll it and update agent.yml.", err) } else { log.Println("WebSocket read error:", err) } @@ -345,7 +345,7 @@ func handleCommand(cm *ConfigManager, msg WSMessage, c MessageWriter, exec Execu log.Printf("Enrolled, but could not persist credentials: %v", err) log.Printf("This agent will re-enroll on every reconnect until %s is writable.", cm.configPath) } else { - log.Printf("Enrolled with the SSO. Credentials written to %s; the join key is no longer needed.", cm.configPath) + log.Printf("Enrolled with Theta Directory. Credentials written to %s; the join key is no longer needed.", cm.configPath) } sendResponse("ok", "enrollment stored") return