From 61a85baf87d89fcc09f9469a113a2ddc982b0a24 Mon Sep 17 00:00:00 2001 From: bndw Date: Mon, 9 Mar 2026 08:01:02 -0700 Subject: feat: phase 2 relay implementation Implement the Axon relay as relay/ (module axon/relay). Includes: - WebSocket framing (RFC 6455, no external deps) in relay/websocket/ - Per-connection auth: challenge/response with ed25519 + allowlist check - Ingest pipeline: sig verify, dedup, ephemeral fanout, SQLite persistence - Subscription manager with prefix-matching filter fanout in relay/subscription/ - SQLite storage with WAL/cache config and UNION query builder in relay/storage/ - Graceful shutdown on SIGINT/SIGTERM - Filter/TagFilter types added to axon core package (required by relay) --- relay/config.go | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 relay/config.go (limited to 'relay/config.go') diff --git a/relay/config.go b/relay/config.go new file mode 100644 index 0000000..e432b85 --- /dev/null +++ b/relay/config.go @@ -0,0 +1,66 @@ +package main + +import ( + "encoding/hex" + "fmt" + "os" + + "gopkg.in/yaml.v3" +) + +// Config holds all relay configuration loaded from config.yaml. +type Config struct { + Addr string `yaml:"addr"` + DB string `yaml:"db"` + RelayURL string `yaml:"relay_url"` + Allowlist []string `yaml:"allowlist"` // hex-encoded pubkeys +} + +// DefaultConfig returns sensible defaults. +func DefaultConfig() Config { + return Config{ + Addr: ":8080", + DB: "axon.db", + RelayURL: "ws://localhost:8080", + } +} + +// AllowlistBytes decodes the hex pubkeys in c.Allowlist and returns them as +// raw byte slices. Returns an error if any entry is not valid 64-char hex. +func (c *Config) AllowlistBytes() ([][]byte, error) { + out := make([][]byte, 0, len(c.Allowlist)) + for _, h := range c.Allowlist { + b, err := hex.DecodeString(h) + if err != nil { + return nil, fmt.Errorf("config: allowlist entry %q is not valid hex: %w", h, err) + } + if len(b) != 32 { + return nil, fmt.Errorf("config: allowlist entry %q decoded to %d bytes, want 32", h, len(b)) + } + out = append(out, b) + } + return out, nil +} + +// LoadConfig reads and parses a YAML config file. Missing fields fall back to +// DefaultConfig values. +func LoadConfig(path string) (Config, error) { + cfg := DefaultConfig() + + f, err := os.Open(path) + if err != nil { + if os.IsNotExist(err) { + // No config file — use defaults. + return cfg, nil + } + return cfg, fmt.Errorf("config: open %q: %w", path, err) + } + defer f.Close() + + dec := yaml.NewDecoder(f) + dec.KnownFields(true) + if err := dec.Decode(&cfg); err != nil { + return cfg, fmt.Errorf("config: decode %q: %w", path, err) + } + return cfg, nil +} -- cgit v1.2.3