Feature 57 (config and provisioning): modules declare config and provisions, their files rendered from templates; synced with main (issues of feature 57, 874)

This commit is contained in:
2026-10-16 07:37:43 +00:00
parent 1a2c917abf
commit 9652b2d5ce
771 changed files with 52063 additions and 1774 deletions
@@ -0,0 +1,64 @@
package main
import (
"encoding/json"
"os"
"path/filepath"
"testing"
)
// The module's directory, where its manifest and its files/ are.
const moduleDir = "../.."
// templateText is the text a file resource places on the machine: its template's, in the module's files/
// (novox/hq ADR 0349 §2). A resource that names no template, or a template that is not there, fails the test.
func templateText(t *testing.T, r map[string]any) string {
t.Helper()
name, _ := r["template"].(string)
if name == "" {
t.Fatalf("resource %v names no template", r["id"])
}
raw, err := os.ReadFile(filepath.Join(moduleDir, filepath.FromSlash(name)))
if err != nil {
t.Fatalf("resource %v names %s: %v", r["id"], name, err)
}
return string(raw)
}
// No file resource carries its text inline: each names its template in files/, and the build packs files/
// into the one archive the controller reads the templates back from (novox/hq ADR 0349 §2).
func TestEveryFileTakesItsTextFromATemplate(t *testing.T) {
raw, err := os.ReadFile(filepath.Join(moduleDir, "module.json"))
if err != nil {
t.Fatal(err)
}
var m struct {
Resources []map[string]any `json:"resources"`
Build struct {
Artifacts []map[string]any `json:"artifacts"`
} `json:"build"`
}
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatal(err)
}
for _, r := range m.Resources {
if _, inline := r["content"]; inline {
t.Errorf("resource %v keeps its text inline: keep it in files/ and name it as the template", r["id"])
}
if r["type"] == "file" {
templateText(t, r)
}
}
packs := 0
for _, a := range m.Build.Artifacts {
if a["kind"] == "archive" && a["from"] == "files" {
packs++
if a["name"] != "files" {
t.Errorf("the templates archive is named %v, not files", a["name"])
}
}
}
if packs != 1 {
t.Errorf("the build packs files/ into %d archives, not one", packs)
}
}
+8
View File
@@ -0,0 +1,8 @@
# Managed by the mesh (module memory-pressure). Replaced on every push; edit the catalogue instead.
#
# systemd-oomd kills the worst unit before the kernel's OOM killer freezes the machine: when swap is
# 90 % used, or when a watched unit stalls on memory for 20 s above its limit.
[OOM]
SwapUsedLimit=90%
DefaultMemoryPressureLimit=60%
DefaultMemoryPressureDurationSec=20s
@@ -0,0 +1,5 @@
# Managed by the mesh (module memory-pressure). Replaced on every push; edit the catalogue instead.
#
# systemd-oomd may act on swap exhaustion across the whole machine.
[Slice]
ManagedOOMSwap=kill
@@ -0,0 +1,9 @@
# Managed by the mesh (module memory-pressure). Replaced on every push; edit the catalogue instead.
#
# Swap-in reads one page, not eight: read-ahead exists to amortise a disk's seek, and compressed swap in
# RAM has none — the speculation only costs decompression and memory.
vm.page-cluster = 0
#
# vm.swappiness is deliberately left alone. The usual zram advice (150-180) holds only while the
# compressed swap has room; once it is full, a higher swappiness moves pages to the disk swap, the thing
# being avoided. Raise it only together with zram-size.
@@ -0,0 +1,7 @@
# Managed by the mesh (module memory-pressure). Replaced on every push; edit the catalogue instead.
#
# systemd-oomd may kill the worst unit in a person's service manager when its memory pressure stays
# above 80 % — instead of the kernel freezing the machine.
[Service]
ManagedOOMMemoryPressure=kill
ManagedOOMMemoryPressureLimit=80%
@@ -0,0 +1,11 @@
# Managed by the mesh (module memory-pressure). Replaced on every push; edit the catalogue instead.
#
# Compressed swap in RAM: fast, at a higher priority than any swap on disk, so it takes the everyday
# pressure before anything spills to a disk. Half the RAM, at most 16 GiB, compressed with zstd.
# The swap on disk, its size and whether one exists at all are the machine's, not this module's.
#
# Read by the generator at boot: a change applies at the next boot.
[zram0]
zram-size = min(ram / 2, 16384)
compression-algorithm = zstd
swap-priority = 100
+10 -5
View File
@@ -28,7 +28,7 @@
"type": "file",
"path": "/etc/systemd/zram-generator.conf",
"mode": "0644",
"content": "# Managed by the mesh (module memory-pressure). Replaced on every push; edit the catalogue instead.\n#\n# Compressed swap in RAM: fast, at a higher priority than any swap on disk, so it takes the everyday\n# pressure before anything spills to a disk. Half the RAM, at most 16 GiB, compressed with zstd.\n# The swap on disk, its size and whether one exists at all are the machine's, not this module's.\n#\n# Read by the generator at boot: a change applies at the next boot.\n[zram0]\nzram-size = min(ram / 2, 16384)\ncompression-algorithm = zstd\nswap-priority = 100\n"
"template": "files/zram-generator.conf"
},
{
"id": "sysctl-drop-ins",
@@ -41,7 +41,7 @@
"type": "file",
"path": "/etc/sysctl.d/90-memory-pressure.conf",
"mode": "0644",
"content": "# Managed by the mesh (module memory-pressure). Replaced on every push; edit the catalogue instead.\n#\n# Swap-in reads one page, not eight: read-ahead exists to amortise a disk's seek, and compressed swap in\n# RAM has none — the speculation only costs decompression and memory.\nvm.page-cluster = 0\n#\n# vm.swappiness is deliberately left alone. The usual zram advice (150-180) holds only while the\n# compressed swap has room; once it is full, a higher swappiness moves pages to the disk swap, the thing\n# being avoided. Raise it only together with zram-size.\n"
"template": "files/sysctl.conf"
},
{
"id": "sysctl",
@@ -62,7 +62,7 @@
"type": "file",
"path": "/etc/systemd/oomd.conf.d/memory-pressure.conf",
"mode": "0644",
"content": "# Managed by the mesh (module memory-pressure). Replaced on every push; edit the catalogue instead.\n#\n# systemd-oomd kills the worst unit before the kernel's OOM killer freezes the machine: when swap is\n# 90 % used, or when a watched unit stalls on memory for 20 s above its limit.\n[OOM]\nSwapUsedLimit=90%\nDefaultMemoryPressureLimit=60%\nDefaultMemoryPressureDurationSec=20s\n"
"template": "files/oomd.conf"
},
{
"id": "root-slice-drop-ins",
@@ -75,7 +75,7 @@
"type": "file",
"path": "/etc/systemd/system/-.slice.d/10-oomd.conf",
"mode": "0644",
"content": "# Managed by the mesh (module memory-pressure). Replaced on every push; edit the catalogue instead.\n#\n# systemd-oomd may act on swap exhaustion across the whole machine.\n[Slice]\nManagedOOMSwap=kill\n"
"template": "files/root-slice.conf"
},
{
"id": "user-manager-drop-ins",
@@ -88,7 +88,7 @@
"type": "file",
"path": "/etc/systemd/system/user@.service.d/10-oomd.conf",
"mode": "0644",
"content": "# Managed by the mesh (module memory-pressure). Replaced on every push; edit the catalogue instead.\n#\n# systemd-oomd may kill the worst unit in a person's service manager when its memory pressure stays\n# above 80 % — instead of the kernel freezing the machine.\n[Service]\nManagedOOMMemoryPressure=kill\nManagedOOMMemoryPressureLimit=80%\n"
"template": "files/user-manager.conf"
},
{
"id": "oomd",
@@ -115,6 +115,11 @@
"loads": [
"memory-pressure"
]
},
{
"name": "files",
"kind": "archive",
"from": "files"
}
]
}