Files
reflector-dashboard/svxdr/main.go
T

133 lines
4.6 KiB
Go

// svxdr — live-data daemon for Reflector Dashboard (nodelist + proxy + astmeta + xlink)
// Copyright (C) 2026 SA7AUX (Björn)
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU Affero General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any
// later version. See the LICENSE file for details.
// svxdr — the merged svx daemon: astmeta + nodelist + proxy + xlink in ONE
// process, each a subsystem goroutine, toggled per box by /etc/svxlink/svxdr.json.
//
// One binary, one config. A reflector box runs all subsystems; a pure AllStar
// bridge box runs only astmeta. Co-located subsystems share memory (no localhost HTTP hop);
// the HTTP talker-meta ingest stays for a REMOTE astmeta feeding this box.
//
// Phase 1 scaffold: config + module toggles + lifecycle (start / SIGHUP reload /
// graceful stop). Subsystems are stubs here and are folded in one at a time
// (proxy, then astmeta, then nodelist+xlink) so the existing standalone
// binaries keep running until cutover.
package main
import (
"context"
"flag"
"log"
"os"
"os/signal"
"sync"
"syscall"
"svxportal/svxdr/astmeta"
"svxportal/svxdr/config"
"svxportal/svxdr/nodelist"
"svxportal/svxdr/proxy"
)
// module is one subsystem. Run blocks until ctx is cancelled; it should return
// nil on clean shutdown. reload is called (best-effort) when the config file
// changes so a running module can pick up new settings without a restart.
type module struct {
name string
enabled func(*config.Config) bool
run func(ctx context.Context, cfg *config.Config) error
}
var modules = []module{
{"proxy", func(c *config.Config) bool { return c.Modules.Proxy }, proxy.Run},
{"astmeta", func(c *config.Config) bool { return c.Modules.Astmeta }, astmeta.Run},
{"nodelist", func(c *config.Config) bool { return c.Modules.Nodelist }, nodelist.Run},
// xlink is not a separate process — it runs inside nodelist, gated by
// modules.xlink in svxdr.json (see nodelist/svxdrcfg.go).
}
func main() {
log.SetFlags(log.LstdFlags)
path := flag.String("config", "", "path to svxdr.json (default "+config.DefaultPath+" or $SVXDR_CONFIG)")
fAstmeta := flag.Bool("astmeta", false, "run astmeta (overrides modules in config)")
fNodelist := flag.Bool("nodelist", false, "run nodelist (overrides modules in config)")
fProxy := flag.Bool("proxy", false, "run proxy (overrides modules in config)")
fXlink := flag.Bool("xlink", false, "run xlink inside nodelist (overrides modules in config)")
flag.Parse()
cfgPath := *path
if cfgPath == "" {
if p := os.Getenv("SVXDR_CONFIG"); p != "" {
cfgPath = p
} else {
cfgPath = config.DefaultPath
}
}
cfg, err := config.Load(cfgPath)
if err != nil {
log.Fatalf("svxdr: config %s: %v", cfgPath, err)
}
// If ANY module flag is passed, the flags fully define the set to run on this
// box (config "modules" is overridden) — handy for per-box units and testing.
if *fAstmeta || *fNodelist || *fProxy || *fXlink {
cfg.Modules = config.Modules{Astmeta: *fAstmeta, Nodelist: *fNodelist, Proxy: *fProxy, Xlink: *fXlink}
log.Printf("svxdr: module set overridden by flags: astmeta=%v nodelist=%v proxy=%v xlink=%v",
*fAstmeta, *fNodelist, *fProxy, *fXlink)
}
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
// SIGHUP -> reload config from disk and hand the new config to modules.
hup := make(chan os.Signal, 1)
signal.Notify(hup, syscall.SIGHUP)
var wg sync.WaitGroup
started := 0
for _, m := range modules {
if !m.enabled(cfg) {
continue
}
started++
wg.Add(1)
go func(m module) {
defer wg.Done()
log.Printf("svxdr: starting module %q", m.name)
if err := m.run(ctx, cfg); err != nil {
log.Printf("svxdr: module %q exited: %v", m.name, err)
}
}(m)
}
if started == 0 {
log.Printf("svxdr: no modules enabled in %s (set \"modules\": {...}) — idle", cfgPath)
} else {
log.Printf("svxdr: %d module(s) running from %s", started, cfgPath)
}
// lifecycle loop: reload on SIGHUP, exit on signal/context.
for {
select {
case <-ctx.Done():
log.Printf("svxdr: shutting down")
wg.Wait()
return
case <-hup:
nc, err := config.Load(cfgPath)
if err != nil {
log.Printf("svxdr: reload failed, keeping current config: %v", err)
continue
}
cfg = nc
log.Printf("svxdr: config reloaded from %s (modules pick up changes on next cycle)", cfgPath)
// NOTE: per-module live reload is wired as each subsystem is folded in.
}
}
}