Nixy
A minimal NixOS/Darwin/Home Manager framework.
What is Nixy?
Nixy helps you organize NixOS configurations around nodes (machines) and modules (reusable features). Instead of managing complex module imports, you declare what each machine needs:
nodes.server = {
system = "x86_64-linux";
base.enable = true;
ssh.enable = true;
};
Key Features
- Node-centric: One node = one machine. All config in one place.
- Conditional modules: Only enabled modules are imported.
- Type-safe: Options on disabled modules throw errors.
- Multi-platform: NixOS, Darwin, and Home Manager support.
- Dependency tracking: Modules can declare requirements.
Quick Start
nix flake init -t github:anialic/nixy#minimal
Then edit nodes/ and modules/ to match your setup.
Getting Started
Installation
Initialize a new project from a template:
nix flake init -t github:anialic/nixy#minimal
Available templates:
minimal- Single NixOS machinemulti-platform- NixOS + Darwin + Home Managerdeploy-rs- Remote deploymentwithout-flakes- Traditional setup
Project Structure
my-config/
├── flake.nix
├── modules/
│ └── base.nix
└── nodes/
└── my-machine.nix
Basic Configuration
flake.nix
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
inputs.nixy.url = "github:anialic/nixy";
outputs = { nixpkgs, nixy, ... }@inputs: nixy.mkFlake {
inherit nixpkgs;
imports = [ ./. ];
args = { inherit inputs; };
};
}
modules/base.nix
{ mkStr, ... }:
{
modules.base = {
target = "nixos";
options.hostName = mkStr null;
module = { node, ... }: {
networking.hostName = node.base.hostName;
system.stateVersion = "24.11";
};
};
}
nodes/my-machine.nix
{
nodes.my-machine = {
system = "x86_64-linux";
base.enable = true;
base.hostName = "my-machine";
};
}
Building
nixos-rebuild switch --flake .#my-machine
Configuration
mkFlake / mkConfiguration
nixy.mkFlake {
nixpkgs; # required - nixpkgs input
imports ? [ ]; # directories or files to scan
args ? { }; # passed to all modules
exclude ? null; # filter function
}
imports
Accepts paths, directories, or inline modules:
imports = [
./. # scan current directory
./modules # scan specific directory
./special.nix # single file
{ ... } # inline module
];
args
Passed to framework modules and NixOS/Darwin/HM modules:
args = { inherit inputs; myCustomArg = "value"; };
exclude
Filter scanned files. Default excludes _*, .*, flake.nix, default.nix:
exclude = { name, path }:
name == "test.nix" || lib.hasPrefix "_" name;
Top-level Options
| Option | Description |
|---|---|
systems | Target systems (default: x86_64/aarch64 linux/darwin) |
modules.* | Module definitions |
nodes.* | Node definitions |
targets.* | Target builders |
rules | Build-time assertions |
perSystem | Per-system outputs |
flake.* | Extra flake outputs |
Modules
Definition
{ mkStr, mkBool, ... }:
{
modules.myModule = {
target = "nixos"; # "nixos", "darwin", "home", or null (all)
requires = [ "base" ]; # dependencies
options = {
setting = mkStr "default";
};
module = { node, ... }: {
# NixOS/Darwin/HM config
};
};
}
Fields
target
Restricts module to specific platforms:
"nixos"- NixOS only"darwin"- nix-darwin only"home"- Home Manager onlynull- All platforms
requires
List of module names that must be enabled:
modules.desktop = {
requires = [ "base" "gui" ];
# ...
};
Build fails if requirements aren’t met.
options
Option declarations using helpers or standard lib.mkOption:
options = {
name = mkStr null;
enabled = mkBool true;
ports = mkList lib.types.port [ 80 443 ];
};
module
NixOS/Darwin/HM module. Receives special arguments:
| Argument | Description |
|---|---|
node | Current node’s clean config |
nodes | All nodes’ clean configs |
name | Node name |
system | System string |
Splitting Modules
Same-name modules across files are merged:
# modules/base/base.nix
modules.base.options.hostName = mkStr null;
modules.base.module = { node, ... }: {
networking.hostName = node.base.hostName;
};
# modules/base/boot.nix
modules.base.module = {
boot.loader.systemd-boot.enable = true;
};
Nodes
Definition
{
nodes.myMachine = {
system = "x86_64-linux";
base.enable = true;
base.hostName = "my-machine";
};
}
Fields
system (required)
Target system: "x86_64-linux", "aarch64-linux", "aarch64-darwin", "x86_64-darwin"
target
Override inferred target. Usually automatic:
*-darwin→"darwin"*-linux→"nixos"
Set explicitly for Home Manager:
nodes."user@host" = {
system = "x86_64-linux";
target = "home";
# ...
};
extraModules
Additional NixOS/Darwin/HM modules:
nodes.server = {
# ...
extraModules = [
{ services.openssh.enable = true; }
./hardware-configuration.nix
];
};
instantiate
Custom builder function:
nodes.stable-server = {
system = "x86_64-linux";
instantiate = { system, modules, specialArgs }:
inputs.nixpkgs-stable.lib.nixosSystem {
inherit system modules specialArgs;
};
# ...
};
Module Options
Enable modules and set options:
nodes.desktop = {
system = "x86_64-linux";
base.enable = true;
base.hostName = "desktop";
base.timeZone = "America/New_York";
gui.enable = true;
gui.driver = "nvidia";
};
Setting options without enable = true throws an error.
Option Helpers
Helpers simplify option declarations. All helpers are available as framework module arguments.
Basic Types
mkStr
String option. Pass null for optional (defaults to null), or a value for required with default.
{ mkStr, ... }:
{
modules.web.options = {
domain = mkStr null; # optional, default null
protocol = mkStr "https"; # required, default "https"
};
}
mkBool
Boolean option.
{ mkBool, ... }:
{
modules.service.options = {
enabled = mkBool true;
debug = mkBool false;
};
}
mkInt
Integer option.
{ mkInt, ... }:
{
modules.cache.options = {
maxSize = mkInt 1024;
ttl = mkInt null; # optional
};
}
mkPort
Port number (1-65535).
{ mkPort, ... }:
{
modules.server.options = {
httpPort = mkPort 80;
httpsPort = mkPort 443;
};
}
mkPath
Path option.
{ mkPath, ... }:
{
modules.backup.options = {
destination = mkPath /var/backup;
source = mkPath null;
};
}
mkLines
Multi-line string, lines are concatenated.
{ mkLines, ... }:
{
modules.nginx.options = {
extraConfig = mkLines "";
};
}
# Usage in node:
nodes.web.nginx.extraConfig = ''
gzip on;
gzip_types text/plain application/json;
'';
mkAttrs
Attribute set (untyped).
{ mkAttrs, ... }:
{
modules.app.options = {
environment = mkAttrs { };
};
}
# Usage:
nodes.server.app.environment = {
NODE_ENV = "production";
PORT = "3000";
};
Collections
mkList
List with element type and default value.
{ mkList, lib, ... }:
{
modules.firewall.options = {
allowedPorts = mkList lib.types.port [ 22 80 443 ];
blockedIPs = mkList lib.types.str [ ];
};
}
mkListOf
List with element type, empty default.
{ mkListOf, lib, ... }:
{
modules.users.options = {
admins = mkListOf lib.types.str; # default: [ ]
keys = mkListOf lib.types.path;
};
}
mkAttrsOf
Attribute set with typed values, empty default.
{ mkAttrsOf, lib, ... }:
{
modules.dns.options = {
records = mkAttrsOf lib.types.str; # default: { }
};
}
# Usage:
nodes.ns.dns.records = {
"example.com" = "192.168.1.1";
"api.example.com" = "192.168.1.2";
};
mkStrList
Shorthand for mkListOf lib.types.str.
{ mkStrList, ... }:
{
modules.git.options = {
allowedUsers = mkStrList; # default: [ ]
};
}
Choice Types
mkEnum
One of predefined values.
{ mkEnum, ... }:
{
modules.log.options = {
level = mkEnum [ "debug" "info" "warn" "error" ] "info";
};
}
mkEither
One of two types.
{ mkEither, lib, ... }:
{
modules.proxy.options = {
upstream = mkEither lib.types.str lib.types.port "localhost";
};
}
# Usage:
nodes.proxy.proxy.upstream = 8080; # port
nodes.proxy.proxy.upstream = "backend"; # string
mkOneOf
One of multiple types.
{ mkOneOf, lib, ... }:
{
modules.config.options = {
value = mkOneOf [ lib.types.str lib.types.int lib.types.bool ] "";
};
}
Advanced Types
mkPackage
Package option, no default (must be set).
{ mkPackage, ... }:
{
modules.editor.options = {
package = mkPackage;
};
}
# Usage:
nodes.dev.editor.package = pkgs.neovim;
mkPackageOr
Package option with default.
{ mkPackageOr, pkgsFor, ... }:
let pkgs = pkgsFor "x86_64-linux";
in {
modules.shell.options = {
package = mkPackageOr pkgs.bash;
};
}
mkRaw
Raw type (any value), no default.
{ mkRaw, ... }:
{
modules.custom.options = {
builder = mkRaw;
};
}
mkRawOr
Raw type with default.
{ mkRawOr, ... }:
{
modules.hook.options = {
preStart = mkRawOr null;
};
}
mkNullable
Any type, nullable with null default.
{ mkNullable, lib, ... }:
{
modules.db.options = {
port = mkNullable lib.types.port; # null or port
host = mkNullable lib.types.str;
};
}
Submodules
mkSub
Nested options as submodule.
{ mkSub, mkStr, mkPort, ... }:
{
modules.database.options = {
connection = mkSub {
host = mkStr "localhost";
port = mkPort 5432;
name = mkStr null;
};
};
}
# Usage:
nodes.app.database.connection = {
host = "db.example.com";
port = 5432;
name = "myapp";
};
mkSubList
List of submodules.
{ mkSubList, mkStr, mkPort, mkInt, ... }:
{
modules.lb.options = {
backends = mkSubList {
host = mkStr null;
port = mkPort 80;
weight = mkInt 1;
};
};
}
# Usage:
nodes.lb.lb.backends = [
{ host = "10.0.0.1"; port = 8080; }
{ host = "10.0.0.2"; port = 8080; weight = 2; }
];
Enable Option
mkEnable
Shorthand for lib.mkEnableOption. Used inside modules for sub-features.
{ mkEnable, ... }:
{
modules.monitoring.options = {
metrics = mkEnable "Prometheus metrics";
logging = mkEnable "structured logging";
};
}
Note: Don’t use mkEnable for the module’s main enable option - nixy adds that automatically.
Multi-platform Setup
Configure NixOS, Darwin, and Home Manager in one flake.
flake.nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nix-darwin.url = "github:LnL7/nix-darwin";
home-manager.url = "github:nix-community/home-manager";
nixy.url = "github:anialic/nixy";
};
outputs = { nixpkgs, nixy, ... }@inputs: nixy.mkFlake {
inherit nixpkgs;
imports = [ ./. ];
args = { inherit inputs; };
};
}
targets.nix
{ inputs, nixpkgs, ... }:
{
targets.darwin = {
instantiate = { system, modules, specialArgs }:
inputs.nix-darwin.lib.darwinSystem { inherit system modules specialArgs; };
output = "darwinConfigurations";
};
targets.home = {
instantiate = { system, modules, specialArgs }:
inputs.home-manager.lib.homeManagerConfiguration {
pkgs = nixpkgs.legacyPackages.${system};
modules = modules;
extraSpecialArgs = specialArgs;
};
output = "homeConfigurations";
};
}
Platform-specific Modules
# modules/base.nix - NixOS
{ mkStr, ... }:
{
modules.base = {
target = "nixos";
options.hostName = mkStr null;
module = { node, ... }: {
networking.hostName = node.base.hostName;
};
};
}
# modules/darwin.nix
{ mkStr, ... }:
{
modules.darwin = {
target = "darwin";
options.hostName = mkStr null;
module = { node, ... }: {
networking.hostName = node.darwin.hostName;
system.stateVersion = 5;
};
};
}
# modules/home.nix
{ mkStr, ... }:
{
modules.home = {
target = "home";
options.username = mkStr null;
module = { node, ... }: {
home.username = node.home.username;
home.stateVersion = "24.11";
programs.home-manager.enable = true;
};
};
}
Nodes
# nodes/nodes.nix
{ inputs, ... }:
{
nodes.workstation = {
system = "x86_64-linux";
base.enable = true;
base.hostName = "workstation";
};
nodes.macbook = {
system = "aarch64-darwin";
darwin.enable = true;
darwin.hostName = "macbook";
};
nodes."alice-home" = {
system = "x86_64-linux";
target = "home";
home.enable = true;
home.username = "alice";
};
}
Deploy-rs Integration
Remote deployment with deploy-rs.
flake.nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
deploy-rs.url = "github:serokell/deploy-rs";
nixy.url = "github:anialic/nixy";
};
outputs = { nixpkgs, nixy, ... }@inputs: nixy.mkFlake {
inherit nixpkgs;
imports = [ ./. ];
args = { inherit inputs; };
};
}
modules/deploy.nix
{ mkStr, ... }:
{
modules.deploy = {
options = {
hostname = mkStr null;
sshUser = mkStr "root";
};
};
}
custom.nix
{ lib, config, inputs, ... }:
let
deployNodes = lib.filterAttrs (_: n: n.deploy.enable or false) config.nodes;
in {
flake.deploy.nodes = lib.mapAttrs (name: node:
let cfg = config.flake.nixosConfigurations.${name};
in {
hostname = node.deploy.hostname;
sshUser = node.deploy.sshUser;
profiles.system = {
user = "root";
path = inputs.deploy-rs.lib.${node._system}.activate.nixos cfg;
};
}
) deployNodes;
}
nodes/server.nix
{
nodes.server = {
system = "x86_64-linux";
base.enable = true;
base.hostName = "server";
deploy.enable = true;
deploy.hostname = "192.168.1.100";
deploy.sshUser = "deploy";
};
}
Deployment
nix run github:serokell/deploy-rs -- .#server