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
|
||||
bin/
|
||||
strix
|
||||
main
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
@@ -37,4 +38,9 @@ Thumbs.db
|
||||
|
||||
# Temporary files
|
||||
tmp/
|
||||
temp/
|
||||
temp/
|
||||
*.dump
|
||||
*_output.txt
|
||||
|
||||
# Configuration (user-specific)
|
||||
strix.yaml
|
||||
@@ -39,6 +39,9 @@ make build
|
||||
|
||||
# Run the application
|
||||
make run
|
||||
|
||||
# The server will start on http://localhost:4567
|
||||
# Open your browser and navigate to http://localhost:4567
|
||||
```
|
||||
|
||||
## 📡 API Endpoints
|
||||
@@ -105,15 +108,54 @@ strix/
|
||||
|
||||
## 🛠️ 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
|
||||
STRIX_HOST=0.0.0.0 # Server host (default: 0.0.0.0)
|
||||
STRIX_PORT=8080 # Server port (default: 8080)
|
||||
STRIX_API_LISTEN=":4567" # Server listen address (overrides strix.yaml)
|
||||
STRIX_LOG_LEVEL=info # Log level: debug, info, warn, error
|
||||
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
|
||||
|
||||
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/utils/logger"
|
||||
"github.com/eduard256/Strix/webui"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -52,8 +53,7 @@ func main() {
|
||||
log.Info("starting Strix",
|
||||
slog.String("version", Version),
|
||||
slog.String("go_version", os.Getenv("GO_VERSION")),
|
||||
slog.String("host", cfg.Server.Host),
|
||||
slog.String("port", cfg.Server.Port),
|
||||
slog.String("listen", cfg.Server.Listen),
|
||||
)
|
||||
|
||||
// Check if ffprobe is available
|
||||
@@ -71,51 +71,39 @@ func main() {
|
||||
// Create Web UI server
|
||||
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{
|
||||
Addr: fmt.Sprintf("%s:%s", cfg.Server.Host, cfg.Server.Port),
|
||||
Handler: apiServer,
|
||||
Addr: cfg.Server.Listen,
|
||||
Handler: unifiedRouter,
|
||||
ReadTimeout: cfg.Server.ReadTimeout,
|
||||
WriteTimeout: cfg.Server.WriteTimeout,
|
||||
IdleTimeout: 120 * time.Second,
|
||||
}
|
||||
|
||||
// Create Web UI HTTP server
|
||||
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
|
||||
// Start server in goroutine
|
||||
go func() {
|
||||
log.Info("API server starting",
|
||||
log.Info("server starting",
|
||||
slog.String("address", httpServer.Addr),
|
||||
slog.String("api_version", "v1"),
|
||||
)
|
||||
|
||||
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Error("API 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)
|
||||
log.Error("server failed", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
// Print endpoints
|
||||
printEndpoints(cfg.Server.Host, cfg.Server.Port)
|
||||
printEndpoints(cfg.Server.Listen)
|
||||
|
||||
// Wait for interrupt signal
|
||||
quit := make(chan os.Signal, 1)
|
||||
@@ -128,19 +116,13 @@ func main() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Shutdown API server
|
||||
// Shutdown server
|
||||
if err := httpServer.Shutdown(ctx); err != nil {
|
||||
log.Error("API server shutdown failed", err)
|
||||
log.Error("server shutdown failed", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Shutdown Web UI server
|
||||
if err := webuiHTTPServer.Shutdown(ctx); err != nil {
|
||||
log.Error("Web UI server shutdown failed", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
log.Info("servers stopped gracefully")
|
||||
log.Info("server stopped gracefully")
|
||||
}
|
||||
|
||||
// checkFFProbe checks if ffprobe is available
|
||||
@@ -167,19 +149,38 @@ func checkFFProbe() error {
|
||||
return fmt.Errorf("ffprobe not found in common locations")
|
||||
}
|
||||
|
||||
// printEndpoints prints available API endpoints
|
||||
func printEndpoints(host, port string) {
|
||||
if host == "0.0.0.0" || host == "" {
|
||||
host = "localhost"
|
||||
// printEndpoints prints available endpoints
|
||||
func printEndpoints(listen string) {
|
||||
// Parse listen address to get host and port
|
||||
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)
|
||||
|
||||
webuiURL := fmt.Sprintf("http://%s:4567", host)
|
||||
|
||||
fmt.Println("\n🌐 Web Interface:")
|
||||
fmt.Println("────────────────────────────────────────────────")
|
||||
fmt.Printf(" Open in browser: %s\n", webuiURL)
|
||||
fmt.Printf(" Open in browser: %s\n", baseURL)
|
||||
fmt.Println("────────────────────────────────────────────────")
|
||||
|
||||
fmt.Println("\n🚀 API Endpoints:")
|
||||
@@ -215,4 +216,4 @@ func printEndpoints(host, port string) {
|
||||
fmt.Println("\n────────────────────────────────────────────────")
|
||||
fmt.Println("📚 Documentation: https://github.com/eduard256/Strix")
|
||||
fmt.Println("────────────────────────────────────────────────\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,4 +23,5 @@ require (
|
||||
golang.org/x/crypto v0.42.0 // indirect
|
||||
golang.org/x/sys v0.36.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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
+7
-23
@@ -118,31 +118,15 @@ func (s *Server) setupRoutes() {
|
||||
})
|
||||
})
|
||||
|
||||
// API version 1 routes
|
||||
s.router.Route("/api/v1", func(r chi.Router) {
|
||||
// Health check
|
||||
r.Get("/health", handlers.NewHealthHandler("1.0.0", s.logger).ServeHTTP)
|
||||
// API routes (mounted at /api/v1 in main.go)
|
||||
// Health check
|
||||
s.router.Get("/health", handlers.NewHealthHandler("1.0.0", s.logger).ServeHTTP)
|
||||
|
||||
// Camera search
|
||||
r.Post("/cameras/search", handlers.NewSearchHandler(s.searchEngine, s.logger).ServeHTTP)
|
||||
// Camera search
|
||||
s.router.Post("/cameras/search", handlers.NewSearchHandler(s.searchEngine, s.logger).ServeHTTP)
|
||||
|
||||
// Stream discovery (SSE)
|
||||
r.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"}`))
|
||||
})
|
||||
// Stream discovery (SSE)
|
||||
s.router.Post("/streams/discover", handlers.NewDiscoverHandler(s.scanner, s.sseServer, s.logger).ServeHTTP)
|
||||
}
|
||||
|
||||
// ServeHTTP implements http.Handler
|
||||
|
||||
+104
-18
@@ -1,10 +1,15 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Config holds application configuration
|
||||
@@ -17,8 +22,7 @@ type Config struct {
|
||||
|
||||
// ServerConfig contains HTTP server settings
|
||||
type ServerConfig struct {
|
||||
Host string
|
||||
Port string
|
||||
Listen string // Address to listen on (e.g., ":4567" or "0.0.0.0:4567")
|
||||
ReadTimeout time.Duration
|
||||
WriteTimeout time.Duration
|
||||
}
|
||||
@@ -35,17 +39,17 @@ type DatabaseConfig struct {
|
||||
|
||||
// ScannerConfig contains stream scanner settings
|
||||
type ScannerConfig struct {
|
||||
DefaultTimeout time.Duration
|
||||
MaxStreams int
|
||||
ModelSearchLimit int
|
||||
WorkerPoolSize int
|
||||
FFProbeTimeout time.Duration
|
||||
RetryAttempts int
|
||||
RetryDelay time.Duration
|
||||
DefaultTimeout time.Duration
|
||||
MaxStreams int
|
||||
ModelSearchLimit int
|
||||
WorkerPoolSize int
|
||||
FFProbeTimeout time.Duration
|
||||
RetryAttempts int
|
||||
RetryDelay time.Duration
|
||||
// Validation settings
|
||||
StrictValidation bool // Enable strict validation mode
|
||||
MinImageSize int // Minimum bytes for valid image (JPEG/PNG)
|
||||
MinVideoStreams int // Minimum video streams required
|
||||
StrictValidation bool // Enable strict validation mode
|
||||
MinImageSize int // Minimum bytes for valid image (JPEG/PNG)
|
||||
MinVideoStreams int // Minimum video streams required
|
||||
}
|
||||
|
||||
// LoggerConfig contains logging settings
|
||||
@@ -54,14 +58,20 @@ type LoggerConfig struct {
|
||||
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
|
||||
func Load() *Config {
|
||||
dataPath := getEnv("STRIX_DATA_PATH", "./data")
|
||||
|
||||
return &Config{
|
||||
cfg := &Config{
|
||||
Server: ServerConfig{
|
||||
Host: getEnv("STRIX_HOST", "0.0.0.0"),
|
||||
Port: getEnv("STRIX_PORT", "8080"),
|
||||
Listen: ":4567", // Default listen address
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
},
|
||||
@@ -83,14 +93,90 @@ func Load() *Config {
|
||||
RetryDelay: 500 * time.Millisecond,
|
||||
// Strict validation enabled by default
|
||||
StrictValidation: true,
|
||||
MinImageSize: 5120, // 5KB minimum for valid images
|
||||
MinVideoStreams: 1, // At least 1 video stream required
|
||||
MinImageSize: 5120, // 5KB minimum for valid images
|
||||
MinVideoStreams: 1, // At least 1 video stream required
|
||||
},
|
||||
Logger: LoggerConfig{
|
||||
Level: getEnv("STRIX_LOG_LEVEL", "info"),
|
||||
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
|
||||
@@ -126,4 +212,4 @@ func getEnv(key, defaultValue string) string {
|
||||
return value
|
||||
}
|
||||
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 {
|
||||
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) {
|
||||
const currentHost = window.location.hostname;
|
||||
this.baseURL = `http://${currentHost}:8080`;
|
||||
this.baseURL = '';
|
||||
} else {
|
||||
this.baseURL = baseURL;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
export class StreamDiscoveryAPI {
|
||||
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) {
|
||||
const currentHost = window.location.hostname;
|
||||
this.baseURL = `http://${currentHost}:8080`;
|
||||
this.baseURL = '';
|
||||
} else {
|
||||
this.baseURL = baseURL;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user