premier commit

This commit is contained in:
MatBureau
2026-04-01 14:02:01 +02:00
commit cbbbf623fb
26 changed files with 1343 additions and 0 deletions

24
buildup/Dockerfile Normal file
View File

@@ -0,0 +1,24 @@
# 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 src/.env /
COPY src/servers.yaml /
COPY --from=builder /go/bin/buildup /buildup
# Partages
EXPOSE 80
# Démarrage
ENTRYPOINT [ "/buildup" ]

38
buildup/Makefile Normal file
View File

@@ -0,0 +1,38 @@
# Variables
COMPOSE ?= docker compose
DOCKER_DIR ?= docker
COMPOSE_FILE ?= docker-compose.yml
run:
@cd src ; go run *.go
build:
@cd src ; go mod tidy ; go build -o ../bin/buildup *.go
clean:
@rm ./bin/buildup
image:
@docker build -t buildup:latest .
start:
@docker run -d -p 80:80 --name buildup buildup:latest
stop:
@docker rm -f buildup
# Démarrage stack
up:
@$(COMPOSE) --project-directory $(DOCKER_DIR) -f $(DOCKER_DIR)/$(COMPOSE_FILE) up -d
# Arrêt de la stack
down:
@$(COMPOSE) --project-directory $(DOCKER_DIR) -f $(DOCKER_DIR)/$(COMPOSE_FILE) down
# Journaux de la stack
logs:
@$(COMPOSE) --project-directory $(DOCKER_DIR) -f $(DOCKER_DIR)/$(COMPOSE_FILE) logs -f
# Liste des processus de la stack
ps:
@$(COMPOSE) --project-directory $(DOCKER_DIR) -f $(DOCKER_DIR)/$(COMPOSE_FILE) ps

11
buildup/docker/.env Normal file
View File

@@ -0,0 +1,11 @@
# Gestion InfluxDB
DOCKER_INFLUXDB_INIT_MODE=setup
DOCKER_INFLUXDB_INIT_USERNAME=admin
DOCKER_INFLUXDB_INIT_PASSWORD=SuperSecret
DOCKER_INFLUXDB_INIT_ORG=myorg
DOCKER_INFLUXDB_INIT_BUCKET=metrics
DOCKER_INFLUXDB_INIT_RETENTION=30d
DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=admin-token
# Gestion Grafana
GF_SECURITY_ADMIN_USER="admin"
GF_SECURITY_ADMIN_PASSWORD="SuperSecret"

View File

@@ -0,0 +1,54 @@
# Warning : 사이트 이름을 정하는 것을 잊지 마세요!
services:
# 웹 프런트엔드
traefik:
image: traefik:v3.6
container_name: front
restart: unless-stopped
command:
- "--providers.docker=true"
- "--entrypoints.http.address=:80"
ports:
- 80:80
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
labels:
- "traefik.enable=true"
# InfluxDB 데이터베이스
influxdb:
image: influxdb:2.8.0
container_name: influxdb
user: "1000:1000"
environment:
DOCKER_INFLUXDB_INIT_MODE:
DOCKER_INFLUXDB_INIT_USERNAME:
DOCKER_INFLUXDB_INIT_PASSWORD:
DOCKER_INFLUXDB_INIT_ORG:
DOCKER_INFLUXDB_INIT_BUCKET:
DOCKER_INFLUXDB_INIT_RETENTION:
DOCKER_INFLUXDB_INIT_ADMIN_TOKEN:
volumes:
- ./influxdb2/data:/var/lib/influxdb2
- ./influxdb2/config:/etc/influxdb2
restart: unless-stopped
labels:
- "traefik.http.routers.influxdb.rule=Host(`influx.test`)" # 사이트 이름
- "traefik.http.services.influxdb.loadbalancer.server.port=8086"
# 그래픽 렌더링 서비스
grafana:
image: grafana/grafana:main
container_name: grafana
user: "1000:1000"
restart: always
depends_on:
- influxdb
environment:
- GF_SECURITY_ADMIN_USER
- GF_SECURITY_ADMIN_PASSWORD
ports:
- 3000:3000
volumes:
- ./grafana/datas:/var/lib/grafana:rw
labels:
- "traefik.http.routers.grafana.rule=Host(`grafana.test`)" # 사이트 이름
- "traefik.http.services.grafana.loadbalancer.server.port=3000"

