Introduction

nixos-unified is a flake-parts module to unify NixOS + nix-darwin + home-manager configuration in a single flake, while providing a consistent interface at DX and UX level.

Why?

nixos-unified provides the following features:

  • One-click activation & deployment
    • Activation: An .#activate flake app that works uniformly on NixOS, nix-darwin and home-manager.
      • .#activate can also remotely activate machines (be it macOS or NixOS) over SSH, thus acting as a simple alternative to deployment tools like deploy-rs and colmena.
    • Also: an .#update flake app to update the primary inputs (which can be overriden)
  • Seamless access to top-level flake
    • All NixOS/ nix-darwin/ home-manager modules receive specialArgs which includes all the information in the top-level flake.
      • This enables those modules to be aware of the flake inputs, for instance.
  • Sensible defaults
  • Autowiring of flake outputs
    • Autowiring: An optional module that will scan the directory structure and wire up the appropriate flake outputs automatically without you having to do it manually.

Getting Started

See: Getting Started.

Getting Started

Pick your desired operating system and follow the below instructions.

tip

Checkout nixos-unified-template for the quickest way to get started.

NixOS

  1. Install NixOS w/ Flakes enabled
  2. Convert your flake.nix to using nixos-unified using the NixOS only template as reference.

non-NixOS Linux

If you use other Linux distros like Ubuntu, you may use just home-manager.

  1. Install Nix
  2. Use the HOME only template

macOS

  1. Install Nix
  2. Use the macOS only template

Activation

nixos-unified provides an .#activate flake app that can be used in place of nixos-rebuild switch (if using NixOS),darwin-rebuild switch (if using nix-darwin) or home-manager switch (if using home-manager)

In addition, it can remotely activate the system over SSH (see further below).

Activating NixOS or nix-darwin configurations

In order to activate a system configuration for the current host ($HOSTNAME), run:

nix run .#activate

tip

Usually, you’d make this your default package, so as to be able to use nix run. In flake.nix:

# In perSystem
{
    packages.default = self'.packages.activate
}

Activating home configuration

If you are on a non-NixOS Linux (or on macOS but you do not use nix-darwin), you will have a home-manager configuration. Suppose, you have it stored in legacyPackages.homeConfigurations."myuser" (where myuser matches $USER), you can activate that by running:

nix run .#activate $USER@

note

The activate app will activate the home-manager configuration if the argument contains a @ (separating user and the optional hostname). The above command has no hostname, indicating that we are activating for the local host.

Per-host home configurations

You may also have separate home configurations for each machine, such as legacyPackages.homeConfigurations."myuser@myhost". These can be activated using:

nix run .#activate $USER@$HOSTNAME

Remote Activation

nixos-unified acts as a lightweight alternative to the various deployment tools such as deploy-rs and colmena. The .#activate app takes the hostname as an argument. If you set the nixos-unified.sshTarget option in your NixOS or nix-darwin configuration, it will run activation over the SSH connection.

Add the following to your configuration – nixosConfigurations.myhost or darwinConfigurations.myhost (depending on the platform):

{
    nixos-unified.sshTarget = "myuser@myhost";
}

Then, you will be able to run the following to deploy to myhost from any machine:

nix run .#activate myhost

Non-goals

Remote activation doesn’t seek to replace other deployment tools, and as such doesn’t provide features like rollbacks. It is meant for simple deployment use cases.

note

It is possible however that nixos-unified can grow to support more sophisticated deployment capabilities

Flake Templates

We provide four templates, depending on your needs:

Available templates

You can easily initialize one of our templates using Omnix1:

1

If you do not use Omnix, you must use nix flake init, and manually change the template values such as username and hostname.

NixOS only

NixOS configuration only, with home-manager

nix --accept-flake-config run github:juspay/omnix -- \
  init -o ~/nix-config github:srid/nixos-unified#linux

macOS only

nix-darwin configuration only, with home-manager

nix --accept-flake-config run github:juspay/omnix -- \
  init -o ~/nix-config github:srid/nixos-unified#macos

Home only

home-manager configuration only (useful if you use other Linux distros or do not have admin access to the machine)

nix --accept-flake-config run github:juspay/omnix -- \
  init -o ~/nix-config github:srid/nixos-unified#home

