Skip to content

Commit 30e821b

Browse files
Merge pull request #44 from hhftechnology/dev
Dev
2 parents a040df8 + 737a733 commit 30e821b

270 files changed

Lines changed: 23443 additions & 9554 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/docker-publish.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ on:
55
branches:
66
- main
77
- dev
8-
- dev-go
9-
- dev-discord
8+
- pangolin
109
tags:
1110
- 'v*'
1211
pull_request:
@@ -76,4 +75,4 @@ jobs:
7675
labels: ${{ steps.meta.outputs.labels }}
7776
cache-from: type=gha
7877
cache-to: type=gha,mode=max
79-
platforms: linux/amd64,linux/arm64
78+
platforms: linux/amd64,linux/arm64

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
# Node.js
1111
# =====================================================
1212
# Logs
13-
logs
13+
/logs
1414
*.log
1515
npm-debug.log*
1616
yarn-debug.log*

README.md

Lines changed: 248 additions & 643 deletions
Large diffs are not rendered by default.

cmd/server/main.go

Lines changed: 108 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,17 @@ 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"
21+
"crowdsec-manager/internal/cache"
1922
"crowdsec-manager/internal/config"
23+
"crowdsec-manager/internal/configvalidator"
2024
"crowdsec-manager/internal/cron"
2125
"crowdsec-manager/internal/database"
2226
"crowdsec-manager/internal/docker"
2327
"crowdsec-manager/internal/logger"
28+
"crowdsec-manager/internal/messaging"
2429
)
2530

