release: v1.8.0 - Active Logged-in Users, Physical Disks & Desktop Operations
This commit is contained in:
@@ -5,6 +5,14 @@ All notable changes to the `theta-agent` daemon will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [v1.8.0] - 2026-08-08
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Active Logged-in User Sessions.** Added `collectLoggedUsers()` gathering terminal sessions (`who` / `host.Users()`) reported in discovery and live telemetry payloads.
|
||||||
|
- **Full Physical Partition & Filesystem Collection.** Switched to `disk.Partitions(true)` to list all physical drives, ZFS pools, and mount points while filtering pseudo/virtual filesystems.
|
||||||
|
- **Desktop Control Operations.** Implemented `desktop_control` WebSocket actions supporting `lock_session` (`loginctl lock-sessions`), `logout_user` (`pkill -u <user>`), `display_off` (`xset dpms force off`), and `sleep_host` (`systemctl suspend`).
|
||||||
|
- **Binary Version Reporting.** Included `AgentVersion` (`v1.8.0`) in discovery and telemetry frames.
|
||||||
|
|
||||||
## [v1.7.0] - 2026-08-08
|
## [v1.7.0] - 2026-08-08
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+62
-10
@@ -48,6 +48,13 @@ type DiskItem struct {
|
|||||||
UsagePercent float64 `json:"usage_percent"`
|
UsagePercent float64 `json:"usage_percent"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LoggedUser struct {
|
||||||
|
User string `json:"user"`
|
||||||
|
Terminal string `json:"terminal"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Started int64 `json:"started"`
|
||||||
|
}
|
||||||
|
|
||||||
type DiscoveryData struct {
|
type DiscoveryData struct {
|
||||||
Hostname string `json:"hostname"`
|
Hostname string `json:"hostname"`
|
||||||
IPs []string `json:"ip_addresses"`
|
IPs []string `json:"ip_addresses"`
|
||||||
@@ -60,20 +67,24 @@ type DiscoveryData struct {
|
|||||||
RAMDetails RAMDetails `json:"ram_details"`
|
RAMDetails RAMDetails `json:"ram_details"`
|
||||||
DiskTotalGB float64 `json:"disk_total_gb"`
|
DiskTotalGB float64 `json:"disk_total_gb"`
|
||||||
Disks []DiskItem `json:"disks"`
|
Disks []DiskItem `json:"disks"`
|
||||||
|
LoggedUsers []LoggedUser `json:"logged_users"`
|
||||||
|
Version string `json:"version"`
|
||||||
Location string `json:"location"`
|
Location string `json:"location"`
|
||||||
Capabilities map[string]interface{} `json:"capabilities"`
|
Capabilities map[string]interface{} `json:"capabilities"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TelemetryData struct {
|
type TelemetryData struct {
|
||||||
CPUUsagePercent float64 `json:"cpu_usage_percent"`
|
CPUUsagePercent float64 `json:"cpu_usage_percent"`
|
||||||
CPUDetails CPUDetails `json:"cpu_details"`
|
CPUDetails CPUDetails `json:"cpu_details"`
|
||||||
RAMUsagePercent float64 `json:"ram_usage_percent"`
|
RAMUsagePercent float64 `json:"ram_usage_percent"`
|
||||||
RAMDetails RAMDetails `json:"ram_details"`
|
RAMDetails RAMDetails `json:"ram_details"`
|
||||||
DiskUsagePercent float64 `json:"disk_usage_percent"`
|
DiskUsagePercent float64 `json:"disk_usage_percent"`
|
||||||
Disks []DiskItem `json:"disks"`
|
Disks []DiskItem `json:"disks"`
|
||||||
ZFSHealth string `json:"zfs_health,omitempty"`
|
LoggedUsers []LoggedUser `json:"logged_users"`
|
||||||
GPUUsage float64 `json:"gpu_usage_percent,omitempty"`
|
Version string `json:"version"`
|
||||||
Timestamp string `json:"timestamp"`
|
ZFSHealth string `json:"zfs_health,omitempty"`
|
||||||
|
GPUUsage float64 `json:"gpu_usage_percent,omitempty"`
|
||||||
|
Timestamp string `json:"timestamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPublicIP() string {
|
func getPublicIP() string {
|
||||||
@@ -176,9 +187,32 @@ func getDriveType(device string) string {
|
|||||||
return "SSD/HDD"
|
return "SSD/HDD"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func collectLoggedUsers() []LoggedUser {
|
||||||
|
var list []LoggedUser
|
||||||
|
users, err := host.Users()
|
||||||
|
if err != nil || len(users) == 0 {
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
for _, u := range users {
|
||||||
|
key := fmt.Sprintf("%s@%s:%s", u.User, u.Terminal, u.Host)
|
||||||
|
if seen[key] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[key] = true
|
||||||
|
list = append(list, LoggedUser{
|
||||||
|
User: u.User,
|
||||||
|
Terminal: u.Terminal,
|
||||||
|
Host: u.Host,
|
||||||
|
Started: int64(u.Started),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
func collectDiskItems() []DiskItem {
|
func collectDiskItems() []DiskItem {
|
||||||
var items []DiskItem
|
var items []DiskItem
|
||||||
partitions, err := disk.Partitions(false)
|
partitions, err := disk.Partitions(true)
|
||||||
if err != nil || len(partitions) == 0 {
|
if err != nil || len(partitions) == 0 {
|
||||||
d, err2 := disk.Usage("/")
|
d, err2 := disk.Usage("/")
|
||||||
if err2 == nil {
|
if err2 == nil {
|
||||||
@@ -195,10 +229,19 @@ func collectDiskItems() []DiskItem {
|
|||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
seen := make(map[string]bool)
|
seen := make(map[string]bool)
|
||||||
|
ignoredFSTypes := map[string]bool{
|
||||||
|
"tmpfs": true, "devtmpfs": true, "proc": true, "sysfs": true,
|
||||||
|
"cgroup": true, "cgroup2": true, "overlay": true, "squashfs": true,
|
||||||
|
"autofs": true, "devpts": true, "mqueue": true,
|
||||||
|
}
|
||||||
|
|
||||||
for _, p := range partitions {
|
for _, p := range partitions {
|
||||||
if strings.HasPrefix(p.Mountpoint, "/proc") || strings.HasPrefix(p.Mountpoint, "/sys") || strings.HasPrefix(p.Mountpoint, "/dev") {
|
if strings.HasPrefix(p.Mountpoint, "/proc") || strings.HasPrefix(p.Mountpoint, "/sys") || strings.HasPrefix(p.Mountpoint, "/dev") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if ignoredFSTypes[strings.ToLower(p.Fstype)] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if seen[p.Mountpoint] {
|
if seen[p.Mountpoint] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -239,6 +282,8 @@ func collectDiskItems() []DiskItem {
|
|||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AgentVersion = "v1.7.0"
|
||||||
|
|
||||||
// CollectDiscoveryData gathers static host information.
|
// CollectDiscoveryData gathers static host information.
|
||||||
func CollectDiscoveryData(cfg *Config) DiscoveryData {
|
func CollectDiscoveryData(cfg *Config) DiscoveryData {
|
||||||
h, _ := host.Info()
|
h, _ := host.Info()
|
||||||
@@ -256,6 +301,7 @@ func CollectDiscoveryData(cfg *Config) DiscoveryData {
|
|||||||
vm := collectRAMDetails()
|
vm := collectRAMDetails()
|
||||||
disks := collectDiskItems()
|
disks := collectDiskItems()
|
||||||
cpuDet := collectCPUDetails()
|
cpuDet := collectCPUDetails()
|
||||||
|
loggedUsers := collectLoggedUsers()
|
||||||
|
|
||||||
pubIP := getPublicIP()
|
pubIP := getPublicIP()
|
||||||
|
|
||||||
@@ -282,6 +328,8 @@ func CollectDiscoveryData(cfg *Config) DiscoveryData {
|
|||||||
RAMDetails: vm,
|
RAMDetails: vm,
|
||||||
DiskTotalGB: diskTotalGB,
|
DiskTotalGB: diskTotalGB,
|
||||||
Disks: disks,
|
Disks: disks,
|
||||||
|
LoggedUsers: loggedUsers,
|
||||||
|
Version: AgentVersion,
|
||||||
Location: cfg.Location,
|
Location: cfg.Location,
|
||||||
Capabilities: map[string]interface{}{
|
Capabilities: map[string]interface{}{
|
||||||
"telemetry": cfg.Capabilities.Telemetry,
|
"telemetry": cfg.Capabilities.Telemetry,
|
||||||
@@ -291,6 +339,7 @@ func CollectDiscoveryData(cfg *Config) DiscoveryData {
|
|||||||
"iam": cfg.Capabilities.IAM,
|
"iam": cfg.Capabilities.IAM,
|
||||||
"reboot": cfg.Capabilities.Reboot,
|
"reboot": cfg.Capabilities.Reboot,
|
||||||
"shutdown": true,
|
"shutdown": true,
|
||||||
|
"desktop_controls": true,
|
||||||
"service_control": cfg.Capabilities.ServiceControl,
|
"service_control": cfg.Capabilities.ServiceControl,
|
||||||
"arbitrary_bash": cfg.Capabilities.ArbitraryBash,
|
"arbitrary_bash": cfg.Capabilities.ArbitraryBash,
|
||||||
},
|
},
|
||||||
@@ -303,6 +352,7 @@ func CollectTelemetryData(exec Executor) TelemetryData {
|
|||||||
vm := collectRAMDetails()
|
vm := collectRAMDetails()
|
||||||
disks := collectDiskItems()
|
disks := collectDiskItems()
|
||||||
cpuDet := collectCPUDetails()
|
cpuDet := collectCPUDetails()
|
||||||
|
loggedUsers := collectLoggedUsers()
|
||||||
|
|
||||||
cpuVal := 0.0
|
cpuVal := 0.0
|
||||||
if len(cpuPerc) > 0 {
|
if len(cpuPerc) > 0 {
|
||||||
@@ -327,6 +377,8 @@ func CollectTelemetryData(exec Executor) TelemetryData {
|
|||||||
RAMDetails: vm,
|
RAMDetails: vm,
|
||||||
DiskUsagePercent: diskVal,
|
DiskUsagePercent: diskVal,
|
||||||
Disks: disks,
|
Disks: disks,
|
||||||
|
LoggedUsers: loggedUsers,
|
||||||
|
Version: AgentVersion,
|
||||||
ZFSHealth: collectZFSHealth(exec),
|
ZFSHealth: collectZFSHealth(exec),
|
||||||
GPUUsage: collectGPUUsage(exec),
|
GPUUsage: collectGPUUsage(exec),
|
||||||
Timestamp: time.Now().Format(time.RFC3339),
|
Timestamp: time.Now().Format(time.RFC3339),
|
||||||
|
|||||||
@@ -388,6 +388,50 @@ func handleCommand(cm *ConfigManager, msg WSMessage, c MessageWriter, exec Execu
|
|||||||
exec.Execute("poweroff")
|
exec.Execute("poweroff")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
case "desktop_control", "lock_session", "logout_user", "display_off", "sleep_host":
|
||||||
|
subAction, _ := msg.Payload["subAction"].(string)
|
||||||
|
if subAction == "" {
|
||||||
|
subAction = msg.Type
|
||||||
|
}
|
||||||
|
targetUser, _ := msg.Payload["user"].(string)
|
||||||
|
log.Printf("Executing desktop control action '%s' for user '%s'...", subAction, targetUser)
|
||||||
|
var out []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch subAction {
|
||||||
|
case "lock_session", "lock":
|
||||||
|
out, err = exec.Execute("loginctl", "lock-sessions")
|
||||||
|
if err != nil {
|
||||||
|
out, err = exec.Execute("xset", "dpms", "force", "off")
|
||||||
|
}
|
||||||
|
case "logout_user", "logout":
|
||||||
|
if targetUser != "" {
|
||||||
|
out, err = exec.Execute("pkill", "-KILL", "-u", targetUser)
|
||||||
|
} else {
|
||||||
|
out, err = exec.Execute("loginctl", "terminate-session")
|
||||||
|
}
|
||||||
|
case "display_off":
|
||||||
|
out, err = exec.Execute("xset", "dpms", "force", "off")
|
||||||
|
case "sleep_host", "sleep":
|
||||||
|
out, err = exec.Execute("systemctl", "suspend")
|
||||||
|
default:
|
||||||
|
sendResponse("error", fmt.Sprintf("unknown desktop action '%s'", subAction))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
errMsg := ""
|
||||||
|
if err != nil {
|
||||||
|
errMsg = err.Error()
|
||||||
|
}
|
||||||
|
respMap := map[string]interface{}{
|
||||||
|
"status": "ok",
|
||||||
|
"subAction": subAction,
|
||||||
|
"output": string(out),
|
||||||
|
"error": errMsg,
|
||||||
|
}
|
||||||
|
respPayload, _ := json.Marshal(respMap)
|
||||||
|
c.WriteMessage(websocket.TextMessage, respPayload)
|
||||||
|
return
|
||||||
case "systemd_action":
|
case "systemd_action":
|
||||||
serviceName, _ := msg.Payload["service"].(string)
|
serviceName, _ := msg.Payload["service"].(string)
|
||||||
action, _ := msg.Payload["action"].(string)
|
action, _ := msg.Payload["action"].(string)
|
||||||
|
|||||||
Reference in New Issue
Block a user