// NextNVR v0.1.0 — Main entry point // Lightweight NVR application for recording and monitoring IP cameras. package main import ( "fmt" "log" "os" "os/signal" "syscall" ) var ( // Version is set at build time via ldflags. Version = "0.1.0" // BuildTime is set at build time via ldflags. BuildTime = "unknown" ) func main() { log.SetFlags(log.LstdFlags | log.Lshortfile) log.Printf("NextNVR v%s (built %s)", Version, BuildTime) // Parse command-line flags. configPath := flagConfigPath() // Load or create configuration. cfg, err := LoadConfig(configPath) if err != nil { log.Printf("No existing config found at %s — starting in setup mode", configPath) cfg = DefaultConfig() // Don't auto-save; wait for the user to complete setup via the web UI. } log.Printf("Server: %s", cfg.Server.Port) log.Printf("Storage: %s (retention: %d days)", cfg.Storage.RecordingsPath, cfg.Storage.RetentionDays) log.Printf("Cameras configured: %d", len(cfg.Cameras)) if cfg.Go2RTC.Enabled { log.Printf("go2rtc: enabled on port %s (binary: %s)", cfg.Go2RTC.Port, cfg.Go2RTC.Binary) } // Signal handling for graceful shutdown. sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) // Initialize application. app := &App{Config: cfg, ConfigPath: configPath} // Start go2rtc if enabled. if cfg.Go2RTC.Enabled { go2rtc := NewGo2RTCManager(cfg.Go2RTC) if err := go2rtc.Start(cfg.Cameras); err != nil { log.Printf("go2rtc: start failed: %v", err) } app.go2rtc = go2rtc } // Start snapshot engine for all enabled cameras. snaps := NewSnapshotEngine(cfg.Storage) snaps.StartAll(cfg.Cameras) app.snapshots = snaps // Start the HTTP server (blocks). errCh := make(chan error, 1) go func() { errCh <- app.StartServer() }() select { case sig := <-sigCh: log.Printf("Received signal: %v — shutting down", sig) case err := <-errCh: if err != nil { log.Printf("Server error: %v", err) } } app.Shutdown() log.Println("NextNVR stopped.") } // flagConfigPath resolves the config file path from CLI flags. func flagConfigPath() string { // Look for --config flag in os.Args. for i, arg := range os.Args[1:] { if arg == "--config" && i+1 < len(os.Args[1:]) { return os.Args[i+2] // +2 because Args[0] is binary name, Args[1:] starts at index 1 } if len(arg) > 9 && arg[:9] == "--config=" { return arg[9:] } } // Default: look next to the binary, then /opt/nextnvr. if _, err := os.Stat("config.yaml"); err == nil { return "config.yaml" } if _, err := os.Stat("/opt/nextnvr/config.yaml"); err == nil { return "/opt/nextnvr/config.yaml" } return "config.yaml" } // App holds the application runtime state. type App struct { Config Config ConfigPath string server *Server go2rtc *Go2RTCManager snapshots *SnapshotEngine } // StartServer initializes and starts the HTTP server. func (a *App) StartServer() error { srv, err := NewServer(a.Config) if err != nil { return fmt.Errorf("creating server: %w", err) } a.server = srv return srv.ListenAndServe() } // Shutdown performs a graceful shutdown of all services. func (a *App) Shutdown() { if a.snapshots != nil { a.snapshots.StopAll() } if a.go2rtc != nil { a.go2rtc.Stop() } if a.server != nil { a.server.Close() } }