Created
March 7, 2026 11:26
-
-
Save shift/6e2c668390ff2cb4319932a7332396ad to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { config, lib, pkgs, ... }: | |
| let | |
| cfg = config.services.swap-sentinel; | |
| # Naming it 'swap-sentinel' to ensure uniqueness. | |
| storagePath = "/var/lib/swap-sentinel"; | |
| in { | |
| options.services.swap-sentinel = { | |
| enable = lib.mkEnableOption "Dynamic Btrfs swapfile manager"; | |
| minFreeMB = lib.mkOption { | |
| type = lib.types.int; | |
| default = 512; | |
| description = "Threshold of free swap in MB before creating a new file."; | |
| }; | |
| chunkSizeGB = lib.mkOption { | |
| type = lib.types.int; | |
| default = 1; | |
| description = "Size of each dynamic swap chunk."; | |
| }; | |
| }; | |
| config = lib.mkIf cfg.enable { | |
| systemd.services.swap-sentinel = { | |
| description = "Dynamic Swap Sentinel for Btrfs"; | |
| after = [ "local-fs.target" ]; | |
| wantedBy = [ "multi-user.target" ]; | |
| path = with pkgs; [ | |
| bash | |
| gawk | |
| procps | |
| util-linux | |
| coreutils | |
| btrfs-progs | |
| ]; | |
| serviceConfig = { | |
| Type = "simple"; | |
| ExecStartPre = "${pkgs.bash}/bin/bash -c 'mkdir -p ${storagePath} && chattr +C ${storagePath} || true'"; | |
| ExecStart = let | |
| script = pkgs.writeShellScript "swap-sentinel-run" '' | |
| set -euo pipefail | |
| CHUNK_SIZE_MB=$(( ${toString cfg.chunkSizeGB} * 1024 )) | |
| THRESHOLD=${toString cfg.minFreeMB} | |
| echo "Sentinel started. Monitoring swap on ${storagePath}..." | |
| while true; do | |
| # Get current free swap in MB | |
| FREE_SWAP=$(free -m | awk '/^Swap:/ {print $4}') | |
| if [ "$FREE_SWAP" -lt "$THRESHOLD" ]; then | |
| TIMESTAMP=$(date +%s) | |
| SWAPFILE="${storagePath}/extra-$TIMESTAMP.swap" | |
| echo "Low swap ($FREE_SWAP MB). Creating $SWAPFILE..." | |
| # Btrfs specific swapfile creation | |
| truncate -s 0 "$SWAPFILE" | |
| chattr +C "$SWAPFILE" | |
| fallocate -l "$(( ${toString cfg.chunkSizeGB} ))G" "$SWAPFILE" | |
| chmod 600 "$SWAPFILE" | |
| mkswap "$SWAPFILE" | |
| swapon "$SWAPFILE" | |
| elif [ "$FREE_SWAP" -gt "$(( CHUNK_SIZE_MB + THRESHOLD + 512 ))" ]; then | |
| # Check if we have dynamic files to remove (ignore the 16GB hibernation file) | |
| OLDEST_DYNAMIC=$(ls -t ${storagePath}/extra-*.swap 2>/dev/null | tail -n 1 || true) | |
| if [ -n "$OLDEST_DYNAMIC" ]; then | |
| echo "High swap headroom ($FREE_SWAP MB). Removing $OLDEST_DYNAMIC..." | |
| swapoff "$OLDEST_DYNAMIC" | |
| rm "$OLDEST_DYNAMIC" | |
| fi | |
| fi | |
| sleep 30 | |
| done | |
| ''; | |
| in "${script}"; | |
| Restart = "always"; | |
| }; | |
| }; | |
| }; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment