add three resource-pressure sections, each asked rather than assumed
Swap covers memory pressure. These are its neighbours: 8. Emergency disk ballast the same valve, for disk 9. earlyoom what happens when swap runs out too 10. inotify watch limit the silent one All three follow the rule this script now works to: the role sets which way the recommendation points, never whether the question is asked. A dev machine is still offered the ballast, with the recommendation pointing the other way; a server is still offered the inotify raise, because anything running `bun --watch` or serving a file browser is a watcher too. The ballast is section 22 of the original, moved up beside swap where it belongs and moved out of the user's home. The original wrote the checker into $USER_HOME/.local/bin and ran it from a root cron — a root cron executing a script in a directory its owner can write is a privilege escalation waiting to be noticed. Moot on a box where that user already has passwordless sudo, but wrong. Both the checker and the file are in root-owned system paths now. Two bugs found by running the generated checker rather than reading it: It df'd the ballast's own directory, which does not exist before the ballast is created — and with `set -euo pipefail` that meant cron mailing an error every ten minutes. It now walks up to a directory that exists, and the installer creates the directory itself rather than depending on the create step. The inotify text claimed a default of 8192. This host is at 29461: Ubuntu raised it, and stating a number the reader can see is wrong on their own screen undermines the rest of the explanation. It now describes the failure instead and prints the machine's actual value. earlyoom is a distro package and a systemd unit, so it is checked with `systemctl is-active` and reports honestly when it installs but fails to start. Verified: checker --status and its no-op path both exit 0 with no directory present, and the helpers report correctly against this host. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -315,6 +315,123 @@ if ! skip; then
|
||||
step_ok
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# 8. Emergency disk ballast
|
||||
# =============================================================================
|
||||
#
|
||||
# Always offered, whatever the role — the role only decides which way the
|
||||
# recommendation points.
|
||||
|
||||
step "Emergency disk ballast"
|
||||
if ! skip; then
|
||||
echo ""
|
||||
info "Emergency disk ballast — a reserve you can burn when the disk fills up"
|
||||
echo " A junk file holding no data, sized at 10% of free disk. A root cron"
|
||||
echo " checks every 10 minutes and deletes it if free space drops below"
|
||||
echo " ${BALLAST_THRESHOLD}%, so you get room to log in and clean up instead of meeting a"
|
||||
echo " wedged machine — Docker, journald and postgres all misbehave badly"
|
||||
echo " at 100% full, and not all of them recover on their own."
|
||||
echo " It is a one-shot valve: once spent, run this again to recreate it."
|
||||
echo ""
|
||||
if is_server; then
|
||||
echo " recommended for ${MACHINE_ROLE} — a full disk on an unattended box is the bad case"
|
||||
else
|
||||
echo " less useful on ${MACHINE_ROLE} — you are sitting at this machine and will notice"
|
||||
fi
|
||||
|
||||
if ballast_exists; then
|
||||
echo " already present: $(ballast_size_human) at ${BALLAST_FILE}"
|
||||
SUMMARY+=("Disk ballast: already present ($(ballast_size_human))")
|
||||
else
|
||||
BALLAST_MB="$(ballast_size_mb)"
|
||||
echo " to create: ${BALLAST_MB}M at ${BALLAST_FILE}"
|
||||
if confirm "Create it?"; then
|
||||
ballast_create "$BALLAST_MB"
|
||||
ballast_install_checker
|
||||
ok "ballast $(ballast_size_human), checker at ${BALLAST_CHECKER}"
|
||||
SUMMARY+=("Disk ballast: $(ballast_size_human), checked every 10 min")
|
||||
else
|
||||
warn "skipped by request"
|
||||
SUMMARY+=("Disk ballast: SKIPPED by request")
|
||||
fi
|
||||
fi
|
||||
step_ok
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# 9. earlyoom
|
||||
# =============================================================================
|
||||
|
||||
step "earlyoom"
|
||||
if ! skip; then
|
||||
echo ""
|
||||
info "earlyoom — keeps the machine reachable when it runs out of memory"
|
||||
echo " The kernel's own OOM killer waits until an allocation actually"
|
||||
echo " fails, and by then the machine has usually spent minutes thrashing:"
|
||||
echo " unresponsive, ssh refusing to connect, nothing to do but reset it."
|
||||
echo " earlyoom watches free memory and kills the biggest consumer while"
|
||||
echo " there is still enough left to stay logged in."
|
||||
echo ""
|
||||
echo " This is what happens after swap runs out, so the two go together."
|
||||
|
||||
if earlyoom_is_active; then
|
||||
echo " already installed and running"
|
||||
SUMMARY+=("earlyoom: already running")
|
||||
elif confirm "Install it?"; then
|
||||
earlyoom_install
|
||||
if earlyoom_is_active; then
|
||||
ok "earlyoom running"
|
||||
SUMMARY+=("earlyoom: installed and running")
|
||||
else
|
||||
warn "earlyoom installed but not running — check: systemctl status earlyoom"
|
||||
SUMMARY+=("earlyoom: installed, not running")
|
||||
fi
|
||||
else
|
||||
warn "skipped by request"
|
||||
SUMMARY+=("earlyoom: SKIPPED by request")
|
||||
fi
|
||||
step_ok
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# 10. inotify watch limit
|
||||
# =============================================================================
|
||||
|
||||
step "inotify watch limit"
|
||||
if ! skip; then
|
||||
CURRENT_WATCHES="$(inotify_current_watches)"
|
||||
|
||||
echo ""
|
||||
info "inotify watch limit — how many files can be watched for changes at once"
|
||||
echo " A single file watcher walking a project with node_modules in it can"
|
||||
echo " exhaust the stock limit on its own, and every watcher on the machine"
|
||||
echo " draws from the same pool. The failure is silent: nothing errors, the"
|
||||
echo " watcher just stops noticing changes. Hot reload goes quiet, a build"
|
||||
echo " stops rebuilding, and the reason is never on screen."
|
||||
echo ""
|
||||
echo " current: ${CURRENT_WATCHES}"
|
||||
echo " to set: ${INOTIFY_WATCHES}"
|
||||
if is_role dev; then
|
||||
echo " recommended on dev — editors, bun --watch and vite are all watchers"
|
||||
else
|
||||
echo " less pressing on ${MACHINE_ROLE}, but anything running bun --watch or"
|
||||
echo " serving a file browser is a watcher too"
|
||||
fi
|
||||
|
||||
if ((CURRENT_WATCHES >= INOTIFY_WATCHES)); then
|
||||
echo " already at or above that, nothing to do"
|
||||
SUMMARY+=("inotify watches: already ${CURRENT_WATCHES}")
|
||||
elif confirm "Raise it?"; then
|
||||
inotify_raise
|
||||
ok "inotify watches raised to $(inotify_current_watches)"
|
||||
SUMMARY+=("inotify watches: raised to ${INOTIFY_WATCHES}")
|
||||
else
|
||||
warn "skipped by request"
|
||||
SUMMARY+=("inotify watches: SKIPPED by request — left at ${CURRENT_WATCHES}")
|
||||
fi
|
||||
step_ok
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# NOT PORTED YET
|
||||
# =============================================================================
|
||||
@@ -324,7 +441,7 @@ fi
|
||||
# auto-suspend · boot-hang fix · user creation ·
|
||||
# ssh keys · ssh hardening · dns · static ip · fail2ban · unattended-upgrades ·
|
||||
# git config · docker · zsh + prompt · tailscale · neovim · js runtimes ·
|
||||
# dev tools · ufw · zshrc · disk ballast
|
||||
# dev tools · ufw · zshrc
|
||||
#
|
||||
# Each arrives as its own commit. Delete this block when the list is empty.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user