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
+4 -4
View File
@@ -15,10 +15,10 @@ or reads `/proc`. It is for the two workstations only.
| path | class (ADR 0182) | from |
|---|---|---|
| `~/.config/user-dirs.dirs` | owned, the found file kept once | [`config/user-dirs.dirs`](config/user-dirs.dirs), three folders from settings |
| `~/.config/user-dirs.locale` | owned, the found file kept once | [`config/user-dirs.locale`](config/user-dirs.locale) |
| `~/.config/user-dirs.conf` | owned, new | [`config/user-dirs.conf`](config/user-dirs.conf): `enabled=False` |
| `/etc/xdg/mimeapps.list` | owned, new | [`config/mimeapps.list`](config/mimeapps.list): the machine's default applications |
| `~/.config/user-dirs.dirs` | owned, the found file kept once | [`files/user-dirs.dirs`](files/user-dirs.dirs), three folders from settings |
| `~/.config/user-dirs.locale` | owned, the found file kept once | [`files/user-dirs.locale`](files/user-dirs.locale) |
| `~/.config/user-dirs.conf` | owned, new | [`files/user-dirs.conf`](files/user-dirs.conf): `enabled=False` |
| `/etc/xdg/mimeapps.list` | owned, new | [`files/mimeapps.list`](files/mimeapps.list): the machine's default applications |
## What it does not own, and why
+4 -4
View File
@@ -55,16 +55,16 @@ func app(name, exec, mime string) string {
// The desktop's folders as found on 2026-10-05, in the module's file with that machine's settings.
func desktopUserDirs(t *testing.T) string {
raw, err := os.ReadFile(filepath.Join("..", "..", "config", "user-dirs.dirs"))
raw, err := os.ReadFile(filepath.Join("..", "..", "files", "user-dirs.dirs"))
if err != nil {
t.Fatal(err)
}
r := strings.NewReplacer("${setting:desktop-dir}", "$HOME/", "${setting:publicshare-dir}", "$HOME/", "${setting:videos-dir}", "$HOME/")
r := strings.NewReplacer("${config:desktop-dir}", "$HOME/", "${config:publicshare-dir}", "$HOME/", "${config:videos-dir}", "$HOME/")
return r.Replace(string(raw))
}
func TestTheFoldersAreReadAsXdgUserDirResolvesThem(t *testing.T) {
conf, _ := os.ReadFile(filepath.Join("..", "..", "config", "user-dirs.conf"))
conf, _ := os.ReadFile(filepath.Join("..", "..", "files", "user-dirs.conf"))
m := fake(t, map[string]string{
home + "/.config/user-dirs.dirs": desktopUserDirs(t),
home + "/.config/user-dirs.conf": string(conf),
@@ -159,7 +159,7 @@ func TestADuplicateGroupOrKeyIsReportedNotMerged(t *testing.T) {
// A machine with the mesh's list, the person's list, and some applications.
func mimeMachine(t *testing.T, extra map[string]string) *Machine {
mesh, err := os.ReadFile(filepath.Join("..", "..", "config", "mimeapps.list"))
mesh, err := os.ReadFile(filepath.Join("..", "..", "files", "mimeapps.list"))
if err != nil {
t.Fatal(err)
}
+36 -7
View File
@@ -74,9 +74,10 @@ func TestItOwnsTheFilesItsSourcesHold(t *testing.T) {
continue
}
files++
src, _ := os.ReadFile(filepath.Join("..", "..", "config", w[2]))
if r["path"] != w[0] || r["mode"] != w[1] || r["content"] != string(src) || r["into"] != nil {
t.Errorf("%s is not config/%s, whole, at %s mode %s: %v", id, w[2], w[0], w[1], r["path"])
_, inline := r["content"]
if _, err := os.Stat(filepath.Join("..", "..", "files", w[2])); err != nil || inline ||
r["template"] != "files/"+w[2] || r["path"] != w[0] || r["mode"] != w[1] || r["into"] != nil {
t.Errorf("%s is not files/%s, whole, as its template, at %s mode %s: %v", id, w[2], w[0], w[1], r["path"])
}
home := strings.HasPrefix(w[0], "${machine:account-home}/")
if home != (r["owner"] == "${machine:account}") {
@@ -100,9 +101,9 @@ func TestItOwnsTheFilesItsSourcesHold(t *testing.T) {
}
func TestTheFoldersFileIsXdgUserDirsOwnFormatWithThreeSettings(t *testing.T) {
raw, _ := os.ReadFile(filepath.Join("..", "..", "config", "user-dirs.dirs"))
raw, _ := os.ReadFile(filepath.Join("..", "..", "files", "user-dirs.dirs"))
text := string(raw)
settings := regexp.MustCompile(`\$\{setting:([a-z0-9-]+)\}`).FindAllStringSubmatch(text, -1)
settings := regexp.MustCompile(`\$\{config:([a-z0-9-]+)\}`).FindAllStringSubmatch(text, -1)
var names []string
for _, s := range settings {
names = append(names, s[1])
@@ -128,14 +129,14 @@ func TestTheFoldersFileIsXdgUserDirsOwnFormatWithThreeSettings(t *testing.T) {
if !strings.Contains(firstLines(text, 3), meshMark) {
t.Error("the header xdg_check recognises the file by")
}
conf, _ := os.ReadFile(filepath.Join("..", "..", "config", "user-dirs.conf"))
conf, _ := os.ReadFile(filepath.Join("..", "..", "files", "user-dirs.conf"))
if !regexp.MustCompile(`(?m)^enabled=False$`).Match(conf) {
t.Error("xdg-user-dirs-update must be disabled, or it rewrites the mesh's file at login")
}
}
func TestTheMeshsDefaultsNameEachTypeOnceInOneGroupEach(t *testing.T) {
raw, _ := os.ReadFile(filepath.Join("..", "..", "config", "mimeapps.list"))
raw, _ := os.ReadFile(filepath.Join("..", "..", "files", "mimeapps.list"))
k := ParseKeyFile(string(raw))
if len(k.DupGroups)+len(k.DupKeys)+len(k.Unparsable) != 0 {
t.Fatalf("%+v", k)
@@ -212,3 +213,31 @@ func TestTheFoldersFileStaysTrustedUntilSettingsAreQuoted(t *testing.T) {
}
t.Fatal("no user-dirs resource")
}
// 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)
}
}
@@ -6,12 +6,12 @@
# xdg-user-dirs-update never rewrites this file: user-dirs.conf beside it says enabled=False. Each
# line is xdg-user-dirs' only format, XDG_xxx_DIR="$HOME/yyy" or XDG_xxx_DIR="/yyy". "$HOME/" is
# the home itself: the folder is not wanted. xdg_user_dirs says what each one resolves to.
XDG_DESKTOP_DIR="${setting:desktop-dir}"
XDG_DESKTOP_DIR="${config:desktop-dir}"
XDG_DOWNLOAD_DIR="$HOME/Downloads"
XDG_TEMPLATES_DIR="$HOME/Templates"
XDG_PUBLICSHARE_DIR="${setting:publicshare-dir}"
XDG_PUBLICSHARE_DIR="${config:publicshare-dir}"
XDG_DOCUMENTS_DIR="$HOME/Documents"
XDG_MUSIC_DIR="$HOME/Music"
XDG_PICTURES_DIR="$HOME/Pictures"
XDG_VIDEOS_DIR="${setting:videos-dir}"
XDG_VIDEOS_DIR="${config:videos-dir}"
XDG_PROJECTS_DIR="$HOME/projects"
+29 -4
View File
@@ -11,6 +11,26 @@
"xdg_autostart",
"xdg_check"
],
"config": {
"desktop-dir": {
"type": "string",
"description": "The desktop folder, as user-dirs.dirs writes it: \"$HOME/<folder>\", or \"$HOME/\" when the folder is not wanted; it differs between the workstations, so each says its own.",
"trusted": true,
"required": true
},
"publicshare-dir": {
"type": "string",
"description": "The public share folder, as user-dirs.dirs writes it: \"$HOME/<folder>\", or \"$HOME/\" when the folder is not wanted; it differs between the workstations, so each says its own.",
"trusted": true,
"required": true
},
"videos-dir": {
"type": "string",
"description": "The videos folder, as user-dirs.dirs writes it: \"$HOME/<folder>\", or \"$HOME/\" when the folder is not wanted; it differs between the workstations, so each says its own.",
"trusted": true,
"required": true
}
},
"resources": [
{
"id": "package-xdg-user-dirs",
@@ -28,7 +48,7 @@
"path": "${machine:account-home}/.config/user-dirs.dirs",
"owner": "${machine:account}",
"mode": "0600",
"content": "# The account's folders (module xdg, novox/hq ADR 0182), written by the mesh and replaced at every\n# push. Adopted from the workstations' own files of 2026-10-05. A folder that differs between the\n# machines is a setting of this module; every other one is the same everywhere. PROJECTS names the\n# folder the operator's repositories are in (it pointed at the home, or was absent).\n#\n# xdg-user-dirs-update never rewrites this file: user-dirs.conf beside it says enabled=False. Each\n# line is xdg-user-dirs' only format, XDG_xxx_DIR=\"$HOME/yyy\" or XDG_xxx_DIR=\"/yyy\". \"$HOME/\" is\n# the home itself: the folder is not wanted. xdg_user_dirs says what each one resolves to.\nXDG_DESKTOP_DIR=\"${setting:desktop-dir}\"\nXDG_DOWNLOAD_DIR=\"$HOME/Downloads\"\nXDG_TEMPLATES_DIR=\"$HOME/Templates\"\nXDG_PUBLICSHARE_DIR=\"${setting:publicshare-dir}\"\nXDG_DOCUMENTS_DIR=\"$HOME/Documents\"\nXDG_MUSIC_DIR=\"$HOME/Music\"\nXDG_PICTURES_DIR=\"$HOME/Pictures\"\nXDG_VIDEOS_DIR=\"${setting:videos-dir}\"\nXDG_PROJECTS_DIR=\"$HOME/projects\"\n"
"template": "files/user-dirs.dirs"
},
{
"id": "user-dirs-locale",
@@ -36,7 +56,7 @@
"path": "${machine:account-home}/.config/user-dirs.locale",
"owner": "${machine:account}",
"mode": "0644",
"content": "en_US\n"
"template": "files/user-dirs.locale"
},
{
"id": "user-dirs-update",
@@ -44,14 +64,14 @@
"path": "${machine:account-home}/.config/user-dirs.conf",
"owner": "${machine:account}",
"mode": "0644",
"content": "# xdg-user-dirs-update for this account (module xdg, novox/hq ADR 0182), written by the mesh and\n# replaced at every push.\n#\n# The update runs at every login (its XDG autostart entry, which the session's dex starts). Enabled,\n# it reassigns a folder it cannot find to the home and adds a line for every folder its defaults\n# know, rewriting user-dirs.dirs, which is the mesh's. Disabled, it does nothing, even with --force.\n# The folders are this module's to say.\nenabled=False\nfilename_encoding=UTF-8\n"
"template": "files/user-dirs.conf"
},
{
"id": "default-applications",
"type": "file",
"path": "/etc/xdg/mimeapps.list",
"mode": "0644",
"content": "# The machine's default applications (module xdg, novox/hq ADR 0182), written by the mesh and\n# replaced at every push.\n#\n# This is the lowest list an application consults. The account's own ~/.config/mimeapps.list comes\n# first: every program that offers \"always open with\" writes there, and so does xdg_default. That\n# file is the person's and is never written by the mesh, so a choice made there always wins; this\n# list answers only what the person has not chosen. Adopted from the workstations' own lists of\n# 2026-10-05, without the entries naming an application neither workstation has installed (see the\n# module's README).\n[Default Applications]\napplication/pdf=firefox.desktop;\napplication/x-extension-htm=firefox.desktop;\napplication/x-extension-shtml=firefox.desktop;\napplication/x-extension-xht=firefox.desktop;\napplication/x-extension-xhtml=firefox.desktop;\napplication/xhtml+xml=firefox.desktop;\ntext/html=firefox.desktop;\nx-scheme-handler/chrome=firefox.desktop;\nx-scheme-handler/ftp=firefox.desktop;\nx-scheme-handler/http=firefox.desktop;\nx-scheme-handler/https=firefox.desktop;\ntext/markdown=org.kde.ghostwriter.desktop;\ninode/directory=thunar.desktop;\nx-scheme-handler/abc=plexamp.desktop;\nx-scheme-handler/slack=slack.desktop;\nx-scheme-handler/tg=org.telegram.desktop.desktop;\n\n[Added Associations]\nvideo/mp4=mpv.desktop;mplayer.desktop;vlc.desktop;\n"
"template": "files/mimeapps.list"
}
],
"build": {
@@ -66,6 +86,11 @@
"loads": [
"xdg-tools"
]
},
{
"name": "files",
"kind": "archive",
"from": "files"
}
]
}