// docker's Go tools bundle (novox/hq ADR 0188, ADR 0193): a process the node's tool runtime launches // and speaks MCP over stdio to, through the Go SDK. It answers for every container on this machine — // the mesh's and every other — and for the runtime's images, networks, volumes, events and // configuration. It runs as the operator account (ADR 0175 §4); docker.go says how it reaches the // daemon's socket. The host applies the module's resources; these tools answer about the runtime. package main import ( "context" "fmt" "math" "os" "strings" stdio "git.novox.be/novox/mesh-sdk/go" ) func main() { // An empty name serves as the module the runtime names (MESH_SERVED_MODULE): docker. if err := stdio.Serve("", tools(NewClient())); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } var containerArg = map[string]any{"type": "string", "description": "the container's name or id"} func tools(c *Client) []stdio.Tool { ctx := context.Background() act := func(verb, description string) stdio.Tool { return stdio.Tool{ Name: "docker_" + verb, Description: description, Input: map[string]any{"container": containerArg}, Run: func(args map[string]any) (any, error) { ref, err := text(args, "container") if err != nil { return nil, err } return c.Act(ctx, verb, ref) }, } } return []stdio.Tool{ { Name: "docker_list", Description: "Every container on this machine — the mesh's and every other — with its image, state, health, restarts, " + "published ports, mounts, compose project, and mesh_held/held_by (the assignment that holds it).", Input: map[string]any{ "held": map[string]any{"type": "string", "enum": []string{"all", "mesh", "other"}, "description": "whose: all (default), the mesh's, or the others"}, "state": map[string]any{"type": "string", "description": "only containers in this state (running, exited, created, restarting, paused, dead)"}, "match": map[string]any{"type": "string", "description": "only containers whose name or image contains this"}, }, Run: func(args map[string]any) (any, error) { list, err := c.Containers(ctx, optional(args, "held"), optional(args, "state"), optional(args, "match")) if err != nil { return nil, err } return map[string]any{"count": len(list), "containers": list}, nil }, }, { Name: "docker_inspect", Description: "One container whole, as docker inspects it, with mesh_held; its environment's values are left out (names kept), because that is where a container's secrets are.", Input: map[string]any{"container": containerArg}, Run: func(args map[string]any) (any, error) { ref, err := text(args, "container") if err != nil { return nil, err } return c.Inspect(ctx, ref) }, }, { Name: "docker_logs", Description: "The last lines one container wrote, both streams merged in order, each with its timestamp (default 200, at most 2000 lines; a line is cut at 4 KiB). " + "A secret the container was given that it printed is shown as [redacted: ], and so is a password inside a URI.", Input: map[string]any{ "container": containerArg, "lines": map[string]any{"type": "integer", "description": "how many lines from the end (default 200, at most 2000)"}, "since": map[string]any{"type": "string", "description": "only lines since then: a duration such as 30m or 2h, or a time"}, }, Run: func(args map[string]any) (any, error) { ref, err := text(args, "container") if err != nil { return nil, err } n, err := bounded(args, "lines", 200, 2000) if err != nil { return nil, err } return c.Logs(ctx, ref, n, optional(args, "since")) }, }, { Name: "docker_secrets_in_logs", Description: "Which containers printed a secret they were given into their own log — by container, module and the secret's name, never its value: " + "each one's recent lines compared with the values of its environment named like a secret and the passwords in its URIs, and any URI carrying a password. " + "A finding is a secret to rotate once the program stops printing it (novox/hq issue 268).", Input: map[string]any{ "held": map[string]any{"type": "string", "enum": []string{"all", "mesh", "other"}, "description": "whose: the mesh's (default), every container, or the others"}, "lines": map[string]any{"type": "integer", "description": "how many lines from the end of each log (default 5000, at most 50000)"}, }, Run: func(args map[string]any) (any, error) { n, err := bounded(args, "lines", 5000, 50000) if err != nil { return nil, err } held := optional(args, "held") if held == "" { held = "mesh" } return c.SecretsInLogs(ctx, held, n) }, }, { Name: "docker_secrets_in_events", Description: "Which secrets exec command lines carried in a window ending now (default the last 60 minutes, at most 24 hours) — the runtime records every exec's command line in its events, " + "so a password passed as an argument is kept there for anyone who may ask it. By container, module and the secret's name, never its value. " + "A finding is code to change (hand the secret over as a file or on stdin) and then a secret to rotate (novox/hq issue 282).", Input: map[string]any{ "minutes": map[string]any{"type": "integer", "description": "how far back (default 60, at most 1440)"}, }, Run: func(args map[string]any) (any, error) { minutes, err := bounded(args, "minutes", 60, 1440) if err != nil { return nil, err } return c.SecretsInEvents(ctx, minutes) }, }, { Name: "docker_stats", Description: "What the running containers use now — CPU, memory, network and disk I/O, processes — the heaviest by memory first; or one container's.", Input: map[string]any{"container": map[string]any{"type": "string", "description": "one container (optional)"}}, Run: func(args map[string]any) (any, error) { stats, err := c.Stats(ctx, optional(args, "container")) if err != nil { return nil, err } return map[string]any{"count": len(stats), "containers": stats}, nil }, }, act("start", "Start one container. A container the mesh holds is started too, and the answer says the node-engine restores what its declaration says at its next apply."), act("stop", "Stop one container (ten seconds, then killed). For a container the mesh holds, the answer says the node-engine will start it again at its next apply if its declaration says running."), act("restart", "Restart one container (ten seconds to stop, then killed); the answer says whether the mesh holds it."), { Name: "docker_top", Description: "The processes running inside one container: pid, user, elapsed time, CPU, resident memory and command.", Input: map[string]any{"container": containerArg}, Run: func(args map[string]any) (any, error) { ref, err := text(args, "container") if err != nil { return nil, err } return c.Top(ctx, ref) }, }, { Name: "docker_images", Description: "The images on this machine, the largest first, each with its size and the containers using it (and whether one of them is the mesh's). " + "filter: all, dangling, unused or used.", Input: map[string]any{ "filter": map[string]any{"type": "string", "enum": []string{"all", "dangling", "unused", "used"}, "description": "which images (default all)"}, "match": map[string]any{"type": "string", "description": "only images whose repository:tag contains this"}, "limit": map[string]any{"type": "integer", "description": "how many to show (default 100, at most 1000); count says how many matched"}, }, Run: func(args map[string]any) (any, error) { n, err := bounded(args, "limit", 100, 1000) if err != nil { return nil, err } return c.Images(ctx, optional(args, "filter"), optional(args, "match"), n) }, }, { Name: "docker_prune", Description: "Reclaim space: dangling images and unused build cache, and — only when containers is true — stopped containers the mesh does not hold. " + "Never a volume, never a container the mesh holds, never an image a container uses. A dry run by default: it lists what would go; dry_run false removes it.", Input: map[string]any{ "dry_run": map[string]any{"type": "boolean", "description": "list only (default true)"}, "images": map[string]any{"type": "boolean", "description": "dangling images (default true)"}, "build_cache": map[string]any{"type": "boolean", "description": "build cache nothing refers to (default true)"}, "containers": map[string]any{"type": "boolean", "description": "stopped containers the mesh does not hold (default false); what they mounted is kept"}, "older_than_hours": map[string]any{"type": "integer", "description": "only what is older than this many hours (default 0: any age)"}, }, Run: func(args map[string]any) (any, error) { older := 0 if v, ok := args["older_than_hours"]; ok && v != nil && v != float64(0) { n, err := bounded(args, "older_than_hours", 0, 24*365) if err != nil { return nil, err } older = n } return c.Prune(ctx, PruneAsk{ DryRun: flag(args, "dry_run", true), Images: flag(args, "images", true), BuildCache: flag(args, "build_cache", true), Containers: flag(args, "containers", false), OlderThanH: older, }) }, }, { Name: "docker_disk_usage", Description: "What the runtime takes on disk (docker system df -v): per kind — images, containers, volumes, build cache — the total, the active and the reclaimable, and the largest of each.", Input: map[string]any{"top": map[string]any{"type": "integer", "description": "how many of the largest per kind (default 10, at most 100)"}}, Run: func(args map[string]any) (any, error) { n, err := bounded(args, "top", 10, 100) if err != nil { return nil, err } return c.DiskUsage(ctx, n) }, }, { Name: "docker_networks", Description: "Every network the runtime has: driver, scope, subnets and gateway, and the running containers on it with their addresses and whether the mesh holds them.", Run: func(map[string]any) (any, error) { return c.Networks(ctx) }, }, { Name: "docker_volumes", Description: "Every volume with the containers mounting it, whether the mesh holds any of them, whether it is anonymous, its compose project, and — when sizes is true (slower) — its size.", Input: map[string]any{ "unmounted": map[string]any{"type": "boolean", "description": "only volumes no container mounts (default false)"}, "sizes": map[string]any{"type": "boolean", "description": "measure each volume (default false: it walks every volume)"}, }, Run: func(args map[string]any) (any, error) { return c.Volumes(ctx, flag(args, "unmounted", false), flag(args, "sizes", false)) }, }, { Name: "docker_events", Description: "What the runtime did in a window ending now (default the last 60 minutes, at most 24 hours): containers created, started, died, health changes, images pulled — with mesh_held. Exec events are left out unless asked; " + "an exec's command line is shown with any secret it carried as [redacted: ] — a value of the container's environment named like a secret, a password in a URI, the word after a password flag.", Input: map[string]any{ "minutes": map[string]any{"type": "integer", "description": "how far back (default 60, at most 1440)"}, "type": map[string]any{"type": "string", "description": "only one kind: container, image, network, volume, daemon, plugin or builder"}, "limit": map[string]any{"type": "integer", "description": "the latest this many (default 200, at most 2000)"}, "execs": map[string]any{"type": "boolean", "description": "include exec_* events (default false: health checks make many)"}, }, Run: func(args map[string]any) (any, error) { minutes, err := bounded(args, "minutes", 60, 1440) if err != nil { return nil, err } limit, err := bounded(args, "limit", 200, 2000) if err != nil { return nil, err } return c.Events(ctx, minutes, optional(args, "type"), limit, flag(args, "execs", false)) }, }, { Name: "docker_daemon_config", Description: "The runtime's configuration: /etc/docker/daemon.json as it is on disk, the daemon's essentials as it runs now (docker info: version, storage and logging drivers, " + "live restore, root directory, insecure registries, warnings), and where the two differ — keys a reload or only a restart would take.", Run: func(map[string]any) (any, error) { return c.DaemonConfig(ctx) }, }, { Name: "docker_unlabelled", Description: "The containers the mesh does not hold — the cleanup list — each with its image, state, compose project and directory, ports and mounts.", Run: func(map[string]any) (any, error) { return c.Unlabelled(ctx) }, }, { Name: "docker_problems", Description: "Every container that is not well: unhealthy, restarting, dead, killed for memory, exited with a failure, or restarted five times or more — with whether the mesh holds it.", Run: func(map[string]any) (any, error) { p, err := c.Problems(ctx) if err != nil { return nil, err } return map[string]any{"count": len(p), "containers": p}, nil }, }, { Name: "docker_ports", Description: "Every port the containers publish on this machine (address:port -> container port), and the containers on the host's network, which publish whatever they listen on.", Run: func(map[string]any) (any, error) { p, err := c.Ports(ctx) if err != nil { return nil, err } return map[string]any{"count": len(p), "ports": p}, nil }, }, } } func text(args map[string]any, key string) (string, error) { s, _ := args[key].(string) if s = strings.TrimSpace(s); s == "" { return "", fmt.Errorf("%s is required", key) } return s, nil } func optional(args map[string]any, key string) string { s, _ := args[key].(string) return strings.TrimSpace(s) } func flag(args map[string]any, key string, def bool) bool { if b, ok := args[key].(bool); ok { return b } return def } // bounded is a whole number argument, defaulted when absent and held to a ceiling. func bounded(args map[string]any, key string, def, most int) (int, error) { v, ok := args[key] if !ok || v == nil { return def, nil } f, ok := v.(float64) if !ok || f != math.Trunc(f) || f < 1 { return 0, fmt.Errorf("%s must be a whole number of at least 1", key) } return int(math.Min(f, float64(most))), nil }