# ---- TERRA ---- { config, pkgs, lib, inputs, ... }: let unstable = import inputs.nixpkgs-unstable { inherit (pkgs.stdenv.hostPlatform) system; config = pkgs.config; }; in { imports = [ ./hardware-configuration.nix ./disk-config.nix ./secrets.nix ../../common.nix ../../services/containers.nix ../../services/vpn/tailscale.nix ../../services/monitoring/node-exporter.nix ../../services/desktop/desktop-hyprland.nix ../../services/desktop/desktop-apps.nix ../../services/desktop/librechat.nix ]; networking.hostName = "terra"; services.flatpak = { enable = true; remotes = [{ name = "flathub"; location = "https://dl.flathub.org/repo/flathub.flatpakrepo"; }]; packages = [ { appId = "com.github.tchx84.Flatseal"; origin = "flathub"; } { appId = "com.blitzfc.qbz"; origin = "flathub"; } { appId = "com.discordapp.Discord"; origin = "flathub"; } { appId = "org.telegram.desktop"; origin = "flathub"; } { appId = "com.bambulab.BambuStudio"; origin = "flathub"; } ]; }; environment.systemPackages = [ unstable.proton-pass-cli ]; # ---- nix-ld: lets generic dynamically-linked Linux binaries run as-is — # needed for editor extensions (Zed/VSCode LSPs, debuggers, etc.) that # download prebuilt binaries not built for NixOS. See # https://nix.dev/permalink/stub-ld ---- programs.nix-ld.enable = true; # ---- home-manager (user-level config for darman) ---- # Base settings (useGlobalPkgs/useUserPackages/backupFileExtension) and the # shared zsh baseline now live in common.nix + home/common.nix, applied to # every host. This just layers terra's desktop/dev-specific profile on top # — home-manager.users.darman.imports merges additively across modules. home-manager.extraSpecialArgs = { inherit unstable inputs; }; home-manager.users.darman.imports = [ ./home.nix ]; # ---- Boot (UEFI) ---- boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; hardware.cpu.amd.updateMicrocode = true; # mercury (aarch64) is built/flashed from here. Without this, `nix build` # for it dies with "platform mismatch" — no qemu binfmt handler registered # and aarch64-linux missing from nix.settings.extra-platforms. This module # sets up both (see CLAUDE.md's aarch64 gotcha). boot.binfmt.emulatedSystems = [ "aarch64-linux" ]; # ---- GPU (Radeon RX 6800 XT / Navi 21) ---- hardware.enableRedistributableFirmware = true; boot.initrd.kernelModules = [ "amdgpu" ]; # /dev/dri/renderD128 is root:render 0660, so rootless podman containers can # only reach the GPU if the *host* user is in render. Needed by the Vulkan # whisper.cpp/llama.cpp containers in ~/Data/Dev/repos/content-trigger-scanner. users.users.darman.extraGroups = [ "render" "video" ]; # ---- ollama (local LLM server, ROCm on the 6800 XT) ---- # Navi 21 is gfx1030 — officially supported by ROCm, so no # rocmOverrideGfx/HSA_OVERRIDE_GFX_VERSION needed (that's for gpus ROCm # doesn't recognize, e.g. RDNA1/gfx101x). The upstream module runs the # service under DynamicUser with SupplementaryGroups=["render"] and # DeviceAllow for char-kfd/char-drm/char-fb already, so unlike jellyfin's # static user it needs no extraGroups wiring here. services.ollama = { enable = true; package = pkgs.ollama-rocm; # keep in sync with services/desktop/librechat.nix's endpoints.custom # default model — LibreChat's config schema needs a non-empty default # even though fetch=true replaces it with whatever's actually pulled. # gemma4:12b: general chat/coding daily driver, fits fully in 16G VRAM — # also doubles as the memory-extraction agent (see librechat.nix): a # 3b model (llama3.2:3b, dropped) couldn't reliably tell the user's # stated facts apart from its own boilerplate, e.g. saving "I am an AI # assistant with tool calling capabilities" as the user's personal_info # after "Hi I'm Erik Simon". Reusing gemma4:12b for both roles also means # no second model needs to swap into VRAM while it's already the active # chat model. # qwen3.6:35b-a3b: MoE (3B active/36B total), ~24GB Q4_K_M — doesn't fit # in VRAM alone, so ollama offloads the inactive experts to CPU RAM. # Sparse activation makes that far less painful than it'd be for a dense # model this size, but still expect it to run slower than the two above. loadModels = [ "gemma4:12b" "qwen3.6:35b-a3b" ]; # Ollama truncates context far below the model's real window unless # told otherwise (the OpenAI-compat /v1 route it's reached through has # no way to set this per-request). 131072 chosen as the practical # ceiling after load-testing with real prompts, not just idle # `ollama ps` checks: # 32768 (31.6k-token prompt) and 65536 (40.8k-token prompt) both stayed # 100% GPU with VRAM barely moving (~10.1G / ~10.67G of 16G) — KV cache # cost barely grows with context, likely sliding-window/local attention # on most of gemma4:12b's layers. At 131072 that stopped being true: a # ~108k-token prompt pushed VRAM to ~11.4G/16G (still 100% GPU, no CPU # spillover, negligible GTT) but with visibly shrinking headroom, and # prefill throughput measurably dropped (~490 -> ~460 tok/s) over just # the last 13k tokens — filling the full window would take minutes of # pure prompt processing. Stopped here rather than push further: next # doubling would risk CPU spillover under any concurrent GPU load # (desktop compositor, jellyfin transcode) for diminishing benefit. environmentVariables.OLLAMA_CONTEXT_LENGTH = "131072"; }; # ---- Dev-data disks — NOT in disko, mounted read-write, never wiped ---- fileSystems."/mnt/hdd_01" = { device = "/dev/disk/by-uuid/b8445126-ec6d-4f88-818a-d9e13031d9a4"; fsType = "ext4"; options = [ "nofail" ]; }; fileSystems."/mnt/ssd_01" = { device = "/dev/disk/by-uuid/6ca18a9f-27bc-4e58-aea8-de43a0d0ed5d"; fsType = "ext4"; options = [ "nofail" ]; }; # jupiter's samba share (services/network/samba.nix) — mounted on demand so # terra doesn't stall boot/login when jupiter is off or unreachable. fileSystems."/mnt/jupiter" = { device = "//jupiter/data"; fsType = "cifs"; options = [ "credentials=${config.sops.templates."jupiter-smb.credentials".path}" "uid=1000" "gid=100" "nofail" "x-systemd.automount" "x-systemd.idle-timeout=60" "x-systemd.mount-timeout=10s" "_netdev" ]; }; system.stateVersion = "26.05"; }