Take the foundation's ports as genesis inputs, check them free, and hand them to the controller as the node's settings (hq ADR 0100)

This commit is contained in:
2026-09-22 17:28:36 +02:00
parent 770f589401
commit 3964d9da0a
10 changed files with 902 additions and 14 deletions
+60 -1
View File
@@ -25,6 +25,7 @@ import (
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"time"
@@ -126,6 +127,14 @@ const usage = `mesh-bootstrap — make a bare machine into a mesh
--packet-filter which packet filter to run (nftables)
--extras catalogue modules beyond the floor, comma-separated
The foundation's ports are this machine's, each checked free before anything is
raised and kept as the node's setting for the module that binds it:
--store-port 5432 --bus-port 5671 --amqp-port 5672 --management-port 15672
--registry-port 5000 (follows --registry, and must agree with it)
--packages-port 3000 --hub-port 51820/udp
--overlay-range the private network's range (default 10.42.0.0/16); refused
if it overlaps an interface or route the machine already has
The installer carries a builder, not a control plane. What raises a mesh is therefore
the same thing that will maintain it, and the control plane a mesh ends up running is
one it built itself, from a repository and a commit it can name and build again.
@@ -176,7 +185,9 @@ func parseArgs(args []string) (string, bootstrap.Options, bool, error) {
HostService: defaultService,
// Longer than the host's 10s: these probes reach a container runtime that may be busy
// pulling, and a probe that times out on a working machine is a false refusal.
Timeout: 30 * time.Second,
Ports: bootstrap.DefaultPorts(),
OverlayRange: bootstrap.DefaultOverlayRange,
Timeout: 30 * time.Second,
// A socket-activated runtime queued behind the network, and a control plane running its
// first `initdb`-shaped wait, are both minutes rather than seconds.
Wait: 3 * time.Minute,
@@ -210,9 +221,38 @@ func parseArgs(args []string) (string, bootstrap.Options, bool, error) {
return "", opts, false, fmt.Errorf(
"unexpected argument %q — try `mesh-bootstrap help`", positionals[0])
}
if err := registryAgrees(set, &opts); err != nil {
return "", opts, false, err
}
return command, opts, jsonOut, nil
}
// registryAgrees makes --registry and --registry-port say one port (novox/hq ADR 0100): the
// registry is raised on the port the node gives it, and every node pulls from the address given.
// Either may be said alone and the other follows; said both ways, they must agree.
func registryAgrees(set *flag.FlagSet, opts *bootstrap.Options) error {
said := map[string]bool{}
set.Visit(func(f *flag.Flag) { said[f.Name] = true })
host, portText, err := net.SplitHostPort(opts.Registry)
if err != nil {
return fmt.Errorf("--registry %q is not host:port: %w", opts.Registry, err)
}
port, err := strconv.Atoi(portText)
if err != nil {
return fmt.Errorf("--registry %q does not end in a port", opts.Registry)
}
switch {
case said["registry-port"] && said["registry"] && port != opts.Ports.Registry:
return fmt.Errorf("--registry %s and --registry-port %d name two ports for one registry",
opts.Registry, opts.Ports.Registry)
case said["registry-port"]:
opts.Registry = net.JoinHostPort(host, strconv.Itoa(opts.Ports.Registry))
case said["registry"]:
opts.Ports.Registry = port
}
return nil
}
func newFlagSet(opts *bootstrap.Options, jsonOut *bool) *flag.FlagSet {
set := flag.NewFlagSet("mesh-bootstrap", flag.ContinueOnError)
set.SetOutput(os.Stderr)
@@ -255,6 +295,25 @@ func newFlagSet(opts *bootstrap.Options, jsonOut *bool) *flag.FlagSet {
set.StringVar(&opts.SDKSource.Ref, "sdk-ref", opts.SDKSource.Ref,
"what of it to build (default main)")
set.StringVar(&opts.Site, "site", "main", "where this machine sits, for the private network")
// The foundation's ports are this node's (novox/hq ADR 0100): each is checked free before
// anything is raised, and becomes the node's setting for the module that binds it.
for _, p := range []struct {
name, what string
into *int
}{
{"store-port", "the store", &opts.Ports.Store},
{"bus-port", "the bus (amqps)", &opts.Ports.Bus},
{"amqp-port", "the broker's AMQP", &opts.Ports.AMQP},
{"management-port", "the broker's management, on loopback", &opts.Ports.Management},
{"registry-port", "the registry", &opts.Ports.Registry},
{"packages-port", "the package registry", &opts.Ports.Packages},
{"hub-port", "the private network's hub (udp)", &opts.Ports.Hub},
} {
set.IntVar(p.into, p.name, *p.into, "the machine's port for "+p.what)
}
set.StringVar(&opts.OverlayRange, "overlay-range", opts.OverlayRange,
"the private network's address range; must not overlap a tunnel the machine already runs")
if opts.Answers == nil {
opts.Answers = map[string]string{}
}