Unify trunk on main: initialization → main #3

Merged
jschoubben merged 58 commits from initialization into main 2026-09-05 01:13:33 +00:00
6 changed files with 96 additions and 8 deletions
Showing only changes of commit e09503acc7 - Show all commits
+10 -5
View File
@@ -39,17 +39,22 @@ test:
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 BUNDLE=path/to/substrate.lock
# 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; }
@cp internal/bundle/substrate.lock internal/bundle/substrate.lock.default
@cp "$(BUNDLE)" internal/bundle/substrate.lock
@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.lock.default internal/bundle/substrate.lock; \
mv internal/bundle/substrate-$(SYSTEM).lock.default internal/bundle/substrate-$(SYSTEM).lock; \
exit $$status
@echo "built carrying $(BUNDLE)"
@echo "built for $(SYSTEM) carrying $(BUNDLE)"
clean:
rm -f mesh-host
+26
View File
@@ -0,0 +1,26 @@
# Examples
## `substrate-first-node.lock`
What an Arch machine must be before a mesh exists — the first steps of the bootstrap in
[novox/hq `07-the-substrate.md`](https://git.novox.be/novox/hq): a container runtime, a store
running, and the control plane's database created inside it.
**It stops there**, and the file says why: loading the control plane's schema and starting the
control plane are the next two steps, and there is no control plane yet. A bundle naming one
would be a bundle that cannot be applied.
Build a host carrying it:
```
make host SYSTEM=arch BUNDLE=examples/substrate-first-node.lock
```
**The image reference has to be replaced before this is useful.** It is written as
`REGISTRY/postgres@DIGEST` because the digest belongs to whatever registry serves it — in the
lab, one the scenario raises, which reports its digests when it comes up. That is not a
placeholder to be tidied away: a bundle is built *for a target*, and which registry that target
pulls from is part of the target.
Verified end to end in a lab machine with no route out: five resources applied, idempotent on a
second run, and `mesh` present in the store afterwards.
+57
View File
@@ -0,0 +1,57 @@
// substrate-arch.lock — what an Arch machine must be before a mesh exists.
//
// The first three steps of the bootstrap (novox/hq 03-DESIGN/01-to-be/07-the-substrate.md).
// Steps four and five — load the control plane's schema, start the control plane — are absent
// because the control plane does not exist yet. A bundle that named it would be a bundle that
// cannot be applied.
//
// PINNED BY DIGEST, and the digest is not decoration: a tag can be made to point at a different
// image, and this file is applied on a machine with no mesh to ask about anything.
//
// The store's data is a NAMED VOLUME rather than a directory on the machine. A directory the
// host creates is owned by root, and the database runs as somebody else inside the container —
// so it could not write, and the container crash-looped. A named volume lets the image set up
// its own ownership, which is what it is for. It also outlives the container, which is what you
// want for the thing holding the mesh's state.
{
"declaration": 1,
"resources": [
{
"id": "container-runtime",
"type": "package",
"package": "docker"
},
{
"id": "container-runtime-running",
"type": "service",
"unit": "docker.service",
"state": "running",
"boot": "enabled"
},
{
"id": "store",
"type": "container",
"name": "mesh-store",
"image": "REGISTRY/postgres@DIGEST",
"env": {
"POSTGRES_PASSWORD": "bootstrap",
"PGDATA": "/var/lib/postgresql/data/pgdata"
},
"volumes": ["mesh-store-data:/var/lib/postgresql/data"]
},
{
"id": "store-ready",
"type": "action",
"in": "mesh-store",
"command": ["sh", "-c", "for i in $(seq 1 60); do pg_isready -U postgres >/dev/null 2>&1 && exit 0; sleep 1; done; exit 1"],
"verify": ["pg_isready", "-U", "postgres"]
},
{
"id": "control-plane-database",
"type": "action",
"in": "mesh-store",
"command": ["sh", "-c", "psql -U postgres -c 'CREATE DATABASE mesh'"],
"verify": ["sh", "-c", "psql -U postgres -lqt | cut -d'|' -f1 | grep -qw mesh"]
}
]
}
+1 -1
View File
@@ -1,7 +1,7 @@
// substrate-alpine.lock — the pinned tier-1 descriptor the ALPINE host carries.
//
// Per system, because its CONTENTS are: this one names apk packages and OpenRC services where
// the arch bundle names pacman packages and systemd units (novox/hq ADR 0060).
// the arch bundle names pacman packages and systemd units (novox/hq ADR 0005).
//
// Empty on purpose. What belongs here is the closure for a one-node mesh, and that is not
// yet known: novox/hq research 012 asks what the minimum actually is, and research 011 is
+1 -1
View File
@@ -5,7 +5,7 @@
//
// The substrate is a container runtime, a store and the control plane (novox/hq
// 07-the-substrate.md). An android host implements `file`, `directory` and `action` and refuses
// `package`, `container` and `service` (ADR 0060) — so every step of the bootstrap is a shape it
// `package`, `container` and `service` (ADR 0005) — so every step of the bootstrap is a shape it
// does not have. No amount of filling this in changes that.
//
// **A partial host can JOIN a mesh and cannot BE the first node.** That is a real distinction
+1 -1
View File
@@ -1,7 +1,7 @@
// substrate-arch.lock — the pinned tier-1 descriptor the ARCH host carries.
//
// Per system, because its CONTENTS are: package names, unit names and service names all differ
// (novox/hq ADR 0060). The mechanism is shared; what it names is not.
// (novox/hq ADR 0005). The mechanism is shared; what it names is not.
//
// Empty on purpose. What belongs here is the closure for a one-node mesh, and that is not
// yet known: novox/hq research 012 asks what the minimum actually is, and research 011 is