diff --git a/auth.go b/auth.go index 4013159..e9ab2ee 100644 --- a/auth.go +++ b/auth.go @@ -224,11 +224,23 @@ func (s *Server) authRequired(next http.Handler) http.Handler { }) } -// viewerOnly restricts access to live wall only (port 8090). +// viewerOnly restricts access when viewer port is disabled, or to live wall only. func (s *Server) viewerOnly(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Check if viewer port is enabled in config. + s.mu.RLock() + enabled := s.appConfig.Server.ViewerPort != "" && s.appConfig.Server.ViewerPort != ":0" + s.mu.RUnlock() + + if !enabled { + msg := `NextNVR

🔒 Local View Disabled

The owner has not enabled local network access.

Go to admin login →
` + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(http.StatusForbidden) + w.Write([]byte(msg)) + return + } + path := r.URL.Path - // Allow live wall assets and recordings. if path == "/" || path == "/index.html" || strings.HasPrefix(path, "/app.js") || strings.HasPrefix(path, "/style.css") || @@ -241,7 +253,6 @@ func (s *Server) viewerOnly(next http.Handler) http.Handler { next.ServeHTTP(w, r) return } - // Block everything else (settings, playback API, config). jsonResponse(w, http.StatusForbidden, APIResponse{Error: "viewer access only"}) }) } diff --git a/config.go b/config.go index 3477d4b..3f400bb 100644 --- a/config.go +++ b/config.go @@ -85,7 +85,7 @@ func DefaultConfig() Config { Server: ServerConfig{ Port: ":8080", BindHost: "0.0.0.0", - ViewerPort: "", + ViewerPort: ":8090", }, Storage: StorageConfig{ RecordingsPath: "/mnt/recordings", diff --git a/config.json.sample b/config.json.sample index 3e352cf..e469649 100644 --- a/config.json.sample +++ b/config.json.sample @@ -2,7 +2,7 @@ "server": { "port": ":8080", "bind_host": "0.0.0.0", - "viewer_port": "" + "viewer_port": ":8090" }, "storage": { "recordings_path": "/mnt/recordings", diff --git a/main.go b/main.go index f6077ad..0f8749a 100644 --- a/main.go +++ b/main.go @@ -90,18 +90,6 @@ func main() { app.server.mu.RUnlock() // Handle viewer server toggle via viewer_port. - viewerOn := liveCfg.Server.ViewerPort != "" && liveCfg.Server.ViewerPort != ":0" - if viewerOn && app.viewerSrv == nil { - go func() { - v, err := app.server.StartViewerServer() - if err != nil { log.Printf("Viewer server: %v", err) } - app.viewerSrv = v - }() - } else if !viewerOn && app.viewerSrv != nil { - log.Println("Stopping viewer server...") - app.viewerSrv.Close() - app.viewerSrv = nil - } if liveCfg.Go2RTC.Enabled { app.go2rtc = NewGo2RTCManager(liveCfg.Go2RTC) app.go2rtc.Start(liveCfg.Cameras) @@ -120,17 +108,12 @@ func main() { }() // Start viewer server only if enabled. - if cfg.Server.ViewerPort != "" && cfg.Server.ViewerPort != ":0" { - go func() { - v, err := app.server.StartViewerServer() - if err != nil { - log.Printf("Viewer server: %v", err) - } - app.viewerSrv = v - }() - } else { - log.Println("Viewer server: disabled (viewer_port empty)") - } + // Start viewer server always — access controlled by config at request level. + go func() { + v, err := app.server.StartViewerServer() + if err != nil { log.Printf("Viewer server: %v", err) } + app.viewerSrv = v + }() select { case sig := <-sigCh: