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
+1 -1
View File
@@ -18,7 +18,7 @@ The window manager as a module (novox/hq ADR 0208, research 026, to-be 42 phase
|---|---|---|
| `~/.config/i3/` | owned directory | its contents other than `config` are found and kept |
| `~/.config/i3/config.d/` | owned directory | other modules' drop-ins, and yours |
| `~/.config/i3/config` | owned (the found file kept once) | the main configuration, from [`config/config`](config/config) |
| `~/.config/i3/config` | owned (the found file kept once) | the main configuration, from [`files/config`](files/config) |
| `/etc/lemurs/wms/i3` | owned | the login manager's session entry: `exec systemd-cat -t x-session /bin/sh "$HOME/.xinitrc"`, the session's output in the journal (`journalctl -t x-session`) because the login manager's pipe has no reader |
**Drop-ins.** Another module adds to i3 with its own `~/.config/i3/config.d/<NN>-<module>.conf`. The
+60 -5
View File
@@ -28,6 +28,11 @@ type manifest struct {
Environment struct {
Variables map[string]string `json:"variables"`
} `json:"environment"`
Items []struct {
Collection string `json:"collection"`
Attributes map[string]string `json:"attributes"`
Template string `json:"template"`
} `json:"items"`
Shell []struct {
For, Slot, Code string
} `json:"shell"`
@@ -46,6 +51,23 @@ func readManifest(t *testing.T) manifest {
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatal(err)
}
// The module's code and variables are the templates of its items (design 57 L3, issues 795 and 807).
for _, it := range m.Items {
text, err := os.ReadFile(filepath.Join("..", "..", it.Template))
if err != nil {
t.Fatal(err)
}
parts := strings.Split(it.Collection, ":")
switch {
case len(parts) == 3 && parts[0] == "shell":
m.Shell = append(m.Shell, struct{ For, Slot, Code string }{parts[1], parts[2], string(text)})
case it.Collection == "environment" && it.Attributes["kind"] == "variable":
if m.Environment.Variables == nil {
m.Environment.Variables = map[string]string{}
}
m.Environment.Variables[it.Attributes["name"]] = strings.TrimSuffix(string(text), "\n")
}
}
return m
}
@@ -108,9 +130,10 @@ func TestItContributesTheSessionsExecLastAndTheDesktopsIdentity(t *testing.T) {
func TestTheConfigurationIsTheModulesFileImprovedAndEndsWithTheDropIns(t *testing.T) {
m := readManifest(t)
r := resource(t, m, "config")
raw, _ := os.ReadFile(filepath.Join("..", "..", "config", "config"))
if r["content"] != string(raw) || r["path"] != "${machine:account-home}/.config/i3/config" || r["into"] != nil {
t.Fatal("the manifest carries config/config whole, as an owned file")
raw, _ := os.ReadFile(filepath.Join("..", "..", "files", "config"))
if _, inline := r["content"]; inline || r["template"] != "files/config" || len(raw) == 0 ||
r["path"] != "${machine:account-home}/.config/i3/config" || r["into"] != nil {
t.Fatal("the manifest takes files/config whole as its template, as an owned file")
}
c := string(raw)
lines := strings.Split(strings.TrimSpace(c), "\n")
@@ -159,7 +182,11 @@ func TestTheLoginManagersEntryRunsTheSessionsStart(t *testing.T) {
if r["path"] != "/etc/lemurs/wms/i3" || r["mode"] != "0755" {
t.Fatalf("%v", r)
}
if !strings.Contains(r["content"].(string), `exec systemd-cat -t x-session /bin/sh "$HOME/.xinitrc"`) {
if r["template"] != "files/wms/i3" {
t.Fatalf("the entry's text is not its template files/wms/i3: %v", r["template"])
}
entry, _ := os.ReadFile(filepath.Join("..", "..", "files", "wms", "i3"))
if !strings.Contains(string(entry), `exec systemd-cat -t x-session /bin/sh "$HOME/.xinitrc"`) {
t.Fatal("the entry runs the session's start, never i3 bare")
}
pkgs := map[string]bool{}
@@ -219,7 +246,7 @@ func TestTheMainFileAndEveryModulesContributionLoadTogether(t *testing.T) {
if len(found) < 5 {
t.Fatalf("expected the launcher, clipboard, wallpaper, bars and the laptop to contribute; found %v", found)
}
main, _ := os.ReadFile(filepath.Join("..", "..", "config", "config"))
main, _ := os.ReadFile(filepath.Join("..", "..", "files", "config"))
text := strings.Replace(string(main), "${contribution:node-display-session:config}", placed.String(), 1)
// The operator's own files are not the catalogue's: the include line itself goes (a contribution may
// name it in a comment, so only the line that is exactly it).
@@ -230,3 +257,31 @@ func TestTheMainFileAndEveryModulesContributionLoadTogether(t *testing.T) {
t.Fatalf("with the contributions of %v: %v\n%s", found, err, out)
}
}
// 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) {
m := readManifest(t)
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 name, ok := r["template"].(string); ok {
if _, err := os.Stat(filepath.Join("..", "..", name)); err != nil {
t.Errorf("resource %v names %s: %v", r["id"], name, err)
}
}
}
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)
}
}
+1
View File
@@ -0,0 +1 @@
i3
+1
View File
@@ -0,0 +1 @@
i3
+9
View File
@@ -0,0 +1,9 @@
#!/bin/sh
# The i3 session (module i3, novox/hq ADR 0208), offered by the login manager. It runs the session's
# start, ~/.xinitrc, which the display server's module writes and which ends by starting i3.
# Replaced at every push.
#
# The login manager hands the session a stdout and stderr whose reading end nobody holds, so a
# program that writes to them dies of EPIPE (an Electron tray app shows it as an error dialog).
# The session's output goes to the journal instead, under `journalctl -t x-session`.
exec systemd-cat -t x-session /bin/sh "$HOME/.xinitrc"
+2
View File
@@ -0,0 +1,2 @@
# The session: i3, with 25 MiB of debug log kept in memory for i3-dump-log.
exec i3 --shmlog-size=26214400
+30 -12
View File
File diff suppressed because one or more lines are too long