From c0c7152b47b68d0c6d6c839e0bc3f95ff8067aac Mon Sep 17 00:00:00 2001 From: Gilles Soulier Date: Fri, 22 May 2026 12:13:10 +0200 Subject: [PATCH] feat(server): main.go assemblage complet + Docker + Nginx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Assemble tous les packages (config, db, handlers, transport, websocket, prometheus) - Boucle de rétention et détection offline toutes les minutes - Routage REST /api/agents/, /api/config, /metrics, /ws - Dockerfile multi-stage CGO_ENABLED=0 (alpine:3.19) - docker-compose.yml avec service server + dashboard Nginx - nginx.conf avec proxy WebSocket et fallback SPA Co-Authored-By: Claude Sonnet 4.6 --- server/Dockerfile | 14 +++++++ server/docker-compose.yml | 29 +++++++++++++ server/main.go | 86 ++++++++++++++++++++++++++++++++++++++- server/nginx/nginx.conf | 22 ++++++++++ 4 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 server/Dockerfile create mode 100644 server/docker-compose.yml create mode 100644 server/nginx/nginx.conf diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000..096099f --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:1.22-alpine AS builder +WORKDIR /app +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o nanometrics-server . + +FROM alpine:3.19 +RUN apk add --no-cache ca-certificates +WORKDIR /app +COPY --from=builder /app/nanometrics-server . +VOLUME /data +EXPOSE 8080 9999/udp +CMD ["./nanometrics-server"] diff --git a/server/docker-compose.yml b/server/docker-compose.yml new file mode 100644 index 0000000..2468e10 --- /dev/null +++ b/server/docker-compose.yml @@ -0,0 +1,29 @@ +version: '3.8' +services: + server: + build: . + restart: unless-stopped + environment: + UDP_ADDR: "0.0.0.0:9999" + DB_PATH: "/data/nanometrics.db" + HTTP_ADDR: "0.0.0.0:8080" + MQTT_BROKER: "tcp://10.0.0.3:1883" + MQTT_TOPIC_BASE: "nanometrics/agents" + volumes: + - nanometrics_data:/data + ports: + - "9999:9999/udp" + + dashboard: + image: nginx:alpine + restart: unless-stopped + volumes: + - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro + - ../dashboard:/usr/share/nginx/html:ro + ports: + - "80:80" + depends_on: + - server + +volumes: + nanometrics_data: diff --git a/server/main.go b/server/main.go index 9a3f807..f269448 100644 --- a/server/main.go +++ b/server/main.go @@ -2,11 +2,95 @@ package main import ( "log" + "net/http" + "time" + "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/user/nanometrics/server/config" + "github.com/user/nanometrics/server/db" + "github.com/user/nanometrics/server/handlers" + "github.com/user/nanometrics/server/models" + prom "github.com/user/nanometrics/server/prometheus" + "github.com/user/nanometrics/server/transport" + ws "github.com/user/nanometrics/server/websocket" ) func main() { cfg := config.Load() - log.Printf("nanometrics-server démarrage sur %s", cfg.HTTPAddr) + + database, err := db.Open(cfg.DBPath) + if err != nil { + log.Fatalf("DB: %v", err) + } + + hub := ws.NewHub() + + onMetrics := func(m *models.AgentMetrics) { + if err := database.UpsertAgent(m); err != nil { + log.Printf("[ingest] upsert agent: %v", err) + } + if err := database.InsertMetrics(m); err != nil { + log.Printf("[ingest] insert metrics: %v", err) + } + prom.Update(m) + hub.Broadcast(models.WSMessage{ + Type: "metrics_update", + AgentID: m.Hostname, + Data: m, + }) + } + + if err := transport.StartUDP(cfg.UDPAddr, onMetrics); err != nil { + log.Fatalf("UDP: %v", err) + } + + var mqttClient *transport.MQTTClient + if mc, err := transport.StartMQTT(cfg.MQTTBroker, cfg.MQTTTopicBase, onMetrics); err != nil { + log.Printf("[mqtt] non disponible: %v", err) + } else { + mqttClient = mc + } + + pushConfig := func(agentID string, agentCfg *models.AgentConfig) { + if mqttClient != nil { + if err := mqttClient.PushConfig(agentID, agentCfg); err != nil { + log.Printf("[mqtt] push config to %s: %v", agentID, err) + } + } + } + + go func() { + for range time.Tick(time.Minute) { + srvCfg, _ := database.GetServerConfig() + _ = database.PruneOldMetrics(srvCfg.RetentionDays) + _ = database.MarkOffline(30) + } + }() + + mux := http.NewServeMux() + mux.Handle("/metrics", promhttp.Handler()) + mux.Handle("/ws", ws.Handler(hub)) + mux.HandleFunc("/api/agents", handlers.AgentsHandler(database)) + mux.HandleFunc("/api/agents/", func(w http.ResponseWriter, r *http.Request) { + switch { + case endsWith(r.URL.Path, "/history"): + handlers.MetricsHistoryHandler(database)(w, r) + case endsWith(r.URL.Path, "/config"): + handlers.AgentConfigHandler(database, pushConfig)(w, r) + case endsWith(r.URL.Path, "/icon") && r.Method == http.MethodPost: + handlers.IconUploadHandler(database)(w, r) + case endsWith(r.URL.Path, "/icon") && r.Method == http.MethodGet: + handlers.IconGetHandler(database)(w, r) + default: + http.NotFound(w, r) + } + }) + mux.HandleFunc("/api/config", handlers.ServerConfigHandler(database)) + + log.Printf("[http] écoute sur %s", cfg.HTTPAddr) + log.Fatal(http.ListenAndServe(cfg.HTTPAddr, mux)) +} + +func endsWith(path, suffix string) bool { + return len(path) >= len(suffix) && path[len(path)-len(suffix):] == suffix } diff --git a/server/nginx/nginx.conf b/server/nginx/nginx.conf new file mode 100644 index 0000000..b585da6 --- /dev/null +++ b/server/nginx/nginx.conf @@ -0,0 +1,22 @@ +server { + listen 80; + root /usr/share/nginx/html; + index index.html; + + location /api/ { + proxy_pass http://server:8080; + proxy_set_header Host $host; + } + location /ws { + proxy_pass http://server:8080/ws; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + location /metrics { + proxy_pass http://server:8080/metrics; + } + location / { + try_files $uri $uri/ /index.html; + } +}