Files
mesh-catalog/modules/systemd-networkd/cmd/uplink-tools/manager.go
T
jochen c619a672a3
mesh/merge-gate pass: builds networkmanager, systemd-networkd, sent nowhere; no bus step; 4 wait(s) for a person; every machine composes with the change as…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group feat/node-uplink-verbs delivered: every member is delivered
Serve the uplink seat's verbs from both of its holders (hq ADR 0241)
What a machine resolves through had no tool: the resolver file and who
wrote it, and every link with its default route and the resolvers its
manager knows. Each holder serves the same two node-uplink verbs; the
reading is one text carried by both, held to it by a test, and only
asking the manager for a link's names is each holder's own.
2026-10-07 19:19:52 +02:00

68 lines
2.0 KiB
Go

package main
// systemd-networkd's word on each link's names: `networkctl status --all`, each link's block read for its
// DNS servers and search domains, which networkd prints whether or not systemd-resolved runs. A link
// networkd does not manage — a tunnel a VPN client raised with `ip`, the mesh's own — says none, and the
// links verb says the link without them.
import (
"context"
"regexp"
"strings"
)
// keyed is a line that names its key: a capitalised name of words, a colon, the value.
var keyed = regexp.MustCompile(`^([A-Z][A-Za-z0-9 ]*): (.*)$`)
func managerDNS(ctx context.Context) (map[string]LinkDNS, error) {
out, err := execRunner(ctx, "networkctl", "status", "--all", "--no-pager", "--no-legend")
if err != nil {
return nil, err
}
return parseNetworkctl(out), nil
}
// parseNetworkctl reads `networkctl status --all`: a block per link, headed "● <index>: <name>", whose
// "Key: value" lines continue on indented lines with no key.
func parseNetworkctl(out string) map[string]LinkDNS {
links := map[string]LinkDNS{}
device, key := "", ""
var cur LinkDNS
flush := func() {
if device != "" && (len(cur.Servers) > 0 || len(cur.Domains) > 0) {
cur.Manager = "systemd-networkd"
links[device] = cur
}
device, key, cur = "", "", LinkDNS{}
}
for _, line := range strings.Split(out, "\n") {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "●") {
flush()
if _, name, ok := strings.Cut(strings.TrimSpace(strings.TrimPrefix(trimmed, "●")), ": "); ok {
device = strings.TrimSpace(name)
}
continue
}
if trimmed == "" {
key = ""
continue
}
value := trimmed
if m := keyed.FindStringSubmatch(trimmed); m != nil {
key, value = m[1], strings.TrimSpace(m[2])
}
switch key {
case "DNS":
cur.Servers = append(cur.Servers, value)
case "Search Domains":
cur.Domains = append(cur.Domains, strings.Fields(value)...)
case "State":
cur.State = value
key = ""
}
}
flush()
return links
}