# hosts/FredOS-Gaming.nix { config, pkgs, lib, inputs, ... }: { config = lib.mkIf (config.networking.hostName == "FredOS-Gaming") { environment.systemPackages = with pkgs; [ mangohud gamescope spotify # Preselect the last stream's audio source (e.g. Hearthstone) when it's # running again, instead of resetting to "None" every Go Live. # The Wayland dialog opens instantly with the preview filling in, and # "Pick Another Window" or shift-clicking share bypasses the game # auto-pick (see xdph.conf in settings/hyprland.nix). (vesktop.overrideAttrs (old: { patches = (old.patches or [ ]) ++ [ ../patches/vesktop-remember-stream-audio.patch ../patches/vesktop-wayland-share-dialog.patch ]; })) lsfg-vk lsfg-vk-ui faugus-launcher haruna gnome-text-editor wowup-cf proton-vpn onlyoffice-desktopeditors qbittorrent cmatrix protonplus unrar p7zip inputs.loupe.packages.${pkgs.stdenv.hostPlatform.system}.default ]; # Nova Pro Wireless base station: open up its vendor HID interface so # quickshell's ANC switcher can write to it directly (nova-anc script in # settings/quickshell.nix). Single-user desktop, 0666 is fine. services.udev.extraRules = '' KERNEL=="hidraw*", ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12e0", MODE="0666" KERNEL=="hidraw*", ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12e5", MODE="0666" KERNEL=="hidraw*", ATTRS{idVendor}=="1038", ATTRS{idProduct}=="225d", MODE="0666" ''; # Force heavy builds off the gaming PC onto the media server so nix updates # never compete with games for CPU/RAM. Pairs with the distributed build # config in common.nix. builders-use-substitutes lets the server pull from # binary caches directly rather than re-uploading through this machine. # # max-jobs = 1 (not 0): trivial preferLocalBuild = true derivations (e.g. # pkgs.writeText for HM config files) are rejected by the remote build hook # and can't be substituted, so they must build locally. They finish in # milliseconds and have no gaming impact. All real compilation still goes to # the remote via the distributed build config. nix.settings.max-jobs = 1; nix.settings.builders-use-substitutes = true; # Lets the server (`ssh gaming` there, 10.0.0.20 reserved in devices.toml) # in for remote fixes. LAN only: nothing in ports.toml forwards to it. services.openssh = { enable = true; settings = { PermitRootLogin = "no"; PasswordAuthentication = false; KbdInteractiveAuthentication = false; }; }; users.users.fred.openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMbIdOSPjnJYQ7z7n0mHKo8T8PnAkCyIBCmT4KpsHyy/ fred@FredOS-Mediaserver" ]; programs.steam = { enable = true; remotePlay.openFirewall = true; package = pkgs.steam.override { extraPkgs = pkgs: with pkgs; [ adwaita-icon-theme ]; }; }; boot.loader.systemd-boot.configurationLimit = 5; boot.initrd.systemd.enable = true; # Early-console mode hint only (Hyprland sets the real mode at runtime via # the desc-matched rule below). Connector name has drifted DP-2/DP-3. # B650M DS3H's ACPI claims the SMBus, hiding the Trident Z5 RAM from # OpenRGB; `lax` lets i2c-piix4 take it. boot.kernelParams = [ "video=DP-3:3440x1440@180" "acpi_enforce_resources=lax" ]; # RAM/mobo RGB control (i2c-piix4 + i2c-dev, udev rules). services.hardware.openrgb = { enable = true; motherboard = "amd"; }; # LAN has no IPv6 route — AAAA lookups succeed but connect fails, which # made NetworkManager's connectivity probe report "limited" at boot # (GNOME's "?" icon) until the next 5-min repoll. # # `enableIPv6 = false` only sets the system-wide sysctl; NetworkManager # still flips disable_ipv6=0 on the live interface because each connection # defaults to `ipv6.method = auto`. The probe then races over a SLAAC ULA # that has no real upstream and we get the "?" again. Force every NM # connection to skip v6 altogether and ignore router advertisements at # the kernel layer for any future interface. networking.enableIPv6 = false; networking.networkmanager.connectionConfig."ipv6.method" = "disabled"; boot.kernel.sysctl = { "net.ipv6.conf.all.accept_ra" = 0; "net.ipv6.conf.default.accept_ra" = 0; # Escape hatch for a wedged GPU: Alt+SysRq+B reboots without the power # button (amdgpu MODE1 reset hung the box on 2026-08-06). "kernel.sysrq" = 1; }; # ...and if the hang takes systemd with it, the chipset watchdog reboots # after 30s on its own, landing back at the autologin session. systemd.settings.Manager = { RuntimeWatchdogSec = "30s"; RebootWatchdogSec = "10min"; }; # NetworkManager only re-runs its connectivity probe on link-up or every # 5 minutes. If the first boot probe races NM startup and reports # "limited", GNOME caches that state until the next repoll — which is # what surfaces as the persistent "?" icon. This oneshot kicks an # explicit recheck once everything's online, which forces NM to # transition to "full" and the icon clears. systemd.services.nm-connectivity-kick = { description = "Force NetworkManager connectivity recheck after boot"; after = [ "NetworkManager.service" "network-online.target" ]; wants = [ "network-online.target" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "oneshot"; ExecStart = "${pkgs.networkmanager}/bin/nmcli networking connectivity check"; }; }; home-manager.users.fred = { ... }: { # Connector drifted DP-2 -> DP-3 after an HDMI detour; desc: matching # parsed fine (configerrors ok) but never actually applied the mode, # whereas plain connector-name matching is proven to hold 180Hz here. wayland.windowManager.hyprland.settings.monitor = [{ output = "DP-3"; mode = "3440x1440@180"; position = "0x0"; scale = 1; # G34WQCP is a wide-gamut VA panel (120% sRGB / 90% DCI-P3). Hyprland # defaults to cm "srgb", i.e. no conversion, so sRGB content came out # oversaturated. Clamp here rather than via the monitor's own sRGB OSD # preset, which locks overdrive off — bad on VA. Panel is natively # 8-bit, so no bitdepth override. cm = "dcip3"; }]; # Not "Proton-CachyOS Latest": Faugus owns that name and, on every # launch, re-downloaded 330MB trying to replace this symlink with its # own copy. Faugus entries use this as runner "Proton-CachyOS Nix". # Hearthstone Deck Tracker's overlay buttons (apps/hdt-wine-overlay for # the rest of the overlay fixes). XWayland gives a click to the topmost # X window under the pointer, and Hyprland raises the game above the # override-redirect overlay in X whenever it focuses it, so the buttons' # clicks landed in the game. Wine never passes the overlay's own # restacks on to X, so raise it from outside while the game is active. systemd.user.services.hdt-overlay-raise = { Unit = { Description = "Keep HDT's overlay above Hearthstone in X"; After = [ "graphical-session.target" ]; PartOf = [ "graphical-session.target" ]; }; Service = { ExecStart = lib.getExe (pkgs.writers.writePython3Bin "hdt-overlay-raise" { libraries = [ pkgs.python3Packages.xlib ]; } '' import time from Xlib import X, display d = display.Display() root = d.screen().root active_atom = d.intern_atom("_NET_ACTIVE_WINDOW") names = {} while True: kids = root.query_tree().children # bottom -> top ids = [w.id for w in kids] for gone in set(names) - set(ids): del names[gone] found = {} for w in kids: if w.id not in names: try: names[w.id] = w.get_wm_name() except Exception: names[w.id] = None if names[w.id] in ("Hearthstone", "HearthstoneOverlay"): found[names[w.id]] = w overlay = found.get("HearthstoneOverlay") game = found.get("Hearthstone") if not (overlay and game): time.sleep(1) continue prop = root.get_full_property(active_atom, X.AnyPropertyType) active = prop.value[0] if prop else 0 if active == game.id and ids.index(overlay.id) < ids.index(game.id): overlay.configure(stack_mode=X.Above) d.sync() time.sleep(0.1) ''); # X connection lost (Hyprland/XWayland restart): start over. Restart = "always"; RestartSec = 5; }; Install.WantedBy = [ "graphical-session.target" ]; }; home.file.".local/share/Steam/compatibilitytools.d/Proton-CachyOS Nix".source = inputs.proton-cachyos-nix.packages.x86_64-linux.proton-cachyos-x86_64-v3.steamcompattool; }; }; }