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>
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)" | ||
| 6 | 6 | install -Dm755 "$src/src/memwatchd.py" /usr/local/lib/memwatch/memwatchd.py |
| 7 | 7 | install -Dm644 "$src/units/memwatch.service" /etc/systemd/system/memwatch.service |
| 8 | 8 | install -Dm644 "$src/README.md" /usr/share/doc/memwatch/README.md |
| 9 | +install -Dm755 "$src/bin/memwatch" /usr/local/bin/memwatch | |
| 9 | 10 | if [[ -e /etc/memwatch.conf ]]; then |
| 10 | 11 | install -Dm644 "$src/memwatch.conf" /etc/memwatch.conf.new |
| 11 | 12 | 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.py | 6 | install -Dm755 "$src/src/memwatchd.py" /usr/local/lib/memwatch/memwatchd.py |
| 7 | install -Dm644 "$src/units/memwatch.service" /etc/systemd/system/memwatch.service | 7 | install -Dm644 "$src/units/memwatch.service" /etc/systemd/system/memwatch.service |
| 8 | install -Dm644 "$src/README.md" /usr/share/doc/memwatch/README.md | 8 | 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 ]]; then | 10 | if [[ -e /etc/memwatch.conf ]]; then |
| 10 | install -Dm644 "$src/memwatch.conf" /etc/memwatch.conf.new | 11 | 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" |