Files
mesh-host/Makefile
T
jschoubben b82ab95f74 mesh-bootstrap: the first-node procedure, as a program rather than a test
The only complete written-down copy of how a mesh is stood up was an integration
test in the lab. That is why every bootstrap gap kept being found late: an install
procedure that lives as a test fixture is exercised by whoever writes tests, never
by whoever installs. This is that procedure.

A separate binary, not a mesh-host subcommand. mesh-host says of itself that it
connects to nothing and listens on nothing and that what it applies comes from a
file, and that sentence is what makes an always-running root daemon auditable. An
installer loads images and interrogates a control plane. Same tier, different
program.

The control plane's image is carried, not built and not fetched. The forge that
holds its source runs on the mesh, so a bootstrap that had to fetch it would need
a mesh in order to raise one. Embedding breaks that cycle the way the carried
bundle breaks "copy it onto a machine and run it". The image id is read out of the
saved tar before the runtime is asked anything, which is what makes the load
idempotent: the installer can ask whether the machine already holds exactly this.

Five steps, each idempotent and each saying whether it found or changed something,
because this is run over and over by somebody getting a machine working. It stops
at a running substrate with a control plane that replies — enrolment, the module
catalogue and assignment are the next stage and are deliberately absent.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-10 23:17:30 +02:00

84 lines
3.9 KiB
Makefile

SYSTEM ?= arch
# The gate. Green is the definition of done (novox/hq how-we-build §5).
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo development)
LDFLAGS := -s -w -X main.builtFor=$(SYSTEM) -X main.version=$(VERSION)
# The bundle a host carries is built INTO it (novox/hq ADR 0038, ADR 0041): a host that needed
# a second file to arrive with it is not "copy it and run it".
BUNDLE ?=
.PHONY: check test vet fmt build clean host hosts bootstrap packaging-test
check: fmt vet test packaging-test build
# One binary per operating system (novox/hq ADR 0060). The system is pinned at link time; a
# host built without one refuses to touch a machine rather than guessing.
hosts:
@for s in arch alpine android; do \
CGO_ENABLED=0 go build -ldflags="-s -w -X main.builtFor=$$s -X main.version=$(VERSION)" \
-o mesh-host-$$s ./cmd/mesh-host || exit 1; \
echo "built mesh-host-$$s"; \
done
packaging-test:
@./packaging/rollback_test.sh
@./packaging/launch_test.sh
@./packaging/roused_test.sh
fmt:
@test -z "$$(gofmt -l . )" || { echo "unformatted:"; gofmt -l . ; exit 1; }
vet:
go vet ./...
# Structure and logic, and the same checks against this machine. The boundary is never mocked.
test:
go test ./... -count=1
# A default build carries no bundle and refuses to reconcile, which is the honest state for a
# host nobody has told what a substrate is.
build:
CGO_ENABLED=0 go build -ldflags="$(LDFLAGS)" -o mesh-host ./cmd/mesh-host
# A host for a real machine, carrying a real bundle:
# make host SYSTEM=arch BUNDLE=path/to/substrate.lock
#
# The bundle replaces the one for SYSTEM, because its contents are per operating system —
# package names and unit names differ (novox/hq ADR 0005).
host:
@test -n "$(BUNDLE)" || { echo "BUNDLE= is required; a host with no bundle cannot raise a first node"; exit 1; }
@test -f "$(BUNDLE)" || { echo "no such bundle: $(BUNDLE)"; exit 1; }
@test -f internal/bundle/substrate-$(SYSTEM).lock || { echo "no bundle slot for SYSTEM=$(SYSTEM)"; exit 1; }
@cp internal/bundle/substrate-$(SYSTEM).lock internal/bundle/substrate-$(SYSTEM).lock.default
@cp "$(BUNDLE)" internal/bundle/substrate-$(SYSTEM).lock
@CGO_ENABLED=0 go build -ldflags="$(LDFLAGS)" -o mesh-host ./cmd/mesh-host; \
status=$$?; \
mv internal/bundle/substrate-$(SYSTEM).lock.default internal/bundle/substrate-$(SYSTEM).lock; \
exit $$status
@echo "built for $(SYSTEM) carrying $(BUNDLE)"
# The installer, carrying the control plane's image:
# make bootstrap IMAGE=mesh-control:v1.2.3
#
# The image is BUILT ELSEWHERE and handed over — mesh-control's own `make image` — and embedded
# here at release time. Not built on the machine being bootstrapped, and not fetched: the forge
# that holds mesh-control's source runs on the mesh, so a bootstrap that had to fetch or build the
# control plane would need a mesh in order to raise one. Carrying it breaks that cycle, the same
# way carrying the bundle breaks the "copy it onto a machine and run it" one (novox/hq ADR 0005).
#
# The saved image occupies the embed slot for the length of one build and the placeholder goes
# back, exactly as `host:` does with the bundle. Nothing large is ever committed.
bootstrap:
@test -n "$(IMAGE)" || { echo "IMAGE= is required; an installer carrying no control-plane image cannot raise a mesh"; exit 1; }
@docker image inspect "$(IMAGE)" >/dev/null 2>&1 || { echo "this machine does not hold $(IMAGE) — build it in mesh-control with 'make image'"; exit 1; }
@cp internal/image/control-plane.tar internal/image/control-plane.tar.placeholder
@docker save --output internal/image/control-plane.tar "$(IMAGE)"
@CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=$(VERSION)" -o mesh-bootstrap ./cmd/mesh-bootstrap; \
status=$$?; \
mv internal/image/control-plane.tar.placeholder internal/image/control-plane.tar; \
exit $$status
@echo "built mesh-bootstrap carrying $(IMAGE)"
clean:
rm -f mesh-host mesh-bootstrap