config: the LAN scanner joins house.go (V-410)

Pure move, with one comment corrected on the way. NetScanConfig.Rate said
"0 ⇒ 50"; netscan.DefaultRate is 100 and has been since the package was
written, and deploy/mavend.json sets 100 explicitly. The three sibling
defaults now name the constant they come from rather than restating a
number that can drift again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-06 01:16:57 +04:00
parent 231a4e00f6
commit 7414ef4c39
2 changed files with 70 additions and 56 deletions
+3 -56
View File
@@ -24,7 +24,6 @@ import (
"github.com/kami/maven/internal/delivery/ntfysink"
"github.com/kami/maven/internal/delivery/telegramsink"
"github.com/kami/maven/internal/morning"
"github.com/kami/maven/internal/netscan"
"github.com/kami/maven/internal/update"
"github.com/kami/maven/internal/vision"
"github.com/robfig/cron/v3"
@@ -285,51 +284,6 @@ type Config struct {
NetScan *NetScanConfig `json:"netscan,omitempty"`
}
// NetScanConfig — the LAN scanner block (Vikunja #257). Dark until
// `"enabled": true`.
//
// The important field is Subnets, and it is the ONLY source of a scan target.
// Nothing an utterance, a router or a scanned host says can widen or move the
// range: internal/netscan.Scanner.Scan takes no target argument at all. Each
// subnet must be private and no larger than netscan.MaxPrefixHosts addresses
// (a /22), enforced at config load rather than at the first spoken scan.
type NetScanConfig struct {
// Subnets — CIDRs to scan, "192.168.1.0/24".
Subnets []string `json:"subnets,omitempty"`
// Ports — TCP ports to try per host. Empty ⇒ 22, 80, 443, 8080.
Ports []int `json:"ports,omitempty"`
// Timeout — per-connection budget. 0 ⇒ 400ms.
Timeout Duration `json:"timeout,omitempty"`
// Rate — connections per second across the whole scan. 0 ⇒ 50. Low on
// purpose: a scan should look like background traffic, not a portscan.
Rate int `json:"rate,omitempty"`
// MaxHosts — cap on addresses probed per scan. 0 ⇒ 256.
MaxHosts int `json:"max_hosts,omitempty"`
// Enabled — false (the default) keeps a written block dark.
Enabled bool `json:"enabled,omitempty"`
}
// NetScanner maps the config block onto the netscan package's own type.
// ok=false when absent or disabled, so validation and daemon wiring cannot
// drift on the mapping.
func (c *Config) NetScanner() (netscan.Config, bool) {
if c.NetScan == nil || !c.NetScan.Enabled {
return netscan.Config{}, false
}
return netscan.Config{
Subnets: c.NetScan.Subnets,
Ports: c.NetScan.Ports,
Timeout: time.Duration(c.NetScan.Timeout),
Rate: c.NetScan.Rate,
MaxHosts: c.NetScan.MaxHosts,
}, true
}
// PraxisConfig — maven's connection to the Praxis attention service.
type PraxisConfig struct {
// URL — the Praxis HTTP API base URL (e.g. "http://localhost:9742").
@@ -1286,10 +1240,7 @@ func (c *Config) applyDefaults() {
c.normaliseSmartHome()
// Same rule for the scanner.
if c.NetScan != nil && !c.NetScan.Enabled {
c.NetScan = nil
}
c.normaliseNetScan()
// Same rule for the crawler: a block that neither answers on demand nor
// watches anything has nothing to do, so it is normalised to "off".
@@ -1445,12 +1396,8 @@ func (c *Config) validate() error {
if err := c.validateSmartHome(); err != nil {
return err
}
// A scanner pointed at the public internet, or at a /8, fails here rather
// than after the packets have already left.
if nc, ok := c.NetScanner(); ok {
if err := netscan.Validate(nc); err != nil {
return err
}
if err := c.validateNetScan(); err != nil {
return err
}
// A media dir that cannot be created, or a vision endpoint that is a typo,
// used to be logged at wiring time and the capability just stayed off. A
+67
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"time"
"github.com/kami/maven/internal/netscan"
"github.com/kami/maven/internal/smarthome"
)
@@ -107,3 +108,69 @@ func (c *Config) validateSmartHome() error {
}
return smarthome.Validate(hc)
}
// NetScanConfig — the LAN scanner block (Vikunja #257). Dark until
// `"enabled": true`.
//
// The important field is Subnets, and it is the ONLY source of a scan target.
// Nothing an utterance, a router or a scanned host says can widen or move the
// range: internal/netscan.Scanner.Scan takes no target argument at all. Each
// subnet must be private and no larger than netscan.MaxPrefixHosts addresses
// (a /22), enforced at config load rather than at the first spoken scan.
type NetScanConfig struct {
// Subnets — CIDRs to scan, "192.168.1.0/24".
Subnets []string `json:"subnets,omitempty"`
// Ports — TCP ports to try per host. Empty ⇒ netscan.DefaultPorts
// (22, 80, 443, 8080).
Ports []int `json:"ports,omitempty"`
// Timeout — per-connection budget. 0 ⇒ netscan.DefaultTimeout (400ms).
Timeout Duration `json:"timeout,omitempty"`
// Rate — connections per second across the whole scan. 0 ⇒
// netscan.DefaultRate (100). Low on purpose: a scan should look like
// background traffic, not a portscan.
Rate int `json:"rate,omitempty"`
// MaxHosts — cap on addresses probed per scan. 0 ⇒ netscan.DefaultMaxHosts
// (256).
MaxHosts int `json:"max_hosts,omitempty"`
// Enabled — false (the default) keeps a written block dark.
Enabled bool `json:"enabled,omitempty"`
}
// NetScanner maps the config block onto the netscan package's own type.
// ok=false when absent or disabled, so validation and daemon wiring cannot
// drift on the mapping.
func (c *Config) NetScanner() (netscan.Config, bool) {
if c.NetScan == nil || !c.NetScan.Enabled {
return netscan.Config{}, false
}
return netscan.Config{
Subnets: c.NetScan.Subnets,
Ports: c.NetScan.Ports,
Timeout: time.Duration(c.NetScan.Timeout),
Rate: c.NetScan.Rate,
MaxHosts: c.NetScan.MaxHosts,
}, true
}
// normaliseNetScan applies the block's defaults. Same rule as the house: not
// enabled is the same as no block at all.
func (c *Config) normaliseNetScan() {
if c.NetScan != nil && !c.NetScan.Enabled {
c.NetScan = nil
}
}
// validateNetScan fails a scanner pointed at the public internet, or at a /8,
// here rather than after the packets have already left.
func (c *Config) validateNetScan() error {
nc, ok := c.NetScanner()
if !ok {
return nil
}
return netscan.Validate(nc)
}