nandi/memwatchpublic Fork 0
83ca73b
Commits
Clone
git clone https://git.rickub.com/nandi/memwatch.git
git clone ssh://git@rickub.com/nandi/memwatch.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

add memwatch control wrapper

A small CLI so the daemon can be driven without remembering systemctl
incantations: memwatch on/off/start/stop/restart/status/logs, plus
dry/live to flip dry_run in /etc/memwatch.conf and restart.

status and logs need no privileges. The rest escalate via run0, falling
back to pkexec then sudo, since run0's polkit path is currently refusing
transient units on this box. Going live prompts for confirmation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-20T15:57:04-07:00 Browse files
83ca73b parent: 33cffdf
added bin/memwatch +74 -0
new file mode 100755
@@ -0,0 +1,74 @@
1+#!/usr/bin/env bash
2+# memwatch -- control wrapper for the memwatch PSI watchdog service.
3+set -euo pipefail
4+
5+UNIT=memwatch.service
6+CONF=/etc/memwatch.conf
7+
8+# This box uses systemd's run0. pkexec and sudo are fallbacks for when the
9+# polkit path for run0 is unhappy.
10+priv() {
11+ if command -v run0 >/dev/null 2>&1 && run0 true >/dev/null 2>&1; then
12+ run0 "$@"
13+ elif command -v pkexec >/dev/null 2>&1; then
14+ pkexec "$@"
15+ elif command -v sudo >/dev/null 2>&1; then
16+ sudo "$@"
17+ else
18+ echo "memwatch: no way to escalate privileges (run0/pkexec/sudo all unusable)" >&2
19+ return 1
20+ fi
21+}
22+
23+mode() {
24+ [[ -r $CONF ]] || { echo "unknown (no $CONF)"; return; }
25+ if grep -qE '^\s*dry_run\s*=\s*0' "$CONF"; then echo live; else echo "dry-run"; fi
26+}
27+
28+set_dry() {
29+ priv sed -i -E "s/^\s*dry_run\s*=.*/dry_run = $1/" "$CONF"
30+}
31+
32+usage() {
33+ cat <<'USAGE'
34+usage: memwatch <command>
35+
36+ on enable and start the watchdog (persists across reboots)
37+ off stop and disable it
38+ start start it for this boot only
39+ stop stop it for this boot only
40+ restart restart it
41+ status service state, current mode, and live memory pressure
42+ logs follow the journal (ctrl-c to quit)
43+ dry switch to dry-run: log victims, kill nothing, then restart
44+ live switch to live: actually freeze and kill, then restart
45+USAGE
46+}
47+
48+case "${1-status}" in
49+ on) priv systemctl enable --now "$UNIT" ;;
50+ off) priv systemctl disable --now "$UNIT" ;;
51+ start) priv systemctl start "$UNIT" ;;
52+ stop) priv systemctl stop "$UNIT" ;;
53+ restart) priv systemctl restart "$UNIT" ;;
54+ logs) journalctl -fu "$UNIT" ;;
55+ dry) set_dry 1; priv systemctl try-restart "$UNIT"; echo "mode: dry-run" ;;
56+ live)
57+ echo "This lets memwatch freeze and kill real processes."
58+ read -rp "type yes to confirm: " ans
59+ [[ $ans == yes ]] || { echo "aborted"; exit 1; }
60+ set_dry 0; priv systemctl try-restart "$UNIT"; echo "mode: live" ;;
61+ status)
62+ printf 'enabled: %s\n' "$(systemctl is-enabled "$UNIT" 2>&1)"
63+ printf 'active: %s\n' "$(systemctl is-active "$UNIT" 2>&1)"
64+ printf 'mode: %s\n' "$(mode)"
65+ printf 'restarts: %s\n' "$(systemctl show "$UNIT" -P NRestarts 2>/dev/null)"
66+ echo
67+ echo "/proc/pressure/memory:"
68+ sed 's/^/ /' /proc/pressure/memory
69+ echo
70+ journalctl -u "$UNIT" --no-pager -n 5 -o cat 2>/dev/null | sed 's/^/ /'
71+ ;;
72+ -h|--help|help) usage ;;
73+ *) usage >&2; exit 2 ;;
74+esac
new file mode 100755
@@ -0,0 +1,74 @@
1+#!/usr/bin/env bash
2+# memwatch -- control wrapper for the memwatch PSI watchdog service.
3+set -euo pipefail
4+
5+UNIT=memwatch.service
6+CONF=/etc/memwatch.conf
7+
8+# This box uses systemd's run0. pkexec and sudo are fallbacks for when the
9+# polkit path for run0 is unhappy.
10+priv() {
11+ if command -v run0 >/dev/null 2>&1 && run0 true >/dev/null 2>&1; then
12+ run0 "$@"
13+ elif command -v pkexec >/dev/null 2>&1; then
14+ pkexec "$@"
15+ elif command -v sudo >/dev/null 2>&1; then
16+ sudo "$@"
17+ else
18+ echo "memwatch: no way to escalate privileges (run0/pkexec/sudo all unusable)" >&2
19+ return 1
20+ fi
21+}
22+
23+mode() {
24+ [[ -r $CONF ]] || { echo "unknown (no $CONF)"; return; }
25+ if grep -qE '^\s*dry_run\s*=\s*0' "$CONF"; then echo live; else echo "dry-run"; fi
26+}
27+
28+set_dry() {
29+ priv sed -i -E "s/^\s*dry_run\s*=.*/dry_run = $1/" "$CONF"
30+}
31+
32+usage() {
33+ cat <<'USAGE'
34+usage: memwatch <command>
35+
36+ on enable and start the watchdog (persists across reboots)
37+ off stop and disable it
38+ start start it for this boot only
39+ stop stop it for this boot only
40+ restart restart it
41+ status service state, current mode, and live memory pressure
42+ logs follow the journal (ctrl-c to quit)
43+ dry switch to dry-run: log victims, kill nothing, then restart
44+ live switch to live: actually freeze and kill, then restart
45+USAGE
46+}
47+
48+case "${1-status}" in
49+ on) priv systemctl enable --now "$UNIT" ;;
50+ off) priv systemctl disable --now "$UNIT" ;;
51+ start) priv systemctl start "$UNIT" ;;
52+ stop) priv systemctl stop "$UNIT" ;;
53+ restart) priv systemctl restart "$UNIT" ;;
54+ logs) journalctl -fu "$UNIT" ;;
55+ dry) set_dry 1; priv systemctl try-restart "$UNIT"; echo "mode: dry-run" ;;
56+ live)
57+ echo "This lets memwatch freeze and kill real processes."
58+ read -rp "type yes to confirm: " ans
59+ [[ $ans == yes ]] || { echo "aborted"; exit 1; }
60+ set_dry 0; priv systemctl try-restart "$UNIT"; echo "mode: live" ;;
61+ status)
62+ printf 'enabled: %s\n' "$(systemctl is-enabled "$UNIT" 2>&1)"
63+ printf 'active: %s\n' "$(systemctl is-active "$UNIT" 2>&1)"
64+ printf 'mode: %s\n' "$(mode)"
65+ printf 'restarts: %s\n' "$(systemctl show "$UNIT" -P NRestarts 2>/dev/null)"
66+ echo
67+ echo "/proc/pressure/memory:"
68+ sed 's/^/ /' /proc/pressure/memory
69+ echo
70+ journalctl -u "$UNIT" --no-pager -n 5 -o cat 2>/dev/null | sed 's/^/ /'
71+ ;;
72+ -h|--help|help) usage ;;
73+ *) usage >&2; exit 2 ;;
74+esac
modified install.sh +1 -0
@@ -6,6 +6,7 @@ src="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
66 install -Dm755 "$src/src/memwatchd.py" /usr/local/lib/memwatch/memwatchd.py
77 install -Dm644 "$src/units/memwatch.service" /etc/systemd/system/memwatch.service
88 install -Dm644 "$src/README.md" /usr/share/doc/memwatch/README.md
9+install -Dm755 "$src/bin/memwatch" /usr/local/bin/memwatch
910 if [[ -e /etc/memwatch.conf ]]; then
1011 install -Dm644 "$src/memwatch.conf" /etc/memwatch.conf.new
1112 echo "kept your /etc/memwatch.conf; new defaults at /etc/memwatch.conf.new"
@@ -6,6 +6,7 @@ src="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6 install -Dm755 "$src/src/memwatchd.py" /usr/local/lib/memwatch/memwatchd.py6 install -Dm755 "$src/src/memwatchd.py" /usr/local/lib/memwatch/memwatchd.py
7 install -Dm644 "$src/units/memwatch.service" /etc/systemd/system/memwatch.service7 install -Dm644 "$src/units/memwatch.service" /etc/systemd/system/memwatch.service
8 install -Dm644 "$src/README.md" /usr/share/doc/memwatch/README.md8 install -Dm644 "$src/README.md" /usr/share/doc/memwatch/README.md
9+install -Dm755 "$src/bin/memwatch" /usr/local/bin/memwatch
9 if [[ -e /etc/memwatch.conf ]]; then10 if [[ -e /etc/memwatch.conf ]]; then
10 install -Dm644 "$src/memwatch.conf" /etc/memwatch.conf.new11 install -Dm644 "$src/memwatch.conf" /etc/memwatch.conf.new
11 echo "kept your /etc/memwatch.conf; new defaults at /etc/memwatch.conf.new"12 echo "kept your /etc/memwatch.conf; new defaults at /etc/memwatch.conf.new"