Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve ota ux #30

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 162 additions & 39 deletions wazuh-agent-status-client/main.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package main

import (
"bufio"
"embed"
"fmt"
"log"
"net"
"time"
"strings"
"bufio"
"runtime"
"strings"
"time"

"github.com/getlantern/systray"
)
Expand All @@ -17,9 +17,9 @@ import (
var embeddedFiles embed.FS

var (
statusItem, connectionItem, pauseItem, updateItem, restartItem *systray.MenuItem
statusIconConnected, statusIconDisconnected []byte
connectionIconConnected, connectionIconDisconnected []byte
statusItem, connectionItem, pauseItem, updateItem, restartItem, versionItem *systray.MenuItem
enabledIcon, disabledIcon []byte
isMonitoringUpdate bool
)

// Main entry point
Expand All @@ -39,10 +39,8 @@ func onReady() {
systray.SetTooltip("Wazuh Agent Status")

// Load icons for status and connection
statusIconConnected, _ = getEmbeddedFile("assets/green-dot.png")
statusIconDisconnected, _ = getEmbeddedFile("assets/gray-dot.png")
connectionIconConnected, _ = getEmbeddedFile("assets/green-dot.png")
connectionIconDisconnected, _ = getEmbeddedFile("assets/gray-dot.png")
enabledIcon, _ = getEmbeddedFile("assets/green-dot.png")
disabledIcon, _ = getEmbeddedFile("assets/gray-dot.png")

// Create menu items
statusItem = systray.AddMenuItem("Agent: Unknown", "Wazuh Agent Status")
Expand All @@ -52,6 +50,8 @@ func onReady() {
restartItem = systray.AddMenuItem("Restart", "Restart the Wazuh Agent")
updateItem = systray.AddMenuItem("Update", "Update the Wazuh Agent")
quitItem := systray.AddMenuItem("Quit", "Quit the application")
systray.AddSeparator()
versionItem = systray.AddMenuItem("Up to date", "The version state of the wazuhbsetup")

// Start background status update
go monitorStatus()
Expand All @@ -62,29 +62,123 @@ func onReady() {

// monitorStatus continuously fetches and updates the agent status
func monitorStatus() {
for {
status, connection := fetchStatus()
go func() {
for {
status, connection := fetchStatus()

// Update status menu item
if status == "Active" {
statusItem.SetTitle("Agent: Active")
statusItem.SetIcon(statusIconConnected)
} else {
statusItem.SetTitle("Agent: Inactive")
statusItem.SetIcon(statusIconDisconnected)
// Update status menu item
if status == "Active" {
statusItem.SetTitle("Agent: Active")
statusItem.SetIcon(enabledIcon)
} else {
statusItem.SetTitle("Agent: Inactive")
statusItem.SetIcon(disabledIcon)
}

// Update connection menu item
if connection == "Connected" {
connectionItem.SetTitle("Connection: Connected")
connectionItem.SetIcon(enabledIcon)
} else {
connectionItem.SetTitle("Connection: Disconnected")
connectionItem.SetIcon(disabledIcon)
}

time.Sleep(5 * time.Second)
}
}()

go func() {
for {
checkVersion()

time.Sleep(1 * time.Hour)
}
}()
}

func checkVersion() {
versionStatus, version := fetchVersionStatus()

if strings.HasPrefix(versionStatus, "Up to date") {
versionItem.SetTitle(version)
versionItem.Disable()
updateItem.SetTitle("Up to date")
updateItem.Disable()
} else if strings.HasPrefix(versionStatus, "Outdated") {
versionItem.SetTitle(version)
versionItem.Disable()
updateItem.SetTitle("Update")
updateItem.Enable()
} else {
versionItem.SetTitle("Version: Unknown")
versionItem.Disable()
updateItem.Disable()
}
}

func fetchVersionStatus() (string, string) {
conn, err := net.Dial("tcp", "localhost:50505")
if err != nil {
log.Printf("Failed to connect to backend: %v", err)
return "Unknown", "Unknown"
}
defer conn.Close()

fmt.Fprintln(conn, "check-version")
reader := bufio.NewReader(conn)
response, err := reader.ReadString('\n')
if err != nil {
log.Printf("Failed to read response: %v", err)
return "Unknown", "Unknown"
}

response = strings.TrimSuffix(response, "\n")
parts := strings.Split(response, ": ")
if len(parts) < 2 {
return "Unknown", "Unknown"
}

parts = strings.Split(parts[1], ", ")
return parts[0], parts[1]
}

// startUpdateMonitor starts the update status monitoring if not already active
func startUpdateMonitor() {
// Only start monitoring if it's not already active
if isMonitoringUpdate {
log.Println("Update monitoring is already running.")
return
}

isMonitoringUpdate = true
sendCommand("update")
go monitorUpdateStatus()
}

// Update connection menu item
if connection == "Connected" {
connectionItem.SetTitle("Connection: Connected")
connectionItem.SetIcon(connectionIconConnected)
// monitorUpdateStatus continuously fetches and updates the update status
func monitorUpdateStatus() {
for isMonitoringUpdate {
updateStatus := fetchUpdateStatus()

// If the update status is "Disable", stop monitoring
if updateStatus == "Disable" {
log.Println("Update status is disabled. Stopping monitoring.")
isMonitoringUpdate = false
checkVersion()
} else {
connectionItem.SetTitle("Connection: Disconnected")
connectionItem.SetIcon(connectionIconDisconnected)
log.Printf("Current update status: %v", updateStatus)
// Update the icon or text based on the update status
updateItem.SetTitle("Updating...")
updateItem.Disable()
}

// Sleep for a short period before checking again
time.Sleep(5 * time.Second)
}

// Reset the flag after monitoring is done
isMonitoringUpdate = false
}

// handleMenuActions listens for menu item clicks and performs actions
Expand All @@ -99,7 +193,7 @@ func handleMenuActions(quitItem *systray.MenuItem) {
sendCommand("restart")
case <-updateItem.ClickedCh:
log.Println("Update clicked")
sendCommand("update")
startUpdateMonitor()
case <-quitItem.ClickedCh:
log.Println("Quit clicked")
systray.Quit()
Expand All @@ -126,19 +220,48 @@ func fetchStatus() (string, string) {
log.Printf("Failed to read response: %v", err)
return "Unknown", "Unknown"
}

response = strings.TrimSuffix(response, "\n")

// Split the string by comma
parts := strings.Split(response, ", ")
parts := strings.Split(response, ", ")

// Extract the values
status := strings.Split(parts[0], ": ")[1]
connection := strings.Split(parts[1], ": ")[1]

// Extract the values
status := strings.Split(parts[0], ": ")[1]
connection := strings.Split(parts[1], ": ")[1]

return status, connection
}

// fetchUpdateStatus retrieves the update status from the backend
func fetchUpdateStatus() string {
conn, err := net.Dial("tcp", "localhost:50505") // Update the port if needed
if err != nil {
log.Printf("Failed to connect to backend: %v", err)
return "Unknown"
}
defer conn.Close()

// Send "update" command to the server
fmt.Fprintln(conn, "update-status")

// Use bufio.Reader to read the response (including newline characters)
reader := bufio.NewReader(conn)
response, err := reader.ReadString('\n') // Read until newline character
if err != nil {
log.Printf("Failed to read response: %v", err)
return "Unknown"
}

// Trim the newline character
response = strings.TrimSuffix(response, "\n")

log.Printf("Update: %v", response)

// Extract the value of the update status
status := strings.Split(response, ": ")[1]
return status
}

// sendCommand sends a command (e.g., pause or restart) to the backend
func sendCommand(command string) {
Expand All @@ -160,12 +283,12 @@ func getEmbeddedFile(path string) ([]byte, error) {

// getIconPath returns iconpath based on the OS
func getIconPath() string {
switch os := runtime.GOOS; os {
case "windows":
return "assets/wazuh-logo.ico" // Path to the ICO icon for Windows
default:
return "assets/wazuh-logo.png" // Default icon path
}
switch os := runtime.GOOS; os {
case "windows":
return "assets/wazuh-logo.ico" // Path to the ICO icon for Windows
default:
return "assets/wazuh-logo.png" // Default icon path
}
}

// onExit is called when the application is terminated
Expand Down
15 changes: 11 additions & 4 deletions wazuh-agent-status/darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os/exec"
"strings"
"time"
"fmt"
)

// checkServiceStatus checks the status of Wazuh agent and its connection on macOS
Expand Down Expand Up @@ -56,18 +57,24 @@ func restartAgent() {
}
}

func notifyUser(title, message string) {
exec.Command("osascript", "-e", fmt.Sprintf(`display dialog "%s" with title "%s" buttons {"Dismiss"} default button "Dismiss"`, message, title)).Run()
}

// updateAgent updates the Wazuh agent on macOS
func updateAgent() {
logFilePath := "/Library/Ossec/logs/active-responses.log"
log.Printf("[%s] Updating Wazuh agent...\n", time.Now().Format(time.RFC3339))
err := exec.Command("sudo", "/Library/Ossec/active-response/bin/adorsys-update.sh").Run()
if err != nil {
log.Printf("[%s] Failed to update the Wazuh agent: %v\n", time.Now().Format(time.RFC3339), err)
errorMessage := fmt.Sprintf("Update failed: %v. Check logs: %s", err, logFilePath)
log.Printf("[%s] %s\n", time.Now().Format(time.RFC3339), errorMessage)
notifyUser("Wazuh Agent Update", errorMessage)
} else {
restartAgent()
log.Printf("[%s] Wazuh agent updated successfully\n", time.Now().Format(time.RFC3339))
notifyUser("Wazuh Agent Update", "Update successful!")
}

restartAgent()

}

func windowsMain() {
Expand Down
29 changes: 24 additions & 5 deletions wazuh-agent-status/linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ package main

import (
"log"
"os"
"os/exec"
"strings"
"time"
"fmt"
)

// checkServiceStatus checks the status of Wazuh agent and its connection on Linux
Expand Down Expand Up @@ -57,18 +59,35 @@ func restartAgent() {
}
}

// Function to check if a path exists
func pathExists(path string) bool {
_, err := os.Stat(path)
return !os.IsNotExist(err)
}

func notifyUser(title, message string) {
iconPath := "/usr/share/pixmaps/wazuh-logo.png"
if pathExists(iconPath) {
exec.Command("notify-send", "--app-name=Wazuh", "-u", "critical", title, message, "-i", iconPath).Run()
} else {
exec.Command("notify-send", "--app-name=Wazuh", "-u", "critical", title, message).Run()
}
}

// updateAgent updates the Wazuh agent on Linux
func updateAgent() {
log.Printf("[%s] Updating Wazuh agent...\n", time.Now().Format(time.RFC3339))
err := exec.Command("sudo", "/var/ossec/active-response/bin/adorsys-update.sh").Run()
err := exec.Command("sudo", "bash", "/var/ossec/active-response/bin/adorsys-update.sh").Run()
if err != nil {
log.Printf("[%s] Failed to update the Wazuh agent: %v\n", time.Now().Format(time.RFC3339), err)
logFilePath := "/var/ossec/logs/active-responses.log"
errorMessage := fmt.Sprintf("Update failed: Check logs for details at %s", logFilePath)
log.Printf("[%s] %s\n", time.Now().Format(time.RFC3339), errorMessage)
notifyUser("Wazuh Agent Update", errorMessage)
} else {
restartAgent()
log.Printf("[%s] Wazuh agent updated successfully\n", time.Now().Format(time.RFC3339))
notifyUser("Wazuh Agent Update", "Update successful!")
}

restartAgent()

}

func windowsMain() {
Expand Down
Loading