Files
mesh-catalog/modules/localization/cmd/localization-tools/main.go
T
jochen a8d308d440 localization: locale, time zone and console keymap as one module
One machine ran another time zone and a German console keymap with no record
why. The module writes /etc/locale.conf and /etc/vconsole.conf whole and sets
the zone through a run-once step of its own Go binary (timedatectl, read
back): /etc/localtime is a link the mesh may not write (hq ADR 0012) and a
module may not declare an action (ADR 0005). Tools: localization_get,
_time_zone, _locales, _keymaps (to-be 42 Phase 1).
2026-10-04 12:50:20 +02:00

107 lines
3.9 KiB
Go

// localization's tools bundle (novox/hq to-be 42 Phase 1, research 026/05), and its step.
//
// Served by the node's runtime over MCP on stdio through the Go SDK (ADR 0188, ADR 0193) when it is
// started with no arguments. Started as `localization-tools set-time-zone <zone>` it is instead the
// module's step, which the host runs once as root for every version of the bundle (localization.go
// says why the time zone is a step and not a file).
package main
import (
"context"
"fmt"
"os"
stdio "git.novox.be/novox/mesh-sdk/go"
)
// binaryName is what the build names this bundle's executable: the manifest's `binary`.
const binaryName = "localization-tools"
func bg() context.Context { return context.Background() }
func main() {
m := ThisMachine()
if len(os.Args) > 1 {
if len(os.Args) != 3 || os.Args[1] != "set-time-zone" {
fmt.Fprintf(os.Stderr, "usage: %s [set-time-zone <zone>]\n", binaryName)
os.Exit(2)
}
said, err := m.SetTimeZoneStep(os.Args[2])
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println(said)
return
}
// An empty name serves as the module the runtime names (MESH_SERVED_MODULE): localization.
if err := stdio.Serve("", tools(m)); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func tools(m *Machine) []stdio.Tool {
return []stdio.Tool{
{
Name: "localization_get",
Description: "The machine's locale (LANG and every LC_* localed reports), whether LANG is generated, its time zone, " +
"whether the clock keeps local time, whether NTP is on and synchronised, the console keymap and the X11 keyboard " +
"settings — and for each of locale, zone and keymap whether it is what the module declares " +
"(en_US.UTF-8, Europe/Brussels, us).",
Input: schema(map[string]any{}),
Run: func(map[string]any) (any, error) { return m.Get() },
},
{
Name: "localization_time_zone",
Description: "The time zone: read it; list the zones matching a word (match); or set one (set), through the time " +
"daemon with sudo -n, read back after. The module declares Europe/Brussels and its step sets it again " +
"whenever the module's bundle changes, which the answer says when another zone is set.",
Input: schema(map[string]any{
"set": map[string]any{"type": "string", "description": "a zone to set, as timedatectl list-timezones names it (optional)"},
"match": map[string]any{"type": "string", "description": "list the zones whose name holds this word (optional)"},
}),
Run: func(args map[string]any) (any, error) {
set, err := text(args, "set", false)
if err != nil {
return nil, err
}
match, err := text(args, "match", false)
if err != nil {
return nil, err
}
return m.TimeZone(set, match)
},
},
{
Name: "localization_locales",
Description: "The locales: generated (locale -a), enabled in /etc/locale.gen, the LANG of /etc/locale.conf and " +
"whether it is generated, and how many glibc can generate — listed when a word narrows them (match).",
Input: schema(map[string]any{
"match": map[string]any{"type": "string", "description": "list the generatable locales whose name holds this word (optional)"},
}),
Run: func(args map[string]any) (any, error) {
match, err := text(args, "match", false)
if err != nil {
return nil, err
}
return m.Locales(match)
},
},
{
Name: "localization_keymaps",
Description: "The console keymap in force and the keymaps this machine has (localectl list-keymaps), narrowed to a word when given; at most 500 listed.",
Input: schema(map[string]any{
"match": map[string]any{"type": "string", "description": "list only keymaps whose name holds this word (optional)"},
}),
Run: func(args map[string]any) (any, error) {
match, err := text(args, "match", false)
if err != nil {
return nil, err
}
return m.Keymaps(match)
},
},
}
}