Two files say one fact, the machine's name, and nothing owned /etc/hostname. The name written is the operator's hostname setting, with no default: three of four machines call themselves something other than their mesh name, and renaming one is the operator's call. It takes effect at the next boot.
84 lines
3.1 KiB
Go
84 lines
3.1 KiB
Go
// hostname-tools (novox/hq ADR 0199, ADR 0223): the tools of the machine's names. One binary,
|
|
// launched by the machine's tool runtime and speaking MCP to it over stdio through the Go SDK (ADR
|
|
// 0193, ADR 0198): the node-hostname seat's three verbs — the hosts file's lines with whose each
|
|
// is, add an operator's line, remove one. They change the machine's file and nothing else; the
|
|
// controller holds none of it. /etc/hostname has no verb: it is the module's resource, set by the
|
|
// module's `hostname` setting.
|
|
//
|
|
// stdout is the MCP channel; everything this module says, it says on stderr.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
)
|
|
|
|
// Seat is the role this module holds.
|
|
const Seat = "node-hostname"
|
|
|
|
func main() {
|
|
if err := stdio.Serve("", tools(HostsFile{Path: HostsPath, Run: execRunner})); err != nil {
|
|
fmt.Fprintf(os.Stderr, "[hostname] %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func str(description string) map[string]any {
|
|
return map[string]any{"type": "string", "description": description}
|
|
}
|
|
|
|
func arg(a map[string]any, k string) string {
|
|
v, _ := a[k].(string)
|
|
return strings.TrimSpace(v)
|
|
}
|
|
|
|
var separators = regexp.MustCompile(`[\s,]+`)
|
|
|
|
// namesArg is the names given, separated by spaces or commas — or, from a caller that sends a list,
|
|
// the list.
|
|
func namesArg(a map[string]any) []string {
|
|
var raw []string
|
|
switch v := a["names"].(type) {
|
|
case string:
|
|
raw = separators.Split(v, -1)
|
|
case []any:
|
|
for _, n := range v {
|
|
if s, ok := n.(string); ok {
|
|
raw = append(raw, separators.Split(s, -1)...)
|
|
}
|
|
}
|
|
}
|
|
names := []string{}
|
|
for _, n := range raw {
|
|
if n != "" {
|
|
names = append(names, n)
|
|
}
|
|
}
|
|
return names
|
|
}
|
|
|
|
// verb is one of the seat's verbs: listed as `<seat>.<verb>`, so the runtime serves it on the seat's
|
|
// subject, as <node>/node-hostname.<verb>.
|
|
func verb(name, description string, input map[string]any, run func(a map[string]any) (any, error)) stdio.Tool {
|
|
return stdio.Tool{Name: Seat + "." + name, Description: description, Input: input, Run: run}
|
|
}
|
|
|
|
func tools(h HostsFile) []stdio.Tool {
|
|
ctx := context.Background()
|
|
return []stdio.Tool{
|
|
verb("entries", "Every line of this machine's /etc/hosts, each marked whose it is: the operator's, or the block of the module or tool that writes it.",
|
|
nil, func(map[string]any) (any, error) { return h.Entries() }),
|
|
verb("add", "Add one address and its names to the operator's lines of this machine's /etc/hosts — a name for this machine's own programs, not the mesh's. Nothing changes when they are already there.",
|
|
map[string]any{"address": str("the IPv4 or IPv6 address"), "names": str("the names for it, separated by spaces")},
|
|
func(a map[string]any) (any, error) { return h.Add(ctx, arg(a, "address"), namesArg(a)) }),
|
|
verb("remove", "Remove one name, or every line of one address, from the operator's lines of this machine's /etc/hosts. A line a module writes is refused, naming the module.",
|
|
map[string]any{"name": str("a host name, or an address to remove every line of")},
|
|
func(a map[string]any) (any, error) { return h.Remove(ctx, arg(a, "name")) }),
|
|
}
|
|
}
|