127 lines
3 KiB
Go
127 lines
3 KiB
Go
// 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 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
|
|
}
|
|
|
|
// 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.go2rtc != nil {
|
|
a.go2rtc.Stop()
|
|
}
|
|
if a.server != nil {
|
|
a.server.Close()
|
|
}
|
|
}
|