# kusanagi — AMD workstation. Everything here is specific to this machine: # ROCm/GPU, the Focusrite Scarlett audio interface, optical-disc archiving, the # NAS mounts, and the Kavita/Immich/Ollama servers. { pkgs, ... }: { imports = [ ../../profiles/desktop.nix ./desktop.nix ../../profiles/dev.nix ../../profiles/apps.nix ../../profiles/sandbox.nix ./hardware-configuration.nix ]; # Run coding agents under the isolated `yokai` sandbox user; henz owns the # canonical repos in ~. See profiles/sandbox.nix for the full workflow. sandbox.owner = "henz"; networking.hostName = "kusanagi"; # This value determines the NixOS release from which the default # settings for stateful data, like file locations and database versions # on your system were taken. It‘s perfectly fine and recommended to leave # this value at the release version of the first install of this system. system.stateVersion = "24.11"; # Did you read the comment? powerManagement.cpuFreqGovernor = "performance"; # NOTE: idle sleep intentionally not configured. Suspend-to-RAM bounced awake # within ~5 s on this hardware (spurious S3 wakeup) and there's no swap to # hibernate into. Deferred to a future disko rebuild (real swap/encryption). boot.binfmt.emulatedSystems = [ "aarch64-linux" ]; # --- GPU (ROCm) --- nixpkgs.config.rocmSupport = true; hardware.graphics.extraPackages = with pkgs; [ rocmPackages.clr.icd ]; # --- Optical disc drives / archiving --- boot.kernelModules = [ "sg" ]; # for disc drive # For disc drives; plus systemd-tag the GPU compute nodes (kfd/renderD*) so # ollama.service can order after them — char devices get no systemd device # unit unless tagged. services.udev.extraRules = '' KERNEL=="sr[0-9]*", GROUP="cdrom", MODE="0660" KERNEL=="sg[0-9]*", GROUP="sg", MODE="0660" SUBSYSTEM=="kfd", TAG+="systemd" SUBSYSTEM=="drm", KERNEL=="renderD*", TAG+="systemd" ''; services.pcscd.enable = true; # abcde: patch its musicbrainz tool to accept discs with 4 args. nixpkgs.overlays = [ (self: super: { abcde = super.abcde.overrideAttrs (oldAttrs: { # We use postInstall to modify the script after it has been copied to the output directory postInstall = (oldAttrs.postInstall or "") + '' # Patch 1: Allow GetOptions to accept 4 arguments (instead of requiring 5+) sed -i 's/discinfo=i{5,}/discinfo=i{4,}/g' $out/bin/abcde-musicbrainz-tool # Patch 2: Lower the logic check from 5 to 4 sed -i 's/$#discinfo < 5/$#discinfo < 4/g' $out/bin/abcde-musicbrainz-tool ''; }); }) ]; # --- Scanner --- hardware.sane.enable = true; # --- Audio interface (Focusrite Scarlett 18i20) --- # Add this for better pro audio support services.pipewire.extraConfig.pipewire."10-low-latency" = { "context.properties" = { "default.clock.rate" = 48000; "default.clock.quantum" = 256; "default.clock.min-quantum" = 256; "default.clock.max-quantum" = 2048; }; }; # Pin the Scarlett 18i20 to the raw "Pro Audio" profile. # The 26.05 pipewire/wireplumber update began preferring the UCM "HiFi" # profile, which splits the interface into Direct1/Line5/SPDIF1/... sinks and # broke monitoring. This priority rule forces the multichannel Pro Audio # profile statelessly on every boot (and survives future UCM renames). services.pipewire.wireplumber.extraConfig."51-scarlett-pro-audio" = { "device.profile.priority.rules" = [ { matches = [ { "device.name" = "alsa_card.usb-Focusrite_Scarlett_18i20_USB_P9DCR6Y378D1E6-00"; } ]; actions = { update-props = { priorities = [ "pro-audio" ]; }; }; } ]; }; # Real-time audio optimizations security.pam.loginLimits = [ { domain = "@audio"; item = "memlock"; type = "-"; value = "unlimited"; } { domain = "@audio"; item = "rtprio"; type = "-"; value = "99"; } { domain = "@audio"; item = "nofile"; type = "soft"; value = "99999"; } { domain = "@audio"; item = "nofile"; type = "hard"; value = "99999"; } ]; # Kernel optimizations for audio boot.kernelParams = [ "threadirqs" "preempt=full" # if using a PREEMPT kernel # Add USB audio optimizations "usbcore.usbfs_memory_mb=1024" ]; # --- MPD (music) --- services.mpd.enable = false; systemd.user.services.mpd = { description = "Music Player Daemon"; after = [ "network.target" "sound.target" ]; wantedBy = [ "default.target" ]; serviceConfig = { ExecStart = "${pkgs.mpd}/bin/mpd --no-daemon"; Type = "notify"; LimitRTPRIO = 50; LimitRTTIME = "infinity"; }; }; # --- NAS (NFS) --- boot.supportedFilesystems = [ "nfs" ]; services.nfs.server.enable = false; services.rpcbind.enable = true; services.nfs.idmapd.settings = { General = { Domain = "kusanagi"; # or your actual domain name }; }; # NAS shares. Use the mDNS-resolvable name (bare "milgrim" doesn't resolve) # and automount on first access so a boot-time DNS/network race can't leave # them unmounted (which silently breaks MPD, whose DB points at these paths). fileSystems."/mnt/milgrim-share" = { device = "milgrim.local:/volume1/share"; fsType = "nfs"; options = [ "nfsvers=4" "rsize=8192" "wsize=8192" "_netdev" "noauto" "x-systemd.automount" "x-systemd.idle-timeout=600" "x-systemd.mount-timeout=10s" ]; }; fileSystems."/mnt/milgrim-public" = { device = "milgrim.local:/volume1/public"; fsType = "nfs"; options = [ "nfsvers=4" "rsize=8192" "wsize=8192" "_netdev" "noauto" "x-systemd.automount" "x-systemd.idle-timeout=600" "x-systemd.mount-timeout=10s" ]; }; # --- Servers --- services.kavita = { enable = true; tokenKeyFile = "/var/lib/kavita/token-key"; settings.Port = 5000; }; systemd.tmpfiles.rules = [ "d /srv/library 2750 henz kavita - -" ]; services.ollama = { enable = true; package = pkgs.ollama-rocm; environmentVariables = { # No HIP_VISIBLE_DEVICES / HSA_OVERRIDE_GFX_VERSION: the 7900 XT is # native gfx1100 and discovery excludes the unsupported Raphael iGPU # (gfx1036) on its own; ollama warns that overriding visible devices can # itself break discovery. The past "runs 100% on CPU" incidents were the # boot-order race handled below, not a gfx-target mismatch. # KV-cache quantization (below) is a no-op without flash attention; ollama # falls back to f16 KV cache, inflating VRAM use and forcing CPU offload. OLLAMA_FLASH_ATTENTION = "1"; OLLAMA_CONTEXT_LENGTH = "32768"; OLLAMA_KV_CACHE_TYPE = "q8_0"; }; }; # ollama probes GPUs once at startup. At boot it raced amdgpu bring-up # (started between /dev/kfd and the render node appearing) and permanently # fell back to CPU. Order it after the tagged compute nodes (udev rules # above) so discovery only runs once the card is really there. systemd.services.ollama = { wants = [ "dev-kfd.device" "dev-dri-renderD128.device" ]; after = [ "dev-kfd.device" "dev-dri-renderD128.device" ]; }; services.immich = { enable = true; machine-learning.enable = false; }; # --- discidurl / haunt tooling (needs the per-machine overlays in flake.nix) --- environment.systemPackages = let # open the MusicBrainz submission URL for the disc in the drive discid = pkgs.writeShellApplication { name = "discid"; runtimeInputs = [ pkgs.discidurl pkgs.xdg-utils ]; text = "discidurl | xargs -r xdg-open"; }; in with pkgs; [ discidurl discid haunt ]; # --- User account (henz) --- # (Desktop groups audio/lp/input come from profiles/desktop.nix.) users.users.henz.isNormalUser = true; users.users.henz.description = "Henry Webster"; users.users.henz.extraGroups = [ # base "networkmanager" "wheel" # kusanagi hardware "cdrom" "optical" "sg" "scanner" ]; nix.settings.trusted-users = [ "root" "henz" ]; users.users.henz.packages = with pkgs; [ # base CLI git neovim tmux btop ripgrep unzip wl-clipboard bc psmisc usbutils stow nushell # kusanagi blender rocmPackages.rpp rocmPackages.hipcc rocmPackages.rocminfo ardour davinci-resolve-studio calf alsa-utils alsa-tools pipewire.jack nfs-utils # music mpd mpc ncmpcpp # for archiving #makemkv abcde libaacs libbdplus libdvdcss mkvtoolnix dvdbackup chromaprint picard flac # scanners simple-scan (pkgs.makeDesktopItem { name = "PICO-8"; desktopName = "PICO-8"; exec = "pico8"; icon = "pico8"; comment = "Fantasy console for making, sharing and playing tiny games"; categories = [ "Game" ]; }) ]; }