Switch desktop environment from Hyprland to Pantheon

Configures Pantheon (LightDM + gala) for daily use on this ASUS TUF
Gaming A15 (FA507NU), and drops everything that only existed to serve
the previous Hyprland/Quickshell setup:

- desktop.nix: services.desktopManager.pantheon instead of
  programs.hyprland + greetd; comfy laptop touchpad defaults.
- drivers.nix: NVIDIA PRIME render offload between the AMD Radeon 680M
  (drives the panel) and the RTX 4050 Max-Q (offload-only), using this
  machine's real PCI bus IDs; asusd/supergfxd for ASUS-specific laptop
  control.
- apps.nix / scripts.nix: removed Hyprland/Quickshell-only packages and
  scripts (kitty, rofi, quickshell, mako, hyprlock, hyprpaper/mpvpaper,
  the Quickshell theme-picker scripts, cursor/wallpaper/display
  helpers) — Pantheon ships its own Files/Terminal/Screenshot/
  notifications/theming natively. Kept the DE-agnostic scripts (vault
  toggle, metadata stripping, brightness control, Wi-Fi hotspot,
  telemetry, weather).
- theme.nix and default-apps.nix deleted entirely — Pantheon owns
  GTK/icon theming itself, no more live dconf/CSS generation pipeline
  needed.
- Deleted hyprland/ and dotfiles/ (kitty, mako, quickshell, rofi).

Verified with `nixos-rebuild build`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RpiovBQL6k5MTDMN3Ln9nL
This commit is contained in:
Nexoniarz 2026-08-31 19:52:59 +02:00
commit 6bcd5e15ad
15 changed files with 766 additions and 0 deletions

37
README.md Normal file
View file

@ -0,0 +1,37 @@
# nexoniarz-config-nixos-laptop
My personal NixOS system configuration tailored for daily use on an
**ASUS TUF Gaming A15 (FA507NU)**.
## Overview
- **OS:** NixOS
- **DE / Compositor:** Pantheon (gala, Wayland)
- **Display manager:** LightDM (elementary greeter)
- **Shell:** Zsh
- **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
`nvidia-offload <command>`)
- **Laptop control:** `asusctl` (fan curves, keyboard backlight, battery
charge limit) + `supergfxctl` (dGPU power-state switching)
## 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)
---
*Made by **Nexoniarz***

18
configuration.nix Normal file
View file

@ -0,0 +1,18 @@
# 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
];
# Do not change this value. Read documentation before updating.
system.stateVersion = "26.05";
}

View file

@ -0,0 +1,45 @@
# Do not modify this file! It was generated by ‘nixos-generate-config’
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{ config, lib, pkgs, modulesPath, ... }:
{
imports =
[ (modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [ "nvme" "xhci_pci" "thunderbolt" "usb_storage" "sd_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-amd" ];
boot.extraModulePackages = [ ];
fileSystems."/" =
{ device = "/dev/disk/by-uuid/2e0afef3-e81a-45c1-a439-c17123eef182";
fsType = "btrfs";
};
fileSystems."/home" =
{ device = "/dev/disk/by-uuid/2e0afef3-e81a-45c1-a439-c17123eef182";
fsType = "btrfs";
options = [ "subvol=home" ];
};
fileSystems."/nix" =
{ device = "/dev/disk/by-uuid/2e0afef3-e81a-45c1-a439-c17123eef182";
fsType = "btrfs";
options = [ "subvol=nix" ];
};
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/CA1D-BBF9";
fsType = "vfat";
options = [ "fmask=0077" "dmask=0077" ];
};
swapDevices =
[ { device = "/dev/disk/by-uuid/f52f4ee6-889e-4c86-82bf-ba8396e07483"; }
];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}

56
modules/apps.nix Normal file
View file

@ -0,0 +1,56 @@
{ config, pkgs, ... }:
{
# 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
# Apps
blender
gimp
inkscape
vesktop
tor-browser
ffmpeg-full
vlc
filezilla
chromium
# Games
prismlauncher
pkgs.osu-lazer-bin
# Dev: compilers, runtimes, languages
glibc
musl
cmake
gnumake # Provides the 'make' command
gcc
python3
openjdk25
claude-code # AI Developer Assistant
];
# Global command-line utilities available everywhere
environment.systemPackages = with pkgs; [
git
gh # GitHub CLI — was only installed ad-hoc before (login didn't survive)
vim
fastfetch
gparted
firejail
gnupg
lmstudio
p7zip
unzip
unrar
libreoffice # GTK3 build — matches Pantheon's native GTK theming, unlike the Qt build
];
}

