feat(sense): add reconciliation safety controls [T-012]
Harness governance / validate (push) Has been cancelled
Harness governance / validate (pull_request) Has been cancelled

This commit is contained in:
QiuSW
2026-08-07 23:00:03 +08:00
parent 677ed732f7
commit 12857fdf32
35 changed files with 2817 additions and 124 deletions
+110 -40
View File
@@ -7,39 +7,52 @@ import (
"net/url"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
)
const (
defaultHTTPAddress = "127.0.0.1:8080"
defaultDatabaseDriver = "sqlite"
defaultDatabaseDSN = "file:data/sense.db"
defaultMediaMTXURL = "http://127.0.0.1:9997"
defaultReconcilePeriod = 5 * time.Second
defaultProbePeriod = 10 * time.Second
defaultONVIFMode = "disabled"
defaultControlAuthMode = "static-sha256"
defaultHTTPAddress = "127.0.0.1:8080"
defaultDatabaseDriver = "sqlite"
postgresDatabaseDriver = "postgres"
defaultDatabaseDSN = "file:data/sense.db"
defaultMediaMTXURL = "http://127.0.0.1:9997"
defaultReconcilePeriod = 5 * time.Second
defaultReconcileLease = 30 * time.Second
defaultOperationTimeout = 20 * time.Second
defaultProbePeriod = 10 * time.Second
defaultOrphanScanPeriod = time.Minute
defaultONVIFMode = "disabled"
defaultControlAuthMode = "static-sha256"
)
var instanceIDPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$`)
type Config struct {
HTTPAddress string
AllowNonLoopback bool
DatabaseDriver string
DatabaseDSN string
MediaMTXURL string
ReconcileInterval time.Duration
ProbeInterval time.Duration
ONVIFMode string
RTSPRewriteHost string
RTSPRewritePort int
RTSPStripQuery bool
ControlAPIEnabled bool
ControlAuthMode string
ControlAuthFile string
ControlCursorKeyFile string
ControlAllowInsecureHTTP bool
HTTPAddress string
AllowNonLoopback bool
DatabaseDriver string
DatabaseDSN string
MediaMTXURL string
ReconcileInterval time.Duration
ReconcileLeaseDuration time.Duration
ReconcileOperationTimeout time.Duration
ProbeInterval time.Duration
InstanceID string
MetricsEnabled bool
OrphanScanEnabled bool
OrphanScanInterval time.Duration
ONVIFMode string
RTSPRewriteHost string
RTSPRewritePort int
RTSPStripQuery bool
ControlAPIEnabled bool
ControlAuthMode string
ControlAuthFile string
ControlCursorKeyFile string
ControlAllowInsecureHTTP bool
}
func Load() (Config, error) {
@@ -51,6 +64,14 @@ func Load() (Config, error) {
if err != nil {
return Config{}, err
}
reconcileLease, err := durationEnv("SENSE_RECONCILE_LEASE_DURATION", defaultReconcileLease)
if err != nil {
return Config{}, err
}
operationTimeout, err := durationEnv("SENSE_RECONCILE_OPERATION_TIMEOUT", defaultOperationTimeout)
if err != nil {
return Config{}, err
}
probePeriod, err := durationEnv("SENSE_PROBE_INTERVAL", defaultProbePeriod)
if err != nil {
return Config{}, err
@@ -71,24 +92,44 @@ func Load() (Config, error) {
if err != nil {
return Config{}, err
}
metricsEnabled, err := boolEnv("SENSE_METRICS_ENABLED", true)
if err != nil {
return Config{}, err
}
databaseDriver := stringEnv("SENSE_DB_DRIVER", defaultDatabaseDriver)
orphanDefault := strings.EqualFold(strings.TrimSpace(databaseDriver), postgresDatabaseDriver)
orphanEnabled, err := boolEnv("SENSE_ORPHAN_SCAN_ENABLED", orphanDefault)
if err != nil {
return Config{}, err
}
orphanPeriod, err := durationEnv("SENSE_ORPHAN_SCAN_INTERVAL", defaultOrphanScanPeriod)
if err != nil {
return Config{}, err
}
cfg := Config{
HTTPAddress: stringEnv("SENSE_HTTP_ADDR", defaultHTTPAddress),
AllowNonLoopback: allow,
DatabaseDriver: stringEnv("SENSE_DB_DRIVER", defaultDatabaseDriver),
DatabaseDSN: stringEnv("SENSE_DB_DSN", defaultDatabaseDSN),
MediaMTXURL: stringEnv("SENSE_MEDIAMTX_URL", defaultMediaMTXURL),
ReconcileInterval: reconcilePeriod,
ProbeInterval: probePeriod,
ONVIFMode: stringEnv("SENSE_ONVIF_MODE", defaultONVIFMode),
RTSPRewriteHost: stringEnv("SENSE_ONVIF_RTSP_REWRITE_HOST", ""),
RTSPRewritePort: rewritePort,
RTSPStripQuery: stripQuery,
ControlAPIEnabled: controlEnabled,
ControlAuthMode: stringEnv("SENSE_CONTROL_AUTH_MODE", defaultControlAuthMode),
ControlAuthFile: stringEnv("SENSE_CONTROL_AUTH_FILE", ""),
ControlCursorKeyFile: stringEnv("SENSE_CONTROL_CURSOR_KEY_FILE", ""),
ControlAllowInsecureHTTP: controlAllowInsecure,
HTTPAddress: stringEnv("SENSE_HTTP_ADDR", defaultHTTPAddress),
AllowNonLoopback: allow,
DatabaseDriver: databaseDriver,
DatabaseDSN: stringEnv("SENSE_DB_DSN", defaultDatabaseDSN),
MediaMTXURL: stringEnv("SENSE_MEDIAMTX_URL", defaultMediaMTXURL),
ReconcileInterval: reconcilePeriod,
ReconcileLeaseDuration: reconcileLease,
ReconcileOperationTimeout: operationTimeout,
ProbeInterval: probePeriod,
InstanceID: stringEnv("SENSE_INSTANCE_ID", ""),
MetricsEnabled: metricsEnabled,
OrphanScanEnabled: orphanEnabled,
OrphanScanInterval: orphanPeriod,
ONVIFMode: stringEnv("SENSE_ONVIF_MODE", defaultONVIFMode),
RTSPRewriteHost: stringEnv("SENSE_ONVIF_RTSP_REWRITE_HOST", ""),
RTSPRewritePort: rewritePort,
RTSPStripQuery: stripQuery,
ControlAPIEnabled: controlEnabled,
ControlAuthMode: stringEnv("SENSE_CONTROL_AUTH_MODE", defaultControlAuthMode),
ControlAuthFile: stringEnv("SENSE_CONTROL_AUTH_FILE", ""),
ControlCursorKeyFile: stringEnv("SENSE_CONTROL_CURSOR_KEY_FILE", ""),
ControlAllowInsecureHTTP: controlAllowInsecure,
}
if err := cfg.Validate(); err != nil {
return Config{}, err
@@ -129,6 +170,35 @@ func (c Config) Validate() error {
if c.ReconcileInterval <= 0 || c.ProbeInterval <= 0 {
return fmt.Errorf("loop intervals must be positive")
}
leaseDuration := c.ReconcileLeaseDuration
if leaseDuration == 0 {
leaseDuration = defaultReconcileLease
}
operationTimeout := c.ReconcileOperationTimeout
if operationTimeout == 0 {
operationTimeout = defaultOperationTimeout
}
if leaseDuration <= 0 || leaseDuration > 5*time.Minute {
return fmt.Errorf("SENSE_RECONCILE_LEASE_DURATION must be positive and at most 5m")
}
if operationTimeout <= 0 || operationTimeout >= leaseDuration {
return fmt.Errorf("SENSE_RECONCILE_OPERATION_TIMEOUT must be positive and shorter than the lease")
}
if c.InstanceID != "" && !instanceIDPattern.MatchString(c.InstanceID) {
return fmt.Errorf("invalid SENSE_INSTANCE_ID")
}
if c.OrphanScanEnabled {
if databaseDriver != postgresDatabaseDriver {
return fmt.Errorf("orphan scanning requires SENSE_DB_DRIVER=postgres")
}
orphanInterval := c.OrphanScanInterval
if orphanInterval == 0 {
orphanInterval = defaultOrphanScanPeriod
}
if orphanInterval < 10*time.Second {
return fmt.Errorf("SENSE_ORPHAN_SCAN_INTERVAL must be at least 10s")
}
}
if c.ONVIFMode != "" && c.ONVIFMode != "disabled" && c.ONVIFMode != "standard" {
return fmt.Errorf("SENSE_ONVIF_MODE must be disabled or standard")
}