5
buildup/src/.env Normal file
View File

@@ -0,0 +1,5 @@
# Gestion InfluxDB
INFLUXDB_TOKEN="admin-token"
INFLUXDB_URL="http://influx.test"
INFLUXDB_ORG="myorg"
INFLUXDB_BUCKET="metrics"

25
buildup/src/config.go Normal file
View File

@@ -0,0 +1,25 @@
package main
import (
"os"
"gopkg.in/yaml.v3"
)
type Config struct {
Servers []string `yaml:"servers"`
}
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var cfg Config
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil, err
}
return &cfg, nil
}

51
buildup/src/cpu/cpu.go Normal file
View File

@@ -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
}

38
buildup/src/disk/disk.go Normal file
View File

@@ -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
}

26
buildup/src/go.mod Normal file
View File

@@ -0,0 +1,26 @@
module github.com/evoliatis/buildup
go 1.25.6
require (
github.com/influxdata/influxdb-client-go/v2 v2.14.0
github.com/joho/godotenv v1.5.1
github.com/shirou/gopsutil/v4 v4.26.1
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/ebitengine/purego v0.9.1 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/oapi-codegen/runtime v1.0.0 // 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/net v0.23.0 // indirect
golang.org/x/sys v0.40.0 // indirect
)

55
buildup/src/go.sum Normal file
View File

@@ -0,0 +1,55 @@
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/influxdata/influxdb-client-go/v2 v2.14.0 h1:AjbBfJuq+QoaXNcrova8smSjwJdUHnwvfjMF71M1iI4=
github.com/influxdata/influxdb-client-go/v2 v2.14.0/go.mod h1:Ahpm3QXKMJslpXl3IftVLVezreAUtBOTZssDrjZEFHI=
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU=
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
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/oapi-codegen/runtime v1.0.0 h1:P4rqFX5fMFWqRzY9M/3YF9+aPSPPB06IzP2P7oOxrWo=
github.com/oapi-codegen/runtime v1.0.0/go.mod h1:LmCUMQuPB4M/nLXilQXhHw+BLZdDb18B34OO356yJ/A=
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/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
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/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
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/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -0,0 +1,80 @@
package goroutines
import (
"context"
"encoding/json"
"log"
"net/http"
"time"
"github.com/evoliatis/buildup/cpu"
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
)
func GoCPU(server, INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN string, tick int) {
// Connexion
client := influxdb2.NewClient(INFLUXDB_URL, INFLUXDB_TOKEN)
defer client.Close()
writeAPI := client.WriteAPIBlocking(INFLUXDB_ORG, INFLUXDB_BUCKET)
ticker := time.NewTicker(time.Duration(tick) * time.Second)
defer ticker.Stop()
for range ticker.C {
// Récupération des valeurs
reqUrl := "http://" + server + "/cpu"
resp, err := http.Get(reqUrl)
if err != nil {
log.Println("Erreur de communication : " + reqUrl)
continue
}
defer resp.Body.Close()
var cpu cpu.CPUInfo
if err := json.NewDecoder(resp.Body).Decode(&cpu); err != nil {
log.Println("Impossible de récupérer : " + reqUrl)
continue
}
for _, c := range cpu.Cores {
tags := map[string]string{ // tags (ex: host, env…)
"cpu": c.Times.CPU, // ex "cpu0"
"vendor": c.Info.VendorID, // utile pour multi-host
"model": c.Info.ModelName,
"host": server,
}
fields := map[string]interface{}{
"cpu": c.Usage,
// Tous les champs TimesStat (numériques)
"user": c.Times.User,
"system": c.Times.System,
"idle": c.Times.Idle,
"nice": c.Times.Nice,
"iowait": c.Times.Iowait,
"irq": c.Times.Irq,
"softirq": c.Times.Softirq,
"steal": c.Times.Steal,
"guest": c.Times.Guest,
"guest_nice": c.Times.GuestNice,
}
// ---- point ----
p := influxdb2.NewPoint(
"cpu",
tags,
fields,
time.Now(),
)
// Ecriture
if err := writeAPI.WritePoint(context.Background(), p); err != nil {
log.Printf("write failed: %v \n", err)
}
}
log.Println("CPU written to InfluxDB :" + reqUrl)
}
}

