feat(macpro31): NVIDIA P400 with CUDA Docker, and a fleet-wide CPU capability gate #94
@@ -56,10 +56,10 @@ module reaches a host: **baseModules** (every NixOS host, via `flake.nix`),
|
|||||||
(pulled in by another module's `imports`).
|
(pulled in by another module's `imports`).
|
||||||
|
|
||||||
| Module | Imported by | What it does / when to use it |
|
| Module | Imported by | What it does / when to use it |
|
||||||
| ------------------ | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------ | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `common-nixos.nix` | baseModules (all NixOS) | Timezone/locale, store hygiene (auto-optimise, big download buffer, **no** auto-GC), the nix-community binary cache, `nix-ld`, base CLI (`git`, `fastfetch`), and the fleet-wide font stack. |
|
| `common-nixos.nix` | baseModules (all NixOS) | Timezone/locale, store hygiene (auto-optimise, big download buffer, **no** auto-GC), the nix-community binary cache, `nix-ld`, base CLI (`git`, `fastfetch`), and the fleet-wide font stack. |
|
||||||
| `users.nix` | baseModules (all NixOS) | Builds `users.users` from the registry for the host's `hostUsers`; enables zsh; enables Firefox + Thunderbird **only** when `features.swayDesktop.enable` is on. Applies per-user `linger`. |
|
| `users.nix` | baseModules (all NixOS) | Builds `users.users` from the registry for the host's `hostUsers`; enables zsh; enables Firefox + Thunderbird **only** when `features.swayDesktop.enable` is on. Applies per-user `linger`. |
|
||||||
| `features.nix` | baseModules (all NixOS) | Declares feature-flag options (currently `features.swayDesktop.enable`) so any host can read/set them without importing the heavy implementation module. |
|
| `features.nix` | baseModules (all NixOS) | Declares the feature-flag options (`features.swayDesktop.enable`, `features.claudeCode.enable`) so any host can read/set them without importing the heavy implementation module, plus the CPU capability fact they derive from (`features.cpu.microarchLevel`) and the assertion that guards it. See "CPU capability gating". |
|
||||||
| `workstation.nix` | transitively (via laptop/desktop) | Form-factor-agnostic base for physical graphical hosts: turns on `swayDesktop`, Dvorak console, PipeWire, firewall (default-deny), fstrim, earlyoom, fwupd, thermald (x86), redistributable fw. |
|
| `workstation.nix` | transitively (via laptop/desktop) | Form-factor-agnostic base for physical graphical hosts: turns on `swayDesktop`, Dvorak console, PipeWire, firewall (default-deny), fstrim, earlyoom, fwupd, thermald (x86), redistributable fw. |
|
||||||
| `laptop.nix` | host table (MBP, T400) | `imports` workstation.nix, then adds the portable bits: iwd Wi-Fi, lid suspend/lock, Bluetooth + blueman. |
|
| `laptop.nix` | host table (MBP, T400) | `imports` workstation.nix, then adds the portable bits: iwd Wi-Fi, lid suspend/lock, Bluetooth + blueman. |
|
||||||
| `desktop.nix` | host table (Mac Pro) | `imports` workstation.nix, then swaps Wi-Fi for wired NetworkManager. Pair with `portable = false` in the host table. |
|
| `desktop.nix` | host table (Mac Pro) | `imports` workstation.nix, then swaps Wi-Fi for wired NetworkManager. Pair with `portable = false` in the host table. |
|
||||||
@@ -74,6 +74,29 @@ Form-factor decision: a **laptop** imports `laptop.nix` (default
|
|||||||
serves. `portable` is threaded through to `home/sway.nix`, which drops the
|
serves. `portable` is threaded through to `home/sway.nix`, which drops the
|
||||||
battery block and brightness keys on desktops.
|
battery block and brightness keys on desktops.
|
||||||
|
|
||||||
|
## CPU capability gating
|
||||||
|
|
||||||
|
Not every host can run everything the fleet installs. Nix cannot probe the CPU
|
||||||
|
(evaluation is pure, and a host may be built elsewhere), so each machine
|
||||||
|
declares what it is and the shared modules derive from that:
|
||||||
|
|
||||||
|
- `features.cpu.microarchLevel` — the x86-64 psABI level the CPU implements
|
||||||
|
(1 = baseline, 2 = SSE4.2/POPCNT, 3 = AVX2, 4 = AVX-512). Defaults to **2**;
|
||||||
|
only a host older than that sets it (the Mac Pro 3,1's 2008 Harpertown Xeons
|
||||||
|
are level 1). Ignored on non-x86_64 hosts.
|
||||||
|
- `features.claudeCode.enable` — derived: on unless the host is below
|
||||||
|
x86-64-v2, because Claude Code's Node runtime needs SSE4.2/POPCNT.
|
||||||
|
[`home/claude.nix`](./home/claude.nix) reads it through home-manager's
|
||||||
|
`osConfig` and installs nothing (CLI, `CLAUDE.md`, output style, memory
|
||||||
|
symlink) when it is off. Hosts with no such option — the Darwin host and the
|
||||||
|
standalone `homeConfigurations` — fall back to enabled.
|
||||||
|
- An assertion in `features.nix` fails evaluation if a host force-enables a
|
||||||
|
flag its declared CPU level cannot support, so the mistake surfaces in
|
||||||
|
`nix flake check`/CI rather than as an illegal-instruction crash on the box.
|
||||||
|
|
||||||
|
Adding another CPU-sensitive tool means deriving one more flag there, not
|
||||||
|
editing every host.
|
||||||
|
|
||||||
## Users
|
## Users
|
||||||
|
|
||||||
Identity is data, kept separate from the reusable modules:
|
Identity is data, kept separate from the reusable modules:
|
||||||
|
|||||||
+10
-2
@@ -175,8 +175,16 @@ forced off there) but still runs the agent.
|
|||||||
|
|
||||||
## Claude Code
|
## Claude Code
|
||||||
|
|
||||||
Managed declaratively by [`claude.nix`](./claude.nix) on every host (the CLI is
|
Managed declaratively by [`claude.nix`](./claude.nix) on every host whose CPU
|
||||||
`pkgs.claude-code`, tracked to unstable via the flake overlay).
|
can run it (the CLI is `pkgs.claude-code`, tracked to unstable via the flake
|
||||||
|
overlay).
|
||||||
|
|
||||||
|
**Capability gate.** The module installs nothing — CLI or files — when
|
||||||
|
`osConfig.features.claudeCode.enable` is off. That flag is derived fleet-wide
|
||||||
|
from the host's declared CPU level (see "CPU capability gating" in the root
|
||||||
|
README): the Node runtime needs SSE4.2/POPCNT, so anything below x86-64-v2 (the
|
||||||
|
Mac Pro 3,1) is excluded. Hosts that do not define the option — the Darwin host
|
||||||
|
and the standalone `homeConfigurations` — keep it enabled.
|
||||||
|
|
||||||
| Managed (static, from Nix) | Left mutable (runtime state) |
|
| Managed (static, from Nix) | Left mutable (runtime state) |
|
||||||
| --------------------------------------------------- | ------------------------------------------------------ |
|
| --------------------------------------------------- | ------------------------------------------------------ |
|
||||||
|
|||||||
+21
-5
@@ -1,4 +1,5 @@
|
|||||||
# Claude Code, configured declaratively via home-manager. Wanted on every host.
|
# Claude Code, configured declaratively via home-manager. Wanted on every host
|
||||||
|
# whose CPU can run it -- see the gate below.
|
||||||
#
|
#
|
||||||
# The STATIC config is managed here: the global CLAUDE.md (persona/context), the
|
# The STATIC config is managed here: the global CLAUDE.md (persona/context), the
|
||||||
# custom output style, and the auto-memory directory. settings.json is
|
# custom output style, and the auto-memory directory. settings.json is
|
||||||
@@ -10,18 +11,33 @@
|
|||||||
# read-only into ~/.claude/memory, so the runtime "save a memory" path no longer
|
# read-only into ~/.claude/memory, so the runtime "save a memory" path no longer
|
||||||
# writes there -- recall still works, but new/changed memories must be added to
|
# writes there -- recall still works, but new/changed memories must be added to
|
||||||
# this repo and rebuilt. CLAUDE.md instructs Claude to do exactly that.
|
# this repo and rebuilt. CLAUDE.md instructs Claude to do exactly that.
|
||||||
{ ... }:
|
{
|
||||||
|
lib,
|
||||||
|
# Set by the NixOS/Darwin home-manager module; absent for the standalone
|
||||||
|
# homeConfigurations, hence the default.
|
||||||
|
osConfig ? { },
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
# Capability gate, declared once for the whole fleet in modules/features.nix
|
||||||
|
# (default: on; off on CPUs below x86-64-v2, which cannot run the Node
|
||||||
|
# runtime Claude Code ships on). Hosts without that option -- the Darwin host
|
||||||
|
# and the portable standalone profile -- fall back to enabled.
|
||||||
|
enable = osConfig.features.claudeCode.enable or true;
|
||||||
|
in
|
||||||
{
|
{
|
||||||
programs.claude-code = {
|
programs.claude-code = {
|
||||||
enable = true;
|
inherit enable;
|
||||||
# package defaults to pkgs.claude-code (tracked to unstable via the flake
|
# package defaults to pkgs.claude-code (tracked to unstable via the flake
|
||||||
# overlay); installs the CLI on every host.
|
# overlay).
|
||||||
|
|
||||||
# ~/.claude/CLAUDE.md -- global instructions / persona / memory workflow.
|
# ~/.claude/CLAUDE.md -- global instructions / persona / memory workflow.
|
||||||
context = ./claude/CLAUDE.md;
|
context = ./claude/CLAUDE.md;
|
||||||
};
|
};
|
||||||
|
|
||||||
home.file = {
|
# Nothing to place when the CLI is not installed: a ~/.claude/memory symlink
|
||||||
|
# with no Claude Code to read it is just dead state.
|
||||||
|
home.file = lib.mkIf enable {
|
||||||
# Custom output style. The module has no option for output-styles/, so place
|
# Custom output style. The module has no option for output-styles/, so place
|
||||||
# it directly; selection (settings.json `outputStyle`) stays mutable.
|
# it directly; selection (settings.json `outputStyle`) stays mutable.
|
||||||
".claude/output-styles/soviet-engineer.md".source = ./claude/output-styles/soviet-engineer.md;
|
".claude/output-styles/soviet-engineer.md".source = ./claude/output-styles/soviet-engineer.md;
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# NVIDIA Quadro P400 (Pascal, GP108) on the Mac Pro 3,1: proprietary driver for
|
||||||
|
# the Sway desktop, plus Docker with GPU/CUDA access for containers.
|
||||||
|
#
|
||||||
|
# Driver branch: 580 (nvidiaPackages.legacy_580), NOT the nixpkgs default
|
||||||
|
# (`production`, currently 595.x). 580 is the last branch that supports
|
||||||
|
# Maxwell/Pascal/Volta -- NVIDIA keeps it as an LTS branch to Aug 2028 -- and a
|
||||||
|
# newer branch simply will not drive this card.
|
||||||
|
#
|
||||||
|
# The driver is unfree, so it is not in the binary cache: the kernel module is
|
||||||
|
# compiled locally. On this machine's 2008 Xeons expect the first rebuild after
|
||||||
|
# a kernel bump to take a long while.
|
||||||
|
{ config, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
# Selects the proprietary driver; the module blacklists nouveau/nvidiafb and
|
||||||
|
# loads nvidia-uvm (needed by CUDA) via modprobe softdep. Naming is historical
|
||||||
|
# -- this option drives the kernel/driver choice on Wayland hosts too, which
|
||||||
|
# is why it is set on a machine that runs no X server.
|
||||||
|
services.xserver.videoDrivers = [ "nvidia" ];
|
||||||
|
|
||||||
|
hardware.nvidia = {
|
||||||
|
package = config.boot.kernelPackages.nvidiaPackages.legacy_580;
|
||||||
|
# Required for Wayland: sets nvidia-drm.modeset=1 (and fbdev=1), without
|
||||||
|
# which wlroots gets no GBM device and Sway/cage fail to start.
|
||||||
|
modesetting.enable = true;
|
||||||
|
# The open kernel modules need Turing or later; Pascal must use the closed
|
||||||
|
# ones. Explicit because the option has no default on driver >= 560.
|
||||||
|
open = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
# wlroots refuses the proprietary NVIDIA driver unless told to proceed. The
|
||||||
|
# greeter's compositor (cage) has no such check; only Sway needs the flag,
|
||||||
|
# which the module bakes into the wrapper the session's .desktop file runs.
|
||||||
|
programs.sway.extraOptions = [ "--unsupported-gpu" ];
|
||||||
|
|
||||||
|
virtualisation.docker.enable = true;
|
||||||
|
|
||||||
|
# CDI-based GPU access for containers: generates /var/run/cdi specs from the
|
||||||
|
# host driver at boot and turns on Docker's CDI feature. Run GPU workloads
|
||||||
|
# with `docker run --device=nvidia.com/gpu=all ...`. The deprecated
|
||||||
|
# virtualisation.docker.enableNvidia runtime wrapper is deliberately not used.
|
||||||
|
hardware.nvidia-container-toolkit.enable = true;
|
||||||
|
}
|
||||||
+67
-2
@@ -6,7 +6,72 @@
|
|||||||
# headless host (e.g. the Pi) must be able to leave it at its default without
|
# headless host (e.g. the Pi) must be able to leave it at its default without
|
||||||
# pulling in modules/sway.nix. The implementation lives in modules/sway.nix,
|
# pulling in modules/sway.nix. The implementation lives in modules/sway.nix,
|
||||||
# gated on this flag.
|
# gated on this flag.
|
||||||
{ lib, ... }:
|
#
|
||||||
|
# The file also carries the host capability facts those flags derive from
|
||||||
|
# (features.cpu.*). features.claudeCode.enable is such a derived flag: it is
|
||||||
|
# computed from the declared CPU level here and read by home/claude.nix through
|
||||||
|
# home-manager's osConfig, so a machine that cannot run the tool never installs
|
||||||
|
# it, on any host, without per-host opt-outs.
|
||||||
{
|
{
|
||||||
options.features.swayDesktop.enable = lib.mkEnableOption "the Sway desktop";
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
cfg = config.features;
|
||||||
|
|
||||||
|
# Claude Code runs on Node, whose V8 build requires SSE4.2 and POPCNT -- the
|
||||||
|
# x86-64-v2 feature set. On an older x86_64 CPU it does not run (illegal
|
||||||
|
# instruction), so it must not be installed there.
|
||||||
|
claudeCodeMinLevel = 2;
|
||||||
|
claudeCodeSupported =
|
||||||
|
!pkgs.stdenv.hostPlatform.isx86_64 || cfg.cpu.microarchLevel >= claudeCodeMinLevel;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.features = {
|
||||||
|
swayDesktop.enable = lib.mkEnableOption "the Sway desktop";
|
||||||
|
|
||||||
|
cpu.microarchLevel = lib.mkOption {
|
||||||
|
type = lib.types.ints.between 1 4;
|
||||||
|
default = 2;
|
||||||
|
example = 1;
|
||||||
|
description = ''
|
||||||
|
The x86-64 psABI microarchitecture level the host CPU implements:
|
||||||
|
1 = the original baseline, 2 = SSE4.2/POPCNT (Nehalem, 2008+),
|
||||||
|
3 = AVX2, 4 = AVX-512.
|
||||||
|
|
||||||
|
Nix cannot detect this (evaluation is pure and hosts are often built
|
||||||
|
elsewhere), so a machine older than the default declares its own level
|
||||||
|
and the flags below derive from it. Ignored on non-x86_64 hosts.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
claudeCode.enable = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = claudeCodeSupported;
|
||||||
|
defaultText = lib.literalMD ''
|
||||||
|
`true`, unless the host declares an x86-64 microarchitecture level
|
||||||
|
below ${toString claudeCodeMinLevel}
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
Whether to install Claude Code in this host's home-manager profiles
|
||||||
|
(implemented in home/claude.nix). Defaults off on CPUs below
|
||||||
|
x86-64-v${toString claudeCodeMinLevel}, which cannot run it; forcing it
|
||||||
|
on such a host is an evaluation error.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config.assertions = [
|
||||||
|
{
|
||||||
|
assertion = cfg.claudeCode.enable -> claudeCodeSupported;
|
||||||
|
message = ''
|
||||||
|
features.claudeCode.enable is on, but this host declares
|
||||||
|
features.cpu.microarchLevel = ${toString cfg.cpu.microarchLevel}.
|
||||||
|
Claude Code needs x86-64-v${toString claudeCodeMinLevel}
|
||||||
|
(SSE4.2/POPCNT) and will not run on an older CPU.
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user