A modular, reproducible NixOS flake configuration managing multiple hosts (desktop and laptop) with Home-Manager integration.
- Overview
- Quick Start
- Onboarding a New Machine
- Project Structure
- Flake Inputs
- Deployment
- Secrets Management
- Configuration
- Troubleshooting
This repository contains a declarative NixOS configuration using flakes with the following features:
- Multi-host support:
desktopandthinkpad(laptop) - Modular architecture: Reusable NixOS and Home-Manager modules
- Desktop environment: Niri (Wayland) with Noctalia shell and Stylix theming
- Security: SOPS encrypted secrets, Mullvad VPN, Tor support
- Development: NVF (Neovim), Helix, language tooling (Go, Python)
- Automation:
justcommands for common tasks
| Host | Description | Key Features |
|---|---|---|
desktop |
Desktop PC | Gaming (Gamescope), full desktop environment |
thinkpad |
Laptop | NVIDIA GPU, power management (TLP) |
# Clone the repository
git clone https://github.com/ArthurExtra/System.git ~/System
cd ~/System
# Apply the configuration (runs full pipeline: modules check, lint, format, nh os switch, home-manager switch)
just all- Clone the repo:
git clone https://github.com/ArthurExtra/System.git ~/System
cd ~/System- Create the host configuration:
cd hosts
cp -r desktop myhost # or thinkpad/server as template- Copy the hardware configuration:
cp /etc/nixos/hardware-configuration.nix hosts/myhost/-
Update personal settings in
shared/constants.nix: Editshared/constants.nixto set your identity (name, email, GitHub handle, GPG signing key) and preferences (terminal, editor, fonts, keyboard layout, theme colors). -
Add the new host to
hosts/_inventory.nix: Openhosts/_inventory.nixand append your new host:
{
hostname = "myhost";
stateVersion = "26.05";
}- Set your hostname:
sudo hostnamectl set-hostname myhost- Setup SOPS encryption key (if not already done):
just sops-setup- Deploy the system:
# For the new host (using nh)
nh os switch --flake ~/System#myhost
# For home-manager (using nh)
nh home switch --flake ~/System#ad@myhost
# Or use the just commands:
just nixos # System rebuild
just home # Home Manager onlySystem/
├── flake.nix # Main flake definition
├── justfile # Task automation commands
├── .sops.yaml # SOPS encryption config
├── shared/ # Cross-boundary shared Nix helpers
│ ├── constants.nix # Single source of truth (user, fonts, colors, keyboard, ports)
│ ├── option-helpers.nix # Typed option constructors (mkBoolOption, mkStrOption, etc.)
│ ├── alias-helpers.nix # Shared shell alias injection for zsh/bash
│ └── *(secret-loader.nix moved to home/_helpers/_secret-loader.nix)*
├── scripts/ # Repository-level audit and lab scripts
│ ├── build/ # modules/security/performance audit scripts
│ ├── ai/ # AI agent launchers, inventory, and log analysis
│ ├── apps/ # Desktop app wrappers (browser-select, youtube-mpv, etc.)
│ ├── hardware/ # Hardware control scripts (nvidia-fans)
│ ├── system/ # System health report collectors
│ ├── sops/ # SOPS editing helpers
│ └── lib/ # Shared shell helpers (logging, testing, AWK)
│
├── hosts/ # Host-specific configurations
│ ├── _inventory.nix # Host list (single source of truth for flake.nix)
│ ├── desktop/ # Desktop PC configuration
│ │ ├── configuration.nix
│ │ ├── hardware-configuration.nix
│ │ └── modules/
│ ├── thinkpad/ # Laptop configuration
│ │ ├── configuration.nix
│ │ ├── hardware-configuration.nix
│ │ └── modules/
│
├── nixos/modules/ # Shared NixOS system modules (two-level pattern)
│ ├── default.nix # Root loader — imports category directories
│ ├── core/ # Boot, Nix daemon, users, SOPS, timezone, locale, stability
│ │ └── default.nix # Imports flat .nix files from parent dir
│ ├── hardware/ # Audio, Bluetooth, GPU, input, power, thermal
│ ├── desktop/ # Niri, SDDM, X11 disabled, XDG portals
│ ├── network/ # NetworkManager, DNSCrypt, Mullvad, Tailscale, Tor
│ ├── security-stack/ # Kernel hardening, Firejail, OpenSnitch, MAC randomization
│ ├── apps/ # Browser deps, Flatpak, Gaming, KDE Connect, Syncthing
│ ├── virtualization/ # Docker, VirtualBox, libvirt, Waydroid, nix-ld
│ ├── observability/ # Monitoring, Netdata, Scrutiny, Glance, Loki
│ ├── performance/ # Boot optimization
│ ├── maintenance/ # Cleanup timers, Restic backup, nh
│ │
│ ├── bootloader.nix # (flat files imported by category default.nix)
│ ├── security.nix # Kernel/sysctl hardening, firewall, AIDE, Chrony, AppArmor
│ └── ... # Other system modules
│
└── home/ # Home-Manager user configuration
├── home.nix # Main Home-Manager entry point
├── assets/ # Static assets (profile picture, etc.)
├── core/ # Home base split (user, session, GTK, activation, desktop-entries)
│
├── packages/ # User package definitions
│ ├── cli.nix # Command-line tools
│ ├── applications.nix # Desktop applications
│ ├── development.nix # Development tools
│ ├── multimedia.nix # Multimedia packages
│ └── ...
│
├── programs/ # Application configurations
│ ├── terminal/ # Terminal, shell (zsh), and CLI tools
│ │ ├── alacritty.nix # Alacritty terminal
│ │ ├── shell.nix # Zsh configuration
│ │ ├── zellij/ # Zellij terminal multiplexer
│ │ └── ...
│ ├── nvf/ # NVF (Neovim)
│ ├── languages/ # Language toolchains (Go, Python, Node.js)
│ ├── ai-agents/ # AI coding agent wrappers and launchers
│ ├── zen-browser/ # Zen Browser multi-profile setup
│ ├── isolation/ # Application isolation wrappers
│ └── ... # brave, discord, gpg, obs, spicetify, ssh, etc.
│
├── desktop/ # Desktop environment configs
│ ├── niri/ # Niri session config
│ ├── noctalia/ # Noctalia shell/bar/launcher
│ ├── qt/ # Qt Wayland integration
│ ├── mime/ # MIME defaults
│ └── udiskie/ # Auto-mount removable media
│
├── themes/ # Theme configurations
│ ├── stylix.nix # Stylix theming engine
│ ├── palette.nix # Catppuccin Mocha color palette
│ └── options.nix # Theme option definitions
│
└── scripts/ # User scripts (nerdfont-fzf, build helpers)
This configuration uses the following flakes:
| Flake | Purpose |
|---|---|
| nixpkgs | NixOS unstable packages |
| nixpkgs-stable | NixOS 26.05 stable packages |
| home-manager | User environment management |
| sops-nix | Secret management with age encryption |
| stylix | System theming (Catppuccin Mocha theme) |
| spicetify-nix | Spotify customization |
| niri | Niri compositor module/overlay (does NOT follow nixpkgs — pinned mesa version required for compatibility) |
| noctalia | Noctalia shell integration |
| nixcord | Discord theming |
| nvf | Neovim configuration framework |
| ghgrab | GitHub release downloader utility |
This repository uses just as a command runner to simplify common tasks.
The repository includes pre-commit and pre-push hooks for automated quality checks:
# Install pre-commit hook (runs modules-check + lint + format before each commit)
ln -sf ../../scripts/build/pre-commit-hook.sh .git/hooks/pre-commit
# Install pre-push hook (enforces GPG-signed commits)
ln -sf ../../scripts/build/pre-push-hook.sh .git/hooks/pre-push| Command | Description |
|---|---|
just |
List all available commands |
just all |
Run full pipeline: modules, lint, format, security, flake check, nixos, home |
just nixos |
Rebuild and switch current host (--hostname $(hostname)) with faster nh flags |
just nixos <host> |
Rebuild and switch a specific host profile (e.g. just nixos desktop) |
just nixos-fast |
Faster switch path (NH_NO_VALIDATE=1, no NOM) for iterative changes |
just home |
Apply Home-Manager configuration using nh with lockfile-write disabled |
just format |
Format all .nix files with nixfmt |
just lint |
Lint all .nix files with statix + bash shellcheck |
just modules |
Check for missing module imports (critical before commits) |
just security |
Scan for risky security patterns and plaintext secret leaks |
just perf |
Print boot/session performance diagnostics |
just hardening |
Run systemd-analyze security report on core services |
just check |
Evaluate full flake outputs with nix flake check |
just eval-audit |
Measure eval time for all host nixos/home outputs |
just eval-current |
Measure eval time for current host outputs only |
just qa |
Full local QA (modules + security + check + eval-audit) |
just qa-fast |
Fast local QA (modules + security + eval-current) |
just update |
Update all flake inputs |
just update-pkgs |
Update nixpkgs only |
just update-pkgs-stable |
Update stable nixpkgs |
just clean |
Clean up old generations and optimize store using nh |
# NixOS system rebuild
sudo nixos-rebuild switch --flake .#desktop
# Home-Manager switch
home-manager switch --flake .#ad@desktop
# Using nh
nh os switch . -- hostname desktop
nh home switch . -- hostname desktopThis configuration uses SOPS with age encryption for secrets.
# Setup age key (if not already done)
just sops-setup
# View current secrets
just sops-view
# Edit secrets
just sops-edit
# NOTE: If interrupted, a decrypted file may remain on disk.
# The .gitignore pattern *-decrypted.* prevents accidental commits.
# Add single secret
# WARNING: The value will be stored in shell history.
# For sensitive values, use `just sops-edit` instead.
just secrets-add github_token ghp_your_token_here| Command | Purpose |
|---|---|
just sops-view |
View decrypted secrets |
just sops-edit |
Edit secrets (opens VS Code) |
just secrets-add key value |
Add single secret |
just sops-decrypt |
Decrypt to file for manual editing |
just sops-encrypt |
Encrypt file back to secrets |
just sops-key |
Show public age key |
just sops-setup |
Setup new age key |
just setup-keys |
Setup SSH and GPG keys from secrets |
nixos/modules/sops.nix
secrets/secrets.yaml
- Secrets are encrypted at rest using age encryption
- Private keys are stored in
~/.config/sops/age/keys.txt - Never commit private keys or decrypted secrets to Git
- Use
just sops-editfor most editing (automatic encrypt/decrypt)
# In any Nix file
sops.secrets.mysecret = {
owner = config.users.users.ad.name;
};
# Access in scripts or services
environment.variables.MY_SECRET = config.sops.placeholder.mysecret;- Create host directory:
mkdir -p hosts/myhost/modules- Create
configuration.nix:
{ config, inputs, hostname, stateVersion, pkgsStable, ... }:
{
imports = [
./hardware-configuration.nix
../../nixos/modules
./modules/default.nix
];
# Host-specific settings
networking.hostName = "myhost";
}- Create
hardware-configuration.nix:
sudo nixos-generate-config --root /mnt --show-hardware-config > hosts/myhost/hardware-configuration.nix- Add to
hosts/_inventory.nix:
{
hostname = "myhost";
stateVersion = "25.11";
}- Create module file in
nixos/modules/(flat file alongside existing ones):
# nixos/modules/my-module.nix
{ config, lib, pkgs, ... }:
{
# Define options under mySystem.* for per-host opt-in
options.mySystem.myModule = {
enable = lib.mkEnableOption "my custom module";
};
config = lib.mkIf config.mySystem.myModule.enable {
# Your configuration here
};
}- Import it from the appropriate category
default.nix(e.g.,nixos/modules/core/default.nix):
{
imports = [
# ... existing modules ...
../my-module.nix
];
}- Enable it in the host configuration (e.g.,
hosts/desktop/configuration.nix):
mySystem.myModule.enable = true;- Run
just modulesto verify the import is detected.
- Create program directory in
home/programs/:
mkdir home/programs/myprogram- Create
default.nix:
{ config, lib, ... }:
{
programs.myprogram = {
enable = true;
# Settings
};
}- Import in
home/programs/default.nix
Edit the appropriate file in home/packages/:
# home/packages/cli.nix
{ pkgs, ... }:
{
home.packages = with pkgs; [
# Add packages here
my-new-package
];
}The configuration uses hostname-based conditional loading:
# In home/packages or home/programs
config = lib.mkIf (hostname == "desktop") {
# Desktop-only packages
};- Compositor: Niri (Wayland)
- Shell: Noctalia (bar, launcher, control center, notifications)
- Theme: Catppuccin Mocha via Stylix
- Screen Locker: swaylock + Noctalia lock integration
- Clipboard stack: wl-paste + cliphist + wl-clip-persist
- Neovim: NVF configuration with LSP
- Editor: Helix
- Languages: Go, Python, Node.js support
- Tools: Git, lazydocker, gh
- VPN: Mullvad, Tailscale support
- Anonymity: Tor relay
- Secrets: SOPS encrypted with age
- Firewall: Configured via networking module
- Gamescope: For Steam games
- optimizations: GPU drivers, performance tweaks
# Check for missing modules
just modules
# Lint files
just lint
# View build logs
sudo nixos-rebuild switch --flake .#desktop --show-trace# Check if age key exists
just sops-key
# Regenerate key
just sops-setup
# Verify .sops.yaml has correct age key
cat .sops.yaml# Check current hostname
hostname
# Set correct hostname
sudo hostnamectl set-hostname desktop# Clean and optimize
just clean
# Manual cleanup
nix-collect-garbage -d
nix store optimise# Update flake lock
nix flake update
# Rebuild after update
just all# Check Home-Manager config
home-manager switch --flake .#ad@desktop --show-trace
# Expire old generations
home-manager expire-generations "-7 days"This configuration is a fork/derivative of the excellent modular foundation by pageton/nixos-config.
While it is based on the original architecture, I actively maintain, customize, and optimize the configurations to fit my workflow, alongside debugging and fixing system bugs as they arise. Special thanks to the original author for the great starting point.
MIT License - see LICENSE for details.