2025-12-09 16:14:43 +00:00
|
|
|
# hosts/FredOS-Gaming.nix
|
2026-03-28 13:42:35 +00:00
|
|
|
{ config, pkgs, lib, inputs, ... }:
|
2025-12-09 16:14:43 +00:00
|
|
|
{
|
|
|
|
|
config = lib.mkIf (config.networking.hostName == "FredOS-Gaming") {
|
|
|
|
|
environment.systemPackages = with pkgs; [
|
2026-01-08 12:18:35 +00:00
|
|
|
mangohud
|
2026-04-18 21:53:15 +01:00
|
|
|
gamescope
|
2026-08-19 17:50:59 +01:00
|
|
|
spotify
|
2026-10-12 20:00:48 +01:00
|
|
|
# Preselect the last stream's audio source (e.g. Hearthstone) when it's
|
|
|
|
|
# running again, instead of resetting to "None" every Go Live.
|
|
|
|
|
(vesktop.overrideAttrs (old: {
|
|
|
|
|
patches = (old.patches or [ ]) ++ [ ../patches/vesktop-remember-stream-audio.patch ];
|
|
|
|
|
}))
|
2026-01-08 15:17:30 +00:00
|
|
|
lsfg-vk
|
2026-01-08 15:36:54 +00:00
|
|
|
lsfg-vk-ui
|
2026-01-16 23:41:18 +00:00
|
|
|
faugus-launcher
|
2026-08-21 09:39:39 +01:00
|
|
|
haruna
|
2026-06-01 15:29:25 +01:00
|
|
|
gnome-text-editor
|
2026-03-01 22:42:47 +00:00
|
|
|
wowup-cf
|
2026-05-18 10:48:22 +01:00
|
|
|
proton-vpn
|
2026-03-28 15:57:10 +00:00
|
|
|
onlyoffice-desktopeditors
|
2026-06-05 23:22:17 +01:00
|
|
|
qbittorrent
|
2026-07-28 11:57:01 +01:00
|
|
|
cmatrix
|
2026-08-01 22:29:01 +01:00
|
|
|
protonplus
|
2026-09-03 12:02:02 +01:00
|
|
|
unrar
|
|
|
|
|
p7zip
|
2026-09-18 20:49:00 +01:00
|
|
|
inputs.loupe.packages.${pkgs.stdenv.hostPlatform.system}.default
|
2025-12-09 16:00:56 +00:00
|
|
|
];
|
2026-04-01 21:14:16 +01:00
|
|
|
|
2026-07-28 19:35:25 +01:00
|
|
|
# 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"
|
|
|
|
|
'';
|
2026-07-28 19:21:37 +01:00
|
|
|
|
2026-05-17 22:32:34 +01:00
|
|
|
# Force heavy builds off the gaming PC onto the media server so nix updates
|
2026-05-17 21:01:00 +01:00
|
|
|
# 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.
|
2026-05-17 22:32:34 +01:00
|
|
|
#
|
|
|
|
|
# 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;
|
2026-05-17 21:01:00 +01:00
|
|
|
nix.settings.builders-use-substitutes = true;
|
|
|
|
|
|
2026-10-08 08:27:26 +01:00
|
|
|
# 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"
|
|
|
|
|
];
|
|
|
|
|
|
2025-12-09 15:58:30 +00:00
|
|
|
programs.steam = {
|
|
|
|
|
enable = true;
|
|
|
|
|
remotePlay.openFirewall = true;
|
|
|
|
|
package = pkgs.steam.override {
|
|
|
|
|
extraPkgs = pkgs: with pkgs; [
|
2025-12-10 20:08:07 +00:00
|
|
|
adwaita-icon-theme
|
2025-12-09 15:58:30 +00:00
|
|
|
];
|
|
|
|
|
};
|
2025-12-09 16:14:43 +00:00
|
|
|
};
|
2026-04-01 21:14:16 +01:00
|
|
|
|
2026-03-28 15:28:37 +00:00
|
|
|
boot.loader.systemd-boot.configurationLimit = 5;
|
|
|
|
|
boot.initrd.systemd.enable = true;
|
2026-06-07 20:40:26 +01:00
|
|
|
# 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.
|
2026-10-05 20:35:06 +01:00
|
|
|
# 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";
|
|
|
|
|
};
|
2026-04-28 13:00:59 +01:00
|
|
|
|
|
|
|
|
# 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.
|
2026-05-02 23:06:33 +01:00
|
|
|
#
|
|
|
|
|
# `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.
|
2026-04-28 13:00:59 +01:00
|
|
|
networking.enableIPv6 = false;
|
2026-05-02 23:06:33 +01:00
|
|
|
networking.networkmanager.connectionConfig."ipv6.method" = "disabled";
|
|
|
|
|
boot.kernel.sysctl = {
|
|
|
|
|
"net.ipv6.conf.all.accept_ra" = 0;
|
|
|
|
|
"net.ipv6.conf.default.accept_ra" = 0;
|
2026-08-06 21:07:46 +01:00
|
|
|
# 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";
|
2026-05-02 23:06:33 +01:00
|
|
|
};
|
2026-05-06 15:56:09 +01:00
|
|
|
|
|
|
|
|
# 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";
|
|
|
|
|
};
|
|
|
|
|
};
|
2026-05-14 10:44:32 +01:00
|
|
|
|
|
|
|
|
home-manager.users.fred = { ... }: {
|
2026-06-08 09:41:17 +01:00
|
|
|
# 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.
|
2026-05-17 20:38:30 +01:00
|
|
|
wayland.windowManager.hyprland.settings.monitor = [{
|
2026-06-08 09:41:17 +01:00
|
|
|
output = "DP-3";
|
2026-05-17 20:38:30 +01:00
|
|
|
mode = "3440x1440@180";
|
|
|
|
|
position = "0x0";
|
|
|
|
|
scale = 1;
|
2026-07-29 12:19:58 +01:00
|
|
|
# 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";
|
2026-05-17 20:38:30 +01:00
|
|
|
}];
|
2026-05-15 20:20:35 +01:00
|
|
|
|
2026-10-08 10:02:46 +01:00
|
|
|
# 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".
|
2026-10-08 11:20:10 +01:00
|
|
|
# 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" ];
|
|
|
|
|
};
|
|
|
|
|
|
2026-10-08 10:02:46 +01:00
|
|
|
home.file.".local/share/Steam/compatibilitytools.d/Proton-CachyOS Nix".source =
|
2026-05-15 20:20:35 +01:00
|
|
|
inputs.proton-cachyos-nix.packages.x86_64-linux.proton-cachyos-x86_64-v3.steamcompattool;
|
2026-05-14 10:44:32 +01:00
|
|
|
};
|
2025-12-09 16:14:43 +00:00
|
|
|
};
|
|
|
|
|
}
|