After initializing the template

Run nix run .#activate (nix run .#activate $USER if you are using the last template, “Home only”) to activate the configuration.

  • on macOS, if you get an error about /etc/nix/nix.conf, run:
    sudo mv /etc/nix/nix.conf /etc/nix/nix.conf.before-nix-darwin
    nix --extra-experimental-features "nix-command flakes" run .#activate
    
  • on macOS, if you had used Determinate Systems nix-installer, you may want to uninstall that Nix, such that we use the one provided by nix-darwin,
    sudo -i nix-env --uninstall nix
    
2

If you are on an Intel Mac, also change nixpkgs.hostPlatform accordingly.

Module Arguments

Each of your NixOS, nix-darwin and home-manager modules implicitly receive a specialArgs called flake.

The components of this flake attrset are:

NameDescription
inputsThe inputs of your flake; inputs.self referring to the flake itself
configThe flake-parts perSystem config

Here is an example of how these can be used:

{ flake, pkgs, lib, ... }:
let
  inherit (flake) config inputs;
  inherit (inputs) self;
in
{
  imports = [
    # Reference a flake input directly from a nix-darwin module
    inputs.agenix.darwinModules.default
  ];

  # Reference an arbitrary flake-parts config
  home-manager.users.${config.me.username} = { };
}

While the above example uses a nix-darwin module, you can do the same on NixOS or home-manager modules.

Flake Outputs

Importing the nixos-unified flake-parts module will autowire the following flake outputs in your flake:

NameDescription
nixos-unified.libFunctions mkLinuxSystem, mkMacosSystem and mkHomeConfiguration
packages.updateFlake app to update key flake inputs
packages.activateFlake app to build & activate the system (locally or remotely over SSH) or home configuration

In addition, all of your NixOS/nix-darwin/home-manager modules implicitly receive the following specialArgs:

  • flake@{self, inputs, config} (config is from flake-parts)
  • rosettaPkgs (if on darwin)

Autowiring

An optional autowiring module is provided that will scan the directory structure and wire up the appropriate flake outputs automatically without you having to do it manually.

A ready demonstration is available in nixos-unified-template as well as srid/nixos-config. In the latter, you will notice the following directory structure:

❮ lsd --tree --depth 1 configurations modules overlays packages
📁 configurations
├── 📁 darwin
├── 📁 home
└── 📁 nixos
📁 modules
├── 📁 darwin
├── 📁 flake-parts
├── 📁 home
└── 📁 nixos
📁 overlays
└── ❄️ default.nix
📁 packages
├── ❄️ git-squash.nix
├── ❄️ sshuttle-via.nix
└── 📁 twitter-convert

Each of these are wired to the corresponding flake output, as indicated in the below table:

DirectoryFlake Output
configurations/nixos/foo.nix1nixosConfigurations.foo
configurations/darwin/foo.nix1darwinConfigurations.foo
configurations/home/foo.nix1legacyPackages.${system}.homeConfigurations.foo2
modules/nixos/foo.nixnixosModules.foo
modules/darwin/foo.nixdarwinModules.foo
modules/flake-parts/foo.nixflakeModules.foo
overlays/foo.nixoverlays.foo

flake-parts

Autowiring is also provided if you use just flake-parts, via the lib.mkFlake function. In your top-level flake.nix, you only need to define your outputs as follows:

{
  inputs = ...;
  outputs = inputs:
    inputs.nixos-unified.lib.mkFlake
      { inherit inputs; root = ./.; };
}

This will,

  • Auto-import flake-parts modules under either ./nix/modules/flake-parts or ./modules/flake-parts (whichever exists)
  • Use a sensible default for systems which can be overriden.
  • Pass root as top-level module args, as a non-recursive way of referring to the path of the flake (without needing inputs.self).

See srid/haskell-template’s flake.nix for a ready example. For another example, see this emanote PR.

1

This path could as well be configurations/nixos/foo/default.nix. Likewise for other output types.

2

Why legacyPackages? Because, creating a home-manager configuration requires pkgs. See https://github.com/nix-community/home-manager/issues/3075

Examples

Release history

Unreleased

  • autoWiring of flake outputs

0.2.0 (2024-10-03)

Initial release, branched from nixos-flake