NixOS On the Framework Laptop

- (8 min read)

I recently purchased a Framework laptop, and since I've been hearing great things about NixOS recently, I decided to give it a go. This post summarizes the steps I took to get everything set up (most importantly suspend-then-hibernate functionality) and some thoughts on the whole situation.

Overall I'm extremely happy with the setup. So far the only issue I've run into with the Framework is the battery life (specifically the battery drain overnight), but getting hibernate setup has removed that issue. NixOS is a complete game-changer and I most certainly can't go back to anything else.

Others have written articles on the same topic, so check those out as well, because I based a lot of my setup on them:

Setting up the Framework Laptop

I ordered the Framework Laptop DIY edition, with 32GB memory, 1TB space, and an i5 processor. Installing the components was easy, and from my understanding even easier now since the wifi-card comes installed.

There was no OS by default, and the only trick to getting it to boot from a USB drive was turning off secure boot. (I haven't purchased a new laptop in so long, I forgot that was a thing you have to do with new laptops). I demoed various OSes via USB and played around with the keyboard, sound and webcam. Also tried out various peripherals with the ports.

There isn't much to write about in this section, since it all Just Worked. Opened it up, marveled at how easy it was to open up, installed my components, and that is it. A solid, functional laptop.

Installing NixOS

Installing NixOS turned out to be just as painless. I used the normal NixOS image, and followed the steps in the NixOS Manual. One of the blog posts above mentions creating a custom image with an updated Linux kernel to get Wifi working during install, but I just had a USB-C ethernet adapter which worked.

I went with an LVM on LUKS setup, and everything Just Worked. Once installed, the only Framework-specific setup I had to do was setting the Linux kernel to the latest:

  # Need the latest kernel for WiFi support on Framework
  boot.kernelPackages = pkgs.linuxPackages_latest;

For battery management I found some TLP settings on the forums, though I haven't done my own measurements or testing really. At some point I'd like to have time to really dive into how to optimize Linux battery life, and what tools there are to measure things, but for now this seems to suffice:

  services.tlp = {
    enable = true;
    settings = {
      CPU_BOOST_ON_BAT = 0;
      CPU_SCALING_GOVERNOR_ON_BATTERY = "powersave";
      START_CHARGE_THRESH_BAT0 = 90;
      STOP_CHARGE_THRESH_BAT0 = 97;
      RUNTIME_PM_ON_BAT = "auto";
    };
  };

At this point, Wifi was working, Bluetooth was working, and in general, everything Just Worked. The one issue -- and it was certainly an issue -- was the battery drain of ~20% overnight. Unfortunately this seems to be a known issue with the Framework laptop, and will hopefully be fixed via firmware updates. But I decided to just figure out how to get hibernate working in the meantime.

Setting up Hibernate

For my hibernation setup I did not have a separate swap partition, which unfortunately a lot of the NixOS wikis and guides assume, but getting things working with a swapfile wasn't too tricky.

I tried to figure out how to get a swapfile set up via the configuration.nix, but gave up pretty quickly. Setting a swapfile up manually was easy, and I just followed the ArchLinux wiki on swap file creation.

It took a few tries to get the NixOS configuration right for actually using the swapfile during the systemctl hibernate call. But that was primarily due to my lack of familiarity with NixOS, and not actually anything complicated.

With a swapfile at /swapfile, I followed these steps:

  1. Grab the swap_device via: (this is also the same device in the hardware-configuration.nix under fileSystems.device, at least for me)
findmnt -no UUID -T [swapfile](/swapfile)
  1. Grab the swap_file_offset via:
filefrag -v /swapfile | awk '$1=="0:" {print substr($4, 1, length($4)-2)}'

Note: The above commands come from the ArchLinux wiki page on Suspend and Hibernate.

Once those two values are found, I added them along with the following configuration to my hardware-configuration.nix:

  # Set up deep sleep + hibernation
  swapDevices = [
    { device = "/swapfile"; }
  ];
  # Partition swapfile is on (after LUKS decryption)
  boot.resumeDevice = "/dev/disk/by-uuid/05032ea4-beb1-4237-ac66-ab0bf79f39a7";
  # Resume Offset is offset of swapfile
  # https://wiki.archlinux.org/title/Power_management/Suspend_and_hibernate#Hibernation_into_swap_file
  boot.kernelParams = [ "mem_sleep_default=deep" "resume_offset=80424960" ];

At this point, systemctl hibernate worked, so it was then just getting everything set up to use suspend-then-hibernate (which suspends initially, but hibernates long-term, giving what I hope to be the best of both worlds!)

That ended up taking a couple of tries as well, specifically I was just missing the systemd HibernateDelaySec setting. But all it actually takes are the following lines in my configuration.nix:

  # Suspend-then-hibernate everywhere
  services.logind = {
    lidSwitch = "suspend-then-hibernate";
    extraConfig = ''
      HandlePowerKey=suspend-then-hibernate
      IdleAction=suspend-then-hibernate
      IdleActionSec=2m
    '';
  };
  systemd.sleep.extraConfig = "HibernateDelaySec=2h";

At this point, suspend-then-hibernate worked, and my laptop would suspend for 2 hours before going into hibernation. Battery life is now preserved overnight!

Conclusions

I'm really happy with the Framework laptop so far. I like the keyboard, and enjoy that it has so much travel. The webcam is great and I haven't had any issues with video calls so far. The speakers are not great, but bluetooth to my BLE speakers works just fine. The battery life is not incredible, but its also not terrible as I keep reading about. As I type this, it has an estimated 9hrs left at 88%, which is totally fine for my purposes. I've had no issues with any peripherals so far, including multiple USB-C dongles I plug in for my workspace. Really the only thing preventing me from loving my Framework is that damn battery drain overnight, but since that has been fixed with the hibernate functionality I'm expecting I'll fall in love soon.

NixOS, on the other hand, I absolutely love. I haven't done anything too fancy with it, so maybe I'll run into one of the rough edges soon and change my opinion. But so far it is a game-changer for me. Hell I've started adding shell.nix to my various projects to avoid polluting my global installed packages just because. It truly lets me keep my system clean, which I'm a huge fan of. I don't really know Nix all too well yet, but it's intuitive for simple configurations.

Contrary to a lot of what I've read about NixOS, I've so far found the documentation to be pretty solid. Not ArchLinux levels of incredible, but definitely far better than my workplace. But maybe I'm just too used to atrocious documentation to really complain! I have had no real issues diving into various Nix packages to figure out what they do, and the packages I've used have all had some really nice functionality already configured that you can just hook into with some Nix configs.

So far I haven't done anything too complicated with NixOS, and have stayed mostly on the beaten path. The most complicated thing I have setup is BorgBackup saving to a remote backup via a daily systemd timer, and it was generally straightforward.[1] The NixOS online option search is extremely handy for figuring out the parameters a package takes. Perhaps if I start going crazy with my system I might run into more issues, but I generally like to keep things simple.

For the instances where I can't figure out how to do it "properly" with nix, I pretty much just do it as I would do it in a different Linux distribution. For example, I wanted my vim configuration and plugins stored and setup via Nix, but couldn't figure it out, so I just did it manually and its fine. I did the same thing with the swapfile creation I talked about above. I wonder if the number of complaints people have about NixOS is trying to do everything with it, which I agree would be great, but its not hard to just keep things moving.

All in all, this has been a set of decisions I'm very happy with. I can highly recommend the Framework laptop with NixOS, and I expect this to be my daily driver for hopefully a very long time. In theory this will be my Ship of Theseus laptop.

--

[1] I did have to add the following to my backup job so it would wait for the network device to be up and not fail immediately. But this was the only thing I had to figure out and not in the docs

preHook = ''
    until /run/wrappers/bin/ping google.com -c1 -q >/dev/null; do :; done
'';