How do I set up a swap file?

I’ve tried adding this to my configuration.nix:

swapDevices = [ { device = "/swapfile"; size = 2048; } ];

But it gives me the error:

A dependency job for swap.target failed. See 'journalctl -xe' for details.
warning: the following units failed: swapfile.swap

● swapfile.swap - /swapfile
   Loaded: loaded (/etc/fstab; generated)
   Active: failed (Result: exit-code) since Sun 2020-07-26 11:05:53 EDT; 24ms ago
     What: /swapfile
     Docs: man:fstab(5)
           man:systemd-fstab-generator(8)
       IP: 0B in, 0B out
      CPU: 2ms

Jul 26 11:05:53 corpus-db-nixos swapon[18433]: swapon: /swapfile: swapon failed: Invalid argument
Jul 26 11:05:53 corpus-db-nixos systemd[1]: Activating swap /swapfile...
Jul 26 11:05:53 corpus-db-nixos systemd[1]: swapfile.swap: Swap process exited, code=exited, status=255/EXCEPTION
Jul 26 11:05:53 corpus-db-nixos systemd[1]: swapfile.swap: Failed with result 'exit-code'.
Jul 26 11:05:53 corpus-db-nixos systemd[1]: Failed to activate swap /swapfile.
warning: error(s) occurred while switching to the new configuration

And indeed when I try to run sudo swapon /swapfile I get: swapon: /swapfile: swapon failed: Invalid argument.

How should I be setting up a swap file? If it makes a difference, I’m trying to do this on a VPS.

1 Like

I set up partitions before nixos-install and then it gets generated for me inside hardware-configuration.nix:

  swapDevices =
    [ { device = "/dev/disk/by-uuid/some-redacted-uuid"; }
    ];

(Maybe there are some other ways, too.)

There is a bug in the NixOS module, see Swap file created by the module not compatible with Linux 5.7 · Issue #91986 · NixOS/nixpkgs · GitHub.

1 Like

I can’t seem to get that workaround working for me, either. I tried removing the old swap file, creating a new one using dd, fixing the permissions, and restarting the service, but I’m still getting swapon failed: Invalid argument, no matter what. Furthermore, in journalctl -xe I see kernel: swapon: swapfile has holes.

Forgot to mention, the size you use for the dd command needs to exactly match the size set in the module, otherwise the module will call fallocate again:

Oh, I didn’t realize it wasn’t a normal swap partition in the OP.

NixOS 20.09
Linux Kernel 5.11.1

For those of you who are still having this problem, the following has worked well for me:

The proper way to create a Swap file on BTRFS is Btrfs - ArchWiki, so I set up a oneshot systemd service to create this file for me that is wanted by the NixOS swap-*.service defined in my hardware-configuration.nix file.

This will create the non-compressed, CoW disabled file in my /swap BTRFS subvolume. Then I leave the allocation and enabling of the swap file to NixOS in hardware-configuration.nix

configuration.nix

  systemd.services = {
    create-swapfile = {
      serviceConfig.Type = "oneshot";
      wantedBy = [ "swap-swapfile.swap" ];
      script = ''
        ${pkgs.coreutils}/bin/truncate -s 0 /swap/swapfile
        ${pkgs.e2fsprogs}/bin/chattr +C /swap/swapfile
        ${pkgs.btrfs-progs}/bin/btrfs property set /swap/swapfile compression none
      '';
    };
  };

hardware-configuration.nix

  fileSystems."/swap" = {
    device = "/dev/disk/by-uuid/545353bc-238b-4a5b-bb48-94832272e0b2";
    fsType = "btrfs";
    options = [ "subvol=swap" "compress=lzo" "noatime" ]; # Note these options effect the entire BTRFS filesystem and not just this volume, with the exception of `"subvol=swap"`, the other options are repeated in my other `fileSystem` mounts
  };

  swapDevices = [{
    device = "/swap/swapfile";
    size = (1024 * 16) + (1024 * 2); # RAM size + 2 GB
  }];

As you can see, my nixops deploy succeeds and upon verification the file is enabled and allocated according to my definition.

7 Likes

Thanks for this example! Worked for me with some small tweaks. My goal was enabling hibernation with BTRFS and LUKS.

First needed to run sudo btrfs subvolume create /swap. Then made the following tweaks:

  1. Seems ${pkgs.btrfs-progs}/bin/btrfs property set /swap/swapfile compression none is no longer needed, and in fact it errored out doing this, so I just deleted the line
  2. Skipped adding fileSystems."/swap"
  3. Noticed the create-swapfile service fails once the swap file exists, so added a check into the script:
swapfile="/swap/swapfile"
if [[ -f "$swapfile" ]]; then
  echo "Swap file $swapfile already exists, taking no action"
else
  echo "Setting up swap file $swapfile"
  ${pkgs.coreutils}/bin/truncate -s 0 "$swapfile"
  ${pkgs.e2fsprogs}/bin/chattr +C "$swapfile"
fi

To hibernate to the file, I followed the Arch wiki for calculating the resume_offset. I created a script to automate the end value.

#!/usr/bin/env bash

tmp_dir="$(mktemp -d)"
btrfs_map_physical="$tmp_dir/btrfs_map_physical"
gcc -O2 -o "$btrfs_map_physical" scripts/btrfs_map_physical.c
physical_offset="$(sudo "$btrfs_map_physical" /swap/swapfile | cut -f9 | head -n2 | tail -n1)"
pagesize="$(getconf PAGESIZE)"
resume_offset=$((physical_offset / pagesize))
rm -r "$tmp_dir"
echo "resume_offset=$resume_offset"

Finally I added the following to my hardware-configuration.nix:
boot.kernelParams = ["resume_offset=95450174"];
boot.resumeDevice = "/dev/disk/by-uuid/${rootUUID}";

Final note, this does not seem to work with boot.initrd.systemd.enable = true;.

1 Like