orama/pkg/cli/monitor/collector.go
anonpenguin23 fa826f0d00 refactor(sandbox): integrate rootwallet SSH keys
- replace standalone sandbox keys with "sandbox/root" vault entry
- update inspector config to use vault targets (no passwords/keys)
- make sandbox default active environment
- add vault helpers and tests for remotessh
2026-03-10 05:25:41 +02:00

175 lines
4.3 KiB
Go

package monitor
import (
"context"
"encoding/json"
"fmt"
"sync"
"time"
"github.com/DeBrosOfficial/network/pkg/cli/production/report"
"github.com/DeBrosOfficial/network/pkg/cli/remotessh"
"github.com/DeBrosOfficial/network/pkg/cli/sandbox"
"github.com/DeBrosOfficial/network/pkg/inspector"
)
// CollectorConfig holds configuration for the collection pipeline.
type CollectorConfig struct {
ConfigPath string
Env string
NodeFilter string
Timeout time.Duration
}
// CollectOnce runs `sudo orama node report --json` on all matching nodes
// in parallel and returns a ClusterSnapshot.
func CollectOnce(ctx context.Context, cfg CollectorConfig) (*ClusterSnapshot, error) {
nodes, cleanup, err := loadNodes(cfg)
if err != nil {
return nil, err
}
defer cleanup()
timeout := cfg.Timeout
if timeout == 0 {
timeout = 30 * time.Second
}
start := time.Now()
snap := &ClusterSnapshot{
Environment: cfg.Env,
CollectedAt: start,
Nodes: make([]CollectionStatus, len(nodes)),
}
var wg sync.WaitGroup
for i, node := range nodes {
wg.Add(1)
go func(idx int, n inspector.Node) {
defer wg.Done()
snap.Nodes[idx] = collectNodeReport(ctx, n, timeout)
}(i, node)
}
wg.Wait()
snap.Duration = time.Since(start)
snap.Alerts = DeriveAlerts(snap)
return snap, nil
}
// collectNodeReport SSHes into a single node and parses the JSON report.
func collectNodeReport(ctx context.Context, node inspector.Node, timeout time.Duration) CollectionStatus {
nodeCtx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
start := time.Now()
result := inspector.RunSSH(nodeCtx, node, "sudo orama node report --json")
cs := CollectionStatus{
Node: node,
Duration: time.Since(start),
Retries: result.Retries,
}
if !result.OK() {
cs.Error = fmt.Errorf("SSH failed (exit %d): %s", result.ExitCode, truncate(result.Stderr, 200))
return cs
}
var rpt report.NodeReport
if err := json.Unmarshal([]byte(result.Stdout), &rpt); err != nil {
cs.Error = fmt.Errorf("parse report JSON: %w (first 200 bytes: %s)", err, truncate(result.Stdout, 200))
return cs
}
// Enrich with node metadata from nodes.conf
if rpt.Hostname == "" {
rpt.Hostname = node.Host
}
rpt.PublicIP = node.Host
cs.Report = &rpt
return cs
}
func filterByHost(nodes []inspector.Node, host string) []inspector.Node {
var filtered []inspector.Node
for _, n := range nodes {
if n.Host == host {
filtered = append(filtered, n)
}
}
return filtered
}
func truncate(s string, maxLen int) string {
if len(s) <= maxLen {
return s
}
return s[:maxLen] + "..."
}
// loadNodes resolves the node list and SSH keys based on the environment.
// For "sandbox", nodes are loaded from the active sandbox state file with
// the sandbox SSH key already set. For other environments, nodes come from
// nodes.conf and use wallet-derived SSH keys.
func loadNodes(cfg CollectorConfig) ([]inspector.Node, func(), error) {
noop := func() {}
if cfg.Env == "sandbox" {
return loadSandboxNodes(cfg)
}
nodes, err := inspector.LoadNodes(cfg.ConfigPath)
if err != nil {
return nil, noop, fmt.Errorf("load nodes: %w", err)
}
nodes = inspector.FilterByEnv(nodes, cfg.Env)
if cfg.NodeFilter != "" {
nodes = filterByHost(nodes, cfg.NodeFilter)
}
if len(nodes) == 0 {
return nil, noop, fmt.Errorf("no nodes found for env %q", cfg.Env)
}
cleanup, err := remotessh.PrepareNodeKeys(nodes)
if err != nil {
return nil, noop, fmt.Errorf("prepare SSH keys: %w", err)
}
return nodes, cleanup, nil
}
// loadSandboxNodes loads nodes from the active sandbox state file.
func loadSandboxNodes(cfg CollectorConfig) ([]inspector.Node, func(), error) {
noop := func() {}
sbxCfg, err := sandbox.LoadConfig()
if err != nil {
return nil, noop, fmt.Errorf("load sandbox config: %w", err)
}
state, err := sandbox.FindActiveSandbox()
if err != nil {
return nil, noop, fmt.Errorf("find active sandbox: %w", err)
}
if state == nil {
return nil, noop, fmt.Errorf("no active sandbox found")
}
nodes := state.ToNodes(sbxCfg.SSHKey.VaultTarget)
if cfg.NodeFilter != "" {
nodes = filterByHost(nodes, cfg.NodeFilter)
}
if len(nodes) == 0 {
return nil, noop, fmt.Errorf("no nodes found for sandbox %q", state.Name)
}
cleanup, err := remotessh.PrepareNodeKeys(nodes)
if err != nil {
return nil, noop, fmt.Errorf("prepare SSH keys: %w", err)
}
return nodes, cleanup, nil
}