From e1fc50228605cb7d899b7778a8106b5beed0da7f Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Sun, 30 Aug 2026 16:06:16 -0400 Subject: [PATCH 1/5] Nudge the bar over IPC when toggling visibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shell notices the bar-off flag through a FileView watch on the toggles directory, and that watch can permanently stop delivering events after flag changes land in quick succession — the bar then stays parked off screen until the shell restarts. Have omarchy-toggle-bar nudge the bar's probe over IPC after flipping the flag, so the toggle no longer depends on the watch staying alive. The watch remains for other writers of the flag. --- bin/omarchy-toggle-bar | 6 ++++++ shell/plugins/bar/Bar.qml | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/bin/omarchy-toggle-bar b/bin/omarchy-toggle-bar index f76fe049..be18a647 100755 --- a/bin/omarchy-toggle-bar +++ b/bin/omarchy-toggle-bar @@ -5,3 +5,9 @@ # omarchy:examples=omarchy toggle bar | omarchy toggle bar off | omarchy toggle bar on omarchy-toggle bar-off "${1:-toggle}" + +# The shell's watch on the toggles directory can miss flag changes that land in +# quick succession, stranding the bar off screen until the shell restarts. +# Nudge the bar to re-read the flag; quiet best-effort so the toggle still +# works when the shell is not up. +omarchy-shell -q omarchy.bar syncHidden diff --git a/shell/plugins/bar/Bar.qml b/shell/plugins/bar/Bar.qml index 9e736b3f..def615af 100644 --- a/shell/plugins/bar/Bar.qml +++ b/shell/plugins/bar/Bar.qml @@ -948,6 +948,21 @@ Item { onFileChanged: barHiddenProbe.running = true } + // The directory watch can permanently stop delivering events after flag + // changes land in quick succession, stranding the bar off screen until the + // shell restarts. `omarchy-toggle-bar` nudges this after flipping the flag + // so the probe re-reads it even when the watch has gone quiet. + IpcHandler { + target: "omarchy.bar" + + // Start rather than restart: a probe already in flight was launched by the + // directory watch after the flag flipped, so its answer is current, and + // killing it here can swallow the result entirely. + function syncHidden(): void { + barHiddenProbe.running = true + } + } + Variants { model: Quickshell.screens From d6130394fa245d55cd2d75dab4c9744b58728540 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Sun, 30 Aug 2026 16:06:29 -0400 Subject: [PATCH 2/5] Default the suite's OMARCHY_PATH to the running session's tree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run over SSH with no OMARCHY_PATH, the acceptance runner defaulted it to its own root — wrong in both sync modes omarchy-iso-test uses. With only test/ synced, the root has no shell or install manifests: omarchy-shell refuses every call and the package audit passes vacuously against an empty manifest. With a full tree synced, the path disagrees with the config path the session shell was started from, and since qs matches instances by that path, every omarchy-shell call reads as "not running". The suite acts on the running session, so ask the user manager for the session's own OMARCHY_PATH first, then fall back to this checkout, then to the installed tree. --- test/acceptance | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/acceptance b/test/acceptance index 192e38f0..37a63f6e 100755 --- a/test/acceptance +++ b/test/acceptance @@ -19,7 +19,22 @@ mkdir -p "$OMARCHY_ACCEPTANCE_DIR" # the session environment is inherited. export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" export DBUS_SESSION_BUS_ADDRESS="${DBUS_SESSION_BUS_ADDRESS:-unix:path=$XDG_RUNTIME_DIR/bus}" -export OMARCHY_PATH="${OMARCHY_PATH:-$ROOT}" +# The suite acts on the running session, and qs matches shell instances by +# config path — pointed at any other tree, every omarchy-shell call reads as +# "not running". So default OMARCHY_PATH to the tree the session was started +# from, then this checkout for standalone use, then the installed tree for +# when only test/ was synced in (omarchy-iso-test's --sync-omarchy). +if [[ -z ${OMARCHY_PATH:-} ]]; then + OMARCHY_PATH=$(systemctl --user show-environment 2>/dev/null | sed -n 's/^OMARCHY_PATH=//p' | tail -1) +fi +if [[ -z $OMARCHY_PATH ]]; then + OMARCHY_PATH=$ROOT +fi +if [[ ! -f $OMARCHY_PATH/shell/shell.qml && -f /usr/share/omarchy/shell/shell.qml ]]; then + OMARCHY_PATH=/usr/share/omarchy +fi +export OMARCHY_PATH + export PATH="$OMARCHY_PATH/bin:$PATH" if [[ -z ${DISPLAY:-} ]]; then From d3a5e6916261bdae5b784a9fd054366571d19659 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Sun, 30 Aug 2026 16:06:43 -0400 Subject: [PATCH 3/5] Fail the package audit when the manifest is missing Reading a nonexistent manifest produced an empty package list, so the audit reported every package installed after checking none of them. --- test/acceptance.d/system-test.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/acceptance.d/system-test.sh b/test/acceptance.d/system-test.sh index 610fcb4b..0d648e9c 100644 --- a/test/acceptance.d/system-test.sh +++ b/test/acceptance.d/system-test.sh @@ -8,12 +8,17 @@ status=0 verify_core_packages() { local package + local manifest="$OMARCHY_PATH/install/omarchy-base.packages" local -a missing=() + # Without this, a missing manifest reads as an empty package list and the + # audit passes having checked nothing. + [[ -f $manifest ]] || fail "all Omarchy core packages are installed" "package manifest not found: $manifest" + while IFS= read -r package; do [[ -z $package || $package == \#* ]] && continue pacman -Q "$package" >/dev/null 2>&1 || missing+=("$package") - done <"$OMARCHY_PATH/install/omarchy-base.packages" + done <"$manifest" (( ${#missing[@]} == 0 )) || fail "all Omarchy core packages are installed" "missing packages: ${missing[*]}" pass "all Omarchy core packages are installed (${#missing[@]} missing)" From 9ca8f90e9171ea9c128811a484875ddc86ee1385 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Sun, 30 Aug 2026 16:07:16 -0400 Subject: [PATCH 4/5] Capture OCR screenshots at 2x scale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tesseract routinely drops small caption text at native resolution — the weather panel's detail labels fail the WIND assertion with the text plainly on screen. Let the compositor upscale the capture instead. --- test/acceptance.d/base-test.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/acceptance.d/base-test.sh b/test/acceptance.d/base-test.sh index 717007c9..023223a1 100644 --- a/test/acceptance.d/base-test.sh +++ b/test/acceptance.d/base-test.sh @@ -36,7 +36,9 @@ screen_contains() { local text="$1" local snapshot="/tmp/omarchy-acceptance-ocr-$$.png" - if ! timeout 10 grim "$snapshot" 2>/dev/null; then + # Capture at 2x scale: tesseract routinely drops small caption text at + # native resolution (the weather panel's detail labels, for one). + if ! timeout 10 grim -s 2 "$snapshot" 2>/dev/null; then rm -f "$snapshot" return 1 fi From 99ec17acfac1d78e895f73532ee82872f316e97d Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Sun, 30 Aug 2026 16:08:12 -0400 Subject: [PATCH 5/5] Cover the 4.0.2 security hardening in the acceptance suite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assert the closed session-to-root paths on an installed system — no blanket input-group membership, no shipped asdcontrol sudoers grant — and exercise omarchy-setup-security-sshd unattended end to end: sshd up, key authorized, password and keyboard-interactive authentication off in the effective config, SSH port rate limited in the firewall. The sshd section mutates the machine, so it requires the explicit OMARCHY_ACCEPTANCE_SUDO_PASSWORD opt-in that omarchy-iso-test passes for its throwaway VMs; elsewhere it skips. --- test/acceptance.d/security-test.sh | 105 +++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100755 test/acceptance.d/security-test.sh diff --git a/test/acceptance.d/security-test.sh b/test/acceptance.d/security-test.sh new file mode 100755 index 00000000..0755c722 --- /dev/null +++ b/test/acceptance.d/security-test.sh @@ -0,0 +1,105 @@ +#!/bin/bash +# +# Verifies the security posture of an installed system: the unprivileged +# session-to-root paths closed for 4.0.2 (blanket input-group grant, shipped +# asdcontrol sudoers authorization) and the SSH hardening flow. +# +# The sshd section reconfigures the machine (enables sshd, opens the firewall, +# disables password logins), so it demands explicit opt-in: it only runs when +# OMARCHY_ACCEPTANCE_SUDO_PASSWORD is set, which omarchy-iso-test does for its +# throwaway VMs. A cached sudo timestamp alone never triggers it, so running +# the suite on a machine you care about cannot reconfigure sshd by accident. + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +# Membership of `input` gives raw access to /dev/input/event*: any process +# running as the user could log keystrokes. Only the opt-in controller and +# ydotool features may grant it. +verify_input_group() { + if id -nG | grep -qw input; then + if pacman -Q xpadneo-dkms &>/dev/null || pacman -Q ydotool &>/dev/null; then + pass "input group membership is backed by an opt-in feature" + else + fail "user is not in the input group" "no controller or ydotool support installed to justify it" + fi + else + pass "user is not in the input group" + fi +} + +sudo_available() { + if sudo -n true 2>/dev/null; then + return 0 + fi + + if [[ -n ${OMARCHY_ACCEPTANCE_SUDO_PASSWORD:-} ]]; then + printf '%s\n' "$OMARCHY_ACCEPTANCE_SUDO_PASSWORD" | sudo -S -v 2>/dev/null + return $? + fi + + return 1 +} + +verify_asdcontrol_sudoers() { + # Omarchy used to ship a passwordless sudoers grant for asdcontrol; that + # authorization now belongs to the package alone. + if sudo -n test -e /etc/sudoers.d/omarchy-asdcontrol; then + fail "no omarchy asdcontrol sudoers grant is shipped" "/etc/sudoers.d/omarchy-asdcontrol exists" + fi + pass "no omarchy asdcontrol sudoers grant is shipped" +} + +verify_sshd_hardening() { + local key_file=/tmp/omarchy-acceptance-sshd-key + local effective_config + + rm -f "$key_file" "$key_file.pub" + ssh-keygen -t ed25519 -N "" -q -C "omarchy-acceptance" -f "$key_file" + + if ! omarchy-setup-security-sshd --key="$(cat "$key_file.pub")" >"$ARTIFACTS/setup-security-sshd.log" 2>&1; then + fail "omarchy-setup-security-sshd completes unattended" "$(tail -5 "$ARTIFACTS/setup-security-sshd.log")" + fi + pass "omarchy-setup-security-sshd completes unattended" + + systemctl is-active sshd.service >/dev/null || fail "sshd is running after setup" + pass "sshd is running after setup" + + grep -qxF "$(cat "$key_file.pub")" "$HOME/.ssh/authorized_keys" || fail "the key is authorized" + pass "the key is authorized" + + # The command verifies its own hardening before keeping it, but assert the + # effective config independently: sshd honors the first value it reads, and + # regressions here reopen password logins. Keywords match case-insensitively + # because OpenSSH 9.x dumps them lowercase and 10.x in CamelCase. + effective_config=$(sudo -n sshd -T) || fail "sshd reports its effective config" + grep -qixF "passwordauthentication no" <<<"$effective_config" || fail "password authentication is off" + pass "password authentication is off" + grep -qixF "kbdinteractiveauthentication no" <<<"$effective_config" || fail "keyboard-interactive authentication is off" + pass "keyboard-interactive authentication is off" + + if omarchy-cmd-present ufw; then + sudo -n ufw status | grep -qE '^22/tcp\s+LIMIT' || fail "the SSH port is rate limited in the firewall" + pass "the SSH port is rate limited in the firewall" + fi + + # Leave the machine as found where cheap: the throwaway key stays useless + # once removed, while the hardening itself is the state under test. + sed -i "\#$(cat "$key_file.pub" | cut -d' ' -f2)#d" "$HOME/.ssh/authorized_keys" + rm -f "$key_file" "$key_file.pub" +} + +verify_input_group + +if sudo_available; then + verify_asdcontrol_sudoers +else + pass "asdcontrol sudoers check skipped: sudo needs a password" +fi + +if [[ -n ${OMARCHY_ACCEPTANCE_SUDO_PASSWORD:-} ]] && sudo_available; then + verify_sshd_hardening +else + pass "sshd hardening exercise skipped: set OMARCHY_ACCEPTANCE_SUDO_PASSWORD to run it" +fi