move hardware config in-repo per-host and mount the data drives
importing /etc/nixos/hardware-configuration.nix by absolute path from the
shared core module had two problems: it made the flake impure (every eval
needed --impure, since the file lives outside the store) and both hosts
resolved to whichever machine's file was on disk, so evaluating t480 on shadow
silently baked in shadow's disk uuids.
shadow now carries its own generated scan at modules/hosts/shadow/_hardware.nix.
the leading underscore keeps import-tree from picking it up as a flake-parts
module - its filter drops any path containing "/_" - so it is imported
explicitly from the host's default.nix.
it is generated with --no-filesystems and all of shadow's filesystems are
declared by hand in disks.nix instead. that split is what makes regeneration
safe: nixos-generate-config emits fileSystems for whatever is mounted at the
time, which would otherwise conflict with the entries below every time the data
drives happened to be mounted. `nix run .#sync-hardware` refreshes the file.
DUMP/GAMES/BAK are mounted by label at /mnt/{dump,games,bak} with
noauto + x-systemd.automount, so the two 2.7T spinners stay parked until
something touches the path and spin back down after 10 minutes idle. nofail
keeps a missing disk from dropping the boot into emergency mode.
/ and /boot are unchanged - still the same uuids, verified identical to the
previous evaluation. t480 keeps the absolute import for now with a TODO; its
scan can only be generated on the machine itself.
This commit is contained in:
@@ -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 -- <host>" >&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/<host>/_hardware.nix from the running machine";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 = [];
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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/<host>/{_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;
|
||||
|
||||
Reference in New Issue
Block a user