The module wrote the configs and the timers, but nothing creates the .snapshots subvolumes they need, so snapper-timeline failed every hour with errno:2 and there was never a single snapshot to roll back to. Removing it rather than fixing it: rollbacks of / are already covered by NixOS generations, and snapshots of /home would have shared a disk with /home. That answers "I deleted the wrong file" but not "the disk is gone", and the second is the failure that actually occurred here.
164 lines
7.1 KiB
Nix
164 lines
7.1 KiB
Nix
# ABOUTME: Host configuration for the desktop: 512G system disk, 1T disk for /home.
|
|
# ABOUTME: Disk layout comes from modules/disk.nix, nothing is generated on the machine.
|
|
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
{
|
|
# Read off the machine with `ls -l /dev/disk/by-id/`: the 512G XPG is the
|
|
# system disk and carries /home as well. disko-install still overrides it with
|
|
# `--disk main <path>`, so check that against lsblk before installing — by-id
|
|
# names follow the drive, not the slot, and a replaced disk changes them.
|
|
#
|
|
# The 1T Crucial P1 (CT1000P1SSD8_2030E2BAC109) carried /home until 09.09.2026
|
|
# and is deliberately out of this layout. Its controller hits 90 °C under pure
|
|
# read load while the rest of the drive stays at 45, throttles from 1.5 GB/s to
|
|
# under a megabyte, and fell off the PCIe bus four times in four days
|
|
# (`controller is down, CSTS=0xffffffff`), forcing /home read-only. SMART is
|
|
# clean and P3CR013 is the final firmware, so there is nothing to repair. The
|
|
# disk stays in the M2_2 slot, but nothing here references it: no partition, no
|
|
# LUKS container, no mount.
|
|
moonarch.disk = {
|
|
enable = true;
|
|
device = "/dev/disk/by-id/nvme-XPG_GAMMIX_S11_Pro_2K322LAES7JG";
|
|
};
|
|
|
|
# The P1 from the comment above is back, but only for data whose loss costs
|
|
# nothing: a Steam library that can be downloaded again, and whatever else ends
|
|
# up not mattering. It stays deliberately outside moonarch.disk — nothing here
|
|
# partitions or formats it, and a rebuild must never touch what is on it.
|
|
#
|
|
# Every option below exists so that a dead or missing disk cannot hold up the
|
|
# boot, which is precisely what this one used to do. The container is opened
|
|
# after stage 1 rather than in the initrd, by a keyfile that itself sits on the
|
|
# encrypted system disk, and the filesystem is mounted only on first access. If
|
|
# the drive is gone, systemd gives up after five seconds and the machine
|
|
# carries on without it. idle-timeout unmounts it again once nothing uses it,
|
|
# which keeps it cold — and cold is when this drive works.
|
|
environment.etc.crypttab.text = ''
|
|
p1 UUID=0dee2385-7130-40c6-aa0c-a233e611b568 /var/keys/p1.key noauto,nofail
|
|
'';
|
|
|
|
fileSystems."/storage" = {
|
|
device = "/dev/mapper/p1";
|
|
fsType = "btrfs";
|
|
options = [
|
|
"subvol=storage"
|
|
"compress=zstd"
|
|
"noatime"
|
|
"noauto"
|
|
"nofail"
|
|
"x-systemd.automount"
|
|
"x-systemd.device-timeout=5"
|
|
"x-systemd.mount-timeout=10"
|
|
"x-systemd.idle-timeout=600"
|
|
];
|
|
};
|
|
|
|
boot.loader.systemd-boot.enable = true;
|
|
boot.loader.efi.canTouchEfiVariables = true;
|
|
boot.kernelPackages = pkgs.linuxPackages_zen;
|
|
boot.kernelParams = [ "quiet" ];
|
|
|
|
networking.hostName = "desktop";
|
|
networking.networkmanager.enable = true;
|
|
|
|
time.timeZone = "Europe/Berlin";
|
|
i18n.defaultLocale = "de_DE.UTF-8";
|
|
i18n.extraLocaleSettings = {
|
|
LC_ADDRESS = "de_DE.UTF-8";
|
|
LC_IDENTIFICATION = "de_DE.UTF-8";
|
|
LC_MEASUREMENT = "de_DE.UTF-8";
|
|
LC_MONETARY = "de_DE.UTF-8";
|
|
LC_NAME = "de_DE.UTF-8";
|
|
LC_NUMERIC = "de_DE.UTF-8";
|
|
LC_PAPER = "de_DE.UTF-8";
|
|
LC_TELEPHONE = "de_DE.UTF-8";
|
|
LC_TIME = "de_DE.UTF-8";
|
|
};
|
|
console.keyMap = "de";
|
|
services.xserver.xkb.layout = "de";
|
|
|
|
moonarch.user = "dkressler";
|
|
|
|
users.users.${config.moonarch.user} = {
|
|
isNormalUser = true;
|
|
# gamemode: the module creates the group but adds nobody. Without it
|
|
# gamemoded is denied the governor change on every game launch.
|
|
extraGroups = [ "networkmanager" "wheel" "video" "input" "plugdev" "docker" "gamemode" ];
|
|
shell = pkgs.zsh;
|
|
# Autologin gets past the greeter without a password, but swaylock
|
|
# authenticates through pam_unix and cannot unlock a screen for an account
|
|
# that has none. `mutableUsers` is at its default, so this applies when the
|
|
# account is created and `passwd` overrides it afterwards. Change it after
|
|
# the first boot — this value is in the repository.
|
|
initialPassword = "moonarch";
|
|
};
|
|
|
|
# LUKS already asked for a passphrase at boot, so the greeter would only ask
|
|
# for a second one. initial_session runs once per boot — the greeter comes
|
|
# back on logout, which is also where a session gets switched. greetd's
|
|
# restart option flips itself off when this is set.
|
|
services.greetd.settings.initial_session = {
|
|
command = "${pkgs.niri}/bin/niri-session";
|
|
user = config.moonarch.user;
|
|
};
|
|
|
|
# No snapper here. It was configured for / and /home and never took a single
|
|
# snapshot: the module writes the configs and the timers, but nobody creates
|
|
# the .snapshots subvolumes they need, so snapper-timeline failed every hour
|
|
# with errno:2 for months without anyone noticing. Rather than paper over that,
|
|
# note what it would and would not have bought: rollbacks of / are already
|
|
# covered by NixOS generations, and snapshots of /home share a disk with /home,
|
|
# so they answer "I deleted the wrong file" but not "the disk is gone" — which
|
|
# is the failure that actually happened here on 09.09.2026. The second question
|
|
# needs a copy off this machine and is still unanswered.
|
|
|
|
# No nixos-hardware profile applies to a self-built desktop, so the firmware
|
|
# this machine needs is enabled here instead.
|
|
hardware.enableRedistributableFirmware = true;
|
|
|
|
# The nixos-hardware profile derives this from enableRedistributableFirmware
|
|
# on the ThinkPad; here nothing does, so without this line the CPU runs on the
|
|
# microcode in its BIOS.
|
|
hardware.cpu.amd.updateMicrocode = true;
|
|
|
|
# The GPU is AMD, so amdgpu and Mesa cover it and there is nothing to declare:
|
|
# the kernel module is in the default initrd and Mesa comes with the graphics
|
|
# stack. An NVIDIA card would have needed videoDrivers and hardware.nvidia.
|
|
|
|
services.fstrim.enable = true;
|
|
|
|
# modules/services.nix turns fwupd on for every host. Lenovo publishes to the
|
|
# LVFS, so it earns its place on the ThinkPad; desktop mainboard vendors
|
|
# mostly do not, which leaves a daemon with nothing to update. mkForce because
|
|
# the shared module sets it unconditionally.
|
|
services.fwupd.enable = lib.mkForce false;
|
|
|
|
# --- Applications, this machine only ---
|
|
#
|
|
# Steam comes as a module rather than a package: it wraps the binary in an FHS
|
|
# environment and turns on 32-bit graphics for the drivers, which the package
|
|
# alone would not do.
|
|
programs.steam.enable = true;
|
|
|
|
# The module, not the package: gamemoded needs cap_sys_nice for renice and a
|
|
# pkexec helper for the governor, so the module wraps the binary, creates the
|
|
# gamemode group and enables the user service. Games are started through
|
|
# `gamemoderun`.
|
|
programs.gamemode.enable = true;
|
|
|
|
# Lets audio applications ask for realtime scheduling, which Bitwig does. The
|
|
# rlimits the PipeWire module sets (rtprio 95, memlock) apply to members of
|
|
# the pipewire group only, and this account is not one.
|
|
security.rtkit.enable = true;
|
|
|
|
environment.systemPackages = [ pkgs.bitwig-studio ];
|
|
|
|
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
|
nixpkgs.config.allowUnfree = true;
|
|
|
|
# The release this machine is first installed with. It stays at this value
|
|
# for the life of the installation and is never raised by an update.
|
|
system.stateVersion = "26.11";
|
|
}
|