Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions default/systemd/user-generators/omarchy-oomd-generator
Original file line number Diff line number Diff line change
@@ -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"
4 changes: 3 additions & 1 deletion docs/file-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
10 changes: 5 additions & 5 deletions migrations/1785424256.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 5 additions & 0 deletions migrations/1785620124.sh
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions test/shell.d/config-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -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.
Expand Down
26 changes: 26 additions & 0 deletions test/shell.d/systemd-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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"