My secondary laptop.
persephone is a 13th gen Intel Framework 13 with 64GiB of RAM. It was my primary laptop when I bought it, but once I took ownership of athena after leaving the job I was using it for, I found myself using that laptop more. I'm still rather fond of the Framework though, and I think it has a lot going for it.
{
config,
lib,
inputs,
pkgs,
utils,
...
}:
{
imports = [
"${inputs.hardware}/framework/13-inch/13th-gen-intel"
./hardware-configuration.nix
];
networking.hostName = "persephone";
services.openssh.enable = true;
system.stateVersion = "23.05";
<<nixos-config>>
_class = "nixos";
}The state version reflects that persephone is I think my oldest still running NixOS machine, as I bought it a few months after I started using Nix. Maybe the Raspberry Pis are slightly older, not sure.
mjm.desktop.enable = true; mjm.desktop.niri.enable = true;
Since it's a workstation, it uses my own desktop module with all of the desktop configuration. I'm using niri on all of my workstations.
mjm.secureboot.enable = true;
persephone uses my secureboot module to set up Lanzaboote, which enables SecureBoot and measured boot on NixOS systems. I use this to avoid needing to unlock the encrypted filesystem on boot: as long as the boot configuration hasn't been tampered with, it will be unlocked automatically.
boot.kernelPackages = pkgs.linuxPackages_latest;
Workstations generally benefit from having the latest kernel changes.
services.fwupd.enable = true;
This machine has some hardware whose firmware can be managed using fwupd, so I enable that.
services.hardware.bolt.enable = true;
This option enables managing access to Thunderbolt devices, which sometimes require approval before they can actually be used.
services.kmscon.config.font-dpi = 144;
persephone has a reasonably high resolution display, so I've configured the console DPI to be a bit higher than default so it's actually readable.
nixpkgs.config.permittedInsecurePackages = [ "electron-39.8.10" ];
My workstations install Bitwarden Desktop to use with my Vaultwarden install. Unfortunately, Bitwarden Desktop is using an end-of-life version of Electron, so I have to allow that insecure package. This nixpkgs config key doesn't merge well across multiple modules, so I keep a single list at the host level instead.
mjm.state = {
enablePreservation = true;
persistDir = "/persist";
directories = [
{
directory = "/nix";
inInitrd = true;
}
"/home"
];
};persephone is my only workstation that using an erase-your-darlings setup. Persistent data is stored under /persist and bind-mounted into place. Only data under /home and /var will be preserved between boots.
boot.initrd.systemd.services.rollback-root = {
description = "Rollback Root Filesystem to Blank Snapshot";
wantedBy = [ "initrd.target" ];
after = [ "${utils.escapeSystemdPath "/persist-tmp"}.mount" ];
requires = [ "${utils.escapeSystemdPath "/persist-tmp"}.mount" ];
before = [ "sysroot.mount" ];
unitConfig.DefaultDependencies = false;
serviceConfig = {
Type = "oneshot";
ExecStart = [
"${config.boot.bcachefs.package}/bin/bcachefs subvolume delete /persist-tmp/@root"
"${config.boot.bcachefs.package}/bin/bcachefs subvolume create /persist-tmp/@root"
];
};
};This systemd service will delete and recreate the @root subvolume. My fileystem is mounted a little strangely to support the root being erased like this every boot. It's mounted at / with X-mount.subdir=@root and at /persist normally. The / mount is essentially a bind mount to the @root subvolume, but the bind mount can't be from the /persist mount because that would require / to already be mounted. X-mount.subdir uses namespace trickery to basically do a bind mount to a place you can't see.
That all works fine, but to manipulate the @root subvolume like this, the actual root of the filesystem needs to be mounted somewhere. At some point later in stage1, it will be mounted at /sysroot/persist, but this service needs to run before even /sysroot is mounted. So I'll need to mount it somewhere temporarily in the initrd root. I've chosen /persist-tmp as that location.
boot.initrd.systemd.mounts = [
{
what = "/dev/disk/by-partlabel/persist";
where = "/persist-tmp";
type = "bcachefs";
after = [ "unlock-bcachefs-${utils.escapeSystemdPath "/"}.service" ];
requires = [ "unlock-bcachefs-${utils.escapeSystemdPath "/"}.service" ];
options = "casefold_disabled";
}
];That /persist-tmp filesystem is mounted with a systemd mount unit in the initrd. Doing it this way, instead of in the fileSystems option, makes it easy to mount it somewhere that won't be visible after switching to stage2. The mount is ordered to run after the service that unlocks the encryption on the filesystem.
With all of these pieces in place, the sequence of steps here is:
1. Unlock the disk
2. Mount the FS at /persist-tmp
3. Delete and recreate the @root subvolume
4. Mount the @root subvolume at /sysroot
5. Mount the FS at /sysroot/persist
There are still some tweaks needed to get unlocking the disk to work correctly:
boot.initrd.systemd.services.unlock-bcachefs-persist.enable = false;
The quirky mount setup I'm using produces two entries in config.fileSystems for the same device. This makes the NixOS bcachefs module generate two systemd services for unlocking it: one for each mountpoint. That's maybe something that would be good to fix, but for now, I'm just disabling one of them. It makes sense to disable the one for /persist, since the mount will already require / to be mounted first, which will then depend on the remaining unlock service, so no new dependencies are needed.
boot.initrd.systemd.services.unlock-bcachefs-- = {
script = lib.mkForce ''
${config.boot.bcachefs.package}/bin/bcachefs unlock "/dev/disk/by-partlabel/persist"
'';
serviceConfig.ImportCredential = "cryptsetup.passphrase";
};I'm overriding the command used to unlock the disk, because I want to use the systemd credential support that is present in "bcachefs unlock" as of v1.38.8. It will call systemd-ask-password with --credential=cryptsetup.passphrase, so I can just add an encrypted credential to the initrd with the disk's passphrase, and it will unlock without prompting.
Hopefully this particular customization won't be needed in the long-term, and NixOS's bcachefs module will just do this.
boot.initrd.systemd.contents."/etc/credstore.encrypted/cryptsetup.passphrase".text = '' DHzAexF2RZGcSwvqCLwg/iAAAAABAAAADAAAABAAAACvbX9O/bszthuMLC4AAAAAhQAAAAAAAAALACM A8AAAACAAAAAAngAgIbJNx/dwOl7VfRjGfUeAacQc/2O2f6aTFcdOqXWXiPoAEAR/FK4w/Ln/NJ26Rn LQ5OakJGOckeWzEz+NEbC6MrvS1/ses8t7SOzttWgSp1JYOGsaIZItP/qBJR9aMLE9SnO5Q47OjtyeH FTbBDxKi5X9FdZBb5Z1QjS1tCxSftAcUDpdSOGByv9q+gd0uDK2hB6OfwoQnpKv5D3WAE4ACAALAAAE EgAgADlCxCFMqhN4xgBpGVOrQ+2Po0GkltPvipl+KDFRd40AEAAgiXUJaLFYPSRqPN+VY7tbMisx/dl K4iHBPdrNXY+d3zMAOULEIUyqE3jGAGkZU6tD7Y+jQaSW0++KmX4oMVF3jQAAAAB4VhGagsEpDRXKmw GcdukJJI7SkzSg2ejwFJPdtUuwvSKZQdkOT586idfngYxmsephW4vw4ZxTBE9cykxnQEC47XDepwO9D UHJzFEGCtWbuVKkuL2eiWW6GA== '';
This cryptsetup.passphrase secret is encrypted via the TPM and locked to PCRs 0, 2, and 7. The "bcachefs unlock" will read it if present and try to use it to unlock the disk.
{
home.sessionVariables.DIPPY_EVAL_CONCURRENCY = "4";
}Just one small tweak for the home-manager config here. By default, my deploy tool, dippy, only evaluates two jobs at once, to avoid using too much RAM. Since persephone has quite a bit of RAM, I configure it to evaluate four jobs at once.
text/gemini;lang=en-USThis content has been proxied by September (UNKNO).