View File

@@ -0,0 +1,76 @@
package goroutines
import (
"context"
"encoding/json"
"log"
"net/http"
"time"
"github.com/evoliatis/buildup/disk"
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
)
func GoDisk(server, INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN string, tick int) {
// Connexion
client := influxdb2.NewClient(INFLUXDB_URL, INFLUXDB_TOKEN)
defer client.Close()
writeAPI := client.WriteAPIBlocking(INFLUXDB_ORG, INFLUXDB_BUCKET)
ticker := time.NewTicker(time.Duration(tick) * time.Second)
defer ticker.Stop()
for range ticker.C {
// Récupération des valeurs
reqUrl := "http://" + server + "/disk"
resp, err := http.Get(reqUrl)
if err != nil {
log.Println("Erreur de communication : " + reqUrl)
continue
}
defer resp.Body.Close()
var disks []disk.DiskFS
if err := json.NewDecoder(resp.Body).Decode(&disks); err != nil {
log.Println("Impossible de récupérer : " + reqUrl)
continue
}
for _, disk := range disks {
tags := map[string]string{ // tags (ex: host, env…)
"host": server,
"device": disk.Partition.Device,
"mountpoint": disk.Partition.Mountpoint,
"fstype": disk.Usage.Fstype,
}
fields := map[string]interface{}{
// Tous les champs TimesStat (numériques)
"total": disk.Usage.Total,
"free": disk.Usage.Free,
"used": disk.Usage.Used,
"usedpcent": disk.Usage.UsedPercent,
"inodestotal": disk.Usage.InodesTotal,
"inodesused": disk.Usage.InodesUsed,
"inodesfree": disk.Usage.InodesFree,
"inodesusedpcent": disk.Usage.InodesUsedPercent,
}
// ---- point ----
p := influxdb2.NewPoint(
"disk",
tags,
fields,
time.Now(),
)
// Ecriture
if err := writeAPI.WritePoint(context.Background(), p); err != nil {
log.Printf("write failed: %v \n", err)
}
}
log.Println("(", len(disks), ") Disks written to InfluxDB :"+reqUrl)
}
}

View File

@@ -0,0 +1,62 @@
package goroutines
import (
"context"
"encoding/json"
"log"
"net/http"
"time"
"github.com/evoliatis/buildup/load"
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
)
func GoLoad(server, INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN string, tick int) {
// Connexion
client := influxdb2.NewClient(INFLUXDB_URL, INFLUXDB_TOKEN)
defer client.Close()
writeAPI := client.WriteAPIBlocking(INFLUXDB_ORG, INFLUXDB_BUCKET)
ticker := time.NewTicker(time.Duration(tick) * time.Second)
defer ticker.Stop()
for range ticker.C {
// Récupération des valeurs
reqUrl := "http://" + server + "/load"
resp, err := http.Get(reqUrl)
if err != nil {
log.Println("Erreur de communication : " + reqUrl)
continue
}
defer resp.Body.Close()
var avg load.LoadInfo
if err := json.NewDecoder(resp.Body).Decode(&avg); err != nil {
log.Println("Impossible de récupérer : " + reqUrl)
continue
}
// ---- point ----
p := influxdb2.NewPoint(
"load_average",
map[string]string{ // tags (ex: host, env…)
"host": server,
},
map[string]interface{}{ // Données à stocker
"avg1": avg.Avg.Load1,
"avg5": avg.Avg.Load5,
"avg15": avg.Avg.Load15,
},
time.Now(),
)
// Ecriture
if err := writeAPI.WritePoint(context.Background(), p); err != nil {
log.Printf("write failed: %v\n", err)
}
log.Println("load average written to InfluxDB : ", reqUrl)
}
}

