ask where the ballast goes and how big it is
Three questions instead of one, because the two the original never asked are the
two that decide whether the thing is useful.
1. Do you want one, with the explanation first.
2. Where. Home (easiest to find again months from now), beside Officer, or a
path typed in. This is not tidiness: the checker measures its own directory,
so a ballast only protects the filesystem it sits on. Choosing where it goes
is choosing which mount is covered.
3. How much, as 5/10/20% — with the actual numbers, and with what would be LEFT
rather than only what is taken:
[1] 5% — reserves 2.9GB leaving 54.3GB free
[2] 10% — reserves 5.8GB leaving 51.4GB free
[3] 20% — reserves 11.5GB leaving 45.7GB free
A percentage on its own is unanswerable. The number that decides it is the
one on the right: the reserve has to be big enough to matter and small
enough not to be the thing that filled the disk.
The size is computed against the filesystem the chosen path lands on, after the
location is known, so the percentages are of the right disk. ballast_free_kb
walks up to a directory that exists, since nothing has created the target yet.
An existing ballast in either default location is found and left alone rather
than a second one being made beside it.
Verified end to end in a temp home: created at the chosen path, 2.9G for 5% of
57.1GB, checker installed and reporting it present.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -196,22 +196,33 @@ swappiness_set() {
|
|||||||
# already has passwordless sudo, but wrong, and not something to carry forward.
|
# already has passwordless sudo, but wrong, and not something to carry forward.
|
||||||
# Both the script and the file now live in root-owned system paths.
|
# Both the script and the file now live in root-owned system paths.
|
||||||
|
|
||||||
BALLAST_FILE=/var/lib/machine-setup/ballast.bin
|
# Where it goes is asked rather than decided. A ballast only protects the
|
||||||
|
# filesystem it is ON — the checker measures its own directory — so the choice is
|
||||||
|
# also a choice of which mount is being protected. Defaults to the user's home,
|
||||||
|
# which on most machines is the same filesystem as / and is the easiest place to
|
||||||
|
# find it again months later.
|
||||||
|
BALLAST_FILE=""
|
||||||
BALLAST_CHECKER=/usr/local/sbin/emergency-disk-check
|
BALLAST_CHECKER=/usr/local/sbin/emergency-disk-check
|
||||||
BALLAST_CRON=/etc/cron.d/emergency-disk-check
|
BALLAST_CRON=/etc/cron.d/emergency-disk-check
|
||||||
BALLAST_THRESHOLD=10
|
BALLAST_THRESHOLD=10
|
||||||
|
BALLAST_NAME=emergency-disk-ballast.bin
|
||||||
|
|
||||||
ballast_exists() { [[ -f "$BALLAST_FILE" ]]; }
|
ballast_exists() { [[ -n "$BALLAST_FILE" && -f "$BALLAST_FILE" ]]; }
|
||||||
|
|
||||||
ballast_size_human() { du -h "$BALLAST_FILE" 2>/dev/null | cut -f1; }
|
ballast_size_human() { du -h "$BALLAST_FILE" 2>/dev/null | cut -f1; }
|
||||||
|
|
||||||
# 10% of what is free right now, in MiB.
|
# The nearest directory that exists, walking up. A path being chosen for the
|
||||||
ballast_size_mb() {
|
# ballast does not mean anything has created it yet, and df cannot measure a
|
||||||
local free_kb
|
# directory that is not there.
|
||||||
free_kb="$(df -Pk /var/lib | awk 'NR == 2 { print $4 }')"
|
existing_ancestor() {
|
||||||
echo $((free_kb / 1024 / 10))
|
local dir="$1"
|
||||||
|
while [[ ! -d "$dir" && "$dir" != "/" ]]; do dir="$(dirname "$dir")"; done
|
||||||
|
echo "$dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Free space in KiB on whichever filesystem would hold this path.
|
||||||
|
ballast_free_kb() { df -Pk "$(existing_ancestor "$1")" | awk 'NR == 2 { print $4 }'; }
|
||||||
|
|
||||||
ballast_create() {
|
ballast_create() {
|
||||||
local mb="$1"
|
local mb="$1"
|
||||||
mkdir -p "$(dirname "$BALLAST_FILE")"
|
mkdir -p "$(dirname "$BALLAST_FILE")"
|
||||||
|
|||||||
@@ -430,34 +430,108 @@ step "Emergency disk ballast"
|
|||||||
if ! skip; then
|
if ! skip; then
|
||||||
echo ""
|
echo ""
|
||||||
info "Emergency disk ballast — a reserve you can burn when the disk fills up"
|
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 " A file holding nothing, whose only job is to be deleted. A root cron"
|
||||||
echo " checks every 10 minutes and deletes it if free space drops below"
|
echo " checks every 10 minutes and removes it if free space drops below"
|
||||||
echo " ${BALLAST_THRESHOLD}%, so you get room to log in and clean up instead of meeting a"
|
echo " ${BALLAST_THRESHOLD}%, which buys you room to log in and clean up rather than"
|
||||||
echo " wedged machine — Docker, journald and postgres all misbehave badly"
|
echo " meeting a wedged machine — Docker, journald and postgres all"
|
||||||
echo " at 100% full, and not all of them recover on their own."
|
echo " misbehave badly at 100% full, and not all of them recover on their"
|
||||||
echo " It is a one-shot valve: once spent, run this again to recreate it."
|
echo " own."
|
||||||
echo ""
|
echo ""
|
||||||
|
echo " It is a one-shot valve: once spent, run this again to recreate it."
|
||||||
if is_server; then
|
if is_server; then
|
||||||
echo " recommended for ${MACHINE_ROLE} — a full disk on an unattended box is the bad case"
|
echo " Worth having on ${MACHINE_ROLE} — a full disk on an unattended box is the bad case."
|
||||||
else
|
else
|
||||||
echo " less useful on ${MACHINE_ROLE} — you are sitting at this machine and will notice"
|
echo " Less useful on ${MACHINE_ROLE} — you are sitting at this machine and will notice."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ -f "${USER_HOME}/${BALLAST_NAME}" ]]; then BALLAST_FILE="${USER_HOME}/${BALLAST_NAME}"; fi
|
||||||
|
[[ -f "${OFFICER_ROOT}/${BALLAST_NAME}" ]] && BALLAST_FILE="${OFFICER_ROOT}/${BALLAST_NAME}"
|
||||||
|
|
||||||
if ballast_exists; then
|
if ballast_exists; then
|
||||||
|
echo ""
|
||||||
echo " already present: $(ballast_size_human) at ${BALLAST_FILE}"
|
echo " already present: $(ballast_size_human) at ${BALLAST_FILE}"
|
||||||
SUMMARY+=("Disk ballast: already present ($(ballast_size_human))")
|
SUMMARY+=("Disk ballast: already present ($(ballast_size_human))")
|
||||||
|
elif ! confirm "Create one?"; then
|
||||||
|
warn "skipped by request"
|
||||||
|
SUMMARY+=("Disk ballast: SKIPPED by request")
|
||||||
else
|
else
|
||||||
BALLAST_MB="$(ballast_size_mb)"
|
# ── where ──
|
||||||
echo " to create: ${BALLAST_MB}M at ${BALLAST_FILE}"
|
#
|
||||||
if confirm "Create it?"; then
|
# A ballast only protects the filesystem it sits on, because the checker
|
||||||
ballast_create "$BALLAST_MB"
|
# measures its own directory. So this is also a choice of which mount is
|
||||||
ballast_install_checker
|
# being protected, not just where the file is tidiest.
|
||||||
ok "ballast $(ballast_size_human), checker at ${BALLAST_CHECKER}"
|
echo ""
|
||||||
SUMMARY+=("Disk ballast: $(ballast_size_human), checked every 10 min")
|
info "Where should it go?"
|
||||||
else
|
echo " [1] ${USER_HOME}/${BALLAST_NAME}"
|
||||||
warn "skipped by request"
|
echo " your home — easiest to find again months from now"
|
||||||
SUMMARY+=("Disk ballast: SKIPPED by request")
|
echo " [2] ${OFFICER_ROOT}/${BALLAST_NAME}"
|
||||||
fi
|
echo " beside Officer — same place as everything else it owns"
|
||||||
|
echo " [3] somewhere else, typed in"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
BALLAST_FILE=""
|
||||||
|
while [[ -z "$BALLAST_FILE" ]]; do
|
||||||
|
if ! read -rp " Which one? (1/2/3) [1]: " BALLAST_WHERE; then
|
||||||
|
echo ""
|
||||||
|
fail "No answer."
|
||||||
|
fi
|
||||||
|
case "${BALLAST_WHERE:-1}" in
|
||||||
|
1) BALLAST_FILE="${USER_HOME}/${BALLAST_NAME}" ;;
|
||||||
|
2) BALLAST_FILE="${OFFICER_ROOT}/${BALLAST_NAME}" ;;
|
||||||
|
3)
|
||||||
|
read -rp " Full path to the file: " BALLAST_TYPED || fail "No answer."
|
||||||
|
BALLAST_TYPED="${BALLAST_TYPED/#\~/$USER_HOME}"
|
||||||
|
if [[ "$BALLAST_TYPED" == /* ]]; then
|
||||||
|
BALLAST_FILE="$BALLAST_TYPED"
|
||||||
|
else
|
||||||
|
warn "That needs to be an absolute path, starting with /"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*) warn "Pick 1, 2 or 3." ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── how much ──
|
||||||
|
#
|
||||||
|
# Percentages mean nothing without the numbers behind them, and the number
|
||||||
|
# that matters is what is LEFT — the point of the reserve is to be big enough
|
||||||
|
# to matter and small enough not to be the thing that filled the disk.
|
||||||
|
BALLAST_FREE_KB="$(ballast_free_kb "$(dirname "$BALLAST_FILE")")"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
info "How much? ${BALLAST_FILE} sits on a filesystem with $(human_bytes $((BALLAST_FREE_KB * 1024))) free."
|
||||||
|
for i in 1 2 3; do
|
||||||
|
case $i in
|
||||||
|
1) BP=5 ;;
|
||||||
|
2) BP=10 ;;
|
||||||
|
3) BP=20 ;;
|
||||||
|
esac
|
||||||
|
BSZ=$((BALLAST_FREE_KB * BP / 100))
|
||||||
|
printf ' [%d] %3d%% — reserves %-8s leaving %s free\n' \
|
||||||
|
"$i" "$BP" "$(human_bytes $((BSZ * 1024)))" "$(human_bytes $(((BALLAST_FREE_KB - BSZ) * 1024)))"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
BALLAST_PCT=""
|
||||||
|
while [[ -z "$BALLAST_PCT" ]]; do
|
||||||
|
if ! read -rp " Which one? (1/2/3) [2]: " BALLAST_SIZE_CHOICE; then
|
||||||
|
echo ""
|
||||||
|
fail "No answer."
|
||||||
|
fi
|
||||||
|
case "${BALLAST_SIZE_CHOICE:-2}" in
|
||||||
|
1) BALLAST_PCT=5 ;;
|
||||||
|
2) BALLAST_PCT=10 ;;
|
||||||
|
3) BALLAST_PCT=20 ;;
|
||||||
|
*) warn "Pick 1, 2 or 3." ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
BALLAST_MB=$((BALLAST_FREE_KB * BALLAST_PCT / 100 / 1024))
|
||||||
|
ballast_create "$BALLAST_MB"
|
||||||
|
ballast_install_checker
|
||||||
|
ok "ballast $(ballast_size_human) at ${BALLAST_FILE}, checked every 10 minutes"
|
||||||
|
ok "status any time: ${BALLAST_CHECKER} --status"
|
||||||
|
SUMMARY+=("Disk ballast: $(ballast_size_human) at ${BALLAST_FILE} (${BALLAST_PCT}%)")
|
||||||
fi
|
fi
|
||||||
step_ok
|
step_ok
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user