diff --git a/cmd/server/main.go b/cmd/server/main.go index 9525f1d..8503e62 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -2,11 +2,20 @@ package main import ( + "context" "fmt" "log" + "net/http" "os" + "os/signal" + "syscall" "time" + "github.com/cubecraft/remoterig/internal/auth" + "github.com/cubecraft/remoterig/internal/db" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" "gopkg.in/yaml.v3" ) @@ -37,14 +46,63 @@ func main() { log.Fatalf("Failed to load config: %v", err) } - // Print config values - log.Printf("Database: %s", cfg.DBPath) - log.Printf("API key set: %t", cfg.APIKey != "") - log.Printf("Server port: %s", cfg.Port) - log.Printf("MQTT broker: %s", cfg.MQTT.Broker) - log.Printf("Platform: %s (max %d cameras)", cfg.Platform.Type, cfg.Platform.MaxCameras) + // Open database + db, err := db.Open(cfg.DBPath) + if err != nil { + log.Fatalf("Failed to open database: %v", err) + } + defer db.Close() + log.Printf("Database open: %s", cfg.DBPath) - log.Println("RemoteRig hub ready") + // Set up router + r := chi.NewRouter() + r.Use(middleware.RequestID) + r.Use(middleware.RealIP) + r.Use(middleware.Logger) + r.Use(middleware.Recoverer) + r.Use(middleware.Timeout(cfg.WriteTimeout)) + + // Health check (no auth) + r.Get("/health", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"status":"ok"}`)) + }) + + // API routes (auth required if API key is configured) + r.Mount("/api/v1", auth.Middleware(cfg.APIKey)(apiRouter(db))) + + // Create server + httpServer := &http.Server{ + Addr: ":" + cfg.Port, + Handler: r, + ReadTimeout: cfg.ReadTimeout, + WriteTimeout: cfg.WriteTimeout, + IdleTimeout: cfg.IdleTimeout, + } + + // Graceful shutdown + go func() { + sigInt := make(chan os.Signal, 1) + signal.Notify(sigInt, syscall.SIGINT, syscall.SIGTERM) + <-sigInt + log.Println("Shutting down server...") + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + httpServer.Shutdown(ctx) + }() + + log.Printf("Server listening on port %s", cfg.Port) + if err := httpServer.ListenAndServe(); err != nil { + log.Fatalf("Server failed: %v", err) + } +} + +// apiRouter creates the API route tree. +func apiRouter(database *db.DB) http.Handler { + r := chi.NewRouter() + // TODO: register handler routes here + // Example: r.Get("/cameras", handlers.ListCameras(database)) + return r } func loadConfig(path string) (*Config, error) { diff --git a/go.mod b/go.mod index a4be2b7..38b71cf 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/cubecraft/remoterig go 1.25.0 require ( + github.com/go-chi/chi/v5 v5.2.5 gopkg.in/yaml.v3 v3.0.1 modernc.org/sqlite v1.50.1 ) diff --git a/go.sum b/go.sum index 4abd016..438a07b 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= +github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= diff --git a/internal/auth/middleware.go b/internal/auth/middleware.go index 9bd04e9..1c5ad6b 100644 --- a/internal/auth/middleware.go +++ b/internal/auth/middleware.go @@ -3,7 +3,6 @@ package auth import ( "net/http" - "strings" ) // Middleware returns a Chi middleware that validates the X-API-Key header. diff --git a/main b/main new file mode 100755 index 0000000..20978d7 Binary files /dev/null and b/main differ