From 654a689fe166cab922e66f1677c407219d1c5296 Mon Sep 17 00:00:00 2001 From: Nexoniarz Date: Fri, 25 Sep 2026 00:02:56 +0200 Subject: [PATCH] Bring laptop in line with desktop config: Plasma 6, Limine, module split The laptop was still on the Hyprland-era Pantheon commit while the desktop moved to KDE Plasma 6 + Limine and split system.nix into focused modules. This brings over everything that isn't hardware, unchanged, so both machines stay diffable: - Plasma 6 on Wayland with SDDM, Konsole, gruvbox icons, KDE MIME defaults (replaces Pantheon/LightDM; libreoffice-qt6 replaces the GTK build) - Limine bootloader with the gruvbox theme, replacing systemd-boot - system.nix split into boot/networking/locale/nix-settings/security - flakes + nix-command, sysctl hardening extras (yama, tcp_timestamps), geoclue off, captive-portal probe off - package parity: obs-studio, lmms, go, qemu, KDE utilities, lean SDR++ and rtl_433 overrides with hardware.rtl-sdr Hardware and power are adapted rather than copied: - hardware.nix keeps the AMD 680M + RTX 4050 PRIME offload setup, asusd and supergfxd, v4l2loopback webcam and the DualSense ERTM fix; OpenRGB and the desktop's single-GPU block are dropped, keyboard lighting is asusctl - power.nix is new: power-profiles-daemon for Plasma's profile switcher, lid-close handling via the current logind.settings.Login spelling, powertop/acpi, notes on asusctl charge limits - no acpi_enforce_resources=lax, ACPI owns the EC asusd drives here - touchpad defaults, and firewall openings so the hotspot script's shared connection can hand out DHCP/DNS - pinentry-qt instead of the desktop's leftover pinentry-rofi, blueman dropped in favour of Plasma's bluedevil hardware-configuration.nix stays tracked here (unlike the desktop, which gitignores it) since it's the only record of this machine's disk layout. Co-Authored-By: Claude Opus 5 --- .gitignore | 3 + README.md | 63 ++++++++---- configuration.nix | 31 ++++-- modules/apps.nix | 92 +++++++++++++++--- modules/audio.nix | 12 +++ modules/boot.nix | 47 +++++++++ modules/default-apps.nix | 80 +++++++++++++++ modules/desktop.nix | 82 +++++++++++----- modules/drivers.nix | 79 --------------- modules/hardware.nix | 110 +++++++++++++++++++++ modules/locale.nix | 19 ++++ modules/networking.nix | 52 ++++++++++ modules/nix-settings.nix | 14 +++ modules/power.nix | 48 +++++++++ modules/security.nix | 40 ++++++++ modules/system.nix | 204 --------------------------------------- modules/users.nix | 14 +-- 17 files changed, 634 insertions(+), 356 deletions(-) create mode 100644 .gitignore create mode 100644 modules/audio.nix create mode 100644 modules/boot.nix create mode 100644 modules/default-apps.nix delete mode 100644 modules/drivers.nix create mode 100644 modules/hardware.nix create mode 100644 modules/locale.nix create mode 100644 modules/networking.nix create mode 100644 modules/nix-settings.nix create mode 100644 modules/power.nix create mode 100644 modules/security.nix delete mode 100644 modules/system.nix diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1b37743 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.claude +result +result-* diff --git a/README.md b/README.md index b714743..296560a 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,18 @@ # nexoniarz-config-nixos-laptop My personal NixOS system configuration tailored for daily use on an -**ASUS TUF Gaming A15 (FA507NU)**. +**ASUS TUF Gaming A15 (FA507NU)**. Same layout and same choices as +[nexoniarz-config-nixos](https://github.com/Nexoniarz/nexoniarz-config-nixos) +(the desktop), with the hardware and power modules swapped for this machine. ## Overview -- **OS:** NixOS -- **DE / Compositor:** Pantheon (gala, Wayland) -- **Display manager:** LightDM (elementary greeter) +- **OS:** NixOS 26.05 (unstable channel) +- **Desktop:** KDE Plasma 6 on Wayland (KWin), SDDM +- **Bootloader:** Limine +- **Terminal:** Konsole - **Shell:** Zsh +- **Audio:** PipeWire - **CPU:** AMD Ryzen 5 7535HS - **GPU:** AMD Radeon 680M (iGPU, drives the internal panel) + NVIDIA RTX 4050 Max-Q (dGPU, PRIME render offload only — run anything through it with @@ -18,19 +22,44 @@ My personal NixOS system configuration tailored for daily use on an ## Layout -- `configuration.nix` — top-level imports -- `hardware-configuration.nix` — generated by `nixos-generate-config`, don't - hand-edit -- `modules/system.nix` — bootloader, networking, time, sound, hardening, - nix-ld -- `modules/drivers.nix` — hybrid graphics (NVIDIA PRIME offload), ASUS - daemons, misc hardware (webcam loopback, tablet driver) -- `modules/desktop.nix` — Pantheon desktop environment -- `modules/users.nix` — user account, Steam, Firefox, Flatpak -- `modules/apps.nix` — all installed packages -- `modules/scripts.nix` + `modules/scripts/` — small custom CLI helpers - (encrypted vault toggle, metadata stripping, brightness control, Wi-Fi - hotspot, system telemetry, weather fetch) +| File | Contents | +|---|---| +| `configuration.nix` | Imports only | +| `hardware-configuration.nix` | Generated by `nixos-generate-config`, don't hand-edit | +| `modules/boot.nix` | Limine, kernel, zram/swap | +| `modules/networking.nix` | NetworkManager, firewall, DNS-over-TLS, MAC randomization | +| `modules/locale.nix` | Timezone, locale, console keymap | +| `modules/nix-settings.nix` | Nix features, GC, unfree | +| `modules/security.nix` | sysctl hardening, sudo, firejail, GnuPG, polkit | +| `modules/hardware.nix` | Hybrid graphics (PRIME offload), ASUS daemons, Bluetooth, I2C, RTL-SDR, webcam loopback, tablet, printing | +| `modules/power.nix` | Power profiles, lid/suspend behaviour, battery notes | +| `modules/audio.nix` | PipeWire | +| `modules/desktop.nix` | Plasma, SDDM, touchpad, fonts, theming | +| `modules/users.nix` | Account, shell, Steam, Flatpak | +| `modules/apps.nix` | Installed packages, nix-ld | +| `modules/default-apps.nix` | MIME associations | +| `modules/scripts.nix` | Custom scripts in `modules/scripts/` | + +Small custom CLI helpers live in `modules/scripts/`: encrypted vault toggle, +metadata stripping, brightness control (kernel backlight or DDC/CI), Wi-Fi +hotspot, system telemetry, weather fetch. + +## Differences from the desktop config + +Everything outside hardware is kept identical on purpose. What differs: + +- `modules/hardware.nix` — AMD iGPU + NVIDIA dGPU on PRIME offload with + `asusd`/`supergfxd`, instead of the desktop's single NVIDIA card and + OpenRGB motherboard lighting. Keyboard lighting is `asusctl aura` here. +- `modules/power.nix` — laptop-only module: power-profiles-daemon (Plasma's + power profile switcher), lid-close behaviour, `powertop`/`acpi`. +- `modules/desktop.nix` — adds touchpad defaults and the Wayland/Ozone flag. +- `modules/boot.nix` — no `acpi_enforce_resources=lax`; ACPI owns the EC that + `asusd` talks to on this machine. +- `modules/networking.nix` — firewall openings for the hotspot script's + `ipv4.method shared` dnsmasq. +- `modules/security.nix` — `pinentry-qt` (Plasma) rather than the desktop's + leftover `pinentry-rofi`. --- diff --git a/configuration.nix b/configuration.nix index 4f14d8d..42d7f6f 100644 --- a/configuration.nix +++ b/configuration.nix @@ -1,18 +1,29 @@ -# Main NixOS configuration file importing modular configurations. - { config, pkgs, ... }: { imports = [ - ./hardware-configuration.nix # Generated hardware scan results - ./modules/system.nix # Bootloader, networking, time, sound, hardening, nix-ld - ./modules/drivers.nix # AMD/NVIDIA hybrid graphics (PRIME) and ASUS laptop control - ./modules/desktop.nix # Pantheon desktop environment (LightDM + gala) - ./modules/users.nix # User accounts and global configurations - ./modules/apps.nix # All installed packages: apps, CLI tools, dev toolchains - ./modules/scripts.nix # Custom system scripts and helpers + ./hardware-configuration.nix + + # Core system + ./modules/boot.nix + ./modules/networking.nix + ./modules/locale.nix + ./modules/nix-settings.nix + ./modules/security.nix + + # Hardware + ./modules/hardware.nix + ./modules/power.nix + ./modules/audio.nix + + # Desktop & user + ./modules/desktop.nix + ./modules/users.nix + ./modules/apps.nix + ./modules/default-apps.nix + ./modules/scripts.nix ]; # Do not change this value. Read documentation before updating. - system.stateVersion = "26.05"; + system.stateVersion = "26.05"; } diff --git a/modules/apps.nix b/modules/apps.nix index 631d2aa..f2d1382 100644 --- a/modules/apps.nix +++ b/modules/apps.nix @@ -1,16 +1,46 @@ { config, pkgs, ... }: +let + + # Both SDR++ and rtl_433 default to supporting every radio ever made, which + # drags in UHD (USRP firmware), BladeRF and LimeSuite — ~1.5 GB for + # hardware we don't own. These two overrides cut that down to the RTL-SDR paths. + + soapysdrLean = pkgs.soapysdr.override { + extraPackages = with pkgs; [ + soapyrtlsdr + soapyremote + ]; + }; + + rtl433Lean = pkgs.rtl_433.override { + soapysdr-with-plugins = soapysdrLean; + }; + + # Leaves the RTL-SDR, rtl_tcp, file and network sources on. + sdrppLean = pkgs.sdrpp.override { + soapy_source = false; + bladerf_source = false; + limesdr_source = false; + plutosdr_source = false; + airspy_source = false; + airspyhf_source = false; + hermes_source = false; + rfspace_source = false; + dragonlabs_source = false; + spectran_http_source = false; + }; + +in { - # Every installed package — GUI apps, CLI tools, and dev toolchains alike. - # If it's "install X", it goes here. users.users."nexoniarz".packages = with pkgs; [ - # Hardware control (Pantheon itself ships Files/Terminal/Screenshot/ - # notifications/dock/launcher — see services.desktopManager.pantheon - # in modules/desktop.nix, nothing to list here for those) - brightnessctl # Backlight brightness (internal laptop panel) - ddcutil # DDC/CI brightness (external monitors) - lm_sensors # sensors-detect helps ddcutil find I2C buses; also feeds asusctl - networkmanager # nmcli, for the Wi-Fi hotspot script + + tumbler + brightnessctl # Backlight on the internal panel + ddcutil # DDC/CI brightness on an external monitor + lm_sensors # sensors-detect also helps ddcutil find its I2C buses + networkmanager # nmcli, for the Wi-Fi hotspot script + xdg-user-dirs # Apps blender @@ -22,35 +52,65 @@ vlc filezilla chromium + obs-studio + sdrppLean + rtl433Lean + lmms # Games prismlauncher - pkgs.osu-lazer-bin + osu-lazer-bin - # Dev: compilers, runtimes, languages + # Dev glibc musl cmake - gnumake # Provides the 'make' command + gnumake gcc python3 openjdk25 - claude-code # AI Developer Assistant + go + claude-code ]; - # Global command-line utilities available everywhere environment.systemPackages = with pkgs; [ git - gh # GitHub CLI — was only installed ad-hoc before (login didn't survive) + gh vim fastfetch gparted + efibootmgr firejail gnupg lmstudio + kdePackages.ark p7zip unzip unrar - libreoffice # GTK3 build — matches Pantheon's native GTK theming, unlike the Qt build + kdePackages.gwenview + kdePackages.kate + kdePackages.qqc2-desktop-style + linphonePackages.linphone-desktop + kdePackages.kcalc + kdePackages.okular + kdePackages.elisa + kdePackages.konsole + kdePackages.dolphin + kdePackages.plasma-systemmonitor + qt5.qtwayland + qt6.qtwayland + qemu + file + ]; + + # Lets non-Nix binaries (e.g. the Tor Browser bundle) find an FHS linker. + programs.nix-ld.enable = true; + programs.nix-ld.libraries = with pkgs; [ + glib gtk3 gdk-pixbuf pango cairo atk + dbus dbus-glib fontconfig freetype + libx11 libxext libxrender libxtst + libxi libxcomposite libxdamage libxfixes + libxrandr libxcursor libxcb libxkbcommon + alsa-lib mesa zlib stdenv.cc.cc ]; } diff --git a/modules/audio.nix b/modules/audio.nix new file mode 100644 index 0000000..9b8434f --- /dev/null +++ b/modules/audio.nix @@ -0,0 +1,12 @@ +{ config, pkgs, ... }: + +{ + security.rtkit.enable = true; + services.pulseaudio.enable = false; + services.pipewire = { + enable = true; + alsa.enable = true; + alsa.support32Bit = true; + pulse.enable = true; + }; +} diff --git a/modules/boot.nix b/modules/boot.nix new file mode 100644 index 0000000..07a3d9c --- /dev/null +++ b/modules/boot.nix @@ -0,0 +1,47 @@ +{ config, lib, pkgs, ... }: + +{ + boot.loader.efi.canTouchEfiVariables = true; + boot.loader.limine = { + enable = true; + maxGenerations = 5; + + # Gruvbox, to match the icon theme. + style = { + wallpapers = [ ]; # Solid background; a path here would be used instead + interface = { + branding = "NixOS"; + brandingColor = "fabd2f"; + helpColor = "928374"; + helpColorBright = "d79921"; + }; + graphicalTerminal = { + foreground = "ebdbb2"; + background = "00282828"; + brightForeground = "fbf1c7"; + palette = "282828;cc241d;98971a;d79921;458588;b16286;689d6a;a89984"; + brightPalette = "928374;fb4934;b8bb26;fabd2f;83a598;d3869b;8ec07c;ebdbb2"; + margin = 32; + }; + }; + }; + + boot.kernelPackages = pkgs.linuxPackages; + boot.supportedFilesystems = [ "fuse" ]; + programs.fuse.userAllowOther = true; + + # zram instead of disk swap: nothing sensitive is written to disk in + # plaintext. systemd's gpt-auto-generator would otherwise re-activate the + # swap partition hardware-configuration.nix still lists, hence the kernel + # param. / /home /nix /boot are declared explicitly there, so they don't + # depend on gpt-auto. + zramSwap.enable = true; + swapDevices = lib.mkForce [ ]; + + # Unlike the desktop this doesn't carry acpi_enforce_resources=lax: that + # only exists there to let lm_sensors poke the Super I/O chip behind ACPI's + # back, and on a laptop — where ACPI genuinely owns the EC that asusd talks + # to — relaxing it is how you get fan/thermal weirdness. CPU temps come + # from k10temp, everything else from asusd. + boot.kernelParams = [ "systemd.gpt_auto=0" ]; +} diff --git a/modules/default-apps.nix b/modules/default-apps.nix new file mode 100644 index 0000000..8f43767 --- /dev/null +++ b/modules/default-apps.nix @@ -0,0 +1,80 @@ +{ config, pkgs, lib, ... }: + +{ + environment.systemPackages = with pkgs; [ + libreoffice-qt6 + ]; + + # GIO picks the terminal from here for "Open Terminal" style actions. + environment.etc."xdg/xdg-terminals.list".text = '' + org.kde.konsole.desktop + ''; + + # System-level fallbacks. Per the XDG spec these are only consulted for a + # mimetype the user's own ~/.config/mimeapps.list doesn't already claim, + # so any "Open With" choice you make later still wins over this. + # + # One mimetype=desktopfile pair per line. The semicolon only separates + # multiple candidates for the SAME key — it does not chain separate pairs, + # and parsers match by scanning for a line starting with "mimetype=", so + # cramming several onto one line silently hides all but the first. + environment.etc."xdg/mimeapps.list".text = + let + gwenviewMimes = [ + "image/avif" "image/gif" "image/heif" "image/jpeg" + "image/jxl" "image/png" "image/bmp" "image/x-eps" "image/x-icns" + "image/x-ico" "image/x-portable-bitmap" "image/x-portable-graymap" + "image/x-portable-pixmap" "image/x-xbitmap" "image/x-xpixmap" + "image/tiff" "image/x-psd" "image/x-webp" "image/webp" "image/x-tga" + "image/x-xcf" "image/openraster" "image/svg+xml" "image/svg+xml-compressed" + ]; + arkMimes = [ + "application/x-cd-image" "application/x-bcpio" "application/x-cpio" + "application/x-cpio-compressed" "application/x-rpm" "application/x-compress" + "application/gzip" "application/x-bzip" "application/x-bzip2" + "application/x-lzma" "application/x-xz" "application/zstd" "application/x-lz4" + "application/x-source-rpm" "application/vnd.debian.binary-package" + "application/vnd.ms-cab-compressed" "application/x-archive" "application/x-tar" + "application/x-compressed-tar" "application/x-bzip-compressed-tar" + "application/x-bzip2-compressed-tar" "application/x-tarz" + "application/x-xz-compressed-tar" "application/x-lzma-compressed-tar" + "application/x-7z-compressed" "application/vnd.rar" "application/zip" + "application/x-java-archive" "application/x-arj" + ]; + okularMimes = [ + "application/pdf" "application/x-pdf" "image/vnd.djvu" "application/epub+zip" + "application/x-fictionbook+xml" "application/x-mobipocket-ebook" + "image/x-portable-document" "application/x-cbr" "application/x-cbz" + "application/x-cbt" "application/x-cb7" "application/postscript" + ]; + vlcMimes = [ + "video/mp4" "video/x-matroska" "video/webm" "video/x-msvideo" + "video/quicktime" "video/mpeg" "video/x-ms-wmv" "video/x-flv" + "video/3gpp" "video/ogg" "video/x-theora+ogg" "video/x-ogm+ogg" + "application/x-matroska" "application/vnd.rn-realmedia" + ]; + elisaMimes = [ + "audio/mpeg" "audio/flac" "audio/x-flac" "audio/ogg" "audio/x-vorbis+ogg" + "audio/opus" "audio/x-opus+ogg" "audio/mp4" "audio/aac" "audio/x-wav" + "audio/wav" "audio/x-ms-wma" "audio/x-aiff" "audio/x-musepack" + ]; + kateMimes = [ + "text/plain" "text/markdown" "text/x-csrc" "text/x-c++src" "text/x-chdr" + "text/x-c++hdr" "text/x-python" "text/x-java" "text/x-shellscript" + "text/x-nix" "text/css" "text/html" "text/xml" "application/json" + "application/x-yaml" "application/toml" "application/javascript" + "application/x-shellscript" "text/x-log" + ]; + line = desktop: mime: "${mime}=${desktop}"; + in + '' + [Default Applications] + inode/directory=org.kde.dolphin.desktop + ${lib.concatMapStringsSep "\n" (line "org.kde.gwenview.desktop") gwenviewMimes} + ${lib.concatMapStringsSep "\n" (line "org.kde.ark.desktop") arkMimes} + ${lib.concatMapStringsSep "\n" (line "org.kde.okular.desktop") okularMimes} + ${lib.concatMapStringsSep "\n" (line "vlc.desktop") vlcMimes} + ${lib.concatMapStringsSep "\n" (line "org.kde.elisa.desktop") elisaMimes} + ${lib.concatMapStringsSep "\n" (line "org.kde.kate.desktop") kateMimes} + ''; +} diff --git a/modules/desktop.nix b/modules/desktop.nix index 32d30b5..2f339d3 100644 --- a/modules/desktop.nix +++ b/modules/desktop.nix @@ -1,38 +1,74 @@ { config, pkgs, ... }: { - # --- Pantheon: elementary OS's desktop environment (gala compositor, Wayland) --- - # Pulls in its own session (pantheon-wayland), top panel (Wingpanel), dock, - # app launcher, Files, Terminal, Screenshot, Photos, notifications, and - # GTK/icon theming — no separate compositor config or dotfile symlinking - # needed, unlike the previous Hyprland setup. + # Plasma ships a proper Wayland session (KWin) with solid Xwayland + # support; xserver stays on for that Xwayland layer and for the xkb + # layout below, not because the session itself needs it. services.xserver.enable = true; - services.xserver.displayManager.lightdm.enable = true; - services.desktopManager.pantheon.enable = true; + services.desktopManager.plasma6.enable = true; + + # SDDM is KDE's own greeter — Qt-based, and launches Wayland sessions + # natively, Plasma included. + services.displayManager.sddm = { + enable = true; + wayland.enable = true; + }; - # Keyboard layout for LightDM/XWayland; Pantheon's own session picks this - # up too. services.xserver.xkb = { layout = "pl"; variant = ""; }; - security.polkit.enable = true; + services.tumbler.enable = true; + services.gvfs.enable = true; - # Native Wayland backend for Electron/Chromium-based apps + # xserver.nix installs xterm unconditionally; Konsole is the terminal here. + services.xserver.excludePackages = [ pkgs.xterm ]; + + # Native Wayland backend for the Electron/Chromium apps (vesktop, chromium, + # lmstudio) instead of letting them fall back to Xwayland. environment.sessionVariables.NIXOS_OZONE_WL = "1"; - # Comfortable trackpad defaults for a laptop — tap-to-click and natural - # scrolling aren't on by default, and gala reads touchpad settings from - # GSettings at runtime (not services.libinput, which is X11-only), so - # they're set here as a dconf default instead. - programs.dconf.profiles.user.databases = [ - { - settings."org/gnome/desktop/peripherals/touchpad" = { - tap-to-click = true; - natural-scroll = true; - two-finger-scrolling-enabled = true; - }; - } + # Laptop trackpad. This writes an Xorg/libinput snippet, so it covers + # Xwayland and any X11 session — KWin's own Wayland session keeps touchpad + # settings per-device in ~/.config/kcminputrc instead (System Settings → + # Mouse & Touchpad), and those keys are keyed by the device's VID/PID, so + # they can't be pinned down from another machine. Set them once there, or + # bring in plasma-manager if you want them declarative too. + services.libinput.enable = true; + services.libinput.touchpad = { + tapping = true; + naturalScrolling = true; + disableWhileTyping = true; + }; + + # Hybrid graphics: KWin normally picks the right GPU on its own, but if a + # session ever comes up rendering on the RTX 4050 (hot, loud, worse battery) + # pin it to the AMD iGPU — bus 35:00.0 from `lspci`. Verify the path exists + # before enabling, an invalid device makes KWin fail to start: + # ls /dev/dri/by-path/ + # environment.sessionVariables.KWIN_DRM_DEVICES = + # "/dev/dri/by-path/pci-0000:35:00.0-card"; + + # Icon/cursor theme packages. Plasma has no gsettings-style declarative + # knob for this the way Pantheon did — pick them in System Settings, or + # bring in plasma-manager later for a fully declarative setup. + environment.systemPackages = [ + pkgs.gruvbox-plus-icons + pkgs.bibata-cursors + ]; + + fonts.packages = with pkgs; [ + corefonts + dejavu_fonts + noto-fonts + noto-fonts-cjk-sans + noto-fonts-color-emoji + nerd-fonts.fira-code + nerd-fonts.jetbrains-mono + nerd-fonts.hack + inter + liberation_ttf + roboto ]; } diff --git a/modules/drivers.nix b/modules/drivers.nix deleted file mode 100644 index 0efa83e..0000000 --- a/modules/drivers.nix +++ /dev/null @@ -1,79 +0,0 @@ -{ config, lib, pkgs, ... }: - -{ - # Enable graphics driver support and 32-bit acceleration (for Steam/gaming) - hardware.graphics = { - enable = true; - enable32Bit = true; - }; - - #OpenTabletDriver - hardware.opentabletdriver.enable = true; - hardware.uinput.enable = true; - boot.kernelModules = [ "uinput" "v4l2loopback" ]; - - # Virtual webcam device so ffmpeg (fed by the GoPro's stream) can appear - # as a normal camera to apps like Discord. exclusive_caps=1 is required - # for Chromium/WebRTC-based apps to recognize it as a real capture device. - boot.extraModulePackages = [ config.boot.kernelPackages.v4l2loopback ]; - boot.extraModprobeConfig = '' - options v4l2loopback devices=1 video_nr=10 card_label="GoPro Webcam" exclusive_caps=1 - # PS4/PS5 controllers (e.g. DualSense) connect over Bluetooth then - # immediately disconnect — a well-known Linux Bluetooth stack issue - # where many adapters' Enhanced Retransmission Mode implementation is - # broken. Disabling ERTM is the standard, widely-documented fix. - options bluetooth disable_ertm=1 - ''; - - # I2C bus, for DDC/CI monitor control (brightness) via ddcutil, when - # driving an external monitor over DisplayPort/HDMI. The internal panel - # uses the kernel backlight class instead (brightness-ctl picks whichever - # applies — see modules/scripts/brightness-ctl.sh). - hardware.i2c.enable = true; - - # --- Hybrid graphics: AMD Radeon 680M (iGPU, drives the internal panel) - # + NVIDIA RTX 4050 Max-Q (dGPU, offload-only) — ASUS TUF Gaming A15 - # FA507NU. Bus IDs come straight from `lspci` on this machine, not - # guessed: 01:00.0 (NVIDIA) -> PCI:1:0:0, 35:00.0 (AMD) -> PCI:53:0:0. - services.xserver.videoDrivers = [ "nvidia" ]; - - hardware.nvidia = { - # Required settings - modesetting.enable = true; - - # Enable NVIDIA Open GPU Kernel Modules (Turing architecture or newer — - # this RTX 4050 is Ada Lovelace, well within support) - open = true; - - # Enable the NVIDIA settings menu - nvidiaSettings = true; - - # Use the stable driver package matching current kernel - package = config.boot.kernelPackages.nvidiaPackages.stable; - - # Fixes suspend/resume hangs that are common with NVIDIA on laptops. - powerManagement.enable = true; - - # PRIME render offload: the AMD iGPU drives the display at all times - # (much better battery life than keeping the dGPU powered on), and the - # NVIDIA GPU is only spun up on demand for a specific process — run - # anything with `nvidia-offload ` (generated by - # enableOffloadCmd) to route it through the RTX 4050, e.g. games or - # Blender's Cycles renderer. - prime = { - offload = { - enable = true; - enableOffloadCmd = true; - }; - amdgpuBusId = "PCI:53:0:0"; - nvidiaBusId = "PCI:1:0:0"; - }; - }; - - # ASUS laptop control: keyboard backlight/fan curves/battery charge limit - # (asusctl) and dGPU power-state switching between Integrated/Hybrid modes - # (supergfxctl) — the two standard asus-linux.org daemons for ROG/TUF - # laptops, both packaged directly in nixpkgs. - services.asusd.enable = true; - services.supergfxd.enable = true; -} diff --git a/modules/hardware.nix b/modules/hardware.nix new file mode 100644 index 0000000..d855f32 --- /dev/null +++ b/modules/hardware.nix @@ -0,0 +1,110 @@ +{ config, lib, pkgs, ... }: + +{ + hardware.graphics = { + enable = true; + enable32Bit = true; + }; + + # --- Hybrid graphics: AMD Radeon 680M (iGPU, drives the internal panel) + # + NVIDIA RTX 4050 Max-Q (dGPU, offload-only) — ASUS TUF Gaming A15 + # FA507NU. Bus IDs come straight from `lspci` on this machine, not + # guessed: 01:00.0 (NVIDIA) -> PCI:1:0:0, 35:00.0 (AMD) -> PCI:53:0:0 + # (the PCI: form is decimal, 0x35 = 53). + services.xserver.videoDrivers = [ "nvidia" ]; + + hardware.nvidia = { + modesetting.enable = true; + + # Open GPU kernel modules — fine on Turing or newer, and this Ada + # Lovelace RTX 4050 is well within support. + open = true; + nvidiaSettings = true; + package = config.boot.kernelPackages.nvidiaPackages.stable; + + # Preserves video memory across suspend/resume, which is what fixes the + # classic NVIDIA-laptop "black screen after lid close" hang. + powerManagement.enable = true; + + # Runtime PM, i.e. letting the dGPU drop to D3cold when idle, is left + # off on purpose: supergfxd below already power-cycles the dGPU when you + # switch to Integrated mode, and driving both power paths at once is the + # usual source of resume hangs on TUF laptops. If you want to try it: + # powerManagement.finegrained = true; + + # PRIME render offload: the AMD iGPU drives the display at all times + # (much better battery life than keeping the dGPU powered on), and the + # NVIDIA GPU only spins up on demand for a specific process — run + # anything with `nvidia-offload ` (generated by + # enableOffloadCmd) to route it through the RTX 4050, e.g. games or + # Blender's Cycles renderer. + prime = { + offload = { + enable = true; + enableOffloadCmd = true; + }; + amdgpuBusId = "PCI:53:0:0"; + nvidiaBusId = "PCI:1:0:0"; + }; + }; + + # ASUS laptop control: keyboard backlight/fan curves/battery charge limit + # (asusctl) and dGPU power-state switching between Integrated/Hybrid modes + # (supergfxctl) — the two standard asus-linux.org daemons for ROG/TUF + # laptops, both packaged directly in nixpkgs. + services.asusd.enable = true; + services.supergfxd.enable = true; + + hardware.opentabletdriver.enable = true; + hardware.uinput.enable = true; + boot.kernelModules = [ "uinput" "v4l2loopback" ]; + + # Virtual webcam device so ffmpeg (fed by the GoPro's stream) can appear + # as a normal camera to apps like Discord. exclusive_caps=1 is required + # for Chromium/WebRTC-based apps to recognize it as a real capture device. + boot.extraModulePackages = [ config.boot.kernelPackages.v4l2loopback ]; + boot.extraModprobeConfig = '' + options v4l2loopback devices=1 video_nr=10 card_label="GoPro Webcam" exclusive_caps=1 + # DualSense/PS4 pads pair over Bluetooth then drop instantly; broken + # ERTM in many adapters. Disabling it is the standard fix. + options bluetooth disable_ertm=1 + ''; + + hardware.bluetooth = { + enable = true; + powerOnBoot = true; + settings = { + General = { + Experimental = true; # Battery level of connected devices + FastConnectable = true; # Faster reconnects, more power draw + }; + Policy = { + AutoEnable = true; + }; + }; + }; + # No blueman here: Plasma brings its own Bluetooth stack (bluedevil), and + # running the GTK applet alongside it just gives you two tray icons + # fighting over the same adapter. The desktop repo still has it enabled, + # left over from the Budgie days. + + # Blog V4 needs the rtlsdrblog fork of librtlsdr, which is what + # pkgs.rtl-sdr already is here. Also blacklists the DVB-T drivers. + hardware.rtl-sdr.enable = true; + + # I2C bus, for DDC/CI brightness on an external monitor over HDMI/USB-C + # via ddcutil. The internal panel goes through the kernel backlight class + # instead — brightness-ctl picks whichever applies, see + # modules/scripts/brightness-ctl.sh. + hardware.i2c.enable = true; + + services.udev.extraRules = '' + # ddcutil without root (i2c group, see modules/users.nix) + SUBSYSTEM=="i2c-dev", GROUP="i2c", MODE="0660" + ''; + + # No OpenRGB: that's the desktop's motherboard/RAM lighting. The keyboard + # backlight on this machine is asusd's job (`asusctl aura`). + + services.printing.enable = true; +} diff --git a/modules/locale.nix b/modules/locale.nix new file mode 100644 index 0000000..5cb596e --- /dev/null +++ b/modules/locale.nix @@ -0,0 +1,19 @@ +{ config, pkgs, ... }: + +{ + time.timeZone = "Europe/Warsaw"; + i18n.defaultLocale = "pl_PL.UTF-8"; + i18n.extraLocaleSettings = { + LC_ADDRESS = "pl_PL.UTF-8"; + LC_IDENTIFICATION = "pl_PL.UTF-8"; + LC_MEASUREMENT = "pl_PL.UTF-8"; + LC_MONETARY = "pl_PL.UTF-8"; + LC_NAME = "pl_PL.UTF-8"; + LC_NUMERIC = "pl_PL.UTF-8"; + LC_PAPER = "pl_PL.UTF-8"; + LC_TELEPHONE = "pl_PL.UTF-8"; + LC_TIME = "pl_PL.UTF-8"; + }; + + console.keyMap = "pl2"; +} diff --git a/modules/networking.nix b/modules/networking.nix new file mode 100644 index 0000000..f4c24a1 --- /dev/null +++ b/modules/networking.nix @@ -0,0 +1,52 @@ +{ config, pkgs, ... }: + +{ + networking.hostName = "nixos"; + networking.networkmanager.enable = true; + + networking.firewall = { + enable = true; + allowPing = true; + allowedTCPPortRanges = [ { from = 1714; to = 1764; } ]; + allowedUDPPortRanges = [ { from = 1714; to = 1764; } ]; + allowedTCPPorts = [ 80 443 ]; + + # modules/scripts/hotspot-start.sh brings up an AP with + # `ipv4.method shared`, which has NetworkManager spawn dnsmasq on the + # Wi-Fi interface. Without these the hotspot associates but clients never + # get a lease, because the global firewall drops their DHCP and DNS + # queries. Scoped to the Wi-Fi interface only ("wl+" is the iptables + # wildcard, so it covers wlan0/wlp*), and dnsmasq only listens while the + # hotspot profile is up — the same NIC can't be AP and client at once. + interfaces."wl+" = { + allowedUDPPorts = [ 53 67 ]; + allowedTCPPorts = [ 53 ]; + }; + }; + + # DNS-over-TLS via systemd-resolved; NetworkManager defers to it rather + # than writing its own /etc/resolv.conf. + networking.nameservers = [ "1.1.1.1#cloudflare-dns.com" "9.9.9.9#dns.quad9.net" ]; + services.resolved = { + enable = true; + settings.Resolve = { + DNSSEC = "allow-downgrade"; + DNSOverTLS = "true"; + }; + }; + networking.networkmanager.dns = "systemd-resolved"; + + # Ethernet stays "stable" (pseudonymous but consistent) so static DHCP + # reservations on the router keep working; Wi-Fi is fully random, which + # matters more here than on the desktop — this machine joins other + # people's networks. + networking.networkmanager.wifi.macAddress = "random"; + networking.networkmanager.wifi.scanRandMacAddress = true; + networking.networkmanager.ethernet.macAddress = "stable"; + + # Disable NetworkManager's periodic captive-portal probe. + networking.networkmanager.settings.connectivity = { + uri = ""; + interval = 0; + }; +} diff --git a/modules/nix-settings.nix b/modules/nix-settings.nix new file mode 100644 index 0000000..4c04710 --- /dev/null +++ b/modules/nix-settings.nix @@ -0,0 +1,14 @@ +{ config, pkgs, ... }: + +{ + nixpkgs.config.allowUnfree = true; + + nix.settings.experimental-features = [ "nix-command" "flakes" ]; + nix.settings.auto-optimise-store = true; + + nix.gc = { + automatic = true; + dates = "weekly"; + options = "--delete-older-than 7d"; + }; +} diff --git a/modules/power.nix b/modules/power.nix new file mode 100644 index 0000000..831b7b8 --- /dev/null +++ b/modules/power.nix @@ -0,0 +1,48 @@ +{ config, lib, pkgs, ... }: + +{ + # Suspend-to-RAM support and the powersave plumbing Plasma builds on: + # plasma6.nix turns powerdevil and upower on when this is set, and it + # defaults to true — spelled out here because on a laptop it matters. + powerManagement.enable = true; + + # Backs Plasma's Power Profiles switcher (Performance / Balanced / + # Power Saver in the battery applet). Plasma already defaults it on; kept + # explicit since it's the one knob that actually changes CPU behaviour. + # Mutually exclusive with tlp and auto-cpufreq — don't add either. + services.power-profiles-daemon.enable = true; + + # Lid behaviour. Closing the lid suspends on battery, but only locks when + # docked or on AC, so an external-monitor setup keeps running with the lid + # shut. Renamed options: this is services.logind.settings.Login now, the + # old services.logind.lidSwitch spelling is deprecated in 26.05. + services.logind.settings.Login = { + HandleLidSwitch = "suspend"; + HandleLidSwitchExternalPower = "lock"; + HandleLidSwitchDocked = "ignore"; + HandlePowerKey = "suspend"; + }; + + # Hibernate isn't available and won't be: swap is zram-only (see + # modules/boot.nix), and security.protectKernelImage in modules/security.nix + # blocks the kexec path hibernation resume needs anyway. + + # Battery longevity is asusd's department rather than a NixOS option — + # there's no hardware.asus module in nixpkgs. Set it once and asusd + # persists it across reboots: + # asusctl -c 80 # stop charging at 80% + # asusctl -c 100 # back to full, before a trip + # Fan curves and keyboard lighting likewise: `asusctl profile -l`, + # `asusctl aura -h`. + + # amd_pstate is left at the kernel's own default. Forcing + # "amd_pstate=active" is the usual tweak for Zen 3+ laptops, but 6.18 + # already picks it when the firmware advertises CPPC, so pinning it here + # would only matter if the BIOS regressed: + # boot.kernelParams = [ "amd_pstate=active" ]; + + environment.systemPackages = with pkgs; [ + powertop # `powertop --auto-tune` and per-process wakeup accounting + acpi # quick battery/thermal readout from a terminal + ]; +} diff --git a/modules/security.nix b/modules/security.nix new file mode 100644 index 0000000..1a563ce --- /dev/null +++ b/modules/security.nix @@ -0,0 +1,40 @@ +{ config, pkgs, ... }: + +{ + security.polkit.enable = true; + + security.sudo.extraConfig = '' + Defaults pwfeedback + ''; + + programs.firejail.enable = true; + + programs.gnupg.agent = { + enable = true; + enableSSHSupport = true; + # Qt pinentry to match Plasma (the desktop still says pinentry-rofi, a + # leftover from its Hyprland days — rofi isn't even installed there). + pinentryPackage = pkgs.pinentry-qt; + }; + + boot.kernel.sysctl = { + "kernel.kptr_restrict" = 2; + "kernel.dmesg_restrict" = 1; + "net.ipv4.conf.all.rp_filter" = 1; + "net.ipv4.conf.default.rp_filter" = 1; + "net.ipv4.conf.all.accept_redirects" = false; + "net.ipv4.conf.default.accept_redirects" = false; + "net.ipv6.conf.all.accept_redirects" = false; + "net.ipv6.conf.default.accept_redirects" = false; + "net.ipv4.conf.all.send_redirects" = false; + "net.ipv4.conf.default.send_redirects" = false; + # Only bites ad-hoc `gdb -p ` on a non-child process. + "kernel.yama.ptrace_scope" = 1; + "net.ipv4.tcp_timestamps" = 0; + }; + + systemd.coredump.enable = false; + security.protectKernelImage = true; + + services.geoclue2.enable = false; +} diff --git a/modules/system.nix b/modules/system.nix deleted file mode 100644 index 8118a20..0000000 --- a/modules/system.nix +++ /dev/null @@ -1,204 +0,0 @@ -{ config, lib, pkgs, ... }: - -{ - # Bootloader & Kernel - boot.loader.systemd-boot.enable = true; - boot.loader.efi.canTouchEfiVariables = true; - boot.loader.systemd-boot.configurationLimit = 5; - boot.kernelPackages = pkgs.linuxPackages; - boot.supportedFilesystems = [ "fuse" ]; - - # Networking - networking.hostName = "nixos"; - networking.networkmanager.enable = true; - - # Firewall Configuration - networking.firewall = { - enable = true; - - # Allow local ping diagnostic queries - allowPing = true; - - allowedTCPPortRanges = [ { from = 1714; to = 1764; } ]; - allowedUDPPortRanges = [ { from = 1714; to = 1764; } ]; - allowedTCPPorts = [ 80 443 ]; - # allowedUDPPorts = [ ]; - }; - - # Time zone and Internationalization - time.timeZone = "Europe/Warsaw"; - i18n.defaultLocale = "pl_PL.UTF-8"; - i18n.extraLocaleSettings = { - LC_ADDRESS = "pl_PL.UTF-8"; - LC_IDENTIFICATION = "pl_PL.UTF-8"; - LC_MEASUREMENT = "pl_PL.UTF-8"; - LC_MONETARY = "pl_PL.UTF-8"; - LC_NAME = "pl_PL.UTF-8"; - LC_NUMERIC = "pl_PL.UTF-8"; - LC_PAPER = "pl_PL.UTF-8"; - LC_TELEPHONE = "pl_PL.UTF-8"; - LC_TIME = "pl_PL.UTF-8"; - }; - - nix.settings.auto-optimise-store = true; - nix.gc = { - automatic = true; - dates = "weekly"; - options = "--delete-older-than 7d"; - }; - - # Console keymap - console.keyMap = "pl2"; - - # Sound and Audio (Pipewire) - security.rtkit.enable = true; - services.pulseaudio.enable = false; - services.pipewire = { - enable = true; - alsa.enable = true; - alsa.support32Bit = true; - pulse.enable = true; - }; - - hardware.bluetooth = { - enable = true; - powerOnBoot = true; - settings = { - General = { - # Shows battery charge of connected devices on supported - # Bluetooth adapters. Defaults to 'false'. - Experimental = true; - # When enabled other devices can connect faster to us, however - # the tradeoff is increased power consumption. Defaults to - # 'false'. - FastConnectable = true; - }; - Policy = { - # Enable all controllers when they are found. This includes - # adapters present on start as well as adapters that are plugged - # in later on. Defaults to 'true'. - AutoEnable = true; - }; - }; - }; - - services.blueman.enable = true; - - #Firejail - programs.firejail = { - enable = true; - }; - - #FUSE shit - programs.fuse.userAllowOther = true; - - # Enable GnuPG Agent with GUI pinentry support - programs.gnupg.agent = { - enable = true; - enableSSHSupport = true; - pinentryPackage = pkgs.pinentry-gnome3; # Matches Pantheon's GTK/gnome-keyring stack - }; - - security.sudo.extraConfig = '' - Defaults pwfeedback - ''; - - # Printing services - services.printing.enable = true; - - nixpkgs.config.allowUnfree = true; - - fonts.packages = with pkgs; [ - corefonts - dejavu_fonts - noto-fonts - noto-fonts-cjk-sans - noto-fonts-color-emoji - nerd-fonts.fira-code - nerd-fonts.jetbrains-mono - nerd-fonts.hack - inter - liberation_ttf - roboto - ]; - - # --- Loader shim for running non-Nix-packaged binaries (e.g. the official - # Tor Browser bundle) that expect a standard FHS dynamic linker/libraries. --- - programs.nix-ld.enable = true; - programs.nix-ld.libraries = with pkgs; [ - glib gtk3 gdk-pixbuf pango cairo atk - dbus dbus-glib fontconfig freetype - libx11 libxext libxrender libxtst - libxi libxcomposite libxdamage libxfixes - libxrandr libxcursor libxcb libxkbcommon - alsa-lib mesa zlib stdenv.cc.cc - ]; - environment.systemPackages = [ pkgs.file ]; - - # --- Hardening: swap, kernel sysctls, DNS, MAC randomization --- - - # Swap: replace disk swap with compressed in-RAM swap. Nothing sensitive - # (decrypted vault contents, browser memory, GPG session data) ever gets - # written to disk in plaintext via swap. Also faster than disk swap under - # memory pressure. - zramSwap.enable = true; - swapDevices = lib.mkForce [ ]; - # systemd's gpt-auto-generator independently re-discovers and activates any - # GPT partition tagged as Linux swap, regardless of the swapDevices list - # above — this disables that auto-discovery so the old disk swap partition - # actually stays off. Doesn't affect /, /home, /nix, /boot: those are - # already explicitly declared in hardware-configuration.nix, not relying on - # gpt-auto. Takes effect on next reboot. - boot.kernelParams = [ "systemd.gpt_auto=0" ]; - - # Kernel/system hardening sysctls - boot.kernel.sysctl = { - # Hide kernel pointers from unprivileged users (blocks a common - # local info-leak used in exploit chains). - "kernel.kptr_restrict" = 2; - # Restrict dmesg access to root. - "kernel.dmesg_restrict" = 1; - # Reverse-path filtering: drop packets with spoofed source addresses. - "net.ipv4.conf.all.rp_filter" = 1; - "net.ipv4.conf.default.rp_filter" = 1; - # Don't accept/send ICMP redirects (blocks a classic local MITM trick). - "net.ipv4.conf.all.accept_redirects" = false; - "net.ipv4.conf.default.accept_redirects" = false; - "net.ipv6.conf.all.accept_redirects" = false; - "net.ipv6.conf.default.accept_redirects" = false; - "net.ipv4.conf.all.send_redirects" = false; - "net.ipv4.conf.default.send_redirects" = false; - }; - - # Crash dumps can contain decrypted memory contents, passwords, keys — - # never write them to disk. - systemd.coredump.enable = false; - - # Blocks kexec-based kernel image tampering. - security.protectKernelImage = true; - - # DNS-over-TLS system-wide (Tor Browser already routes its own DNS - # through Tor, so this only affects everything else). Cloudflare + Quad9 - # both support DoT; "allow-downgrade" avoids hard failures on domains - # with broken DNSSEC deployment. - networking.nameservers = [ "1.1.1.1#cloudflare-dns.com" "9.9.9.9#dns.quad9.net" ]; - services.resolved = { - enable = true; - settings.Resolve = { - DNSSEC = "allow-downgrade"; - DNSOverTLS = "true"; - }; - }; - networking.networkmanager.dns = "systemd-resolved"; - - # MAC address randomization. - # WiFi: fully random per connection (you're on ethernet now, but this - # protects you if you ever use WiFi). - # Ethernet: "stable" — a consistent pseudonymous MAC per connection - # profile rather than your real hardware MAC, so it won't break any - # static DHCP reservation/MAC filtering on your OpenWRT router, while - # still not exposing your real hardware identity. - networking.networkmanager.wifi.macAddress = "random"; - networking.networkmanager.wifi.scanRandMacAddress = true; - networking.networkmanager.ethernet.macAddress = "stable"; -} diff --git a/modules/users.nix b/modules/users.nix index fe92cf7..121d6f5 100644 --- a/modules/users.nix +++ b/modules/users.nix @@ -1,27 +1,27 @@ { config, pkgs, ... }: { - # Primary user account configuration (Access and settings only) users.users."nexoniarz" = { isNormalUser = true; description = "Nexoniarz"; - extraGroups = [ "networkmanager" "wheel" "fuse" "i2c" "video" ]; + extraGroups = [ "networkmanager" "wheel" "fuse" "i2c" "video" "plugdev" ]; shell = pkgs.zsh; }; - # Default shell (ZSH) programs.zsh.enable = true; - - # Web browser configuration programs.firefox.enable = true; - # Enable Steam with optimized system configurations programs.steam = { enable = true; remotePlay.openFirewall = true; dedicatedServer.openFirewall = true; }; - # Enable Flatpak support services.flatpak.enable = true; + + # Flatpak's exported .desktop files aren't on XDG_DATA_DIRS by default, + # so Bottles would otherwise not show up in the launcher. + systemd.tmpfiles.rules = [ + "L+ /var/lib/flatpak/exports/share/applications - - - - /var/lib/flatpak/app/com.usebottles.bottles/current/active/export/share/applications" + ]; }