Files
mesh-host/internal/bundle/bundle.go
T
jschoubben ee2648188d Repoint ADR references after HQ consolidated 65 records to 23
96 comments across the two repos named records that no longer exist. Each now
points at the consolidated record that holds its reasoning -- ADR 0034 (a test
defends a decision) is 0017, the eight host records are 0005, the four lab
records are 0016.

Worth noting for next time: these are references from outside HQ, so renumbering
there is not free. It cost 38 files here.
2026-08-28 23:33:44 +02:00

100 lines
3.7 KiB
Go

// Package bundle is the declaration the host carries.
//
// novox/hq ADR 0004: the host has one behaviour and two sources of declaration — the control
// plane when a mesh is reachable, and this when none is. The first node is not a different
// kind of node; it is a node whose mesh is not up yet, and this is what it applies until it is.
//
// Carried inside the binary rather than beside it, because "copy it onto a machine and run it
// is the whole installation" (ADR 0005) stops being true the moment a second file has to
// arrive with it.
package bundle
import (
_ "embed"
"errors"
"fmt"
"strings"
"github.com/novox/mesh-host/internal/declaration"
)
// One bundle per operating system, because its CONTENTS are per system even though its
// mechanism is not: package names, unit names and service names all differ
// (novox/hq ADR 0005). All three are embedded and the host applies the one it was built for —
// an arch host never reads the alpine bundle.
//
// A host whose bundle is only comments carries nothing, and says so rather than applying
// nothing and reporting success — a host that silently did nothing on a first node would look
// exactly like one that worked.
//
//go:embed substrate-arch.lock
var archLock []byte
//go:embed substrate-alpine.lock
var alpineLock []byte
//go:embed substrate-android.lock
var androidLock []byte
var locks = map[string][]byte{
"arch": archLock,
"alpine": alpineLock,
"android": androidLock,
}
// ErrEmpty means this host carries no bundle.
var ErrEmpty = errors.New(
"this host carries no bundle. A host without one cannot raise a first node, and applying " +
"nothing would look exactly like applying something")
// Raw returns the carried bytes, for inspection.
func Raw(system string) []byte { return locks[system] }
// IsEmpty reports whether anything was built in. A bundle of only comments and whitespace is
// empty for this purpose: a placeholder is a comment, and treating it as content would mean a
// host claims to carry a substrate it does not.
func IsEmpty(system string) bool {
for _, line := range strings.Split(string(locks[system]), "\n") {
line = strings.TrimSpace(line)
if line != "" && !strings.HasPrefix(line, "//") {
return false
}
}
return true
}
// Load parses the carried bundle.
//
// The same parser the link will use. A bundle that reaches a machine and is then refused by the
// host that carries it would be a build-time mistake discovered at the worst possible moment,
// which is why `mesh-host bundle` exists to ask before it matters.
func Load(system string) (*declaration.Declaration, error) {
if _, known := locks[system]; !known {
return nil, fmt.Errorf("no bundle is built for %q", system)
}
if IsEmpty(system) {
return nil, ErrEmpty
}
// ParseTrusted: the bundle arrives with the binary, so it may carry actions the link may
// not (novox/hq ADR 0005). The bootstrap needs them — creating the control plane's database
// happens before there is any mesh to ask for one.
return declaration.ParseTrusted(stripComments(locks[system]))
}
// stripComments removes whole-line `//` comments so a bundle can be annotated.
//
// It is JSON on the wire and a pinned, hand-authored artefact here, and a pinned thing nobody
// can annotate is a pinned thing nobody can review. Only whole lines: anything cleverer would
// need to know where strings begin and end, and a parser that half-understands its input is
// worse than one that does not try.
func stripComments(raw []byte) []byte {
var kept []string
for _, line := range strings.Split(string(raw), "\n") {
if strings.HasPrefix(strings.TrimSpace(line), "//") {
continue
}
kept = append(kept, line)
}
return []byte(strings.Join(kept, "\n"))
}