diff --git a/modules/apps/sync-hardware.nix b/modules/apps/sync-hardware.nix new file mode 100644 index 0000000..4ca3bf7 --- /dev/null +++ b/modules/apps/sync-hardware.nix @@ -0,0 +1,84 @@ +{...}: { + # `nix run .#sync-hardware` + # + # Refreshes the calling host's generated hardware module in-repo, replacing the + # old habit of importing /etc/nixos/hardware-configuration.nix by absolute path. + # + # Why bother: that absolute import left the flake impure (every eval needed + # --impure, since the file lives outside the store) and — worse — meant *both* + # hosts resolved to whichever machine's hardware file was on disk at build + # time. Building t480 from shadow silently baked in shadow's disk UUIDs. + # + # Run this on a host after changing its hardware, then commit the result. + perSystem = {pkgs, ...}: let + script = pkgs.writeShellApplication { + name = "sync-hardware"; + runtimeInputs = [pkgs.nixos-install-tools pkgs.git pkgs.coreutils pkgs.nettools]; + text = '' + set -euo pipefail + + host="''${1:-$(hostname)}" + + root=$(git rev-parse --show-toplevel 2>/dev/null || true) + if [ -z "$root" ]; then + echo "sync-hardware: run this from inside the config repo." >&2 + exit 1 + fi + + dir="$root/modules/hosts/$host" + if [ ! -d "$dir" ]; then + echo "sync-hardware: no host directory at $dir" >&2 + echo " known hosts: $(cd "$root/modules/hosts" && echo *)" >&2 + echo " pass an explicit name with: nix run .#sync-hardware -- " >&2 + exit 1 + fi + + out="$dir/_hardware.nix" + + # --no-filesystems is load-bearing: fileSystems/swapDevices are declared + # by hand in the host's disks.nix. Without the flag this would emit + # entries for whatever is mounted right now, duplicating (and conflicting + # with) those definitions. + header=$(cat <<'EOF' + # GENERATED FILE — do not edit by hand. + # + # Regenerate on the host itself with: nix run .#sync-hardware + # + # Produced by `nixos-generate-config --show-hardware-config --no-filesystems`. + # The --no-filesystems flag is deliberate: fileSystems and swapDevices are + # declared by hand in ./disks.nix, so regenerating this file can never clobber + # them or emit duplicates for whatever happened to be mounted at the time. + # + # Leading underscore keeps import-tree from auto-importing this as a + # flake-parts module (its filter drops any path containing "/_"); it is + # imported explicitly from ./default.nix instead. + EOF + ) + + tmp=$(mktemp) + trap 'rm -f "$tmp"' EXIT + + # Strip the upstream 3-line "Do not modify" banner; ours replaces it. + { + printf '%s\n' "$header" | sed 's/^ //' + nixos-generate-config --show-hardware-config --no-filesystems | tail -n +4 + } > "$tmp" + + if [ -f "$out" ] && cmp -s "$tmp" "$out"; then + echo "sync-hardware: $out already up to date." + exit 0 + fi + + cp "$tmp" "$out" + echo "sync-hardware: wrote $out" + echo " review with: git diff -- $out" + ''; + }; + in { + apps.sync-hardware = { + type = "app"; + program = "${script}/bin/sync-hardware"; + meta.description = "Regenerate modules/hosts//_hardware.nix from the running machine"; + }; + }; +} diff --git a/modules/hosts/shadow/_hardware.nix b/modules/hosts/shadow/_hardware.nix new file mode 100644 index 0000000..591ca04 --- /dev/null +++ b/modules/hosts/shadow/_hardware.nix @@ -0,0 +1,27 @@ +# GENERATED FILE — do not edit by hand. +# +# Regenerate on the host itself with: nix run .#sync-hardware +# +# Produced by `nixos-generate-config --show-hardware-config --no-filesystems`. +# The --no-filesystems flag is deliberate: fileSystems and swapDevices are +# declared by hand in ./disks.nix, so regenerating this file can never clobber +# them or emit duplicates for whatever happened to be mounted at the time. +# +# Leading underscore keeps import-tree from auto-importing this as a +# flake-parts module (its filter drops any path containing "/_"); it is +# imported explicitly from ./default.nix instead. +{ config, lib, pkgs, modulesPath, ... }: + +{ + imports = + [ (modulesPath + "/installer/scan/not-detected.nix") + ]; + + boot.initrd.availableKernelModules = [ "nvme" "xhci_pci" "ahci" "usbhid" "uas" "sd_mod" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-amd" ]; + boot.extraModulePackages = [ ]; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; +} diff --git a/modules/hosts/shadow/default.nix b/modules/hosts/shadow/default.nix index 3195431..e67f396 100644 --- a/modules/hosts/shadow/default.nix +++ b/modules/hosts/shadow/default.nix @@ -5,8 +5,12 @@ }: { flake.nixosConfigurations.shadow = inputs.nixpkgs.lib.nixosSystem { modules = with self.nixosModules; [ + # Generated hardware scan, in-repo so the flake stays pure. + # Refresh with `nix run .#sync-hardware` (run on shadow itself). + ./_hardware.nix desktop shadowConfiguration + shadowDisks cpuAmd gpuNvidia development diff --git a/modules/hosts/shadow/disks.nix b/modules/hosts/shadow/disks.nix new file mode 100644 index 0000000..cdd2207 --- /dev/null +++ b/modules/hosts/shadow/disks.nix @@ -0,0 +1,56 @@ +{...}: { + # Every filesystem on shadow lives here, hand-maintained. + # + # hardware.nix is generated with `--no-filesystems` precisely so it never + # emits fileSystems/swapDevices entries of its own. That keeps regeneration + # (`nix run .#sync-hardware`) safe: re-running it can never clobber these + # definitions, nor produce duplicates for whichever data drives happened to + # be mounted at the time it ran. + flake.nixosModules.shadowDisks = {...}: let + # Secondary data drive, addressed by filesystem label. + # + # noauto + x-systemd.automount makes systemd generate an .automount unit and + # mount on first access to the path rather than at boot — the two 2.7T + # spinners stay parked until something actually touches them. idle-timeout + # unmounts them again once quiet so they can spin back down. + # + # nofail matters even with automount: it stops a missing or dying disk from + # becoming a failed dependency that drops the boot into emergency mode. + # + # On-disk ownership is already ahmza mode 755 on all three, so no uid mapping + # is needed — ext4 stores ownership in the filesystem and ignores uid=/gid=. + dataDrive = label: { + device = "/dev/disk/by-label/${label}"; + fsType = "ext4"; + options = [ + "nofail" + "noauto" + "x-systemd.automount" + "x-systemd.idle-timeout=600" + "x-systemd.device-timeout=10" + ]; + }; + in { + fileSystems = { + # Boot essentials, by UUID. UUIDs rather than labels here on purpose: + # these two must resolve unambiguously, and a label is only as unique as + # the last disk someone plugged in. + "/" = { + device = "/dev/disk/by-uuid/deff63fc-1738-4cfc-a79c-06e58c78471a"; + fsType = "ext4"; + }; + + "/boot" = { + device = "/dev/disk/by-uuid/5873-AD05"; + fsType = "vfat"; + options = ["fmask=0077" "dmask=0077"]; + }; + + "/mnt/dump" = dataDrive "DUMP"; + "/mnt/games" = dataDrive "GAMES"; + "/mnt/bak" = dataDrive "BAK"; + }; + + swapDevices = []; + }; +} diff --git a/modules/hosts/t480/default.nix b/modules/hosts/t480/default.nix index 4a4e954..1539b3e 100644 --- a/modules/hosts/t480/default.nix +++ b/modules/hosts/t480/default.nix @@ -5,6 +5,17 @@ }: { flake.nixosConfigurations.t480 = inputs.nixpkgs.lib.nixosSystem { modules = with self.nixosModules; [ + # TODO: still importing by absolute path, so evaluating t480 requires + # --impure and picks up the hardware file of whatever machine you build + # from (on shadow, that means shadow's disk UUIDs — wrong but harmless + # until you actually deploy). Fix by running, ON THE T480 ITSELF: + # + # nix run .#sync-hardware + # + # then create modules/hosts/t480/disks.nix (mirroring shadow's) and swap + # this line for `./_hardware.nix` + `t480Disks`. Cannot be done remotely: + # the hardware scan has to happen on the target machine. + /etc/nixos/hardware-configuration.nix desktop t480Configuration # CPU microcode + integrated Intel GPU. The T480 also has an NVIDIA MX150; diff --git a/modules/system/core/default.nix b/modules/system/core/default.nix index e0475f6..c89f2d9 100644 --- a/modules/system/core/default.nix +++ b/modules/system/core/default.nix @@ -18,11 +18,11 @@ nix-ld ]; in { - imports = - [ - /etc/nixos/hardware-configuration.nix - ] - ++ modules; + # Hardware/disk config is deliberately NOT imported here. It is per-host and + # lives in modules/hosts//{_hardware.nix,disks.nix}, wired up by that + # host's default.nix. Importing it from this shared module used to mean both + # hosts resolved to whichever machine's /etc/nixos file existed at build time. + imports = modules; services = { openssh.enable = true; avahi.enable = true;