Unify trunk on main: initialization → main #3
@@ -9,7 +9,7 @@ mesh-host profile
|
|||||||
```
|
```
|
||||||
|
|
||||||
That is the whole installation. One statically linked binary, nothing else present, no runtime
|
That is the whole installation. One statically linked binary, nothing else present, no runtime
|
||||||
to install first ([`novox/hq` ADR 0041](https://git.novox.be/novox/hq)).
|
to install first ([`novox/hq` ADR 0005](https://git.novox.be/novox/hq)).
|
||||||
|
|
||||||
## What it is for
|
## What it is for
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ cannot be asked to: [firewall privileged]
|
|||||||
|
|
||||||
A declaration is JSON, versioned, and an **ordered list** of resources — the order is stated
|
A declaration is JSON, versioned, and an **ordered list** of resources — the order is stated
|
||||||
rather than derived, because deriving it would be the host deciding
|
rather than derived, because deriving it would be the host deciding
|
||||||
([`novox/hq` ADR 0043](https://git.novox.be/novox/hq)). The vocabulary is `directory`, `file`
|
([`novox/hq` ADR 0005](https://git.novox.be/novox/hq)). The vocabulary is `directory`, `file`
|
||||||
and `service`, and **anything outside it refuses the whole declaration**: a host that skipped
|
and `service`, and **anything outside it refuses the whole declaration**: a host that skipped
|
||||||
what it did not understand would apply most of a declaration and report success.
|
what it did not understand would apply most of a declaration and report success.
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@ CGO_ENABLED=0 go build -ldflags="-s -w" -o mesh-host ./cmd/mesh-host
|
|||||||
Roughly 3 MB, static, no dynamic dependencies. Cross-compiles with `GOOS`/`GOARCH`; a host is
|
Roughly 3 MB, static, no dynamic dependencies. Cross-compiles with `GOOS`/`GOARCH`; a host is
|
||||||
built once per architecture and copied, never built on the machine it runs on.
|
built once per architecture and copied, never built on the machine it runs on.
|
||||||
|
|
||||||
**Mocking the boundary is forbidden** ([`novox/hq` ADR 0034](https://git.novox.be/novox/hq)).
|
**Mocking the boundary is forbidden** ([`novox/hq` ADR 0017](https://git.novox.be/novox/hq)).
|
||||||
Every detector is exercised against a fake runner for its logic *and* against this machine for
|
Every detector is exercised against a fake runner for its logic *and* against this machine for
|
||||||
its behaviour. The tests do not assert which capabilities a machine has — that varies, and is
|
its behaviour. The tests do not assert which capabilities a machine has — that varies, and is
|
||||||
the point of detecting — they assert that detection tells the truth about whatever is there.
|
the point of detecting — they assert that detection tells the truth about whatever is there.
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import (
|
|||||||
// version is stamped at build time. Unset in a development build, and said so rather than
|
// version is stamped at build time. Unset in a development build, and said so rather than
|
||||||
// defaulted to something that looks like a release.
|
// defaulted to something that looks like a release.
|
||||||
// builtFor names the operating system this host was built for, set at link time
|
// builtFor names the operating system this host was built for, set at link time
|
||||||
// (novox/hq ADR 0060). A host built without one refuses to do anything that touches the
|
// (novox/hq ADR 0005). A host built without one refuses to do anything that touches the
|
||||||
// machine, rather than guessing and calling a package manager that is not there.
|
// machine, rather than guessing and calling a package manager that is not there.
|
||||||
var builtFor = ""
|
var builtFor = ""
|
||||||
|
|
||||||
@@ -161,7 +161,7 @@ func run(ctx context.Context, command string, opts options) error {
|
|||||||
return fmt.Errorf("reading the declaration: %w", err)
|
return fmt.Errorf("reading the declaration: %w", err)
|
||||||
}
|
}
|
||||||
// ParseTrusted: a file handed to the host by someone already running it as root is
|
// ParseTrusted: a file handed to the host by someone already running it as root is
|
||||||
// not the link. novox/hq ADR 0047 bounds what a REMOTE party may push; someone who
|
// not the link. novox/hq ADR 0005 bounds what a REMOTE party may push; someone who
|
||||||
// can write this file and run this binary can do anything the binary can, so refusing
|
// can write this file and run this binary can do anything the binary can, so refusing
|
||||||
// them an action would buy nothing and would make an action untestable except by
|
// them an action would buy nothing and would make an action untestable except by
|
||||||
// rebuilding the bundle.
|
// rebuilding the bundle.
|
||||||
@@ -172,7 +172,7 @@ func run(ctx context.Context, command string, opts options) error {
|
|||||||
return runApply(ctx, opts, d, opts.file)
|
return runApply(ctx, opts, d, opts.file)
|
||||||
|
|
||||||
case "reconcile":
|
case "reconcile":
|
||||||
// The first node's path. novox/hq ADR 0038: no mesh reachable means the declaration
|
// The first node's path. novox/hq ADR 0004: no mesh reachable means the declaration
|
||||||
// comes from the bundle the host carries. There is no link yet, so this is currently
|
// comes from the bundle the host carries. There is no link yet, so this is currently
|
||||||
// the only source — which is a stage, not a design, and saying so beats implying the
|
// the only source — which is a stage, not a design, and saying so beats implying the
|
||||||
// other source exists.
|
// other source exists.
|
||||||
@@ -275,7 +275,7 @@ func writeInventory(inv inventory.Inventory) {
|
|||||||
writeProfile(inv.Profile)
|
writeProfile(inv.Profile)
|
||||||
|
|
||||||
// Printed last and never hidden. An inventory that quietly omits what it could not read
|
// Printed last and never hidden. An inventory that quietly omits what it could not read
|
||||||
// is the same fault as a report assembled from intent (novox/hq ADR 0035).
|
// is the same fault as a report assembled from intent (novox/hq ADR 0018).
|
||||||
if len(inv.Unreadable) > 0 {
|
if len(inv.Unreadable) > 0 {
|
||||||
fmt.Println("\ncould not read:")
|
fmt.Println("\ncould not read:")
|
||||||
for _, u := range inv.Unreadable {
|
for _, u := range inv.Unreadable {
|
||||||
@@ -307,7 +307,7 @@ func runApply(ctx context.Context, opts options, d *declaration.Declaration, sou
|
|||||||
}
|
}
|
||||||
// Refuse a declaration naming a shape this host cannot apply, before anything is applied.
|
// Refuse a declaration naming a shape this host cannot apply, before anything is applied.
|
||||||
// An android host has no `package` applier, and finding that out half way through is the
|
// An android host has no `package` applier, and finding that out half way through is the
|
||||||
// half-configured machine this host exists to prevent (novox/hq ADR 0060).
|
// half-configured machine this host exists to prevent (novox/hq ADR 0005).
|
||||||
if err := system.Check(sys, d); err != nil {
|
if err := system.Check(sys, d); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -336,7 +336,7 @@ func runApply(ctx context.Context, opts options, d *declaration.Declaration, sou
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only now, and only after a clean apply: this version got as far as a completed
|
// Only now, and only after a clean apply: this version got as far as a completed
|
||||||
// reconcile, which is the whole of what "known good" claims (novox/hq ADR 0059). Not
|
// reconcile, which is the whole of what "known good" claims (novox/hq ADR 0005). Not
|
||||||
// health — a disconnected node is ordinary, and a resource that fails is the machine's
|
// health — a disconnected node is ordinary, and a resource that fails is the machine's
|
||||||
// problem rather than the binary's.
|
// problem rather than the binary's.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
// Three properties, each following a recorded decision, and each of them the difference
|
// Three properties, each following a recorded decision, and each of them the difference
|
||||||
// between this and a script that writes files:
|
// between this and a script that writes files:
|
||||||
//
|
//
|
||||||
// - A failed step fails the apply (novox/hq ADR 0008). Not "logs and continues": a partial
|
// - A failed step fails the apply (novox/hq ADR 0010). Not "logs and continues": a partial
|
||||||
// apply that reports success is the mesh's most expensive shape.
|
// apply that reports success is the mesh's most expensive shape.
|
||||||
// - Every applier READS BACK. Setting a value is not evidence the value took.
|
// - Every applier READS BACK. Setting a value is not evidence the value took.
|
||||||
// - What was applied is recorded after it works, never before (ADR 0035). A failed apply
|
// - What was applied is recorded after it works, never before (ADR 0018). A failed apply
|
||||||
// leaves the machine in whatever state it reached, and nothing must claim otherwise.
|
// leaves the machine in whatever state it reached, and nothing must claim otherwise.
|
||||||
package apply
|
package apply
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Runner executes a command. The real one is used everywhere outside unit tests; behaviour
|
// Runner executes a command. The real one is used everywhere outside unit tests; behaviour
|
||||||
// against a real system is tested alongside rather than mocked (novox/hq ADR 0034).
|
// against a real system is tested alongside rather than mocked (novox/hq ADR 0017).
|
||||||
type Runner = system.Runner
|
type Runner = system.Runner
|
||||||
|
|
||||||
// Outcome is what happened to one resource.
|
// Outcome is what happened to one resource.
|
||||||
@@ -379,7 +379,7 @@ func applyService(ctx context.Context, sys system.System, r *declaration.Service
|
|||||||
// what it actually did.
|
// what it actually did.
|
||||||
//
|
//
|
||||||
// Only ever called for something in the store, which is what bounds it: the host is
|
// Only ever called for something in the store, which is what bounds it: the host is
|
||||||
// authoritative over its own footprint and inert everywhere else (novox/hq ADR 0043).
|
// authoritative over its own footprint and inert everywhere else (novox/hq ADR 0005).
|
||||||
//
|
//
|
||||||
// It returns the action rather than assuming "removed", because for half the vocabulary the
|
// It returns the action rather than assuming "removed", because for half the vocabulary the
|
||||||
// honest word is "forgotten". A host that reported a package removed when it left the package
|
// honest word is "forgotten". A host that reported a package removed when it left the package
|
||||||
@@ -469,7 +469,7 @@ func ExecRunner(ctx context.Context, name string, args ...string) (string, error
|
|||||||
//
|
//
|
||||||
// It never upgrades and never removes. "Present" is the whole of what a package resource
|
// It never upgrades and never removes. "Present" is the whole of what a package resource
|
||||||
// asserts, because version is the package manager's business and the mesh does not have a
|
// asserts, because version is the package manager's business and the mesh does not have a
|
||||||
// second opinion about it (novox/hq ADR 0041 — the host depends on nothing, and that includes
|
// second opinion about it (novox/hq ADR 0005 — the host depends on nothing, and that includes
|
||||||
// not becoming a second package manager).
|
// not becoming a second package manager).
|
||||||
func applyPackage(ctx context.Context, sys system.System, r *declaration.Package, run Runner) (Outcome, error) {
|
func applyPackage(ctx context.Context, sys system.System, r *declaration.Package, run Runner) (Outcome, error) {
|
||||||
out := begin(r)
|
out := begin(r)
|
||||||
@@ -641,7 +641,7 @@ func sortedKeys(m map[string]string) []string {
|
|||||||
// idempotency check and the read-back. Running it first is how the host knows whether there is
|
// idempotency check and the read-back. Running it first is how the host knows whether there is
|
||||||
// anything to do — it does not know what a database is, so "is the database there" is a
|
// anything to do — it does not know what a database is, so "is the database there" is a
|
||||||
// question only the declaration can ask. Running it again afterwards is how the host knows the
|
// question only the declaration can ask. Running it again afterwards is how the host knows the
|
||||||
// command had the effect it claimed (novox/hq ADR 0047).
|
// command had the effect it claimed (novox/hq ADR 0005).
|
||||||
func applyAction(ctx context.Context, r *declaration.Action, run Runner) (Outcome, error) {
|
func applyAction(ctx context.Context, r *declaration.Action, run Runner) (Outcome, error) {
|
||||||
out := begin(r)
|
out := begin(r)
|
||||||
|
|
||||||
@@ -682,7 +682,7 @@ func runAction(ctx context.Context, r *declaration.Action, argv []string, run Ru
|
|||||||
//
|
//
|
||||||
// Two, because two exist on machines the mesh runs on. The list is short on purpose: each entry
|
// Two, because two exist on machines the mesh runs on. The list is short on purpose: each entry
|
||||||
// is a claim that its probe and its CLI have been checked, not that a binary of that name might
|
// is a claim that its probe and its CLI have been checked, not that a binary of that name might
|
||||||
// work (novox/hq ADR 0060).
|
// work (novox/hq ADR 0005).
|
||||||
//
|
//
|
||||||
// The probe differs and the rest does not, which is what makes this a lookup rather than an
|
// The probe differs and the rest does not, which is what makes this a lookup rather than an
|
||||||
// interface. `docker info --format {{.ServerVersion}}` fails on podman — the field does not
|
// interface. `docker info --format {{.ServerVersion}}` fails on podman — the field does not
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
"github.com/novox/mesh-host/internal/system"
|
"github.com/novox/mesh-host/internal/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Each test names the decision it defends (novox/hq ADR 0034).
|
// Each test names the decision it defends (novox/hq ADR 0017).
|
||||||
|
|
||||||
func parse(t *testing.T, raw string) *declaration.Declaration {
|
func parse(t *testing.T, raw string) *declaration.Declaration {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
@@ -87,7 +87,7 @@ func TestADriftedMachineIsReturned(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestADroppedResourceIsRemoved(t *testing.T) {
|
func TestADroppedResourceIsRemoved(t *testing.T) {
|
||||||
// novox/hq ADR 0043: the host removes what it previously applied and is no longer
|
// novox/hq ADR 0005: the host removes what it previously applied and is no longer
|
||||||
// declared. Removing a line from a declaration is an act with an effect.
|
// declared. Removing a line from a declaration is an act with an effect.
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
keep := filepath.Join(dir, "keep.conf")
|
keep := filepath.Join(dir, "keep.conf")
|
||||||
@@ -178,7 +178,7 @@ func TestARenameToTheSamePathDoesNotDeleteTheNewFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAFailedStepFailsTheApply(t *testing.T) {
|
func TestAFailedStepFailsTheApply(t *testing.T) {
|
||||||
// novox/hq ADR 0008. And the error carries what HAD been done, because the machine is in
|
// novox/hq ADR 0010. And the error carries what HAD been done, because the machine is in
|
||||||
// whatever state the apply reached and the only honest thing to hand back is that list.
|
// whatever state the apply reached and the only honest thing to hand back is that list.
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
blocker := filepath.Join(dir, "blocker")
|
blocker := filepath.Join(dir, "blocker")
|
||||||
@@ -214,7 +214,7 @@ func TestAFailedStepFailsTheApply(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNothingIsRecordedUntilItWorked(t *testing.T) {
|
func TestNothingIsRecordedUntilItWorked(t *testing.T) {
|
||||||
// novox/hq ADR 0035. A record written before the fact restates the request in a new place
|
// novox/hq ADR 0018. A record written before the fact restates the request in a new place
|
||||||
// and inherits none of the authority of having happened.
|
// and inherits none of the authority of having happened.
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
blocker := filepath.Join(dir, "blocker")
|
blocker := filepath.Join(dir, "blocker")
|
||||||
@@ -416,7 +416,7 @@ func TestForgettingAUnitThatIsGoneDoesNotStrandTheNode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- package, container and action (novox/hq 07-the-substrate.md, ADR 0046, ADR 0047) ---
|
// --- package, container and action (novox/hq 07-the-substrate.md, ADR 0006, ADR 0005) ---
|
||||||
|
|
||||||
func parseTrusted(t *testing.T, raw string) *declaration.Declaration {
|
func parseTrusted(t *testing.T, raw string) *declaration.Declaration {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
@@ -831,7 +831,7 @@ func TestAnUnknownBootStateIsRefusedNotGuessed(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- more than one container runtime (novox/hq ADR 0060) ---
|
// --- more than one container runtime (novox/hq ADR 0005) ---
|
||||||
|
|
||||||
func TestTheRuntimeProbeIsPerRuntime(t *testing.T) {
|
func TestTheRuntimeProbeIsPerRuntime(t *testing.T) {
|
||||||
// Verified against a real podman 6.1.0 before this was written:
|
// Verified against a real podman 6.1.0 before this was written:
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
// Package bundle is the declaration the host carries.
|
// Package bundle is the declaration the host carries.
|
||||||
//
|
//
|
||||||
// novox/hq ADR 0038: the host has one behaviour and two sources of declaration — the control
|
// 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
|
// 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.
|
// 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
|
// 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
|
// is the whole installation" (ADR 0005) stops being true the moment a second file has to
|
||||||
// arrive with it.
|
// arrive with it.
|
||||||
package bundle
|
package bundle
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
|
|
||||||
// One bundle per operating system, because its CONTENTS are per system even though its
|
// 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
|
// 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 —
|
// (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.
|
// an arch host never reads the alpine bundle.
|
||||||
//
|
//
|
||||||
// A host whose bundle is only comments carries nothing, and says so rather than applying
|
// A host whose bundle is only comments carries nothing, and says so rather than applying
|
||||||
@@ -76,7 +76,7 @@ func Load(system string) (*declaration.Declaration, error) {
|
|||||||
return nil, ErrEmpty
|
return nil, ErrEmpty
|
||||||
}
|
}
|
||||||
// ParseTrusted: the bundle arrives with the binary, so it may carry actions the link may
|
// 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
|
// 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.
|
// happens before there is any mesh to ask for one.
|
||||||
return declaration.ParseTrusted(stripComments(locks[system]))
|
return declaration.ParseTrusted(stripComments(locks[system]))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Data, never instructions. The vocabulary is finite, versioned, and anything outside it
|
// Data, never instructions. The vocabulary is finite, versioned, and anything outside it
|
||||||
// refuses the whole declaration rather than being skipped — a host that applied most of what
|
// refuses the whole declaration rather than being skipped — a host that applied most of what
|
||||||
// it was sent and reported success is a node that looks configured and is not
|
// it was sent and reported success is a node that looks configured and is not
|
||||||
// (novox/hq ADR 0043).
|
// (novox/hq ADR 0005).
|
||||||
package declaration
|
package declaration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -162,7 +162,7 @@ type Container struct {
|
|||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Type Type `json:"type"`
|
Type Type `json:"type"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
// Image is pinned by digest (novox/hq ADR 0046) — a tag moves and a digest does not.
|
// Image is pinned by digest (novox/hq ADR 0006) — a tag moves and a digest does not.
|
||||||
Image string `json:"image"`
|
Image string `json:"image"`
|
||||||
Env map[string]string `json:"env,omitempty"`
|
Env map[string]string `json:"env,omitempty"`
|
||||||
Ports []string `json:"ports,omitempty"`
|
Ports []string `json:"ports,omitempty"`
|
||||||
@@ -189,7 +189,7 @@ type Action struct {
|
|||||||
Command []string `json:"command"`
|
Command []string `json:"command"`
|
||||||
// Verify is not optional and is not a courtesy. It is the read-back AND the idempotency
|
// Verify is not optional and is not a courtesy. It is the read-back AND the idempotency
|
||||||
// check: the host does not know what a database is, so "is it already there" is a question
|
// check: the host does not know what a database is, so "is it already there" is a question
|
||||||
// only the declaration can ask (novox/hq ADR 0047).
|
// only the declaration can ask (novox/hq ADR 0005).
|
||||||
Verify []string `json:"verify"`
|
Verify []string `json:"verify"`
|
||||||
// In names a container to run inside. Empty means the machine itself.
|
// In names a container to run inside. Empty means the machine itself.
|
||||||
In string `json:"in,omitempty"`
|
In string `json:"in,omitempty"`
|
||||||
@@ -207,7 +207,7 @@ func (a *Action) Target() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Action) validate(where string, allowActions bool) []string {
|
func (a *Action) validate(where string, allowActions bool) []string {
|
||||||
// The bound the whole security argument rests on (novox/hq ADR 0047).
|
// The bound the whole security argument rests on (novox/hq ADR 0005).
|
||||||
if !allowActions {
|
if !allowActions {
|
||||||
return []string{where +
|
return []string{where +
|
||||||
": an action arrived over the link, and the link may not carry one. The host " +
|
": an action arrived over the link, and the link may not carry one. The host " +
|
||||||
@@ -264,7 +264,7 @@ type Declaration struct {
|
|||||||
// nothing to check against.
|
// nothing to check against.
|
||||||
For string
|
For string
|
||||||
// Resources, in the order they are applied. The host does not sort them: ordering is a
|
// Resources, in the order they are applied. The host does not sort them: ordering is a
|
||||||
// decision, and deciding is not what the host does (novox/hq ADR 0037).
|
// decision, and deciding is not what the host does (novox/hq ADR 0005).
|
||||||
Resources []Resource
|
Resources []Resource
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,14 +286,14 @@ func (e *RefusalError) Error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse reads a declaration that arrived over the link, and refuses anything it does not fully
|
// Parse reads a declaration that arrived over the link, and refuses anything it does not fully
|
||||||
// understand — including any action, which the link may not carry (novox/hq ADR 0047).
|
// understand — including any action, which the link may not carry (novox/hq ADR 0005).
|
||||||
func Parse(raw []byte) (*Declaration, error) { return parse(raw, false) }
|
func Parse(raw []byte) (*Declaration, error) { return parse(raw, false) }
|
||||||
|
|
||||||
// ParseTrusted reads a declaration from a source already as privileged as the host itself: the
|
// ParseTrusted reads a declaration from a source already as privileged as the host itself: the
|
||||||
// bundle it carries, or a file handed to it by someone who is running it as root.
|
// bundle it carries, or a file handed to it by someone who is running it as root.
|
||||||
//
|
//
|
||||||
// Actions are permitted here and nowhere else. The asymmetry is deliberate and is the entire
|
// Actions are permitted here and nowhere else. The asymmetry is deliberate and is the entire
|
||||||
// content of ADR 0047: refusing actions from the bundle buys nothing, because whoever built the
|
// content of ADR 0005: refusing actions from the bundle buys nothing, because whoever built the
|
||||||
// bundle built the binary; refusing them from the link buys the bound on what a compromised
|
// bundle built the binary; refusing them from the link buys the bound on what a compromised
|
||||||
// control plane can express.
|
// control plane can express.
|
||||||
func ParseTrusted(raw []byte) (*Declaration, error) { return parse(raw, true) }
|
func ParseTrusted(raw []byte) (*Declaration, error) { return parse(raw, true) }
|
||||||
@@ -450,7 +450,7 @@ func checkMode(where, mode string) []string {
|
|||||||
// checkImage insists on a digest.
|
// checkImage insists on a digest.
|
||||||
//
|
//
|
||||||
// A tag moves and a digest does not. The bundle's whole claim is that what it names is exact
|
// A tag moves and a digest does not. The bundle's whole claim is that what it names is exact
|
||||||
// (novox/hq ADR 0046), and a bundle pinning `postgres:17` pins nothing — it names whatever
|
// (novox/hq ADR 0006), and a bundle pinning `postgres:17` pins nothing — it names whatever
|
||||||
// that tag points at on the day the host happens to run.
|
// that tag points at on the day the host happens to run.
|
||||||
func checkImage(where, image string) []string {
|
func checkImage(where, image string) []string {
|
||||||
if image == "" {
|
if image == "" {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Each test names the decision it defends (novox/hq ADR 0034). The decision here is ADR 0043,
|
// Each test names the decision it defends (novox/hq ADR 0017). The decision here is ADR 0005,
|
||||||
// and the property it turns on is that unknown is REFUSED, never skipped.
|
// and the property it turns on is that unknown is REFUSED, never skipped.
|
||||||
|
|
||||||
func valid() string {
|
func valid() string {
|
||||||
@@ -160,7 +160,7 @@ func TestAnEmptyDeclarationIsAMistake(t *testing.T) {
|
|||||||
// --- the vocabulary the substrate bootstrap needs (novox/hq 07-the-substrate.md) ---
|
// --- the vocabulary the substrate bootstrap needs (novox/hq 07-the-substrate.md) ---
|
||||||
|
|
||||||
func TestAnActionOverTheLinkIsRefused(t *testing.T) {
|
func TestAnActionOverTheLinkIsRefused(t *testing.T) {
|
||||||
// novox/hq ADR 0047. The link may push declarations of known shape and never a command to
|
// novox/hq ADR 0005. The link may push declarations of known shape and never a command to
|
||||||
// run. This is the boundary the whole security argument rests on, so it is asserted
|
// run. This is the boundary the whole security argument rests on, so it is asserted
|
||||||
// directly rather than inferred from the type list.
|
// directly rather than inferred from the type list.
|
||||||
raw := []byte(`{"declaration":1,"resources":[
|
raw := []byte(`{"declaration":1,"resources":[
|
||||||
@@ -195,7 +195,7 @@ func TestAnActionWithoutVerifyIsRefused(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAnImageMustBePinnedByDigest(t *testing.T) {
|
func TestAnImageMustBePinnedByDigest(t *testing.T) {
|
||||||
// novox/hq ADR 0046: reproducibility comes from pinning the identity of a thing. A bundle
|
// novox/hq ADR 0006: reproducibility comes from pinning the identity of a thing. A bundle
|
||||||
// naming a tag pins nothing — it names whatever that tag points at on the day it runs.
|
// naming a tag pins nothing — it names whatever that tag points at on the day it runs.
|
||||||
for _, image := range []string{
|
for _, image := range []string{
|
||||||
"postgres:17",
|
"postgres:17",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Reported upward and never asked downward (novox/hq 03-DESIGN/01-to-be/05-the-node-host.md).
|
// Reported upward and never asked downward (novox/hq 03-DESIGN/01-to-be/05-the-node-host.md).
|
||||||
// Everything here is read from the machine at the moment of asking — nothing is remembered,
|
// Everything here is read from the machine at the moment of asking — nothing is remembered,
|
||||||
// nothing is derived from a file that says what the machine ought to be
|
// nothing is derived from a file that says what the machine ought to be
|
||||||
// (novox/hq ADR 0035).
|
// (novox/hq ADR 0018).
|
||||||
package inventory
|
package inventory
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -39,7 +39,7 @@ type Inventory struct {
|
|||||||
|
|
||||||
// ObservedAt is when this was read. An inventory with no timestamp cannot be told from a
|
// ObservedAt is when this was read. An inventory with no timestamp cannot be told from a
|
||||||
// stale one, and a node that has been unreachable for a week is an ordinary situation
|
// stale one, and a node that has been unreachable for a week is an ordinary situation
|
||||||
// (novox/hq ADR 0036) rather than an error — so the age of the observation is part of it.
|
// (novox/hq ADR 0004) rather than an error — so the age of the observation is part of it.
|
||||||
ObservedAt time.Time `json:"observed_at"`
|
ObservedAt time.Time `json:"observed_at"`
|
||||||
|
|
||||||
// Unreadable lists what could not be determined, and why. An absent field and a field that
|
// Unreadable lists what could not be determined, and why. An absent field and a field that
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ func TestMemoryIsReadOrReportedMissing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTheInventorySaysWhenItWasTaken(t *testing.T) {
|
func TestTheInventorySaysWhenItWasTaken(t *testing.T) {
|
||||||
// A node unreachable for a week is an ordinary situation (novox/hq ADR 0036), so an
|
// A node unreachable for a week is an ordinary situation (novox/hq ADR 0004), so an
|
||||||
// inventory that cannot be told from a stale one is missing the fact that matters.
|
// inventory that cannot be told from a stale one is missing the fact that matters.
|
||||||
before := time.Now().UTC()
|
before := time.Now().UTC()
|
||||||
inv := Collect(context.Background(), nil, nil, time.Second)
|
inv := Collect(context.Background(), nil, nil, time.Second)
|
||||||
@@ -79,7 +79,7 @@ func TestTheInventorySaysWhenItWasTaken(t *testing.T) {
|
|||||||
|
|
||||||
func TestTheHostDoesNotNameTheNode(t *testing.T) {
|
func TestTheHostDoesNotNameTheNode(t *testing.T) {
|
||||||
// A node's name is assigned by the mesh. A host that named itself would be deciding
|
// A node's name is assigned by the mesh. A host that named itself would be deciding
|
||||||
// something, which is precisely what novox/hq ADR 0037 forbids it to do.
|
// something, which is precisely what novox/hq ADR 0005 forbids it to do.
|
||||||
inv := Collect(context.Background(), nil, nil, time.Second)
|
inv := Collect(context.Background(), nil, nil, time.Second)
|
||||||
hostname, _ := os.Hostname()
|
hostname, _ := os.Hostname()
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ func TestTheHostDoesNotNameTheNode(t *testing.T) {
|
|||||||
// --- against this machine ---------------------------------------------------------------
|
// --- against this machine ---------------------------------------------------------------
|
||||||
|
|
||||||
func TestAgainstThisMachine_inventoryIsTrue(t *testing.T) {
|
func TestAgainstThisMachine_inventoryIsTrue(t *testing.T) {
|
||||||
// novox/hq ADR 0034: behaviour against a real system is tested alongside, not mocked.
|
// novox/hq ADR 0017: behaviour against a real system is tested alongside, not mocked.
|
||||||
inv := Collect(context.Background(), nil, profile.Default(nil), 10*time.Second)
|
inv := Collect(context.Background(), nil, profile.Default(nil), 10*time.Second)
|
||||||
|
|
||||||
if inv.Machine == "" {
|
if inv.Machine == "" {
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ func firstLine(s string) string {
|
|||||||
// privileged reports whether the host can change this machine at all.
|
// privileged reports whether the host can change this machine at all.
|
||||||
//
|
//
|
||||||
// Reported as a capability rather than checked at startup on purpose: a host that cannot act
|
// Reported as a capability rather than checked at startup on purpose: a host that cannot act
|
||||||
// is still a host that can report, and novox/hq ADR 0036 says what varies between nodes lives
|
// is still a host that can report, and novox/hq ADR 0004 says what varies between nodes lives
|
||||||
// here rather than in the definition of a node.
|
// here rather than in the definition of a node.
|
||||||
type privileged struct{}
|
type privileged struct{}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ type Detector interface {
|
|||||||
// Runner executes a command. Replaceable in tests for the pure-logic layer ONLY — every
|
// Runner executes a command. Replaceable in tests for the pure-logic layer ONLY — every
|
||||||
// detector in this package is exercised against the real machine as well, because a test that
|
// detector in this package is exercised against the real machine as well, because a test that
|
||||||
// fakes the system under detection asserts that the fake behaves as expected
|
// fakes the system under detection asserts that the fake behaves as expected
|
||||||
// (novox/hq ADR 0034).
|
// (novox/hq ADR 0017).
|
||||||
type Runner func(ctx context.Context, name string, args ...string) (stdout string, err error)
|
type Runner func(ctx context.Context, name string, args ...string) (stdout string, err error)
|
||||||
|
|
||||||
// ExecRunner runs a real command, with output captured and stdin closed.
|
// ExecRunner runs a real command, with output captured and stdin closed.
|
||||||
@@ -97,7 +97,7 @@ func (p Profile) Missing() []string {
|
|||||||
// Detect runs every detector and collects the verdicts.
|
// Detect runs every detector and collects the verdicts.
|
||||||
//
|
//
|
||||||
// A detector that fails does not fail the profile. This is deliberately NOT the rule in
|
// A detector that fails does not fail the profile. This is deliberately NOT the rule in
|
||||||
// novox/hq ADR 0008: that rule governs applying state, where a failed step means the machine
|
// novox/hq ADR 0010: that rule governs applying state, where a failed step means the machine
|
||||||
// is not what was asked for. Detection is the opposite — a failed probe is a finding, and the
|
// is not what was asked for. Detection is the opposite — a failed probe is a finding, and the
|
||||||
// finding is "absent, because the probe failed", which is exactly what a caller needs to know.
|
// finding is "absent, because the probe failed", which is exactly what a caller needs to know.
|
||||||
// Aborting would replace one legible absence with total ignorance.
|
// Aborting would replace one legible absence with total ignorance.
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Against the real machine. novox/hq ADR 0034: structure and logic are tested first, behaviour
|
// Against the real machine. novox/hq ADR 0017: structure and logic are tested first, behaviour
|
||||||
// against a real system alongside, and mocking the boundary is forbidden — a test that fakes
|
// against a real system alongside, and mocking the boundary is forbidden — a test that fakes
|
||||||
// the system under detection asserts that the fake behaves as expected.
|
// the system under detection asserts that the fake behaves as expected.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The decision each test defends is named in the test, per novox/hq ADR 0034. These cover
|
// The decision each test defends is named in the test, per novox/hq ADR 0017. These cover
|
||||||
// structure and logic; profile_system_test.go covers the same detectors against the real
|
// structure and logic; profile_system_test.go covers the same detectors against the real
|
||||||
// machine, because a test that fakes the system under detection asserts only that the fake
|
// machine, because a test that fakes the system under detection asserts only that the fake
|
||||||
// behaves as expected.
|
// behaves as expected.
|
||||||
@@ -57,7 +57,7 @@ func TestEveryVerdictSaysHowItKnows(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDetectionSurvivesAFailingProbe(t *testing.T) {
|
func TestDetectionSurvivesAFailingProbe(t *testing.T) {
|
||||||
// Deliberately NOT ADR 0008. That rule governs APPLYING state, where a failed step means
|
// Deliberately NOT ADR 0010. That rule governs APPLYING state, where a failed step means
|
||||||
// the machine is not what was asked for. A failed probe is a finding, and aborting would
|
// the machine is not what was asked for. A failed probe is a finding, and aborting would
|
||||||
// replace one legible absence with total ignorance of the rest.
|
// replace one legible absence with total ignorance of the rest.
|
||||||
only := func(ctx context.Context, name string, args ...string) (string, error) {
|
only := func(ctx context.Context, name string, args ...string) (string, error) {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
// Package store is what this node knows about itself, and it is authoritative while
|
// Package store is what this node knows about itself, and it is authoritative while
|
||||||
// disconnected.
|
// disconnected.
|
||||||
//
|
//
|
||||||
// Not a cache of the control plane. novox/hq ADR 0036 makes disconnection an ordinary
|
// Not a cache of the control plane. novox/hq ADR 0004 makes disconnection an ordinary
|
||||||
// situation rather than an exception, and this is what makes it ordinary: a machine shut for a
|
// situation rather than an exception, and this is what makes it ordinary: a machine shut for a
|
||||||
// week comes back and reconciles, it does not come back and ask what it is.
|
// week comes back and reconciles, it does not come back and ask what it is.
|
||||||
//
|
//
|
||||||
// Its first job arrives with the first apply rather than with the link (ADR 0043): the host
|
// Its first job arrives with the first apply rather than with the link (ADR 0005): the host
|
||||||
// removes what it previously applied and is no longer declared, and it can only know that
|
// removes what it previously applied and is no longer declared, and it can only know that
|
||||||
// because it wrote it down.
|
// because it wrote it down.
|
||||||
package store
|
package store
|
||||||
@@ -27,7 +27,7 @@ const DefaultPath = "/var/lib/mesh-host/state.json"
|
|||||||
|
|
||||||
// Applied is one resource the host put on this machine, and what it did.
|
// Applied is one resource the host put on this machine, and what it did.
|
||||||
//
|
//
|
||||||
// Recorded AFTER the resource was applied and read back, never before (novox/hq ADR 0035).
|
// Recorded AFTER the resource was applied and read back, never before (novox/hq ADR 0018).
|
||||||
// A record written up front restates the request in a new place and inherits none of the
|
// A record written up front restates the request in a new place and inherits none of the
|
||||||
// authority of having happened.
|
// authority of having happened.
|
||||||
type Applied struct {
|
type Applied struct {
|
||||||
@@ -164,7 +164,7 @@ func (s *State) Forget(id string) {
|
|||||||
//
|
//
|
||||||
// Reverse order because undoing in the order things were made undoes a directory before the
|
// Reverse order because undoing in the order things were made undoes a directory before the
|
||||||
// file inside it. Reversing is the only ordering the host can derive without deciding
|
// file inside it. Reversing is the only ordering the host can derive without deciding
|
||||||
// anything, which is the line novox/hq ADR 0037 draws.
|
// anything, which is the line novox/hq ADR 0005 draws.
|
||||||
func (s State) Orphans(declared map[string]bool) []Applied {
|
func (s State) Orphans(declared map[string]bool) []Applied {
|
||||||
var out []Applied
|
var out []Applied
|
||||||
for i := len(s.Resources) - 1; i >= 0; i-- {
|
for i := len(s.Resources) - 1; i >= 0; i-- {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
// way to run something — and refuses the other three. That is not a broken host: a declaration
|
// way to run something — and refuses the other three. That is not a broken host: a declaration
|
||||||
// naming a shape this host does not implement is refused whole, the same treatment an unknown
|
// naming a shape this host does not implement is refused whole, the same treatment an unknown
|
||||||
// type gets, and the profile tells the control plane which shapes exist so it never sends one
|
// type gets, and the profile tells the control plane which shapes exist so it never sends one
|
||||||
// it cannot do (novox/hq ADR 0060).
|
// it cannot do (novox/hq ADR 0005).
|
||||||
//
|
//
|
||||||
// What it cannot do, and why:
|
// What it cannot do, and why:
|
||||||
//
|
//
|
||||||
@@ -24,13 +24,13 @@ import (
|
|||||||
// and an unlocked bootloader. On a normal device nothing can register with it.
|
// and an unlocked bootloader. On a normal device nothing can register with it.
|
||||||
// - **container** — no container runtime, and no kernel access to give one.
|
// - **container** — no container runtime, and no kernel access to give one.
|
||||||
//
|
//
|
||||||
// **This host is EPISODIC** (novox/hq ADR 0062). Everywhere else an init runs the launcher at
|
// **This host is EPISODIC** (novox/hq ADR 0005). Everywhere else an init runs the launcher at
|
||||||
// boot and the launcher supervises the host. Android grants neither: nothing to register with
|
// boot and the launcher supervises the host. Android grants neither: nothing to register with
|
||||||
// without root, and nothing worth supervising, because a supervisor would be killed alongside
|
// without root, and nothing worth supervising, because a supervisor would be killed alongside
|
||||||
// what it supervises.
|
// what it supervises.
|
||||||
//
|
//
|
||||||
// So it runs when the platform allows and is killed when the platform wants the memory — and
|
// So it runs when the platform allows and is killed when the platform wants the memory — and
|
||||||
// that is **disconnection**, which ADR 0036 already made an ordinary situation rather than an
|
// that is **disconnection**, which ADR 0004 already made an ordinary situation rather than an
|
||||||
// exception. It needs no keep-alive and no new mechanism: the store is already authoritative
|
// exception. It needs no keep-alive and no new mechanism: the store is already authoritative
|
||||||
// while disconnected, reconcile already happens on start, and the mesh already reports *last
|
// while disconnected, reconcile already happens on start, and the mesh already reports *last
|
||||||
// heard from* rather than alarming on silence.
|
// heard from* rather than alarming on silence.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Package system is the part of the host that differs between operating systems.
|
// Package system is the part of the host that differs between operating systems.
|
||||||
//
|
//
|
||||||
// novox/hq ADR 0060. A machine has apk because it is Alpine; the package manager, the service
|
// novox/hq ADR 0005. A machine has apk because it is Alpine; the package manager, the service
|
||||||
// manager and the packaging format arrive together as one decision somebody made at install
|
// manager and the packaging format arrive together as one decision somebody made at install
|
||||||
// time. So they are not independent knobs — they are one implementation, named after the system
|
// time. So they are not independent knobs — they are one implementation, named after the system
|
||||||
// it belongs to.
|
// it belongs to.
|
||||||
@@ -75,7 +75,7 @@ func Supports(s System, t declaration.Type) bool {
|
|||||||
// Check refuses a declaration naming a shape this host cannot apply.
|
// Check refuses a declaration naming a shape this host cannot apply.
|
||||||
//
|
//
|
||||||
// Refused whole and before anything is applied, which is the same treatment an unknown type
|
// Refused whole and before anything is applied, which is the same treatment an unknown type
|
||||||
// gets (novox/hq ADR 0043) — a host that applied the parts it understood would leave a machine
|
// gets (novox/hq ADR 0005) — a host that applied the parts it understood would leave a machine
|
||||||
// that looks configured and is not. The reason differs and the outcome does not.
|
// that looks configured and is not. The reason differs and the outcome does not.
|
||||||
func Check(s System, d *declaration.Declaration) error {
|
func Check(s System, d *declaration.Declaration) error {
|
||||||
var problems []string
|
var problems []string
|
||||||
@@ -116,7 +116,7 @@ func everyShape() []declaration.Type {
|
|||||||
// portableShapes need only a filesystem and a way to run something.
|
// portableShapes need only a filesystem and a way to run something.
|
||||||
//
|
//
|
||||||
// The floor. A host that can do nothing else can still do these, which is what makes a partial
|
// The floor. A host that can do nothing else can still do these, which is what makes a partial
|
||||||
// host a real thing rather than a broken one (novox/hq ADR 0060).
|
// host a real thing rather than a broken one (novox/hq ADR 0005).
|
||||||
func portableShapes() []declaration.Type {
|
func portableShapes() []declaration.Type {
|
||||||
return []declaration.Type{
|
return []declaration.Type{
|
||||||
declaration.TypeDirectory, declaration.TypeFile, declaration.TypeAction,
|
declaration.TypeDirectory, declaration.TypeFile, declaration.TypeAction,
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ func TestOpenRCBootStateComesFromTheRunlevel(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEachSystemUsesItsOwnCommands(t *testing.T) {
|
func TestEachSystemUsesItsOwnCommands(t *testing.T) {
|
||||||
// The whole point of ADR 0060: the alpine host must never reach for systemctl, and the arch
|
// The whole point of ADR 0005: the alpine host must never reach for systemctl, and the arch
|
||||||
// host must never reach for rc-service.
|
// host must never reach for rc-service.
|
||||||
for _, tc := range []struct{ name, forbidden string }{
|
for _, tc := range []struct{ name, forbidden string }{
|
||||||
{"arch", "rc-service"},
|
{"arch", "rc-service"},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Package upgrade is how the host survives replacing itself.
|
// Package upgrade is how the host survives replacing itself.
|
||||||
//
|
//
|
||||||
// novox/hq ADR 0057 and ADR 0059. Two facts, and neither is the host judging its own health:
|
// novox/hq ADR 0005 and ADR 0005. Two facts, and neither is the host judging its own health:
|
||||||
//
|
//
|
||||||
// - whether the executable this process started from has been replaced on disk, which is how
|
// - whether the executable this process started from has been replaced on disk, which is how
|
||||||
// it knows to stand aside for a new one;
|
// it knows to stand aside for a new one;
|
||||||
@@ -42,7 +42,7 @@ type Self struct {
|
|||||||
//
|
//
|
||||||
// path is what os.Executable() returned; a test passes one it can manipulate, because the
|
// path is what os.Executable() returned; a test passes one it can manipulate, because the
|
||||||
// boundary being tested is the filesystem and a fake would assert that the fake behaves as
|
// boundary being tested is the filesystem and a fake would assert that the fake behaves as
|
||||||
// expected (novox/hq ADR 0034).
|
// expected (novox/hq ADR 0017).
|
||||||
func Current(path string) (Self, error) {
|
func Current(path string) (Self, error) {
|
||||||
info, err := os.Stat(path)
|
info, err := os.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Against the real filesystem rather than a fake one. What is being tested is how the operating
|
// Against the real filesystem rather than a fake one. What is being tested is how the operating
|
||||||
// system behaves when a file is replaced under a running process, and a fake would assert that
|
// system behaves when a file is replaced under a running process, and a fake would assert that
|
||||||
// the fake behaves as expected (novox/hq ADR 0034).
|
// the fake behaves as expected (novox/hq ADR 0017).
|
||||||
func started(t *testing.T) (Self, string) {
|
func started(t *testing.T) (Self, string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
binary := filepath.Join(t.TempDir(), "mesh-host")
|
binary := filepath.Join(t.TempDir(), "mesh-host")
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ done
|
|||||||
# These need the launcher to actually run as a supervisor rather than one iteration, so they do
|
# These need the launcher to actually run as a supervisor rather than one iteration, so they do
|
||||||
# not set MESH_HOST_RUN_ONCE.
|
# not set MESH_HOST_RUN_ONCE.
|
||||||
|
|
||||||
# A host that exits 0 has upgraded itself and stood aside (novox/hq ADR 0057). The launcher must
|
# A host that exits 0 has upgraded itself and stood aside (novox/hq ADR 0005). The launcher must
|
||||||
# start it again — and must NOT count it, because it did not fail.
|
# start it again — and must NOT count it, because it did not fail.
|
||||||
setup
|
setup
|
||||||
unset MESH_HOST_RUN_ONCE
|
unset MESH_HOST_RUN_ONCE
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# Supervise the host: start it, watch it, and decide what to do when it stops.
|
# Supervise the host: start it, watch it, and decide what to do when it stops.
|
||||||
#
|
#
|
||||||
# novox/hq ADR 0061. The init is asked for ONE thing — run this at boot — and everything else
|
# novox/hq ADR 0005. The init is asked for ONE thing — run this at boot — and everything else
|
||||||
# lives here, in a script that can be tested. Whether to restart, how long to wait, when to give
|
# lives here, in a script that can be tested. Whether to restart, how long to wait, when to give
|
||||||
# up, when to roll back: all of it is policy, and policy in a unit file can only be read and
|
# up, when to roll back: all of it is policy, and policy in a unit file can only be read and
|
||||||
# hoped for.
|
# hoped for.
|
||||||
@@ -109,7 +109,7 @@ while :; do
|
|||||||
case "$status" in
|
case "$status" in
|
||||||
0)
|
0)
|
||||||
# Exited cleanly. That is how the host stands aside for a new binary after an
|
# Exited cleanly. That is how the host stands aside for a new binary after an
|
||||||
# upgrade (novox/hq ADR 0057) — so loop and run whatever is now on disk.
|
# upgrade (novox/hq ADR 0005) — so loop and run whatever is now on disk.
|
||||||
#
|
#
|
||||||
# Deliberately NOT counted, and this is the whole reason the counter is
|
# Deliberately NOT counted, and this is the whole reason the counter is
|
||||||
# incremented here rather than before the start: counting attempts meant a host
|
# incremented here rather than before the start: counting attempts meant a host
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# Put the host back on the last version that worked.
|
# Put the host back on the last version that worked.
|
||||||
#
|
#
|
||||||
# novox/hq ADR 0059. This runs when nox-mesh-host will not start, so it shares no code with it
|
# novox/hq ADR 0005. This runs when nox-mesh-host will not start, so it shares no code with it
|
||||||
# and calls none of it: a binary that cannot start cannot be its own recovery. POSIX sh, no
|
# and calls none of it: a binary that cannot start cannot be its own recovery. POSIX sh, no
|
||||||
# bashisms, nothing that has to be installed.
|
# bashisms, nothing that has to be installed.
|
||||||
#
|
#
|
||||||
@@ -60,6 +60,6 @@ if ! pacman -U --noconfirm "$PKG"; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Deliberately does NOT start anything. The launcher called this and will exec the host next,
|
# Deliberately does NOT start anything. The launcher called this and will exec the host next,
|
||||||
# so starting it here would run two. novox/hq ADR 0061 moved that responsibility; this script
|
# so starting it here would run two. novox/hq ADR 0005 moved that responsibility; this script
|
||||||
# installs a version and says so, and nothing else.
|
# installs a version and says so, and nothing else.
|
||||||
say "rolled back to $VERSION. the launcher will start it."
|
say "rolled back to $VERSION. the launcher will start it."
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/sbin/openrc-run
|
#!/sbin/openrc-run
|
||||||
# The Alpine equivalent of the systemd unit beside this. Four lines of the same two facts:
|
# The Alpine equivalent of the systemd unit beside this. Four lines of the same two facts:
|
||||||
# run the launcher, and bring it back if it dies. Everything else is in the launcher, which is
|
# run the launcher, and bring it back if it dies. Everything else is in the launcher, which is
|
||||||
# what makes a second init transcription rather than a port (novox/hq ADR 0061).
|
# what makes a second init transcription rather than a port (novox/hq ADR 0005).
|
||||||
name="nox-mesh-host"
|
name="nox-mesh-host"
|
||||||
command="/usr/lib/nox-mesh-host/launch"
|
command="/usr/lib/nox-mesh-host/launch"
|
||||||
supervisor="supervise-daemon"
|
supervisor="supervise-daemon"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Description=Novox Mesh node host
|
|||||||
After=network-online.target
|
After=network-online.target
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
|
|
||||||
# One line of policy: run the launcher at boot (novox/hq ADR 0061). Restarting the host,
|
# One line of policy: run the launcher at boot (novox/hq ADR 0005). Restarting the host,
|
||||||
# backing off, giving up and rolling back are all the launcher's, where they can be tested.
|
# backing off, giving up and rolling back are all the launcher's, where they can be tested.
|
||||||
# Restart= here is a backstop for the launcher itself being killed, not the mechanism.
|
# Restart= here is a backstop for the launcher itself being killed, not the mechanism.
|
||||||
[Service]
|
[Service]
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ touch "$MESH_HOST_PKG_CACHE/nox-mesh-host-1.4.2-1-x86_64.pkg.tar.zst"
|
|||||||
check "installs the known-good version" "pacman is asked to install the cached package" \
|
check "installs the known-good version" "pacman is asked to install the cached package" \
|
||||||
"$(grep -c 'nox-mesh-host-1.4.2' "$MESH_HOST_STATE_DIR/pacman.calls" 2>/dev/null || echo 0)" "1"
|
"$(grep -c 'nox-mesh-host-1.4.2' "$MESH_HOST_STATE_DIR/pacman.calls" 2>/dev/null || echo 0)" "1"
|
||||||
# It installs and stops. The launcher execs the host next, and starting it here would run two
|
# It installs and stops. The launcher execs the host next, and starting it here would run two
|
||||||
# (novox/hq ADR 0061).
|
# (novox/hq ADR 0005).
|
||||||
check "does not start anything itself" "the launcher owns starting" \
|
check "does not start anything itself" "the launcher owns starting" \
|
||||||
"$([ -f "$MESH_HOST_STATE_DIR/systemctl.calls" ] && echo started || echo not-started)" "not-started"
|
"$([ -f "$MESH_HOST_STATE_DIR/systemctl.calls" ] && echo started || echo not-started)" "not-started"
|
||||||
check "records that it rolled back" "the attempted marker holds the version" \
|
check "records that it rolled back" "the attempted marker holds the version" \
|
||||||
|
|||||||
Reference in New Issue
Block a user