View File

@@ -0,0 +1,79 @@
package goroutines
import (
"context"
"encoding/json"
"log"
"net/http"
"time"
"github.com/evoliatis/buildup/memory"
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
)
func GoMem(server, INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN string, tick int) {
// Connexion
client := influxdb2.NewClient(INFLUXDB_URL, INFLUXDB_TOKEN)
defer client.Close()
writeAPI := client.WriteAPIBlocking(INFLUXDB_ORG, INFLUXDB_BUCKET)
ticker := time.NewTicker(time.Duration(tick) * time.Second)
defer ticker.Stop()
for range ticker.C {
// Récupération des valeurs
reqUrl := "http://" + server + "/mem"
resp, err := http.Get(reqUrl)
if err != nil {
log.Println("Erreur de communication : " + reqUrl)
continue
}
defer resp.Body.Close()
var mem memory.MemInfo
if err := json.NewDecoder(resp.Body).Decode(&mem); err != nil {
log.Println("Impossible de récupérer : " + reqUrl)
continue
}
// ---- point ----
tags := map[string]string{ // tags (ex: host, env…)
"host": server,
}
fields := map[string]interface{}{
// Tous les champs TimesStat (numériques)
"total": mem.Virtual.Total,
"available": mem.Virtual.Available,
"used": mem.Virtual.Used,
"used_pcent": mem.Virtual.UsedPercent,
"free": mem.Virtual.Free,
"active": mem.Virtual.Active,
"inactive": mem.Virtual.Inactive,
"cached": mem.Virtual.Cached,
"dirty": mem.Virtual.Dirty,
"shared": mem.Virtual.Shared,
"swap_total": mem.Swap.Total,
"swap_used": mem.Swap.Used,
"swap_used_pcent": mem.Swap.UsedPercent,
"swap_free": mem.Swap.Free,
"swap_pgin": mem.Swap.PgIn,
"swap_pgout": mem.Swap.PgOut,
"swap_pgfault": mem.Swap.PgFault,
}
p := influxdb2.NewPoint(
"memory",
tags,
fields,
time.Now(),
)
// Ecriture
if err := writeAPI.WritePoint(context.Background(), p); err != nil {
log.Printf("write failed: %v\n", err)
}
log.Println("memory written to InfluxDB : ", reqUrl)
}
}

View File

@@ -0,0 +1,78 @@
package goroutines
import (
"context"
"encoding/json"
"log"
"net/http"
"strconv"
"time"
"github.com/evoliatis/buildup/netcard"
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
)
func GoNet(server, INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN string, tick int) {
// Connexion
client := influxdb2.NewClient(INFLUXDB_URL, INFLUXDB_TOKEN)
defer client.Close()
writeAPI := client.WriteAPIBlocking(INFLUXDB_ORG, INFLUXDB_BUCKET)
ticker := time.NewTicker(time.Duration(tick) * time.Second)
defer ticker.Stop()
for range ticker.C {
// Récupération des valeurs
reqUrl := "http://" + server + "/net"
resp, err := http.Get(reqUrl)
if err != nil {
log.Println("Erreur de communication : " + reqUrl)
continue
}
defer resp.Body.Close()
var nets []netcard.NetCard
if err := json.NewDecoder(resp.Body).Decode(&nets); err != nil {
log.Println("Impossible de récupérer : " + reqUrl)
continue
}
for _, net := range nets {
tags := map[string]string{ // tags (ex: host, env…)
"host": "linux",
"name": net.Interface.Name,
"mtu": strconv.FormatInt(int64(net.Interface.MTU), 10),
"hardwareaddr": net.Interface.HardwareAddr,
}
fields := map[string]interface{}{ // Données à stocker
"bytesent": net.IO.BytesSent,
"byterecv": net.IO.BytesRecv,
"packetsent": net.IO.PacketsSent,
"packetrecv": net.IO.PacketsRecv,
"errorin": net.IO.Errin,
"errorout": net.IO.Errout,
"dropin": net.IO.Dropin,
"dropout": net.IO.Dropout,
"fifoin": net.IO.Fifoin,
"fifoout": net.IO.Fifoout,
}
// ---- points ----
p := influxdb2.NewPoint(
"network",
tags,
fields,
time.Now(),
)
// Ecriture
if err := writeAPI.WritePoint(context.Background(), p); err != nil {
log.Printf("write failed: %v \n", err)
}
}
log.Println("(", len(nets), ") network written to InfluxDB : "+reqUrl)
}
}

