Add unified port configuration system
- Unified API and WebUI on single configurable port (default: 4567) - Added strix.yaml configuration file support (go2rtc-style format) - Environment variable STRIX_API_LISTEN overrides config file - Port validation and source logging - Relative URLs in frontend for automatic port detection - Removed separate server instances - Cleaned up temporary files and updated .gitignore - Updated documentation with configuration examples
This commit is contained in:
+7
-1
@@ -1,6 +1,7 @@
|
|||||||
# Binaries
|
# Binaries
|
||||||
bin/
|
bin/
|
||||||
strix
|
strix
|
||||||
|
main
|
||||||
*.exe
|
*.exe
|
||||||
*.exe~
|
*.exe~
|
||||||
*.dll
|
*.dll
|
||||||
@@ -37,4 +38,9 @@ Thumbs.db
|
|||||||
|
|
||||||
# Temporary files
|
# Temporary files
|
||||||
tmp/
|
tmp/
|
||||||
temp/
|
temp/
|
||||||
|
*.dump
|
||||||
|
*_output.txt
|
||||||
|
|
||||||
|
# Configuration (user-specific)
|
||||||
|
strix.yaml
|
||||||
@@ -39,6 +39,9 @@ make build
|
|||||||
|
|
||||||
# Run the application
|
# Run the application
|
||||||
make run
|
make run
|
||||||
|
|
||||||
|
# The server will start on http://localhost:4567
|
||||||
|
# Open your browser and navigate to http://localhost:4567
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📡 API Endpoints
|
## 📡 API Endpoints
|
||||||
@@ -105,15 +108,54 @@ strix/
|
|||||||
|
|
||||||
## 🛠️ Configuration
|
## 🛠️ Configuration
|
||||||
|
|
||||||
Environment variables:
|
Strix can be configured via `strix.yaml` file or environment variables.
|
||||||
|
|
||||||
|
### Configuration File (strix.yaml)
|
||||||
|
|
||||||
|
Create a `strix.yaml` file in the same directory as the binary:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# API Server Configuration
|
||||||
|
api:
|
||||||
|
listen: ":4567" # Format: ":port" or "host:port"
|
||||||
|
```
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
```yaml
|
||||||
|
api:
|
||||||
|
listen: ":4567" # All interfaces, port 4567 (default)
|
||||||
|
# listen: "127.0.0.1:4567" # Localhost only
|
||||||
|
# listen: ":8080" # Custom port
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
Environment variables override config file values:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
STRIX_HOST=0.0.0.0 # Server host (default: 0.0.0.0)
|
STRIX_API_LISTEN=":4567" # Server listen address (overrides strix.yaml)
|
||||||
STRIX_PORT=8080 # Server port (default: 8080)
|
|
||||||
STRIX_LOG_LEVEL=info # Log level: debug, info, warn, error
|
STRIX_LOG_LEVEL=info # Log level: debug, info, warn, error
|
||||||
STRIX_LOG_FORMAT=json # Log format: json, text
|
STRIX_LOG_FORMAT=json # Log format: json, text
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Configuration Priority
|
||||||
|
|
||||||
|
1. **Environment variable** `STRIX_API_LISTEN` (highest priority)
|
||||||
|
2. **Config file** `strix.yaml`
|
||||||
|
3. **Default value** `:4567` (lowest priority)
|
||||||
|
|
||||||
|
### Quick Start with Custom Port
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Using environment variable
|
||||||
|
STRIX_API_LISTEN=":8080" ./strix
|
||||||
|
|
||||||
|
# Or using config file
|
||||||
|
cp strix.yaml.example strix.yaml
|
||||||
|
# Edit strix.yaml, then:
|
||||||
|
./strix
|
||||||
|
```
|
||||||
|
|
||||||
## 📊 Camera Database
|
## 📊 Camera Database
|
||||||
|
|
||||||
The system includes a comprehensive database of camera models:
|
The system includes a comprehensive database of camera models:
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
% Total % Received % Xferd Average Speed Time Time Time Current
|
|
||||||
Dload Upload Total Spent Left Speed
|
|
||||||
|
|
||||||
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
|
|
||||||
100 324 0 157 100 167 209 222 --:--:-- --:--:-- --:--:-- 431
|
|
||||||
100 324 0 157 100 167 89 95 0:00:01 0:00:01 --:--:-- 184
|
|
||||||
100 324 0 157 100 167 57 60 0:00:02 0:00:02 --:--:-- 117
|
|
||||||
100 1075 0 908 100 167 242 44 0:00:03 0:00:03 --:--:-- 286
|
|
||||||
100 1752 0 1585 100 167 296 31 0:00:05 0:00:05 --:--:-- 327
|
|
||||||
100 1752 0 1585 100 167 249 26 0:00:06 0:00:06 --:--:-- 255
|
|
||||||
100 1816 0 1649 100 167 244 24 0:00:06 0:00:06 --:--:-- 298
|
|
||||||
100 1816 0 1649 100 167 212 21 0:00:07 0:00:07 --:--:-- 298
|
|
||||||
100 2163 0 1996 100 167 228 19 0:00:08 0:00:08 --:--:-- 218
|
|
||||||
100 2163 0 1996 100 167 204 17 0:00:09 0:00:09 --:--:-- 93
|
|
||||||
100 2227 0 2060 100 167 191 15 0:00:11 0:00:10 0:00:01 107
|
|
||||||
100 2227 0 2060 100 167 175 14 0:00:11 0:00:11 --:--:-- 82
|
|
||||||
100 2291 0 2124 100 167 166 13 0:00:12 0:00:12 --:--:-- 95
|
|
||||||
100 2291 0 2124 100 167 154 12 0:00:13 0:00:13 --:--:-- 25
|
|
||||||
100 2291 0 2124 100 167 143 11 0:00:15 0:00:14 0:00:01 25
|
|
||||||
100 2353 0 2186 100 167 138 10 0:00:16 0:00:15 0:00:01 25
|
|
||||||
100 2353 0 2186 100 167 130 9 0:00:18 0:00:16 0:00:02 25
|
|
||||||
100 2353 0 2186 100 167 123 9 0:00:18 0:00:17 0:00:01 12
|
|
||||||
100 2353 0 2186 100 167 116 8 0:00:20 0:00:18 0:00:02 12
|
|
||||||
100 2353 0 2186 100 167 110 8 0:00:20 0:00:19 0:00:01 12
|
|
||||||
100 2353 0 2186 100 167 105 8 0:00:20 0:00:20 --:--:-- 0
|
|
||||||
100 2353 0 2186 100 167 100 7 0:00:23 0:00:21 0:00:02 0
|
|
||||||
100 2353 0 2186 100 167 96 7 0:00:23 0:00:22 0:00:01 0
|
|
||||||
100 2353 0 2186 100 167 92 7 0:00:23 0:00:23 --:--:-- 0
|
|
||||||
100 2353 0 2186 100 167 88 6 0:00:27 0:00:24 0:00:03 0
|
|
||||||
100 2353 0 2186 100 167 84 6 0:00:27 0:00:25 0:00:02 0
|
|
||||||
100 2353 0 2186 100 167 81 6 0:00:27 0:00:26 0:00:01 0
|
|
||||||
100 2353 0 2186 100 167 78 6 0:00:27 0:00:27 --:--:-- 0
|
|
||||||
100 2353 0 2186 100 167 76 5 0:00:33 0:00:28 0:00:05 0
|
|
||||||
100 2353 0 2186 100 167 73 5 0:00:33 0:00:29 0:00:04 0
|
|
||||||
100 2353 0 2186 100 167 71 5 0:00:33 0:00:30 0:00:03 0
|
|
||||||
100 2353 0 2186 100 167 68 5 0:00:33 0:00:31 0:00:02 0
|
|
||||||
100 2353 0 2186 100 167 66 5 0:00:33 0:00:32 0:00:01 0
|
|
||||||
100 2353 0 2186 100 167 64 4 0:00:41 0:00:33 0:00:08 0
|
|
||||||
100 2353 0 2186 100 167 64 4 0:00:41 0:00:33 0:00:08 0
|
|
||||||
curl: (18) transfer closed with outstanding read data remaining
|
|
||||||
event: scan_started
|
|
||||||
data: {"max_streams":5,"model":"NVR","target":"10.0.20.110","timeout":60}
|
|
||||||
|
|
||||||
event: progress
|
|
||||||
data: {"tested":0,"found":0,"remaining":959}
|
|
||||||
|
|
||||||
event: stream_found
|
|
||||||
data: {"stream":{"url":"http://admin:5f8a5b7s9m@10.0.20.110/bubble/live?ch=0\u0026stream=1","type":"BUBBLE","protocol":"http","port":0,"working":true,"has_audio":false,"test_time_ms":11294107,"metadata":{"content_type":"video/bubble","stream_type":"main"}}}
|
|
||||||
|
|
||||||
event: progress
|
|
||||||
data: {"tested":226,"found":1,"remaining":733}
|
|
||||||
|
|
||||||
event: stream_found
|
|
||||||
data: {"stream":{"url":"http://admin:5f8a5b7s9m@10.0.20.110/bubble/live?ch=0\u0026stream=0","type":"BUBBLE","protocol":"http","port":0,"working":true,"has_audio":false,"test_time_ms":212128072,"metadata":{"content_type":"video/bubble","stream_type":"main"}}}
|
|
||||||
|
|
||||||
event: progress
|
|
||||||
data: {"tested":232,"found":2,"remaining":727}
|
|
||||||
+48
-47
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/eduard256/Strix/internal/config"
|
"github.com/eduard256/Strix/internal/config"
|
||||||
"github.com/eduard256/Strix/internal/utils/logger"
|
"github.com/eduard256/Strix/internal/utils/logger"
|
||||||
"github.com/eduard256/Strix/webui"
|
"github.com/eduard256/Strix/webui"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -52,8 +53,7 @@ func main() {
|
|||||||
log.Info("starting Strix",
|
log.Info("starting Strix",
|
||||||
slog.String("version", Version),
|
slog.String("version", Version),
|
||||||
slog.String("go_version", os.Getenv("GO_VERSION")),
|
slog.String("go_version", os.Getenv("GO_VERSION")),
|
||||||
slog.String("host", cfg.Server.Host),
|
slog.String("listen", cfg.Server.Listen),
|
||||||
slog.String("port", cfg.Server.Port),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check if ffprobe is available
|
// Check if ffprobe is available
|
||||||
@@ -71,51 +71,39 @@ func main() {
|
|||||||
// Create Web UI server
|
// Create Web UI server
|
||||||
webuiServer := webui.NewServer(log)
|
webuiServer := webui.NewServer(log)
|
||||||
|
|
||||||
// Create API HTTP server
|
// Create unified router combining API and WebUI
|
||||||
|
unifiedRouter := chi.NewRouter()
|
||||||
|
|
||||||
|
// Mount API routes at /api/v1/*
|
||||||
|
unifiedRouter.Mount("/api/v1", apiServer.GetRouter())
|
||||||
|
|
||||||
|
// Mount WebUI routes at /* (serves everything else including root)
|
||||||
|
unifiedRouter.Mount("/", webuiServer.GetRouter())
|
||||||
|
|
||||||
|
// Create unified HTTP server
|
||||||
httpServer := &http.Server{
|
httpServer := &http.Server{
|
||||||
Addr: fmt.Sprintf("%s:%s", cfg.Server.Host, cfg.Server.Port),
|
Addr: cfg.Server.Listen,
|
||||||
Handler: apiServer,
|
Handler: unifiedRouter,
|
||||||
ReadTimeout: cfg.Server.ReadTimeout,
|
ReadTimeout: cfg.Server.ReadTimeout,
|
||||||
WriteTimeout: cfg.Server.WriteTimeout,
|
WriteTimeout: cfg.Server.WriteTimeout,
|
||||||
IdleTimeout: 120 * time.Second,
|
IdleTimeout: 120 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create Web UI HTTP server
|
// Start server in goroutine
|
||||||
webuiHTTPServer := &http.Server{
|
|
||||||
Addr: fmt.Sprintf("%s:4567", cfg.Server.Host),
|
|
||||||
Handler: webuiServer,
|
|
||||||
ReadTimeout: cfg.Server.ReadTimeout,
|
|
||||||
WriteTimeout: cfg.Server.WriteTimeout,
|
|
||||||
IdleTimeout: 120 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start API server in goroutine
|
|
||||||
go func() {
|
go func() {
|
||||||
log.Info("API server starting",
|
log.Info("server starting",
|
||||||
slog.String("address", httpServer.Addr),
|
slog.String("address", httpServer.Addr),
|
||||||
slog.String("api_version", "v1"),
|
slog.String("api_version", "v1"),
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
log.Error("API server failed", err)
|
log.Error("server failed", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Start Web UI server in goroutine
|
|
||||||
go func() {
|
|
||||||
log.Info("Web UI server starting",
|
|
||||||
slog.String("address", webuiHTTPServer.Addr),
|
|
||||||
)
|
|
||||||
|
|
||||||
if err := webuiHTTPServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
||||||
log.Error("Web UI server failed", err)
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Print endpoints
|
// Print endpoints
|
||||||
printEndpoints(cfg.Server.Host, cfg.Server.Port)
|
printEndpoints(cfg.Server.Listen)
|
||||||
|
|
||||||
// Wait for interrupt signal
|
// Wait for interrupt signal
|
||||||
quit := make(chan os.Signal, 1)
|
quit := make(chan os.Signal, 1)
|
||||||
@@ -128,19 +116,13 @@ func main() {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
// Shutdown API server
|
// Shutdown server
|
||||||
if err := httpServer.Shutdown(ctx); err != nil {
|
if err := httpServer.Shutdown(ctx); err != nil {
|
||||||
log.Error("API server shutdown failed", err)
|
log.Error("server shutdown failed", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown Web UI server
|
log.Info("server stopped gracefully")
|
||||||
if err := webuiHTTPServer.Shutdown(ctx); err != nil {
|
|
||||||
log.Error("Web UI server shutdown failed", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("servers stopped gracefully")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkFFProbe checks if ffprobe is available
|
// checkFFProbe checks if ffprobe is available
|
||||||
@@ -167,19 +149,38 @@ func checkFFProbe() error {
|
|||||||
return fmt.Errorf("ffprobe not found in common locations")
|
return fmt.Errorf("ffprobe not found in common locations")
|
||||||
}
|
}
|
||||||
|
|
||||||
// printEndpoints prints available API endpoints
|
// printEndpoints prints available endpoints
|
||||||
func printEndpoints(host, port string) {
|
func printEndpoints(listen string) {
|
||||||
if host == "0.0.0.0" || host == "" {
|
// Parse listen address to get host and port
|
||||||
host = "localhost"
|
host := "localhost"
|
||||||
|
port := "4567"
|
||||||
|
|
||||||
|
// Extract port from listen address
|
||||||
|
if len(listen) > 0 {
|
||||||
|
if listen[0] == ':' {
|
||||||
|
port = listen[1:]
|
||||||
|
} else {
|
||||||
|
// Parse host:port format
|
||||||
|
for i := len(listen) - 1; i >= 0; i-- {
|
||||||
|
if listen[i] == ':' {
|
||||||
|
port = listen[i+1:]
|
||||||
|
if i > 0 {
|
||||||
|
host = listen[:i]
|
||||||
|
if host == "0.0.0.0" || host == "" {
|
||||||
|
host = "localhost"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
baseURL := fmt.Sprintf("http://%s:%s", host, port)
|
baseURL := fmt.Sprintf("http://%s:%s", host, port)
|
||||||
|
|
||||||
webuiURL := fmt.Sprintf("http://%s:4567", host)
|
|
||||||
|
|
||||||
fmt.Println("\n🌐 Web Interface:")
|
fmt.Println("\n🌐 Web Interface:")
|
||||||
fmt.Println("────────────────────────────────────────────────")
|
fmt.Println("────────────────────────────────────────────────")
|
||||||
fmt.Printf(" Open in browser: %s\n", webuiURL)
|
fmt.Printf(" Open in browser: %s\n", baseURL)
|
||||||
fmt.Println("────────────────────────────────────────────────")
|
fmt.Println("────────────────────────────────────────────────")
|
||||||
|
|
||||||
fmt.Println("\n🚀 API Endpoints:")
|
fmt.Println("\n🚀 API Endpoints:")
|
||||||
@@ -215,4 +216,4 @@ func printEndpoints(host, port string) {
|
|||||||
fmt.Println("\n────────────────────────────────────────────────")
|
fmt.Println("\n────────────────────────────────────────────────")
|
||||||
fmt.Println("📚 Documentation: https://github.com/eduard256/Strix")
|
fmt.Println("📚 Documentation: https://github.com/eduard256/Strix")
|
||||||
fmt.Println("────────────────────────────────────────────────\n")
|
fmt.Println("────────────────────────────────────────────────\n")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,4 +23,5 @@ require (
|
|||||||
golang.org/x/crypto v0.42.0 // indirect
|
golang.org/x/crypto v0.42.0 // indirect
|
||||||
golang.org/x/sys v0.36.0 // indirect
|
golang.org/x/sys v0.36.0 // indirect
|
||||||
golang.org/x/text v0.29.0 // indirect
|
golang.org/x/text v0.29.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -67,5 +67,6 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
|||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
+7
-23
@@ -118,31 +118,15 @@ func (s *Server) setupRoutes() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// API version 1 routes
|
// API routes (mounted at /api/v1 in main.go)
|
||||||
s.router.Route("/api/v1", func(r chi.Router) {
|
// Health check
|
||||||
// Health check
|
s.router.Get("/health", handlers.NewHealthHandler("1.0.0", s.logger).ServeHTTP)
|
||||||
r.Get("/health", handlers.NewHealthHandler("1.0.0", s.logger).ServeHTTP)
|
|
||||||
|
|
||||||
// Camera search
|
// Camera search
|
||||||
r.Post("/cameras/search", handlers.NewSearchHandler(s.searchEngine, s.logger).ServeHTTP)
|
s.router.Post("/cameras/search", handlers.NewSearchHandler(s.searchEngine, s.logger).ServeHTTP)
|
||||||
|
|
||||||
// Stream discovery (SSE)
|
// Stream discovery (SSE)
|
||||||
r.Post("/streams/discover", handlers.NewDiscoverHandler(s.scanner, s.sseServer, s.logger).ServeHTTP)
|
s.router.Post("/streams/discover", handlers.NewDiscoverHandler(s.scanner, s.sseServer, s.logger).ServeHTTP)
|
||||||
})
|
|
||||||
|
|
||||||
// Root health check
|
|
||||||
s.router.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Write([]byte(`{"name":"Strix","version":"1.0.0","api":"v1"}`))
|
|
||||||
})
|
|
||||||
|
|
||||||
// 404 handler
|
|
||||||
s.router.NotFound(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
w.Write([]byte(`{"error":"Not found"}`))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP implements http.Handler
|
// ServeHTTP implements http.Handler
|
||||||
|
|||||||
+104
-18
@@ -1,10 +1,15 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config holds application configuration
|
// Config holds application configuration
|
||||||
@@ -17,8 +22,7 @@ type Config struct {
|
|||||||
|
|
||||||
// ServerConfig contains HTTP server settings
|
// ServerConfig contains HTTP server settings
|
||||||
type ServerConfig struct {
|
type ServerConfig struct {
|
||||||
Host string
|
Listen string // Address to listen on (e.g., ":4567" or "0.0.0.0:4567")
|
||||||
Port string
|
|
||||||
ReadTimeout time.Duration
|
ReadTimeout time.Duration
|
||||||
WriteTimeout time.Duration
|
WriteTimeout time.Duration
|
||||||
}
|
}
|
||||||
@@ -35,17 +39,17 @@ type DatabaseConfig struct {
|
|||||||
|
|
||||||
// ScannerConfig contains stream scanner settings
|
// ScannerConfig contains stream scanner settings
|
||||||
type ScannerConfig struct {
|
type ScannerConfig struct {
|
||||||
DefaultTimeout time.Duration
|
DefaultTimeout time.Duration
|
||||||
MaxStreams int
|
MaxStreams int
|
||||||
ModelSearchLimit int
|
ModelSearchLimit int
|
||||||
WorkerPoolSize int
|
WorkerPoolSize int
|
||||||
FFProbeTimeout time.Duration
|
FFProbeTimeout time.Duration
|
||||||
RetryAttempts int
|
RetryAttempts int
|
||||||
RetryDelay time.Duration
|
RetryDelay time.Duration
|
||||||
// Validation settings
|
// Validation settings
|
||||||
StrictValidation bool // Enable strict validation mode
|
StrictValidation bool // Enable strict validation mode
|
||||||
MinImageSize int // Minimum bytes for valid image (JPEG/PNG)
|
MinImageSize int // Minimum bytes for valid image (JPEG/PNG)
|
||||||
MinVideoStreams int // Minimum video streams required
|
MinVideoStreams int // Minimum video streams required
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoggerConfig contains logging settings
|
// LoggerConfig contains logging settings
|
||||||
@@ -54,14 +58,20 @@ type LoggerConfig struct {
|
|||||||
Format string // "text" or "json"
|
Format string // "text" or "json"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// yamlConfig represents the structure of strix.yaml
|
||||||
|
type yamlConfig struct {
|
||||||
|
API struct {
|
||||||
|
Listen string `yaml:"listen"`
|
||||||
|
} `yaml:"api"`
|
||||||
|
}
|
||||||
|
|
||||||
// Load returns configuration with defaults
|
// Load returns configuration with defaults
|
||||||
func Load() *Config {
|
func Load() *Config {
|
||||||
dataPath := getEnv("STRIX_DATA_PATH", "./data")
|
dataPath := getEnv("STRIX_DATA_PATH", "./data")
|
||||||
|
|
||||||
return &Config{
|
cfg := &Config{
|
||||||
Server: ServerConfig{
|
Server: ServerConfig{
|
||||||
Host: getEnv("STRIX_HOST", "0.0.0.0"),
|
Listen: ":4567", // Default listen address
|
||||||
Port: getEnv("STRIX_PORT", "8080"),
|
|
||||||
ReadTimeout: 30 * time.Second,
|
ReadTimeout: 30 * time.Second,
|
||||||
WriteTimeout: 30 * time.Second,
|
WriteTimeout: 30 * time.Second,
|
||||||
},
|
},
|
||||||
@@ -83,14 +93,90 @@ func Load() *Config {
|
|||||||
RetryDelay: 500 * time.Millisecond,
|
RetryDelay: 500 * time.Millisecond,
|
||||||
// Strict validation enabled by default
|
// Strict validation enabled by default
|
||||||
StrictValidation: true,
|
StrictValidation: true,
|
||||||
MinImageSize: 5120, // 5KB minimum for valid images
|
MinImageSize: 5120, // 5KB minimum for valid images
|
||||||
MinVideoStreams: 1, // At least 1 video stream required
|
MinVideoStreams: 1, // At least 1 video stream required
|
||||||
},
|
},
|
||||||
Logger: LoggerConfig{
|
Logger: LoggerConfig{
|
||||||
Level: getEnv("STRIX_LOG_LEVEL", "info"),
|
Level: getEnv("STRIX_LOG_LEVEL", "info"),
|
||||||
Format: getEnv("STRIX_LOG_FORMAT", "json"),
|
Format: getEnv("STRIX_LOG_FORMAT", "json"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load from strix.yaml if exists
|
||||||
|
configSource := "default"
|
||||||
|
if err := loadYAML(cfg); err == nil {
|
||||||
|
configSource = "strix.yaml"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Environment variable overrides everything
|
||||||
|
if envListen := os.Getenv("STRIX_API_LISTEN"); envListen != "" {
|
||||||
|
cfg.Server.Listen = envListen
|
||||||
|
configSource = "environment variable STRIX_API_LISTEN"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate listen address
|
||||||
|
if err := validateListen(cfg.Server.Listen); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "ERROR: Invalid listen address '%s': %v\n", cfg.Server.Listen, err)
|
||||||
|
fmt.Fprintf(os.Stderr, "Using default: :4567\n")
|
||||||
|
cfg.Server.Listen = ":4567"
|
||||||
|
configSource = "default (validation failed)"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log configuration source
|
||||||
|
fmt.Printf("INFO: API listen address '%s' loaded from %s\n", cfg.Server.Listen, configSource)
|
||||||
|
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadYAML attempts to load configuration from strix.yaml
|
||||||
|
func loadYAML(cfg *Config) error {
|
||||||
|
data, err := os.ReadFile("./strix.yaml")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var yamlCfg yamlConfig
|
||||||
|
if err := yaml.Unmarshal(data, &yamlCfg); err != nil {
|
||||||
|
return fmt.Errorf("failed to parse strix.yaml: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply yaml configuration
|
||||||
|
if yamlCfg.API.Listen != "" {
|
||||||
|
cfg.Server.Listen = yamlCfg.API.Listen
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateListen validates the listen address format and port range
|
||||||
|
func validateListen(listen string) error {
|
||||||
|
if listen == "" {
|
||||||
|
return fmt.Errorf("listen address cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the listen address
|
||||||
|
parts := strings.Split(listen, ":")
|
||||||
|
if len(parts) < 2 {
|
||||||
|
return fmt.Errorf("invalid format, expected ':port' or 'host:port', got '%s'", listen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get port (last part)
|
||||||
|
portStr := parts[len(parts)-1]
|
||||||
|
if portStr == "" {
|
||||||
|
return fmt.Errorf("port cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate port number
|
||||||
|
port, err := strconv.Atoi(portStr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid port number '%s': %w", portStr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if port < 1 || port > 65535 {
|
||||||
|
return fmt.Errorf("port %d out of valid range (1-65535)", port)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetupLogger configures the global logger
|
// SetupLogger configures the global logger
|
||||||
@@ -126,4 +212,4 @@ func getEnv(key, defaultValue string) string {
|
|||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Strix Configuration Example
|
||||||
|
# Copy this file to strix.yaml and modify as needed
|
||||||
|
|
||||||
|
# API Server Configuration
|
||||||
|
api:
|
||||||
|
# Listen address in format ":port" or "host:port"
|
||||||
|
# Default: ":4567"
|
||||||
|
listen: ":4567"
|
||||||
|
|
||||||
|
# Examples:
|
||||||
|
# listen: ":4567" # Listen on all interfaces, port 4567 (default)
|
||||||
|
# listen: "0.0.0.0:4567" # Explicitly listen on all interfaces
|
||||||
|
# listen: "127.0.0.1:4567" # Listen only on localhost (secure local-only access)
|
||||||
|
# listen: ":8080" # Custom port on all interfaces
|
||||||
|
|
||||||
|
# Configuration Priority (highest to lowest):
|
||||||
|
# 1. Environment variable: STRIX_API_LISTEN
|
||||||
|
# 2. This file: strix.yaml
|
||||||
|
# 3. Default value: :4567
|
||||||
|
|
||||||
|
# Quick Start:
|
||||||
|
# 1. Copy this file: cp strix.yaml.example strix.yaml
|
||||||
|
# 2. Edit the listen address if needed
|
||||||
|
# 3. Run strix: ./strix
|
||||||
|
# 4. Or set via environment: STRIX_API_LISTEN=":8080" ./strix
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
export class CameraSearchAPI {
|
export class CameraSearchAPI {
|
||||||
constructor(baseURL = null) {
|
constructor(baseURL = null) {
|
||||||
// Auto-detect API URL based on current host
|
// Use relative URLs since API and UI are on the same port
|
||||||
if (!baseURL) {
|
if (!baseURL) {
|
||||||
const currentHost = window.location.hostname;
|
this.baseURL = '';
|
||||||
this.baseURL = `http://${currentHost}:8080`;
|
|
||||||
} else {
|
} else {
|
||||||
this.baseURL = baseURL;
|
this.baseURL = baseURL;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
export class StreamDiscoveryAPI {
|
export class StreamDiscoveryAPI {
|
||||||
constructor(baseURL = null) {
|
constructor(baseURL = null) {
|
||||||
// Auto-detect API URL based on current host
|
// Use relative URLs since API and UI are on the same port
|
||||||
if (!baseURL) {
|
if (!baseURL) {
|
||||||
const currentHost = window.location.hostname;
|
this.baseURL = '';
|
||||||
this.baseURL = `http://${currentHost}:8080`;
|
|
||||||
} else {
|
} else {
|
||||||
this.baseURL = baseURL;
|
this.baseURL = baseURL;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user