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
84 lines
3.9 KiB
Makefile
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
|