A NixOS library for declarative host management, theming, and desktop configuration
- Overview
- Quick Start
- Installation
- Home Manager Modules
- NixOS Modules
- Flake-Parts Modules
- Library Reference
- Real-World Example
- Design Philosophy
- Contributing
mix.nix is a library of Nix utilities designed to simplify NixOS and Home Manager configurations. It provides reusable patterns, type definitions, and builder functions that reduce boilerplate and enforce consistency across flake-based configurations.
The library offers:
- Declarative host/user management - Define users once, reference them across multiple hosts, auto-generate
nixosConfigurations - Wallpaper-based theming - Generate Material You color schemes from your wallpaper using matugen
- Multi-monitor configuration - Declare monitor layouts once, use everywhere
- Git-crypt secrets integration - Load encrypted secrets with validation to prevent accidental plaintext commits
- Directory auto-discovery - Drop files in directories, they're automatically imported
mix.nix is consumed by other flakes via inputs.mix-nix and extends nixpkgs.lib with custom utilities.
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
mix-nix.url = "github:tophc7/mix.nix";
# mix-nix follows nixpkgs automatically
};
outputs = { nixpkgs, mix-nix, ... }: {
# Import a Home Manager module directly
homeConfigurations.myuser = home-manager.lib.homeManagerConfiguration {
# ...
modules = [
mix-nix.homeManagerModules.theme
{
theme = {
enable = true;
image = ./wallpaper.jpg;
base16.generate = true; # Generate colors from wallpaper
};
}
];
};
};
}See Installation for full setup with flake-parts.
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
home-manager.url = "github:nix-community/home-manager";
mix-nix = {
url = "github:tophc7/mix.nix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = inputs@{ flake-parts, mix-nix, ... }:
flake-parts.lib.mkFlake {
inherit inputs;
specialArgs = { lib = mix-nix.lib; };
} {
imports = [ mix-nix.flakeModules.default ];
# Or import selectively:
# imports = [
# mix-nix.flakeModules.hosts
# mix-nix.flakeModules.secrets
# mix-nix.flakeModules.modules
# ];
systems = [ "x86_64-linux" "aarch64-linux" ];
mix = {
# Your host and user configurations...
};
};
}Why
specialArgs? The extendedlib(withlib.hosts,lib.secrets, etc.) must be passed viaspecialArgsso flake-parts modules can access it.specialArgshas highest priority and cannot be shadowed.
Use lib.mkFlake for the same declarative host management without flake-parts:
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
home-manager.url = "github:nix-community/home-manager";
mix-nix = {
url = "github:tophc7/mix.nix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { nixpkgs, mix-nix, home-manager, ... }@inputs:
mix-nix.lib.mkFlake {
inherit inputs;
homeManager = home-manager;
coreModules = [ ./modules/core ];
coreHomeModules = [
./home/core
mix-nix.homeManagerModules.theme
mix-nix.homeManagerModules.monitors
];
hostsDir = ./hosts;
hostsHomeDir = ./home/hosts;
# Optional: git-crypt encrypted secrets
secrets = {
file = ./secrets.nix;
gitattributes = ./.gitattributes;
};
users.myuser = {
name = "myuser";
shell = nixpkgs.legacyPackages.x86_64-linux.fish;
home.directory = ./home/users/myuser;
};
hosts.desktop = {
user = "myuser";
desktop = "niri";
};
};
# Returns: { nixosConfigurations.desktop = <nixosSystem>; }
}For non-flake configurations, use builtins.getFlake to access flake outputs:
# configuration.nix or home.nix
let
mix-nix = builtins.getFlake "github:tophc7/mix.nix";
# Or pin to a specific commit:
# mix-nix = builtins.getFlake "github:tophc7/mix.nix/<commit-sha>";
in
{
imports = [
mix-nix.homeManagerModules.theme
mix-nix.homeManagerModules.monitors
];
theme = {
enable = true;
image = ./wallpaper.jpg;
};
}Note: Requires
--impureflag when building. For full host management withlib.mkFlake, use a flake-based setup instead.
Import modules via inputs.mix-nix.homeManagerModules.<name>.
theme - Wallpaper-based theming with Material You colors
A centralized theme specification module. Declares theme identity (wallpaper, colors, icons, fonts) and optionally generates color schemes from your wallpaper using matugen.
Note: This module does NOT wire settings to stylix/GTK. Consumers read
theme.*values and apply them to their preferred theming system.
| Option | Type | Default | Description |
|---|---|---|---|
theme.enable |
bool |
false |
Enable theme specification |
theme.image |
path |
required | Path to wallpaper image |
theme.polarity |
"light" | "dark" |
"dark" |
Light or dark theme variant |
| Option | Type | Default | Description |
|---|---|---|---|
theme.icon |
null | submodule |
null |
Icon theme specification |
theme.icon.package |
package |
- | Icon theme package (e.g., pkgs.papirus-icon-theme) |
theme.icon.name |
string |
- | Icon theme name (e.g., "Papirus") |
| Option | Type | Default | Description |
|---|---|---|---|
theme.pointer |
null | submodule |
null |
Cursor theme specification |
theme.pointer.package |
package |
- | Cursor theme package |
theme.pointer.name |
string |
- | Cursor theme name |
theme.pointer.size |
int |
24 |
Cursor size in pixels |
| Option | Type | Default | Description |
|---|---|---|---|
theme.fonts |
null | submodule |
null |
Font specification |
theme.fonts.serif |
{package, name} |
- | Serif font configuration |
theme.fonts.sansSerif |
{package, name} |
- | Sans-serif font configuration |
theme.fonts.monospace |
{package, name} |
- | Monospace font configuration |
theme.fonts.emoji |
{package, name} |
- | Emoji font configuration |
theme.fonts.sizes.applications |
int |
12 |
Application font size |
theme.fonts.sizes.desktop |
int |
11 |
Desktop element font size |
theme.fonts.sizes.popups |
int |
11 |
Popup/notification font size |
theme.fonts.sizes.terminal |
int |
12 |
Terminal font size |
| Option | Type | Default | Description |
|---|---|---|---|
theme.base16.file |
null | path |
null |
Path to pre-made base16 YAML file |
theme.base16.package |
null | package |
null |
Base16 scheme package |
theme.base16.generate |
bool |
false |
Generate from wallpaper using matugen |
Only one of
base16.file,base16.package, orbase16.generatecan be set.
| Option | Type | Default | Description |
|---|---|---|---|
theme.matugen.package |
package |
inputs.matugen |
Matugen package |
theme.matugen.scheme |
enum |
"scheme-expressive" |
Material You scheme type |
theme.matugen.templates |
attrsOf {template, path} |
{} |
Custom template configurations |
Available schemes: scheme-content, scheme-expressive, scheme-fidelity, scheme-fruit-salad, scheme-monochrome, scheme-neutral, scheme-rainbow, scheme-tonal-spot, scheme-vibrant
| Option | Type | Default | Description |
|---|---|---|---|
theme.installGeneratedFiles |
bool |
true |
Install generated files to home directory |
| Option | Type | Description |
|---|---|---|
theme.generated.base16Scheme |
null | path |
Path to the generated or provided base16 scheme |
theme.generated.files |
attrsOf path |
Paths to all generated matugen files |
theme.generated.derivation |
null | package |
The matugen output derivation |
{ pkgs, config, ... }:
{
theme = {
enable = true;
image = ./wallpapers/mountain.jpg;
polarity = "dark";
icon = {
package = pkgs.papirus-icon-theme;
name = "Papirus-Dark";
};
pointer = {
package = pkgs.bibata-cursors;
name = "Bibata-Modern-Classic";
size = 24;
};
# Generate base16 colors from wallpaper
base16.generate = true;
matugen.scheme = "scheme-tonal-spot";
# Custom templates for other apps
matugen.templates = {
waybar = {
template = ./templates/waybar-colors.css;
path = ".config/waybar/colors.css";
};
};
};
# Wire to stylix (example)
stylix.image = config.theme.image;
stylix.base16Scheme = config.theme.generated.base16Scheme;
}monitors - Multi-monitor configuration
A monitor specification module for declaring display layouts. Like the theme module, this provides a centralized source of truth - consumers are responsible for reading monitors values and applying them to their compositor or display manager.
Note: The
vrr = "on-demand"option is specific to niri's fullscreen-only VRR mode, but the module itself is compositor-agnostic.
| Option | Type | Default | Description |
|---|---|---|---|
monitors |
listOf submodule |
[] |
List of monitor configurations |
monitors.*.name |
string |
required | Monitor output name (e.g., "DP-1", "HDMI-A-1", "eDP-1") |
monitors.*.primary |
bool |
false |
Whether this is the primary monitor |
monitors.*.width |
int |
required | Horizontal resolution in pixels |
monitors.*.height |
int |
required | Vertical resolution in pixels |
monitors.*.refreshRate |
int | float |
60 |
Refresh rate in Hz |
monitors.*.x |
int |
0 |
X position in combined display layout |
monitors.*.y |
int |
0 |
Y position in combined display layout |
monitors.*.scale |
number |
1.0 |
Display scaling factor (1.0 = 100%) |
monitors.*.transform |
int |
0 |
Rotation (see below) |
monitors.*.enabled |
bool |
true |
Whether monitor is enabled |
monitors.*.hdr |
bool |
false |
Enable HDR |
monitors.*.vrr |
bool | "on-demand" |
false |
Variable Refresh Rate |
Transform values:
0- Normal (landscape)1- 90 degrees clockwise (portrait right)2- 180 degrees (landscape flipped)3- 270 degrees clockwise (portrait left)4-7- Flipped variants
VRR values:
true- Always enabledfalse- Disabled"on-demand"- Enabled only for fullscreen apps (niri)
Assertion: If monitors are defined, exactly one must be marked as
primary = true.
{
monitors = [
{
name = "DP-1";
primary = true;
width = 2560;
height = 1440;
refreshRate = 144;
vrr = true;
}
{
name = "HDMI-A-1";
width = 1920;
height = 1080;
x = 2560; # Position to the right
scale = 1.0;
}
];
}fastfetch - Styled system info display
Opinionated fastfetch configuration with weather integration and custom logos.
| Option | Type | Default | Description |
|---|---|---|---|
mix.fastfetch.enable |
bool |
false |
Enable fastfetch module |
mix.fastfetch.weather.location |
string |
required | City for weather display (e.g., "London,UK") |
mix.fastfetch.logo.source |
null | path |
null |
Direct path to logo file (highest priority) |
mix.fastfetch.logo.directory |
null | path |
null |
Directory containing hostname-based logos |
mix.fastfetch.logo.hostname |
null | string |
null |
Hostname for directory lookup |
Logo resolution priority:
logo.source- Direct path if specifiedlogo.directory/<hostname>.png- Hostname-based lookup- Bundled fallback
nix.png
When used with mix.hosts.mkHost, logo.hostname defaults to host.hostName from specialArgs.
{
mix.fastfetch = {
enable = true;
weather.location = "San Francisco,US";
# Option A: Direct logo
logo.source = ./my-logo.png;
# Option B: Directory-based (looks for <hostname>.png)
# logo.directory = ./logos;
};
}nautilus - File manager configuration
Configure Nautilus/GNOME Files with GTK bookmarks and custom folder icons.
| Option | Type | Default | Description |
|---|---|---|---|
programs.nautilus.enable |
bool |
false |
Enable Nautilus configuration |
programs.nautilus.bookmarks |
listOf {path, name?} |
[] |
Sidebar bookmarks |
programs.nautilus.bookmarks.*.path |
string |
required | Absolute path for the bookmark |
programs.nautilus.bookmarks.*.name |
null | string |
null |
Display name (uses basename if null) |
programs.nautilus.folderIcons |
attrsOf string |
{} |
Folder path to icon name mappings |
{
programs.nautilus = {
enable = true;
bookmarks = [
{ path = "/fast"; name = "Fast Storage"; }
{ path = "/repo"; name = "Repositories"; }
{ path = "/home/user/Documents"; } # Uses "Documents" as name
];
folderIcons = {
"/steam" = "folder-steam";
"/repo" = "folder-git";
"/home/user/Downloads" = "folder-download";
};
};
}Import modules via inputs.mix-nix.nixosModules.<name>.
newt - Pangolin Docker tunnel client
Runs Newt in a Docker container for Pangolin tunneling with full Docker socket access, enabling container network validation and orchestration features.
Note: This module disables the upstream native
services.networking.newtmodule to avoid conflicts. Use upstream if you prefer a native (non-Docker) approach.
| Option | Type | Default | Description |
|---|---|---|---|
services.newt.enable |
bool |
false |
Enable Newt Docker container service |
services.newt.id |
string |
required | Newt ID for authentication with Pangolin server |
services.newt.secret |
null | string |
null |
Plaintext secret (not recommended for production) |
services.newt.secretFile |
null | path |
null |
Path to env file containing NEWT_SECRET=... |
services.newt.image |
string |
"fosrl/newt" |
Docker image to use |
services.newt.pangolinEndpoint |
string |
required | Pangolin server endpoint URL |
services.newt.networkName |
string |
"newt" |
Docker network name for container communication |
services.newt.networkAlias |
string |
"newt" |
Network alias for the container |
services.newt.useHostNetwork |
bool |
false |
Use host networking (disables network validation) |
services.newt.extraNetworks |
listOf string |
[] |
Additional Docker networks to connect to |
Authentication: Either
secretorsecretFilemust be set, but not both. UsesecretFilewith sops-nix or agenix for production.
{ config, ... }:
{
imports = [ inputs.mix-nix.nixosModules.newt ];
services.newt = {
enable = true;
id = "your-newt-id";
secretFile = config.sops.secrets.newt-secret.path;
pangolinEndpoint = "https://pangolin.example.com";
# Optional: connect to additional networks
extraNetworks = [ "traefik" ];
};
}olm - Pangolin native tunnel client
Native OLM binary for WireGuard-based Pangolin tunneling. Connects your machine directly to Pangolin/Newt sites without Docker.
| Option | Type | Default | Description |
|---|---|---|---|
services.olm.enable |
bool |
false |
Enable OLM tunneling client |
services.olm.autoStart |
bool |
false |
Start automatically at boot |
services.olm.id |
string |
required | OLM client identifier |
services.olm.secret |
null | string |
null |
Plaintext secret (not recommended) |
services.olm.secretFile |
null | path |
null |
Path to file containing the secret |
services.olm.endpoint |
string |
required | Pangolin endpoint URL |
services.olm.configFile |
null | path |
null |
Config file path (overrides other options) |
| Option | Type | Default | Description |
|---|---|---|---|
services.olm.endpointIP |
null | string |
null |
Direct IP to bypass DNS/proxy |
services.olm.mtu |
int |
1280 |
Network interface MTU |
services.olm.dns |
null | string |
null |
DNS server (uses system default if null) |
services.olm.interfaceName |
string |
"olm0" |
WireGuard interface name |
services.olm.holepunch |
bool |
false |
Enable NAT traversal (experimental) |
| Option | Type | Default | Description |
|---|---|---|---|
services.olm.logLevel |
enum |
"INFO" |
DEBUG, INFO, WARN, ERROR, or FATAL |
services.olm.pingInterval |
string |
"3s" |
Server ping frequency |
services.olm.pingTimeout |
string |
"5s" |
Ping response timeout |
services.olm.healthFile |
null | path |
null |
Path for connection status tracking |
| Option | Type | Default | Description |
|---|---|---|---|
services.olm.package |
package |
pkgs.fosrl-olm |
OLM package to use |
services.olm.enableGnomeExtension |
bool |
false |
Enable GNOME Shell panel toggle |
services.olm.gnomeExtensionPackage |
package |
pkgs.olm-toggle |
GNOME extension package |
Authentication: One of
secret,secretFile, orconfigFilemust be set. UsesecretFilewith sops-nix or agenix for production.
{ config, ... }:
{
imports = [ inputs.mix-nix.nixosModules.olm ];
services.olm = {
enable = true;
autoStart = false; # Manual control via systemctl
id = "your-olm-id";
secretFile = config.sops.secrets.olm-secret.path;
endpoint = "https://pangolin.example.com";
# Optional tuning
mtu = 1400;
logLevel = "DEBUG";
# Desktop integration
enableGnomeExtension = true;
};
}oci-stacks - OCI container stack orchestration
Abstracts Docker container orchestration boilerplate by generating network services, systemd service configuration, and root targets from simple stack definitions.
Note: Modules using
lib.infra.*require the extended lib viaspecialArgsinnixosSystem.
| Option | Type | Default | Description |
|---|---|---|---|
virtualisation.oci-stacks |
attrsOf stackType |
{} |
OCI container stack definitions |
virtualisation.oci-stacks.<name>.containers |
attrsOf attrs |
{} |
Container definitions (passed to oci-containers) |
virtualisation.oci-stacks.<name>.network |
string | submodule |
stack name | Network configuration |
virtualisation.oci-stacks.<name>.description |
null | string |
null |
Description for the systemd root target |
When network is an attrset instead of a string:
| Option | Type | Default | Description |
|---|---|---|---|
network.name |
null | string |
stack name | Network name |
network.driver |
string |
"bridge" |
Docker network driver |
network.subnet |
null | string |
null |
Network subnet (e.g., "10.1.1.0/24") |
network.gateway |
null | string |
null |
Network gateway IP |
network.script |
null | lines |
null |
Custom network creation script |
network.external |
listOf string |
[] |
External networks (soft dependencies from other stacks) |
For each stack, the module automatically creates:
- Containers passed through to
virtualisation.oci-containers - Network service (
docker-network-<name>) with automatic creation/cleanup - Container services with restart policies via
lib.infra.containers.serviceDefaults - Network dependencies wired between containers and networks
- Root systemd target (
docker-compose-<name>-root) for orchestration
{ config, ... }:
{
imports = [ inputs.mix-nix.nixosModules.oci-stacks ];
virtualisation.oci-stacks.myapp = {
containers.myapp = {
image = "myapp:latest";
ports = [ "8080:80" ];
extraOptions = [
"--network=myapp"
"--network-alias=myapp"
];
};
description = "My application stack";
};
# With custom network configuration
virtualisation.oci-stacks.database = {
containers.postgres = {
image = "postgres:16";
extraOptions = [
"--network=database"
"--network-alias=db"
];
environment = {
POSTGRES_PASSWORD = "secret";
};
};
network = {
name = "database";
subnet = "10.1.1.0/24";
gateway = "10.1.1.1";
};
};
# Stack depending on external network
virtualisation.oci-stacks.webapp = {
containers.web = {
image = "nginx:latest";
extraOptions = [
"--network=webapp"
"--network=database" # Connect to database network
];
};
network = {
external = [ "database" ]; # Soft dependency on database network
};
};
}Import all modules at once:
imports = [ inputs.mix-nix.flakeModules.default ];Or import selectively via imports = [ inputs.mix-nix.flakeModules.<name> ].
hosts - Declarative host and user management
The primary integration point. Define users once, reference them across hosts, and automatically generate nixosConfigurations.
| Option | Type | Default | Description |
|---|---|---|---|
mix.users |
attrsOf userSpec |
{} |
User definitions |
mix.users.<name>.name |
string |
required | Username |
mix.users.<name>.uid |
null | int |
null |
User ID (null for auto) |
mix.users.<name>.group |
string |
"users" |
Primary group |
mix.users.<name>.shell |
package | string |
required | Default shell (package or name like "fish") |
mix.users.<name>.extraGroups |
listOf string |
["wheel", "networkmanager"] |
Additional groups |
Note: Home Manager is auto-enabled via
usersHomeDirdiscovery. If<usersHomeDir>/<username>/or<usersHomeDir>/<username>.nixexists, HM is enabled for that user.
| Option | Type | Default | Description |
|---|---|---|---|
mix.hosts |
attrsOf hostSpec |
{} |
Host definitions |
mix.hosts.<name>.enable |
bool |
true |
Build this host |
mix.hosts.<name>.hostName |
string |
attr name | Hostname |
mix.hosts.<name>.system |
enum |
"x86_64-linux" |
Architecture (x86_64-linux, aarch64-linux) |
mix.hosts.<name>.user |
string |
required | Username from mix.users |
mix.hosts.<name>.isServer |
bool |
false |
Server mode (affects autoLogin) |
mix.hosts.<name>.isMinimal |
bool |
false |
Skip user/host HM directories |
mix.hosts.<name>.desktop |
null | string |
null |
Desktop environment (null = headless) |
mix.hosts.<name>.autoLogin |
bool |
derived | Auto-login (default: !isServer && desktop != null) |
mix.hosts.<name>.specialArgs |
attrs |
{} |
Extra specialArgs for nixosSystem |
| Option | Type | Default | Description |
|---|---|---|---|
mix.coreModules |
listOf deferredModule |
[] |
NixOS modules applied to ALL hosts |
mix.coreHomeModules |
listOf deferredModule |
[] |
HM modules applied to ALL users with HM enabled |
| Option | Type | Default | Description |
|---|---|---|---|
mix.hostsDir |
null | path |
null |
Auto-discover NixOS configs from <hostsDir>/<hostname>/ |
mix.hostsHomeDir |
null | path |
null |
Auto-discover HM configs from <hostsHomeDir>/<hostname>/ |
mix.usersHomeDir |
null | path |
null |
Auto-discover user HM configs from <usersHomeDir>/<user>/ |
Note: Home Manager is automatically enabled for a user when their home config path exists (either via
usersHomeDirauto-discovery or explicithome.directory).
Auto-discovery supports both directory and flat file layouts:
# Directory style (recommended for complex configs)
hosts/desktop/default.nix → imported as ./hosts/desktop
home/users/toph/default.nix → imported as ./home/users/toph
# Flat file style (simpler for small configs)
hosts/desktop.nix → imported directly
home/users/toph.nix → imported directly
The lookup order is: directory first, then flat file.
Important: mix.nix only imports the default.nix from each directory path you provide. It does NOT recursively scan or auto-import sibling files.
For example, with this configuration:
mix = {
coreModules = [ ./modules/core ]; # Imports ./modules/core/default.nix
coreHomeModules = [ ./home/core ]; # Imports ./home/core/default.nix
hostsDir = ./hosts; # Imports ./hosts/<hostname>/ or ./hosts/<hostname>.nix
hostsHomeDir = ./home/hosts; # Imports ./home/hosts/<hostname>/ or .nix
usersHomeDir = ./home/users; # Imports ./home/users/<username>/ or .nix (enables HM)
};Each default.nix controls what gets imported from its directory. This gives you full control over your module structure.
If you want automatic sibling import, use lib.scanPaths in your default.nix:
# modules/core/default.nix
{ lib, ... }:
{
imports = lib.scanPaths ./.; # Auto-imports all .nix files and directories with default.nix
}lib.scanPaths returns paths to:
- All
.nixfiles (exceptdefault.nixitself) - All directories (Nix will look for their
default.nix)
lib.scanPaths includes ALL directories, so directories without a default.nix will cause import failures:
home/hosts/gojo/
├── default.nix # Entry point
├── theme.nix # ✅ Imported as file
├── config/ # ✅ Imported if config/default.nix exists
│ └── default.nix
└── wallpapers/ # ❌ FAILS - no default.nix!
└── mountain.png
Solutions:
-
Keep assets outside scanned directories:
home/hosts/gojo/ ├── default.nix ├── theme.nix └── wallpaper.png # Reference directly: ./wallpaper.png -
Don't use
scanPaths- import explicitly:# default.nix { ... }: { imports = [ ./theme.nix ./programs.nix # Don't import ./wallpapers - it's just assets ]; }
-
Use a dedicated assets path referenced in your config:
# theme.nix { ... }: { theme.image = ./assets/wallpaper.png; # Direct path, not imported }
| Option | Type | Default | Description |
|---|---|---|---|
mix.userSpecType |
raw |
lib.hosts.types.userSpec |
Custom user type via lib.hosts.mkUserSpec |
mix.hostSpecType |
raw |
lib.hosts.types.hostSpec |
Custom host type via lib.hosts.mkHostSpec |
mix.homeManager |
null | attrs |
inputs.home-manager |
Home Manager input |
When using mix.hosts, these are available in your NixOS and Home Manager modules:
host- The full resolved host spec with user data merged insecrets- Loaded secrets (if usingmix.secrets)
{
imports = [ inputs.mix-nix.flakeModules.hosts ];
mix = {
coreModules = [ ./modules/core ];
coreHomeModules = [ ./home/core ];
hostsDir = ./hosts;
hostsHomeDir = ./home/hosts;
# Auto-discover user HM configs from ./home/users/<username>/
usersHomeDir = ./home/users;
users = {
toph = {
name = "toph";
uid = 1000;
shell = pkgs.fish;
# HM enabled via usersHomeDir auto-discovery (./home/users/toph/)
};
admin = {
name = "admin";
shell = pkgs.bash;
# No ./home/users/admin/ = system user only, no Home Manager
};
};
hosts = {
desktop = {
user = "toph"; # References mix.users.toph
desktop = "niri";
# autoLogin defaults to true (not server, has desktop)
};
server = {
user = "admin";
isServer = true;
system = "aarch64-linux";
# autoLogin defaults to false (is server)
};
laptop = {
user = "toph";
desktop = "gnome";
isMinimal = true; # Only coreHomeModules, skip user/host dirs
};
};
};
}secrets - Git-crypt encrypted secrets
Load git-crypt encrypted secrets with validation to prevent accidental plaintext commits.
| Option | Type | Default | Description |
|---|---|---|---|
mix.secrets.file |
null | path |
null |
Path to secrets.nix (should be git-crypt encrypted) |
mix.secrets.gitattributes |
null | path |
null |
Path to .gitattributes for validation |
mix.secrets.pattern |
string |
"secrets.nix" |
Pattern to match in .gitattributes |
mix.secrets.skipValidation |
bool |
false |
Skip git-crypt check (NOT RECOMMENDED) |
mix.secrets.loaded |
attrsOf anything |
(read-only) | The loaded secrets |
{
imports = [
inputs.mix-nix.flakeModules.secrets
inputs.mix-nix.flakeModules.hosts
];
mix.secrets = {
file = ./secrets.nix;
gitattributes = ./.gitattributes;
};
# Secrets are automatically available in NixOS/HM modules via:
# - specialArgs: secrets.myKey
# - Or access config.mix.secrets.loaded.myKey in flake-parts
}Your .gitattributes should include:
secrets.nix filter=git-crypt diff=git-crypt
modules - NixOS and Home Manager module exports
Exposes NixOS and Home Manager modules as flake outputs.
Outputs:
nixosModules.<name>- Individual NixOS modulesnixosModules.default- All NixOS moduleshomeManagerModules.<name>- Individual HM moduleshomeManagerModules.default- All HM modules
# Import individual module
imports = [ inputs.mix-nix.homeManagerModules.theme ];
# Import all modules
imports = [ inputs.mix-nix.homeManagerModules.default ];overlays - Package overlays
Provides a combined overlay with stable/unstable channels and custom packages.
Provides:
pkgs.stable.*- Packages from stable nixpkgspkgs.unstable.*- Packages from unstable nixpkgs- Custom packages from mix.nix
- Package overrides
{
nixpkgs.overlays = [ inputs.mix-nix.overlays.default ];
}
# Then use:
environment.systemPackages = [ pkgs.stable.firefox pkgs.unstable.neovim ];packages - Custom package definitions
Custom packages built by mix.nix.
| Package | Description |
|---|---|
eden |
Nintendo Switch video game console emulator |
eightbitdo-updater |
8BitDo controller firmware updater |
journey |
Cross-platform journal app |
monocraft-nerd-fonts |
Minecraft-style monospace font with Nerd Font icons |
olm-toggle |
GNOME Shell extension to toggle OLM tunneling service |
procon2-init |
Nintendo Switch 2 Pro Controller USB initializer |
proton-cachyos |
CachyOS Proton build with optimizations |
WiiUDownloader |
GUI to download Wii U content from Nintendo servers |
# List available packages
nix flake show github:tophc7/mix.nix
# Build a package
nix build github:tophc7/mix.nix#proton-cachyosdevshell - Development environment
Development environment for working on mix.nix itself.
Includes:
nil- Nix LSPnixfmt-rfc-style- Official Nix formatterstatix- Nix linterdeadnix- Dead code findergit
nix develop # Enter development shell
nix fmt # Format with nixfmt-rfc-styleInternal utilities for direct use. Access via the extended lib.
Directory scanning and module auto-discovery.
scanPaths path- Returns paths to all importable modules (directories + .nix, excluding default.nix)scanNames path- Returns just filenames (not full paths)scanAttrs path- Returns attrset of{ name = ./path; }for module indicesimportAndMerge path args- Import all files and merge their attrsetsimportAttrs path args- Import all and return attrset of evaluated resultsrelativeTo basePath- Curried path resolver for composability with map
For non-flake-parts users. Returns { nixosConfigurations = {...}; }.
lib.mkFlake {
inputs; # Required: flake inputs
users; # Required: { name = userSpec; }
hosts; # Required: { name = hostSpec; }
secrets ? {}; # Optional: { file, gitattributes, ... }
coreModules ? []; # Optional: NixOS modules for all hosts
coreHomeModules ? []; # Optional: HM modules for all users
hostsDir ? null; # Optional: auto-discover NixOS configs
hostsHomeDir ? null; # Optional: auto-discover host HM configs
usersHomeDir ? null; # Optional: auto-discover user HM configs
homeManager ? null; # Optional: home-manager input
}Types and builders for declarative configurations.
types.userSpec- User specification typetypes.hostSpec- Host specification typemkUserSpec extraModule- Factory to extend user typemkHostSpec extraModule- Factory to extend host typemkHost {...}- Build single nixosConfigurationmkHosts {...}- Build multiple nixosConfigurations
Note: The
shelloption in userSpec accepts either apackageor astring(e.g.,pkgs.fishor"fish").
Utilities for OCI container management with systemd integration.
serviceDefaults- Default systemd service config for containers (restart policies, timing)
For full container stack orchestration (networks, targets, dependencies), use the oci-stacks module instead.
{ lib, ... }:
{
# Apply service defaults to a container service
systemd.services."docker-myapp".serviceConfig = lib.infra.containers.serviceDefaults;
}mkWineApp pkgs {...}- Create Wine application wrapper with isolated prefixmatugen.mkBase16Template {...}- Generate base16 template for matugenmatugen.mkTemplateConfig {...}- Generate matugen template configmatugen.mkDerivation {...}- Build matugen derivationmonitors.findPrimary monitors- Find primary monitor from listmonitors.findByName name monitors- Find monitor by output name (e.g., "DP-1")monitors.filterEnabled monitors- Filter to only enabled monitorsmonitors.countPrimary monitors- Count monitors marked as primary (for validation)monitors.getDefaults monitors- Get primary monitor settings with fallbacks{ width, height, refreshRate, vrr, hdr }monitors.effectiveWidth monitor- Get width accounting for rotationmonitors.effectiveHeight monitor- Get height accounting for rotationmonitors.isPortrait monitor- Check if rotated 90/270 degreesmonitors.toResolutionStr monitor- Format as "WIDTHxHEIGHT@RATE"monitors.toPositionStr monitor- Format position as "Xx Y"monitors.totalWidth monitors- Calculate total width of enabled monitors (scaled)monitors.totalHeight monitors- Calculate max height of enabled monitors (scaled)
load {path, gitattributes, ...}- Import secrets with git-crypt validationmkModule secrets- Generate NixOS/HM module exposing secretsassertGitCrypt {gitattributesPath, pattern}- Validate git-crypt config
Complete flake.nix showing typical usage:
{
description = "My NixOS configuration";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nixpkgs-stable.url = "github:NixOS/nixpkgs/nixos-25.05";
flake-parts.url = "github:hercules-ci/flake-parts";
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
mix-nix = {
url = "github:tophc7/mix.nix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = inputs@{ flake-parts, mix-nix, ... }:
flake-parts.lib.mkFlake {
inherit inputs;
specialArgs = { lib = mix-nix.lib; };
} ({ pkgs, ... }: {
imports = [ mix-nix.flakeModules.default ];
systems = [ "x86_64-linux" "aarch64-linux" ];
mix = {
# Secrets (git-crypt encrypted)
secrets = {
file = ./secrets.nix;
gitattributes = ./.gitattributes;
};
# Core modules for all hosts
coreModules = [
./modules/core
./modules/nix-settings.nix
];
coreHomeModules = [
./home/core
mix-nix.homeManagerModules.theme
mix-nix.homeManagerModules.monitors
];
# Auto-discovery directories
hostsDir = ./hosts;
hostsHomeDir = ./home/hosts;
usersHomeDir = ./home/users; # HM enabled if ./home/users/<username>/ exists
# User definitions
users.toph = {
name = "toph";
uid = 1000;
shell = pkgs.fish;
extraGroups = [ "wheel" "docker" "audio" "video" ];
# HM auto-enabled via usersHomeDir (./home/users/toph/)
};
# Host definitions
hosts = {
desktop = {
user = "toph";
desktop = "niri";
};
laptop = {
user = "toph";
desktop = "gnome";
};
homelab = {
user = "toph";
isServer = true;
};
};
};
});
}Directory structure (directories - complex configs):
.
├── flake.nix
├── secrets.nix # git-crypt encrypted
├── .gitattributes # secrets.nix filter=git-crypt
├── modules/
│ ├── home/
│ │ ├── core/
│ │ │ └── default.nix # Core HM modules (applied to all)
│ │ └── common/
│ │ └── default.nix # Optional shared HM modules
│ └── host/
│ ├── core/
│ │ └── default.nix # Core NixOS modules (applied to all)
│ └── common/
│ └── default.nix # Optional shared NixOS modules
├── hosts/
│ ├── desktop/
│ │ └── default.nix # NixOS config for 'desktop' host
│ ├── laptop/
│ │ └── default.nix
│ └── homelab/
│ └── default.nix
└── home/
├── users/
│ └── toph/
│ └── default.nix # User-specific HM config (enables HM)
└── hosts/
├── desktop/
│ └── default.nix # Host-specific HM config
├── laptop/
│ └── default.nix
└── homelab/
└── default.nix
Directory structure (flat files - simpler):
.
├── flake.nix
├── secrets.nix # git-crypt encrypted
├── home/
│ ├── core.nix # Core HM modules
│ ├── hosts/
│ │ ├── desktop.nix # Host-specific HM config
│ │ └── server.nix
│ └── users/
│ └── toph.nix # User-specific HM config (enables HM)
├── hosts/
│ ├── desktop.nix # NixOS config for desktop
│ └── server.nix # NixOS config for server
└── modules/
├── core.nix # Core NixOS modules
└── secrets.nix # git-crypt encrypted
-
Directory Auto-Discovery Over Explicit Lists Drop files in directories - they're automatically imported. No manual
imports = [ ./a.nix ./b.nix ]. -
Declarative Specs Describe "What", Not "How" Specifications define properties and identity. Implementation details live in directories.
-
Convention Over Configuration Consistent directory structures reduce cognitive load. Follow established patterns.
-
Extensibility Via Factory Functions Base types stay minimal. Use
mkUserSpec/mkHostSpecto add custom options. -
Tools Over Configs While some modules (like fastfetch) include opinionated defaults, the focus is on providing reusable tools and specifications rather than full system configurations.
# Enter development shell
nix develop
# Available tools: nil, nixfmt-rfc-style, statix, deadnix, git# Check flake validity
nix flake check
# Format code
nix fmt
# Show outputs
nix flake show- Follow existing code patterns
- Add documentation in file headers
- Use
nixfmt-rfc-styleformatting - Test changes with
nix flake check