Remade the client in go, now named monitor-agent. The agents now support http basic auth
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
#!/bin/bash
|
||||
|
||||
# --- Configuration (Edit these before spreading) ---
|
||||
TARGET_SERVICE_UUID=""
|
||||
SERVER_URL="https://monitor.example.com"
|
||||
AUTH_USER="monitor_user"
|
||||
AUTH_PASS="monitor_user_pass"
|
||||
UPDATE_INTERVAL=10
|
||||
|
||||
# --- Script Logic ---
|
||||
|
||||
# 1. Ensure the script is run as root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Please run as root (use sudo)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Starting deployment of Monitoring Agent..."
|
||||
|
||||
# 2. Install dependencies (libcap2-bin is needed for setcap)
|
||||
echo "Installing dependencies..."
|
||||
apt-get update -qq
|
||||
apt-get install -y libcap2-bin uuid-runtime -qq
|
||||
|
||||
# 3. Create the system user if it doesn't exist
|
||||
if ! id "monitor-agent" &>/dev/null; then
|
||||
echo "Creating monitor-agent system user..."
|
||||
useradd -r -s /bin/false monitor-agent
|
||||
fi
|
||||
|
||||
# 4. Create necessary directories
|
||||
echo "Creating directories..."
|
||||
mkdir -p /etc/monitor-agent
|
||||
mkdir -p /var/lib/monitor-agent
|
||||
chown -R monitor-agent:monitor-agent /var/lib/monitor-agent
|
||||
|
||||
# 5. Install the binary
|
||||
if [ -f "./agent-go" ]; then
|
||||
echo "Installing binary to /usr/local/bin..."
|
||||
cp ./agent-go /usr/local/bin/monitor-agent
|
||||
chmod +x /usr/local/bin/monitor-agent
|
||||
# Grant ping capabilities without root
|
||||
setcap cap_net_raw+ep /usr/local/bin/monitor-agent
|
||||
else
|
||||
echo "Error: 'monitor-agent' binary not found in current directory!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 6. Generate a unique NODE_UUID for this specific server
|
||||
NEW_UUID=$(uuidgen)
|
||||
echo "Generated unique Node UUID: $NEW_UUID"
|
||||
|
||||
# 7. Create the Environment File
|
||||
echo "Creating configuration file..."
|
||||
cat <<EOF > /etc/monitor-agent/agent.env
|
||||
NODE_UUID=$NEW_UUID
|
||||
TARGET_SERVICE_UUID=$TARGET_SERVICE_UUID
|
||||
SERVER_URL=$SERVER_URL
|
||||
BASIC_AUTH_USERNAME=$AUTH_USER
|
||||
BASIC_AUTH_PASSWORD=$AUTH_PASS
|
||||
UPDATE_INTERVAL_SECONDS=$UPDATE_INTERVAL
|
||||
PEERS_FILE=/var/lib/monitor-agent/known_peers.json
|
||||
EOF
|
||||
|
||||
chmod 600 /etc/monitor-agent/agent.env
|
||||
chown monitor-agent:monitor-agent /etc/monitor-agent/agent.env
|
||||
|
||||
# 8. Create the Systemd Service File
|
||||
echo "Creating systemd service..."
|
||||
cat <<EOF > /etc/systemd/system/monitor-agent.service
|
||||
[Unit]
|
||||
Description=Node Monitoring Agent
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=monitor-agent
|
||||
Group=monitor-agent
|
||||
WorkingDirectory=/var/lib/monitor-agent
|
||||
EnvironmentFile=/etc/monitor-agent/agent.env
|
||||
ExecStart=/usr/local/bin/monitor-agent
|
||||
CapabilityBoundingSet=CAP_NET_RAW
|
||||
AmbientCapabilities=CAP_NET_RAW
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
# 9. Start the service
|
||||
echo "Reloading systemd and starting service..."
|
||||
systemctl daemon-reload
|
||||
systemctl enable monitor-agent
|
||||
systemctl restart monitor-agent
|
||||
|
||||
echo "------------------------------------------------"
|
||||
echo "Deployment Complete!"
|
||||
echo "Node UUID: $NEW_UUID"
|
||||
echo "Check status with: systemctl status monitor-agent"
|
||||
echo "View logs with: journalctl -u monitor-agent -f"
|
||||
echo "------------------------------------------------"
|
||||
@@ -0,0 +1,19 @@
|
||||
module aget_go
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/prometheus-community/pro-bing v0.9.1 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
golang.org/x/net v0.56.0 // indirect
|
||||
golang.org/x/sync v0.21.0 // indirect
|
||||
golang.org/x/sys v0.46.0 // indirect
|
||||
)
|
||||
@@ -0,0 +1,290 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
probing "github.com/prometheus-community/pro-bing"
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"github.com/shirou/gopsutil/v3/host"
|
||||
"github.com/shirou/gopsutil/v3/load"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
)
|
||||
|
||||
// --- Configuration ---
|
||||
|
||||
var (
|
||||
NodeUUID = getEnv("NODE_UUID", uuid.New().String())
|
||||
TargetServiceUUID = getEnv("TARGET_SERVICE_UUID", "ab73d00a-8169-46bb-997d-f13e5f760973")
|
||||
BasicAuthUser = getEnv("BASIC_AUTH_USERNAME", "")
|
||||
BasicAuthPass = getEnv("BASIC_AUTH_PASSWORD", "")
|
||||
ServerBaseURL = getEnv("SERVER_URL", "https://test.mystaginglab.net")
|
||||
UpdateInterval = getEnvInt("UPDATE_INTERVAL_SECONDS", 10)
|
||||
PeersFile = getEnv("PEERS_FILE", fmt.Sprintf("known_peers_%s.json", NodeUUID))
|
||||
LocalIP = getLocalIP()
|
||||
KnownPeers = make(map[string]string)
|
||||
KnownPeersMu sync.RWMutex
|
||||
)
|
||||
|
||||
// --- Structs for JSON ---
|
||||
|
||||
type StatusData struct {
|
||||
UptimeSeconds uint64 `json:"uptime_seconds"`
|
||||
LoadAvg []float64 `json:"load_avg"`
|
||||
MemoryUsagePercent float64 `json:"memory_usage_percent"`
|
||||
}
|
||||
|
||||
type Payload struct {
|
||||
Node string `json:"node"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Status StatusData `json:"status"`
|
||||
Pings map[string]float64 `json:"pings"`
|
||||
}
|
||||
|
||||
type PeerInfo struct {
|
||||
IP string `json:"ip"`
|
||||
LastSeen string `json:"last_seen"`
|
||||
}
|
||||
|
||||
type ServerResponse struct {
|
||||
Message string `json:"message"`
|
||||
Peers map[string]PeerInfo `json:"peers"`
|
||||
}
|
||||
|
||||
// --- Helper Functions ---
|
||||
|
||||
func getEnv(key, fallback string) string {
|
||||
if value, ok := os.LookupEnv(key); ok {
|
||||
return value
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func getEnvInt(key string, fallback int) int {
|
||||
if value, ok := os.LookupEnv(key); ok {
|
||||
if i, err := strconv.Atoi(value); err == nil {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func getLocalIP() string {
|
||||
conn, err := net.Dial("udp", "8.8.8.8:80")
|
||||
if err != nil {
|
||||
return "127.0.0.1"
|
||||
}
|
||||
defer conn.Close()
|
||||
localAddr := conn.LocalAddr().(*net.UDPAddr)
|
||||
return localAddr.IP.String()
|
||||
}
|
||||
|
||||
// --- Peer Persistence ---
|
||||
|
||||
func loadPeers() {
|
||||
KnownPeersMu.Lock()
|
||||
defer KnownPeersMu.Unlock()
|
||||
|
||||
data, err := os.ReadFile(PeersFile)
|
||||
if err != nil {
|
||||
log.Printf("No existing peers file found or error reading: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// The Python script handles both {uuid: ip} and {uuid: {ip: ip}}
|
||||
// We'll decode into a generic map first to handle flexibility
|
||||
var raw map[string]interface{}
|
||||
if err := json.Unmarshal(data, &raw); err != nil {
|
||||
log.Printf("Error decoding peers JSON: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range raw {
|
||||
if ipStr, ok := v.(string); ok {
|
||||
KnownPeers[k] = ipStr
|
||||
} else if ipMap, ok := v.(map[string]interface{}); ok {
|
||||
if ip, ok := ipMap["ip"].(string); ok {
|
||||
KnownPeers[k] = ip
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Printf("Loaded %d known peers from %s", len(KnownPeers), PeersFile)
|
||||
}
|
||||
|
||||
func savePeers() {
|
||||
KnownPeersMu.RLock()
|
||||
defer KnownPeersMu.RUnlock()
|
||||
|
||||
data, err := json.MarshalIndent(KnownPeers, "", " ")
|
||||
if err != nil {
|
||||
log.Printf("Error marshaling peers: %v", err)
|
||||
return
|
||||
}
|
||||
if err := os.WriteFile(PeersFile, data, 0644); err != nil {
|
||||
log.Printf("Error saving peers to file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Metrics Collection ---
|
||||
|
||||
func getSystemMetrics() StatusData {
|
||||
uptime, _ := host.Uptime()
|
||||
|
||||
var loadValues []float64
|
||||
avg, err := load.Avg()
|
||||
if err != nil {
|
||||
// Fallback for Windows/Non-Unix like the Python script
|
||||
c, _ := cpu.Percent(500*time.Millisecond, false)
|
||||
cpuUsage := 0.0
|
||||
if len(c) > 0 {
|
||||
cpuUsage = c[0] / 100.0
|
||||
}
|
||||
loadValues = []float64{cpuUsage, cpuUsage * 0.9, cpuUsage * 0.8}
|
||||
} else {
|
||||
loadValues = []float64{avg.Load1, avg.Load5, avg.Load15}
|
||||
}
|
||||
|
||||
v, _ := mem.VirtualMemory()
|
||||
|
||||
return StatusData{
|
||||
UptimeSeconds: uptime,
|
||||
LoadAvg: loadValues,
|
||||
MemoryUsagePercent: v.UsedPercent,
|
||||
}
|
||||
}
|
||||
|
||||
// --- Ping Logic ---
|
||||
|
||||
func performPings(targets map[string]string) map[string]float64 {
|
||||
results := make(map[string]float64)
|
||||
|
||||
// Ping self
|
||||
results[NodeUUID] = runPing(LocalIP)
|
||||
|
||||
// Ping peers
|
||||
for uuid, ip := range targets {
|
||||
if uuid == NodeUUID {
|
||||
continue
|
||||
}
|
||||
results[uuid] = runPing(ip)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func runPing(ip string) float64 {
|
||||
pinger, err := probing.NewPinger(ip)
|
||||
if err != nil {
|
||||
return -1.0
|
||||
}
|
||||
|
||||
// On Linux, this requires sudo or 'setcap cap_net_raw+ep'
|
||||
// If not root, you can try: pinger.SetPrivileged(false) which uses UDP pings
|
||||
if runtime.GOOS == "windows" {
|
||||
pinger.SetPrivileged(true)
|
||||
} else {
|
||||
pinger.SetPrivileged(false) // Try unprivileged first
|
||||
}
|
||||
|
||||
pinger.Count = 1
|
||||
pinger.Timeout = 2 * time.Second
|
||||
|
||||
err = pinger.Run()
|
||||
if err != nil {
|
||||
return -1.0
|
||||
}
|
||||
|
||||
stats := pinger.Statistics()
|
||||
if stats.PacketsRecv > 0 {
|
||||
return float64(stats.AvgRtt.Microseconds()) / 1000.0
|
||||
}
|
||||
return -1.0
|
||||
}
|
||||
|
||||
// --- Main Loop ---
|
||||
|
||||
func main() {
|
||||
log.Printf("Starting Go Node Client %s", NodeUUID)
|
||||
log.Printf("Local IP: %s | Server: %s", LocalIP, ServerBaseURL)
|
||||
|
||||
loadPeers()
|
||||
|
||||
ticker := time.NewTicker(time.Duration(UpdateInterval) * time.Second)
|
||||
client := &http.Client{Timeout: 15 * time.Second}
|
||||
|
||||
for ; ; <-ticker.C {
|
||||
metrics := getSystemMetrics()
|
||||
|
||||
KnownPeersMu.RLock()
|
||||
targets := make(map[string]string)
|
||||
for k, v := range KnownPeers {
|
||||
targets[k] = v
|
||||
}
|
||||
KnownPeersMu.RUnlock()
|
||||
|
||||
pingResults := performPings(targets)
|
||||
|
||||
payload := Payload{
|
||||
Node: NodeUUID,
|
||||
Timestamp: time.Now().UTC().Format(time.RFC3339),
|
||||
Status: metrics,
|
||||
Pings: pingResults,
|
||||
}
|
||||
|
||||
jsonPayload, _ := json.Marshal(payload)
|
||||
url := fmt.Sprintf("%s/%s/%s/", ServerBaseURL, TargetServiceUUID, NodeUUID)
|
||||
|
||||
req, err := http.NewRequest("PUT", url, bytes.NewBuffer(jsonPayload))
|
||||
if err != nil {
|
||||
log.Printf("Error creating request: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if BasicAuthUser != "" {
|
||||
auth := BasicAuthUser + ":" + BasicAuthPass
|
||||
req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth)))
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("Request failed: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
var serverResp ServerResponse
|
||||
if err := json.Unmarshal(body, &serverResp); err == nil {
|
||||
log.Printf("Update sent. Server: %s", serverResp.Message)
|
||||
|
||||
// Update peers
|
||||
newPeers := make(map[string]string)
|
||||
for id, info := range serverResp.Peers {
|
||||
newPeers[id] = info.IP
|
||||
}
|
||||
|
||||
KnownPeersMu.Lock()
|
||||
KnownPeers = newPeers
|
||||
KnownPeersMu.Unlock()
|
||||
savePeers()
|
||||
}
|
||||
} else {
|
||||
log.Printf("Server returned error %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user