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}