View File

@@ -0,0 +1,70 @@
package goroutines
import (
"context"
"encoding/json"
"log"
"net/http"
"strconv"
"time"
"github.com/evoliatis/buildup/proc"
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
)
func GoProc(server, INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN string, tick int) {
// Connexion
client := influxdb2.NewClient(INFLUXDB_URL, INFLUXDB_TOKEN)
defer client.Close()
writeAPI := client.WriteAPIBlocking(INFLUXDB_ORG, INFLUXDB_BUCKET)
ticker := time.NewTicker(time.Duration(tick) * time.Second)
defer ticker.Stop()
for range ticker.C {
// Récupération des valeurs
reqUrl := "http://" + server + "/ps"
resp, err := http.Get(reqUrl)
if err != nil {
log.Println("Erreur de communication : " + reqUrl)
continue
}
defer resp.Body.Close()
var procs []proc.Proc
if err := json.NewDecoder(resp.Body).Decode(&procs); err != nil {
log.Println("Impossible de récupérer : " + reqUrl)
continue
}
for _, proc := range procs {
tags := map[string]string{ // tags (ex: host, env…)
"host": "linux",
"name": proc.Name,
"pid": strconv.FormatInt(int64(proc.Pid), 10),
}
fields := map[string]interface{}{ // Données à stocker
"CPU": proc.CPU,
"Memory": proc.Memory,
"Status": proc.Status,
}
// ---- points ----
p := influxdb2.NewPoint(
"processes",
tags,
fields,
time.Now(),
)
// Ecriture
if err := writeAPI.WritePoint(context.Background(), p); err != nil {
log.Printf("write failed: %v \n", err)
}
}
log.Println("(", len(procs), ") procs written to InfluxDB : "+reqUrl)
}
}

11
buildup/src/handle.go Normal file
View File

@@ -0,0 +1,11 @@
package main
import (
"fmt"
"net/http"
)
// health URL
func HealthHandle(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "ok")
}

27
buildup/src/load/load.go Normal file
View File

@@ -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
}

40
buildup/src/main.go Normal file
View File

@@ -0,0 +1,40 @@
package main
import (
"log"
"net/http"
"os"
"github.com/evoliatis/buildup/goroutines"
"github.com/joho/godotenv"
)
func main() {
// .env 파일을 불러오세요
if err := godotenv.Load(); err != nil {
log.Fatalln("fichier .env non trouvé")
}
InToken := os.Getenv("INFLUXDB_TOKEN")
InUrl := os.Getenv("INFLUXDB_URL")
InOrg := os.Getenv("INFLUXDB_ORG")
InBucket := os.Getenv("INFLUXDB_BUCKET")
// 서버 목록을 불러오세요
cfg, err := LoadConfig("servers.yaml")
if err != nil {
log.Fatalln("fichier servers.yaml non trouvé")
}
// Go routines
for _, srv := range cfg.Servers {
go goroutines.GoLoad(srv, InUrl, InOrg, InBucket, InToken, 10)
go goroutines.GoProc(srv, InUrl, InOrg, InBucket, InToken, 10)
go goroutines.GoCPU(srv, InUrl, InOrg, InBucket, InToken, 10)
go goroutines.GoMem(srv, InUrl, InOrg, InBucket, InToken, 10)
go goroutines.GoNet(srv, InUrl, InOrg, InBucket, InToken, 10)
go goroutines.GoDisk(srv, InUrl, InOrg, InBucket, InToken, 10)
}
log.Println("listening on :80")
log.Fatal(http.ListenAndServe(":80", router()))
}

