diff --git a/go/testdata/beside/mesh-catalog/modules/adwaita/files/cursor-index.theme b/go/testdata/beside/mesh-catalog/modules/adwaita/files/cursor-index.theme
new file mode 100644
index 0000000..d848240
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/adwaita/files/cursor-index.theme
@@ -0,0 +1,5 @@
+# Written by the mesh (module adwaita, novox/hq ADR 0208): the default cursor theme, for programs that
+# read neither XCURSOR_THEME nor the X resources.
+[Icon Theme]
+Name=Default
+Inherits=Adwaita
diff --git a/go/testdata/beside/mesh-catalog/modules/adwaita/files/env-gtk-theme b/go/testdata/beside/mesh-catalog/modules/adwaita/files/env-gtk-theme
new file mode 100644
index 0000000..b98c809
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/adwaita/files/env-gtk-theme
@@ -0,0 +1 @@
+Adwaita:dark
diff --git a/go/testdata/beside/mesh-catalog/modules/adwaita/files/env-gtk2-rc-files b/go/testdata/beside/mesh-catalog/modules/adwaita/files/env-gtk2-rc-files
new file mode 100644
index 0000000..8a782fb
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/adwaita/files/env-gtk2-rc-files
@@ -0,0 +1 @@
+/usr/share/themes/Adwaita-dark/gtk-2.0/gtkrc
diff --git a/go/testdata/beside/mesh-catalog/modules/adwaita/files/env-qt-qpa-platformtheme b/go/testdata/beside/mesh-catalog/modules/adwaita/files/env-qt-qpa-platformtheme
new file mode 100644
index 0000000..349e5d6
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/adwaita/files/env-qt-qpa-platformtheme
@@ -0,0 +1 @@
+qt6ct
diff --git a/go/testdata/beside/mesh-catalog/modules/adwaita/files/env-qt-select b/go/testdata/beside/mesh-catalog/modules/adwaita/files/env-qt-select
new file mode 100644
index 0000000..1e8b314
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/adwaita/files/env-qt-select
@@ -0,0 +1 @@
+6
diff --git a/go/testdata/beside/mesh-catalog/modules/adwaita/files/env-qt-style-override b/go/testdata/beside/mesh-catalog/modules/adwaita/files/env-qt-style-override
new file mode 100644
index 0000000..a0da7db
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/adwaita/files/env-qt-style-override
@@ -0,0 +1 @@
+Fusion
diff --git a/go/testdata/beside/mesh-catalog/modules/adwaita/files/env-xcursor-size b/go/testdata/beside/mesh-catalog/modules/adwaita/files/env-xcursor-size
new file mode 100644
index 0000000..a45fd52
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/adwaita/files/env-xcursor-size
@@ -0,0 +1 @@
+24
diff --git a/go/testdata/beside/mesh-catalog/modules/adwaita/files/env-xcursor-theme b/go/testdata/beside/mesh-catalog/modules/adwaita/files/env-xcursor-theme
new file mode 100644
index 0000000..cb9676b
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/adwaita/files/env-xcursor-theme
@@ -0,0 +1 @@
+Adwaita
diff --git a/go/testdata/beside/mesh-catalog/modules/adwaita/files/gtk-settings.ini b/go/testdata/beside/mesh-catalog/modules/adwaita/files/gtk-settings.ini
new file mode 100644
index 0000000..a535ba4
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/adwaita/files/gtk-settings.ini
@@ -0,0 +1,9 @@
+# Written by the mesh (module adwaita, novox/hq ADR 0208), for GTK 3 and GTK 4 alike. Replaced at
+# every push; adwaita_appearance switches dark and light for the running session.
+[Settings]
+gtk-theme-name=Adwaita
+gtk-icon-theme-name=Adwaita
+gtk-cursor-theme-name=Adwaita
+gtk-cursor-theme-size=24
+gtk-font-name=Inter 11
+gtk-application-prefer-dark-theme=1
diff --git a/go/testdata/beside/mesh-catalog/modules/adwaita/files/portals.conf b/go/testdata/beside/mesh-catalog/modules/adwaita/files/portals.conf
new file mode 100644
index 0000000..d2e813f
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/adwaita/files/portals.conf
@@ -0,0 +1,11 @@
+# Written by the mesh (module adwaita, novox/hq ADR 0208). Replaced at every push.
+#
+# Which portal backend answers each interface. i3 is not a desktop xdg-desktop-portal knows, so with
+# no preference it uses whichever backend happens to be installed: fine while gtk is the only one,
+# wrong the day another arrives as somebody else's dependency. Named instead. gtk also serves
+# org.freedesktop.appearance (dark or light) from GSettings, which the session's start sets.
+[preferred]
+default=gtk
+# Secrets for sandboxed programs come from the keyring's backend. Without this line no backend answers
+# the interface: gtk does not implement it, and gnome-keyring's names only GNOME as its desktop.
+org.freedesktop.impl.portal.Secret=gnome-keyring
diff --git a/go/testdata/beside/mesh-catalog/modules/adwaita/files/qt6ct.conf b/go/testdata/beside/mesh-catalog/modules/adwaita/files/qt6ct.conf
new file mode 100644
index 0000000..7f396d2
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/adwaita/files/qt6ct.conf
@@ -0,0 +1,29 @@
+[Appearance]
+color_scheme_path=/usr/share/qt6ct/colors/darker.conf
+custom_palette=true
+icon_theme=Adwaita
+standard_dialogs=default
+style=Fusion
+
+[Fonts]
+fixed="JetBrainsMono Nerd Font,11,-1,5,50,0,0,0,0,0"
+general="Inter,11,-1,5,50,0,0,0,0,0"
+
+[Interface]
+activate_item_on_single_click=1
+buttonbox_layout=0
+cursor_flash_time=1000
+dialog_buttons_have_icons=1
+double_click_interval=400
+gui_effects=@Invalid()
+keyboard_scheme=2
+menus_have_icons=true
+show_shortcuts_in_context_menus=true
+stylesheets=@Invalid()
+toolbutton_style=4
+underline_shortcut=1
+wheel_scroll_lines=3
+
+[Troubleshooting]
+force_raster_widgets=1
+ignored_applications=@Invalid()
diff --git a/go/testdata/beside/mesh-catalog/modules/adwaita/files/xinitrc-normal b/go/testdata/beside/mesh-catalog/modules/adwaita/files/xinitrc-normal
new file mode 100644
index 0000000..864b3cf
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/adwaita/files/xinitrc-normal
@@ -0,0 +1,10 @@
+# The appearance (module adwaita): GSettings is where the portal reads dark or light, and the portal is
+# the only way it reaches Electron, Chromium, Firefox and flatpaks. Set at every session start to the
+# module's default; adwaita_appearance switches it for a session.
+gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark' || true
+gsettings set org.gnome.desktop.interface gtk-theme 'Adwaita' || true
+gsettings set org.gnome.desktop.interface icon-theme 'Adwaita' || true
+gsettings set org.gnome.desktop.interface cursor-theme 'Adwaita' || true
+gsettings set org.gnome.desktop.interface cursor-size 24 || true
+gsettings set org.gnome.desktop.interface font-name 'Inter 11' || true
+gsettings set org.gnome.desktop.interface monospace-font-name 'JetBrainsMono Nerd Font 11' || true
diff --git a/go/testdata/beside/mesh-catalog/modules/adwaita/files/xresources-normal b/go/testdata/beside/mesh-catalog/modules/adwaita/files/xresources-normal
new file mode 100644
index 0000000..02e7ec3
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/adwaita/files/xresources-normal
@@ -0,0 +1,3 @@
+! adwaita: the cursor, for X programs that take it from the resources.
+Xcursor.theme: Adwaita
+Xcursor.size: 24
diff --git a/go/testdata/beside/mesh-catalog/modules/artifact-store-tools/files/store-names b/go/testdata/beside/mesh-catalog/modules/artifact-store-tools/files/store-names
new file mode 100644
index 0000000..29cab31
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/artifact-store-tools/files/store-names
@@ -0,0 +1,4 @@
+# The names the artifact store had before, from the setting older-store-names, written by the mesh.
+# artifact_store_references reads every definition under these too (novox/hq issue 323).
+# Replaced on every push; change the setting, never this file.
+older=${config:older-store-names}
diff --git a/go/testdata/beside/mesh-catalog/modules/asus-zephyrus-g14/files/bin/zephyrus-backlight b/go/testdata/beside/mesh-catalog/modules/asus-zephyrus-g14/files/bin/zephyrus-backlight
new file mode 100644
index 0000000..1a3ec31
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/asus-zephyrus-g14/files/bin/zephyrus-backlight
@@ -0,0 +1,33 @@
+#!/bin/bash
+# zephyrus-backlight + | - | PERCENT — step or set the internal panel's backlight, never below 1 %.
+# Shipped by the mesh's asus-zephyrus-g14 module; edit the catalogue.
+#
+# The panel is the backlight beneath the eDP connector, not a name: in hybrid mode this model also
+# registers the discrete GPU's backlight (nvidia_0), which moves nothing. Writable by the video group
+# through the module's udev rule, so the vendor-key trigger needs no root.
+set -u
+STEP=5
+dev=""
+for d in /sys/class/backlight/*; do
+ [ -e "$d" ] || continue
+ case "$(readlink -f "$d")" in *-eDP-*) dev=$d; break ;; esac
+done
+if [ -z "$dev" ]; then
+ for d in /sys/class/backlight/*; do [ -e "$d" ] && { dev=$d; break; }; done
+fi
+[ -n "$dev" ] || { echo "zephyrus-backlight: no backlight" >&2; exit 1; }
+cur=$(cat "$dev/brightness")
+max=$(cat "$dev/max_brightness")
+pct=$(( cur * 100 / max ))
+case "${1:-}" in
+ +|up|Up) pct=$(( pct + STEP )) ;;
+ -|down|Down) pct=$(( pct - STEP )) ;;
+ ''|*[!0-9]*) echo "usage: zephyrus-backlight + | - | PERCENT" >&2; exit 2 ;;
+ *) pct=$1 ;;
+esac
+(( pct < 1 )) && pct=1
+(( pct > 100 )) && pct=100
+new=$(( max * pct / 100 ))
+(( new < 1 )) && new=1
+printf '%s' "$new" >"$dev/brightness" || exit 1
+exec "$(dirname "$0")/zephyrus-notify" 5555 "Brightness: ${pct}%"
diff --git a/go/testdata/beside/mesh-catalog/modules/asus-zephyrus-g14/files/bin/zephyrus-display b/go/testdata/beside/mesh-catalog/modules/asus-zephyrus-g14/files/bin/zephyrus-display
new file mode 100644
index 0000000..0b7d21b
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/asus-zephyrus-g14/files/bin/zephyrus-display
@@ -0,0 +1,41 @@
+#!/bin/bash
+# zephyrus-display primary | order — the laptop's internal panel among the outputs.
+# Shipped by the mesh's asus-zephyrus-g14 module; edit the catalogue.
+#
+# primary makes the internal panel X's primary output, where the bars' tray goes.
+# order the display key (Fn+F9, which the firmware sends as Super+P): odd workspaces to the
+# internal panel, even ones to the first external output, or all to the panel when it
+# is alone. The predecessor's orden-workspaces.
+#
+# The panel is found, not named: the connected output whose name starts with eDP. The predecessor
+# wrote eDP-1, which is what this model calls it today and not a promise.
+set -u
+here=$(dirname "$0")
+panel=$(xrandr --query 2>/dev/null | awk '$2 == "connected" && $1 ~ /^eDP/ { print $1; exit }')
+[ -n "$panel" ] || { echo "zephyrus-display: no internal panel connected" >&2; exit 1; }
+case "${1:-}" in
+primary)
+ exec xrandr --output "$panel" --primary
+ ;;
+order)
+ external=$(xrandr --listmonitors 2>/dev/null | awk -v p="$panel" 'NR > 1 && $NF != p { print $NF; exit }')
+ [ -n "$external" ] || external=$panel
+ # i3's workspace list, one object per workspace; num and output read from each. No jq: the
+ # fields are flat strings and numbers, and rect, the only nested value, holds neither name.
+ i3-msg -t get_workspaces 2>/dev/null | sed 's/},{"id"/}\n{"id"/g' |
+ while read -r ws; do
+ num=$(printf '%s' "$ws" | grep -o '"num":-\?[0-9]*' | cut -d: -f2)
+ out=$(printf '%s' "$ws" | grep -o '"output":"[^"]*"' | cut -d'"' -f4)
+ [ -n "$num" ] && [ "$num" -ge 0 ] || continue
+ if [ $((num % 2)) -eq 0 ]; then dest=$external; else dest=$panel; fi
+ [ "$out" = "$dest" ] && continue
+ i3-msg "workspace number $num; move workspace to output $dest" >/dev/null
+ done
+ if [ "$external" = "$panel" ]; then
+ "$here/zephyrus-notify" 7780 "Workspaces: all on the panel"
+ else
+ "$here/zephyrus-notify" 7780 "Workspaces: odd on the panel, even on $external"
+ fi
+ ;;
+*) echo "usage: zephyrus-display primary | order" >&2; exit 2 ;;
+esac
diff --git a/go/testdata/beside/mesh-catalog/modules/asus-zephyrus-g14/files/bin/zephyrus-kbd-notify b/go/testdata/beside/mesh-catalog/modules/asus-zephyrus-g14/files/bin/zephyrus-kbd-notify
new file mode 100644
index 0000000..aee878b
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/asus-zephyrus-g14/files/bin/zephyrus-kbd-notify
@@ -0,0 +1,20 @@
+#!/bin/bash
+# zephyrus-kbd-notify — shows the keyboard backlight's level when it changes. The level itself is set
+# by the firmware and asusd (Fn+F2/F3), which tell UPower; this only listens and notifies. Started once
+# per session from the module's i3 fragment. Shipped by the mesh's asus-zephyrus-g14 module.
+#
+# One instance per session: i3 runs its `exec` lines again on an in-place restart, and the
+# predecessor's listener ran twice after one, showing every change twice.
+set -u
+here=$(dirname "$0")
+lock="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/zephyrus-kbd-notify.lock"
+exec 9>"$lock"
+flock -n 9 || exit 0
+levels=(Off Low Med High)
+dbus-monitor --system "type='signal',interface='org.freedesktop.UPower.KbdBacklight',member='BrightnessChanged'" 2>/dev/null |
+ while read -r line; do
+ if [[ $line =~ int32\ ([0-9]+) ]]; then
+ v=${BASH_REMATCH[1]}
+ "$here/zephyrus-notify" 5556 "Keyboard: ${levels[$v]:-$v}"
+ fi
+ done
diff --git a/go/testdata/beside/mesh-catalog/modules/asus-zephyrus-g14/files/bin/zephyrus-media b/go/testdata/beside/mesh-catalog/modules/asus-zephyrus-g14/files/bin/zephyrus-media
new file mode 100644
index 0000000..8b77d79
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/asus-zephyrus-g14/files/bin/zephyrus-media
@@ -0,0 +1,27 @@
+#!/bin/bash
+# zephyrus-media play-pause | next | previous — the media keys, through MPRIS (playerctl), with a short
+# notification of what happened. Shipped by the mesh's asus-zephyrus-g14 module; edit the catalogue.
+#
+# From the predecessor's media-control, kept: the lock against a double fire (the vendor keys can
+# repeat), and the notification. Dropped: its fallback to a media server's local API, which needed a
+# token from a file of secrets (novox/hq research 027 question 2). A player that speaks MPRIS is
+# reached; one that does not says so.
+set -u
+here=$(dirname "$0")
+action=${1:-play-pause}
+case "$action" in play-pause | next | previous) ;; *) echo "usage: zephyrus-media play-pause | next | previous" >&2; exit 2 ;; esac
+lock="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/zephyrus-media.lock"
+exec 9>"$lock"
+flock -n 9 || exit 0
+if ! playerctl status >/dev/null 2>&1; then
+ "$here/zephyrus-notify" 7777 "Media: no player"
+ exit 0
+fi
+playerctl "$action" 2>/dev/null
+if [ "$action" = play-pause ]; then
+ sleep 0.2
+ [ "$(playerctl status 2>/dev/null)" = Playing ] && label=Playing || label=Paused
+else
+ label=${action^}
+fi
+"$here/zephyrus-notify" 7777 "Media: $label"
diff --git a/go/testdata/beside/mesh-catalog/modules/asus-zephyrus-g14/files/bin/zephyrus-notify b/go/testdata/beside/mesh-catalog/modules/asus-zephyrus-g14/files/bin/zephyrus-notify
new file mode 100644
index 0000000..1e8273f
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/asus-zephyrus-g14/files/bin/zephyrus-notify
@@ -0,0 +1,12 @@
+#!/bin/bash
+# zephyrus-notify ID SUMMARY — a short desktop notification that replaces the previous one with the
+# same ID, through the session's notification service on its bus. busctl is the service manager's
+# own client, so nothing is installed for it. Shipped by the mesh's asus-zephyrus-g14 module.
+set -u
+id=${1:-0}
+summary=${2:-}
+uid=$(id -u)
+DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/${uid}/bus" \
+ busctl --user call org.freedesktop.Notifications /org/freedesktop/Notifications \
+ org.freedesktop.Notifications Notify susssasa{sv}i \
+ asus-zephyrus-g14 "$id" "" "$summary" "" 0 1 urgency y 0 1500 >/dev/null 2>&1 || true
diff --git a/go/testdata/beside/mesh-catalog/modules/asus-zephyrus-g14/files/bin/zephyrus-session b/go/testdata/beside/mesh-catalog/modules/asus-zephyrus-g14/files/bin/zephyrus-session
new file mode 100644
index 0000000..0f21f91
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/asus-zephyrus-g14/files/bin/zephyrus-session
@@ -0,0 +1,46 @@
+#!/bin/bash
+# zephyrus-session COMMAND [ARG...] — run a command in the operator's graphical session from outside
+# it: from a vendor-key trigger, which triggerhappy runs as the operator's account but with none of
+# the session's environment. Shipped by the mesh's asus-zephyrus-g14 module; edit the catalogue.
+#
+# What it replaces: the predecessor's `as-user`, which triggerhappy ran as root and which `su`-ed to
+# a named person with a hard-coded user id and display, and sourced a file of secrets on the way.
+# Here the account is whoever runs it, the bus is that account's, and the display is the one the
+# account's own session uses. Nothing is sourced.
+set -u
+uid=$(id -u)
+export XDG_RUNTIME_DIR="/run/user/${uid}"
+export DBUS_SESSION_BUS_ADDRESS="unix:path=${XDG_RUNTIME_DIR}/bus"
+home=$(getent passwd "$uid" | cut -d: -f6)
+[ -n "$home" ] && export HOME="$home"
+# The session's own window manager says best which display and authority the session uses: read
+# them from its environment, as the desktop modules' session finder does. Then logind, then any
+# process of this account that has a display.
+from_environ() {
+ env=$(tr '\0' '\n' <"/proc/$1/environ" 2>/dev/null) || return 1
+ d=$(printf '%s\n' "$env" | sed -n 's/^DISPLAY=//p' | head -n1)
+ [ -n "$d" ] || return 1
+ export DISPLAY="$d"
+ a=$(printf '%s\n' "$env" | sed -n 's/^XAUTHORITY=//p' | head -n1)
+ [ -n "$a" ] && export XAUTHORITY="$a"
+ return 0
+}
+if [ -z "${DISPLAY:-}" ]; then
+ for pid in $(pgrep -xu "$uid" i3 2>/dev/null); do
+ from_environ "$pid" && break
+ done
+fi
+if [ -z "${DISPLAY:-}" ]; then
+ for s in $(loginctl list-sessions --no-legend 2>/dev/null | awk -v u="$uid" '$2 == u { print $1 }'); do
+ d=$(loginctl show-session "$s" -p Display --value 2>/dev/null)
+ if [ -n "$d" ]; then export DISPLAY="$d"; break; fi
+ done
+fi
+if [ -z "${DISPLAY:-}" ]; then
+ for pid in $(pgrep -u "$uid" 2>/dev/null); do
+ from_environ "$pid" && break
+ done
+fi
+: "${XAUTHORITY:=${HOME}/.Xauthority}"
+export XAUTHORITY
+exec "$@"
diff --git a/go/testdata/beside/mesh-catalog/modules/asus-zephyrus-g14/files/bin/zephyrus-touchpad b/go/testdata/beside/mesh-catalog/modules/asus-zephyrus-g14/files/bin/zephyrus-touchpad
new file mode 100644
index 0000000..637be36
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/asus-zephyrus-g14/files/bin/zephyrus-touchpad
@@ -0,0 +1,29 @@
+#!/bin/bash
+# zephyrus-touchpad reset | toggle — apply the touchpad's settings again, or switch it on or off.
+# Shipped by the mesh's asus-zephyrus-g14 module; edit the catalogue.
+#
+# The settings themselves are an X input class (/etc/X11/xorg.conf.d/30-asus-zephyrus-g14-touchpad.conf),
+# which X applies every time the device appears — after a resume too, which is what the predecessor's
+# sleep hook existed for. This is the manual form, bound to the touchpad key.
+set -u
+here=$(dirname "$0")
+name=$("$here/zephyrus-session" xinput list --name-only 2>/dev/null | grep -m1 -i 'touchpad')
+[ -n "$name" ] || { echo "zephyrus-touchpad: no touchpad in this session" >&2; exit 1; }
+x() { "$here/zephyrus-session" xinput "$@"; }
+case "${1:-reset}" in
+ reset)
+ x set-prop "$name" "libinput Tapping Enabled" 1
+ x set-prop "$name" "libinput Natural Scrolling Enabled" 1
+ x set-prop "$name" "libinput Accel Speed" 0.15
+ x enable "$name"
+ "$here/zephyrus-session" "$here/zephyrus-notify" 7779 "Touchpad: reset"
+ ;;
+ toggle)
+ if x list-props "$name" | grep -q 'Device Enabled ([0-9]*):[[:space:]]*1'; then
+ x disable "$name"; "$here/zephyrus-session" "$here/zephyrus-notify" 7779 "Touchpad: off"
+ else
+ x enable "$name"; "$here/zephyrus-session" "$here/zephyrus-notify" 7779 "Touchpad: on"
+ fi
+ ;;
+ *) echo "usage: zephyrus-touchpad reset | toggle" >&2; exit 2 ;;
+esac
diff --git a/go/testdata/beside/mesh-catalog/modules/baserow/files/config.json b/go/testdata/beside/mesh-catalog/modules/baserow/files/config.json
new file mode 100644
index 0000000..955ac24
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/baserow/files/config.json
@@ -0,0 +1,4 @@
+{
+ "password": "${secret:admin}",
+ "host": "${bound:route:name}"
+}
diff --git a/go/testdata/beside/mesh-catalog/modules/baserow/files/server.env b/go/testdata/beside/mesh-catalog/modules/baserow/files/server.env
new file mode 100644
index 0000000..7b716b4
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/baserow/files/server.env
@@ -0,0 +1,7 @@
+DATABASE_HOST=${bound:postgres-database:at}
+DATABASE_PORT=${bound:postgres-database:port}
+DATABASE_NAME=${bound:postgres-database:as}
+DATABASE_USER=${bound:postgres-database:as}
+DATABASE_PASSWORD_FILE=/run/secrets/database
+DISABLE_EMBEDDED_PSQL=true
+BASEROW_PUBLIC_URL=https://${bound:route:name}
diff --git a/go/testdata/beside/mesh-catalog/modules/betterbird/files/accounts.json b/go/testdata/beside/mesh-catalog/modules/betterbird/files/accounts.json
new file mode 100644
index 0000000..e182cc3
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/betterbird/files/accounts.json
@@ -0,0 +1,3 @@
+{
+ "accounts": {}
+}
diff --git a/go/testdata/beside/mesh-catalog/modules/betterbird/files/betterbird b/go/testdata/beside/mesh-catalog/modules/betterbird/files/betterbird
new file mode 100644
index 0000000..3ce088a
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/betterbird/files/betterbird
@@ -0,0 +1,10 @@
+#!/bin/sh
+# Betterbird's start, written by the mesh (module betterbird, novox/hq ADR 0316). Replaced at every push.
+#
+# Before Betterbird starts, the module's tools place its user.js in the profile (the settings prefs, accounts, identities, calendars,
+# address-books and tags) and write what Betterbird rewrites itself: the account lists and each declared account's filters. They do
+# it only while no Betterbird of this account runs, because Betterbird rewrites its saved preferences when it quits; a
+# second start while one runs only hands over to it. What the preparation could not do it says here, and
+# betterbird_check says it too. Betterbird starts either way: its configuration never holds the mail back.
+/usr/local/lib/mesh-betterbird/betterbird-tools prepare || echo "betterbird: the mesh's preparation did not complete; betterbird_check says why" >&2
+exec /opt/betterbird/betterbird "$@"
diff --git a/go/testdata/beside/mesh-catalog/modules/betterbird/files/configuration.json b/go/testdata/beside/mesh-catalog/modules/betterbird/files/configuration.json
new file mode 100644
index 0000000..7020446
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/betterbird/files/configuration.json
@@ -0,0 +1,6 @@
+{
+ "identities": {},
+ "calendars": {},
+ "address-books": {},
+ "filters": {}
+}
diff --git a/go/testdata/beside/mesh-catalog/modules/betterbird/files/eu.betterbird.Betterbird.desktop b/go/testdata/beside/mesh-catalog/modules/betterbird/files/eu.betterbird.Betterbird.desktop
new file mode 100644
index 0000000..e409fcf
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/betterbird/files/eu.betterbird.Betterbird.desktop
@@ -0,0 +1,25 @@
+[Desktop Entry]
+Version=1.0
+Type=Application
+Name=Betterbird
+GenericName=Mail Client
+Comment=Send and receive mail with Betterbird
+Exec=/usr/local/bin/betterbird %u
+Icon=/opt/betterbird/chrome/icons/default/default128.png
+Terminal=false
+Categories=Network;Email;
+MimeType=message/rfc822;x-scheme-handler/mailto;application/x-xpinstall;application/x-extension-ics;text/calendar;text/vcard;text/x-vcard;x-scheme-handler/webcal;x-scheme-handler/webcals;x-scheme-handler/mid;
+StartupNotify=true
+StartupWMClass=eu.betterbird.Betterbird
+Keywords=Email;E-mail;Calendar;Thunderbird;
+Actions=ComposeMessage;OpenAddressBook;
+# Written by the mesh (module betterbird, novox/hq ADR 0316 §3), in place of the community package's entry:
+# it starts Betterbird through the module's start, and names the icon in the installation, with no link.
+
+[Desktop Action ComposeMessage]
+Name=Write new message
+Exec=/usr/local/bin/betterbird -compose
+
+[Desktop Action OpenAddressBook]
+Name=Open address book
+Exec=/usr/local/bin/betterbird -addressbook
diff --git a/go/testdata/beside/mesh-catalog/modules/betterbird/files/prefs.json b/go/testdata/beside/mesh-catalog/modules/betterbird/files/prefs.json
new file mode 100644
index 0000000..45fd973
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/betterbird/files/prefs.json
@@ -0,0 +1,3 @@
+{
+ "prefs": {}
+}
diff --git a/go/testdata/beside/mesh-catalog/modules/betterbird/files/tags.json b/go/testdata/beside/mesh-catalog/modules/betterbird/files/tags.json
new file mode 100644
index 0000000..4772f09
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/betterbird/files/tags.json
@@ -0,0 +1,3 @@
+{
+ "tags": {}
+}
diff --git a/go/testdata/beside/mesh-catalog/modules/ca-trust/files/anchor b/go/testdata/beside/mesh-catalog/modules/ca-trust/files/anchor
new file mode 100644
index 0000000..72576d2
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/ca-trust/files/anchor
@@ -0,0 +1,58 @@
+#!/bin/sh
+# The mesh's internal certificate authority, trusted by this machine.
+#
+# Written by the mesh from the ca-trust module's manifest (novox/hq ADR 0147).
+# Editing it here lasts until the next apply.
+#
+# There is no prior trust to verify the fetch against — this is the thing that
+# establishes it — so it is made over the mesh's own private network, which is
+# what authenticates it (novox/hq ADR 0098, the same reasoning that lets the
+# route proxy fetch this root for itself). What comes back is checked here: a
+# body that is not a certificate is refused now, rather than believed and then
+# failed by whatever reads the trust store next.
+set -eu
+
+ROOTS='https://${bound:internal-acme-ca:at}:${bound:internal-acme-ca:port}${bound:internal-acme-ca:roots}'
+ANCHORS=/etc/ca-certificates/trust-source/anchors
+ANCHOR="$ANCHORS/mesh-internal-ca.crt"
+
+# Arch's layout, said out loud rather than assumed: a machine that keeps its
+# anchors elsewhere fails here, visibly, instead of writing a file nothing
+# reads. That failure is the signal that this belongs in the host, where one
+# operating system's difference lives (novox/hq ADR 0147, option 2).
+[ -d "$ANCHORS" ] || {
+ echo "this machine keeps no trust anchors in $ANCHORS; ca-trust is written for that layout" >&2
+ exit 1
+}
+
+case "${1:-}" in
+install)
+ tmp=$(mktemp)
+ trap 'rm -f "$tmp"' EXIT
+ # The authority may still be starting, or this machine may have come up
+ # before it: two minutes of asking, then an honest failure.
+ n=0
+ while [ "$n" -lt 60 ]; do
+ if curl --fail --silent --show-error --insecure --max-time 10 \
+ --output "$tmp" "$ROOTS" &&
+ grep -q 'BEGIN CERTIFICATE' "$tmp"; then
+ install -m 0644 "$tmp" "$ANCHOR"
+ update-ca-trust
+ exit 0
+ fi
+ n=$((n + 1))
+ sleep 2
+ done
+ echo "the authority at $ROOTS did not serve a certificate within two minutes" >&2
+ exit 1
+ ;;
+remove)
+ # What stopping the unit does, and therefore what being unassigned does.
+ rm -f "$ANCHOR"
+ update-ca-trust
+ ;;
+*)
+ echo "usage: $(basename "$0") install|remove" >&2
+ exit 2
+ ;;
+esac
diff --git a/go/testdata/beside/mesh-catalog/modules/ca-trust/files/mesh-ca-trust.service b/go/testdata/beside/mesh-catalog/modules/ca-trust/files/mesh-ca-trust.service
new file mode 100644
index 0000000..2411560
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/ca-trust/files/mesh-ca-trust.service
@@ -0,0 +1,16 @@
+[Unit]
+Description=The mesh's internal certificate authority, trusted by this machine
+# novox/hq ADR 0147. Starting this unit places the mesh's root among this
+# machine's trust anchors; stopping it takes the root away again, which is what
+# the host does when the module is no longer assigned here.
+Wants=network-online.target
+After=network-online.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=${dir:state}/anchor install
+ExecStop=${dir:state}/anchor remove
+
+[Install]
+WantedBy=multi-user.target
diff --git a/go/testdata/beside/mesh-catalog/modules/claude-code/files/agent-account b/go/testdata/beside/mesh-catalog/modules/claude-code/files/agent-account
new file mode 100644
index 0000000..b081a6c
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/claude-code/files/agent-account
@@ -0,0 +1 @@
+${machine:agent-account}
diff --git a/go/testdata/beside/mesh-catalog/modules/claude-code/files/claude-agent b/go/testdata/beside/mesh-catalog/modules/claude-code/files/claude-agent
new file mode 100644
index 0000000..e3d330f
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/claude-code/files/claude-agent
@@ -0,0 +1,13 @@
+#!/bin/sh
+# The mesh's (module claude-code, novox/hq ADR 0266): start the agent as the account agents run as on this
+# machine. Written whole at every push: an edit here is overwritten.
+agent='${machine:agent-account}'
+if [ "$(id -un)" = "$agent" ]; then
+ exec claude "$@"
+fi
+# Another account, the operator's, becomes the agent's through its own sudo: the person is the authority. The
+# operator's override of the guard travels only when it is set in this shell, as it would reach claude.
+if [ -n "$MESH_GUARD_OVERRIDE" ]; then
+ exec sudo -iu "$agent" env MESH_GUARD_OVERRIDE="$MESH_GUARD_OVERRIDE" claude "$@"
+fi
+exec sudo -iu "$agent" claude "$@"
diff --git a/go/testdata/beside/mesh-catalog/modules/claude-code/files/facts.json b/go/testdata/beside/mesh-catalog/modules/claude-code/files/facts.json
new file mode 100644
index 0000000..7fd72bd
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/claude-code/files/facts.json
@@ -0,0 +1,4 @@
+{
+ "node": "${machine:name}",
+ "console": "http://127.0.0.1:${bound:mcp-endpoint:port}/mcp"
+}
diff --git a/go/testdata/beside/mesh-catalog/modules/claude-code/files/settings.json b/go/testdata/beside/mesh-catalog/modules/claude-code/files/settings.json
new file mode 100644
index 0000000..8284e66
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/claude-code/files/settings.json
@@ -0,0 +1,10 @@
+{
+ "role": "",
+ "mcp_servers": {},
+ "managed_settings": {},
+ "instructions": {},
+ "output_styles": {},
+ "skills": {},
+ "commands": {},
+ "agents": {}
+}
diff --git a/go/testdata/beside/mesh-catalog/modules/claude-code/files/zsh-normal b/go/testdata/beside/mesh-catalog/modules/claude-code/files/zsh-normal
new file mode 100644
index 0000000..b90b85f
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/claude-code/files/zsh-normal
@@ -0,0 +1,12 @@
+# The agent's session (module claude-code, novox/hq ADR 0266): where this machine names an account of the
+# agents' own, claude in an interactive zsh is claude-agent, which starts the session as that account. Where
+# agents run as the operator's account the file names that account, and nothing is added. claude-agent runs
+# the real claude: exec, sudo and its sh see no alias.
+if [[ -r /etc/claude-code/agent-account ]]; then
+ () {
+ local agent=$(/dev/null | grep -E '^(DISPLAY|XAUTHORITY)='); do
+ export "$pair"
+ done
+ [ -n "$DISPLAY" ] && break
+ set -- /tmp/.X11-unix/X*
+ if [ $# -eq 1 ] && [ -S "$1" ]; then
+ DISPLAY=":${1##*/X}"
+ export DISPLAY
+ else
+ sleep 1
+ fi
+done
+if [ -z "$XAUTHORITY" ] && [ -r "$HOME/.Xauthority" ]; then
+ XAUTHORITY="$HOME/.Xauthority"
+ export XAUTHORITY
+fi
+# clipmenud allows one daemon per account: it takes an exclusive flock on its session_lock file and exits
+# with status 2 when that is held. After a delivery without a new login, the session's older clipmenud (from
+# its xinitrc) still holds it, and a start would run the unit round its restart loop. So this waits, quietly,
+# until the lock is free: it looks at the same file clipmenud locks (the directory is clipmenud's own rule)
+# and lets go at once. Exit status 1 is "held" for util-linux and BusyBox flock alike (no -E: BusyBox lacks it); any other outcome starts clipmenud, which says its own
+# error. Another daemon winning the gap between look and start costs one restart, not a loop.
+lock="${CM_DIR:-${XDG_RUNTIME_DIR-${TMPDIR-/tmp}}}/clipmenu.6.$USER/session_lock"
+while [ -e "$lock" ]; do
+ flock -n "$lock" true
+ [ $? -eq 1 ] || break
+ sleep 2
+done
+PATH="/usr/local/lib/mesh-clipmenu:$PATH"
+export PATH
+exec clipmenud
diff --git a/go/testdata/beside/mesh-catalog/modules/clipmenu/files/bin/xsel b/go/testdata/beside/mesh-catalog/modules/clipmenu/files/bin/xsel
new file mode 100644
index 0000000..10f9bbd
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/clipmenu/files/bin/xsel
@@ -0,0 +1,20 @@
+#!/bin/sh
+# xsel as clipmenud sees it, written by the mesh (module clipmenu). Replaced at every push.
+#
+# clipmenud records text, and reads a selection with `timeout 1 xsel -o`. A selection holding an
+# image (a screenshot copied as image/png) is sent in pieces; one second is too short for megabytes,
+# timeout kills xsel half way, and the program owning the image waits for ever for a reader that is
+# gone. From then on nothing can ask the clipboard anything: pastes hang, and an Electron app that
+# asks on its main thread freezes. So a read goes ahead only when the selection offers text.
+case " $* " in
+*" -o "* | *" --output "*)
+ selection=clipboard
+ case " $* " in
+ *" --primary "* | *" -p "*) selection=primary ;;
+ *" --secondary "* | *" -s "*) selection=secondary ;;
+ esac
+ targets=$(timeout 1 xclip -selection "$selection" -t TARGETS -o 2>/dev/null) || exit 1
+ printf '%s\n' "$targets" | grep -qxE 'UTF8_STRING|STRING|TEXT|text/plain(;charset=utf-8)?' || exit 1
+ ;;
+esac
+exec /usr/bin/xsel "$@"
diff --git a/go/testdata/beside/mesh-catalog/modules/clipmenu/files/env-cm-histlength b/go/testdata/beside/mesh-catalog/modules/clipmenu/files/env-cm-histlength
new file mode 100644
index 0000000..60d3b2f
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/clipmenu/files/env-cm-histlength
@@ -0,0 +1 @@
+15
diff --git a/go/testdata/beside/mesh-catalog/modules/clipmenu/files/env-cm-max-clips b/go/testdata/beside/mesh-catalog/modules/clipmenu/files/env-cm-max-clips
new file mode 100644
index 0000000..1b79f38
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/clipmenu/files/env-cm-max-clips
@@ -0,0 +1 @@
+500
diff --git a/go/testdata/beside/mesh-catalog/modules/clipmenu/files/env-cm-selections b/go/testdata/beside/mesh-catalog/modules/clipmenu/files/env-cm-selections
new file mode 100644
index 0000000..613d81a
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/clipmenu/files/env-cm-selections
@@ -0,0 +1 @@
+clipboard
diff --git a/go/testdata/beside/mesh-catalog/modules/clipmenu/files/i3/50-clipmenu.conf b/go/testdata/beside/mesh-catalog/modules/clipmenu/files/i3/50-clipmenu.conf
new file mode 100644
index 0000000..41835e6
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/clipmenu/files/i3/50-clipmenu.conf
@@ -0,0 +1,5 @@
+# The clipboard's history key (module clipmenu, novox/hq ADR 0208). Owned by the mesh: replaced at
+# every push. clipmenu shows the history through `dmenu`, the node's dmenu-compatible command, which
+# the holder of node-launcher answers (rofi on the workstations); the chosen entry is put back on the
+# clipboard.
+bindsym $mod+period exec --no-startup-id clipmenu -p Clipboard
diff --git a/go/testdata/beside/mesh-catalog/modules/clipmenu/files/systemd/clipmenud-mesh.conf b/go/testdata/beside/mesh-catalog/modules/clipmenu/files/systemd/clipmenud-mesh.conf
new file mode 100644
index 0000000..799e22d
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/clipmenu/files/systemd/clipmenud-mesh.conf
@@ -0,0 +1,14 @@
+# clipmenud in the operator's account (module clipmenu, novox/hq ADR 0208). Owned by the mesh: replaced at every push.
+# The package's unit starts clipmenud with no DISPLAY; the module's launcher finds the display and puts the
+# text-only xsel first on the PATH. The settings repeat the module's environment, which the account's
+# service manager does not read.
+[Unit]
+StartLimitIntervalSec=0
+
+[Service]
+RestartSec=2s
+Environment=CM_SELECTIONS=clipboard
+Environment=CM_MAX_CLIPS=500
+Environment=CM_HISTLENGTH=15
+ExecStart=
+ExecStart=/usr/local/lib/mesh-clipmenu/clipmenud-session
diff --git a/go/testdata/beside/mesh-catalog/modules/confluence/files/config.json b/go/testdata/beside/mesh-catalog/modules/confluence/files/config.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/confluence/files/config.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/go/testdata/beside/mesh-catalog/modules/desk-channel/files/settings.json b/go/testdata/beside/mesh-catalog/modules/desk-channel/files/settings.json
new file mode 100644
index 0000000..1ef16fd
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/desk-channel/files/settings.json
@@ -0,0 +1,3 @@
+{
+ "desktop-machines": []
+}
diff --git a/go/testdata/beside/mesh-catalog/modules/dnsmasq/files/dnsmasq.conf b/go/testdata/beside/mesh-catalog/modules/dnsmasq/files/dnsmasq.conf
new file mode 100644
index 0000000..10f3926
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/dnsmasq/files/dnsmasq.conf
@@ -0,0 +1,87 @@
+# Managed by the mesh. dnsmasq's own defaults are replaced whole rather than
+# patched, because this module owns the file and a patch would leave whatever
+# was there before to be discovered later.
+#
+# This is one of the mesh's resolvers (novox/hq ADR 0194, 0223): it holds the
+# `mesh-dns-resolver` seat, which more than one machine may hold, each answering
+# the same names from the same roster. Every node and every container lists every
+# holder and no public resolver, so whichever answers first gives the one answer.
+# It holds the mesh's own names and nothing else (ADR 0191) — the roster is the
+# mesh's, rendered into each holder; no other copy lives on any machine.
+
+# What the mesh computed: one wildcard per machine — its name and everything
+# under it — and the mesh's own suffix as a local domain, so a name under it is
+# answered here or not at all and is never asked upstream. Rewritten whenever a
+# machine joins or leaves, which is why the service below restarts on it: a
+# reload makes dnsmasq re-read hosts files, not its configuration, and a
+# wildcard is configuration.
+conf-file=/etc/mesh-resolver/nodes.conf
+
+# Every zone a module answers itself (ADR 0199): one forwarding line per zone, to
+# the private address of the machine its module runs on and the port its
+# answerer is published on. Nothing in a zone is answered here; what names a zone
+# holds is the module's. Rewritten whenever a zone is declared or moves, and the
+# service restarts on it for the same reason as above.
+conf-file=/etc/mesh-resolver/zones.conf
+
+# Where it answers: this machine's private address, so every node — and every
+# container on every node, which copies its machine's resolvers — can ask; and
+# loopback, for this machine's own use. Never a LAN address: a device that is
+# not a member cannot reach what the mesh's names point at, and a LAN's resolver
+# is its router's (ADR 0194).
+#
+# Named as an ADDRESS and not as the interface that carries it, on purpose. A
+# container's query is addressed to this address but arrives on the runtime's
+# bridge, and dnsmasq checks every query against what it was told to answer on:
+# an `interface=` line admits queries by the interface they arrive on, a
+# `listen-address=` line by the address they are sent to. With `interface=mesh0`
+# alone, a query from a container was received and dropped without a word
+# (novox/hq 04-ISSUES/110). The address admits it whatever bridge it comes in on.
+#
+# bind-interfaces, and never bind-dynamic (novox/hq issue 348). bind-dynamic
+# re-reads the machine's addresses on every change of any link and closes each
+# listener whose interface that reading did not find; a reading made while the
+# container runtime deletes a bridge's virtual links fails ("index gone"), and
+# then every listener is closed until a later change reads cleanly. On
+# 2026-10-09 the private address was left unbound for twenty minutes, the daemon
+# running and silent. bind-interfaces binds each address once, at start, and
+# never closes it. It cannot bind an address that is not there yet, so the unit
+# waits for it (the drop-in below); a new private address restarts the unit, as
+# this file changing always did.
+bind-interfaces
+listen-address=${machine:address}
+listen-address=127.0.0.1
+
+# **It must never read resolv.conf to find out where to forward.** This
+# machine's resolv.conf names this resolver — so a resolver that read it for
+# upstreams would find itself, and every query it could not answer locally would
+# loop until its receive queue filled. That is not theoretical: it filled with
+# 15KB of queries and every lookup on the machine hung. no-resolv makes that loop
+# impossible: the upstreams are the two lines below, and nothing on the machine
+# can redirect them. The mesh's own names never reach them: the local= line in
+# the file above stops them here, answered or refused.
+no-resolv
+server=1.1.1.1
+server=8.8.8.8
+
+# Both upstreams validate DNSSEC and say so with the Authenticated Data bit,
+# and this passes that bit down rather than dropping it, which dnsmasq does
+# unless told. It does not validate itself: an answer's trust is the upstream's
+# and the path to it, which is what a forwarding resolver's trust always was.
+# Without it a program that refuses to run behind a resolver that does not
+# validate — a mail system's admin does exactly that check at start — cannot
+# use this resolver (novox/hq 04-ISSUES/171).
+proxy-dnssec
+
+# A name without a dot is never forwarded, and reverse lookups of private ranges
+# are answered here rather than asking the world who 10.x is.
+domain-needed
+bogus-priv
+
+# **No hosts file, and no operator's files.** This machine's /etc/hosts is its
+# own — the hosts module's block and the operator's lines (ADR 0199) — and the
+# mesh's resolver answers every node, so a line written for one machine's own
+# programs must not become an answer for all of them. Every per-node resolver
+# went wrong exactly here: a hosts file read once at start, an operator's old
+# line beside the mesh's, a drop-in read from a directory nobody owned.
+no-hosts
diff --git a/go/testdata/beside/mesh-catalog/modules/dnsmasq/files/mesh.conf b/go/testdata/beside/mesh-catalog/modules/dnsmasq/files/mesh.conf
new file mode 100644
index 0000000..96d8d7d
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/dnsmasq/files/mesh.conf
@@ -0,0 +1,14 @@
+# Written by the mesh (module dnsmasq, novox/hq issue 348). Replaced on every push.
+#
+# The resolver binds its addresses once, at start (bind-interfaces), so it starts only once this
+# machine's private address is there: it waits up to 80 seconds for it, and a start that found it
+# missing is tried again every ten seconds rather than left failed.
+[Unit]
+After=network-online.target
+Wants=network-online.target
+StartLimitIntervalSec=0
+
+[Service]
+ExecStartPre=/usr/bin/sh -c 'for i in $(seq 1 80); do ip -4 -o addr show to ${machine:address} | grep -q . && exit 0; sleep 1; done; echo "${machine:address} is not on this machine" >&2; exit 1'
+Restart=on-failure
+RestartSec=10s
diff --git a/go/testdata/beside/mesh-catalog/modules/docker/files/daemon.json b/go/testdata/beside/mesh-catalog/modules/docker/files/daemon.json
new file mode 100644
index 0000000..83750f4
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/docker/files/daemon.json
@@ -0,0 +1 @@
+{"live-restore": true, "insecure-registries": ["${seat:mesh-artifact-store:reach}"], "log-driver": "json-file", "log-opts": {"max-size": "${config:log-max-size}", "max-file": "${config:log-max-file}"}}
diff --git a/go/testdata/beside/mesh-catalog/modules/docker/files/docker-prune.service b/go/testdata/beside/mesh-catalog/modules/docker/files/docker-prune.service
new file mode 100644
index 0000000..b9b92c1
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/docker/files/docker-prune.service
@@ -0,0 +1,15 @@
+# Generated by the mesh. Do not edit — module docker writes this file and replaces it at every push.
+[Unit]
+Description=Prune dangling images and unused build cache (the mesh's docker module)
+# Never volumes, never a container, never an image a container uses: dangling
+# images and build cache nothing refers to, unused for a week. What a person
+# prunes beyond that is docker_prune's, by hand.
+After=docker.service
+ConditionPathExists=/run/docker.sock
+
+[Service]
+Type=oneshot
+Nice=19
+IOSchedulingClass=idle
+ExecStart=/usr/bin/docker image prune --force --filter until=168h
+ExecStart=/usr/bin/docker builder prune --force --filter until=168h
diff --git a/go/testdata/beside/mesh-catalog/modules/docker/files/docker-prune.timer b/go/testdata/beside/mesh-catalog/modules/docker/files/docker-prune.timer
new file mode 100644
index 0000000..82befd1
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/docker/files/docker-prune.timer
@@ -0,0 +1,11 @@
+# Generated by the mesh. Do not edit — module docker writes this file and replaces it at every push.
+[Unit]
+Description=Weekly prune of dangling images and unused build cache (the mesh's docker module)
+
+[Timer]
+OnCalendar=weekly
+RandomizedDelaySec=1h
+Persistent=true
+
+[Install]
+WantedBy=timers.target
diff --git a/go/testdata/beside/mesh-catalog/modules/docker/files/log-rotation.conf b/go/testdata/beside/mesh-catalog/modules/docker/files/log-rotation.conf
new file mode 100644
index 0000000..3919036
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/docker/files/log-rotation.conf
@@ -0,0 +1,5 @@
+# The log rotation daemon.json takes, written by the mesh from this machine's settings log-max-size and
+# log-max-file, and judged by the step log-rotation-check before daemon.json is written (novox/hq issue 452).
+# Replaced on every push; change the settings, never this file.
+size=${config:log-max-size}
+files=${config:log-max-file}
diff --git a/go/testdata/beside/mesh-catalog/modules/dunst/files/dunstrc b/go/testdata/beside/mesh-catalog/modules/dunst/files/dunstrc
new file mode 100644
index 0000000..afba676
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/dunst/files/dunstrc
@@ -0,0 +1,110 @@
+# dunst, the notifier (module dunst, novox/hq ADR 0208). Owned by the mesh: this file is replaced
+# at every push. Adopted from the laptop's file of 2026-10-04 (the two workstations differed in
+# position, transparency and corner radius; the laptop's square, opaque, top-right one matches the
+# rest of the desktop). Only what differs from dunst's defaults, and what the desktop relies on.
+#
+# Other modules' rules go in ~/.config/dunst/dunstrc.d/*.conf, which dunst reads after this file,
+# so a drop-in outranks it. dunst is started by D-Bus on the first notification: nothing starts it.
+# When the mesh rewrites this file (a font-size or width setting changed), it tells a running dunst
+# to read it again (its unit's reload, dunstctl reload); what is on screen stays.
+
+[global]
+ monitor = 0
+ follow = none
+
+ # Geometry
+ width = ${config:width}
+ height = (0, 300)
+ origin = top-right
+ offset = (10, 50)
+ notification_limit = 20
+
+ progress_bar = true
+ progress_bar_height = 10
+ progress_bar_frame_width = 1
+ progress_bar_min_width = 150
+ progress_bar_max_width = 300
+
+ indicate_hidden = yes
+ transparency = 0
+ separator_height = 2
+ padding = 8
+ horizontal_padding = 8
+ text_icon_padding = 0
+ frame_width = 3
+ frame_color = "#de5200"
+ gap_size = 0
+ separator_color = frame
+ sort = yes
+ corner_radius = 0
+
+ # Text: the interface face (research 026/04)
+ font = Inter ${config:font-size}
+ line_height = 0
+ markup = full
+ format = "%s\n%b"
+ alignment = left
+ vertical_alignment = center
+ show_age_threshold = 60
+ ellipsize = middle
+ ignore_newline = no
+ stack_duplicates = true
+ hide_duplicate_count = false
+ show_indicators = yes
+
+ # Icons, from the desktop's icon theme
+ enable_recursive_icon_lookup = true
+ icon_theme = Adwaita
+ icon_position = left
+ min_icon_size = 32
+ max_icon_size = 128
+
+ # History
+ sticky_history = yes
+ history_length = 20
+
+ # The context menu is rofi (installed by the rofi module); -no-custom keeps typed text from becoming a choice. Not `dmenu`, the node-launcher's command in
+ # ~/.local/bin: dunst runs in the account's service manager, whose PATH does not hold that
+ # directory (seen on the laptop on 2026-10-10), so the menu never opened. Links open in the
+ # desktop's default browser.
+ dmenu = rofi -dmenu -no-custom -p dunst
+ browser = /usr/bin/xdg-open
+ always_run_script = true
+
+ title = Dunst
+ class = Dunst
+ ignore_dbusclose = false
+
+ # A tap answers (the touchpads have no middle button). do_action runs the notification's only
+ # action, or opens the context menu when it has several; without actions it opens its one link,
+ # if it has one. The notification then closes; a menu dismissed with Escape leaves it shown.
+ mouse_left_click = do_action, close_current
+ mouse_middle_click = do_action, close_current
+ mouse_right_click = close_all
+
+[urgency_low]
+ background = "#000000"
+ foreground = "#ffffff"
+ timeout = 10
+
+[urgency_normal]
+ background = "#000000"
+ foreground = "#ffffff"
+ timeout = 10
+
+[urgency_critical]
+ background = "#000000"
+ foreground = "#ffffff"
+ frame_color = "#ff0000"
+ timeout = 0
+
+# What is shown once is never kept (the review of 2026-10-09): a notification marked secret (a link's
+# code, category mesh.secret) and any transient one leave no entry in the history, which
+# node-notifier.history serves to every caller of the mesh's verbs.
+[mesh-secret]
+ category = "mesh.secret"
+ history_ignore = yes
+
+[transient-history-ignore]
+ match_transient = yes
+ history_ignore = yes
diff --git a/go/testdata/beside/mesh-catalog/modules/dunst/files/i3/50-dunst.conf b/go/testdata/beside/mesh-catalog/modules/dunst/files/i3/50-dunst.conf
new file mode 100644
index 0000000..fec1f8d
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/dunst/files/i3/50-dunst.conf
@@ -0,0 +1,4 @@
+# The notifications' key (module dunst, novox/hq ADR 0208). Owned by the mesh: replaced at every push.
+# Super+N opens the menu (rofi) of the actions and links of every notification on screen, so one is
+# chosen from the keyboard as a tap chooses it on the notification.
+bindsym $mod+n exec --no-startup-id dunstctl context
diff --git a/go/testdata/beside/mesh-catalog/modules/fail2ban/files/fail2ban b/go/testdata/beside/mesh-catalog/modules/fail2ban/files/fail2ban
new file mode 100644
index 0000000..b5395db
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/fail2ban/files/fail2ban
@@ -0,0 +1,7 @@
+/var/log/fail2ban.log {
+ missingok
+ notifempty
+ postrotate
+ /usr/bin/fail2ban-client flushlogs >/dev/null || true
+ endscript
+}
diff --git a/go/testdata/beside/mesh-catalog/modules/fail2ban/files/iptables-allports-dualchain.conf b/go/testdata/beside/mesh-catalog/modules/fail2ban/files/iptables-allports-dualchain.conf
new file mode 100644
index 0000000..2c9fc73
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/fail2ban/files/iptables-allports-dualchain.conf
@@ -0,0 +1,40 @@
+# Fail2Ban action: ban in both INPUT and DOCKER-USER chains
+# Used by recidive to block repeat offenders from both host and Docker services
+
+[INCLUDES]
+
+before = iptables.conf
+
+[Definition]
+
+type = allports
+
+actionstart = { -C f2b- -j >/dev/null 2>&1; } || { -N f2b- || true; -A f2b- -j ; }
+ { -C INPUT -p -j f2b- >/dev/null 2>&1; } || { -I INPUT -p -j f2b-; }
+ { -C DOCKER-USER -p -j f2b- >/dev/null 2>&1; } || { -I DOCKER-USER -p -j f2b-; }
+
+actionstop = -D INPUT -p -j f2b- 2>/dev/null || true
+ -D DOCKER-USER -p -j f2b- 2>/dev/null || true
+ -F f2b-
+ -X f2b-
+
+actioncheck = -n -L f2b- >/dev/null
+
+actionban = -I f2b- 1 -s -j
+
+actionunban = -D f2b- -s -j
+
+[Init]
+
+chain = INPUT
+name = default
+protocol = tcp
+blocktype = REJECT --reject-with icmp-port-unreachable
+returntype = RETURN
+lockingopt = -w
+iptables = iptables
+
+[Init?family=inet6]
+
+blocktype = REJECT --reject-with icmp6-port-unreachable
+iptables = ip6tables
diff --git a/go/testdata/beside/mesh-catalog/modules/fail2ban/files/jail.local b/go/testdata/beside/mesh-catalog/modules/fail2ban/files/jail.local
new file mode 100644
index 0000000..1fa0ffe
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/fail2ban/files/jail.local
@@ -0,0 +1,48 @@
+[INCLUDES]
+
+before = paths-arch.conf
+
+[DEFAULT]
+
+# Never act on the machine itself or on a tunnel peer: the mesh's private range is
+# ${machine:mesh-range}, named here rather than written as a value the module cannot
+# know (novox/hq ADR 0112). Without this, fail2ban could ban the mesh's own nodes.
+# **A ban list never holds a neighbour.** The mesh's own range is named rather than written
+# (novox/hq ADR 0112), and every private range beside it: a source on one is somebody's own
+# network, not the internet. On a machine behind a router that reflects local traffic, every
+# client in the house arrives as the gateway's address — so one mistyped local request banned
+# 192.168.1.1 on the home server and would have cut the whole house off from it (ADR 0186).
+#
+# **Nor the operator's own addresses** (novox/hq ADR 0339, issue 238): the public addresses the operator names
+# as theirs, in the mesh-wide setting own-addresses. An agent polling the forge from the operator's home banned
+# that home for four weeks on 2026-10-04, and nothing in the house reached the control-node until it was found
+# among 400 bans. The file is written by this module's watch from the setting, one entry per line; fail2ban
+# reads it again within a second of a change, so a new value needs no restart. Empty until the operator
+# fills the setting. A jail that sets its own ignoreip leaves all of this out; the watch says so.
+ignoreip = 127.0.0.1/8 ::1 ${machine:mesh-range} 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 169.254.0.0/16 fc00::/7 fe80::/10 file:/var/lib/mesh-fail2ban/own-addresses
+
+# Three failures in a day ban for a day (novox/hq ADR 0179). The attackers this mesh sees pace
+# themselves at one try every ten minutes, under any ten-minute window; a day's window counts
+# them, and a day's ban costs a person who mistyped three times once, from one address, while
+# the mesh's own range is never banned at all.
+bantime = 1d
+findtime = 1d
+maxretry = 3
+
+# Ban through iptables, not through a firewall front-end the machine may not have. ufw is
+# installed on two of this mesh's machines and absent on the other two, and fail2ban finds out
+# only at ban time: the service reports healthy, the jail counts the attempt, the ban command
+# exits 127, and nothing is blocked. Proven on 2026-09-28 -- 'ufw: command not found' on a
+# machine the mesh reported as protected.
+#
+# The action below is this module's own, already used by the recidive jail on every machine
+# here, and it bans in DOCKER-USER as well as INPUT, so a container's published port is
+# covered too.
+banaction = iptables-allports-dualchain
+banaction_allports = iptables-allports-dualchain
+
+[sshd]
+enabled = true
+port = ssh
+logpath = %(sshd_log)s
+backend = %(sshd_backend)s
diff --git a/go/testdata/beside/mesh-catalog/modules/fail2ban/files/mesh-own-addresses.json b/go/testdata/beside/mesh-catalog/modules/fail2ban/files/mesh-own-addresses.json
new file mode 100644
index 0000000..0eb9604
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/fail2ban/files/mesh-own-addresses.json
@@ -0,0 +1,3 @@
+{
+ "own-addresses": []
+}
diff --git a/go/testdata/beside/mesh-catalog/modules/fail2ban/files/own-addresses b/go/testdata/beside/mesh-catalog/modules/fail2ban/files/own-addresses
new file mode 100644
index 0000000..e69de29
diff --git a/go/testdata/beside/mesh-catalog/modules/fail2ban/files/recidive.conf b/go/testdata/beside/mesh-catalog/modules/fail2ban/files/recidive.conf
new file mode 100644
index 0000000..1f8786f
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/fail2ban/files/recidive.conf
@@ -0,0 +1,9 @@
+[recidive]
+enabled = true
+logpath = /var/log/fail2ban.log
+# Ban in both INPUT (host services like SSH) and DOCKER-USER (container services)
+banaction = iptables-allports-dualchain
+# Banned twice in two weeks, by any jail, is banned for four (novox/hq ADR 0179).
+bantime = 4w
+findtime = 2w
+maxretry = 2
diff --git a/go/testdata/beside/mesh-catalog/modules/fail2ban/files/sshd.conf b/go/testdata/beside/mesh-catalog/modules/fail2ban/files/sshd.conf
new file mode 100644
index 0000000..c8a30f2
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/fail2ban/files/sshd.conf
@@ -0,0 +1,8 @@
+[sshd]
+enabled = true
+port = ssh
+logpath = %(sshd_log)s
+backend = %(sshd_backend)s
+maxretry = 3
+findtime = 1d
+bantime = 1d
diff --git a/go/testdata/beside/mesh-catalog/modules/feh/files/fehbg b/go/testdata/beside/mesh-catalog/modules/feh/files/fehbg
new file mode 100644
index 0000000..a3159bb
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/feh/files/fehbg
@@ -0,0 +1,6 @@
+#!/bin/sh
+# The wallpaper (module feh, novox/hq ADR 0208). Owned by the mesh: replaced at every push. The
+# session's start runs it, and so may anything that wants the declared wallpaper back. The image is
+# the module's own, in ~/.local/share/feh/wallpapers. feh_set changes the wallpaper for a session
+# without touching this file.
+feh --no-fehbg --bg-fill "$HOME/.local/share/feh/wallpapers/default.jpg"
diff --git a/go/testdata/beside/mesh-catalog/modules/feh/files/i3/50-feh.conf b/go/testdata/beside/mesh-catalog/modules/feh/files/i3/50-feh.conf
new file mode 100644
index 0000000..40ac7da
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/feh/files/i3/50-feh.conf
@@ -0,0 +1,3 @@
+# The wallpaper's key (module feh, novox/hq ADR 0208). Owned by the mesh: replaced at every push.
+# It puts the declared wallpaper back, after a monitor change or a wallpaper set for the session.
+bindsym $mod+Shift+b exec --no-startup-id ~/.fehbg
diff --git a/go/testdata/beside/mesh-catalog/modules/feh/files/xinitrc-normal b/go/testdata/beside/mesh-catalog/modules/feh/files/xinitrc-normal
new file mode 100644
index 0000000..8e91e82
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/feh/files/xinitrc-normal
@@ -0,0 +1,2 @@
+# The wallpaper (module feh, novox/hq ADR 0208): the declared one, set once per session.
+"$HOME/.fehbg"
diff --git a/go/testdata/beside/mesh-catalog/modules/fonts/files/50-mesh-fonts.conf b/go/testdata/beside/mesh-catalog/modules/fonts/files/50-mesh-fonts.conf
new file mode 100644
index 0000000..76d0c87
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/fonts/files/50-mesh-fonts.conf
@@ -0,0 +1,40 @@
+
+
+
+
+ The mesh: the faces monospace, sans-serif, serif and emoji mean
+
+
+ Hack Nerd Fontmonospace
+ MesloLGS NFmonospace
+ Iosevka Nerd Fontmonospace
+ JetBrains Mono Nerd FontJetBrainsMono Nerd Font
+
+
+ monospaceJetBrainsMono Nerd Font
+ sans-serifInterNoto Sans
+ system-uiInter
+ serifNoto Serif
+ emojiNoto Color Emoji
+
+
+
+ Symbols Nerd Font
+ Noto Color Emoji
+
+
diff --git a/go/testdata/beside/mesh-catalog/modules/gitea/files/agent-teams.json b/go/testdata/beside/mesh-catalog/modules/gitea/files/agent-teams.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/gitea/files/agent-teams.json
@@ -0,0 +1 @@
+{}
diff --git a/go/testdata/beside/mesh-catalog/modules/gitea/files/config.json b/go/testdata/beside/mesh-catalog/modules/gitea/files/config.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/gitea/files/config.json
@@ -0,0 +1 @@
+{}
diff --git a/go/testdata/beside/mesh-catalog/modules/gitea/files/consumers.json b/go/testdata/beside/mesh-catalog/modules/gitea/files/consumers.json
new file mode 100644
index 0000000..790b51f
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/gitea/files/consumers.json
@@ -0,0 +1 @@
+${consumers:forge-account}
diff --git a/go/testdata/beside/mesh-catalog/modules/gitea/files/origins b/go/testdata/beside/mesh-catalog/modules/gitea/files/origins
new file mode 100644
index 0000000..6bbfb35
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/gitea/files/origins
@@ -0,0 +1 @@
+https://${bound:route:name-web}
diff --git a/go/testdata/beside/mesh-catalog/modules/gitea/files/repositories.json b/go/testdata/beside/mesh-catalog/modules/gitea/files/repositories.json
new file mode 100644
index 0000000..36626cc
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/gitea/files/repositories.json
@@ -0,0 +1 @@
+{"repositories": ${config:repositories}}
diff --git a/go/testdata/beside/mesh-catalog/modules/gitea/files/server.env b/go/testdata/beside/mesh-catalog/modules/gitea/files/server.env
new file mode 100644
index 0000000..8cb88e2
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/gitea/files/server.env
@@ -0,0 +1,7 @@
+GITEA__security__INTERNAL_TOKEN=${secret:internal-token}
+GITEA__database__DB_TYPE=postgres
+GITEA__database__HOST=${bound:postgres-database:at}:${bound:postgres-database:port}
+GITEA__database__NAME=${bound:postgres-database:as}
+GITEA__database__USER=${bound:postgres-database:as}
+GITEA__database__PASSWD=${secret:postgres-database}
+GITEA__webhook__ALLOWED_HOST_LIST=172.17.0.1
diff --git a/go/testdata/beside/mesh-catalog/modules/gitlab/files/config.json b/go/testdata/beside/mesh-catalog/modules/gitlab/files/config.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/gitlab/files/config.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/go/testdata/beside/mesh-catalog/modules/gnome-keyring/files/pam/login b/go/testdata/beside/mesh-catalog/modules/gnome-keyring/files/pam/login
new file mode 100644
index 0000000..47833d9
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/gnome-keyring/files/pam/login
@@ -0,0 +1,4 @@
+# The login keyring (module gnome-keyring, novox/hq ADR 0208, ADR 0102): the password typed at the
+# login screen unlocks the keyring, and the login session starts the keyring daemon with it.
+auth optional pam_gnome_keyring.so
+session optional pam_gnome_keyring.so auto_start
diff --git a/go/testdata/beside/mesh-catalog/modules/gnome-keyring/files/pam/passwd b/go/testdata/beside/mesh-catalog/modules/gnome-keyring/files/pam/passwd
new file mode 100644
index 0000000..dd43bb8
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/gnome-keyring/files/pam/passwd
@@ -0,0 +1,3 @@
+# The login keyring (module gnome-keyring, novox/hq ADR 0208, ADR 0102): changing the account's
+# password changes the login keyring's with it, so the next login still unlocks it.
+password optional pam_gnome_keyring.so
diff --git a/go/testdata/beside/mesh-catalog/modules/gnome-keyring/files/xinitrc-first b/go/testdata/beside/mesh-catalog/modules/gnome-keyring/files/xinitrc-first
new file mode 100644
index 0000000..5fb92b8
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/gnome-keyring/files/xinitrc-first
@@ -0,0 +1,5 @@
+# The ssh agent (module gnome-keyring, novox/hq ADR 0208): gcr's, which the account's service manager
+# starts on first use from its socket. Named here, for the session and every terminal it starts, until
+# the account's environment can say a path under the runtime directory (see the module's README).
+SSH_AUTH_SOCK="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/gcr/ssh"
+export SSH_AUTH_SOCK
diff --git a/go/testdata/beside/mesh-catalog/modules/grafana/files/admin.secret b/go/testdata/beside/mesh-catalog/modules/grafana/files/admin.secret
new file mode 100644
index 0000000..51bf424
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/grafana/files/admin.secret
@@ -0,0 +1 @@
+${secret:admin}
\ No newline at end of file
diff --git a/go/testdata/beside/mesh-catalog/modules/grafana/files/config.json b/go/testdata/beside/mesh-catalog/modules/grafana/files/config.json
new file mode 100644
index 0000000..567df9a
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/grafana/files/config.json
@@ -0,0 +1,4 @@
+{
+ "user": "admin",
+ "password": "${secret:admin}"
+}
diff --git a/go/testdata/beside/mesh-catalog/modules/grafana/files/influxdb-api b/go/testdata/beside/mesh-catalog/modules/grafana/files/influxdb-api
new file mode 100644
index 0000000..8329ab7
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/grafana/files/influxdb-api
@@ -0,0 +1 @@
+${secret:influxdb-api}
\ No newline at end of file
diff --git a/go/testdata/beside/mesh-catalog/modules/grafana/files/mesh-influxdb.yaml b/go/testdata/beside/mesh-catalog/modules/grafana/files/mesh-influxdb.yaml
new file mode 100644
index 0000000..e341005
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/grafana/files/mesh-influxdb.yaml
@@ -0,0 +1,19 @@
+apiVersion: 1
+# Written by the mesh from grafana's influxdb-api binding, only while it is bound (novox/hq ADR 0330):
+# without influxdb, grafana runs with no mesh data source. grafana reads it at start. Its own name and
+# uid, so a data source somebody made in the UI is never overwritten, and read-only in the UI because
+# the mesh resets it. The password is read from the file the mesh delivers, never written here.
+datasources:
+ - name: InfluxDB (mesh)
+ uid: mesh-influxdb-api
+ type: influxdb
+ access: proxy
+ url: ${bound:influxdb-api:scheme}://${bound:influxdb-api:at}:${bound:influxdb-api:port}
+ user: ${bound:influxdb-api:as}
+ isDefault: false
+ editable: false
+ jsonData:
+ dbName: ${bound:influxdb-api:bucket}
+ httpMode: POST
+ secureJsonData:
+ password: $__file{/run/secrets/influxdb/influxdb-api}
diff --git a/go/testdata/beside/mesh-catalog/modules/grafana/files/oidc-client.secret b/go/testdata/beside/mesh-catalog/modules/grafana/files/oidc-client.secret
new file mode 100644
index 0000000..9146dc1
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/grafana/files/oidc-client.secret
@@ -0,0 +1 @@
+${secret:oidc-client}
\ No newline at end of file
diff --git a/go/testdata/beside/mesh-catalog/modules/grafana/files/oidc.env b/go/testdata/beside/mesh-catalog/modules/grafana/files/oidc.env
new file mode 100644
index 0000000..bcc1e9c
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/grafana/files/oidc.env
@@ -0,0 +1,13 @@
+GF_SERVER_ROOT_URL=https://${bound:route:name}
+GF_AUTH_GENERIC_OAUTH_ENABLED=true
+GF_AUTH_GENERIC_OAUTH_NAME=Keycloak
+GF_AUTH_GENERIC_OAUTH_CLIENT_ID=${bound:oidc-client:as}
+GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET__FILE=/run/secrets/oidc-client
+GF_AUTH_GENERIC_OAUTH_SCOPES=openid email profile roles
+GF_AUTH_GENERIC_OAUTH_AUTH_URL=${bound:oidc-client:issuer}${bound:oidc-client:authorization-path}
+GF_AUTH_GENERIC_OAUTH_TOKEN_URL=${bound:oidc-client:issuer}${bound:oidc-client:token-path}
+GF_AUTH_GENERIC_OAUTH_API_URL=${bound:oidc-client:issuer}${bound:oidc-client:userinfo-path}
+GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH=contains(roles[*], 'admin') && 'Admin' || contains(realm_access.roles[*], 'admin') && 'Admin' || 'Viewer'
+GF_AUTH_GENERIC_OAUTH_USE_PKCE=true
+GF_AUTH_GENERIC_OAUTH_ALLOW_SIGN_UP=true
+GF_AUTH_GENERIC_OAUTH_ALLOW_ASSIGN_GRAFANA_ADMIN=true
diff --git a/go/testdata/beside/mesh-catalog/modules/hello-web/files/index.html b/go/testdata/beside/mesh-catalog/modules/hello-web/files/index.html
new file mode 100644
index 0000000..0d513fd
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/hello-web/files/index.html
@@ -0,0 +1 @@
+hello from hello-web, routed by the mesh
diff --git a/go/testdata/beside/mesh-catalog/modules/home-assistant/files/config.json b/go/testdata/beside/mesh-catalog/modules/home-assistant/files/config.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/home-assistant/files/config.json
@@ -0,0 +1 @@
+{}
diff --git a/go/testdata/beside/mesh-catalog/modules/home-assistant/files/provisions.env b/go/testdata/beside/mesh-catalog/modules/home-assistant/files/provisions.env
new file mode 100644
index 0000000..93aba0c
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/home-assistant/files/provisions.env
@@ -0,0 +1,4 @@
+MESH_HOMEASSISTANT_URL=http://127.0.0.1:${port:8123}
+MESH_HOMEASSISTANT_TOKEN_FILE=${dir:mesh-state}/token
+MESH_PROVISIONS_DIR=${dir:state}
+MESH_WRITTEN_DIR=${dir:written}
diff --git a/go/testdata/beside/mesh-catalog/modules/hostname/files/hostname b/go/testdata/beside/mesh-catalog/modules/hostname/files/hostname
new file mode 100644
index 0000000..34c12a4
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/hostname/files/hostname
@@ -0,0 +1 @@
+${config:hostname}
diff --git a/go/testdata/beside/mesh-catalog/modules/hostname/files/hosts b/go/testdata/beside/mesh-catalog/modules/hostname/files/hosts
new file mode 100644
index 0000000..7c9c073
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/hostname/files/hosts
@@ -0,0 +1,7 @@
+# The machine's own names (module hostname, novox/hq ADR 0199, ADR 0223). Every line outside this
+# block is the operator's: kept across every push, changed through the node-hostname verbs add and
+# remove, and given back when this module goes. The mesh's names are not here: the mesh's resolver
+# answers them.
+127.0.0.1 localhost
+::1 localhost
+127.0.1.1 ${machine:name}
diff --git a/go/testdata/beside/mesh-catalog/modules/i3/files/config b/go/testdata/beside/mesh-catalog/modules/i3/files/config
new file mode 100644
index 0000000..c49398c
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/i3/files/config
@@ -0,0 +1,201 @@
+# i3 config file (v4), written by the mesh (module i3, novox/hq ADR 0208). Replaced at every push;
+# change the module instead. i3's user guide is the reference.
+#
+# Other modules add to this configuration as contributions to node-display-session (novox/hq ADR
+# 0212): the mesh places their lines near the end, each module's under a line naming it, where every
+# variable set here ($mod, $ws1 … $ws10) is in scope. A file of yours in ~/.config/i3/config.d/ is read
+# after them, by the include at the very end, and is yours.
+#
+# The reload watcher of this module reloads i3 when this file or a drop-in changes, after checking the
+# result with i3 -C; it never reloads into a configuration with errors.
+
+# Font for window titles, and the bars below: the mesh's monospace face (research 026/04).
+font pango:JetBrainsMono Nerd Font 11
+
+# XDG autostart entries (~/.config/autostart, /etc/xdg/autostart), started once at login.
+exec --no-startup-id dex --autostart --environment i3
+
+#########################################
+###### Keys ####
+#########################################
+# To find key symbols: xmodmap -pke / xmodmap -pm
+set $mod Mod4
+set $alt Mod1
+set $shift Shift
+set $ctrl Control
+
+# use these keys for focus, movement, and resize directions when reaching for
+# the arrows is not convenient
+set $left h
+set $down j
+set $up k
+set $right l
+
+# use Mouse+$mod to drag floating windows to their wanted position
+floating_modifier $mod
+
+# move tiling windows via drag & drop by left-clicking into the title bar,
+# or left-clicking anywhere into the window while holding the floating modifier.
+tiling_drag modifier titlebar
+
+# start a terminal: whichever the terminal module names in $TERMINAL
+bindsym $mod+Return exec i3-sensible-terminal
+
+# kill focused window
+bindsym $mod+$shift+q kill
+
+# change focus
+bindsym $mod+$left focus left
+bindsym $mod+$down focus down
+bindsym $mod+$up focus up
+bindsym $mod+$right focus right
+
+bindsym $mod+Left focus left
+bindsym $mod+Down focus down
+bindsym $mod+Up focus up
+bindsym $mod+Right focus right
+
+# move focused window
+bindsym $mod+$shift+$left move left
+bindsym $mod+$shift+$down move down
+bindsym $mod+$shift+$up move up
+bindsym $mod+$shift+$right move right
+
+bindsym $mod+$shift+Left move left
+bindsym $mod+$shift+Down move down
+bindsym $mod+$shift+Up move up
+bindsym $mod+$shift+Right move right
+
+# split in horizontal orientation
+bindsym $mod+c split h
+# split in vertical orientation
+bindsym $mod+v split v
+
+# enter fullscreen mode for the focused container
+bindsym $mod+f fullscreen toggle
+
+# change container layout (stacked, tabbed, toggle split)
+bindsym $mod+s layout stacking
+bindsym $mod+w layout tabbed
+bindsym $mod+e layout toggle split
+
+# toggle tiling / floating
+bindsym $mod+$shift+space floating toggle
+
+# change focus between tiling / floating windows
+bindsym $mod+space focus mode_toggle
+
+# focus the parent container
+bindsym $mod+a focus parent
+
+# alt-tab functionality
+bindsym $mod+Tab workspace back_and_forth
+
+#########################################
+###### Workspace mgmt ####
+#########################################
+set $ws1 "1"
+set $ws2 "2"
+set $ws3 "3"
+set $ws4 "4"
+set $ws5 "5"
+set $ws6 "6"
+set $ws7 "7"
+set $ws8 "8"
+set $ws9 "9"
+set $ws10 "10"
+
+bindsym $mod+1 workspace number $ws1
+bindsym $mod+2 workspace number $ws2
+bindsym $mod+3 workspace number $ws3
+bindsym $mod+4 workspace number $ws4
+bindsym $mod+5 workspace number $ws5
+bindsym $mod+6 workspace number $ws6
+bindsym $mod+7 workspace number $ws7
+bindsym $mod+8 workspace number $ws8
+bindsym $mod+9 workspace number $ws9
+bindsym $mod+0 workspace number $ws10
+
+bindsym $mod+$shift+1 move container to workspace number $ws1
+bindsym $mod+$shift+2 move container to workspace number $ws2
+bindsym $mod+$shift+3 move container to workspace number $ws3
+bindsym $mod+$shift+4 move container to workspace number $ws4
+bindsym $mod+$shift+5 move container to workspace number $ws5
+bindsym $mod+$shift+6 move container to workspace number $ws6
+bindsym $mod+$shift+7 move container to workspace number $ws7
+bindsym $mod+$shift+8 move container to workspace number $ws8
+bindsym $mod+$shift+9 move container to workspace number $ws9
+bindsym $mod+$shift+0 move container to workspace number $ws10
+
+#########################################
+###### Window mgmt ####
+#########################################
+set $resize_px 10 px
+bindsym $mod+$ctrl+$left resize shrink width $resize_px
+bindsym $mod+$ctrl+$down resize grow height $resize_px
+bindsym $mod+$ctrl+$up resize shrink height $resize_px
+bindsym $mod+$ctrl+$right resize grow width $resize_px
+
+#########################################
+###### Session mgmt ####
+#########################################
+bindsym $mod+$shift+e exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -B 'Yes, exit i3' 'i3-msg exit'"
+
+# Lock the screen through logind, so the one locker the lock screen's module runs handles it (and
+# suspend and lid close too).
+bindsym $mod+Delete exec --no-startup-id loginctl lock-session
+
+# reload the configuration file
+bindsym $mod+$shift+c reload
+
+# restart i3 inplace (preserves your layout/session, can be used to upgrade i3)
+bindsym $mod+$shift+r restart
+
+#########################################
+###### Borders ####
+#########################################
+default_border pixel 1
+smart_borders on
+
+#########################################
+###### Gaps ####
+#########################################
+gaps inner 0
+gaps outer 0
+
+# Only the accent-bearing slots are themed; background and text keep i3's own defaults. Borders are
+# `pixel`, so no title bar shows: the colour says which window has focus.
+# border background text indicator child_border
+client.focused #de5200 #de5200 #1E2127 #de5200 #de5200
+client.urgent #900000 #900000 #ffffff #900000 #900000
+
+#########################################
+###### Until their modules carry them ##
+#########################################
+# Each line below belongs to something other than i3, named on its line. When that module is written
+# it contributes the line to node-display-session, and the line goes from here in the same change.
+# The launcher, the clipboard, the wallpaper, the bars, a machine model's keys and the peripherals'
+# tray already contribute theirs (rofi, clipmenu, feh, i3status-rust, asus-zephyrus-g14,
+# polychromatic).
+
+# the operator's scripts: volume, games volume, sessions, screenshot
+bindsym XF86AudioRaiseVolume exec --no-startup-id volume-notify up
+bindsym XF86AudioLowerVolume exec --no-startup-id volume-notify down
+bindsym XF86AudioMute exec --no-startup-id volume-notify mute
+bindsym XF86AudioMicMute exec --no-startup-id mic-notify
+bindsym $ctrl+XF86AudioRaiseVolume exec --no-startup-id set-games-volume 5
+bindsym $ctrl+XF86AudioLowerVolume exec --no-startup-id set-games-volume -5
+bindsym $mod+$shift+Return exec --no-startup-id ~/scripts/i3-sessions/launcher.sh
+bindsym --release $ctrl+$shift+x exec --no-startup-id $XDG_CONFIG_HOME/i3/scripts/screenshot.sh
+
+#########################################
+###### Other modules' lines ####
+#########################################
+# Placed by the mesh from every other module's contribution (novox/hq ADR 0212): the launcher, the
+# clipboard, the wallpaper, the bars, a machine model's keys, the peripherals' tray. Each module's
+# under a line naming it.
+${contribution:node-display-session:config}
+#########################################
+###### Your own files ####
+#########################################
+include ~/.config/i3/config.d/*.conf
diff --git a/go/testdata/beside/mesh-catalog/modules/i3/files/env-xdg-current-desktop b/go/testdata/beside/mesh-catalog/modules/i3/files/env-xdg-current-desktop
new file mode 100644
index 0000000..04ad29f
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/i3/files/env-xdg-current-desktop
@@ -0,0 +1 @@
+i3
diff --git a/go/testdata/beside/mesh-catalog/modules/i3/files/env-xdg-session-desktop b/go/testdata/beside/mesh-catalog/modules/i3/files/env-xdg-session-desktop
new file mode 100644
index 0000000..04ad29f
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/i3/files/env-xdg-session-desktop
@@ -0,0 +1 @@
+i3
diff --git a/go/testdata/beside/mesh-catalog/modules/i3/files/wms/i3 b/go/testdata/beside/mesh-catalog/modules/i3/files/wms/i3
new file mode 100644
index 0000000..4e269ad
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/i3/files/wms/i3
@@ -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"
diff --git a/go/testdata/beside/mesh-catalog/modules/i3/files/xinitrc-last b/go/testdata/beside/mesh-catalog/modules/i3/files/xinitrc-last
new file mode 100644
index 0000000..e84ff3d
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/i3/files/xinitrc-last
@@ -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
diff --git a/go/testdata/beside/mesh-catalog/modules/i3status-rust/files/bin/i3bar-watchdog b/go/testdata/beside/mesh-catalog/modules/i3status-rust/files/bin/i3bar-watchdog
new file mode 100644
index 0000000..63b2b02
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/i3status-rust/files/bin/i3bar-watchdog
@@ -0,0 +1,64 @@
+#!/usr/bin/env bash
+# i3bar-watchdog [session-pid] (module i3status-rust, novox/hq ADR 0208): respawns a bar that died.
+#
+# i3 starts one i3bar per `bar { }` block and never restarts one that exits, and a reload does not
+# either. Changing the monitor setup reliably kills the bar that owns the tray. This brings back the
+# one missing i3bar, without restarting i3, and logs the outputs at that moment: the evidence for the
+# cause, which is i3bar's.
+#
+# Started once per session from the session's start, with the session's own pid; it ends when that
+# process does. Adopted from the predecessor's i3-bar-watchdog user unit of 2026-10-04.
+set -uo pipefail
+
+session_pid="${1:-}"
+interval="${I3_BAR_WATCHDOG_INTERVAL:-5}"
+# Two misses in a row before acting: an i3 restart tears every bar down and starts them again.
+confirm="${I3_BAR_WATCHDOG_CONFIRM:-2}"
+# Never respawn the same bar more often than this, so a bar that dies at once is not a tight loop.
+cooldown="${I3_BAR_WATCHDOG_COOLDOWN:-30}"
+
+log() { printf 'i3bar-watchdog: %s\n' "$*" >&2; }
+
+for tool in i3-msg i3bar pgrep; do
+ command -v "$tool" >/dev/null 2>&1 || {
+ log "$tool is missing; not watching"
+ exit 1
+ }
+done
+
+declare -A missed=() fixed=()
+
+bar_ids() { i3-msg -t get_bar_config 2>/dev/null | grep -oE '"[^"]+"' | tr -d '"'; }
+outputs() { xrandr --listmonitors 2>/dev/null | tail -n +2 | awk '{print $2" "$3}' | tr '\n' ' '; }
+session_alive() { [ -z "$session_pid" ] || kill -0 "$session_pid" 2>/dev/null; }
+
+while session_alive; do
+ sleep "$interval"
+ socket="$(i3 --get-socketpath 2>/dev/null)"
+ [ -n "$socket" ] && [ -S "$socket" ] || continue
+ ids="$(bar_ids)"
+ [ -n "$ids" ] || continue
+ while read -r id; do
+ [ -n "$id" ] || continue
+ if pgrep -u "$EUID" -f -- "i3bar --bar_id=$id" >/dev/null 2>&1; then
+ missed[$id]=0
+ continue
+ fi
+ missed[$id]=$((${missed[$id]:-0} + 1))
+ [ "${missed[$id]}" -ge "$confirm" ] || continue
+ now="$(date +%s)"
+ if [ $((now - ${fixed[$id]:-0})) -lt "$cooldown" ]; then
+ continue
+ fi
+ log "$id is gone; respawning it. Outputs now: $(outputs)"
+ nohup i3bar --bar_id="$id" --socket="$socket" >/dev/null 2>&1 &
+ disown 2>/dev/null || true
+ fixed[$id]="$now"
+ sleep 2
+ if pgrep -u "$EUID" -f -- "i3bar --bar_id=$id" >/dev/null 2>&1; then
+ missed[$id]=0
+ else
+ log "$id exited within 2s of respawning; check its status_command"
+ fi
+ done <<<"$ids"
+done
diff --git a/go/testdata/beside/mesh-catalog/modules/i3status-rust/files/bin/i3status-updates b/go/testdata/beside/mesh-catalog/modules/i3status-rust/files/bin/i3status-updates
new file mode 100644
index 0000000..1a70cf9
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/i3status-rust/files/bin/i3status-updates
@@ -0,0 +1,63 @@
+#!/usr/bin/env bash
+# i3status-updates [threshold] (module i3status-rust, novox/hq ADR 0208): the pending updates, for
+# the bottom bar's custom block (json = true). Emits one JSON line.
+#
+# Adopted from the operator's pkg-updates of 2026-10-04. The native `packages` block showed a raw
+# red error on every transient network failure, which on a roaming laptop is often. This keeps the
+# last good result through a failure and shows an error only after `threshold` failures in a row
+# (default 10). Official packages through checkupdates (pacman-contrib, a temporary database, no
+# root); AUR packages through paru when it is installed, and none counted when it is not.
+set -uo pipefail
+
+threshold="${1:-10}"
+icon="update"
+state_dir="${XDG_RUNTIME_DIR:-/tmp}/i3status-updates"
+mkdir -p "$state_dir"
+last="$state_dir/last_good.json"
+failures="$state_dir/failures"
+
+json() { printf '{"icon":"%s","state":"%s","text":"%s","short_text":"%s"}' "$icon" "$1" "$2" "$3"; }
+count() { [ -n "$1" ] && printf '%s\n' "$1" | grep -c . || echo 0; }
+
+official="$(checkupdates 2>/dev/null)"
+official_rc=$?
+aur="" aur_rc=0
+if command -v paru >/dev/null 2>&1; then
+ aur="$(paru -Qua 2>/dev/null)"
+ aur_rc=$?
+fi
+
+# checkupdates: 0 updates listed, 2 none pending; anything else is a failure.
+if { [ "$official_rc" -eq 0 ] || [ "$official_rc" -eq 2 ]; } && [ "$aur_rc" -eq 0 ]; then
+ o="$(count "$official")"
+ a="$(count "$aur")"
+ total=$((o + a))
+ if [ "$total" -eq 0 ]; then
+ payload="$(json Good "up to date" "")"
+ else
+ # A kernel update wants a reboot: the one worth not putting off.
+ state=Info
+ printf '%s\n' "$official" | grep -qE '^(linux|linux-lts|linux-zen) ' && state=Warning
+ label="$o + $a = $total updates"
+ [ "$a" -eq 0 ] && label="$total updates"
+ [ "$total" -eq 1 ] && label="1 update"
+ payload="$(json "$state" "$label" "$total")"
+ fi
+ printf '%s' "$payload" >"$last"
+ printf '0' >"$failures"
+ printf '%s\n' "$payload"
+ exit 0
+fi
+
+n=$(($(cat "$failures" 2>/dev/null || echo 0) + 1))
+printf '%s' "$n" >"$failures"
+if [ "$n" -ge "$threshold" ]; then
+ json Critical "update check failing (${n}x)" "!"
+ echo
+elif [ -s "$last" ]; then
+ cat "$last"
+ echo
+else
+ json Idle "" ""
+ echo
+fi
diff --git a/go/testdata/beside/mesh-catalog/modules/i3status-rust/files/bin/i3status-value b/go/testdata/beside/mesh-catalog/modules/i3status-rust/files/bin/i3status-value
new file mode 100644
index 0000000..8fea95e
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/i3status-rust/files/bin/i3status-value
@@ -0,0 +1,13 @@
+#!/bin/sh
+# i3status-value (module i3status-rust, novox/hq ADR 0260): one value another module keeps
+# on the bus, for a block it contributed.
+#
+# This module's own tools bundle watches the state on the bus (the mesh grants it the read) and leaves
+# the value's text in this module's own directory; this prints it. A value not refreshed for thirty
+# seconds is one its module stopped giving, and prints nothing, so the block hides rather than show an
+# old number as if it were now.
+dir="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/i3status-rust/values"
+f="$dir/$1@$2"
+if [ -r "$f" ] && [ $(( $(date +%s) - $(stat -c %Y "$f") )) -le 30 ]; then
+ head -n 1 "$f"
+fi
diff --git a/go/testdata/beside/mesh-catalog/modules/i3status-rust/files/bottom-bar.toml b/go/testdata/beside/mesh-catalog/modules/i3status-rust/files/bottom-bar.toml
new file mode 100644
index 0000000..12e6805
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/i3status-rust/files/bottom-bar.toml
@@ -0,0 +1,109 @@
+# The bottom bar (module i3status-rust, novox/hq ADR 0208). Owned by the mesh: replaced at every
+# push. Adopted from the workstations of 2026-10-04 with what follows a machine's hardware or one
+# person's devices left out: a block that depends on the hardware is contributed by the module that
+# knows it (novox/hq ADR 0255), and a headset's address is not the bar's to know. Every block written
+# here works the same on any machine.
+icons_format = "{icon}"
+
+[theme]
+theme = "plain"
+[theme.overrides]
+idle_bg = "#000000"
+idle_fg = "#f1f1f1"
+info_bg = "#000000"
+info_fg = "#f1f1f1"
+good_bg = "#000000"
+good_fg = "#859900"
+warning_bg = "#000000"
+warning_fg = "#de5200"
+critical_bg = "#000000"
+critical_fg = "#ff0000"
+separator = " | "
+separator_fg = "#ffffff"
+separator_bg = "#000000"
+
+[icons]
+icons = "custom-icons"
+
+[[block]]
+block = "tea_timer"
+format = "$icon{ $minutes:$seconds|}"
+done_cmd = "notify-send 'Timer finished'"
+
+# Pending updates: official and, where an AUR helper is installed, AUR. The script keeps the last good
+# count through a network gap and shows an error only after ten failed checks in a row. Click: the
+# list, in the launcher's menu.
+[[block]]
+block = "custom"
+command = "~/.local/bin/i3status-updates 10"
+json = true
+interval = 1800
+[[block.click]]
+button = "left"
+cmd = "checkupdates | dmenu -l 20 -p Updates"
+
+[[block]]
+block = "cpu"
+
+[[block]]
+block = "disk_space"
+path = "/"
+info_type = "used"
+alert_unit = "GB"
+interval = 20
+warning = 850
+alert = 920
+format = "$icon / $used.eng(w:2) ($percentage.eng(w:2))"
+
+[[block]]
+block = "disk_space"
+path = "/home"
+info_type = "used"
+alert_unit = "GB"
+interval = 20
+warning = 850
+alert = 920
+format = "$icon /home $used.eng(w:2) ($percentage.eng(w:2))"
+
+[[block]]
+block = "memory"
+# What programs hold, without the cache the kernel gives back when asked: the figure the memory-pressure
+# module judges by. Counting the cache as used read near full on a machine with room to spare.
+format = "$icon $mem_used.eng(w:2) ($mem_used_percents.eng(w:2))"
+format_alt = "$icon_swap $swap_used.eng(w:2) ($swap_used_percents.eng(w:2))"
+
+# Blocks other modules contribute beside the machine's resources (novox/hq ADR 0255), rendered from
+# what they show by this module's template, and only on a machine that has what they show.
+${collected:contribution:node-bar:block:bar=bottom,place=resources}
+[[block]]
+block = "docker"
+interval = 2
+format = "$icon $running/$total"
+
+[[block]]
+block = "sound"
+[[block.click]]
+button = "left"
+cmd = "pavucontrol"
+
+# Blocks other modules contribute beside the sound (novox/hq ADR 0255): the power module's battery and
+# power draw, where the machine has them.
+${collected:contribution:node-bar:block:bar=bottom,place=status}
+# The weather from the Norwegian Meteorological Institute, which needs no key, where the machine is.
+[[block]]
+block = "weather"
+format = "$icon $weather_verbose ($location) $temp"
+autolocate = true
+autolocate_interval = 600
+[block.service]
+name = "metno"
+
+[[block]]
+block = "notify"
+driver = "dunst"
+format = " $icon {($notification_count.eng(w:1))|}"
+
+[[block]]
+block = "time"
+interval = 1
+format = "$timestamp.datetime(f:'%a %d/%m %H:%M:%S')"
diff --git a/go/testdata/beside/mesh-catalog/modules/i3status-rust/files/i3/60-i3status-rust.conf b/go/testdata/beside/mesh-catalog/modules/i3status-rust/files/i3/60-i3status-rust.conf
new file mode 100644
index 0000000..20acbb1
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/i3status-rust/files/i3/60-i3status-rust.conf
@@ -0,0 +1,34 @@
+# The bars (module i3status-rust, novox/hq ADR 0208). Owned by the mesh: replaced at every push.
+# i3 reads this file through its configuration's `include ~/.config/i3/config.d/*.conf`. The bottom
+# bar shows the machine; the top bar the focused window and the tray, on the primary output. The
+# face is the monospace one every desktop module names.
+bar {
+ font pango:JetBrainsMono Nerd Font 11
+ position bottom
+ status_command i3status-rs ~/.config/i3status-rust/bottom-bar.toml
+ tray_output none
+ colors {
+ separator #ffffff
+ background #000000
+ statusline #ffffff
+ # The text on an accent-coloured button is dark: light text on the accent was barely
+ # legible.
+ focused_workspace #de5200 #de5200 #000000
+ active_workspace #de5200 #de5200 #000000
+ inactive_workspace #000000 #000000 #ffffff
+ urgent_workspace #2f343a #900000 #ffffff
+ }
+}
+
+bar {
+ font pango:JetBrainsMono Nerd Font 11
+ position top
+ status_command i3status-rs ~/.config/i3status-rust/top-bar.toml
+ workspace_buttons no
+ tray_output primary
+ colors {
+ separator #ffffff
+ background #000000
+ statusline #ffffff
+ }
+}
diff --git a/go/testdata/beside/mesh-catalog/modules/i3status-rust/files/icons/custom-icons.toml b/go/testdata/beside/mesh-catalog/modules/i3status-rust/files/icons/custom-icons.toml
new file mode 100644
index 0000000..9636a82
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/i3status-rust/files/icons/custom-icons.toml
@@ -0,0 +1,135 @@
+# Icons for the bars (module i3status-rust, novox/hq ADR 0208), from the JetBrains Mono Nerd Font.
+# Owned by the mesh: replaced at every push. Adopted unchanged from the workstations of 2026-10-04.
+# Material from NerdFont
+# Codepoints from the Nerd Fonts cheat sheet
+backlight = [
+ "\ue38d", # nf-weather-moon_new
+ "\ue3d4", # nf-weather-moon_alt_waxing_gibbous_6
+ "\ue3d3", # nf-weather-moon_alt_waxing_gibbous_5
+ "\ue3d2", # nf-weather-moon_alt_waxing_gibbous_4
+ "\ue3d1", # nf-weather-moon_alt_waxing_gibbous_3
+ "\ue3d0", # nf-weather-moon_alt_waxing_gibbous_2
+ "\ue3cf", # nf-weather-moon_alt_waxing_gibbous_1
+ "\ue3ce", # nf-weather-moon_alt_first_quarter
+ "\ue3cd", # nf-weather-moon_alt_waxing_crescent_6
+ "\ue3cc", # nf-weather-moon_alt_waxing_crescent_5
+ "\ue3cb", # nf-weather-moon_alt_waxing_crescent_4
+ "\ue3ca", # nf-weather-moon_alt_waxing_crescent_3
+ "\ue3c9", # nf-weather-moon_alt_waxing_crescent_2
+ "\ue3c8", # nf-weather-moon_alt_waxing_crescent_1
+ "\ue39b", # nf-weather-moon_full
+]
+bat_charging = "\U000f0084" # nf-md-battery_charging
+bat_not_available = "\U000f0091" # nf-md-battery_unknown
+bat = [
+ "\U000f007a", # nf-md-battery_10
+ "\U000f007b", # nf-md-battery_20
+ "\U000f007c", # nf-md-battery_30
+ "\U000f007d", # nf-md-battery_40
+ "\U000f007e", # nf-md-battery_50
+ "\U000f007f", # nf-md-battery_60
+ "\U000f0080", # nf-md-battery_70
+ "\U000f0081", # nf-md-battery_80
+ "\U000f0082", # nf-md-battery_90
+ "\U000f0079", # nf-md-battery
+]
+bell = "\U000f009c" # nf-md-bell_outline
+bell-slash = "\U000f009b" # nf-md-bell_off
+blackberry = "\uf307" # nf-md-blackberry
+bluetooth = "\U000f00af" # nf-md-bluetooth
+calendar = "\U000f00ed" # nf-md-calendar
+cogs = "\U000f0493" # nf-md-cog
+cpu = [
+ "\U000F0F86", # nf-md-speedometer_slow
+ "\U000F0F85", # nf-md-speedometer_medium
+ "\U000F04C5", # nf-md-speedometer
+]
+cpu_boost_on = "\U000f0521" # nf-md-toggle_switch
+cpu_boost_off = "\U000f0a19" # nf-md-toggle_switch_off_outline
+disk_drive = "\U000f02ca" # nf-md-harddisk
+docker = "\uf308" # nf-linux-docker
+github = "\U000f02a4" # nf-md-github
+gpu = "\U000f0379" # nf-md-monitor
+headphones = "\U000f02cb" # nf-md-headphones
+joystick = "\U000f0297" # nf-md-gamepad_variant
+keyboard = "\U000f030c" # nf-md-keyboard
+mail = "\U000f01ee" # nf-md-email
+memory_mem = "\U000f035b" # nf-md-memory
+memory_swap = "\U000f02ca" # nf-md-harddisk
+mouse = "\U000f037d" # nf-md-mouse
+music = "\U000f075a" # nf-md-music
+music_next = "\U000f04ad" # nf-md-skip_next
+music_pause = "\U000f03e4" # nf-md-pause
+music_play = "\U000f040a" # nf-md-play
+music_prev = "\U000f04ae" # nf-md-skip_previous
+net_bridge = "\U000f04aa" # nf-md-sitemap
+net_down = "\U000f01da" # nf-md-download
+net_loopback = "\U000f006f" # nf-md-backup_restore
+net_modem = "\U000f03f2" # nf-md-phone
+net_cellular = [
+ "\U000F08FD", # nf-md-network_strength_off_outline
+ "\U000F08FE", # nf-md-network_strength_outline
+ "\U000F08F4", # nf-md-network_strength_1
+ "\U000F08F6", # nf-md-network_strength_2
+ "\U000F08F8", # nf-md-network_strength_3
+ "\U000F08FA", # nf-md-network_strength_4
+]
+net_up = "\U000f0552" # nf-md-upload
+net_vpn = "\U000f0582" # nf-md-vpn
+net_wired = "\U000f0200" # nf-md-ethernet
+net_wireless = [
+ "\U000F092F", # nf-md-wifi_strength_outline
+ "\U000F091F", # nf-md-wifi_strength_1
+ "\U000F0922", # nf-md-wifi_strength_2
+ "\U000F0925", # nf-md-wifi_strength_3
+ "\U000F0928", # nf-md-wifi_strength_4
+]
+notification = "\U000f009c" # nf-md-bell_outline
+phone = "\U000f03f2" # nf-md-phone
+phone_disconnected = "\U000f0658" # nf-md-phone_minus
+ping = "\U000f051f" # nf-md-timer_sand
+pomodoro = "\ue001" # nf-pom-pomodoro_done
+pomodoro_break = "\U000f0176" # nf-md-coffee
+pomodoro_paused = "\U000f03e4" # nf-md-pause
+pomodoro_started = "\U000f040a" # nf-md-play
+pomodoro_stopped = "\U000f04db" # nf-md-stop
+resolution = "\U000f0293" # nf-md-fullscreen
+tasks = "\U000f05c7" # nf-md-playlist_check
+tea = "\U000f0d9e" # nf-md-tea
+thermometer = [
+ "\U000f10c3", # nf-md-thermometer_low
+ "\U000f050f", # nf-md-thermometer
+ "\U000f10c2", # nf-md-thermometer_high
+]
+time = "\U000f0150" # nf-md-clock_outline
+toggle_off = "\U000f0a19" # nf-md-toggle_switch_off_outline
+toggle_on = "\U000f0521" # nf-md-toggle_switch
+unknown = "\U000f0186" # nf-md-comment_question_outline | TODO: Make default?
+update = "\U000f03d5" # nf-md-package_up
+uptime = "\U000f0153" # nf-md-clock_in
+volume_muted = "\U000f075f" # nf-md-volume_mute
+volume = [
+ "\U000f057f", # nf-md-volume_low
+ "\U000f0580", # nf-md-volume_medium
+ "\U000f057e", # nf-md-volume_high
+]
+microphone_muted = "\U000f036d" # nf-md-microphone_off
+microphone = [
+ "\U000f036e", # nf-md-microphone_outline
+ "\U000f036c", # nf-md-microphone
+ "\U000f036c", # nf-md-microphone
+]
+xrandr = "\U000f037a" # nf-md-monitor_multiple
+
+# Weather icons (the names the weather block documents)
+weather_sun = "\ue30d" # nf-weather-day_sunny (when weather is reported as “Clear” during the day)
+weather_moon = "\ue32b" # nf-weather-night_clear (when weather is reported as “Clear” at night)
+weather_clouds = "\ue312" # nf-weather-cloudy (when weather is reported as “Clouds” during the day)
+weather_clouds_night = "\ue37e" # nf-weather-night_alt_cloudy (when weather is reported as “Clouds” at night)
+weather_fog = "\ue313" # nf-weather-fog (when weather is reported as “Fog” or “Mist” during the day)
+weather_fog_night = "\ue346" # nf-weather-night_fog (when weather is reported as “Fog” or “Mist” at night)
+weather_rain = "\ue318" # nf-weather-rain (when weather is reported as “Rain” or “Drizzle” during the day)
+weather_rain_night = "\ue325" # nf-weather-night_alt_rain (when weather is reported as “Rain” or “Drizzle” at night)
+weather_snow = "\ue31a" # nf-weather-snow (when weather is reported as “Snow”)
+weather_thunder = "\ue31d" # nf-weather-thunderstorm (when weather is reported as “Thunderstorm” during the day)
+weather_thunder_night = "\ue32a" # nf-weather-night_alt_thunderstorm (when weather is reported as “Thunderstorm” at night)
diff --git a/go/testdata/beside/mesh-catalog/modules/i3status-rust/files/top-bar.toml b/go/testdata/beside/mesh-catalog/modules/i3status-rust/files/top-bar.toml
new file mode 100644
index 0000000..be198e8
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/i3status-rust/files/top-bar.toml
@@ -0,0 +1,37 @@
+# The top bar (module i3status-rust, novox/hq ADR 0208): the focused window's title and the uptime.
+# Owned by the mesh: replaced at every push. Adopted unchanged from the workstations of 2026-10-04.
+icons_format = "{icon}"
+
+[theme]
+theme = "plain"
+[theme.overrides]
+idle_bg = "#000000"
+idle_fg = "#f1f1f1"
+info_bg = "#000000"
+info_fg = "#f1f1f1"
+good_bg = "#000000"
+good_fg = "#859900"
+warning_bg = "#000000"
+warning_fg = "#de5200"
+critical_bg = "#000000"
+critical_fg = "#ff0000"
+separator = " | "
+separator_fg = "#ffffff"
+separator_bg = "#000000"
+
+[icons]
+icons = "custom-icons"
+
+[[block]]
+block = "focused_window"
+[block.format]
+full = " $title.str(max_w:50) |"
+short = " $title.str(max_w:100) |"
+
+[[block]]
+block = "uptime"
+interval = 60
+
+# Blocks other modules contribute to the top bar (novox/hq ADR 0255). None do yet; both places are kept
+# so that one that does is never dropped.
+${collected:contribution:node-bar:block:bar=top,place=resources}${collected:contribution:node-bar:block:bar=top,place=status}
diff --git a/go/testdata/beside/mesh-catalog/modules/i3status-rust/files/xinitrc-normal b/go/testdata/beside/mesh-catalog/modules/i3status-rust/files/xinitrc-normal
new file mode 100644
index 0000000..888acdb
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/i3status-rust/files/xinitrc-normal
@@ -0,0 +1,3 @@
+# The bar watchdog (module i3status-rust, novox/hq ADR 0208): i3 never restarts a bar that dies; this
+# brings it back. Once per session, ending with the session's own process ($$).
+"$HOME/.local/bin/i3bar-watchdog" "$$" &
diff --git a/go/testdata/beside/mesh-catalog/modules/icecast/files/config.json b/go/testdata/beside/mesh-catalog/modules/icecast/files/config.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/icecast/files/config.json
@@ -0,0 +1 @@
+{}
diff --git a/go/testdata/beside/mesh-catalog/modules/icecast/files/icecast.xml b/go/testdata/beside/mesh-catalog/modules/icecast/files/icecast.xml
new file mode 100644
index 0000000..de4dd79
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/icecast/files/icecast.xml
@@ -0,0 +1,56 @@
+
+
+ Earth
+ icemaster@localhost
+
+ 100
+ 2
+ 524288
+ 30
+ 15
+ 10
+ 1
+ 65535
+
+
+ ${secret:source}
+ ${secret:relay}
+ admin
+ ${secret:admin}
+
+
+ localhost
+
+ 8000
+
+
+
+
+ 1
+
+ /usr/share/icecast
+ /var/log/icecast
+ /usr/share/icecast/web
+ /usr/share/icecast/admin
+
+
+
+ access.log
+ error.log
+ 3
+ 10000
+
+
+ 0
+
+
+ icecast
+ icecast
+
+
+
diff --git a/go/testdata/beside/mesh-catalog/modules/influxdb/files/config.json b/go/testdata/beside/mesh-catalog/modules/influxdb/files/config.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/influxdb/files/config.json
@@ -0,0 +1 @@
+{}
diff --git a/go/testdata/beside/mesh-catalog/modules/influxdb/files/server.env b/go/testdata/beside/mesh-catalog/modules/influxdb/files/server.env
new file mode 100644
index 0000000..d2552e0
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/influxdb/files/server.env
@@ -0,0 +1,6 @@
+DOCKER_INFLUXDB_INIT_MODE=setup
+DOCKER_INFLUXDB_INIT_USERNAME=admin
+DOCKER_INFLUXDB_INIT_PASSWORD_FILE=/run/secrets/admin
+DOCKER_INFLUXDB_INIT_ADMIN_TOKEN_FILE=/run/secrets/admin-token
+DOCKER_INFLUXDB_INIT_ORG=mesh
+DOCKER_INFLUXDB_INIT_BUCKET=default
diff --git a/go/testdata/beside/mesh-catalog/modules/invoicing/files/api.env b/go/testdata/beside/mesh-catalog/modules/invoicing/files/api.env
new file mode 100644
index 0000000..8eaee6f
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/invoicing/files/api.env
@@ -0,0 +1,9 @@
+NODE_ENV=production
+PORT=9000
+MONGO_URL=mongodb://${bound:mongodb-database:as}:${secret:mongodb-database}@${bound:mongodb-database:at}:${bound:mongodb-database:port}/${bound:mongodb-database:as}?authSource=${bound:mongodb-database:as}
+MONGO_DB=${bound:mongodb-database:as}
+MINIO_BUCKET=${bound:s3-bucket:bucket}
+MINIO_ENDPOINT=${bound:s3-bucket:at}
+MINIO_PORT=${bound:s3-bucket:port}
+MINIO_ACCESSKEY=${bound:s3-bucket:as}
+MINIO_SECRET=${secret:s3-bucket}
diff --git a/go/testdata/beside/mesh-catalog/modules/jira/files/config.json b/go/testdata/beside/mesh-catalog/modules/jira/files/config.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/jira/files/config.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/go/testdata/beside/mesh-catalog/modules/kernel/files/huge-pages.sh b/go/testdata/beside/mesh-catalog/modules/kernel/files/huge-pages.sh
new file mode 100644
index 0000000..e0a9815
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/kernel/files/huge-pages.sh
@@ -0,0 +1,84 @@
+#!/bin/sh
+# Transparent huge pages as the mesh sets them on this machine (module kernel, novox/hq issue 746).
+# Written by the mesh from this machine's setting transparent-huge-pages and run by
+# mesh-kernel-huge-pages.service: `apply` at boot and whenever the setting changes, `restore` when the
+# unit stops (the module unassigned, or the machine shutting down). Replaced on every push; change the
+# setting, never this file. A change of the setting reloads the unit, which runs `apply` again: the mode
+# moves straight to the new value and never passes back through the one the machine was found with.
+#
+# huge-pages apply|restore [ []]
+#
+# The two directories are arguments only so the module's tests can run this against a stand-in tree.
+set -eu
+
+want='${config:transparent-huge-pages}'
+sys=/sys/kernel/mm/transparent_hugepage
+state='${dir:state}'
+[ $# -ge 2 ] && sys=$2
+[ $# -ge 3 ] && state=$3
+
+case "$want" in
+always | madvise | never) ;;
+*)
+ echo "the setting transparent-huge-pages is '$want'; the kernel takes always, madvise or never" >&2
+ exit 2
+ ;;
+esac
+[ -f "$sys/enabled" ] || {
+ echo "this kernel has no $sys/enabled: it was built without transparent huge pages, so there is nothing to set" >&2
+ exit 1
+}
+
+# The mode in force is the word the kernel brackets: "always [madvise] never". A file holding one bare
+# word (the stand-in tree of the tests) is read as that word.
+mode() { sed -n -e 's/.*\[\([a-z]*\)\].*/\1/p' -e t -e 's/^ *\([a-z]*\) *$/\1/p' "$sys/enabled"; }
+
+[ $# -ge 1 ] || set -- usage
+case "$1" in
+apply)
+ now=$(mode)
+ # What the machine had before the mesh first set it, kept once, so `restore` gives it back.
+ [ -s "$state/found" ] || printf '%s\n' "$now" >"$state/found"
+ if [ "$now" = "$want" ]; then
+ echo "transparent huge pages: $want, as set"
+ exit 0
+ fi
+ printf '%s\n' "$want" >"$sys/enabled"
+ got=$(mode)
+ [ "$got" = "$want" ] || {
+ echo "wrote $want to $sys/enabled and the kernel says $got" >&2
+ exit 1
+ }
+ echo "transparent huge pages: $now -> $want"
+ # A file folio already in the page cache keeps its size: the active journal files, mapped by journald
+ # and by every reader, stay in 2 MiB folios until they are archived (issue 746). Rotating starts new
+ # files that are mapped under the new mode. A failed rotation is said and does not fail the setting.
+ # At boot nothing is in huge folios yet, and the persistent journal opens only after this unit: no rotation
+ # while the service manager is still bringing the machine up.
+ booting=$(systemctl is-system-running 2>/dev/null || true)
+ case "$booting" in
+ initializing | starting) echo "the machine is still starting: nothing to rotate yet" ;;
+ *)
+ if [ "$want" != always ] && command -v journalctl >/dev/null 2>&1; then
+ journalctl --rotate || echo "the journal could not be rotated: its active files keep their huge pages until they are archived" >&2
+ fi
+ ;;
+ esac
+ ;;
+restore)
+ found=$(cat "$state/found" 2>/dev/null || true)
+ case "$found" in
+ always | madvise | never)
+ printf '%s\n' "$found" >"$sys/enabled"
+ echo "transparent huge pages: back to $found, as the machine had them"
+ ;;
+ *) echo "transparent huge pages: nothing recorded as found, left at $(mode)" ;;
+ esac
+ # Unassigned or shut down: nothing of the module is kept; the next start records what it finds again.
+ rm -f "$state/found"
+ ;;
+*)
+ echo "usage: huge-pages apply|restore [ []]" >&2
+ exit 2
+ ;;
+esac
diff --git a/go/testdata/beside/mesh-catalog/modules/kernel/files/mesh-kernel-huge-pages.service b/go/testdata/beside/mesh-catalog/modules/kernel/files/mesh-kernel-huge-pages.service
new file mode 100644
index 0000000..77a9059
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/kernel/files/mesh-kernel-huge-pages.service
@@ -0,0 +1,25 @@
+[Unit]
+Description=Transparent huge pages as the mesh sets them on this machine
+# Module kernel, novox/hq issue 746. Starting this unit sets the mode from the setting
+# transparent-huge-pages; reloading it (the setting changed) sets the new mode; stopping it gives back the
+# mode the machine had before. It runs early in boot, before the services that fill the page cache.
+DefaultDependencies=no
+After=local-fs.target
+Before=sysinit.target shutdown.target
+Conflicts=shutdown.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=${dir:state}/huge-pages apply
+ExecReload=${dir:state}/huge-pages apply
+ExecStop=${dir:state}/huge-pages restore
+# It writes one file under /sys and its own state, and asks the journal daemon to rotate over its socket.
+ProtectSystem=strict
+ReadWritePaths=${dir:state}
+ProtectHome=yes
+PrivateNetwork=yes
+NoNewPrivileges=yes
+
+[Install]
+WantedBy=sysinit.target
diff --git a/go/testdata/beside/mesh-catalog/modules/keycloak/files/admin.env b/go/testdata/beside/mesh-catalog/modules/keycloak/files/admin.env
new file mode 100644
index 0000000..3e52ae5
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/keycloak/files/admin.env
@@ -0,0 +1,2 @@
+KEYCLOAK_ADMIN=admin
+KEYCLOAK_ADMIN_PASSWORD=${secret:admin}
diff --git a/go/testdata/beside/mesh-catalog/modules/keycloak/files/config.json b/go/testdata/beside/mesh-catalog/modules/keycloak/files/config.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/keycloak/files/config.json
@@ -0,0 +1 @@
+{}
diff --git a/go/testdata/beside/mesh-catalog/modules/keycloak/files/consumers.json b/go/testdata/beside/mesh-catalog/modules/keycloak/files/consumers.json
new file mode 100644
index 0000000..b0af957
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/keycloak/files/consumers.json
@@ -0,0 +1 @@
+${consumers:oidc-client}
diff --git a/go/testdata/beside/mesh-catalog/modules/keycloak/files/database.env b/go/testdata/beside/mesh-catalog/modules/keycloak/files/database.env
new file mode 100644
index 0000000..454ee99
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/keycloak/files/database.env
@@ -0,0 +1,3 @@
+KC_DB_URL=jdbc:postgresql://${bound:postgres-database:at}:${bound:postgres-database:port}/${bound:postgres-database:as}
+KC_DB_USERNAME=${bound:postgres-database:as}
+KC_DB_PASSWORD=${secret:postgres-database}
diff --git a/go/testdata/beside/mesh-catalog/modules/keycloak/files/hostname.env b/go/testdata/beside/mesh-catalog/modules/keycloak/files/hostname.env
new file mode 100644
index 0000000..92f061a
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/keycloak/files/hostname.env
@@ -0,0 +1 @@
+KC_HOSTNAME=https://${bound:route:name}
diff --git a/go/testdata/beside/mesh-catalog/modules/lab/files/lab.env b/go/testdata/beside/mesh-catalog/modules/lab/files/lab.env
new file mode 100644
index 0000000..35aa1a2
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/lab/files/lab.env
@@ -0,0 +1,3 @@
+MESH_LAB_FORGE=${config:forge}
+MESH_LAB_FORGE_BINDING=${dir:state}/package-registry.json
+MESH_LAB_FORGE_SECRET=${dir:state}/package-registry.secret
diff --git a/go/testdata/beside/mesh-catalog/modules/lemurs/files/config.toml b/go/testdata/beside/mesh-catalog/modules/lemurs/files/config.toml
new file mode 100644
index 0000000..6e5667d
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/lemurs/files/config.toml
@@ -0,0 +1,369 @@
+# The login manager's configuration, written by the mesh (module lemurs, novox/hq ADR 0208). Replaced
+# at every push; change the module instead. The structure is lemurs 0.4's own (the shipped file, with
+# every option, as lemurs requires); what the mesh changes from it is marked "mesh:".
+#
+# The sessions offered are the executable files other modules place in the two scripts directories
+# below (/etc/lemurs/wms for X, /etc/lemurs/wayland for Wayland), one per session module. A file there
+# is named as the session is offered. Desktop entries that packages install (/usr/share/xsessions) are
+# not offered: they start the window manager bare, skipping the session's start (~/.xinitrc), which is
+# where the account's environment, the X resources and every module's session lines are.
+#
+# Lemurs configuration file.
+# Contains all the customization options of lemurs.
+#
+# Note: that as of now you need to have all options in the selected
+# configuration file. Otherwise Lemurs will not work.
+#
+# Colors:
+# ---------
+# There is a list of predefined colors. These include:
+# - black
+# - white
+# - (dark) gray
+# - (light) red
+# - (light) blue
+# - (light) green
+# - (light) magenta
+# - (light) cyan
+# - (light) yellow
+# - orange
+#
+# You can also utilize custom colors with hex color codes.
+# "#87CEEB" will create a Sky Blue color.
+#
+# Note: If the color wasn't recognized, it will default to white.
+# ---------
+#
+# Modifiers:
+# ---------
+# There is a number of modifiers you can use. These can be combined by
+# delimiting them with a comma (e.g. "bold,italic"). The modifiers are:
+# - bold
+# - dim
+# - italic
+# - underlined
+# - reverse
+# - crossed out
+# - hidden
+# ---------
+#
+
+# The tty which contains lemurs. This has to be mirrored in the lemurs.service
+tty = 2
+
+# Where to log the main lemurs control flow.
+main_log_path = "/var/log/lemurs.log"
+
+# Where to log to for the client. The Client is the Desktop Environment or
+# Window Manager for Xorg, the Compositor for Wayland and the Shell for TTY.
+client_log_path = "/var/log/lemurs.client.log"
+
+# At which point to point the cache. If you want to disable the cache globally
+# you can use `/dev/null`.
+cache_path = "/var/cache/lemurs"
+
+# Disable all logging. This is overwritten by the `--no-log` flag.
+do_log = true
+
+# The PAM service that should be used to login
+pam_service = "lemurs"
+
+# Path to system shell that gets used to execute linux commands. In almost all
+# cases, this should refer to a bash shell.
+system_shell = "/bin/sh"
+
+# Initial state of the `PATH` environment variable.
+initial_path = "/usr/local/sbin:/usr/local/bin:/usr/bin"
+
+# The type flag that will be appended to the shell that calls the session
+# environment. This may depend on your shell. Options:
+# - 'none'. Disables calling a login shell
+# - 'short'. Produces the `-l` flag. Supported by most shells.
+# - 'long'. This produces the `--login` flag and is suited for bash and zsh.
+shell_login_flag = "short"
+
+# Focus behaviour of fields when Lemurs is initially started
+#
+# Possible values:
+# - default: Initially focus on first non-cached value
+# - no-focus: No initial focus
+# - environment: Initially focus on the environment selector
+# - username: Initially focus on the username field
+# - password: Initially focus on the password field
+focus_behaviour = "default"
+
+# General settings for background style
+[background]
+
+# Control whether to render background widget or not
+show_background = false
+
+[background.style]
+# Allow to set the default background color for the login shell
+color = "black"
+# Settings for the background block's borders
+show_border = true
+border_color = "white"
+
+[power_controls]
+# The margin between hints
+hint_margin = 2
+
+# There are no additional entries by default
+entries = []
+
+# Example
+# Reboot to another os option
+#[[power_controls.entries]]
+## The text in the top-left to display how to reboot.
+#hint = "Reboot to OS"
+#
+## The color and modifiers of the hint in the top-left corner
+#hint_color = "dark gray"
+#hint_modifiers = ""
+#
+## The key used to reboot. Possibilities are F1 to F12.
+#key = "F3"
+## The command that is executed when the key is pressed
+#cmd = "efibootmgr -n0 && systemctl reboot -l"
+
+
+# If you want to remove the base_entries
+# base_entries = []
+
+# Shutdown option
+[[power_controls.base_entries]]
+# The text in the top-left to display how to shutdown.
+hint = "Shutdown"
+
+# The color and modifiers of the hint in the top-left corner
+hint_color = "dark gray"
+hint_modifiers = ""
+
+# The key used to shutdown. Possibilities are F1 to F12.
+key = "F1"
+# The command that is executed when the key is pressed
+cmd = "systemctl poweroff -l"
+
+# Reboot option
+[[power_controls.base_entries]]
+# The text in the top-left to display how to reboot.
+hint = "Reboot"
+
+# The color and modifiers of the hint in the top-left corner
+hint_color = "dark gray"
+hint_modifiers = ""
+
+# The key used to reboot. Possibilities are F1 to F12.
+key = "F2"
+# The command that is executed when the key is pressed
+cmd = "systemctl reboot -l"
+
+# Setting for the selector of the desktop environment you are using.
+[environment_switcher]
+# Terms:
+# ---------
+# Movers: indicators which show which direction one can move whilst selecting
+# the desktop environment
+# Selected: The currently selected desktop environment.
+# Neighbours: The adjacent desktop environment to the one current selected
+#
+# Visualisation:
+#
+# < i3 bspwm awesome >
+#
+# ^ ^ ^ ^ ^
+# | | | | |
+# mover | selected | mover
+# | |
+# neighbour neighbour
+# ---------
+#
+
+# Control the visibility of the switcher
+# Options:
+# - "visible" - Always show the switcher [default]
+# - "hidden" - Always hide the switcher
+# - [key] - F1-F12 to be able to toggle the visibility
+# mesh: hidden, as both workstations had it, but F3 shows it, so a second session can be chosen.
+switcher_visibility = "F3"
+
+# The text in the top-left to display how to toggle the switcher. The text
+# '%key%' will be replaced with the switcher_visibility key. This is not shown
+# if switcher_visibility is set to "visible" or "hidden".
+toggle_hint = "Switcher %key%"
+
+# The color and modifiers of the hint in the top-left corner
+toggle_hint_color = "dark gray"
+toggle_hint_modifiers = ""
+
+
+# Show an option for the TTY shell when logging in as one of the environments.
+# NOTE: it is always shown when no viable options are found.
+include_tty_shell = false
+
+# Remember the selected environment after logging in for the next time
+remember = true
+
+# Enables showing the movers
+show_movers = true
+
+# Mover's color and modifiers whilst the selector is unfocused
+mover_color = "dark gray"
+mover_modifiers = ""
+
+# Mover's color and modifiers whilst the selector is focused
+mover_color_focused = "orange"
+mover_modifiers_focused = "bold"
+
+# The characters used to display the movers. Suggestions are:
+# - "<" ">"
+# - "<-" "->"
+# - "<<" ">>"
+# - "[" "]"
+left_mover = "<"
+right_mover = ">"
+
+# The margin between the movers and the neighbours or selected (depending on
+# `show_neighbours`)
+mover_margin = 1
+
+# Enables showing the neighbours
+show_neighbours = true
+
+# Neighbours' color and modifiers whilst the selector is unfocused
+neighbour_color = "dark gray"
+neighbour_modifiers = ""
+
+# Neighbours' color and modifiers whilst the selector is focused
+neighbour_color_focused = "gray"
+neighbour_modifiers_focused = ""
+
+# Margin between neighbours and selected
+neighbour_margin = 1
+
+# Selected's color and modifiers whilst the selector is unfocused
+selected_color = "gray"
+selected_modifiers = "underlined"
+
+# Selected's color and modifiers whilst the selector is focused
+selected_color_focused = "white"
+selected_modifiers_focused = "bold"
+
+# The length of the name of the desktop environment which is displayed.
+max_display_length = 8
+
+# The text used when no desktop environments are available
+no_envs_text = "No environments..."
+
+# The color and modifiers of the 'no desktop environments available text'
+# whilst the selector is unfocused
+no_envs_color = "white"
+no_envs_modifiers = ""
+
+# The color and modifiers of the 'no desktop environments available text'
+# whilst the selector is focused
+no_envs_color_focused = "red"
+no_envs_modifiers_focused = ""
+
+[username_field]
+
+# Remember the username for the next time after a successful login attempt.
+remember = true
+
+[username_field.style]
+# Enables showing a title
+show_title = true
+# The text used within the title
+title = "Login"
+
+# The title's color and modifiers whilst the username field is unfocused
+title_color = "white"
+content_color = "white"
+
+# The title's color and modifiers whilst the username field is focused
+title_color_focused = "orange"
+content_color_focused = "orange"
+
+# Enables showing the borders
+show_border = true
+# The borders' color and modifiers whilst the username field is unfocused
+border_color = "white"
+# The borders' color and modifiers whilst the username field is focused
+border_color_focused = "orange"
+
+# Constrain the width of the username field
+use_max_width = true
+# The constraint of the username field's width
+max_width = 48
+
+[password_field]
+
+# The character used for replacement when typing a password. Leave empty for no
+# feedback.
+# Note: Only one character is accepted.
+content_replacement_character = "*"
+
+[password_field.style]
+# Enables showing a title
+show_title = true
+# The text used within the title
+title = "Password"
+
+# The title's color and modifiers whilst the password field is unfocused
+title_color = "white"
+content_color = "white"
+
+# The title's color and modifiers whilst the password field is focused
+title_color_focused = "orange"
+content_color_focused = "orange"
+
+# Enables showing the borders
+show_border = true
+# The borders' color and modifiers whilst the password field is unfocused
+border_color = "white"
+# The borders' color and modifiers whilst the password field is focused
+border_color_focused = "orange"
+
+# Constrain the width of the password field
+use_max_width = true
+# The constraint of the password field's width
+max_width = 48
+
+[x11]
+# Where to log to for the XServer.
+xserver_log_path = "/var/log/lemurs.xorg.log"
+
+# The value of the `DISPLAY` environment variable for X11 sessions
+x11_display = ":1"
+
+# How many seconds to give the X server to start. To make it infinitely, put it
+# to 0.
+xserver_timeout_secs = 60
+
+# Where to find the X11 server binary
+# mesh: the server through the wrapper below, which adds -noreset.
+xserver_path = "/etc/lemurs/xserver"
+
+# Where to find the X11 xauth binary
+xauth_path = "/usr/bin/xauth"
+
+# Path to the directory where the startup scripts for the X11 sessions are found
+scripts_path = "/etc/lemurs/wms"
+
+# Path to the xsetup script that is needed for the environment setup of the
+# window manager.
+xsetup_path = "/etc/lemurs/xsetup.sh"
+
+# The directory to use for desktop entries X11 sessions.
+# mesh: an empty directory of the module's own, so no package's desktop entry is offered.
+xsessions_path = "/etc/lemurs/xsessions"
+
+[wayland]
+# Path to the directory where the startup scripts for the Wayland sessions are
+# found
+scripts_path = "/etc/lemurs/wayland"
+
+# The directory to use for desktop entries wayland sessions.
+# mesh: likewise for Wayland.
+wayland_sessions_path = "/etc/lemurs/wayland-sessions"
diff --git a/go/testdata/beside/mesh-catalog/modules/lemurs/files/xserver b/go/testdata/beside/mesh-catalog/modules/lemurs/files/xserver
new file mode 100644
index 0000000..5908f22
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/lemurs/files/xserver
@@ -0,0 +1,8 @@
+#!/bin/sh
+# The X server as lemurs starts it, written by the mesh (module lemurs). Replaced at every push.
+#
+# -noreset: without it, X resets whenever its last client disconnects, and a reset empties the
+# resource database. The session's start merges the X resources with xrdb before any long-lived
+# client is connected, so the server reset twice in the four seconds after a login and threw the
+# resources away: xterm fell back to the bitmap "fixed" font, without the mesh's face and colours.
+exec /usr/bin/X "$@" -noreset
diff --git a/go/testdata/beside/mesh-catalog/modules/letta/files/config.json b/go/testdata/beside/mesh-catalog/modules/letta/files/config.json
new file mode 100644
index 0000000..8ed994d
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/letta/files/config.json
@@ -0,0 +1,3 @@
+{
+ "password": "${secret:server-password}"
+}
diff --git a/go/testdata/beside/mesh-catalog/modules/letta/files/pgpass b/go/testdata/beside/mesh-catalog/modules/letta/files/pgpass
new file mode 100644
index 0000000..cd41b66
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/letta/files/pgpass
@@ -0,0 +1 @@
+*:*:*:${bound:postgres-database:as}:${secret:postgres-database}
diff --git a/go/testdata/beside/mesh-catalog/modules/letta/files/server.env b/go/testdata/beside/mesh-catalog/modules/letta/files/server.env
new file mode 100644
index 0000000..fe2acb9
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/letta/files/server.env
@@ -0,0 +1,6 @@
+LETTA_PG_URI=postgresql://${bound:postgres-database:as}@${bound:postgres-database:at}:${bound:postgres-database:port}/${bound:postgres-database:as}
+PGPASSFILE=/run/secrets/pgpass
+LETTA_SERVER_PASSWORD=${secret:server-password}
+OPENAI_API_KEY=${secret:openai-api-key}
+SECURE=true
+TZ=Europe/Brussels
diff --git a/go/testdata/beside/mesh-catalog/modules/letta/files/start.sh b/go/testdata/beside/mesh-catalog/modules/letta/files/start.sh
new file mode 100644
index 0000000..9955592
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/letta/files/start.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+# Generated by the mesh. Do not edit: module letta writes this file and replaces it at every push.
+#
+# letta 0.6.8 prints secrets it is given to its log (novox/hq issue 268):
+# letta/server/rest_api/app.py prints its server password when it starts in secure mode;
+# startup.sh, alembic/env.py and letta/server/server.py print LETTA_PG_URI whole.
+# The URI carries no password (libpq reads it from PGPASSFILE), and the one print of the server
+# password is rewritten before the server starts. Either one failing refuses the start: a letta
+# that does not start says why here, and one that leaks says nothing.
+set -e
+app=/app/letta/server/rest_api/app.py
+sed -i 's/Using secure mode with password: {random_password}/Using secure mode (the password is not printed)/' "$app"
+if grep -q 'print(.*random_password' "$app"; then
+ echo "letta: $app still prints the server password; not starting (novox/hq issue 268)" >&2
+ exit 1
+fi
+case "$LETTA_PG_URI" in
+ *://*:*@*)
+ echo "letta: LETTA_PG_URI carries a password, and letta prints that URI; not starting (novox/hq issue 268)" >&2
+ exit 1
+ ;;
+esac
+exec ./letta/server/startup.sh
diff --git a/go/testdata/beside/mesh-catalog/modules/local-model-consumer/files/openai.env b/go/testdata/beside/mesh-catalog/modules/local-model-consumer/files/openai.env
new file mode 100644
index 0000000..9aca396
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/local-model-consumer/files/openai.env
@@ -0,0 +1,3 @@
+OPENAI_BASE_URL=http://${bound:model-access:at}:${bound:model-access:port}/v1
+OPENAI_MODEL=${bound:model-access:model}
+OPENAI_API_KEY=local
diff --git a/go/testdata/beside/mesh-catalog/modules/localization/files/locale.conf b/go/testdata/beside/mesh-catalog/modules/localization/files/locale.conf
new file mode 100644
index 0000000..fe405b5
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/localization/files/locale.conf
@@ -0,0 +1,3 @@
+# The mesh's (module localization, novox/hq to-be 42): the system locale. Written whole at every
+# push; an edit here is overwritten. Read at the next login.
+LANG=en_US.UTF-8
diff --git a/go/testdata/beside/mesh-catalog/modules/localization/files/vconsole.conf b/go/testdata/beside/mesh-catalog/modules/localization/files/vconsole.conf
new file mode 100644
index 0000000..88d96f6
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/localization/files/vconsole.conf
@@ -0,0 +1,3 @@
+# The mesh's (module localization, novox/hq to-be 42): the console keymap. Written whole at every
+# push; an edit here is overwritten. Read at the next boot.
+KEYMAP=us
diff --git a/go/testdata/beside/mesh-catalog/modules/logrotate/files/logrotate.conf b/go/testdata/beside/mesh-catalog/modules/logrotate/files/logrotate.conf
new file mode 100644
index 0000000..d646137
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/logrotate/files/logrotate.conf
@@ -0,0 +1,32 @@
+# The mesh's (module logrotate, novox/hq to-be 42): the base configuration every rotation inherits.
+# Written whole at every push; an edit here is overwritten. Each package's own rules are in
+# /etc/logrotate.d and stay the packages'.
+
+# Weekly, four weeks kept, a new empty log created after each rotation.
+weekly
+rotate 4
+create
+
+# Rotated logs are compressed, one rotation late, so a program still writing to the file it had open
+# loses nothing to the compression.
+compress
+delaycompress
+
+# A package's replaced configuration is never read as a rule.
+tabooext + .pacorig .pacnew .pacsave
+
+include /etc/logrotate.d
+
+/var/log/wtmp {
+ monthly
+ create 0664 root utmp
+ minsize 1M
+ rotate 1
+}
+
+/var/log/btmp {
+ missingok
+ monthly
+ create 0600 root utmp
+ rotate 1
+}
diff --git a/go/testdata/beside/mesh-catalog/modules/mailu/files/admin.env b/go/testdata/beside/mesh-catalog/modules/mailu/files/admin.env
new file mode 100644
index 0000000..f066bb5
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/mailu/files/admin.env
@@ -0,0 +1,2 @@
+INITIAL_ADMIN_PW=${secret:admin}
+API_TOKEN=${secret:api-token}
diff --git a/go/testdata/beside/mesh-catalog/modules/mailu/files/config.json b/go/testdata/beside/mesh-catalog/modules/mailu/files/config.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/mailu/files/config.json
@@ -0,0 +1 @@
+{}
diff --git a/go/testdata/beside/mesh-catalog/modules/mailu/files/database.env b/go/testdata/beside/mesh-catalog/modules/mailu/files/database.env
new file mode 100644
index 0000000..ba70d3d
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/mailu/files/database.env
@@ -0,0 +1,5 @@
+DB_FLAVOR=postgresql
+DB_HOST=${bound:postgres-database:at}:${bound:postgres-database:port}
+DB_USER=${bound:postgres-database:as}
+DB_NAME=${bound:postgres-database:as}
+DB_PW=${secret:postgres-database}
diff --git a/go/testdata/beside/mesh-catalog/modules/mailu/files/mailu.env b/go/testdata/beside/mesh-catalog/modules/mailu/files/mailu.env
new file mode 100644
index 0000000..2c977d3
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/mailu/files/mailu.env
@@ -0,0 +1,78 @@
+ADMIN_ADDRESS=mailu-admin
+ANTISPAM_ADDRESS=mailu-antispam
+ANTIVIRUS_ADDRESS=mailu-antivirus
+IMAP_ADDRESS=mailu-imap
+SMTP_ADDRESS=mailu-smtp
+FRONT_ADDRESS=mailu-front
+WEBMAIL_ADDRESS=mailu-webmail
+WEBDAV_ADDRESS=mailu-webdav
+REDIS_ADDRESS=mailu-redis
+PORTS=25,80,443,465,993,995,4190,110,143,587
+DOMAIN=${config:domain}
+HOSTNAMES=${bound:route:name-web}
+POSTMASTER=admin
+SITENAME=${config:sitename}
+WEBSITE=${config:website}
+TLS_FLAVOR=letsencrypt
+SUBNET=192.168.203.0/24
+COMPOSE_PROJECT_NAME=mailu
+ANTIVIRUS=clamav
+WEBMAIL=roundcube
+WEBDAV=radicale
+FETCHMAIL_ENABLED=True
+FETCHMAIL_DELAY=600
+ADMIN=true
+WEB_ADMIN=/admin
+WEB_WEBMAIL=/webmail
+WEBROOT_REDIRECT=/webmail
+API=true
+WEB_API=/api
+AUTH_RATELIMIT_IP=6000/hour
+AUTH_RATELIMIT_USER=1000/day
+CREDENTIAL_ROUNDS=12
+PASSWORD_SCHEME=PBKDF2
+DISABLE_STATISTICS=True
+MESSAGE_SIZE_LIMIT=50000000
+MESSAGE_RATELIMIT=200/day
+RECIPIENT_DELIMITER=+
+POSTFIX_MYNETWORKS=127.0.0.0/8 [::1]/128
+RELAYNETS=
+RELAYHOST=
+REJECT_UNLISTED_RECIPIENT=
+DB_FLAVOR=postgresql
+INITIAL_ADMIN_ACCOUNT=admin
+INITIAL_ADMIN_DOMAIN=${config:domain}
+INITIAL_ADMIN_MODE=ifmissing
+SMTP_PORT=25
+SMTPS_PORT=465
+SUBMISSION_PORT=587
+POP3_PORT=110
+POP3S_PORT=995
+IMAP_PORT=143
+IMAPS_PORT=993
+HTTP_PORT=7080
+HTTPS_PORT=7443
+AUTOMX_PORT=4243
+AMX_SMTP_ADDRESS=${bound:route:name-web}
+AMX_SMTP_PORT=587
+AMX_IMAP_ADDRESS=${bound:route:name-web}
+AMX_IMAP_PORT=143
+AMX_MAIL_DOMAINS=${config:domain}
+DMARC_RUA=admin
+DMARC_RUF=admin
+LETSENCRYPT_SHORTCHAIN=True
+TZ=Etc/UTC
+LOG_LEVEL=INFO
+WELCOME=false
+REAL_IP_HEADER=X-Real-IP
+REAL_IP_FROM=${config:proxy-address}
+COMPRESSION=
+COMPRESS_LEVEL=
+COMPRESSION_LEVEL=
+BIND_ADDRESS4=127.0.0.1
+BIND_ADDRESS6=::1
+MAILU_VERSION=1.9
+DOCKER_ORG=mailu
+DOCKER_PREFIX=
+WELCOME_SUBJECT=Welcome to your new email account
+WELCOME_BODY=Welcome to your new email account, if you can read this, then it is configured properly!
diff --git a/go/testdata/beside/mesh-catalog/modules/mailu/files/secret.env b/go/testdata/beside/mesh-catalog/modules/mailu/files/secret.env
new file mode 100644
index 0000000..c6f6b66
--- /dev/null
+++ b/go/testdata/beside/mesh-catalog/modules/mailu/files/secret.env
@@ -0,0 +1 @@
+SECRET_KEY=${secret:secret-key}