NewRouter creates the HTTP router with all routes and middleware. Returns (handler, guacdProcess) - guacdProcess should be stopped on shutdown. frontendFS is the embedded frontend static files (static/frontend/dist); pass nil to disable SPA serving.
(ctx context.Context, cfg *config.Config, db *database.DB, rdb *redisclient.Client, registry *agentregistry.Registry, queueClient *asynq.Client, queueInspector *asynq.Inspector, ctxRegistry *hostctx.Registry, poolCache *hostctx.PoolCache, redisCache *hostctx.RedisCache, notifyEmit *notifications.Emitter, log *slog.Logger, frontendFS fs.FS)
| 38 | // Returns (handler, guacdProcess) - guacdProcess should be stopped on shutdown. |
| 39 | // frontendFS is the embedded frontend static files (static/frontend/dist); pass nil to disable SPA serving. |
| 40 | func NewRouter(ctx context.Context, cfg *config.Config, db *database.DB, rdb *redisclient.Client, registry *agentregistry.Registry, queueClient *asynq.Client, queueInspector *asynq.Inspector, ctxRegistry *hostctx.Registry, poolCache *hostctx.PoolCache, redisCache *hostctx.RedisCache, notifyEmit *notifications.Emitter, log *slog.Logger, frontendFS fs.FS) (http.Handler, *guacd.Process) { |
| 41 | r := chi.NewRouter() |
| 42 | |
| 43 | var dbProvider database.DBProvider |
| 44 | if poolCache != nil { |
| 45 | dbProvider = &hostctx.DBResolver{Default: db} |
| 46 | } else { |
| 47 | dbProvider = db |
| 48 | } |
| 49 | |
| 50 | // Build a RedisResolver so stores always call .RDB(ctx) for per-context isolation. |
| 51 | redisResolver := &hostctx.RedisResolver{Default: rdb} |
| 52 | settingsStore := store.NewSettingsStore(dbProvider) |
| 53 | settings, _ := settingsStore.GetFirst(ctx) |
| 54 | resolved := config.ResolveConfig(ctx, cfg, settings) |
| 55 | |
| 56 | r.Use(middleware.RequestID()) |
| 57 | r.Use(middleware.Recovery(log)) |
| 58 | if poolCache != nil { |
| 59 | r.Use(hostctx.Middleware(ctxRegistry, poolCache, redisCache, db, rdb, cfg.RegistryReloadSecret)) |
| 60 | } else { |
| 61 | r.Use(func(next http.Handler) http.Handler { |
| 62 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 63 | ctx := hostctx.WithDB(r.Context(), db) |
| 64 | ctx = hostctx.WithRedis(ctx, rdb) |
| 65 | next.ServeHTTP(w, r.WithContext(ctx)) |
| 66 | }) |
| 67 | }) |
| 68 | } |
| 69 | r.Use(middleware.CORS(resolved.CORSOrigin, corsOriginResolver(ctxRegistry))) |
| 70 | if resolved.TrustProxy { |
| 71 | r.Use(chimw.RealIP) |
| 72 | } |
| 73 | // Note: chimw.Timeout is NOT applied globally because it conflicts with |
| 74 | // WebSocket/SSE routes (hijacked connections). It writes a 503 to a |
| 75 | // hijacked ResponseWriter causing "WriteHeader on hijacked connection". |
| 76 | // Instead, timeout is applied per-group below, skipping WS routes. |
| 77 | |
| 78 | if poolCache != nil && cfg.RegistryReloadSecret != "" { |
| 79 | r.Post("/internal/reload-tenant", hostctx.ReloadHandler(poolCache, redisCache, cfg.RegistryReloadSecret)) |
| 80 | } |
| 81 | |
| 82 | usersStore := store.NewUsersStore(dbProvider) |
| 83 | permissionsStore := store.NewPermissionsStore(dbProvider) |
| 84 | |
| 85 | if cfg.EnablePprof { |
| 86 | // Pprof endpoints require admin authentication to prevent information leakage. |
| 87 | r.Group(func(r chi.Router) { |
| 88 | r.Use(middleware.RequirePermission("can_manage_settings", permissionsStore)) |
| 89 | r.Handle("/debug/pprof/*", http.HandlerFunc(pprof.Index)) |
| 90 | r.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline)) |
| 91 | r.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile)) |
| 92 | r.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol)) |
| 93 | r.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace)) |
| 94 | }) |
| 95 | } |
| 96 | dashboardPrefsStore := store.NewDashboardPreferencesStore(dbProvider) |
| 97 |
nothing calls this directly
no test coverage detected