Compare commits

..
Author SHA1 Message Date
Nexoniarz
654a689fe1 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 <noreply@anthropic.com>
2026-09-25 00:02:56 +02:00
20 changed files with 197 additions and 9609 deletions

View file

@ -1,9 +1,9 @@
# nexoniarz-config-nixos-laptop # nexoniarz-config-nixos-laptop
My personal NixOS configuration for an ASUS TUF Gaming A15 (FA507NU). My personal NixOS system configuration tailored for daily use on an
Shares its layout and settings with my PC config **ASUS TUF Gaming A15 (FA507NU)**. Same layout and same choices as
([nexoniarz-config-nixos](https://github.com/Nexoniarz/nexoniarz-config-nixos)); [nexoniarz-config-nixos](https://github.com/Nexoniarz/nexoniarz-config-nixos)
only the hardware differs. (the desktop), with the hardware and power modules swapped for this machine.
## Overview ## Overview
@ -14,39 +14,52 @@ only the hardware differs.
- **Shell:** Zsh - **Shell:** Zsh
- **Audio:** PipeWire - **Audio:** PipeWire
- **CPU:** AMD Ryzen 5 7535HS - **CPU:** AMD Ryzen 5 7535HS
- **GPU:** AMD Radeon 680M (iGPU, drives the panel) + NVIDIA RTX 4050 Max-Q - **GPU:** AMD Radeon 680M (iGPU, drives the internal panel) + NVIDIA RTX
(PRIME render offload only; run things on it with `nvidia-offload <command>`) 4050 Max-Q (dGPU, PRIME render offload only — run anything through it with
- **Laptop control:** asusctl (fans, keyboard backlight, charge limit) + supergfxctl (dGPU power state) `nvidia-offload <command>`)
- **Laptop control:** `asusctl` (fan curves, keyboard backlight, battery
charge limit) + `supergfxctl` (dGPU power-state switching)
## Layout ## Layout
| File | Contents | | File | Contents |
|---|---| |---|---|
| `configuration.nix` | Imports only | | `configuration.nix` | Imports only |
| `hardware-configuration.nix` | Generated by `nixos-generate-config`, don't hand-edit |
| `modules/boot.nix` | Limine, kernel, zram/swap | | `modules/boot.nix` | Limine, kernel, zram/swap |
| `modules/networking.nix` | NetworkManager, firewall, DNS-over-TLS, MAC randomization | | `modules/networking.nix` | NetworkManager, firewall, DNS-over-TLS, MAC randomization |
| `modules/locale.nix` | Timezone, locale, console keymap | | `modules/locale.nix` | Timezone, locale, console keymap |
| `modules/nix-settings.nix` | Nix features, GC, unfree | | `modules/nix-settings.nix` | Nix features, GC, unfree |
| `modules/security.nix` | sysctl hardening, sudo, firejail, GnuPG, polkit | | `modules/security.nix` | sysctl hardening, sudo, firejail, GnuPG, polkit |
| `modules/remote.nix` | SSH between my machines (keys only) and ZeroVRAM's port 8080, both for the home network only; fixed host names `nexo-pc` / `nexo-laptop` | | `modules/hardware.nix` | Hybrid graphics (PRIME offload), ASUS daemons, Bluetooth, I2C, RTL-SDR, webcam loopback, tablet, printing |
| `modules/hardware.nix` | Hybrid graphics (PRIME offload), ASUS daemons, tablet, Bluetooth, I2C, RTL-SDR, printing | | `modules/power.nix` | Power profiles, lid/suspend behaviour, battery notes |
| `modules/audio.nix` | PipeWire | | `modules/audio.nix` | PipeWire |
| `modules/desktop.nix` | Plasma, SDDM, fonts, theming | | `modules/desktop.nix` | Plasma, SDDM, touchpad, fonts, theming |
| `modules/users.nix` | Account, shell, Steam, Flatpak | | `modules/users.nix` | Account, shell, Steam, Flatpak |
| `modules/apps.nix` | Installed packages, nix-ld, AppImage support | | `modules/apps.nix` | Installed packages, nix-ld |
| `modules/default-apps.nix` | MIME associations | | `modules/default-apps.nix` | MIME associations |
| `modules/scripts.nix` | Custom scripts in `modules/scripts/` (vault, metadata stripping, brightness, Wi-Fi hotspot, telemetry, weather, NexoPass) | | `modules/scripts.nix` | Custom scripts in `modules/scripts/` |
| `modules/fastfetch.nix` | Installs `configs/fastfetch/config.jsonc` system-wide |
| `modules/firefox.nix` | Firefox, with prefs from `configs/firefox/prefs.js` (reapplied each start, not locked), and its own font list without the Microsoft core fonts (against font fingerprinting) |
| `configs/` | Plain app config files (Firefox prefs, fastfetch) used by the modules |
## NexoPass 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.
`nexopass` (in `modules/scripts/`) derives site passwords from a master with Argon2id, so no password is ever stored. You can have several accounts, each with its own master and an encrypted site list (current versions plus an archive of old ones) in `~/.local/share/nexopass/accounts/`. One app password or PIN unlocks them all; a master is only typed to create or recover an account. ## Differences from the desktop config
Passwords of 5-15 words or 12-64 characters, "did you mean" for typos, "stay unlocked" for a few minutes (kernel keyring), encrypted export/import shared with the NexoPass Android app, and `nexopass settings`. Run `nexopass help` for everything else. Apache License 2.0. Everything outside hardware is kept identical on purpose. What differs:
`nexopass-gui` is the [NexoPass](https://github.com/Nexoniarz/NexoPass) window (also on Android and Windows), fetched from its GitHub release. It uses the same files as `nexopass`, so both see the same accounts and app password. - `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`.
--- ---

View file

@ -1,80 +0,0 @@
// Gruvbox-themed fastfetch, matching the Limine and icon theme.
// Colors: yellow fabd2f, aqua 8ec07c, blue 83a598, orange fe8019, gray 928374.
{
"$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json",
"logo": {
"source": "nixos",
"color": {
"1": "38;2;131;165;152",
"2": "38;2;142;192;124",
"3": "38;2;131;165;152",
"4": "38;2;142;192;124",
"5": "38;2;131;165;152",
"6": "38;2;142;192;124"
},
"padding": { "top": 1, "left": 1, "right": 3 }
},
"display": {
"separator": " ",
"color": {
"keys": "38;2;250;189;47",
"title": "38;2;254;128;25",
"output": "38;2;235;219;178"
},
"key": { "width": 13 },
"bar": {
"char": { "elapsed": "■", "total": "■" },
"border": { "left": "", "right": "" },
"color": { "total": "38;2;80;73;69" },
"width": 12
},
"percent": {
"type": ["num", "bar"],
"color": {
"green": "38;2;184;187;38",
"yellow": "38;2;250;189;47",
"red": "38;2;251;73;52"
}
},
"size": { "binaryPrefix": "iec", "ndigits": 1 }
},
"modules": [
{
"type": "title",
"format": "{#1}󱄅 {#38;2;254;128;25}{user-name}{#38;2;146;131;116}@{#38;2;142;192;124}{host-name}"
},
{
"type": "custom",
"format": "{#38;2;146;131;116}╭─────────────── {#38;2;131;165;152}system{#38;2;146;131;116} ───────────────"
},
{ "type": "os", "key": "│ 󱄅 os", "keyColor": "38;2;131;165;152", "format": "{pretty-name}" },
{ "type": "kernel", "key": "│ 󰌽 kernel", "keyColor": "38;2;131;165;152" },
{ "type": "packages", "key": "│ 󰏖 pkgs", "keyColor": "38;2;131;165;152", "format": "{nix-system} sys · {nix-user} user · {flatpak-all} flatpak" },
{ "type": "uptime", "key": "│ 󰔟 uptime", "keyColor": "38;2;131;165;152" },
{ "type": "shell", "key": "│ 󰆍 shell", "keyColor": "38;2;131;165;152" },
{
"type": "custom",
"format": "{#38;2;146;131;116}├─────────────── {#38;2;142;192;124}desktop{#38;2;146;131;116} ──────────────"
},
{ "type": "de", "key": "│ 󰕮 de", "keyColor": "38;2;142;192;124" },
{ "type": "wm", "key": "│ 󰖲 wm", "keyColor": "38;2;142;192;124" },
{ "type": "icons", "key": "│ 󰀻 icons", "keyColor": "38;2;142;192;124", "format": "{1}" },
{ "type": "cursor", "key": "│ 󰇀 cursor", "keyColor": "38;2;142;192;124" },
{
"type": "custom",
"format": "{#38;2;146;131;116}├─────────────── {#38;2;254;128;25}hardware{#38;2;146;131;116} ─────────────"
},
{ "type": "host", "key": "│ 󰍹 board", "keyColor": "38;2;254;128;25" },
{ "type": "cpu", "key": "│ 󰻠 cpu", "keyColor": "38;2;254;128;25", "temp": true, "format": "{name} ({cores-logical}) @ {freq-max}" },
{ "type": "gpu", "key": "│ 󰢮 gpu", "keyColor": "38;2;254;128;25", "format": "{name}" },
{ "type": "display", "key": "│ 󰍺 display","keyColor": "38;2;254;128;25", "format": "{width}x{height} @ {refresh-rate} Hz" },
{ "type": "memory", "key": "│ 󰑭 ram", "keyColor": "38;2;254;128;25", "format": "{percentage-bar} {used} / {total} ({percentage})" },
{ "type": "disk", "key": "│ 󰋊 disk", "keyColor": "38;2;254;128;25", "folders": "/", "format": "{size-percentage-bar} {size-used} / {size-total} ({size-percentage})" },
{
"type": "custom",
"format": "{#38;2;146;131;116}╰──────────────────────────────────────────"
},
"break",
{ "type": "colors", "symbol": "circle", "paddingLeft": 2 }
]
}

View file

@ -1,147 +0,0 @@
// Nexoniarz's Firefox prefs (Firefox 157, "Nova" design)
//
// Loaded by NixOS (modules/firefox.nix) into Firefox's autoconfig. pref()
// sets the value on every Firefox start but doesn't lock it: you can still
// change things in Settings/about:config, they just reset on next start.
// To change a setting permanently, edit it here and nixos-rebuild.
// To stop managing a setting, delete its line (Firefox then keeps
// whatever value it last had).
//
// GPU video decoding also needs env vars set in modules/hardware.nix:
// LIBVA_DRIVER_NAME=nvidia NVD_BACKEND=direct MOZ_DISABLE_RDD_SANDBOX=1
/*** PRIVACY: tracking & fingerprinting ***/
// Strict Enhanced Tracking Protection: blocks trackers, cryptominers and
// known fingerprinters, isolates cookies per site (Total Cookie Protection),
// strips tracking parameters from URLs, and turns on fingerprintingProtection
// (randomized canvas, fewer exposed hardware details).
// If a site breaks: click the shield in the address bar and turn it off
// for that site only.
pref("browser.contentblocking.category", "strict");
// Fingerprinting protection at full strength: everything resistFingerprinting
// does (a Windows user agent and platform that agree, UTC time, a fixed core
// count, the bundled font list, no precise GPU name, canvas noise...), but
// switched on through fingerprintingProtection, because only that one takes
// exceptions. The one exception: prefers-color-scheme isn't forced to light,
// so sites still get dark mode.
// To check it worked: coveryourtracks.eff.org should show Windows in both
// User Agent and Platform, and timezone UTC.
pref("privacy.resistFingerprinting", false);
pref("privacy.fingerprintingProtection", true);
pref("privacy.fingerprintingProtection.pbmode", true);
pref("privacy.fingerprintingProtection.overrides", "+AllTargets,-CSSPrefersColorScheme");
// Ask sites for English pages the way most English-speaking browsers do
// (en-US, en), so the language header doesn't stand out. en-GB would be
// rarer, and rarer is what gets you tracked.
pref("intl.accept_languages", "en-US, en");
pref("privacy.spoof_english", 2);
// Sites only see base system fonts, not the ones installed on top
// (the Microsoft core fonts made the font list 1 in 9,481).
pref("layout.css.font-visibility.standard", 1);
pref("layout.css.font-visibility.trackingprotection", 1);
pref("layout.css.font-visibility.private", 1);
pref("layout.css.font-visibility.resistFingerprinting", 1);
// WebGPU tells sites about the graphics card in detail. Off breaks some
// sites (3D, AI demos, some games); uncomment to turn it off.
// pref("dom.webgpu.enabled", false);
// First-party isolation stays ON. Every existing cookie in this profile was
// saved with a first-party tag; turning this off makes Firefox ignore them
// all (logged out everywhere). Works alongside Strict mode.
pref("privacy.firstparty.isolate", true);
// Cross-site requests only send the origin (https://site.com/), not the
// full page URL you came from.
pref("network.http.referer.XOriginTrimmingPolicy", 2);
// Tell sites not to sell/share your data (legally binding in some places).
pref("privacy.globalprivacycontrol.enabled", true);
// No WebRTC: stops IP leaks. Web video calls (Meet, Discord in-browser)
// need this set to true.
pref("media.peerconnection.enabled", false);
/*** PRIVACY: Mozilla data collection ***/
pref("datareporting.healthreport.uploadEnabled", false);
pref("datareporting.policy.dataSubmissionEnabled", false);
pref("datareporting.usage.uploadEnabled", false);
pref("toolkit.telemetry.enabled", false);
pref("toolkit.telemetry.unified", false);
pref("app.shield.optoutstudies.enabled", false);
pref("app.normandy.enabled", false);
pref("nimbus.rollouts.enabled", false);
pref("browser.discovery.enabled", false);
// Firefox Suggest launched in Poland with Firefox 157; it adds sponsored
// results to the address bar.
pref("browser.urlbar.quicksuggest.enabled", false);
pref("browser.urlbar.suggest.quicksuggest.sponsored", false);
pref("browser.urlbar.suggest.quicksuggest.nonsponsored", false);
pref("browser.newtabpage.activity-stream.showSponsored", false);
pref("browser.newtabpage.activity-stream.showSponsoredTopSites", false);
/*** SECURITY ***/
// HTTPS-Only: upgrade every site to HTTPS and warn before loading plain
// HTTP. Was enabled once, but it's off now.
pref("dom.security.https_only_mode", true);
// Safe Browsing back on for malware/phishing. It checks a local list and
// only sends a partial hash when a URL matches, never your history.
// The remote download check (which uploads file metadata to Google) stays off.
pref("browser.safebrowsing.malware.enabled", true);
pref("browser.safebrowsing.phishing.enabled", true);
pref("browser.safebrowsing.downloads.enabled", true);
pref("browser.safebrowsing.downloads.remote.enabled", false);
/*** NETWORK ***/
// DNS: use the system resolver (systemd-resolved with DNS-over-TLS) and
// never let Firefox switch itself to its own DNS-over-HTTPS provider.
pref("network.trr.mode", 5);
// No preloading pages or DNS you didn't ask for.
pref("network.dns.disablePrefetch", true);
pref("network.prefetch-next", false);
pref("network.http.speculative-parallel-limit", 0);
pref("browser.urlbar.speculativeConnect.enabled", false);
pref("network.captive-portal-service.enabled", false);
pref("network.connectivity-service.enabled", false);
/*** GPU VIDEO DECODING (RTX 3060 Ti, nvidia-vaapi-driver) ***/
// Firefox blocklists VA-API on NVIDIA; force it on. The 3060 Ti decodes
// H.264, VP9 and AV1 in hardware. (media.ffmpeg.vaapi.enabled is gone
// since Firefox 137; this pref replaced it.)
pref("media.hardware-video-decoding.force-enabled", true);
pref("media.rdd-ffmpeg.enabled", true);
pref("gfx.webrender.all", true);
/*** LOOK & FEEL ***/
// Compact mode (back in Firefox 157): thinner tabs and toolbars.
// Switch back via ☰ > More tools > Customize toolbar > Density.
pref("browser.compactmode.show", true);
pref("browser.uidensity", 1);
// Smoother, momentum-based scrolling, nicer at 240 Hz.
pref("general.smoothScroll.msdPhysics.enabled", true);
// Middle-click on a page to autoscroll (like on Windows).
pref("general.autoScroll", true);
// Closing the last tab doesn't close the window.
pref("browser.tabs.closeWindowWithLastTab", false);
// Show the full URL including https:// so you always see what site you're on.
pref("browser.urlbar.trimHttps", false);
pref("browser.urlbar.trimURLs", false);
// No "proceed with caution" page every time you open about:config.
pref("browser.aboutConfig.showWarning", false);

View file

@ -10,10 +10,10 @@
./modules/locale.nix ./modules/locale.nix
./modules/nix-settings.nix ./modules/nix-settings.nix
./modules/security.nix ./modules/security.nix
./modules/remote.nix
# Hardware # Hardware
./modules/hardware.nix ./modules/hardware.nix
./modules/power.nix
./modules/audio.nix ./modules/audio.nix
# Desktop & user # Desktop & user
@ -22,8 +22,6 @@
./modules/apps.nix ./modules/apps.nix
./modules/default-apps.nix ./modules/default-apps.nix
./modules/scripts.nix ./modules/scripts.nix
./modules/fastfetch.nix
./modules/firefox.nix
]; ];
# Do not change this value. Read documentation before updating. # Do not change this value. Read documentation before updating.

View file

@ -36,9 +36,9 @@ in
users.users."nexoniarz".packages = with pkgs; [ users.users."nexoniarz".packages = with pkgs; [
tumbler tumbler
brightnessctl brightnessctl # Backlight on the internal panel
ddcutil ddcutil # DDC/CI brightness on an external monitor
lm_sensors lm_sensors # sensors-detect also helps ddcutil find its I2C buses
networkmanager # nmcli, for the Wi-Fi hotspot script networkmanager # nmcli, for the Wi-Fi hotspot script
xdg-user-dirs xdg-user-dirs
@ -56,7 +56,6 @@ in
sdrppLean sdrppLean
rtl433Lean rtl433Lean
lmms lmms
krita
# Games # Games
prismlauncher prismlauncher
@ -72,7 +71,6 @@ in
openjdk25 openjdk25
go go
claude-code claude-code
codex
]; ];
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
@ -99,17 +97,10 @@ in
kdePackages.konsole kdePackages.konsole
kdePackages.dolphin kdePackages.dolphin
kdePackages.plasma-systemmonitor kdePackages.plasma-systemmonitor
kdePackages.kdialog
qt5.qtwayland qt5.qtwayland
qt6.qtwayland qt6.qtwayland
qemu qemu
file file
wineWow64Packages.stable
ghidra
jdk
python3
radare2
binwalk
]; ];
# Lets non-Nix binaries (e.g. the Tor Browser bundle) find an FHS linker. # Lets non-Nix binaries (e.g. the Tor Browser bundle) find an FHS linker.
@ -122,9 +113,4 @@ in
libxrandr libxcursor libxcb libxkbcommon libxrandr libxcursor libxcb libxkbcommon
alsa-lib mesa zlib stdenv.cc.cc alsa-lib mesa zlib stdenv.cc.cc
]; ];
# Registers AppImages with binfmt_misc, so ./Foo.AppImage (or a double-click
# in Dolphin) runs straight through appimage-run's FHS environment.
programs.appimage.enable = true;
programs.appimage.binfmt = true;
} }

