Découverte & inventaire
++ Collecte automatique des constantes systèmes (CPU, RAM, stockage, OS, logiciels, services), + avec un format homogène pour simplifier le reporting. +
+commit 784838fe5cce7673448aa6746073e409dd245967 Author: MatBureau <86561676+MatBureau@users.noreply.github.com> Date: Wed Apr 1 14:05:17 2026 +0200 premier commit diff --git a/inventory/Dockerfile b/inventory/Dockerfile new file mode 100644 index 0000000..ce76c76 --- /dev/null +++ b/inventory/Dockerfile @@ -0,0 +1,22 @@ +# Image golang pour la compilation +FROM golang:1.25 AS builder + +# Copier les fichiers dont ont a besoin +COPY src /go/src +COPY Makefile /go + +# Compilation +RUN make build + +# Image finale +FROM busybox + +# Installer le logiciel compilé +COPY src/www /www +COPY --from=builder /go/bin/inventory /inventory + +# Partages +EXPOSE 80 + +# Démarrage +ENTRYPOINT [ "/inventory" ] \ No newline at end of file diff --git a/inventory/Makefile b/inventory/Makefile new file mode 100644 index 0000000..97168a8 --- /dev/null +++ b/inventory/Makefile @@ -0,0 +1,17 @@ +run: + @cd src ; go run *.go + +build: + @cd src ; go mod tidy ; go build -o ../bin/inventory *.go + +clean: + @rm ./bin/inventory + +image: + @docker build -t inventory:latest . + +start: + @docker run -d -p 80:80 --name inventory inventory:latest + +stop: + @docker rm -f inventory diff --git a/inventory/src/cpu/cpu.go b/inventory/src/cpu/cpu.go new file mode 100644 index 0000000..d5f90f9 --- /dev/null +++ b/inventory/src/cpu/cpu.go @@ -0,0 +1,51 @@ +package cpu + +import ( + "github.com/shirou/gopsutil/v4/cpu" +) + +type CPUCore struct { + Info cpu.InfoStat `json:"info"` + Usage float64 `json:"usage_percent"` + Times *cpu.TimesStat `json:"times,omitempty"` +} + +type CPUInfo struct { + Cores []CPUCore `json:"cores"` +} + +func ReadCPU() (*CPUInfo, error) { + infos, err := cpu.Info() + if err != nil { + return nil, err + } + + usage, err := cpu.Percent(0, true) // usage par core + if err != nil { + return nil, err + } + + times, err := cpu.Times(true) // stats par core + if err != nil { + return nil, err + } + + out := make([]CPUCore, 0, len(infos)) + for i, info := range infos { + core := CPUCore{ + Info: info, + } + + if i < len(usage) { + core.Usage = usage[i] + } + if i < len(times) { + tmp := times[i] // adresse stable + core.Times = &tmp + } + + out = append(out, core) + } + + return &CPUInfo{Cores: out}, nil +} diff --git a/inventory/src/disk/disk.go b/inventory/src/disk/disk.go new file mode 100644 index 0000000..9e7ad98 --- /dev/null +++ b/inventory/src/disk/disk.go @@ -0,0 +1,38 @@ +package disk + +import ( + "github.com/shirou/gopsutil/v4/disk" +) + +type DiskFS struct { + Partition disk.PartitionStat `json:"partition"` + Usage *disk.UsageStat `json:"usage,omitempty"` +} + +func ReadDisk() (*[]DiskFS, error) { + parts, err := disk.Partitions(true) // true = toutes les partitions + if err != nil { + return nil, err + } + + var out []DiskFS + + for _, p := range parts { + fs := DiskFS{Partition: p} + + // Usage peut échouer (ex: pseudo-fs, permissions) + if u, err := disk.Usage(p.Mountpoint); err == nil { + fs.Usage = u + } + + // Exclusion des FS spécifique au système + switch fs.Partition.Fstype { + case "none", "proc", "tmpfs", "overlay", "sysfs", "cgroup2", "mqueue", "nsfs": + // ignoré + default: + out = append(out, fs) + } + } + + return &out, nil +} diff --git a/inventory/src/go.mod b/inventory/src/go.mod new file mode 100644 index 0000000..677c6f2 --- /dev/null +++ b/inventory/src/go.mod @@ -0,0 +1,16 @@ +module github.com/evoliatis/buildup + +go 1.25.6 + +require github.com/shirou/gopsutil/v4 v4.26.1 + +require ( + github.com/ebitengine/purego v0.9.1 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/tklauser/go-sysconf v0.3.16 // indirect + github.com/tklauser/numcpus v0.11.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + golang.org/x/sys v0.40.0 // indirect +) diff --git a/inventory/src/go.sum b/inventory/src/go.sum new file mode 100644 index 0000000..f64e822 --- /dev/null +++ b/inventory/src/go.sum @@ -0,0 +1,32 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/shirou/gopsutil/v4 v4.26.1 h1:TOkEyriIXk2HX9d4isZJtbjXbEjf5qyKPAzbzY0JWSo= +github.com/shirou/gopsutil/v4 v4.26.1/go.mod h1:medLI9/UNAb0dOI9Q3/7yWSqKkj00u+1tgY8nvv41pc= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= +github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= +github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= +github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/inventory/src/handle.go b/inventory/src/handle.go new file mode 100644 index 0000000..713c668 --- /dev/null +++ b/inventory/src/handle.go @@ -0,0 +1,108 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/evoliatis/buildup/cpu" + "github.com/evoliatis/buildup/disk" + "github.com/evoliatis/buildup/load" + "github.com/evoliatis/buildup/memory" + "github.com/evoliatis/buildup/netcard" + "github.com/evoliatis/buildup/proc" +) + +func HealthHandle(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "ok") +} + +// Liste des processus +func PSHandler(w http.ResponseWriter, r *http.Request) { + myProcs, err := proc.ReadProc("") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(myProcs) +} + +// Liste des processus par user +func PSUserHandler(w http.ResponseWriter, r *http.Request) { + user := r.PathValue("user") + myProcs, err := proc.ReadProc(user) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(myProcs) +} + +func NetHandler(w http.ResponseWriter, r *http.Request) { + out, err := netcard.ReadNetwork("") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + _ = json.NewEncoder(w).Encode(out) +} + +func NetNameHandler(w http.ResponseWriter, r *http.Request) { + card := r.PathValue("card") + out, err := netcard.ReadNetwork(card) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + _ = json.NewEncoder(w).Encode(out) +} + +func MemHandler(w http.ResponseWriter, r *http.Request) { + out, err := memory.ReadMemory() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + _ = json.NewEncoder(w).Encode(out) +} + +func DiskHandler(w http.ResponseWriter, r *http.Request) { + out, err := disk.ReadDisk() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + _ = json.NewEncoder(w).Encode(out) +} + +func LoadHandler(w http.ResponseWriter, r *http.Request) { + out, err := load.ReadLoad() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + _ = json.NewEncoder(w).Encode(out) +} + +func CPUHandler(w http.ResponseWriter, r *http.Request) { + out, err := cpu.ReadCPU() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + _ = json.NewEncoder(w).Encode(out) +} diff --git a/inventory/src/load/load.go b/inventory/src/load/load.go new file mode 100644 index 0000000..9b76c10 --- /dev/null +++ b/inventory/src/load/load.go @@ -0,0 +1,27 @@ +package load + +import ( + "github.com/shirou/gopsutil/v4/load" +) + +type LoadInfo struct { + Avg *load.AvgStat `json:"avg"` + Misc *load.MiscStat `json:"misc"` +} + +func ReadLoad() (*LoadInfo, error) { + avg, err := load.Avg() + if err != nil { + return nil, err + } + + misc, err := load.Misc() + if err != nil { + return nil, err + } + + return &LoadInfo{ + Avg: avg, + Misc: misc, + }, nil +} diff --git a/inventory/src/main.go b/inventory/src/main.go new file mode 100644 index 0000000..6aa0d83 --- /dev/null +++ b/inventory/src/main.go @@ -0,0 +1,12 @@ +package main + +import ( + "log" + "net/http" +) + +func main() { + + log.Println("listening on :80") + log.Fatal(http.ListenAndServe(":80", router())) +} diff --git a/inventory/src/memory/memory.go b/inventory/src/memory/memory.go new file mode 100644 index 0000000..e2d3606 --- /dev/null +++ b/inventory/src/memory/memory.go @@ -0,0 +1,27 @@ +package memory + +import ( + "github.com/shirou/gopsutil/v4/mem" +) + +type MemInfo struct { + Virtual *mem.VirtualMemoryStat `json:"virtual"` + Swap *mem.SwapMemoryStat `json:"swap"` +} + +func ReadMemory() (*MemInfo, error) { + vm, err := mem.VirtualMemory() + if err != nil { + return nil, err + } + + sm, err := mem.SwapMemory() + if err != nil { + return nil, err + } + + return &MemInfo{ + Virtual: vm, + Swap: sm, + }, nil +} diff --git a/inventory/src/netcard/netcard.go b/inventory/src/netcard/netcard.go new file mode 100644 index 0000000..d981333 --- /dev/null +++ b/inventory/src/netcard/netcard.go @@ -0,0 +1,42 @@ +package netcard + +import ( + "github.com/shirou/gopsutil/v4/net" +) + +type NetCard struct { + Interface net.InterfaceStat `json:"interface"` + IO *net.IOCountersStat `json:"io,omitempty"` +} + +func ReadNetwork(nom string) (*[]NetCard, error) { + ifaces, err := net.Interfaces() + if err != nil { + return nil, err + } + + counters, err := net.IOCounters(true) // true => stats par interface + if err != nil { + return nil, err + } + + // index des compteurs par nom d'interface + byName := make(map[string]net.IOCountersStat, len(counters)) + for _, c := range counters { + byName[c.Name] = c + } + + // fusion + out := make([]NetCard, 0, len(ifaces)) + for _, itf := range ifaces { + card := NetCard{Interface: itf} + if c, ok := byName[itf.Name]; ok { + tmp := c + card.IO = &tmp + } + if nom == "" || nom == card.Interface.Name { + out = append(out, card) + } + } + return &out, nil +} diff --git a/inventory/src/proc/proc.go b/inventory/src/proc/proc.go new file mode 100644 index 0000000..1d241f1 --- /dev/null +++ b/inventory/src/proc/proc.go @@ -0,0 +1,35 @@ +package proc + +import "github.com/shirou/gopsutil/v4/process" + +type Proc struct { + Pid int32 `json:"pid"` + Name string `json:"name"` + User string `json:"user"` + CPU float64 `json:"cpu"` + Memory float32 `json:"memory"` + Status string `json:"status"` +} + +// Chargement des Processus +func ReadProc(user string) (*[]Proc, error) { + var myProcs []Proc + procs, err := process.Processes() + if err != nil { + return nil, err + } + for _, proc := range procs { + var myProc Proc + myProc.User, _ = proc.Username() + if user == "" || user == myProc.User { + myProc.Pid = proc.Pid + myProc.Name, _ = proc.Name() + myProc.CPU, _ = proc.CPUPercent() + myProc.Memory, _ = proc.MemoryPercent() + status, _ := proc.Status() + myProc.Status = status[0] + myProcs = append(myProcs, myProc) + } + } + return &myProcs, nil +} diff --git a/inventory/src/routes.go b/inventory/src/routes.go new file mode 100644 index 0000000..6d682e4 --- /dev/null +++ b/inventory/src/routes.go @@ -0,0 +1,27 @@ +package main + +import "net/http" + +// Routeur pour l'ensemble de nos routes + +func router() http.Handler { + mux := http.NewServeMux() + // Routes de test + mux.HandleFunc("GET /health", HealthHandle) + + // Routes techniques + mux.HandleFunc("GET /cpu", CPUHandler) + mux.HandleFunc("GET /ps", PSHandler) + mux.HandleFunc("GET /ps/{user}", PSUserHandler) + mux.HandleFunc("GET /net", NetHandler) + mux.HandleFunc("GET /net/{card}", NetNameHandler) + mux.HandleFunc("GET /mem", MemHandler) + mux.HandleFunc("GET /disk", DiskHandler) + mux.HandleFunc("GET /load", LoadHandler) + + // Autres cas : fichiers statiques + fs := http.FileServer(http.Dir("www")) + mux.Handle("/", fs) + + return mux +} diff --git a/inventory/src/www/disk.html b/inventory/src/www/disk.html new file mode 100644 index 0000000..5a5408a --- /dev/null +++ b/inventory/src/www/disk.html @@ -0,0 +1,147 @@ + + +
+ +| Device | +Mountpoint | +Type | +Options | +Total | +Utilisé | +Libre | +Utilisé % | +
|---|---|---|---|---|---|---|---|
| Chargement… | |||||||
+ Inventory automatise l’inventaire matériel & logiciel des postes sur le réseau : + collecte des constantes système, normalisation, historisation, et exposition via API. + Une interface moderne, sans lourdeur. +
+ + + + +
+ + Collecte automatique des constantes systèmes (CPU, RAM, stockage, OS, logiciels, services), + avec un format homogène pour simplifier le reporting. +
++ Communication chiffrée, authentification de l’agent, et principe du moindre privilège. + L’inventaire sans ouvrir des portes inutiles. +
++ Données prêtes pour l’ITSM / CMDB : endpoints simples, export, et intégration dans vos + dashboards (et vos scripts qui sauvent la journée). +
+/load — jauges 0 → 4.0/cpu — usage_percent 0 → 100 (par cœur)/mem — rafraîchissement toutes les 5s/net — calcul des débits via delta bytes — rafraîchissement toutes les 5s| Interface | +Adresses | +Rx (Mb/s) | +Tx (Mb/s) | +Rx total | +Tx total | +MTU | +Flags / MAC | +Drops | +Errors | +
|---|---|---|---|---|---|---|---|---|---|
| Chargement… | |||||||||
/ps — rafraîchissement toutes les 5s| PID | +Nom | +Utilisateur | +Statut | +CPU % | +Mémoire % | +
|---|---|---|---|---|---|
| Chargement… | |||||