first version of client
This commit is contained in:
parent
8bcb0dd653
commit
5a6ccbc588
11
Makefile
11
Makefile
@ -1,13 +1,16 @@
|
||||
GO_FILES := $(shell find . -name '*.go' -not -path './build/*')
|
||||
|
||||
build: $(GO_FILES)
|
||||
build-server: $(GO_FILES)
|
||||
mkdir -p build
|
||||
go build -o build/zadmin-server ./cmd/server/main.go
|
||||
|
||||
build-client: $(GO_FILES)
|
||||
mkdir -p build
|
||||
go build -o build/zadmin-client ./cmd/client/main.go
|
||||
|
||||
docker-up:
|
||||
mkdir -p ./tmp
|
||||
docker compose -f ./deployments/compose-dev.yaml up
|
||||
docker compose -f ./deployments/compose-dev.yaml up -d
|
||||
|
||||
docker-down:
|
||||
docker compose -f ./deployments/compose-dev.yaml down
|
||||
@ -15,10 +18,10 @@ docker-down:
|
||||
docker-clean: docker-down
|
||||
rm -rf ./tmp
|
||||
|
||||
server: build
|
||||
server: build-server
|
||||
./build/zadmin-server
|
||||
|
||||
client: build
|
||||
client: build-client
|
||||
./build/zadmin-client
|
||||
|
||||
clean:
|
||||
|
@ -1,8 +1,56 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
func main() {
|
||||
fmt.Println("Hello, World!")
|
||||
"git.mziesel.nl/mans/zadmin/internal/agent"
|
||||
"git.mziesel.nl/mans/zadmin/internal/models"
|
||||
"github.com/nats-io/nats.go"
|
||||
)
|
||||
|
||||
var DefaultAgentConfig models.MachineAgentConfig = models.MachineAgentConfig{
|
||||
RESTServerHostname: "localhost",
|
||||
RESTServerPort: 443,
|
||||
NATSServerUrl: nats.DefaultURL,
|
||||
HelloInterval: 30,
|
||||
MetricsInterval: 30,
|
||||
GetIPv4Endpoint: "https://ip4.mziesel.nl/json",
|
||||
GetIPv6Endpoint: "https://ip6.mziesel.nl/json",
|
||||
}
|
||||
|
||||
func main() {
|
||||
agentConfig := DefaultAgentConfig
|
||||
nc, err := nats.Connect(agentConfig.NATSServerUrl)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("failed to connect to nats-server: %s\n", err)
|
||||
}
|
||||
|
||||
log.Println("connected to nats-server")
|
||||
defer func() {
|
||||
nc.Publish("client/disconnected", []byte("Goodbye!"))
|
||||
nc.Close()
|
||||
}()
|
||||
|
||||
nc.Publish("client/connected", []byte("Hello!"))
|
||||
nc.Publish("client/hello", []byte("Hello!"))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
shouldExit := false
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for !shouldExit {
|
||||
agent.PublishMachineData(agentConfig, nc)
|
||||
time.Sleep(30 * time.Second)
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
nc.Publish("client/disconnected", []byte("Goodbye!"))
|
||||
nc.Close()
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
|
||||
"git.mziesel.nl/mans/zadmin/internal/models"
|
||||
"github.com/nats-io/nats.go"
|
||||
)
|
||||
|
||||
@ -16,8 +18,23 @@ func main() {
|
||||
log.Println("connected to nats-server")
|
||||
|
||||
// simply print to the console for now
|
||||
nc.Subscribe("*", func(msg *nats.Msg) {
|
||||
log.Printf("got message: %s\n", msg.Data)
|
||||
nc.Subscribe(".", func(msg *nats.Msg) {
|
||||
log.Printf("Msg received on [%s] : %s\n", msg.Subject, string(msg.Data))
|
||||
})
|
||||
nc.Subscribe("client/disconnected", func(msg *nats.Msg) {
|
||||
log.Printf("Msg received on [%s] : %s\n", msg.Subject, string(msg.Data))
|
||||
})
|
||||
nc.Subscribe("client/connected", func(msg *nats.Msg) {
|
||||
log.Printf("Msg received on [%s] : %s\n", msg.Subject, string(msg.Data))
|
||||
})
|
||||
nc.Subscribe("client/machinedata", func(msg *nats.Msg) {
|
||||
data := models.MachineData{}
|
||||
err = json.Unmarshal(msg.Data, &data)
|
||||
if err != nil {
|
||||
log.Println("failed to Unmarshal MachineData: ", err)
|
||||
} else {
|
||||
log.Printf("Msg received on [%s]: %s\n", msg.Subject, models.PrettyFormatData(data))
|
||||
}
|
||||
})
|
||||
|
||||
shouldExit := false
|
||||
|
@ -3,3 +3,4 @@ services:
|
||||
image: nats:latest
|
||||
ports:
|
||||
- 4222:4222
|
||||
command: ["-DV"]
|
||||
|
7
go.mod
7
go.mod
@ -2,12 +2,15 @@ module git.mziesel.nl/mans/zadmin
|
||||
|
||||
go 1.23.0
|
||||
|
||||
require github.com/nats-io/nats.go v1.37.0
|
||||
require (
|
||||
github.com/mackerelio/go-osstat v0.2.5
|
||||
github.com/nats-io/nats.go v1.37.0
|
||||
golang.org/x/sys v0.20.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/klauspost/compress v1.17.2 // indirect
|
||||
github.com/nats-io/nkeys v0.4.7 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
golang.org/x/crypto v0.18.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
)
|
||||
|
6
go.sum
6
go.sum
@ -1,5 +1,7 @@
|
||||
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
|
||||
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/mackerelio/go-osstat v0.2.5 h1:+MqTbZUhoIt4m8qzkVoXUJg1EuifwlAJSk4Yl2GXh+o=
|
||||
github.com/mackerelio/go-osstat v0.2.5/go.mod h1:atxwWF+POUZcdtR1wnsUcQxTytoHG4uhl2AKKzrOajY=
|
||||
github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE=
|
||||
github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
|
||||
github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI=
|
||||
@ -8,5 +10,5 @@ github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
|
148
internal/agent/collect.go
Normal file
148
internal/agent/collect.go
Normal file
@ -0,0 +1,148 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/mackerelio/go-osstat/cpu"
|
||||
"github.com/mackerelio/go-osstat/loadavg"
|
||||
"github.com/mackerelio/go-osstat/memory"
|
||||
"github.com/mackerelio/go-osstat/uptime"
|
||||
|
||||
"git.mziesel.nl/mans/zadmin/internal/models"
|
||||
"github.com/nats-io/nats.go"
|
||||
)
|
||||
|
||||
func PublishMachineData(ac models.MachineAgentConfig, nc *nats.Conn) {
|
||||
machineData, err := GetMachineData(ac)
|
||||
if err != nil {
|
||||
LogError("failed to get MachineData", err)
|
||||
return
|
||||
}
|
||||
machineDataJSON, err := json.Marshal(machineData)
|
||||
if err != nil {
|
||||
LogError("failed to marshal MachineData", err)
|
||||
return
|
||||
}
|
||||
if err := nc.Publish("client/machinedata", machineDataJSON); err != nil {
|
||||
LogError("failed to publish MachineData", err)
|
||||
}
|
||||
log.Println("sent data to nats-server")
|
||||
}
|
||||
|
||||
func GetMachineData(ac models.MachineAgentConfig) (models.MachineData, error) {
|
||||
data := models.MachineData{}
|
||||
|
||||
// get hostname
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
LogError("failed to get system hostname", err)
|
||||
return data, err
|
||||
}
|
||||
|
||||
data.Hostname = hostname
|
||||
data.AgentVersion = "TODO"
|
||||
|
||||
type IPResponse struct {
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
// get public ipv4
|
||||
ip4resp, err := http.Get(ac.GetIPv4Endpoint)
|
||||
if err != nil {
|
||||
LogError("failed to get IPv4 address", err)
|
||||
return data, err
|
||||
}
|
||||
|
||||
ipResponseBody := IPResponse{}
|
||||
if err := json.NewDecoder(ip4resp.Body).Decode(&ipResponseBody); err != nil {
|
||||
LogError("failed to decode IPv4 address", err)
|
||||
return data, err
|
||||
}
|
||||
|
||||
data.PublicIPv4Address = ipResponseBody.Address
|
||||
|
||||
// get public ipv6
|
||||
ip6resp, err := http.Get(ac.GetIPv6Endpoint)
|
||||
if err != nil {
|
||||
LogError("failed to get IPv6 address", err)
|
||||
return data, err
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(ip6resp.Body).Decode(&ipResponseBody); err != nil {
|
||||
LogError("failed to decode IPv6 address", err)
|
||||
return data, err
|
||||
}
|
||||
|
||||
data.PublicIPv6Address = ipResponseBody.Address
|
||||
|
||||
// get machine uptime
|
||||
uptime, err := uptime.GetUptime()
|
||||
if err != nil {
|
||||
LogError("failed to get machine uptime", err)
|
||||
return data, nil
|
||||
}
|
||||
data.UptimeSeconds = int(uptime.Seconds())
|
||||
|
||||
// get useage statistics
|
||||
useageStatistics, err := CollectUsageStatistics(ac)
|
||||
if err != nil {
|
||||
LogError("failed to get useage statistics", err)
|
||||
return data, nil
|
||||
}
|
||||
|
||||
data.UsageStatistics = useageStatistics
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func LogError(msg string, err error) {
|
||||
log.Printf("ERROR: %s: %v", msg, err)
|
||||
}
|
||||
|
||||
func CollectUsageStatistics(ac models.MachineAgentConfig) (models.UsageStatistics, error) {
|
||||
statistics := models.UsageStatistics{}
|
||||
|
||||
statistics.TimeCollected = time.Now()
|
||||
|
||||
memory, err := memory.Get()
|
||||
|
||||
if err != nil {
|
||||
LogError("failed to collect memory statistics", err)
|
||||
return statistics, err
|
||||
}
|
||||
|
||||
statistics.MemoryTotal = memory.Total
|
||||
statistics.MemoryUsed = memory.Used
|
||||
statistics.MemoryCached = memory.Cached
|
||||
statistics.MemoryFree = memory.Free
|
||||
statistics.MemoryAvailable = memory.Available
|
||||
|
||||
cpu, err := cpu.Get()
|
||||
if err != nil {
|
||||
LogError("failed to collect cpu statistics", err)
|
||||
return statistics, err
|
||||
}
|
||||
|
||||
statistics.CpuUser = cpu.User
|
||||
statistics.CpuSystem = cpu.System
|
||||
statistics.CpuIdle = cpu.Idle
|
||||
statistics.CpuIowait = cpu.Iowait
|
||||
statistics.CpuSteal = cpu.Steal
|
||||
statistics.CpuCount = cpu.CPUCount
|
||||
|
||||
loadAvg, err := loadavg.Get()
|
||||
if err != nil {
|
||||
LogError("failed to collect loadavg statistics", err)
|
||||
return statistics, err
|
||||
}
|
||||
|
||||
statistics.LoadAVG1 = loadAvg.Loadavg1
|
||||
statistics.LoadAVG5 = loadAvg.Loadavg5
|
||||
statistics.LoadAVG15 = loadAvg.Loadavg15
|
||||
|
||||
return statistics, nil
|
||||
}
|
14
internal/models/helpers.go
Normal file
14
internal/models/helpers.go
Normal file
@ -0,0 +1,14 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
)
|
||||
|
||||
func PrettyFormatData(s any) string {
|
||||
prettyData, err := json.MarshalIndent(s, "", "\t")
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
return string(prettyData)
|
||||
}
|
@ -1,6 +1,10 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Model for the user accounts
|
||||
type AccountModel struct {
|
||||
@ -29,6 +33,7 @@ type MachineModel struct {
|
||||
GoArch string `json:"go_arch"` // Go arch
|
||||
}
|
||||
|
||||
// Data of the machine
|
||||
type MachineData struct {
|
||||
FirstSeen time.Time `json:"first_seen"` // Time of first contact with zadmin
|
||||
LastSeen time.Time `json:"last_seen"` // Time of last contact with zadmin
|
||||
@ -45,6 +50,23 @@ type MachineData struct {
|
||||
OSVersion string `json:"os_version"` // OS version
|
||||
}
|
||||
|
||||
// String method for MachineData
|
||||
func (m MachineData) String() string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(fmt.Sprintf("MachineData:\n"))
|
||||
sb.WriteString(fmt.Sprintf(" FirstSeen: %s\n", m.FirstSeen))
|
||||
sb.WriteString(fmt.Sprintf(" LastSeen: %s\n", m.LastSeen))
|
||||
sb.WriteString(fmt.Sprintf(" Hostname: %s\n", m.Hostname))
|
||||
sb.WriteString(fmt.Sprintf(" PublicIPv4Address: %s\n", m.PublicIPv4Address))
|
||||
sb.WriteString(fmt.Sprintf(" PublicIPv6Address: %s\n", m.PublicIPv6Address))
|
||||
sb.WriteString(fmt.Sprintf(" AgentVersion: %s\n", m.AgentVersion))
|
||||
sb.WriteString(fmt.Sprintf(" UptimeSeconds: %d\n", m.UptimeSeconds))
|
||||
sb.WriteString(fmt.Sprintf(" OSVersion: %s\n", m.OSVersion))
|
||||
sb.WriteString(fmt.Sprintf(" LoggedOnUsers: %v\n", m.LoggedOnUsers))
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// Software installed on a Machine
|
||||
type Software struct {
|
||||
Name string
|
||||
@ -57,10 +79,21 @@ type AntivirusInfo struct {
|
||||
}
|
||||
|
||||
type UsageStatistics struct {
|
||||
AvailCPU int `json:"avail_cpu"` // Available mCPU
|
||||
UsedCPU int `json:"used_cpu"` // Used mCPU
|
||||
AvailRAM int `json:"avail_ram"` // Available RAM in MB
|
||||
UsedRAM int `json:"used_ram"` // Used RAM in MB
|
||||
TimeCollected time.Time `json:"time_collected"` // Time metrics were collected at
|
||||
CpuUser uint64 `json:"cpu_user"` // The amount of time the CPU spends executing user-level processes
|
||||
CpuSystem uint64 `json:"cpu_system"` // The time the CPU spends executing kernel-level processes
|
||||
CpuIdle uint64 `json:"cpu_idle"` // The percentage of time the CPU is idle and not doing any work
|
||||
CpuIowait uint64 `json:"cpu_iowait"` // The time the CPU spends waiting for I/O operations to complete
|
||||
CpuSteal uint64 `json:"cpu_steal"` // The amount of time a virtual CPU waits for the hypervisor to service its requests
|
||||
CpuCount int `json:"cpu_count"` // Amount of CPUs present in machine
|
||||
MemoryTotal uint64 `json:"memory_total"` // Total memory
|
||||
MemoryUsed uint64 `json:"memory_used"` // Used memory
|
||||
MemoryCached uint64 `json:"memory_cached"` // Cached memory
|
||||
MemoryFree uint64 `json:"memory_free"` // Free memory
|
||||
MemoryAvailable uint64 `json:"memory_available"` // Available memory
|
||||
LoadAVG1 float64 `json:"loadavg_1"` // 1-minute Load Average
|
||||
LoadAVG5 float64 `json:"loadavg_5"` // 5-minute Load Average
|
||||
LoadAVG15 float64 `json:"loadavg_15"` // 15-minute Load Average
|
||||
}
|
||||
|
||||
type DiskInformation struct {
|
||||
@ -81,7 +114,9 @@ type MachineInterfaceDetails struct {
|
||||
type MachineAgentConfig struct {
|
||||
RESTServerHostname string `json:"rest_server_hostname"` // Hostname used for REST requests
|
||||
RESTServerPort int `json:"rest_server_port"` // Port used for REST requests
|
||||
NATSServerHostname string `json:"nats_server_hostname"` // Hostname used to contact nats.io
|
||||
NATSServerPort int `json:"nats_server_port"` // Port used for REST requests
|
||||
NATSServerUrl string `json:"nats_server_url"` // URL used to contact nats.io
|
||||
HelloInterval int `json:"checkin_interval"` // interval of sending hello message, indicating host is online
|
||||
MetricsInterval int `json:"metrics_interval"` // interval of sending hello message, indicating host is online
|
||||
GetIPv4Endpoint string `json:"get_ipv4_endpoint"` // endpoint that returns a json doc with the IPv4 address
|
||||
GetIPv6Endpoint string `json:"get_ipv6_endpoint"` // endpoint that returns a json doc with the IPv6 address
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user