My primary laptop.
athena is a 2023 16" MacBook Pro with an M2 Max. It used to be a work laptop, but I was allowed to keep it when I left that job. After it was wiped, I installed NixOS on it almost immediately, using the nixos-apple-silicon project.
I lucked out here, as the M2 is currently the most powerful Apple Silicon processor that has decent Linux support. It's a pretty beastly machine aside from the 32GB of RAM, which feels a bit anemic when trying to do big Nix builds.
This is probably the workstation I use the most often these days.
{
lib,
pkgs,
...
}:
let
<<make-subvolume-mount>>
in
{
networking.hostName = "athena";
nixpkgs.hostPlatform = "aarch64-linux";
system.stateVersion = "25.05";
<<nixos-config>>
_class = "nixos";
}boot.initrd.availableKernelModules = [ "usb_storage" "usbhid" "sdhci_pci" "rtc_macsmc" "spmi_apple_controller" "apple_nvmem_spmi" ]; boot.initrd.kernelModules = [ "dm-snapshot" ];
Most of this came from nixos-generate-config. rtc_macsmc and the two below it are there to get support for the real-time clock as early as possible, since before that, the time will be incorrect. This is particularly important for auto-login, as an invalid time can cause keys in the kernel keyring to be expired prematurely.
hardware.enableRedistributableFirmware = true;
I'm not sure if this enables any relevant firmware for this machine, but it doesn't hurt.
boot.initrd.luks.devices.cryptroot = {
device = "/dev/disk/by-partuuid/8a1cdb50-eaca-460e-8348-47d58050f9f4";
preLVM = true;
};The partition on the disk that is used for Linux is set up as btrfs-on-LVM-on-LUKS. The LUKS container needs to be unlocked on boot, and since it holds the root FS, that needs to be done in stage 1. Since the LUKS container contains an LVM volume group, the preLVM option must be set to true, so unlocking happens before LVM is set up.
mkSubvolumeMount = name: {
device = "/dev/lvm/nixos";
fsType = "btrfs";
options = [
"subvol=${name}"
"compress=zstd"
"relatime"
];
};fileSystems."/" = mkSubvolumeMount "root"; fileSystems."/nix" = mkSubvolumeMount "nix"; fileSystems."/home" = mkSubvolumeMount "home";
This machine uses three btrfs subvolumes for /, /nix, and /home. / and /nix actually get mounted in stage 1, but NixOS has special treatment for these mountpoints and a few others and marks them as neededForBoot automatically.
fileSystems."/boot" = {
device = "/dev/disk/by-partuuid/47ecfec3-26aa-4d4c-a7e2-e76e6fddd09d";
fsType = "vfat";
options = [
"fmask=0022"
"dmask=0022"
];
};
boot.loader.systemd-boot.enable = true;My systems use systemd-boot wherever possible, as it's a simple reliable bootloader for UEFI. athena is my only workstation that can't use Lanzaboote for SecureBoot, because it lacks a traditional TPM. Instead, it has Apple's Secure Enclave, which doesn't currently have Linux support.
Either way, I need the EFI system partition mounted at /boot.
swapDevices = [
{
device = "/var/lib/swapfile";
size = 32 * 1024;
}
];
boot.zswap.enable = true;Everybody needs a little swap. 32G to be specific. And since there's actual swap space on disk, I enable zswap.
mjm.desktop.enable = true; mjm.desktop.niri.enable = true; mjm.desktop.autoLogin.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.
I'm also enabling auto-login, which for my machines means I'm unlocking the disk using a PIN of some kind (FIDO2 in this case, since athena doesn't have a TPM) and then using that same PIN to also unlock gnome-keyring after automatically logging in at the display manager.
hardware.asahi.enable = true;
Since this is an Apple Silicon machine, Asahi hardware support needs to be enabled. The Apple silicon support modules are imported in my nixos/base module, but disabled by default.
services.kmscon.config.font-size = 32;
athena has a pretty high resolution display, so I've configured the console to be a bit bigger than the default so it's actually readable.
environment.systemPackages = [ pkgs.tuxvdmtool ];
tuxvdmtool is a special tool from Asahi Linux. I think its main purpose is getting a serial console to an Apple Silicon device, but that's not why I have it installed. There's a bug around USB devices and suspend on Asahi Linux, where the devices don't reconnect when waking. This is annoying for the YubiKey that I always have connected to this laptop for SSH. With this tool, I can run "run0 tuxvdmtool disconnect" and any USB devices will reconnect. That's annoying, but less annoying than physically pulling out the very small YubiKey.
nix.settings.max-jobs = 4;
By default, Nix will run as many concurrent builds as you have CPU cores. athena has 12 cores, but only 32GiB of RAM. Without extra configuration, that makes it very easy for a Nix build to exhaust RAM by running too many jobs at once, so I've configured it to only run four builds at a time.
Even if I had more RAM, it would probably still be a good idea to limit concurrent Nix builds more than the default. Each build can potentially use all of the cores in the system (unless you configure that limit to be lower), so with the default settings you can end up with pretty high resource contention. You're generally better off reducing these a bit, so that you have some oversubscription (in case you have builds that don't use many resources) but not nearly as much as there is by default.
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.
specialisation.plasma.configuration = {
mjm.desktop.niri.enable = lib.mkForce false;
mjm.desktop.plasma.enable = true;
programs.ssh.startAgent = true;
};I keep around a specialisation of the system that uses Plasma 6 instead of niri. The SSH agent option is because the Niri setup uses gnome-keyring, which includes an SSH agent as part of it. Plasma doesn't do something like this by default, so I need to enable starting the normal SSH agent.
{
<<home-config>>
}athena has a few small customizations to my default home-manager config.
mjm.homelab.enableYubiKey = true;
I have some special SSH configuration for logging into my home servers, and by default that expects to use a key from the TPM via ssh-tpm-agent. Since athena doesn't have a TPM, I use a key on a YubiKey instead. This option enables that change in configuration.
programs.ssh.settings."*" = {
AddKeysToAgent = "yes";
ControlMaster = "auto";
ControlPersist = "5m";
ControlPath = "\${XDG_RUNTIME_DIR}/ssh-%C.sock";
};This SSH configuration largely exists to mask some of inconvenience of using a YubiKey for SSH instead of a TPM-based key. Adding the key to the agent prevents needing to enter a PIN every time, but a touch of the key is still required on every connection. I'm quite prone to just sitting there waiting for SSH to connect without realizing that the YubiKey is flashing waiting for a touch. So persisting connections and reusing them can help alleviate that a bit, at least when making multiple connections to the same host.
text/gemini;lang=en-USThis content has been proxied by September (UNKNO).