Each node's reflector ranked its own list on its own timer, and on 2026-10-31 one node's lagging first mirror left it a Node.js version behind the rest. The list is now the module's state mirrors, key list: the node that opens a week's round ranks it with the module's reflector options, keeps only mirrors whose last sync is no older than the newest found, and the round's key names the list's revision. Every keeper writes /etc/pacman.d/mirrorlist whole from the key through the root helper's new verb mirrorlist, when the key changes and when the file differs, and never writes an empty or unreadable list. reflector.timer is declared stopped and disabled, and reflector.conf saves nothing. pacman_mirrors answers the shared list, its ranker and age and whether this node's file matches; with refresh it asks this node's keeper to rank again, refused while a round runs, naming it. The round's integration test proves the opening write and both nodes' files. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
136 lines
5.7 KiB
Go
136 lines
5.7 KiB
Go
package main
|
|
|
|
// The module's shape (novox/hq to-be 42 Phase 1, ADR 0207, research 027): it holds the
|
|
// node-package-manager seat and declares the package manager's own package; it owns pacman.conf
|
|
// whole — proven by pacman-conf on the rendered file, because a pacman.conf pacman cannot read is a
|
|
// machine that can neither install nor upgrade — and reflector's configuration, with the refresher
|
|
// and the cache cleaner on their timers.
|
|
|
|
import (
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestItHoldsThePackageManagerSeatAndDeclaresItsPackage(t *testing.T) {
|
|
m := manifest(t)
|
|
if len(m.Claims) != 1 || m.Claims[0]["name"] != "node-package-manager" || m.Claims[0]["scope"] != "node" || m.Claims[0]["serves"] != nil {
|
|
t.Fatalf("claims: %v", m.Claims)
|
|
}
|
|
for id, pkg := range map[string]string{"package": "pacman", "contrib": "pacman-contrib", "reflector": "reflector"} {
|
|
if r := m.resource(t, id); r["package"] != pkg || r["absent"] != nil {
|
|
t.Errorf("%s: %v", id, r)
|
|
}
|
|
}
|
|
// ADR 0585 §3: reflector's weekly timer is declared stopped and disabled, the mesh's list ranked in the round instead.
|
|
for id, unit := range map[string][3]string{"mirror-refresh": {"reflector.timer", "stopped", "disabled"},
|
|
"cache-cleaning": {"paccache.timer", "running", "enabled"}} {
|
|
r := m.resource(t, id)
|
|
if r["unit"] != unit[0] || r["state"] != unit[1] || r["boot"] != unit[2] {
|
|
t.Errorf("%s: %v", id, r)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPacmanConfIsWholeTheUnionOfRepositoriesAndTheImprovedOptions(t *testing.T) {
|
|
f := manifest(t).resource(t, "config")
|
|
content := templateText(t, f)
|
|
if f["path"] != "/etc/pacman.conf" || f["into"] != nil || !strings.HasPrefix(content, MeshHeader) {
|
|
t.Fatalf("%v", f)
|
|
}
|
|
var sections []string
|
|
for _, l := range strings.Split(content, "\n") {
|
|
if strings.HasPrefix(l, "[") {
|
|
sections = append(sections, l)
|
|
}
|
|
}
|
|
if strings.Join(sections, " ") != "[options] [core] [extra] [multilib]" {
|
|
t.Fatalf("sections: %v", sections)
|
|
}
|
|
for _, want := range []string{"\nColor\n", "\nCheckSpace\n", "\nVerbosePkgLists\n", "\nParallelDownloads = 5\n", "\nDownloadUser = alpm\n", "\nSigLevel = Required DatabaseOptional\n"} {
|
|
if !strings.Contains(content, want) {
|
|
t.Errorf("missing %q", strings.TrimSpace(want))
|
|
}
|
|
}
|
|
conf, err := exec.LookPath("pacman-conf")
|
|
if err != nil {
|
|
t.Skip("pacman-conf is not installed here; the rendered file is not proven")
|
|
}
|
|
file := filepath.Join(t.TempDir(), "pacman.conf")
|
|
if err := os.WriteFile(file, []byte(provenConf(t, content, filepath.Dir(file))), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
repos, err := exec.Command(conf, "--config", file, "--repo-list").CombinedOutput()
|
|
if err != nil || strings.Join(strings.Fields(string(repos)), " ") != "core extra multilib" {
|
|
t.Fatalf("pacman-conf --repo-list: %v\n%s", err, repos)
|
|
}
|
|
out, err := exec.Command(conf, "--config", file).CombinedOutput()
|
|
if err != nil {
|
|
t.Fatalf("pacman-conf refuses the file: %v\n%s", err, out)
|
|
}
|
|
c := ParseConf(string(out))
|
|
if c.Options["ParallelDownloads"][0] != "5" || c.Options["DownloadUser"] == nil || c.Options["Color"] == nil || c.Options["VerbosePkgLists"] == nil {
|
|
t.Fatalf("pacman-conf does not read the options as written: %v", c.Options)
|
|
}
|
|
}
|
|
|
|
// Every repository of the shipped file reads the mirror list reflector writes, and the file pacman-conf proves reads
|
|
// nothing of the machine: the unit level runs shut off (novox/hq ADR 0448), where /etc/pacman.d is not there
|
|
// (issue 1891).
|
|
func TestTheProvenConfReadsNothingOfTheMachine(t *testing.T) {
|
|
content := templateText(t, manifest(t).resource(t, "config"))
|
|
if n := strings.Count(content, "Include = "+Mirrorlist+"\n"); n != 3 || strings.Count(content, "Include") != n {
|
|
t.Fatalf("every repository reads %s, and nothing else is included: %d of 3\n%s", Mirrorlist, n, content)
|
|
}
|
|
dir := t.TempDir()
|
|
proven := provenConf(t, content, dir)
|
|
if strings.Contains(proven, "/etc/") {
|
|
t.Fatalf("the proven file still names the machine's /etc:\n%s", proven)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(dir, "mirrorlist")); err != nil {
|
|
t.Fatalf("the proven file's mirror list is not in its directory: %v", err)
|
|
}
|
|
}
|
|
|
|
// provenConf is the shipped pacman.conf with its repositories reading a mirror list of its own in dir, made there,
|
|
// so pacman-conf proves the file the module ships and reads nothing of the machine.
|
|
func provenConf(t *testing.T, content, dir string) string {
|
|
t.Helper()
|
|
list := filepath.Join(dir, "mirrorlist")
|
|
if err := os.WriteFile(list, []byte("Server = https://mirror.example/$repo/os/$arch\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return strings.ReplaceAll(content, "Include = "+Mirrorlist, "Include = "+list)
|
|
}
|
|
|
|
func TestReflectorRanksWithTheModulesOptionsAndSavesNoList(t *testing.T) {
|
|
content := templateText(t, manifest(t).resource(t, "mirrors"))
|
|
opts := map[string]string{}
|
|
for _, l := range strings.Split(content, "\n") {
|
|
if l == "" || strings.HasPrefix(l, "#") {
|
|
continue
|
|
}
|
|
k, v, _ := strings.Cut(l, " ")
|
|
opts[k] = v
|
|
}
|
|
// ADR 0585 §3: nothing saves a list but the keeper, from the mesh's.
|
|
if _, saves := opts["--save"]; saves || opts["--protocol"] != "https" || opts["--latest"] != "20" || opts["--sort"] != "rate" || opts["--country"] == "" {
|
|
t.Fatalf("%v", opts)
|
|
}
|
|
if manifest(t).resource(t, "mirrors")["path"] != ReflectorConf {
|
|
t.Fatal("reflector reads its options from " + ReflectorConf)
|
|
}
|
|
}
|
|
|
|
func TestTheReflectorPackageIsDeclaredBeforeTheFileItShips(t *testing.T) {
|
|
order := map[string]int{}
|
|
for i, r := range manifest(t).Resources {
|
|
order[r["id"].(string)] = i
|
|
}
|
|
if order["reflector"] > order["mirrors"] || order["contrib"] > order["cache-cleaning"] || order["reflector"] > order["mirror-refresh"] {
|
|
t.Fatalf("a package's file and timer come after the package: %v", order)
|
|
}
|
|
}
|