// Package bundle is the declaration the host carries. // // novox/hq ADR 0038: 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 0041) 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 0060). 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 0047). 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")) }