View file

@ -8,18 +8,5 @@
alsa.enable = true; alsa.enable = true;
alsa.support32Bit = true; alsa.support32Bit = true;
pulse.enable = true; pulse.enable = true;
# Bluetooth headphones are playback-only here (mic comes from the USB
# codec). Disable HSP/HFP so they never drop to 8/16 kHz mono call mode.
wireplumber.extraConfig."11-bluetooth-policy" = {
"wireplumber.settings" = {
"bluetooth.autoswitch-to-headset-profile" = false;
};
"monitor.bluez.properties" = {
# PC only sends audio. Allowing a2dp_sink lets the Monitor II (which
# also advertises an A2DP source) connect backwards as "audio gateway".
"bluez5.roles" = [ "a2dp_source" ];
};
};
}; };
} }

View file

@ -32,8 +32,16 @@
# zram instead of disk swap: nothing sensitive is written to disk in # zram instead of disk swap: nothing sensitive is written to disk in
# plaintext. systemd's gpt-auto-generator would otherwise re-activate the # plaintext. systemd's gpt-auto-generator would otherwise re-activate the
# old swap partition regardless of swapDevices, hence the kernel param. # 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; zramSwap.enable = true;
swapDevices = lib.mkForce [ ]; 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" ]; boot.kernelParams = [ "systemd.gpt_auto=0" ];
} }

