ADR 0262: one listing of every preference, and research 037 surveying the literals to convert
The operator asked for one place that lists every module's preferences and each machine's value, and for a list of the values the catalogues hard-code that should become preferences.
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
---
|
||||
status: active
|
||||
initiated: 2026-10-08
|
||||
touches:
|
||||
- 02-DECISIONS/0262-a-preference-has-a-default-in-its-manifest-and-the-operators-own-value-has-none.md
|
||||
- 03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md
|
||||
- the catalogue's module manifests
|
||||
- the media catalogue's module manifests
|
||||
became: []
|
||||
---
|
||||
|
||||
# 037 — The catalogue's hard-coded preferences
|
||||
|
||||
## What is investigated
|
||||
|
||||
[ADR 0262](../../02-DECISIONS/0262-a-preference-has-a-default-in-its-manifest-and-the-operators-own-value-has-none.md)
|
||||
lets a manifest give a **preference** a default, which the mesh-wide settings and a node's settings
|
||||
override. Before it, a value a person might want to tune per machine could only be written into a
|
||||
module's file as a literal. This effort lists those literals in both catalogues: the value, where it
|
||||
is, the key and default it would take as a preference, and why a person would change it.
|
||||
|
||||
## Why
|
||||
|
||||
The first case was the notifier's font size, which is less than half as tall on a laptop panel of
|
||||
about 240 DPI as on a desktop monitor of about 110 DPI, while X is told 96 on both. The same is true of
|
||||
every size, font and gap on the desktop, and of retention counts, timeouts and thresholds in the
|
||||
services. A list says which modules to convert, in what order, and which values are shared between
|
||||
modules and should keep one default.
|
||||
|
||||
## What it touches
|
||||
|
||||
The manifests of both catalogues, and through ADR 0262 the controller's `settings` verb, which lists
|
||||
every declared preference. Nothing is changed here: each conversion is a change to its module, made on
|
||||
the operator's yes.
|
||||
|
||||
## Documents
|
||||
|
||||
- [01 — the survey](01-the-survey.md): every candidate, by module, strongest first, with the modules
|
||||
that already take settings and those that have nothing to convert.
|
||||
@@ -0,0 +1,115 @@
|
||||
# 01 — The survey
|
||||
|
||||
Both catalogues were read on 2026-10-08: every manifest's file contents and the sources they are
|
||||
copied from, the shell code modules contribute to the login session, and the contributions to the
|
||||
display session. Nothing was changed. Each row is a literal that is a preference in the sense of
|
||||
[ADR 0262](../../02-DECISIONS/0262-a-preference-has-a-default-in-its-manifest-and-the-operators-own-value-has-none.md):
|
||||
true of the software, and only tuned per machine or per mesh. The proposed default is always today's
|
||||
value, so converting a module changes no machine until a value is set.
|
||||
|
||||
Values that are the operator's own (names, addresses, accounts, secrets) are left out, as are values the
|
||||
mesh assigns (`${port:…}`, `${dir:…}`, `${machine:…}`, `${bound:…}`, `${secret:…}`) and structure nobody
|
||||
tunes. Every proposed key passes ADR 0262's whole-word check.
|
||||
|
||||
**Where a literal sits decides what converting it needs.** A row marked *(shell)* is in code a module
|
||||
contributes to the login shell, and one marked *(contribution)* is in a contribution to the display
|
||||
session. ADR 0262 fills `${setting:…}` in files, contributions and served facts, but not in shell code,
|
||||
so the *(shell)* rows need the controller to fill shell code too before they can convert. A row marked
|
||||
*(compiled)* is in a module's own program and needs the program to read the value from a file first.
|
||||
|
||||
## The desktop, strongest first
|
||||
|
||||
| Module | Where | Today | Key | Why a person changes it |
|
||||
|---|---|---|---|---|
|
||||
| xorg | the X resources file | `Xft.dpi: 96` | `dpi` | A laptop panel of about 240 DPI and a desktop monitor of about 110 DPI want different values; the file's own comment already expects a setting |
|
||||
| xterm | X resources *(shell)* | `faceSize: 12`, face JetBrainsMono Nerd Font, `saveLines: 10000` | `font-size`, `font`, `scrollback-lines` | Terminal text size per screen; the face and history are taste |
|
||||
| i3 | its configuration | `font pango:JetBrainsMono Nerd Font 11` | `font-size` | Title text size follows the screen's density |
|
||||
| i3 | its configuration | `gaps inner 0`, `gaps outer 0`, `default_border pixel 1`, resize step 10 px | `inner-gap`, `outer-gap`, `border-width`, `resize-step` | Taste and density; a 1-pixel border is hard to see on a dense panel |
|
||||
| i3 | its configuration | focused colour `#de5200` | `accent-colour` | Taste; the same colour is in the bar and the launcher (see below) |
|
||||
| i3status-rust | the display-session contribution *(contribution)* | bar font size 11 | `font-size` | The bar's height follows it |
|
||||
| i3status-rust | the top bar | window title cut at 50 characters | `title-width` | How much title fits depends on the screen's width |
|
||||
| i3status-rust | the bottom bar | disk warning at 850 GB, critical at 920 GB | `disk-warning`, `disk-critical` | Thresholds written for one disk; wrong on a smaller laptop drive |
|
||||
| i3status-rust | the bottom bar | updates warn at 10, polled every 1800 s; clock format with seconds, every 1 s; container block every 2 s, disk every 20 s | `updates-threshold`, `updates-interval`, `clock-format`, `refresh-interval` | Polling costs battery; the clock is taste |
|
||||
| rofi | its theme | font size 11, width 600 px, 8 lines, icon 24 px, padding 10 px, message font size 11 | `font-size`, `width`, `lines`, `icon-size`, `padding`, `message-font-size` | The launcher's size per screen |
|
||||
| rofi | the power menu theme | width 800 px, font size 32 | `power-menu-width`, `power-menu-font-size` | Too large on a small panel. The power menu script also passes 350 px, which overrides 800: one of the two is dead |
|
||||
| rofi | its configuration | history of 25 | `history-size` | History length |
|
||||
| screen-lock | the session's X settings *(shell)* | lock after 1800 s; display standby 1800 s, off 3600 s | `lock-after`, `display-standby-after`, `display-off-after` | The clearest laptop and desktop difference |
|
||||
| screen-lock | its wrapper script | blur 5, ring width 7.0, accent `#ca4a00`, time and date formats | `blur`, `ring-width`, `accent-colour`, `clock-format`, `date-format` | Look, density and locale |
|
||||
| picom | its configuration | terminal opacity 0.95 focused and 0.75 not; tooltip 0.95; fade step 0.05; corner radius 0 | `terminal-opacity`, `inactive-terminal-opacity`, `tooltip-opacity`, `fade-step`, `corner-radius` | Taste |
|
||||
| picom | its configuration | `backend = "glx"` with vsync | `backend` | Depends on each machine's graphics driver |
|
||||
| adwaita | GTK 3 and 4 settings, Qt settings, and the GNOME settings *(shell)* | interface font Inter 11, monospace 11; cursor size 24 (also in X resources and GNOME settings); double-click 400 ms, 3 scroll lines | `font-size`, `cursor-size`, `double-click-interval`, `scroll-lines` | Interface size on a dense panel; the cursor size must change in all four places together |
|
||||
| fonts | the fontconfig defaults | monospace JetBrainsMono Nerd Font, sans Inter, serif Noto Serif | `monospace-face`, `sans-face`, `serif-face` | Taste; every desktop module names these faces again |
|
||||
| the laptop's hardware module | touchpad configuration and its script | acceleration 0.15, tapping on, natural scrolling on | `touchpad-speed`, `tap-to-click`, `natural-scrolling` | Pointer feel; the script repeats 0.15 |
|
||||
| the laptop's hardware module | the power daemon's battery levels | low 15 %, critical 10 %, act at 7 %, action suspend | `battery-low`, `battery-critical`, `battery-action-at`, `battery-action` | Thresholds move as a battery wears |
|
||||
| the laptop's hardware module | its scripts | brightness step 5; on-screen display for 1500 ms | `brightness-step`, `notification-timeout` | Feel |
|
||||
| feh | the wallpaper command | `--bg-fill` | `wallpaper-mode` | Fill, scale or centre is taste |
|
||||
| zsh | its rc file | `HISTSIZE` and `SAVEHIST` 100000 | `history-size` | History length |
|
||||
| lemurs | its configuration | background black, border white | `background-colour` | Low value; only the start of the file was read |
|
||||
|
||||
## Services
|
||||
|
||||
| Module | Where | Today | Key | Why a person changes it |
|
||||
|---|---|---|---|---|
|
||||
| restic | its program *(compiled)* | keep 14 daily, 8 weekly, 6 monthly | `keep-daily`, `keep-weekly`, `keep-monthly` | Retention is the clearest service preference, and today it cannot be changed without a new build |
|
||||
| logrotate | its configuration | weekly, keep 4; the login record monthly, keep 1 | `rotate-period`, `rotate-count`, `wtmp-rotate-count` | Retention per machine and disk |
|
||||
| docker | the prune unit and timer | prune what is older than 168 h, weekly | `prune-age`, `prune-schedule` | How long images and build cache stay |
|
||||
| docker | the daemon's configuration | no log rotation at all | `log-size`, `log-files` | **A new value, not a conversion:** container logs grow without bound today. A default such as 10 MB and 3 files changes every machine, so it is a decision of its own |
|
||||
| memory-pressure | the zram generator | `min(ram / 2, 16384)` | `zram-size` | RAM and workload |
|
||||
| memory-pressure | the out-of-memory daemon and the session's limit | swap used 90 %, pressure 60 % for 20 s; the session 80 % | `swap-used-limit`, `pressure-limit`, `pressure-duration`, `session-pressure-limit` | How early processes are killed |
|
||||
| time-sync | its servers | a regional pool | `pool-region` | The region is the mesh's |
|
||||
| pacman | its configuration and the mirror list's options | 5 parallel downloads; a country list, the latest 20, sorted by rate | `parallel-downloads`, `mirror-countries`, `mirror-count` | Bandwidth and region |
|
||||
| fail2ban | its jails | ban 1 day after 3 failures in 1 day; repeat offenders 4 weeks after 2 in 2 weeks | `ban-time`, `find-time`, `max-retries`, `repeat-ban-time`, `repeat-find-time`, `repeat-max-retries` | Strictness |
|
||||
| sshd | its configuration | port 22 | `listen-port` | A port people move. The filter and the jail name the port too, and must follow it |
|
||||
| dnsmasq | its configuration | two public resolvers | `upstream-resolvers` | The choice of resolver |
|
||||
| icecast | its configuration | client timeout 30 s, source 10 s, burst 65535, queue 524288, log level 3, log size 10000 | `client-timeout`, `source-timeout`, `burst-size`, `log-size` | Streaming tuning |
|
||||
| mailu | its environment file | message size 50 MB, 200 messages a day, sign-in rate limits, fetch every 600 s, log level | `message-size-limit`, `message-rate-limit`, `sign-in-rate-limit`, `fetch-delay`, `log-level` | Real limits a person sets |
|
||||
| matrix | the homeserver's configuration | request size 20 MB | `upload-size-limit` | The upload cap |
|
||||
| supabase | three files | a sign-in lifetime of 3600 s, written three times | `session-lifetime` | One value in three places, which a setting keeps equal |
|
||||
| supabase | the functions' main | 150 MB, 60 s | `function-memory-limit`, `function-time-limit` | Function limits |
|
||||
| power | its stop unit and script | 120 s, 30 s | (weak) | Bounds on shutdown work, rarely changed |
|
||||
|
||||
## The media catalogue
|
||||
|
||||
| Module | Where | Today | Key | Why |
|
||||
|---|---|---|---|---|
|
||||
| qbittorrent | its web login script | ban for 60 s | `ban-duration` | How long a failed login is locked out |
|
||||
| tautulli | its start script | wait 60 s for the media server | `media-server-wait` | Low value |
|
||||
|
||||
The other media modules carry only identities from settings, key-provisioning scripts and mergeable
|
||||
files. Nothing to convert.
|
||||
|
||||
## Already taking settings
|
||||
|
||||
- **`${setting:…}`:** the notifier (font size and width, the first preferences), power, hostname, xdg,
|
||||
keycloak, mailu, lab, and every media module.
|
||||
- **A mergeable file:** baserow, claude-code, the licence manager, confluence, gitea, gitlab, grafana,
|
||||
home-assistant, icecast, influxdb, jira, keycloak, letta, mailu, matrix, mesh-watcher, messenger,
|
||||
nextcloud, nodered, records, route-adapter, searxng, step-ca, supabase, unifi; in the media
|
||||
catalogue bazarr, bookshelf, jackett, kometa, lidarr, nzbget, ombi, qbittorrent, radarr, sonarr and
|
||||
tautulli. A mergeable file's content already is its defaults; ADR 0164's declaration would list its
|
||||
keys.
|
||||
|
||||
## Nothing to convert
|
||||
|
||||
avahi, blueman, bluetooth, build-agent, ca-trust, clipmenu (its 1-second timeout is a safety bound),
|
||||
cups, dbus, de-spiegel, disk-load, distribution, dmenu, gnome-keyring, hello-web, invoicing, letta,
|
||||
local-model-consumer, localization (the language and keymap are the operator's, not a preference),
|
||||
mesh-catalog, minio, model-usage, mongodb, mosquitto, mssql, n8n, nats, networkmanager, nftables,
|
||||
node-env, only-office, redis, route-proxy, ssh-client, sudo, systemd, systemd-networkd,
|
||||
systemd-resolved, triggerhappy, umami, xclip. A container's internal port is structure, not a
|
||||
preference.
|
||||
|
||||
## What follows from it
|
||||
|
||||
- **Shared values want one default each.** The accent colour is `#de5200` in the window manager, the
|
||||
bar and the launcher, and `#ca4a00` in the lock screen. The interface font size is 11 in the window
|
||||
manager, the bar, the launcher and the GTK, Qt and GNOME settings, and 12 in the terminal. As one
|
||||
setting per module they would drift. Either each module declares the same default and a person sets
|
||||
them together, or the mesh grows a preference several modules read. That is a decision, not a
|
||||
conversion.
|
||||
- **Three prerequisites in the controller and the modules:** filling `${setting:…}` in shell code, for
|
||||
the *(shell)* rows; the restic program reading its retention from a file; and, for the container
|
||||
runtime's log rotation, a decision on the new default.
|
||||
- **Order:** the density rows first (xorg's DPI, then the font sizes of the window manager, the bar,
|
||||
the launcher, the terminal and the interface, and the cursor size), because the laptop panel is the
|
||||
case that started this. Then the lock and display timeouts, then retention and thresholds.
|
||||
+10
@@ -87,6 +87,13 @@ reads before replacing them ([ADR 0217](0217-no-change-to-a-machine-takes-effect
|
||||
each key the module gives a default or that is set, its effective value, and its source: `default`,
|
||||
the mesh, or the node. Where the mesh or the node overrides a default, the default is shown beside it.
|
||||
|
||||
**One listing of every preference, in the controller's `settings` verb.** Asked with `list:
|
||||
"preferences"`, or with no module, `settings` lists every module that declares a preference: each key,
|
||||
its default and why, and, for every machine the module is assigned to, the value there and where it
|
||||
comes from (the default, the mesh, or the node). A module and a node narrow it. No module builds a
|
||||
settings tool of its own; this verb is the interface for all of them, so a person or an agent finds every
|
||||
preference in one place.
|
||||
|
||||
**A default must be read by something.** A declared key that no file, contribution or served fact asks
|
||||
for is refused, because a default that reaches nothing is the same mistake as a stray setting.
|
||||
|
||||
@@ -139,6 +146,7 @@ rolled back. The same holds for every field a later record adds.
|
||||
| A default is read by the installation check | `TestTheInstallationCheckReadsADefault`, including a setting called `description` |
|
||||
| A default fills a contribution and a served fact, and never replaces a contribution's own value | `TestADefaultFillsAContributionAndAServedFactButNotALiteral` |
|
||||
| A node called `default` is a node | `TestANodeCalledDefaultIsANodesLayer` |
|
||||
| One listing of every preference, per machine with its source | `TestSettingsListPreferences` (the verb's arguments, a listing other than preferences refused) and `TestPreferencesSayEachMachinesValueAndItsSource`; live, `mesh-controller.settings {list: "preferences"}` lists the notifier on both machines |
|
||||
| A stored manifest with an unknown key is read, and registration refuses it | `TestAStoredManifestWithAnUnknownKeyIsReadAndRegistrationRefusesIt` |
|
||||
| Nothing changes where no machine sets a value | The notifier's manifest test fills the file with the defaults and finds today's lines; live, `mesh-controller.plan` for the workstation shows no change to the notifier's file after the catalogue change is sent |
|
||||
|
||||
@@ -154,4 +162,6 @@ rolled back. The same holds for every field a later record adds.
|
||||
before replacing them
|
||||
- [Design 27](../03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md): the operator as a
|
||||
provider
|
||||
- [Research 037](../01-RESEARCH/037-the-catalogues-hard-coded-preferences/00-overview.md): the literals in
|
||||
both catalogues that would become preferences
|
||||
- mesh-controller `internal/catalogue/setting_defaults.go`; mesh-catalog `modules/dunst`
|
||||
|
||||
Reference in New Issue
Block a user