38
modules/desktop.nix Normal file
View file

@ -0,0 +1,38 @@
{ 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.
services.xserver.enable = true;
services.xserver.displayManager.lightdm.enable = true;
services.desktopManager.pantheon.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;
# Native Wayland backend for Electron/Chromium-based apps
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;
};
}
];
}

79
modules/drivers.nix Normal file
View file

@ -0,0 +1,79 @@
{ 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 <command>` (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;
}

25
modules/scripts.nix Normal file
View file

@ -0,0 +1,25 @@
{ config, pkgs, ... }:
let
# Wrap external shell scripts into system executables
vaultToggle = pkgs.writeShellScriptBin "vault-toggle" (builtins.readFile ./scripts/vault-toggle.sh);
stripMetadata = pkgs.writeShellScriptBin "strip-metadata" (builtins.readFile ./scripts/strip-metadata.sh);
brightnessCtl = pkgs.writeShellScriptBin "brightness-ctl" (builtins.readFile ./scripts/brightness-ctl.sh);
hotspotStart = pkgs.writeShellScriptBin "hotspot-start" (builtins.readFile ./scripts/hotspot-start.sh);
systemTelemetry = pkgs.writeShellScriptBin "system-telemetry" (builtins.readFile ./scripts/system-telemetry.sh);
weatherFetch = pkgs.writeShellScriptBin "weather-fetch" (builtins.readFile ./scripts/weather-fetch.sh);
in
{
# Install CLI dependencies required by the scripts
environment.systemPackages = with pkgs; [
gocryptfs
exiftool
jq
vaultToggle
stripMetadata
brightnessCtl
hotspotStart
systemTelemetry
weatherFetch
];
}

View file

@ -0,0 +1,56 @@
#!/usr/bin/env bash
# Brightness control abstraction: prefers the kernel backlight class (laptop
# panels, via brightnessctl); falls back to DDC/CI over I2C (external
# monitors, via ddcutil) when no backlight device exists. Keeps the rest of
# the config (Quickshell's brightness popup) hardware-agnostic.
set -euo pipefail
has_backlight() {
[ -n "$(ls -A /sys/class/backlight 2>/dev/null)" ]
}
case "${1:-get}" in
get)
if has_backlight; then
brightnessctl -m | cut -d, -f4 | tr -d '%'
else
ddcutil getvcp 10 --brief 2>/dev/null | awk '{print $4}'
fi
;;
set)
pct="${2:?usage: brightness-ctl set <0-100>}"
if has_backlight; then
brightnessctl set "${pct}%" > /dev/null
else
ddcutil setvcp 10 "$pct" > /dev/null
fi
;;
up|down)
# brightnessctl takes a relative step directly, but ddcutil's
# setvcp only takes an absolute value — so the DDC/CI path (this
# machine's AOC 27G2G8 has no backlight device, so it always takes
# this path) has to read the current level and clamp the step
# itself.
step="${2:-5}"
if has_backlight; then
if [ "$1" = "up" ]; then
brightnessctl set "+${step}%" > /dev/null
else
brightnessctl set "${step}%-" > /dev/null
fi
else
cur=$(ddcutil getvcp 10 --brief 2>/dev/null | awk '{print $4}')
cur="${cur:-50}"
if [ "$1" = "up" ]; then
new=$(( cur + step > 100 ? 100 : cur + step ))
else
new=$(( cur - step < 0 ? 0 : cur - step ))
fi
ddcutil setvcp 10 "$new" > /dev/null
fi
;;
*)
echo "usage: brightness-ctl [get|set <0-100>|up [step]|down [step]]" >&2
exit 1
;;
esac

View file

@ -0,0 +1,33 @@
#!/usr/bin/env bash
# Starts a Wi-Fi hotspot with explicit security/band control — the simple
# `nmcli device wifi hotspot` one-liner doesn't expose those.
# hotspot-start <ssid> <password> <wpa2|wpa3> <auto|bg|a>
set -euo pipefail
ssid="${1:?usage: hotspot-start <ssid> <password> <wpa2|wpa3> <auto|bg|a>}"
password="${2:?password required}"
security="${3:-wpa2}"
band="${4:-auto}"
iface=$(nmcli -t -f DEVICE,TYPE device | awk -F: '$2=="wifi"{print $1; exit}')
if [ -z "$iface" ]; then
echo "No Wi-Fi device found" >&2
exit 1
fi
# Idempotent: drop any previous Hotspot profile from an earlier run first.
nmcli connection delete Hotspot >/dev/null 2>&1 || true
key_mgmt="wpa-psk"
[ "$security" = "wpa3" ] && key_mgmt="sae"
nmcli connection add type wifi ifname "$iface" con-name Hotspot autoconnect no ssid "$ssid" > /dev/null
nmcli connection modify Hotspot 802-11-wireless.mode ap
if [ "$band" != "auto" ]; then
nmcli connection modify Hotspot 802-11-wireless.band "$band"
fi
nmcli connection modify Hotspot wifi-sec.key-mgmt "$key_mgmt"
nmcli connection modify Hotspot wifi-sec.psk "$password"
nmcli connection modify Hotspot ipv4.method shared
nmcli connection up Hotspot

View file

@ -0,0 +1,24 @@
#!/usr/bin/env bash
if [ $# -eq 0 ]; then
echo "Usage: strip-metadata <file_or_directory>"
exit 1
fi
TARGET="$1"
echo "[*] Stripping metadata from: $TARGET"
# Remove all embedded metadata while keeping original filenames
if command -v exiftool &> /dev/null; then
exiftool -all= -overwrite_original -r "$TARGET"
echo "[+] Exif metadata removed."
else
echo "[!] Warning: exiftool is not installed."
fi
# Reset timestamps (atime and mtime) to Epoch (1970-01-01 00:00:00)
echo "[*] Resetting timestamps..."
find "$TARGET" -exec touch -t 197001010000.00 {} +
echo "[+] Operation completed successfully."

View file

@ -0,0 +1,62 @@
#!/usr/bin/env bash
# Single-shot system telemetry snapshot — CPU/RAM/GPU/VRAM/disk usage — as
# one JSON object, so the Quickshell System panel can poll this on a Timer
# instead of spawning half a dozen separate processes every tick.
set -euo pipefail
# --- CPU ---
# /proc/stat's first line ("cpu ...") is cumulative jiffies since boot —
# computing instantaneous usage needs two samples across a short gap, so
# both reads happen inside this one invocation rather than needing
# cross-poll state kept on the QML side.
read_cpu() {
read -r _ user nice system idle iowait irq softirq steal _ < /proc/stat
echo "$((user + nice + system + irq + softirq + steal)) $((user + nice + system + idle + iowait + irq + softirq + steal))"
}
read -r cpu_busy1 cpu_total1 <<< "$(read_cpu)"
sleep 0.2
read -r cpu_busy2 cpu_total2 <<< "$(read_cpu)"
cpu_busy_delta=$((cpu_busy2 - cpu_busy1))
cpu_total_delta=$((cpu_total2 - cpu_total1))
cpu_pct=0
[ "$cpu_total_delta" -gt 0 ] && cpu_pct=$((100 * cpu_busy_delta / cpu_total_delta))
# --- RAM ---
mem_total_kb=$(awk '/^MemTotal:/{print $2}' /proc/meminfo)
mem_avail_kb=$(awk '/^MemAvailable:/{print $2}' /proc/meminfo)
mem_used_kb=$((mem_total_kb - mem_avail_kb))
# --- GPU / VRAM ---
# NVIDIA checked first via nvidia-smi (works regardless of which DRM card
# index the driver landed on); AMD falls back to the card0 sysfs counters
# directly — whichever's actually present decides which branch fires, so
# this works unmodified on either machine.
gpu_pct=-1
vram_used_mb=-1
vram_total_mb=-1
if command -v nvidia-smi > /dev/null 2>&1 && nvidia-smi -L > /dev/null 2>&1; then
nv_out="$(nvidia-smi --query-gpu=utilization.gpu,memory.used,memory.total --format=csv,noheader,nounits -i 0 2>/dev/null | tr -d ' ')"
IFS=',' read -r gpu_pct vram_used_mb vram_total_mb <<< "$nv_out"
elif [ -e /sys/class/drm/card0/device/gpu_busy_percent ]; then
gpu_pct="$(cat /sys/class/drm/card0/device/gpu_busy_percent 2>/dev/null || echo -1)"
if [ -e /sys/class/drm/card0/device/mem_info_vram_used ]; then
vram_used_mb=$(($(cat /sys/class/drm/card0/device/mem_info_vram_used) / 1024 / 1024))
vram_total_mb=$(($(cat /sys/class/drm/card0/device/mem_info_vram_total) / 1024 / 1024))
fi
fi
# --- Disks (root + home) ---
# Positional ($NF for mountpoint), not header-text matching — `df -h`'s
# header is locale-translated (verified live: "System plików"/"%uż." under
# this machine's pl_PL locale, not "Filesystem"/"Use%"), so matching on
# English header text would silently find nothing.
disks_json="$(df -h 2>/dev/null | awk '
NR == 1 { next }
$NF == "/" || $NF == "/home" {
gsub(/%/, "", $5)
printf "%s{\"mount\":\"%s\",\"size\":\"%s\",\"used\":\"%s\",\"avail\":\"%s\",\"pct\":\"%s\"}", (n++ ? "," : ""), $NF, $2, $3, $4, $5
}
')"
printf '{"cpuPct":%s,"memUsedKb":%s,"memTotalKb":%s,"gpuPct":%s,"vramUsedMb":%s,"vramTotalMb":%s,"disks":[%s]}\n' \
"$cpu_pct" "$mem_used_kb" "$mem_total_kb" "$gpu_pct" "$vram_used_mb" "$vram_total_mb" "$disks_json"

30
modules/scripts/vault-toggle.sh Executable file
View file

@ -0,0 +1,30 @@
#!/usr/bin/env bash
CIPHER_DIR="/run/media/nexoniarz/Lukas/.vault_cipher"
MOUNT_DIR="$HOME/Vault"
mkdir -p "$CIPHER_DIR" "$MOUNT_DIR"
if mountpoint -q "$MOUNT_DIR"; then
echo "[*] Synchronizing buffers and unmounting Vault..."
sync
if fusermount -u "$MOUNT_DIR"; then
echo "[+] Vault unmounted successfully."
else
echo "[!] Failed to unmount Vault. Check if any application is using $MOUNT_DIR"
exit 1
fi
else
echo "[*] Mounting Vault..."
if [ ! -f "$CIPHER_DIR/gocryptfs.conf" ]; then
echo "[!] Container not initialized. Initializing new gocryptfs vault..."
gocryptfs -init "$CIPHER_DIR" || exit 1
fi
if gocryptfs -allow_other "$CIPHER_DIR" "$MOUNT_DIR"; then
echo "[+] Vault mounted successfully at $MOUNT_DIR"
else
echo "[!] Mount failed."
exit 1
fi
fi

View file

@ -0,0 +1,32 @@
#!/usr/bin/env bash
# Fetches current weather + a short hourly forecast from Open-Meteo (free,
# no API key/account needed) for either the machine's IP-geolocated
# position (default, via ip-api.com — also free/keyless) or an explicitly
# named city, and prints one combined JSON object.
set -euo pipefail
city="${1:-}"
if [ -n "$city" ]; then
encoded="$(jq -sRr @uri <<< "$city")"
geo="$(curl -s "https://geocoding-api.open-meteo.com/v1/search?count=1&name=$encoded")"
lat="$(jq -r '.results[0].latitude // empty' <<< "$geo")"
lon="$(jq -r '.results[0].longitude // empty' <<< "$geo")"
place="$(jq -r '.results[0].name // empty' <<< "$geo")"
country="$(jq -r '.results[0].country // empty' <<< "$geo")"
else
ipgeo="$(curl -s "http://ip-api.com/json/")"
lat="$(jq -r '.lat // empty' <<< "$ipgeo")"
lon="$(jq -r '.lon // empty' <<< "$ipgeo")"
place="$(jq -r '.city // empty' <<< "$ipgeo")"
country="$(jq -r '.country // empty' <<< "$ipgeo")"
fi
if [ -z "$lat" ] || [ -z "$lon" ]; then
echo '{"error":"location lookup failed"}'
exit 0
fi
weather="$(curl -s "https://api.open-meteo.com/v1/forecast?latitude=$lat&longitude=$lon&current=temperature_2m,weather_code,wind_speed_10m,precipitation&hourly=temperature_2m,weather_code,precipitation_probability&timezone=auto&forecast_days=2")"
jq --arg place "$place" --arg country "$country" '. + {place: $place, country: $country}' <<< "$weather"

204
modules/system.nix Normal file
View file

@ -0,0 +1,204 @@
{ 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";
}

27
modules/users.nix Normal file
View file

@ -0,0 +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" ];
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;
}