Add svxdr: the live-data backend (nodelist + proxy; astmeta/xlink optional), config example, systemd unit, INSTALL section 9
svxdr sources from the internal repo with AGPL headers; install/svxdr.json.example is a plain-reflector layout (reflector 8099 -> proxy 8090 -> nodelist 8098) with CHANGEME secrets and no site values. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
// svxdr — live-data daemon for SvxPortal (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.
|
||||
|
||||
// Package statusnorm holds the ONE rule that turns a reflector's shared sat-login
|
||||
// (e.g. KT4ROY-ASL, used by all its TG nodes) into the per-TG display callsign
|
||||
// (KT4ROY-ASL1..4). It is applied ONCE where a status is ingested (the proxy's
|
||||
// cache fill, and the federation crawl), so every consumer downstream reads
|
||||
// already-correct data — no per-view special-casing anywhere.
|
||||
package statusnorm
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// Normalize rewrites the callsign of every node in trunks[].nodes and
|
||||
// satellites[].nodes: rename(callsign, tg) returns the replacement, or "" to keep
|
||||
// it. All other data is preserved. Parse failure or no change -> raw unchanged.
|
||||
func Normalize(raw []byte, rename func(callsign string, tg int) string) []byte {
|
||||
var top map[string]json.RawMessage
|
||||
if json.Unmarshal(raw, &top) != nil {
|
||||
return raw
|
||||
}
|
||||
changed := false
|
||||
|
||||
// Top-level nodes[]: a group can present as a plain node (e.g. a DMR gateway),
|
||||
// not only under a trunk/satellite. Keyed by the node's login/identity, so rename
|
||||
// by that KEY (its callsign field may be the operator, not the login).
|
||||
if blk, ok := top["nodes"]; ok {
|
||||
var nm map[string]json.RawMessage
|
||||
if json.Unmarshal(blk, &nm) == nil {
|
||||
blkChanged := false
|
||||
for id, nRaw := range nm {
|
||||
var n map[string]json.RawMessage
|
||||
if json.Unmarshal(nRaw, &n) != nil {
|
||||
continue
|
||||
}
|
||||
cs, tg := callsignTG(n)
|
||||
repl := rename(id, tg) // match on the node's KEY (its login)
|
||||
if repl == "" || repl == cs {
|
||||
continue
|
||||
}
|
||||
b, _ := json.Marshal(repl)
|
||||
n["callsign"] = b
|
||||
if enc, err := json.Marshal(n); err == nil {
|
||||
nm[id] = enc
|
||||
blkChanged = true
|
||||
}
|
||||
}
|
||||
if blkChanged {
|
||||
if enc, err := json.Marshal(nm); err == nil {
|
||||
top["nodes"] = enc
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, key := range []string{"trunks", "satellites"} {
|
||||
blk, ok := top[key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
var group map[string]json.RawMessage
|
||||
if json.Unmarshal(blk, &group) != nil {
|
||||
continue
|
||||
}
|
||||
blkChanged := false
|
||||
for id, objRaw := range group {
|
||||
var obj map[string]json.RawMessage
|
||||
if json.Unmarshal(objRaw, &obj) != nil {
|
||||
continue
|
||||
}
|
||||
nodesRaw, ok := obj["nodes"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
var nodes []json.RawMessage
|
||||
if json.Unmarshal(nodesRaw, &nodes) != nil {
|
||||
continue
|
||||
}
|
||||
nodesChanged := false
|
||||
for i, nRaw := range nodes {
|
||||
var n map[string]json.RawMessage
|
||||
if json.Unmarshal(nRaw, &n) != nil {
|
||||
continue
|
||||
}
|
||||
cs, tg := callsignTG(n)
|
||||
if cs == "" {
|
||||
continue
|
||||
}
|
||||
repl := rename(cs, tg)
|
||||
if repl == "" || repl == cs {
|
||||
continue
|
||||
}
|
||||
b, _ := json.Marshal(repl)
|
||||
n["callsign"] = b
|
||||
if enc, err := json.Marshal(n); err == nil {
|
||||
nodes[i] = enc
|
||||
nodesChanged = true
|
||||
}
|
||||
}
|
||||
if nodesChanged {
|
||||
if enc, err := json.Marshal(nodes); err == nil {
|
||||
obj["nodes"] = enc
|
||||
if oenc, err := json.Marshal(obj); err == nil {
|
||||
group[id] = oenc
|
||||
blkChanged = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if blkChanged {
|
||||
if enc, err := json.Marshal(group); err == nil {
|
||||
top[key] = enc
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !changed {
|
||||
return raw
|
||||
}
|
||||
if enc, err := json.Marshal(top); err == nil {
|
||||
return enc
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
func callsignTG(n map[string]json.RawMessage) (string, int) {
|
||||
var cs string
|
||||
if v, ok := n["callsign"]; ok {
|
||||
_ = json.Unmarshal(v, &cs)
|
||||
}
|
||||
var tg int
|
||||
if v, ok := n["tg"]; ok {
|
||||
_ = json.Unmarshal(v, &tg)
|
||||
}
|
||||
return cs, tg
|
||||
}
|
||||
Reference in New Issue
Block a user