#!/usr/bin/env bash
# Enlarge host swap on a GitHub-hosted runner. Replaces the runner's
# default ~4 GB swap with a fixed-size swapfile so transient memory
# spikes (e.g. peertube plugin install, issue #162) don't trip the host
# OOM-killer, WITHOUT starving the rest of the job of disk space.
#
# Uses / when containerd already lives on /mnt (relocate_docker_dataroot.sh
# runs first), otherwise prefers /mnt when it is a separate partition (classic ubuntu-latest
# layout with /dev/sdb mounted at /mnt). On current public runners
# there is no separate /mnt partition — everything lives on /, so we
# place the swapfile there. The swap size is a hard constant, never a
# "free minus buffer" computation: a previous version consumed ~99 GB
# on a 145 GB root, leaving so little headroom that later Docker layer
# writes (Playwright test image pull) failed with ENOSPC. See #162.
#
# Usage: enlarge_swap.sh [size-gb]
# Default: 16 GB (empirically sufficient for the peertube spike).
#
# Debug toggle: set SKIP_SWAP=1 to leave the runner's default swap
# untouched (A/B probe for issues caused by this script itself).

set -euo pipefail

HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DUMP="${HERE}/dump_state.sh"

if [ "${SKIP_SWAP:-0}" = "1" ]; then
	echo "SKIP_SWAP=1 — leaving default runner swap in place."
	"$DUMP" "skip-swap (SKIP_SWAP=1)"
	exit 0
fi

SWAP_GB="${1:-16}"
MIN_FREE_GB=$((SWAP_GB + 4)) # need the target size plus a little slack

"$DUMP" "before swap resize"

sudo swapoff -a || true # nocheck: shell-or-true -- grandfathered: worked in practice; TODO: sharpen to catch only the exact tolerated error
sudo rm -f /mnt/swapfile /swapfile

if mountpoint -q /mnt 2>/dev/null; then
	mnt_free=$(df --output=avail -B1G /mnt | tail -n1 | tr -d ' ')
else
	mnt_free=0
fi
root_free=$(df --output=avail -B1G / | tail -n1 | tr -d ' ')

echo "Target swap: ${SWAP_GB}G. Free: / ${root_free}G, /mnt ${mnt_free}G (separate mount: $(mountpoint -q /mnt 2>/dev/null && echo yes || echo no))"

storage_on_mnt=no
if mountpoint -q /mnt 2>/dev/null && containerd_dev="$(stat -c %d /var/lib/containerd 2>/dev/null)" &&
	[ "$containerd_dev" = "$(stat -c %d /mnt)" ]; then
	storage_on_mnt=yes
fi

if [ "$storage_on_mnt" = yes ] && [ "$root_free" -ge "$MIN_FREE_GB" ]; then
	target=/swapfile
	chosen="/ (containerd and Docker live on /mnt)"
elif [ "$mnt_free" -ge "$MIN_FREE_GB" ]; then
	target=/mnt/swapfile
	chosen="/mnt (separate partition)"
elif [ "$root_free" -ge "$MIN_FREE_GB" ]; then
	target=/swapfile
	chosen="/ (no separate /mnt)"
else
	echo "Neither / nor /mnt has ${MIN_FREE_GB} GB free for a ${SWAP_GB} GB swapfile" >&2
	df -h / /mnt >&2
	exit 1
fi

echo "Chose ${chosen}: creating ${SWAP_GB}G swapfile at ${target}"

sudo fallocate -l "${SWAP_GB}G" "$target"
sudo chmod 600 "$target"
sudo mkswap "$target"
sudo swapon "$target"

"$DUMP" "after swap resize → ${SWAP_GB}G at ${target}"
