mirror of
https://github.com/kevinveenbirkenbach/create-linux-swapfile.git
synced 2026-10-26 07:52:14 +00:00
On a btrfs root the swapfile was written with dd from /dev/zero, so a swapfile the size of the RAM cost tens of GiB of writes and stalled every other writer on the filesystem. A caller that bounds the run, such as an async Ansible task, killed dd halfway, and the next run found the wrong size and started over. btrfs accepts a preallocated NOCOW swapfile and the swap directory already carries +C, so every filesystem now allocates with fallocate. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
104 lines
3.8 KiB
Bash
104 lines
3.8 KiB
Bash
#!/bin/sh
|
|
# An unprivileged container cannot activate swap, so the run is expected to fail
|
|
# at mkswap or swapon - which of the two depends on the host's /proc/swaps.
|
|
# Everything before it - findmnt, fallocate, chmod - runs for real.
|
|
set -eu
|
|
|
|
fail() {
|
|
echo "FAIL: $1" >&2
|
|
exit 1
|
|
}
|
|
|
|
SWAPFORGE="$(command -v swap-forge)" || fail "swap-forge is not on PATH after install"
|
|
echo "ok: swap-forge installed at ${SWAPFORGE}"
|
|
|
|
command -v swafo >/dev/null || fail "the swafo alias is not on PATH after install"
|
|
echo "ok: swafo alias installed"
|
|
|
|
case "${SWAPFORGE}" in
|
|
/usr/local/bin/*) ;;
|
|
*) fail "swap-forge resolved to ${SWAPFORGE}, outside the install prefix" ;;
|
|
esac
|
|
echo "ok: resolved from the installed package, not the source tree"
|
|
|
|
for binary in findmnt swapoff fallocate chmod mkswap swapon chattr btrfs; do
|
|
command -v "${binary}" >/dev/null || fail "${binary} is missing from the image"
|
|
done
|
|
echo "ok: every declared binary is present"
|
|
|
|
swap-forge --help | grep -q "Swapfile size" || fail "--help does not describe the tool"
|
|
echo "ok: --help"
|
|
|
|
set +e
|
|
swap-forge >/dev/null 2>&1
|
|
status=$?
|
|
set -e
|
|
[ "${status}" -eq 2 ] || fail "a missing size exited ${status}, expected 2"
|
|
echo "ok: a missing size exits 2"
|
|
|
|
set +e
|
|
output="$(swap-forge 64T 2>&1)"
|
|
status=$?
|
|
set -e
|
|
[ "${status}" -eq 1 ] || fail "an invalid size exited ${status}, expected 1"
|
|
echo "${output}" | grep -q "Invalid size format" || fail "invalid size message wrong"
|
|
if echo "${output}" | grep -q "Traceback"; then
|
|
fail "an invalid size produced a traceback"
|
|
fi
|
|
echo "ok: an invalid size exits 1 without a traceback"
|
|
|
|
printf 'UUID=deadbeef / ext4 defaults 0 1\n' >/etc/fstab
|
|
cp /etc/fstab /tmp/fstab.before
|
|
|
|
set +e
|
|
output="$(swap-forge 8M 2>&1)"
|
|
status=$?
|
|
set -e
|
|
[ "${status}" -eq 1 ] || fail "the blocked activation exited ${status}, expected 1"
|
|
echo "${output}" | grep -q "Creating 8 MiB swapfile" || fail "the creation banner is missing"
|
|
echo "${output}" | grep -qE "'(mkswap|swapon)' failed with exit code" || fail "the blocked activation was not reported cleanly"
|
|
if echo "${output}" | grep -q "Traceback"; then
|
|
fail "the blocked activation produced a traceback"
|
|
fi
|
|
echo "ok: real fallocate and chmod ran; the blocked activation exits 1 cleanly"
|
|
|
|
[ -f /swapfile ] || fail "/swapfile was not created"
|
|
size="$(stat -c %s /swapfile)"
|
|
[ "${size}" -eq 8388608 ] || fail "/swapfile is ${size} bytes, expected 8388608"
|
|
echo "ok: /swapfile exists with exactly the requested size"
|
|
|
|
cmp -s /etc/fstab /tmp/fstab.before || fail "/etc/fstab changed although activation failed"
|
|
echo "ok: /etc/fstab is untouched when activation fails"
|
|
|
|
set +e
|
|
output="$(swap-forge 8M 2>&1)"
|
|
status=$?
|
|
set -e
|
|
[ "${status}" -eq 0 ] || fail "the idempotent rerun exited ${status}, expected 0"
|
|
echo "${output}" | grep -q "already exists with correct size (8 MiB). Skipping." \
|
|
|| fail "an existing swapfile of the right size was not detected"
|
|
echo "ok: a rerun with the same size is a no-op - the idempotency the role relies on"
|
|
|
|
set +e
|
|
output="$(swap-forge 12M 2>&1)"
|
|
status=$?
|
|
set -e
|
|
echo "${output}" | grep -q "has size 8 MiB, expected 12 MiB. Recreating..." \
|
|
|| fail "a size change did not trigger a recreation"
|
|
size="$(stat -c %s /swapfile)"
|
|
[ "${size}" -eq 12582912 ] || fail "/swapfile is ${size} bytes after resize, expected 12582912"
|
|
echo "ok: a different size really removes and recreates the swapfile"
|
|
|
|
cmp -s /etc/fstab /tmp/fstab.before || fail "/etc/fstab changed during the resize cycle"
|
|
echo "ok: /etc/fstab survived the resize cycle byte for byte"
|
|
|
|
set +e
|
|
output="$(env PATH=/nonexistent-bin "${SWAPFORGE}" 8M 2>&1)"
|
|
status=$?
|
|
set -e
|
|
[ "${status}" -eq 127 ] || fail "missing findmnt exited ${status}, expected 127"
|
|
echo "${output}" | grep -q "required command 'findmnt' is not installed" || fail "missing findmnt message wrong"
|
|
echo "ok: missing findmnt exits 127 with a clean message"
|
|
|
|
echo "ALL E2E CHECKS PASSED"
|