Files
mesh-host/internal/bootstrap/module.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

146 lines
6.1 KiB
Go

package bootstrap
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
)
// placeholderDigest is what the catalogue writes where a built image's digest will go.
//
// Sixty-four zeros. A manifest in the catalogue names an image the mesh builds and pushes, and
// until that has happened there is no digest to name — so the convention is a digest that is
// obviously not one, replaced by the pipeline when it publishes. The installer meets it twice: it
// must NOT be there in the registry's manifest, whose image is upstream and never built
// (novox/hq 04-ISSUES/029), and it MUST be there in the control plane's, which is the image
// step 8 has just pushed.
const placeholderDigest = "sha256:" + "0000000000000000000000000000000000000000000000000000000000000000"
// catalogueDir is where a mesh-catalog checkout keeps its manifests.
const catalogueDir = "modules"
// manifestFile is the path a module's manifest is read from, given a catalogue checkout.
func manifestFile(catalogue, module string) string {
return filepath.Join(catalogue, catalogueDir, module, "module.json")
}
// readManifest reads one module's manifest out of a mesh-catalog checkout.
//
// **From a checkout rather than from anything the mesh serves**, and that is the ordering the whole
// pivot exists to respect: at this point the mesh has no build machine, no forge and — until step 7
// finishes — no registry. What a manifest is, is a file; the installer is handed the directory it
// is in and reads it.
func readManifest(catalogue, module string) ([]byte, error) {
path := manifestFile(catalogue, module)
raw, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf(
"the %s module's manifest could not be read: %w\n"+
"--catalog is a checkout of the mesh's catalogue repository, and this is read from "+
"%s inside it", module, err, filepath.Join(catalogueDir, module, "module.json"))
}
return raw, nil
}
// Installed is what installing one module did.
type Installed struct {
// Module is its name.
Module string
// Known is true when the mesh already had this module in its catalogue. The manifest is
// registered either way — a re-run with a changed manifest must land — so this reports what
// was found rather than what was skipped.
Known bool
// Assigned is true when this node was given the module during this run.
Assigned bool
// Pushed is what the mesh said when it sent this node its declaration.
Pushed string
}
// installModule registers a manifest, gives it to this node, and sends it.
//
// The three verbs a person types, in the order they type them, through the same commands. There is
// no installer-only path into the mesh: everything here is `module add`, `assign` and `push`, so
// what the installer does on a bare machine and what an operator does on a running mesh are the
// same act (novox/hq ADR 0035, one refusal per act however it is asked for).
func installModule(ctx context.Context, o Options, control controlPlane, module string,
manifest []byte, say func(string)) (Installed, error) {
out, err := registerAndAssign(ctx, o, control, module, manifest, say)
if err != nil {
return out, err
}
out.Pushed, err = pushNode(ctx, o, control, say)
return out, err
}
// registerAndAssign is the half of installing that happens before anything is sent.
//
// Split out because one module needs something in between: the control plane's own store
// connections have to be accepted before its declaration is composed, or the mesh would seal
// thirty-two random bytes into the file it expects a connection string in and the container would
// come up unable to open anything (mesh-controller's `secret accept`, and what it exists for).
//
// **`module add` is run every time and is not skipped when the module is already known.** It is an
// upsert on the manifest, and the manifest is exactly what changes between runs — step 9 registers
// the control plane with a digest that did not exist the first time. Skipping it because the name
// was already in the catalogue would silently pin the mesh to the previous image.
func registerAndAssign(ctx context.Context, o Options, control controlPlane, module string,
manifest []byte, say func(string)) (Installed, error) {
out := Installed{Module: module}
known, err := control.tell(ctx, "module", "list")
if err != nil {
return out, err
}
out.Known = mentions(known, module)
remote := "/" + module + "-module.json"
if err := control.carrying(ctx, module+"-module.json", manifest, remote); err != nil {
return out, err
}
if _, err := control.tell(ctx, "module", "add", remote); err != nil {
return out, err
}
if out.Known {
say(" registered " + module + " — the mesh already knew it; the manifest is now this one")
} else {
say(" registered " + module)
}
assigned, err := control.tell(ctx, "assign", o.Node, module)
if err != nil {
return out, err
}
out.Assigned = true
say(" assigned " + module + " to " + o.Node)
if refusal := strings.TrimSpace(assigned); strings.Contains(refusal, "but ") {
// `assign` records what a person meant and says at once when the machine cannot host it.
// Repeated rather than swallowed: the push below will apply everything else and this is
// the only place the reason appears.
say(indent(refusal))
}
return out, nil
}
// pushNode sends this node everything it should be.
//
// Given the long wait rather than the probe timeout: a push composes every declaration this node
// should hold, seals every secret in them and publishes them, and on a first node that is the
// slowest thing the mesh does.
func pushNode(ctx context.Context, o Options, control controlPlane, say func(string)) (string, error) {
said, err := control.within(o.Wait).tell(ctx, "push", o.Node)
if err != nil {
return "", fmt.Errorf(
"%w\n\nWhat was registered and assigned is registered and assigned, and this node has "+
"not been sent it. Nothing is half-applied — a push the mesh refused sent nothing "+
"at all. Fix what it named and run this installer again: it will find the module "+
"already registered and try the push again", err)
}
say(" pushed " + o.Node)
return strings.TrimSpace(said), nil
}