View File

@@ -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
}

View File

@@ -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
}

35
buildup/src/proc/proc.go Normal file
View File

@@ -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
}

17
buildup/src/routes.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import "net/http"
// Routeur pour l'ensemble de nos routes
func router() http.Handler {
mux := http.NewServeMux()
// health URL
mux.HandleFunc("GET /health", HealthHandle)
// Autres cas : fichiers statiques
fs := http.FileServer(http.Dir("www"))
mux.Handle("/", fs)
return mux
}

2
buildup/src/servers.yaml Normal file
View File

@@ -0,0 +1,2 @@
servers:
- linux:8080

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

370
buildup/src/www/index.html Normal file
View File

@@ -0,0 +1,370 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="description" content="Reveleant Software Solution — Une vision (très) corporate, avec une touche d'humour geek." />
<title>Reveleant Software Solution — Note interne</title>
<style>
:root{
--bg0:#0b0f14;
--bg1:#0b1a16;
--card: rgba(255,255,255,.06);
--stroke: rgba(255,255,255,.12);
--text: rgba(255,255,255,.92);
--muted: rgba(255,255,255,.72);
--accent:#38e38d;
--accent2:#1fbf6a;
--shadow: 0 18px 60px rgba(0,0,0,.45);
--radius: 18px;
--radius2: 26px;
}
*{box-sizing:border-box}
html,body{height:100%}
body{
margin:0;
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji","Segoe UI Emoji";
color: var(--text);
background:
radial-gradient(1100px 700px at 18% 10%, rgba(56,227,141,.18), transparent 55%),
radial-gradient(900px 600px at 88% 42%, rgba(31,191,106,.14), transparent 60%),
linear-gradient(180deg, var(--bg0), var(--bg1));
overflow-x:hidden;
}
.grid{
position:fixed; inset:0;
pointer-events:none;
background-image:
linear-gradient(to right, rgba(255,255,255,.06) 1px, transparent 1px),
linear-gradient(to bottom, rgba(255,255,255,.06) 1px, transparent 1px);
background-size: 44px 44px;
mask-image: radial-gradient(740px 420px at 32% 18%, black 42%, transparent 88%);
opacity:.30;
}
.wrap{max-width:1100px;margin:0 auto;padding:26px 18px 64px}
header{
display:flex;align-items:center;justify-content:space-between;gap:16px;
padding:14px 16px;
border:1px solid var(--stroke);
background: linear-gradient(180deg, rgba(255,255,255,.08), rgba(255,255,255,.04));
border-radius: var(--radius2);
box-shadow: var(--shadow);
backdrop-filter: blur(10px);
}
.brand{
display:flex;align-items:center;gap:14px;min-width:240px;
}
.brand-mark{
width:40px;height:40px;border-radius:14px;
border:1px solid rgba(56,227,141,.30);
background: linear-gradient(180deg, rgba(56,227,141,.18), rgba(31,191,106,.08));
display:grid;place-items:center;
box-shadow: 0 10px 26px rgba(31,191,106,.18);
color: rgba(255,255,255,.9);
font-weight:900;
}
.brand strong{letter-spacing:.3px}
.brand span{display:block;color:var(--muted);font-size:12.5px;margin-top:1px}
nav{display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end}
.pill{
text-decoration:none;
font-size:13px;
padding:9px 12px;
border-radius:999px;
border:1px solid var(--stroke);
background: rgba(255,255,255,.04);
color: rgba(255,255,255,.86);
}
.pill:hover{background: rgba(255,255,255,.07)}
.cta{
text-decoration:none;
font-weight:700;
letter-spacing:.2px;
padding:10px 16px;
border-radius:999px;
border:1px solid rgba(56,227,141,.45);
background: linear-gradient(
180deg,
rgba(56,227,141,.35),
rgba(31,191,106,.20)
);
color: #ffffff; /* ✅ FIX PRINCIPAL */
box-shadow:
0 10px 30px rgba(31,191,106,.25),
inset 0 0 0 1px rgba(255,255,255,.08);
}
.cta:hover{
filter: brightness(1.08);
color: #ffffff;
}
.hero{
margin-top:18px;
display:grid;
grid-template-columns: 1.05fr .95fr;
gap:16px;
align-items:stretch;
}
@media (max-width: 920px){
.hero{grid-template-columns:1fr}
nav{justify-content:flex-start}
}
.panel{
border:1px solid var(--stroke);
background: linear-gradient(180deg, rgba(255,255,255,.07), rgba(255,255,255,.03));
border-radius: var(--radius2);
box-shadow: var(--shadow);
backdrop-filter: blur(10px);
overflow:hidden;
}
.left{padding:24px}
.kicker{
display:inline-flex;align-items:center;gap:10px;
padding:8px 12px;border-radius:999px;
border:1px solid rgba(56,227,141,.28);
background: rgba(56,227,141,.09);
color: rgba(255,255,255,.86);
font-size:13px;
}
.pulse{
width:10px;height:10px;border-radius:999px;
background: var(--accent);
box-shadow: 0 0 0 0 rgba(56,227,141,.45);
animation: pulse 1.8s infinite;
}
@keyframes pulse{
0%{box-shadow:0 0 0 0 rgba(56,227,141,.45)}
70%{box-shadow:0 0 0 14px rgba(56,227,141,0)}
100%{box-shadow:0 0 0 0 rgba(56,227,141,0)}
}
h1{
margin:14px 0 10px;
font-size:34px;
line-height:1.08;
letter-spacing:-.6px;
}
.lead{
margin:0;
color: var(--muted);
font-size:15.8px;
line-height:1.6;
max-width: 66ch;
}
hr{
border:0;
height:1px;
background: linear-gradient(90deg, transparent, rgba(56,227,141,.25), rgba(255,255,255,.10), transparent);
margin:16px 0;
}
.section h2{
margin:0 0 8px;
font-size:15px;
letter-spacing:.2px;
text-transform: uppercase;
color: rgba(255,255,255,.88);
display:flex;align-items:center;gap:10px;
}
.section p{
margin:0;
color: rgba(255,255,255,.76);
line-height:1.65;
font-size:14.5px;
}
.section p + p{margin-top:10px}
.right{
position:relative;
padding:18px;
display:flex;
align-items:center;
justify-content:center;
min-height: 360px;
}
.logo-wrap{
position:relative;
width:min(520px, 96%);
padding:16px;
border-radius: 18px;
border:1px solid rgba(255,255,255,.10);
background: rgba(0,0,0,.10);
}
.logo-img{
width:100%;
height:auto;
border-radius: 12px;
filter: drop-shadow(0 18px 42px rgba(0,0,0,.45));
display:block;
}
.glow{
position:absolute; inset:auto;
width:560px; height:560px;
left: 50%; top: 46%;
transform: translate(-50%,-50%);
background:
radial-gradient(circle at 35% 35%, rgba(56,227,141,.22), transparent 55%),
radial-gradient(circle at 60% 55%, rgba(31,191,106,.14), transparent 60%);
filter: blur(14px);
z-index:-1;
opacity:.95;
pointer-events:none;
}
.actions{
margin-top:14px;
display:flex;
gap:10px;
flex-wrap:wrap;
}
.btn{
display:inline-flex;align-items:center;justify-content:center;gap:10px;
text-decoration:none;
padding:10px 12px;
border-radius: 14px;
border:1px solid var(--stroke);
background: rgba(255,255,255,.04);
color: rgba(255,255,255,.90);
font-weight:700;
font-size:13.5px;
}
.btn:hover{background: rgba(255,255,255,.07)}
.btn-primary{
border-color: rgba(56,227,141,.35);
background: linear-gradient(180deg, rgba(56,227,141,.22), rgba(31,191,106,.10));
box-shadow: 0 10px 30px rgba(31,191,106,.16);
}
footer{
margin-top:16px;
padding:14px 16px;
border-radius: var(--radius2);
border:1px solid var(--stroke);
background: rgba(255,255,255,.03);
color: rgba(255,255,255,.68);
font-size:13px;
display:flex;gap:10px;flex-wrap:wrap;align-items:center;justify-content:space-between;
}
.badge{
display:inline-flex;align-items:center;gap:10px;
padding:8px 10px;border-radius:999px;
border:1px solid rgba(56,227,141,.22);
background: rgba(56,227,141,.08);
color: rgba(255,255,255,.80);
}
.mini{width:8px;height:8px;border-radius:999px;background:var(--accent);box-shadow:0 0 18px rgba(56,227,141,.35)}
.fine a{color: rgba(255,255,255,.86); text-decoration:none; border-bottom:1px dashed rgba(255,255,255,.22)}
.fine a:hover{border-bottom-color: rgba(56,227,141,.45)}
</style>
</head>
<body>
<div class="grid" aria-hidden="true"></div>
<div class="wrap">
<header>
<div class="brand">
<div class="brand-mark" aria-hidden="true">R</div>
<div>
<strong>Reveleant Software Solution</strong>
<span>Note interne • Version corporate, humour contrôlé</span>
</div>
</div>
<nav aria-label="Navigation">
<a class="pill" href="#discours">Discours</a>
<a class="pill" href="#morale">Morale</a>
<!-- Remplace si besoin -->
<a class="cta" href="https://poudreverte.org/" target="_blank" rel="noopener noreferrer">Site de léditeur</a>
</nav>
</header>
<section class="hero" id="discours" aria-label="Discours">
<div class="panel left">
<div class="kicker"><span class="pulse" aria-hidden="true"></span> Communication interne</div>
<h1>🎤 Le développeur, la direction… et le vent.</h1>
<p class="lead">
Ceci est une prise de parole officielle de l'équipe de développement,
mais avec un bug volontaire dans le sarcasme. 🧑‍💻
</p>
<hr/>
<div class="section">
<h2>🧾 Promesses & livrables</h2>
<p>
La direction adore dire : <em>“On na pas beaucoup de budget, mais cest un super projet humain.”</em><br/>
Traduction : tu seras payé en <strong>expérience</strong>, en <strong>tickets</strong>, et en <strong>promesses compressées en ZIP</strong>.
</p>
</div>
<hr/>
<div class="section">
<h2>🌬️ La direction</h2>
<p>
La direction, cest un peu comme une API REST mal documentée :
elle promet beaucoup, répond rarement,
et quand elle répond, cest un 200 OK avec un body vide.
</p>
<p>
Les promesses, cest un peu comme un site rempli de concepts : beaucoup dair, peu de portance… 💨
</p>
<p>
On nous vend du <strong>disruptif</strong>, de l<strong>auto-scalable</strong>… et au final on déploie surtout :
une VM trop petite et un développeur trop tard. ⚙️
</p>
</div>
<hr/>
<div class="section" id="morale">
<h2>✅ Morale</h2>
<p>
Nous aimons les rêves. Nous aimons lambition. Mais nous aimons aussi les choses testables :
des specs claires, des dates réelles, et des promesses qui passent les tests unitaires. 🧪
</p>
</div>
<div class="actions">
<a class="btn btn-primary" href="https://poudreverte.org/" target="_blank" rel="noopener noreferrer">Ouvrir le site éditeur ↗</a>
<a class="btn" href="#discours">Revenir en haut</a>
</div>
</div>
<div class="panel right" aria-label="Logo">
<div class="glow" aria-hidden="true"></div>
<div class="logo-wrap">
<img class="logo-img" src="img/logo.png" alt="Logo Reveleant Software Solution" />
</div>
</div>
</section>
<footer>
<div class="badge"><span class="mini" aria-hidden="true"></span>Corporate • Stable • (Version en bêta)</div>
<div class="fine">
© <strong>Reveleant Software Solution</strong><a href="https://poudreverte.org/" target="_blank" rel="noopener noreferrer">accéder au site</a>
</div>
</footer>
</div>
</body>
</html>