Files
mesh-host/internal/bootstrap/talk.go
T
jschoubben 121367319d Rename mesh-control -> mesh-controller, substrate -> foundation
One name per thing, per the HQ glossary: the module/container/image/binary/repo
becomes mesh-controller, the seat the-controller, and the store+broker pair the
foundation (embedded base bundles, default template and example lock renamed with
their go:embed directives). No behaviour change — a pure vocabulary rename.

Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
2026-09-16 18:40:40 +02:00

120 lines
5.2 KiB
Go

package bootstrap
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"time"
)
// controlPlane is the running control plane, asked things.
//
// **Through `docker exec`, not over a network.** The control plane listens on nothing — `serve` is
// a broker consumer, and every administrative verb is a subcommand of the same binary that opens
// the stores directly (mesh-controller's own usage). So the way to tell a mesh anything, from the
// machine the mesh is on, is to run its binary inside its own container. That is also what the lab
// does, and having the installer and the lab drive the mesh identically is the point: the lab is
// meant to exercise the installer, not a second procedure that resembles it.
//
// It carries which container, because the whole pivot turns on there being two of them: the
// foundation's `temp-mesh-controller` for steps 6 to 9, and the module's `mesh-controller` afterwards.
type controlPlane struct {
container string
run Runner
timeout time.Duration
}
// within is the same control plane, asked with a different patience.
//
// A copy rather than a field somebody sets, so a slow command cannot leave every command after it
// slow: the caller that needs the long wait says so on the call.
func (c controlPlane) within(timeout time.Duration) controlPlane {
c.timeout = timeout
return c
}
// tell runs a mesh-controller subcommand and gives back what it said.
//
// The failure carries the command AND the output. A mesh-controller refusal is a paragraph explaining
// what is wrong — "nothing provides route, wanted by registry" — and an installer that reported
// only "exit status 1" would throw away the one thing a person needs.
func (c controlPlane) tell(ctx context.Context, args ...string) (string, error) {
asking, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
out, err := c.run(asking, "docker", append(
[]string{"exec", c.container, controlPlaneBinary}, args...)...)
if err != nil {
return out, fmt.Errorf("`%s %s` was refused: %w\n%s",
c.container, strings.Join(args, " "), err, indent(strings.TrimSpace(out)))
}
return out, nil
}
// carry puts a file inside the control plane's container.
//
// **Because every command that takes a file reads it from its own filesystem.** `module add
// <file>` and `secret accept --from <file>` open a path, and the process doing the opening is
// inside the container. The installer is not. So the file is copied in first, exactly as the lab
// does it.
//
// The image is `FROM scratch` and has no shell, so nothing inside can move a file, change its mode
// or clean up after itself. What is copied in stays until the container is replaced — which, for
// the temporary control plane, is a container that gets removed at step 10 and takes its contents
// with it.
func (c controlPlane) carry(ctx context.Context, local, remote string) error {
asking, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
if _, err := c.run(asking, "docker", "cp", local, c.container+":"+remote); err != nil {
return fmt.Errorf("cannot put %s into %s at %s: %w", local, c.container, remote, err)
}
return nil
}
// carrying writes bytes to a temporary file on the machine and copies them into the container.
//
// **0644, and that is not carelessness — 0600 would break it.** `docker cp` keeps the ownership and
// mode a file had outside, the control plane's image runs as 65534, and the process that reads
// these files is that one. The lab paid for this exactly once: a 0600 root-owned key copied in
// landed unreadable, `secret accept` failed with `permission denied`, and what depended on it
// crash-looped on material it never received. There is no shell in the image to chown it with.
//
// What goes through here is a module manifest and a store connection string. The connection is the
// same value the produced bundle already holds in the clear — a foundation names its own bootstrap
// credentials, and at genesis there is nowhere else for them to be — so this widens nothing. The
// file on the machine is removed at once, and the copy inside the container goes when the
// container does, which for the temporary control plane is step 10.
func (c controlPlane) carrying(ctx context.Context, name string, content []byte, remote string) error {
local := filepath.Join(os.TempDir(), name)
if err := os.WriteFile(local, content, 0o644); err != nil {
return fmt.Errorf("nowhere to stage %s before copying it into %s: %w", name, c.container, err)
}
defer os.Remove(local)
return c.carry(ctx, local, remote)
}
func indent(s string) string {
if s == "" {
return ""
}
return " " + strings.ReplaceAll(s, "\n", "\n ")
}
// mentions reports whether one of a listing's lines starts with this exact word.
//
// Line-and-word rather than a substring search, because these listings are columns and a
// substring match would find `registry` inside `registry-mirror` and report a module installed
// that is not. Every one of mesh-controller's `list` verbs prints the name first on the line.
func mentions(listing, name string) bool {
for _, line := range strings.Split(listing, "\n") {
first, _, _ := strings.Cut(strings.TrimSpace(line), " ")
if first == name {
return true
}
}
return false
}