2631
// Main entry point for the CrowdSec Manager server
@@ -35,6 +40,7 @@ func main() {
3540

3641
// Initialize structured logger with configured level and output file
3742
logger.Init(cfg.LogLevel, cfg.LogFile)
43+
defer logger.Sync()
3844

3945
// Initialize SQLite database connection with automatic schema migration
4046
db, err := database.New(cfg.DatabasePath)
@@ -44,12 +50,15 @@ func main() {
4450
defer db.Close()
4551
logger.Info("Database initialized", "path", cfg.DatabasePath)
4652

47-
// Initialize Docker API client with automatic version negotiation
48-
dockerClient, err := docker.NewClient()
53+
// Initialize multi-host Docker client (falls back to single host if DOCKER_HOSTS is empty)
54+
multiHost, err := docker.NewMultiHostClient(cfg.DockerHosts)
4955
if err != nil {
5056
logger.Fatal("Failed to initialize Docker client", "error", err)
5157
}
52-
defer dockerClient.Close()
58+
defer multiHost.Close()
59+
60+
// Default client for backward compatibility with existing handler signatures
61+
dockerClient := multiHost.DefaultClient()
5362

5463
dataDir := cfg.ConfigDir
5564

@@ -61,6 +70,29 @@ func main() {
6170
cronScheduler.Start()
6271
defer cronScheduler.Stop()
6372

73+
// Initialize WebSocket/SSE hub (always available for real-time events)
74+
hub := messaging.NewHub()
75+
go hub.Run()
76+
defer hub.Stop()
77+
78+
// Initialize config validator for drift detection and recovery
79+
validator := configvalidator.NewValidator(db, dockerClient, hub, cfg)
80+
handlers.SetConfigValidator(validator)
81+
82+
// Snapshot all configs on startup (populates DB if empty)
83+
validator.SnapshotAll()
84+
85+
// Validate configs and warn about drift
86+
if report := validator.ValidateAll(); report.Overall != "ok" {
87+
logger.Warn("Config drift detected on startup", "overall", report.Overall)
88+
}
89+
90+
// Initialize NATS messaging (optional — nil-safe when disabled)
91+
publisher, natsCleanup := initMessaging(cfg, hub)
92+
if natsCleanup != nil {
93+
defer natsCleanup()
94+
}
95+
6496
// Configure HTTP router with recovery middleware and custom logger
6597
router := gin.New()
6698
router.Use(gin.Recovery())
@@ -70,7 +102,7 @@ func main() {
70102
router.Use(cors.New(cors.Config{
71103
AllowOrigins: []string{"http://localhost:3000", "http://localhost:5173"},
72104
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
73-
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
105+
AllowHeaders: []string{"Origin", "Content-Type", "Authorization", "X-Docker-Host"},
74106
ExposeHeaders: []string{"Content-Length"},
75107
AllowCredentials: true,
76108
MaxAge: 12 * time.Hour,
@@ -83,6 +115,13 @@ func main() {
83115

84116
// Register all API route groups under /api prefix
85117
apiGroup := router.Group("/api")
118+
119+
// Add rate limiting middleware (100 requests per minute per IP)
120+
apiGroup.Use(middleware.RateLimiter(100))
121+
122+
// Add Docker host selector middleware for multi-host support
123+
apiGroup.Use(middleware.DockerHostSelector(multiHost))
124+
86125
{
87126
api.RegisterHealthRoutes(apiGroup, dockerClient, db, cfg)
88127
api.RegisterIPRoutes(apiGroup, dockerClient, cfg)
@@ -94,17 +133,47 @@ func main() {
94133
api.RegisterBackupRoutes(apiGroup, backupManager, dockerClient)
95134
api.RegisterUpdateRoutes(apiGroup, dockerClient, cfg)
96135
api.RegisterCronRoutes(apiGroup, cronScheduler)
97-
api.RegisterServicesRoutes(apiGroup, dockerClient, db, cfg)
136+
ttlCache := cache.New()
137+
api.RegisterServicesRoutes(apiGroup, dockerClient, db, cfg, ttlCache)
98138
api.RegisterNotificationRoutes(apiGroup, dockerClient, db, cfg)
99139
api.RegisterProfileRoutes(apiGroup, db, cfg, dockerClient)
140+
api.RegisterHostRoutes(apiGroup, multiHost)
141+
api.RegisterTerminalRoutes(apiGroup, dockerClient)
142+
143+
// Hub browser routes
144+
api.RegisterHubRoutes(apiGroup, dockerClient, db, cfg)
145+
146+
// Simulation mode routes
147+
api.RegisterSimulationRoutes(apiGroup, dockerClient, cfg)
148+
149+
// Event routes (hub is always available for SSE/WebSocket)
150+
api.RegisterEventRoutes(apiGroup, hub)
151+
152+
// Config validation routes
153+
api.RegisterConfigValidationRoutes(apiGroup, validator)
100154
}
101155

102-
// Serve React frontend static assets and handle client-side routing
156+
// Bridge NATS events to WebSocket hub (if both are available)
157+
if publisher != nil && hub != nil {
158+
go bridgeNATSToHub(cfg, hub)
159+
}
160+
// Suppress unused variable warnings — publisher will be used by handlers in Phase 4
161+
_ = publisher
162+
163+
// Serve React frontend static assets and handle client-side routing.
164+
// Assets use content-hashed filenames so they can be cached indefinitely.
103165
router.Static("/assets", "./web/dist/assets")
104-
router.StaticFile("/", "./web/dist/index.html")
105-
router.NoRoute(func(c *gin.Context) {
166+
// index.html must not be cached — it references hashed asset URLs that
167+
// change on each build. Without no-cache the browser may serve a stale
168+
// copy (304) that points to old, non-existent JS chunks.
169+
serveIndex := func(c *gin.Context) {
170+
c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
171+
c.Header("Pragma", "no-cache")
172+
c.Header("Expires", "0")
106173
c.File("./web/dist/index.html")
107-
})
174+
}
175+
router.GET("/", func(c *gin.Context) { serveIndex(c) })
176+
router.NoRoute(serveIndex)
108177

109178
// Create HTTP server with production-ready timeouts to prevent resource exhaustion
110179
srv := &http.Server{
@@ -141,6 +210,36 @@ func main() {
141210
logger.Info("Server exited")
142211
}
143212

213+
// initMessaging initializes NATS client and publisher.
214+
// Returns nil values when NATS is disabled — all are nil-safe.
215+
func initMessaging(cfg *config.Config, hub *messaging.Hub) (*messaging.Publisher, func()) {
216+
if !cfg.NatsEnabled || cfg.NatsURL == "" {
217+
logger.Info("NATS messaging disabled")
218+
return nil, nil
219+
}
220+
221+
natsClient, err := messaging.NewClient(cfg.NatsURL, cfg.NatsToken)
222+
if err != nil {
223+
logger.Error("Failed to connect to NATS (messaging disabled)", "error", err)
224+
return nil, nil
225+
}
226+
227+
publisher := messaging.NewPublisher(natsClient)
228+
229+
logger.Info("NATS messaging initialized", "url", cfg.NatsURL)
230+
231+
cleanup := func() {
232+
natsClient.Close()
233+
}
234+
return publisher, cleanup
235+
}
236+
237+
// bridgeNATSToHub subscribes to NATS subjects and forwards events to the WebSocket hub
238+
func bridgeNATSToHub(cfg *config.Config, hub *messaging.Hub) {
239+
// This will be wired up when NATS client Subscribe is implemented
240+
logger.Info("NATS-to-WebSocket bridge started")
241+
}
242+
144243
// checkPrerequisites verifies that Docker daemon is running and required containers exist
145244
// This function is defined but not currently called in main - consider adding prerequisite checks if needed
146245
func checkPrerequisites(client *docker.Client, cfg *config.Config) error {
File renamed without changes.

docker-compose.dev.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ services:
5353
cd /app &&
5454
go install github.com/cosmtrek/air@latest &&
5555
cd /app/web && npm install && npm run dev &
56-
cd /app && air
56+
cd /app && air -c configs/.air.toml
5757
"
5858
5959
# Pangolin service

0 commit comments

Comments
 (0)