port the swap section, and size it against the disk
Four things the original got wrong, all of which only show up on a machine that is not this one: It detected swap with `swapon --show | grep -q '/'` — a test for a swap FILE. A machine using zram or a swap partition reports no swap at all, and the step would add a swapfile beside working swap. Reads SwapTotal from /proc/meminfo now, which covers every kind. It never looked at free disk. On a VPS with 4G free and 16G of RAM it would fallocate 8G, fail, and take the run down under `set -e`. The recommendation is now capped by what is actually there, keeping 5G back, and refuses rather than shrinking to something useless. fallocate was assumed to work. It produces a file that btrfs and zfs will not swap on, so dd is the fallback — slow, but it always works. swappiness was written by sed'ing /etc/sysctl.conf in place, tangling it with whatever else lives there. It is a drop-in at /etc/sysctl.d now, so what this script set is visible as its own file. Role-dependent, which is the first use of MACHINE_ROLE: swappiness 10 on a server, where swapping is the emergency valve and a page fault on a request path is latency somebody is waiting for; the kernel default of 60 on dev, where swapping out an application nobody has touched in an hour is exactly what you want. WSL is left alone entirely — WSL2 runs its own managed swap inside the VM, and a swapfile written here is wasted disk the kernel will not use. Sizes are reported rounded rather than floored. A 4 GiB swapfile is 4194300 kB, which floors to 3 and reads as though a gigabyte went missing. Free disk stays floored, deliberately: it decides how much to allocate, and rounding up invents space. Verified on this host — 4G RAM, 4G existing swap correctly detected and left alone — and with the disk check stubbed at 6G free (caps to 1G) and 3G free (refuses). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -89,6 +89,90 @@ locale_set() {
|
||||
esac
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Swap
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
SWAPFILE=/swapfile
|
||||
|
||||
# Rounded to nearest, not floored: a 4 GiB swapfile is 4194300 kB, which floors
|
||||
# to 3 and reads as though a gigabyte went missing. Same for RAM, where 3.7 GiB
|
||||
# reporting as "3G" makes the sizing tiers look wrong.
|
||||
kb_to_gb_rounded() { echo $((($1 + 524288) / 1048576)); }
|
||||
|
||||
# Total active swap in GiB, 0 if there is none.
|
||||
#
|
||||
# From /proc/meminfo rather than by grepping swapon's output for a slash, which
|
||||
# is what the original did to spot a swap FILE — that test reports no swap at all
|
||||
# on a machine using zram or a swap partition, and the step would then add a
|
||||
# swapfile beside perfectly good swap.
|
||||
swap_active_gb() { kb_to_gb_rounded "$(awk '/^SwapTotal:/ { print $2 }' /proc/meminfo)"; }
|
||||
|
||||
ram_gb() { kb_to_gb_rounded "$(awk '/^MemTotal:/ { print $2 }' /proc/meminfo)"; }
|
||||
|
||||
# Free space on the filesystem that would hold the swapfile, in GiB. Floored
|
||||
# rather than rounded, deliberately: this one decides how much to allocate, and
|
||||
# rounding up invents space that is not there.
|
||||
disk_free_gb() { echo $(($(df -Pk "$(dirname "$SWAPFILE")" | awk 'NR == 2 { print $4 }') / 1024 / 1024)); }
|
||||
|
||||
# How much swap this machine should have.
|
||||
#
|
||||
# The tiers are the original's. What is new is that the answer is capped by what
|
||||
# is actually on the disk — the original would try to fallocate 8G on a VPS with
|
||||
# 4G free, fail, and take the run down with it.
|
||||
swap_recommended_gb() {
|
||||
local ram size
|
||||
ram="$(ram_gb)"
|
||||
if ((ram <= 2)); then
|
||||
size=2
|
||||
elif ((ram <= 8)); then
|
||||
size=4
|
||||
else
|
||||
size=8
|
||||
fi
|
||||
|
||||
# Leave a few gigabytes behind. A swapfile that fills the disk is a worse
|
||||
# problem than no swapfile.
|
||||
local room=$(($(disk_free_gb) - 5))
|
||||
((room < size)) && size="$room"
|
||||
((size < 1)) && size=0
|
||||
echo "$size"
|
||||
}
|
||||
|
||||
# How eagerly the kernel swaps, by role.
|
||||
#
|
||||
# 10 on a server: swapping is the emergency valve, not a routine, and the cost of
|
||||
# a page fault on a request path is latency somebody is waiting for. A desktop is
|
||||
# the opposite case — swapping out an application nobody has touched in an hour
|
||||
# is exactly what you want — so dev keeps the kernel default of 60.
|
||||
swappiness_for_role() { if is_server; then echo 10; else echo 60; fi; }
|
||||
|
||||
swap_create() {
|
||||
local gb="$1"
|
||||
|
||||
# fallocate is instant but produces a file some filesystems refuse to swap on
|
||||
# (btrfs without the right attributes, zfs at all). dd is slow and always
|
||||
# works, so it is the fallback rather than the default.
|
||||
if ! fallocate -l "${gb}G" "$SWAPFILE" 2>/dev/null; then
|
||||
info " fallocate is not usable here — writing the file with dd, which is slower"
|
||||
dd if=/dev/zero of="$SWAPFILE" bs=1M count=$((gb * 1024)) status=none
|
||||
fi
|
||||
|
||||
chmod 600 "$SWAPFILE"
|
||||
mkswap "$SWAPFILE" >/dev/null
|
||||
swapon "$SWAPFILE"
|
||||
|
||||
grep -qs "^${SWAPFILE}[[:space:]]" /etc/fstab || echo "${SWAPFILE} none swap sw 0 0" >>/etc/fstab
|
||||
}
|
||||
|
||||
# Written as a drop-in rather than by rewriting /etc/sysctl.conf in place. The
|
||||
# original sed'd that file, which means the setting is tangled up with whatever
|
||||
# else lives there and is invisible to anyone looking for what this script did.
|
||||
swappiness_set() {
|
||||
echo "vm.swappiness=$1" >/etc/sysctl.d/99-machine-setup-swappiness.conf
|
||||
sysctl -q -w "vm.swappiness=$1"
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Timezone
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user