From f8937eb9f0dfb2cba10a3bb97afa028c53c82363 Mon Sep 17 00:00:00 2001 From: Mateus Pym Date: Sat, 1 Aug 2026 18:39:43 -0300 Subject: [PATCH] Keep app.slice loadable with private procfs --- .../user-generators/omarchy-oomd-generator | 16 ++++++++++++ docs/file-layout.md | 4 ++- migrations/1785424256.sh | 10 +++---- migrations/1785620124.sh | 5 ++++ test/shell.d/config-test.sh | 9 +++++++ test/shell.d/systemd-test.sh | 26 +++++++++++++++++++ 6 files changed, 64 insertions(+), 6 deletions(-) create mode 100755 default/systemd/user-generators/omarchy-oomd-generator create mode 100644 migrations/1785620124.sh diff --git a/default/systemd/user-generators/omarchy-oomd-generator b/default/systemd/user-generators/omarchy-oomd-generator new file mode 100755 index 0000000000..23a4aed9a0 --- /dev/null +++ b/default/systemd/user-generators/omarchy-oomd-generator @@ -0,0 +1,16 @@ +#!/bin/bash + +set -euo pipefail + +proc_root="${OMARCHY_OOMD_PROC_ROOT:-/proc}" +oomd_dropin="${OMARCHY_OOMD_DROPIN:-/usr/share/omarchy/default/systemd/user/app.slice.d/10-oomd.conf}" +generator_dir="${1:?systemd user generator output directory is required}" + +# systemd resolves ManagedOOM dependencies by reading PID 1's cgroup. A +# hidepid=noaccess/invisible procfs makes that lookup fail and the whole slice +# unloadable, which also prevents D-Bus and graphical app scopes from starting. +[[ -r $proc_root/1/cgroup ]] || exit 0 +[[ -f $oomd_dropin ]] || exit 0 + +mkdir -p "$generator_dir/app.slice.d" +ln -s "$oomd_dropin" "$generator_dir/app.slice.d/10-oomd.conf" diff --git a/docs/file-layout.md b/docs/file-layout.md index 29e8196aff..ba445789f7 100644 --- a/docs/file-layout.md +++ b/docs/file-layout.md @@ -105,7 +105,9 @@ default/** ──► omarchy-settings /usr/share/omarchy ├─ xdg-terminal-exec/*.list /usr/share/xdg-terminal-exec/ ├─ applications/mimeapps.list /usr/share/applications/mimeapps.list ├─ systemd/user/*.{service,path} /usr/lib/systemd/user/ - ├─ systemd/user/app.slice.d/10-oomd.conf /usr/lib/systemd/user/app.slice.d/ + ├─ systemd/user/app.slice.d/10-oomd.conf generator template (kept under /usr/share/omarchy/) + ├─ systemd/user-generators/omarchy-oomd-generator /usr/lib/systemd/user-generators/ + │ → runtime app.slice drop-in when PID 1 is visible ├─ systemd/system-sleep/unmount-fuse /usr/lib/systemd/system-sleep/ ├─ systemd/zram-generator.conf.d/90-omarchy.conf /usr/lib/systemd/zram-generator.conf.d/ ├─ fonts/omarchy/omarchy.ttf /usr/share/fonts/omarchy/ diff --git a/migrations/1785424256.sh b/migrations/1785424256.sh index d1d2f4cea5..95a03f572f 100644 --- a/migrations/1785424256.sh +++ b/migrations/1785424256.sh @@ -23,9 +23,9 @@ else echo "Could not enable systemd-oomd.service; memory pressure will still take the session down." fi -# Pick up /usr/lib/systemd/user/app.slice.d/10-oomd.conf without waiting for -# the next login. That drop-in is what marks app.slice as a kill candidate; -# until the user manager reloads and reports it, oomd is running with nothing -# to act on. An `omarchy update` over SSH or from a TTY has no user manager to -# reload, and there the next graphical login picks it up on its own. +# Run the user generator and pick up app.slice candidacy without waiting for +# the next login. The generator skips the drop-in when procfs hides PID 1, +# because systemd cannot resolve ManagedOOM dependencies in that setup. An +# `omarchy update` over SSH or from a TTY has no user manager to reload, and +# there the next graphical login runs the generator on its own. systemctl --user daemon-reload >/dev/null 2>&1 || true diff --git a/migrations/1785620124.sh b/migrations/1785620124.sh new file mode 100644 index 0000000000..e93f22fe5e --- /dev/null +++ b/migrations/1785620124.sh @@ -0,0 +1,5 @@ +echo "Keep systemd-oomd from breaking app launches with a private procfs" + +# Replace the old unconditional app.slice drop-in with generator output. A +# reload also removes previously generated candidacy when PID 1 is hidden. +systemctl --user daemon-reload >/dev/null 2>&1 || true diff --git a/test/shell.d/config-test.sh b/test/shell.d/config-test.sh index 7ee5a8695a..73570053e6 100755 --- a/test/shell.d/config-test.sh +++ b/test/shell.d/config-test.sh @@ -140,6 +140,7 @@ package_defaults = [ ("default/systemd/user/omarchy-migrate-notify.service", "/usr/lib/systemd/user/omarchy-migrate-notify.service", "systemd/user/omarchy-migrate-notify.service"), ("default/systemd/user/omarchy-tailscale-receive.service", "/usr/lib/systemd/user/omarchy-tailscale-receive.service", "systemd/user/omarchy-tailscale-receive.service"), ("default/systemd/user/omarchy-fcitx5.service", "/usr/lib/systemd/user/omarchy-fcitx5.service", "systemd/user/omarchy-fcitx5.service"), + ("default/systemd/user-generators/omarchy-oomd-generator", "/usr/lib/systemd/user-generators/omarchy-oomd-generator", "systemd/user-generators/omarchy-oomd-generator"), ("default/systemd/zram-generator.conf.d/90-omarchy.conf", "/usr/lib/systemd/zram-generator.conf.d/90-omarchy.conf", "systemd/zram-generator.conf.d/90-omarchy.conf"), ("default/fonts/omarchy/omarchy.ttf", "/usr/share/fonts/omarchy/omarchy.ttf", "omarchy.ttf"), ("default/snapper/root", "/etc/snapper/config-templates/omarchy", "snapper/root"), @@ -153,6 +154,14 @@ for source, destination, legacy in package_defaults: if destination and (source not in pkgbuild or destination not in pkgbuild): errors.append(f"PKGBUILD does not explicitly install {source} -> {destination}") +oomd_vendor_source = "default/systemd/user/app.slice.d/10-oomd.conf" +oomd_vendor_destination = "/usr/lib/systemd/user/app.slice.d/10-oomd.conf" +if oomd_vendor_source in pkgbuild and oomd_vendor_destination in pkgbuild: + errors.append( + "PKGBUILD installs app.slice OOM candidacy unconditionally instead of through " + "omarchy-oomd-generator" + ) + # Existing users have an absolute wants symlink to the old unit path, and the # migration that repoints it only runs for users who run an update -- the # opposite of who the notifier is for. Dropping this alias strands them. diff --git a/test/shell.d/systemd-test.sh b/test/shell.d/systemd-test.sh index 7d4f67c7f9..0ede32b950 100755 --- a/test/shell.d/systemd-test.sh +++ b/test/shell.d/systemd-test.sh @@ -94,6 +94,28 @@ grep -Fx 'ManagedOOMMemoryPressure=kill' "$oomd_slice" >/dev/null || grep -Fx 'ManagedOOMSwap=kill' "$oomd_slice" >/dev/null || fail "no swap backstop for the slower shape of the same failure" +oomd_generator="$ROOT/default/systemd/user-generators/omarchy-oomd-generator" +[[ -x $oomd_generator ]] || + fail "app.slice candidacy is not installed through an executable user generator" + +oomd_generator_test=$(mktemp -d) +mkdir -p "$oomd_generator_test/proc" "$oomd_generator_test/private-output" +OMARCHY_OOMD_PROC_ROOT="$oomd_generator_test/proc" \ + OMARCHY_OOMD_DROPIN="$oomd_slice" \ + "$oomd_generator" "$oomd_generator_test/private-output" +[[ ! -e $oomd_generator_test/private-output/app.slice.d/10-oomd.conf ]] || + fail "OOM candidacy is generated when procfs hides PID 1, which makes app.slice unloadable" + +mkdir -p "$oomd_generator_test/proc/1" "$oomd_generator_test/normal-output" +touch "$oomd_generator_test/proc/1/cgroup" +OMARCHY_OOMD_PROC_ROOT="$oomd_generator_test/proc" \ + OMARCHY_OOMD_DROPIN="$oomd_slice" \ + "$oomd_generator" "$oomd_generator_test/normal-output" +[[ $(readlink "$oomd_generator_test/normal-output/app.slice.d/10-oomd.conf") == "$oomd_slice" ]] || + fail "OOM candidacy is not generated when systemd can inspect PID 1's cgroup" +rm -rf "$oomd_generator_test" +pass "systemd-oomd candidacy stays compatible with private procfs mounts" + # Hyprland lives in session.slice/wayland-wm@hyprland.desktop.service. Marking # any ancestor of that as a kill candidate puts the compositor back in the # victim pool, which is the crash this whole thing exists to prevent. @@ -117,4 +139,8 @@ oomd_migration=$(grep -rl 'systemd-oomd.service' "$ROOT/migrations" | head -n 1 fail "existing installs never enable systemd-oomd; enable-services.sh only runs at install time" grep -F 'systemctl --user daemon-reload' "$oomd_migration" >/dev/null || fail "migration leaves the user manager unaware of app.slice candidacy until the next login" + +oomd_compat_migration="$ROOT/migrations/1785620124.sh" +grep -F 'systemctl --user daemon-reload' "$oomd_compat_migration" >/dev/null || + fail "existing user managers keep the unconditional OOM drop-in loaded after upgrade" pass "existing installs enable systemd-oomd and report app.slice without a relogin"