View file

@ -25,37 +25,39 @@
# xserver.nix installs xterm unconditionally; Konsole is the terminal here. # xserver.nix installs xterm unconditionally; Konsole is the terminal here.
services.xserver.excludePackages = [ pkgs.xterm ]; 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";
# 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 # Icon/cursor theme packages. Plasma has no gsettings-style declarative
# knob for this the way Budgie did — pick them in System Settings, or # knob for this the way Pantheon did — pick them in System Settings, or
# bring in plasma-manager later for a fully declarative setup. # bring in plasma-manager later for a fully declarative setup.
environment.systemPackages = [ environment.systemPackages = [
pkgs.gruvbox-plus-icons pkgs.gruvbox-plus-icons
pkgs.bibata-cursors pkgs.bibata-cursors
]; ];
# The third-party "Bouncing Popups" KWin effect (~/.local/share/kwin/effects)
# stacks on top of Plasma's own slidingpopups/fadingpopups and forces blur
# on every popup and the panel, making applet popups and panel settings
# open sluggishly. [$i] locks the key so ~/.config/kwinrc can't re-enable it.
environment.etc."xdg/kwinrc".text = ''
[Plugins]
bouncingPopupsEnabled[$i]=false
'';
# Breeze Dark as the default Plasma (panel/popup) theme. Avoid sparse
# third-party themes like "Darkly" (18 files): every SVG they lack falls
# back via an uncached search of all ~89 XDG_DATA_DIRS, which made applet
# popups and panel settings take 2-6 s to open. Not locked, so the theme
# can still be changed in System Settings.
environment.etc."xdg/plasmarc".text = ''
[Theme]
name=breeze-dark
'';
environment.extraInit = ''
export XDG_DATA_DIRS="$XDG_DATA_DIRS:${pkgs.gtk3}/share/gsettings-schemas/${pkgs.gtk3.name}"
'';
fonts.packages = with pkgs; [ fonts.packages = with pkgs; [
corefonts corefonts
dejavu_fonts dejavu_fonts

View file

@ -1,7 +0,0 @@
{ config, pkgs, ... }:
{
# System-wide config; fastfetch searches /etc/xdg/fastfetch/ after
# ~/.config/fastfetch/, so a user config there would still win.
environment.etc."xdg/fastfetch/config.jsonc".source = ../configs/fastfetch/config.jsonc;
}

View file

@ -1,36 +0,0 @@
{ config, pkgs, ... }:
let
# Firefox's own font setup: every system font except the Microsoft core
# fonts. Sites measure which fonts are installed to fingerprint you, and
# Comic Sans, Impact, Georgia, Verdana... made that list 1 in 9,000.
# Only Firefox is affected: other apps still have corefonts. In Firefox,
# Arial, Times New Roman and Courier New render as Liberation (same
# letter widths), the rest fall back to Noto.
firefoxFonts = pkgs.writeText "firefox-fonts.conf" ''
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
<fontconfig>
<include ignore_missing="no">/etc/fonts/fonts.conf</include>
<selectfont>
<rejectfont>
<glob>${pkgs.corefonts}/*</glob>
</rejectfont>
</selectfont>
</fontconfig>
'';
in
{
programs.firefox = {
enable = true;
# The wrapper sets FONTCONFIG_FILE when Firefox starts. (overrideAttrs
# keeps the package overridable, so the autoConfig below still applies.)
package = pkgs.firefox.overrideAttrs (old: {
makeWrapperArgs = old.makeWrapperArgs ++ [ "--set" "FONTCONFIG_FILE" "${firefoxFonts}" ];
});
# Not locked: values are reapplied on every Firefox start, but can be
# changed in the UI for the current session. (Read as text: the module
# doesn't copy autoConfigFiles paths into the store.)
autoConfig = builtins.readFile ../configs/firefox/prefs.js;
};
}

View file

@ -6,22 +6,38 @@
enable32Bit = true; enable32Bit = true;
}; };
# Hybrid graphics on the ASUS TUF Gaming A15 (FA507NU): AMD Radeon 680M # --- Hybrid graphics: AMD Radeon 680M (iGPU, drives the internal panel)
# iGPU drives the internal panel, NVIDIA RTX 4050 Max-Q is offload-only. # + NVIDIA RTX 4050 Max-Q (dGPU, offload-only) — ASUS TUF Gaming A15
# Run something on the dGPU with `nvidia-offload <command>`. # FA507NU. Bus IDs come straight from `lspci` on this machine, not
# Bus IDs from `lspci` on this machine: 01:00.0 (NVIDIA) -> PCI:1:0:0, # guessed: 01:00.0 (NVIDIA) -> PCI:1:0:0, 35:00.0 (AMD) -> PCI:53:0:0
# 35:00.0 (AMD) -> PCI:53:0:0. # (the PCI: form is decimal, 0x35 = 53).
services.xserver.videoDrivers = [ "nvidia" ]; services.xserver.videoDrivers = [ "nvidia" ];
hardware.nvidia = { hardware.nvidia = {
modesetting.enable = true; modesetting.enable = true;
# Open GPU kernel modules — fine on Turing or newer, and this Ada
# Lovelace RTX 4050 is well within support.
open = true; open = true;
nvidiaSettings = true; nvidiaSettings = true;
package = config.boot.kernelPackages.nvidiaPackages.stable; package = config.boot.kernelPackages.nvidiaPackages.stable;
# Fixes suspend/resume hangs common with NVIDIA on laptops. # Preserves video memory across suspend/resume, which is what fixes the
# classic NVIDIA-laptop "black screen after lid close" hang.
powerManagement.enable = true; 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 <command>` (generated by
# enableOffloadCmd) to route it through the RTX 4050, e.g. games or
# Blender's Cycles renderer.
prime = { prime = {
offload = { offload = {
enable = true; enable = true;
@ -31,17 +47,28 @@
nvidiaBusId = "PCI:1:0:0"; nvidiaBusId = "PCI:1:0:0";
}; };
}; };
# No LIBVA/NVD env vars here (unlike the PC): video decoding runs on the
# AMD iGPU, which libva picks automatically (radeonsi).
# asusctl (fan curves, keyboard backlight, battery charge limit) and # ASUS laptop control: keyboard backlight/fan curves/battery charge limit
# supergfxctl (dGPU power-state switching), from asus-linux.org. # (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.asusd.enable = true;
services.supergfxd.enable = true; services.supergfxd.enable = true;
hardware.opentabletdriver.enable = true; hardware.opentabletdriver.enable = true;
hardware.uinput.enable = true; hardware.uinput.enable = true;
boot.kernelModules = [ "uinput" ]; 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 = { hardware.bluetooth = {
enable = true; enable = true;
@ -56,21 +83,28 @@
}; };
}; };
}; };
# Plasma ships its own Bluetooth UI (Bluedevil); blueman just duplicated it. # No blueman here: Plasma brings its own Bluetooth stack (bluedevil), and
# running the GTK applet alongside it just gives you two tray icons
boot.extraModprobeConfig = '' # fighting over the same adapter. The desktop repo still has it enabled,
# DualSense/PS4 pads pair over Bluetooth then drop instantly; broken # left over from the Budgie days.
# ERTM in many adapters. Disabling it is the standard fix.
options bluetooth disable_ertm=1
'';
# Blog V4 needs the rtlsdrblog fork of librtlsdr, which is what # Blog V4 needs the rtlsdrblog fork of librtlsdr, which is what
# pkgs.rtl-sdr already is here. Also blacklists the DVB-T drivers. # pkgs.rtl-sdr already is here. Also blacklists the DVB-T drivers.
hardware.rtl-sdr.enable = true; hardware.rtl-sdr.enable = true;
# For ddcutil on an external monitor; the internal panel uses the kernel # I2C bus, for DDC/CI brightness on an external monitor over HDMI/USB-C
# backlight instead (brightness-ctl picks whichever applies). # 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; 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; services.printing.enable = true;
} }

View file

@ -1,7 +1,7 @@
{ config, pkgs, ... }: { config, pkgs, ... }:
{ {
networking.hostName = "nexo-laptop"; networking.hostName = "nixos";
networking.networkmanager.enable = true; networking.networkmanager.enable = true;
networking.firewall = { networking.firewall = {
@ -10,6 +10,18 @@
allowedTCPPortRanges = [ { from = 1714; to = 1764; } ]; allowedTCPPortRanges = [ { from = 1714; to = 1764; } ];
allowedUDPPortRanges = [ { from = 1714; to = 1764; } ]; allowedUDPPortRanges = [ { from = 1714; to = 1764; } ];
allowedTCPPorts = [ 80 443 ]; 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 # DNS-over-TLS via systemd-resolved; NetworkManager defers to it rather
@ -25,7 +37,9 @@
networking.networkmanager.dns = "systemd-resolved"; networking.networkmanager.dns = "systemd-resolved";
# Ethernet stays "stable" (pseudonymous but consistent) so static DHCP # Ethernet stays "stable" (pseudonymous but consistent) so static DHCP
# reservations on the router keep working; Wi-Fi is fully random. # 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.macAddress = "random";
networking.networkmanager.wifi.scanRandMacAddress = true; networking.networkmanager.wifi.scanRandMacAddress = true;
networking.networkmanager.ethernet.macAddress = "stable"; networking.networkmanager.ethernet.macAddress = "stable";

48
modules/power.nix Normal file
View file

@ -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
];
}

View file

@ -1,38 +0,0 @@
{ config, lib, pkgs, ... }:
# My machines working together at home: SSH (keys only) and ZeroVRAM's zvm_node (port 8080),
# both reachable from the home network only — not from other networks the laptop joins.
let
homeLan = "192.168.1.0/24";
homePorts = [ 22 8080 ]; # SSH, zvm_node
rule = op: port: "iptables ${op} nixos-fw -p tcp -s ${homeLan} --dport ${toString port} -j nixos-fw-accept";
in
{
# Fixed names for the machines (their addresses are reserved in the router). The router's own
# *.lan names don't resolve here: DNS goes over TLS to public resolvers (networking.nix).
networking.hosts = {
"192.168.1.225" = [ "nexo-pc" ];
"192.168.1.197" = [ "nexo-laptop" ];
};
services.openssh = {
enable = true;
openFirewall = false; # opened below for the home network only
settings = {
PasswordAuthentication = false;
KbdInteractiveAuthentication = false;
PermitRootLogin = "no";
AllowUsers = [ "nexoniarz" ];
};
};
# Keys allowed to log in here: the PC
users.users."nexoniarz".openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAPXDmX7gYH0A2zez4f1F63kjAI8t7iyte+gXmyI4wtz nexoniarz@nexo-pc"
];
# Inserted at the top of the firewall chain (rules appended at the end would come after its
# final reject); removed again when the firewall stops or reloads.
networking.firewall.extraCommands = lib.concatMapStringsSep "\n" (rule "-I") homePorts;
networking.firewall.extraStopCommands = lib.concatMapStringsSep "\n" (p: "${rule "-D" p} || true") homePorts;
}

View file

@ -1,45 +1,16 @@
{ config, pkgs, lib, ... }: { config, pkgs, ... }:
let let
# Wrap external shell scripts into system executables
vaultToggle = pkgs.writeShellScriptBin "vault-toggle" (builtins.readFile ./scripts/vault-toggle.sh); vaultToggle = pkgs.writeShellScriptBin "vault-toggle" (builtins.readFile ./scripts/vault-toggle.sh);
stripMetadata = pkgs.writeShellScriptBin "strip-metadata" (builtins.readFile ./scripts/strip-metadata.sh); stripMetadata = pkgs.writeShellScriptBin "strip-metadata" (builtins.readFile ./scripts/strip-metadata.sh);
brightnessCtl = pkgs.writeShellScriptBin "brightness-ctl" (builtins.readFile ./scripts/brightness-ctl.sh); brightnessCtl = pkgs.writeShellScriptBin "brightness-ctl" (builtins.readFile ./scripts/brightness-ctl.sh);
hotspotStart = pkgs.writeShellScriptBin "hotspot-start" (builtins.readFile ./scripts/hotspot-start.sh); hotspotStart = pkgs.writeShellScriptBin "hotspot-start" (builtins.readFile ./scripts/hotspot-start.sh);
systemTelemetry = pkgs.writeShellScriptBin "system-telemetry" (builtins.readFile ./scripts/system-telemetry.sh); systemTelemetry = pkgs.writeShellScriptBin "system-telemetry" (builtins.readFile ./scripts/system-telemetry.sh);
weatherFetch = pkgs.writeShellScriptBin "weather-fetch" (builtins.readFile ./scripts/weather-fetch.sh); weatherFetch = pkgs.writeShellScriptBin "weather-fetch" (builtins.readFile ./scripts/weather-fetch.sh);
nexopass = pkgs.writeShellApplication {
name = "nexopass";
runtimeInputs = with pkgs; [ libargon2 gnupg wl-clipboard keyutils coreutils gnused ];
runtimeEnv.NEXOPASS_WORDLIST = "${./scripts/eff_large_wordlist.txt}";
text = builtins.readFile ./scripts/nexopass.sh;
};
# NexoPass window (github.com/Nexoniarz/NexoPass). Shares accounts, site
# lists and the app password with the nexopass command above.
nexopassGuiVersion = "1.3.1";
nexopassGuiJar = pkgs.fetchurl {
url = "https://github.com/Nexoniarz/NexoPass/releases/download/v${nexopassGuiVersion}/NexoPass-linux-x64-${nexopassGuiVersion}.jar";
hash = "sha256-WeUixuz+aLPsMDj+a0Gs09gfikMYYCf8uuZIN89CqVo=";
};
nexopassGuiLibs = with pkgs; [ libGL libx11 libxext libxrender libxtst libxi libxrandr libxcursor fontconfig freetype ];
nexopassGuiDesktop = pkgs.makeDesktopItem {
name = "nexopass";
desktopName = "NexoPass";
comment = "Passwords derived from a master";
exec = "nexopass-gui";
icon = "nexopass";
categories = [ "Utility" "Security" ];
};
nexopassGui = pkgs.runCommand "nexopass-gui-${nexopassGuiVersion}" { nativeBuildInputs = [ pkgs.makeWrapper ]; } ''
makeWrapper ${pkgs.jdk17}/bin/java $out/bin/nexopass-gui \
--add-flags "-Xmx1g -jar ${nexopassGuiJar}" \
--prefix LD_LIBRARY_PATH : /run/opengl-driver/lib:${lib.makeLibraryPath nexopassGuiLibs}
install -Dm644 ${./scripts/nexopass.png} $out/share/icons/hicolor/512x512/apps/nexopass.png
install -Dm644 ${nexopassGuiDesktop}/share/applications/nexopass.desktop $out/share/applications/nexopass.desktop
'';
in in
{ {
# Install CLI dependencies required by the scripts
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
gocryptfs gocryptfs
exiftool exiftool
@ -50,7 +21,5 @@ in
hotspotStart hotspotStart
systemTelemetry systemTelemetry
weatherFetch weatherFetch
nexopass
nexopassGui
]; ];
} }

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

File diff suppressed because it is too large Load diff

View file

@ -12,7 +12,9 @@
programs.gnupg.agent = { programs.gnupg.agent = {
enable = true; enable = true;
enableSSHSupport = true; enableSSHSupport = true;
pinentryPackage = pkgs.pinentry-rofi; # 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 = { boot.kernel.sysctl = {

View file

@ -9,6 +9,7 @@
}; };
programs.zsh.enable = true; programs.zsh.enable = true;
programs.firefox.enable = true;
programs.steam = { programs.steam = {
enable = true; enable = true;