release: v1.7.0 - Linux ARM, Windows, and macOS theta-agent binaries
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
# Binaries and build output
|
||||||
|
theta-agent
|
||||||
|
theta-agent-*
|
||||||
|
dist/
|
||||||
|
*.exe
|
||||||
@@ -5,6 +5,16 @@ 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.7.0] - 2026-08-08
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Multi-Architecture & Multi-OS Binaries.** Built cross-platform targets for Linux ARM (arm64, armv7), Windows (amd64, arm64), and macOS (Intel, Apple Silicon M1/M2/M3/M4).
|
||||||
|
- **Cross-Compilation Pipeline (`build_all.sh`).** Automated Go build toolchain generating static binaries for all 7 target platforms.
|
||||||
|
- **Installer OS & Architecture Auto-Detection.** Updated `install.sh` to auto-detect `uname -s` and `uname -m` to download matching release binaries.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Systemd & Docker Command Dispatching.** Handled systemd actions (`start`, `stop`, `restart`, `reload`) and Docker container metrics/actions cleanly across Linux distributions.
|
||||||
|
|
||||||
## [v1.6.0] - 2026-08-07
|
## [v1.6.0] - 2026-08-07
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
Executable
+36
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Cross-compilation script for Theta Agent across Linux (amd64, arm64, armv7), Windows (amd64, arm64), and macOS (amd64, arm64).
|
||||||
|
|
||||||
|
DIST_DIR="./dist"
|
||||||
|
mkdir -p "$DIST_DIR"
|
||||||
|
|
||||||
|
LDFLAGS="-s -w"
|
||||||
|
|
||||||
|
echo "Building Theta Agent binaries..."
|
||||||
|
|
||||||
|
echo " -> linux/amd64..."
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="$LDFLAGS" -o "$DIST_DIR/theta-agent-linux-amd64"
|
||||||
|
|
||||||
|
echo " -> linux/arm64..."
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="$LDFLAGS" -o "$DIST_DIR/theta-agent-linux-arm64"
|
||||||
|
|
||||||
|
echo " -> linux/armv7..."
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags="$LDFLAGS" -o "$DIST_DIR/theta-agent-linux-armv7"
|
||||||
|
|
||||||
|
echo " -> windows/amd64..."
|
||||||
|
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="$LDFLAGS" -o "$DIST_DIR/theta-agent-windows-amd64.exe"
|
||||||
|
|
||||||
|
echo " -> windows/arm64..."
|
||||||
|
CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -ldflags="$LDFLAGS" -o "$DIST_DIR/theta-agent-windows-arm64.exe"
|
||||||
|
|
||||||
|
echo " -> darwin/amd64 (macOS Intel)..."
|
||||||
|
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="$LDFLAGS" -o "$DIST_DIR/theta-agent-darwin-amd64"
|
||||||
|
|
||||||
|
echo " -> darwin/arm64 (macOS Apple Silicon)..."
|
||||||
|
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="$LDFLAGS" -o "$DIST_DIR/theta-agent-darwin-arm64"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Build complete! Artifacts in $DIST_DIR:"
|
||||||
|
ls -lh "$DIST_DIR"
|
||||||
@@ -32,7 +32,7 @@ func handleCLI(args []string) bool {
|
|||||||
runReinitialize(args[1:])
|
runReinitialize(args[1:])
|
||||||
return true
|
return true
|
||||||
case "--version", "version", "-v":
|
case "--version", "version", "-v":
|
||||||
fmt.Println("Theta Agent v1.2.0")
|
fmt.Println("Theta Agent v1.7.0")
|
||||||
return true
|
return true
|
||||||
case "--help", "help", "-h":
|
case "--help", "help", "-h":
|
||||||
printUsage()
|
printUsage()
|
||||||
|
|||||||
+33
-2
@@ -110,9 +110,40 @@ fi
|
|||||||
|
|
||||||
log "Starting Theta Agent installation..."
|
log "Starting Theta Agent installation..."
|
||||||
|
|
||||||
|
# Architecture and OS detection
|
||||||
|
OS_NAME="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||||
|
ARCH_NAME="$(uname -m)"
|
||||||
|
BINARY_NAME="theta-agent-linux-amd64"
|
||||||
|
|
||||||
|
case "$OS_NAME" in
|
||||||
|
linux*)
|
||||||
|
case "$ARCH_NAME" in
|
||||||
|
x86_64|amd64) BINARY_NAME="theta-agent-linux-amd64" ;;
|
||||||
|
aarch64|arm64) BINARY_NAME="theta-agent-linux-arm64" ;;
|
||||||
|
armv7*|armhf) BINARY_NAME="theta-agent-linux-armv7" ;;
|
||||||
|
*) BINARY_NAME="theta-agent-linux-amd64" ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
darwin*)
|
||||||
|
case "$ARCH_NAME" in
|
||||||
|
x86_64|amd64) BINARY_NAME="theta-agent-darwin-amd64" ;;
|
||||||
|
arm64|aarch64) BINARY_NAME="theta-agent-darwin-arm64" ;;
|
||||||
|
*) BINARY_NAME="theta-agent-darwin-arm64" ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
mingw*|msys*|cygwin*)
|
||||||
|
case "$ARCH_NAME" in
|
||||||
|
aarch64|arm64) BINARY_NAME="theta-agent-windows-arm64.exe" ;;
|
||||||
|
*) BINARY_NAME="theta-agent-windows-amd64.exe" ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
BINARY_URL="https://github.com/theta42/theta-agent/releases/latest/download/${BINARY_NAME}"
|
||||||
|
|
||||||
# 3. Install binary
|
# 3. Install binary
|
||||||
log "Downloading binary from $BINARY_URL..."
|
log "Detected OS: $OS_NAME ($ARCH_NAME) -> Downloading binary $BINARY_NAME..."
|
||||||
curl -fsSL "$BINARY_URL" -o "$BIN_PATH.tmp" || error "Failed to download binary."
|
curl -fsSL "$BINARY_URL" -o "$BIN_PATH.tmp" || error "Failed to download binary from $BINARY_URL"
|
||||||
chmod +x "$BIN_PATH.tmp"
|
chmod +x "$BIN_PATH.tmp"
|
||||||
mv -f "$BIN_PATH.tmp" "$BIN_PATH"
|
mv -f "$BIN_PATH.tmp" "$BIN_PATH"
|
||||||
|
|
||||||
|
|||||||
+235
-30
@@ -7,6 +7,9 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -17,6 +20,34 @@ import (
|
|||||||
"github.com/shirou/gopsutil/v3/mem"
|
"github.com/shirou/gopsutil/v3/mem"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type CPUDetails struct {
|
||||||
|
Model string `json:"model"`
|
||||||
|
Cores int `json:"cores"`
|
||||||
|
Threads int `json:"threads"`
|
||||||
|
MHz float64 `json:"mhz"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RAMDetails struct {
|
||||||
|
TotalBytes uint64 `json:"total_bytes"`
|
||||||
|
UsedBytes uint64 `json:"used_bytes"`
|
||||||
|
BuffersCacheBytes uint64 `json:"buffers_cache_bytes"`
|
||||||
|
FreeBytes uint64 `json:"free_bytes"`
|
||||||
|
UsedPercent float64 `json:"used_percent"`
|
||||||
|
BuffersCachePercent float64 `json:"buffers_cache_percent"`
|
||||||
|
FreePercent float64 `json:"free_percent"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiskItem struct {
|
||||||
|
Mountpoint string `json:"mountpoint"`
|
||||||
|
Device string `json:"device"`
|
||||||
|
FSType string `json:"fstype"`
|
||||||
|
DriveType string `json:"drivetype"`
|
||||||
|
TotalBytes uint64 `json:"total_bytes"`
|
||||||
|
UsedBytes uint64 `json:"used_bytes"`
|
||||||
|
FreeBytes uint64 `json:"free_bytes"`
|
||||||
|
UsagePercent float64 `json:"usage_percent"`
|
||||||
|
}
|
||||||
|
|
||||||
type DiscoveryData struct {
|
type DiscoveryData struct {
|
||||||
Hostname string `json:"hostname"`
|
Hostname string `json:"hostname"`
|
||||||
IPs []string `json:"ip_addresses"`
|
IPs []string `json:"ip_addresses"`
|
||||||
@@ -24,19 +55,25 @@ type DiscoveryData struct {
|
|||||||
OS string `json:"os"`
|
OS string `json:"os"`
|
||||||
Kernel string `json:"kernel"`
|
Kernel string `json:"kernel"`
|
||||||
CPUModel string `json:"cpu"`
|
CPUModel string `json:"cpu"`
|
||||||
|
CPUDetails CPUDetails `json:"cpu_details"`
|
||||||
RAMTotalGB float64 `json:"ram_total_gb"`
|
RAMTotalGB float64 `json:"ram_total_gb"`
|
||||||
|
RAMDetails RAMDetails `json:"ram_details"`
|
||||||
DiskTotalGB float64 `json:"disk_total_gb"`
|
DiskTotalGB float64 `json:"disk_total_gb"`
|
||||||
|
Disks []DiskItem `json:"disks"`
|
||||||
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"`
|
||||||
RAMUsagePercent float64 `json:"ram_usage_percent"`
|
CPUDetails CPUDetails `json:"cpu_details"`
|
||||||
DiskUsagePercent float64 `json:"disk_usage_percent"`
|
RAMUsagePercent float64 `json:"ram_usage_percent"`
|
||||||
ZFSHealth string `json:"zfs_health,omitempty"`
|
RAMDetails RAMDetails `json:"ram_details"`
|
||||||
GPUUsage float64 `json:"gpu_usage_percent,omitempty"`
|
DiskUsagePercent float64 `json:"disk_usage_percent"`
|
||||||
Timestamp string `json:"timestamp"`
|
Disks []DiskItem `json:"disks"`
|
||||||
|
ZFSHealth string `json:"zfs_health,omitempty"`
|
||||||
|
GPUUsage float64 `json:"gpu_usage_percent,omitempty"`
|
||||||
|
Timestamp string `json:"timestamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPublicIP() string {
|
func getPublicIP() string {
|
||||||
@@ -62,6 +99,146 @@ func getPublicIP() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func collectCPUDetails() CPUDetails {
|
||||||
|
cpuInfo, _ := cpu.Info()
|
||||||
|
model := "Unknown"
|
||||||
|
cores := 0
|
||||||
|
mhz := 0.0
|
||||||
|
if len(cpuInfo) > 0 {
|
||||||
|
model = cpuInfo[0].ModelName
|
||||||
|
if model == "" {
|
||||||
|
model = cpuInfo[0].Model
|
||||||
|
}
|
||||||
|
cores = int(cpuInfo[0].Cores)
|
||||||
|
mhz = cpuInfo[0].Mhz
|
||||||
|
}
|
||||||
|
threads := runtime.NumCPU()
|
||||||
|
if t, err := cpu.Counts(true); err == nil && t > 0 {
|
||||||
|
threads = t
|
||||||
|
}
|
||||||
|
if cores <= 0 {
|
||||||
|
if c, err := cpu.Counts(false); err == nil && c > 0 {
|
||||||
|
cores = c
|
||||||
|
} else {
|
||||||
|
cores = threads
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return CPUDetails{
|
||||||
|
Model: model,
|
||||||
|
Cores: cores,
|
||||||
|
Threads: threads,
|
||||||
|
MHz: mhz,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectRAMDetails() RAMDetails {
|
||||||
|
vm, err := mem.VirtualMemory()
|
||||||
|
if err != nil || vm == nil {
|
||||||
|
return RAMDetails{}
|
||||||
|
}
|
||||||
|
bufCache := vm.Buffers + vm.Cached
|
||||||
|
total := float64(vm.Total)
|
||||||
|
usedPct := 0.0
|
||||||
|
bufPct := 0.0
|
||||||
|
freePct := 0.0
|
||||||
|
if total > 0 {
|
||||||
|
usedPct = (float64(vm.Used) / total) * 100.0
|
||||||
|
bufPct = (float64(bufCache) / total) * 100.0
|
||||||
|
freePct = (float64(vm.Free) / total) * 100.0
|
||||||
|
}
|
||||||
|
return RAMDetails{
|
||||||
|
TotalBytes: vm.Total,
|
||||||
|
UsedBytes: vm.Used,
|
||||||
|
BuffersCacheBytes: bufCache,
|
||||||
|
FreeBytes: vm.Free,
|
||||||
|
UsedPercent: usedPct,
|
||||||
|
BuffersCachePercent: bufPct,
|
||||||
|
FreePercent: freePct,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDriveType(device string) string {
|
||||||
|
devName := filepath.Base(device)
|
||||||
|
devName = strings.TrimRight(devName, "0123456789p")
|
||||||
|
if strings.HasPrefix(devName, "nvme") {
|
||||||
|
return "NVMe"
|
||||||
|
}
|
||||||
|
rotPath := filepath.Join("/sys/block", devName, "queue/rotational")
|
||||||
|
data, err := os.ReadFile(rotPath)
|
||||||
|
if err == nil {
|
||||||
|
val := strings.TrimSpace(string(data))
|
||||||
|
if val == "0" {
|
||||||
|
return "SSD"
|
||||||
|
} else if val == "1" {
|
||||||
|
return "HDD"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "SSD/HDD"
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectDiskItems() []DiskItem {
|
||||||
|
var items []DiskItem
|
||||||
|
partitions, err := disk.Partitions(false)
|
||||||
|
if err != nil || len(partitions) == 0 {
|
||||||
|
d, err2 := disk.Usage("/")
|
||||||
|
if err2 == nil {
|
||||||
|
items = append(items, DiskItem{
|
||||||
|
Mountpoint: "/",
|
||||||
|
FSType: d.Fstype,
|
||||||
|
DriveType: getDriveType(d.Path),
|
||||||
|
TotalBytes: d.Total,
|
||||||
|
UsedBytes: d.Used,
|
||||||
|
FreeBytes: d.Free,
|
||||||
|
UsagePercent: d.UsedPercent,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
for _, p := range partitions {
|
||||||
|
if strings.HasPrefix(p.Mountpoint, "/proc") || strings.HasPrefix(p.Mountpoint, "/sys") || strings.HasPrefix(p.Mountpoint, "/dev") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if seen[p.Mountpoint] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[p.Mountpoint] = true
|
||||||
|
u, err := disk.Usage(p.Mountpoint)
|
||||||
|
if err != nil || u.Total == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fstype := p.Fstype
|
||||||
|
if fstype == "" {
|
||||||
|
fstype = u.Fstype
|
||||||
|
}
|
||||||
|
items = append(items, DiskItem{
|
||||||
|
Mountpoint: p.Mountpoint,
|
||||||
|
Device: p.Device,
|
||||||
|
FSType: fstype,
|
||||||
|
DriveType: getDriveType(p.Device),
|
||||||
|
TotalBytes: u.Total,
|
||||||
|
UsedBytes: u.Used,
|
||||||
|
FreeBytes: u.Free,
|
||||||
|
UsagePercent: u.UsedPercent,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if len(items) == 0 {
|
||||||
|
d, err2 := disk.Usage("/")
|
||||||
|
if err2 == nil {
|
||||||
|
items = append(items, DiskItem{
|
||||||
|
Mountpoint: "/",
|
||||||
|
FSType: d.Fstype,
|
||||||
|
DriveType: getDriveType(d.Path),
|
||||||
|
TotalBytes: d.Total,
|
||||||
|
UsedBytes: d.Used,
|
||||||
|
FreeBytes: d.Free,
|
||||||
|
UsagePercent: d.UsedPercent,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
// 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()
|
||||||
@@ -76,27 +253,36 @@ func CollectDiscoveryData(cfg *Config) DiscoveryData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vm, _ := mem.VirtualMemory()
|
vm := collectRAMDetails()
|
||||||
d, _ := disk.Usage("/")
|
disks := collectDiskItems()
|
||||||
|
cpuDet := collectCPUDetails()
|
||||||
cpuInfo, _ := cpu.Info()
|
|
||||||
cpuModel := "Unknown"
|
|
||||||
if len(cpuInfo) > 0 {
|
|
||||||
cpuModel = cpuInfo[0].Model
|
|
||||||
}
|
|
||||||
|
|
||||||
pubIP := getPublicIP()
|
pubIP := getPublicIP()
|
||||||
|
|
||||||
|
diskTotalGB := 0.0
|
||||||
|
for _, d := range disks {
|
||||||
|
if d.Mountpoint == "/" {
|
||||||
|
diskTotalGB = float64(d.TotalBytes) / (1024 * 1024 * 1024)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if diskTotalGB == 0 && len(disks) > 0 {
|
||||||
|
diskTotalGB = float64(disks[0].TotalBytes) / (1024 * 1024 * 1024)
|
||||||
|
}
|
||||||
|
|
||||||
return DiscoveryData{
|
return DiscoveryData{
|
||||||
Hostname: h.Hostname,
|
Hostname: h.Hostname,
|
||||||
IPs: ips,
|
IPs: ips,
|
||||||
PublicIP: pubIP,
|
PublicIP: pubIP,
|
||||||
OS: fmt.Sprintf("%s %s", h.OS, h.Platform),
|
OS: fmt.Sprintf("%s %s", h.OS, h.Platform),
|
||||||
Kernel: h.KernelVersion,
|
Kernel: h.KernelVersion,
|
||||||
CPUModel: cpuModel,
|
CPUModel: cpuDet.Model,
|
||||||
RAMTotalGB: float64(vm.Total) / (1024 * 1024 * 1024),
|
CPUDetails: cpuDet,
|
||||||
DiskTotalGB: float64(d.Total) / (1024 * 1024 * 1024),
|
RAMTotalGB: float64(vm.TotalBytes) / (1024 * 1024 * 1024),
|
||||||
Location: cfg.Location,
|
RAMDetails: vm,
|
||||||
|
DiskTotalGB: diskTotalGB,
|
||||||
|
Disks: disks,
|
||||||
|
Location: cfg.Location,
|
||||||
Capabilities: map[string]interface{}{
|
Capabilities: map[string]interface{}{
|
||||||
"telemetry": cfg.Capabilities.Telemetry,
|
"telemetry": cfg.Capabilities.Telemetry,
|
||||||
"configure_ldap": cfg.Capabilities.ConfigureLDAP,
|
"configure_ldap": cfg.Capabilities.ConfigureLDAP,
|
||||||
@@ -104,6 +290,7 @@ func CollectDiscoveryData(cfg *Config) DiscoveryData {
|
|||||||
"secrets": cfg.Capabilities.Secrets,
|
"secrets": cfg.Capabilities.Secrets,
|
||||||
"iam": cfg.Capabilities.IAM,
|
"iam": cfg.Capabilities.IAM,
|
||||||
"reboot": cfg.Capabilities.Reboot,
|
"reboot": cfg.Capabilities.Reboot,
|
||||||
|
"shutdown": true,
|
||||||
"service_control": cfg.Capabilities.ServiceControl,
|
"service_control": cfg.Capabilities.ServiceControl,
|
||||||
"arbitrary_bash": cfg.Capabilities.ArbitraryBash,
|
"arbitrary_bash": cfg.Capabilities.ArbitraryBash,
|
||||||
},
|
},
|
||||||
@@ -113,21 +300,36 @@ func CollectDiscoveryData(cfg *Config) DiscoveryData {
|
|||||||
// CollectTelemetryData gathers real-time performance metrics including ZFS and GPU.
|
// CollectTelemetryData gathers real-time performance metrics including ZFS and GPU.
|
||||||
func CollectTelemetryData(exec Executor) TelemetryData {
|
func CollectTelemetryData(exec Executor) TelemetryData {
|
||||||
cpuPerc, _ := cpu.Percent(time.Second, false)
|
cpuPerc, _ := cpu.Percent(time.Second, false)
|
||||||
vm, _ := mem.VirtualMemory()
|
vm := collectRAMDetails()
|
||||||
d, _ := disk.Usage("/")
|
disks := collectDiskItems()
|
||||||
|
cpuDet := collectCPUDetails()
|
||||||
|
|
||||||
cpuVal := 0.0
|
cpuVal := 0.0
|
||||||
if len(cpuPerc) > 0 {
|
if len(cpuPerc) > 0 {
|
||||||
cpuVal = cpuPerc[0]
|
cpuVal = cpuPerc[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diskVal := 0.0
|
||||||
|
for _, d := range disks {
|
||||||
|
if d.Mountpoint == "/" {
|
||||||
|
diskVal = d.UsagePercent
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if diskVal == 0 && len(disks) > 0 {
|
||||||
|
diskVal = disks[0].UsagePercent
|
||||||
|
}
|
||||||
|
|
||||||
return TelemetryData{
|
return TelemetryData{
|
||||||
CPUUsagePercent: cpuVal,
|
CPUUsagePercent: cpuVal,
|
||||||
|
CPUDetails: cpuDet,
|
||||||
RAMUsagePercent: vm.UsedPercent,
|
RAMUsagePercent: vm.UsedPercent,
|
||||||
DiskUsagePercent: d.UsedPercent,
|
RAMDetails: vm,
|
||||||
ZFSHealth: collectZFSHealth(exec),
|
DiskUsagePercent: diskVal,
|
||||||
GPUUsage: collectGPUUsage(exec),
|
Disks: disks,
|
||||||
Timestamp: time.Now().Format(time.RFC3339),
|
ZFSHealth: collectZFSHealth(exec),
|
||||||
|
GPUUsage: collectGPUUsage(exec),
|
||||||
|
Timestamp: time.Now().Format(time.RFC3339),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,10 +396,13 @@ func StartTelemetryLoop(c MessageWriter, cm *ConfigManager, exec Executor, stopC
|
|||||||
Type: "telemetry",
|
Type: "telemetry",
|
||||||
Payload: map[string]interface{}{
|
Payload: map[string]interface{}{
|
||||||
"cpu_usage_percent": telemetry.CPUUsagePercent,
|
"cpu_usage_percent": telemetry.CPUUsagePercent,
|
||||||
|
"cpu_details": telemetry.CPUDetails,
|
||||||
"ram_usage_percent": telemetry.RAMUsagePercent,
|
"ram_usage_percent": telemetry.RAMUsagePercent,
|
||||||
|
"ram_details": telemetry.RAMDetails,
|
||||||
"disk_usage_percent": telemetry.DiskUsagePercent,
|
"disk_usage_percent": telemetry.DiskUsagePercent,
|
||||||
|
"disks": telemetry.Disks,
|
||||||
"zfs_health": telemetry.ZFSHealth,
|
"zfs_health": telemetry.ZFSHealth,
|
||||||
"gpu_usage_percent": telemetry.GPUUsage,
|
"gpu_usage_percent": telemetry.GPUUsage,
|
||||||
"timestamp": telemetry.Timestamp,
|
"timestamp": telemetry.Timestamp,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
BIN
Binary file not shown.
Binary file not shown.
@@ -372,6 +372,52 @@ func handleCommand(cm *ConfigManager, msg WSMessage, c MessageWriter, exec Execu
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
sendResponse("ok", "system rebooting")
|
sendResponse("ok", "system rebooting")
|
||||||
|
case "shutdown":
|
||||||
|
if !verifySignature(cfg, msg) {
|
||||||
|
sendResponse("error", "signature verification failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !cfg.Capabilities.Reboot {
|
||||||
|
log.Println("Shutdown rejected: capability disabled in agent.yml")
|
||||||
|
sendResponse("error", "shutdown capability disabled")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("Executing shutdown...")
|
||||||
|
sendResponse("ok", "system shutting down")
|
||||||
|
if _, err := exec.Execute("shutdown", "-h", "now"); err != nil {
|
||||||
|
exec.Execute("poweroff")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case "systemd_action":
|
||||||
|
serviceName, _ := msg.Payload["service"].(string)
|
||||||
|
action, _ := msg.Payload["action"].(string)
|
||||||
|
if serviceName == "" {
|
||||||
|
sendResponse("error", "service name required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if action == "" {
|
||||||
|
action = "status"
|
||||||
|
}
|
||||||
|
if action != "status" && !verifySignature(cfg, msg) {
|
||||||
|
sendResponse("error", "signature verification failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("Executing systemctl %s %s...", action, serviceName)
|
||||||
|
out, err := exec.Execute("systemctl", action, serviceName)
|
||||||
|
errMsg := ""
|
||||||
|
if err != nil {
|
||||||
|
errMsg = err.Error()
|
||||||
|
}
|
||||||
|
respMap := map[string]interface{}{
|
||||||
|
"status": "ok",
|
||||||
|
"service": serviceName,
|
||||||
|
"action": action,
|
||||||
|
"output": string(out),
|
||||||
|
"error": errMsg,
|
||||||
|
}
|
||||||
|
respPayload, _ := json.Marshal(respMap)
|
||||||
|
c.WriteMessage(websocket.TextMessage, respPayload)
|
||||||
|
return
|
||||||
case "service_restart":
|
case "service_restart":
|
||||||
serviceName, ok := msg.Payload["service"].(string)
|
serviceName, ok := msg.Payload["service"].(string)
|
||||||
if !ok || !cfg.Capabilities.CanManageService(serviceName) {
|
if !ok || !cfg.Capabilities.CanManageService(serviceName) {
|
||||||
|
|||||||
Reference in New Issue
Block a user