@@ -15,12 +15,16 @@ import (
1515 "github.com/gin-gonic/gin"
1616
1717 "crowdsec-manager/internal/api"
18+ "crowdsec-manager/internal/api/handlers"
19+ "crowdsec-manager/internal/api/middleware"
1820 "crowdsec-manager/internal/backup"
1921 "crowdsec-manager/internal/config"
22+ "crowdsec-manager/internal/configvalidator"
2023 "crowdsec-manager/internal/cron"
2124 "crowdsec-manager/internal/database"
2225 "crowdsec-manager/internal/docker"
2326 "crowdsec-manager/internal/logger"
27+ "crowdsec-manager/internal/messaging"
2428)
2529
2630// Main entry point for the CrowdSec Manager server
@@ -35,6 +39,7 @@ func main() {
3539
3640 // Initialize structured logger with configured level and output file
3741 logger .Init (cfg .LogLevel , cfg .LogFile )
42+ defer logger .Sync ()
3843
3944 // Initialize SQLite database connection with automatic schema migration
4045 db , err := database .New (cfg .DatabasePath )
@@ -44,12 +49,15 @@ func main() {
4449 defer db .Close ()
4550 logger .Info ("Database initialized" , "path" , cfg .DatabasePath )
4651
47- // Initialize Docker API client with automatic version negotiation
48- dockerClient , err := docker .NewClient ( )
52+ // Initialize multi-host Docker client (falls back to single host if DOCKER_HOSTS is empty)
53+ multiHost , err := docker .NewMultiHostClient ( cfg . DockerHosts )
4954 if err != nil {
5055 logger .Fatal ("Failed to initialize Docker client" , "error" , err )
5156 }
52- defer dockerClient .Close ()
57+ defer multiHost .Close ()
58+
59+ // Default client for backward compatibility with existing handler signatures
60+ dockerClient := multiHost .DefaultClient ()
5361
5462 dataDir := cfg .ConfigDir
5563
@@ -61,6 +69,29 @@ func main() {
6169 cronScheduler .Start ()
6270 defer cronScheduler .Stop ()
6371
72+ // Initialize WebSocket/SSE hub (always available for real-time events)
73+ hub := messaging .NewHub ()
74+ go hub .Run ()
75+ defer hub .Stop ()
76+
77+ // Initialize config validator for drift detection and recovery
78+ validator := configvalidator .NewValidator (db , dockerClient , hub , cfg )
79+ handlers .SetConfigValidator (validator )
80+
81+ // Snapshot all configs on startup (populates DB if empty)
82+ validator .SnapshotAll ()
83+
84+ // Validate configs and warn about drift
85+ if report := validator .ValidateAll (); report .Overall != "ok" {
86+ logger .Warn ("Config drift detected on startup" , "overall" , report .Overall )
87+ }
88+
89+ // Initialize NATS messaging (optional — nil-safe when disabled)
90+ publisher , natsCleanup := initMessaging (cfg , hub )
91+ if natsCleanup != nil {
92+ defer natsCleanup ()
93+ }
94+
6495 // Configure HTTP router with recovery middleware and custom logger
6596 router := gin .New ()
6697 router .Use (gin .Recovery ())
@@ -70,7 +101,7 @@ func main() {
70101 router .Use (cors .New (cors.Config {
71102 AllowOrigins : []string {"http://localhost:3000" , "http://localhost:5173" },
72103 AllowMethods : []string {"GET" , "POST" , "PUT" , "DELETE" , "OPTIONS" },
73- AllowHeaders : []string {"Origin" , "Content-Type" , "Authorization" },
104+ AllowHeaders : []string {"Origin" , "Content-Type" , "Authorization" , "X-Docker-Host" },
74105 ExposeHeaders : []string {"Content-Length" },
75106 AllowCredentials : true ,
76107 MaxAge : 12 * time .Hour ,
@@ -83,6 +114,13 @@ func main() {
83114
84115 // Register all API route groups under /api prefix
85116 apiGroup := router .Group ("/api" )
117+
118+ // Add rate limiting middleware (100 requests per minute per IP)
119+ apiGroup .Use (middleware .RateLimiter (100 ))
120+
121+ // Add Docker host selector middleware for multi-host support
122+ apiGroup .Use (middleware .DockerHostSelector (multiHost ))
123+
86124 {
87125 api .RegisterHealthRoutes (apiGroup , dockerClient , db , cfg )
88126 api .RegisterIPRoutes (apiGroup , dockerClient , cfg )
@@ -97,8 +135,29 @@ func main() {
97135 api .RegisterServicesRoutes (apiGroup , dockerClient , db , cfg )
98136 api .RegisterNotificationRoutes (apiGroup , dockerClient , db , cfg )
99137 api .RegisterProfileRoutes (apiGroup , db , cfg , dockerClient )
138+ api .RegisterHostRoutes (apiGroup , multiHost )
139+ api .RegisterTerminalRoutes (apiGroup , dockerClient )
140+
141+ // Hub browser routes
142+ api .RegisterHubRoutes (apiGroup , dockerClient , db , cfg )
143+
144+ // Simulation mode routes
145+ api .RegisterSimulationRoutes (apiGroup , dockerClient , cfg )
146+
147+ // Event routes (hub is always available for SSE/WebSocket)
148+ api .RegisterEventRoutes (apiGroup , hub )
149+
150+ // Config validation routes
151+ api .RegisterConfigValidationRoutes (apiGroup , validator )
100152 }
101153
154+ // Bridge NATS events to WebSocket hub (if both are available)
155+ if publisher != nil && hub != nil {
156+ go bridgeNATSToHub (cfg , hub )
157+ }
158+ // Suppress unused variable warnings — publisher will be used by handlers in Phase 4
159+ _ = publisher
160+
102161 // Serve React frontend static assets and handle client-side routing
103162 router .Static ("/assets" , "./web/dist/assets" )
104163 router .StaticFile ("/" , "./web/dist/index.html" )
@@ -141,6 +200,36 @@ func main() {
141200 logger .Info ("Server exited" )
142201}
143202
203+ // initMessaging initializes NATS client and publisher.
204+ // Returns nil values when NATS is disabled — all are nil-safe.
205+ func initMessaging (cfg * config.Config , hub * messaging.Hub ) (* messaging.Publisher , func ()) {
206+ if ! cfg .NatsEnabled || cfg .NatsURL == "" {
207+ logger .Info ("NATS messaging disabled" )
208+ return nil , nil
209+ }
210+
211+ natsClient , err := messaging .NewClient (cfg .NatsURL , cfg .NatsToken )
212+ if err != nil {
213+ logger .Error ("Failed to connect to NATS (messaging disabled)" , "error" , err )
214+ return nil , nil
215+ }
216+
217+ publisher := messaging .NewPublisher (natsClient )
218+
219+ logger .Info ("NATS messaging initialized" , "url" , cfg .NatsURL )
220+
221+ cleanup := func () {
222+ natsClient .Close ()
223+ }
224+ return publisher , cleanup
225+ }
226+
227+ // bridgeNATSToHub subscribes to NATS subjects and forwards events to the WebSocket hub
228+ func bridgeNATSToHub (cfg * config.Config , hub * messaging.Hub ) {
229+ // This will be wired up when NATS client Subscribe is implemented
230+ logger .Info ("NATS-to-WebSocket bridge started" )
231+ }
232+
144233// checkPrerequisites verifies that Docker daemon is running and required containers exist
145234// This function is defined but not currently called in main - consider adding prerequisite checks if needed
146235func checkPrerequisites (client * docker.Client , cfg * config.Config ) error {
0 commit comments