Capture the catalogue modules' files directories (adwaita to mailu) at mesh-catalog bb0fe93b for the moved catalogue tests (issue 1379)
Part 3 of 4 of go/testdata/beside.
This commit is contained in:
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
Adwaita:dark
|
||||
@@ -0,0 +1 @@
|
||||
/usr/share/themes/Adwaita-dark/gtk-2.0/gtkrc
|
||||
+1
@@ -0,0 +1 @@
|
||||
qt6ct
|
||||
@@ -0,0 +1 @@
|
||||
6
|
||||
@@ -0,0 +1 @@
|
||||
Fusion
|
||||
@@ -0,0 +1 @@
|
||||
24
|
||||
@@ -0,0 +1 @@
|
||||
Adwaita
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
! adwaita: the cursor, for X programs that take it from the resources.
|
||||
Xcursor.theme: Adwaita
|
||||
Xcursor.size: 24
|
||||
+4
@@ -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}
|
||||
+33
@@ -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}%"
|
||||
+41
@@ -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
|
||||
+20
@@ -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
|
||||
+27
@@ -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"
|
||||
+12
@@ -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
|
||||
+46
@@ -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 "$@"
|
||||
+29
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"password": "${secret:admin}",
|
||||
"host": "${bound:route:name}"
|
||||
}
|
||||
@@ -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}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"accounts": {}
|
||||
}
|
||||
@@ -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 "$@"
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"identities": {},
|
||||
"calendars": {},
|
||||
"address-books": {},
|
||||
"filters": {}
|
||||
}
|
||||
+25
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"prefs": {}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"tags": {}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
${machine:agent-account}
|
||||
@@ -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 "$@"
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"node": "${machine:name}",
|
||||
"console": "http://127.0.0.1:${bound:mcp-endpoint:port}/mcp"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"role": "",
|
||||
"mcp_servers": {},
|
||||
"managed_settings": {},
|
||||
"instructions": {},
|
||||
"output_styles": {},
|
||||
"skills": {},
|
||||
"commands": {},
|
||||
"agents": {}
|
||||
}
|
||||
@@ -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=$(</etc/claude-code/agent-account)
|
||||
if [[ -n $agent && $agent != $USERNAME ]]; then
|
||||
alias claude=claude-agent
|
||||
fi
|
||||
}
|
||||
fi
|
||||
+1
@@ -0,0 +1 @@
|
||||
postgresql://${bound:postgres-database:as}:${secret:postgres-database}@${bound:postgres-database:at}:${bound:postgres-database:port}/${bound:postgres-database:as}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"cadence_minutes": 240,
|
||||
"floor_minutes": 60,
|
||||
"failures_to_notify": 3,
|
||||
"cooldown_hours": 24,
|
||||
"refresh_warn_days": 3
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/bin/sh
|
||||
# clipmenud as the operator's user unit starts it, written by the mesh (module clipmenu). Replaced at every push.
|
||||
#
|
||||
# The account's service manager starts before the X server does, and has a DISPLAY only once the session's
|
||||
# xinitrc has handed it over (module xorg). So this waits, quietly, until the display is known, and exits
|
||||
# only when the session ends with it: a quick exit would run the unit into its start limit. DISPLAY, when
|
||||
# the manager has it, wins. Without it, exactly one X socket names the display; none or several (a
|
||||
# second server beside the session) wait for the session's own to arrive.
|
||||
# A running process does not see what the session hands the manager later, so each pass asks the manager.
|
||||
until [ -n "$DISPLAY" ]; do
|
||||
for pair in $(systemctl --user show-environment 2>/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
|
||||
@@ -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 "$@"
|
||||
@@ -0,0 +1 @@
|
||||
15
|
||||
@@ -0,0 +1 @@
|
||||
500
|
||||
@@ -0,0 +1 @@
|
||||
clipboard
|
||||
@@ -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
|
||||
+14
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"desktop-machines": []
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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}"}}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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}
|
||||
@@ -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 = "<b>%s</b>\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
|
||||
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
/var/log/fail2ban.log {
|
||||
missingok
|
||||
notifempty
|
||||
postrotate
|
||||
/usr/bin/fail2ban-client flushlogs >/dev/null || true
|
||||
endscript
|
||||
}
|
||||
+40
@@ -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 = { <iptables> -C f2b-<name> -j <returntype> >/dev/null 2>&1; } || { <iptables> -N f2b-<name> || true; <iptables> -A f2b-<name> -j <returntype>; }
|
||||
{ <iptables> -C INPUT -p <protocol> -j f2b-<name> >/dev/null 2>&1; } || { <iptables> -I INPUT -p <protocol> -j f2b-<name>; }
|
||||
{ <iptables> -C DOCKER-USER -p <protocol> -j f2b-<name> >/dev/null 2>&1; } || { <iptables> -I DOCKER-USER -p <protocol> -j f2b-<name>; }
|
||||
|
||||
actionstop = <iptables> -D INPUT -p <protocol> -j f2b-<name> 2>/dev/null || true
|
||||
<iptables> -D DOCKER-USER -p <protocol> -j f2b-<name> 2>/dev/null || true
|
||||
<iptables> -F f2b-<name>
|
||||
<iptables> -X f2b-<name>
|
||||
|
||||
actioncheck = <iptables> -n -L f2b-<name> >/dev/null
|
||||
|
||||
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
|
||||
|
||||
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
|
||||
|
||||
[Init]
|
||||
|
||||
chain = INPUT
|
||||
name = default
|
||||
protocol = tcp
|
||||
blocktype = REJECT --reject-with icmp-port-unreachable
|
||||
returntype = RETURN
|
||||
lockingopt = -w
|
||||
iptables = iptables <lockingopt>
|
||||
|
||||
[Init?family=inet6]
|
||||
|
||||
blocktype = REJECT --reject-with icmp6-port-unreachable
|
||||
iptables = ip6tables <lockingopt>
|
||||
@@ -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
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"own-addresses": []
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
[sshd]
|
||||
enabled = true
|
||||
port = ssh
|
||||
logpath = %(sshd_log)s
|
||||
backend = %(sshd_backend)s
|
||||
maxretry = 3
|
||||
findtime = 1d
|
||||
bantime = 1d
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
# The wallpaper (module feh, novox/hq ADR 0208): the declared one, set once per session.
|
||||
"$HOME/.fehbg"
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
|
||||
<!--
|
||||
The mesh's file (module fonts, novox/hq research 026/04): which face each generic family means for
|
||||
this account, so every program that asks for monospace, sans-serif, serif or emoji gets the same
|
||||
face. Replaced at every push. Other files in this directory are yours.
|
||||
-->
|
||||
<fontconfig>
|
||||
<description>The mesh: the faces monospace, sans-serif, serif and emoji mean</description>
|
||||
|
||||
<!--
|
||||
Families the desktop's files named before this module, mapped to monospace for as long as they
|
||||
are not installed: a configuration still naming one gets the chosen monospace face rather than
|
||||
fontconfig's last resort, sans-serif. Where the family is still installed, it is used as before.
|
||||
Placed first, so the monospace they lead to is resolved by the rule below.
|
||||
-->
|
||||
<alias><family>Hack Nerd Font</family><accept><family>monospace</family></accept></alias>
|
||||
<alias><family>MesloLGS NF</family><accept><family>monospace</family></accept></alias>
|
||||
<alias><family>Iosevka Nerd Font</family><accept><family>monospace</family></accept></alias>
|
||||
<alias><family>JetBrains Mono Nerd Font</family><accept><family>JetBrainsMono Nerd Font</family></accept></alias>
|
||||
|
||||
<!--
|
||||
The decided faces, bound the same as the request: a program asking for monospace asks strongly,
|
||||
and a face added weakly loses to the distribution's own choice (measured with fontconfig 2.18).
|
||||
-->
|
||||
<alias binding="same"><family>monospace</family><prefer><family>JetBrainsMono Nerd Font</family></prefer></alias>
|
||||
<alias binding="same"><family>sans-serif</family><prefer><family>Inter</family><family>Noto Sans</family></prefer></alias>
|
||||
<alias binding="same"><family>system-ui</family><prefer><family>Inter</family></prefer></alias>
|
||||
<alias binding="same"><family>serif</family><prefer><family>Noto Serif</family></prefer></alias>
|
||||
<alias binding="same"><family>emoji</family><prefer><family>Noto Color Emoji</family></prefer></alias>
|
||||
|
||||
<!--
|
||||
Fallbacks for any face: an icon a face lacks comes from the Nerd Fonts symbols, an emoji from
|
||||
Noto Color Emoji. Appended last, so they never displace a face that has the character.
|
||||
-->
|
||||
<match target="pattern">
|
||||
<edit name="family" mode="append_last"><string>Symbols Nerd Font</string></edit>
|
||||
<edit name="family" mode="append_last"><string>Noto Color Emoji</string></edit>
|
||||
</match>
|
||||
</fontconfig>
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1 @@
|
||||
${consumers:forge-account}
|
||||
@@ -0,0 +1 @@
|
||||
https://${bound:route:name-web}
|
||||
@@ -0,0 +1 @@
|
||||
{"repositories": ${config:repositories}}
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
${secret:admin}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"user": "admin",
|
||||
"password": "${secret:admin}"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
${secret:influxdb-api}
|
||||
@@ -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}
|
||||
@@ -0,0 +1 @@
|
||||
${secret:oidc-client}
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
hello from hello-web, routed by the mesh
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -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}
|
||||
@@ -0,0 +1 @@
|
||||
${config:hostname}
|
||||
@@ -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}
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
i3
|
||||
@@ -0,0 +1 @@
|
||||
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"
|
||||
@@ -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
|
||||
+64
@@ -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
|
||||
+63
@@ -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
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
# i3status-value <state> <key> (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
|
||||
@@ -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')"
|
||||
+34
@@ -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
|
||||
}
|
||||
}
|
||||
+135
@@ -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)
|
||||
@@ -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}
|
||||
@@ -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" "$$" &
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user