NixOS Config Base: A modularized base for NixOS configs

I recently created NixOS Config Base, a modularized base for more advanced NixOS configurations. It aims to help produce modularized NixOS configurations quickly and easily.

I have created a personal NixOS config based on this.

Please check it out! :slight_smile:

7 Likes

I want to split configuration of my desktop PC into multiple modules but I was not able to come up with a consistent system that makes sense. It is a hard problem.

I am not particularly convinced system.nix × packages.nix dichotomy is a good idea since the line between user space and system is bit blurry. For example, does services.xserver.desktopManager.gnome.enable = true; go to system? What about environment.systemPackages = [pkgs.gnomeExtensions.dash-to-dock];? And most software is distributed as a package, yet sometimes it needs to be installed using a module – like programs.wireshark.enable = true; which also creates the necessary user group.

The cleanest I have seen were concern-based categories (e.g. desktop-environment.nix, development-tools.nix…), rather than grouping by type (e.g. services.nix, programs.nix…).

It is a bit easier on servers, since there I can split the configuration hierarchically based on domain name. Although some services like nginx are still stuck in top-level configuration.nix.

Related discussions

Other templates

Organization discussions

13 Likes

How about a heirarchy of functionality (for example GNOME shell config would go in desktop-environment/GNOME/shell.nix), using a tree of includes, so there might be a include.nix in every directory, which includes all include.nix one level down plus all other nix files in the current directory?

1 Like

How about a heirarchy of functionality (for example GNOME shell config would go in desktop-environment/GNOME/shell.nix), using a tree of include s, so there might be a include.nix in every directory, which includes all include.nix one level down plus all other nix files in the current directory?

The list given by @jtojnar is very helpful for getting some inspiration. For directories, you typically have a top-level default.nix which imports all .nix files in the directory. You don’t need to type out default.nix when importing the directory (./dir implies ./dir/default.nix).

Furthermore, you can define custom options that activate other configs. For example, my modules/desktop/default.nix is always imported but it does nothing until I set zhaofeng.desktop.enable = true;.

2 Likes

not exactly what you are looking for, but I do something similar using haumea

Yes, I have something similar based on various roles (home network member → various dns, ntp and backup settings; general desktop → DE and other tools; development → vscode and friends that aren’t in per-project devshells; photography; work stuff; etc) and a set of various container definitions that might notionally be run on any of the hosts.

Another repository I’ve taken inspiration from calls them traits.

I was thinking of a (Java package format)-style way to categorise config files.

E.g. GNOME shell config goes in org/GNOME/shell/

I’ve used many classification systems or methods over the years.
After a while you always end up with a “misc” file/directory/box in which you stuff everything :smiley:
And then you start all over again…

2 Likes

BTW I forked Nix Starter Configs into the repo.

Puppet promotes a scheme that is called The roles and profile method. It’s basically an extra layer of abstraction that shields away the technical components, allowing you to focus on the use case of the machine.

  • With “profiles” you group together the software components into a technology stack.
  • With “roles” you compose your target machine class with those profiles.

Example

A developer workstation computer that may run GNOME by default.

# Role for profiles that need to make a developer machine work
class role::workstation::developer (
  $desktop = 'gnome',
) {
  # All roles should include the base profile
  include profile::base
  include profile::authentication::client
  include profile::workstation
  include "profile::desktop::${desktop}"
  include profile::development
}

The base profile configures, e.g., the underlying OS flavor.

# Base profile (includes component modules for all nodes)
class profile::base {
  case $::osfamily {

    'Debian': {
      include unattended_upgrades

      package {[
          'apt-transport-https',
          'aptitude',
          'software-properties-common',
          'ssh',
          'uuid-runtime',
          'vim',
        ]:
        ensure => present,
      }

      service { 'systemd-timesyncd.service':
        ensure   => running,
        enable   => true,
        flags    => '--now',
        provider => systemd,
      }
    }

    'RedHat': {
      package { 'puppetlabs-release-pc1-el-7':
        # enable official Puppet Labs collection repository (old agent otherwise)
        ensure   => present,
        provider => rpm,
        source   => 'https://yum.puppetlabs.com/puppetlabs-release-pc1-el-7.noarch.rpm',
      }

      package {[
          'openssh-clients',
          'openssh-server',
          'puppet-agent',
          'vim-enhanced',
        ]:
        ensure => present,
      }
    }

    default: {
      notify { "Operating system ${::operatingsystem} not supported": }
    }
  }

  package {[
      'curl',
      'htop',
      'nano',
      'ncdu',
      'sudo',
      'tmux',
      'tree',
    ]:
    ensure => present,
  }
}

The workstation profile configures common settings and software of any PC of this type.

# Workstation base profile (includes component modules for all client PCs)
class profile::workstation {
  include software::browsers::chrome
  include software::browsers::firefox
  include software::communication::matrix

  include mycompany::backup::restore
  include mycompany::branding
  include mycompany::printers
  include mycompany::userdefaults

  class { 'keyboard':
    layout => 'en',
  }

  class { 'locales':
    default_locale => 'en_US.UTF-8',
    locales        => [
      'en_US.UTF-8 UTF-8',
      'de_DE.UTF-8 UTF-8',
      'it_IT.UTF-8 UTF-8',
    ],
  }

  # common packages needed everywhere
  package {[
      'gimp',
      'python-gpgme',
      'virtualbox',
    ]:
    ensure => present,
  }
}

The desktop role will apparently configure what’s in the (default setup) of GNOME, KDE, etc.

And so forth.

Summary

In essence, this scheme allows to keep a more high-level picture of what use case you’re dealing with when configuring your target system. When the roles are cleanly separated, the composition in a role is a very helpful tool to shape your system.

That’s how it works in Puppet when you do it right. I feel that the typical NixOS configs are too deep into the implementation details. Maybe such a scheme would help to stay more focused. Has anyone ever tried something like this?

I think many long lived NixOS configurations for multi-machine setups converge towards something like that?

I structured my NixOS config based on the puppet “profiles” and “roles” scheme some years ago. (A machine pulls in a single role, and a role contains one or more profiles.) When you’re at that high level abstraction, the config gets quite opinionated: what does the “workstation” role contain? So there’s not much sharing possible at the GitHub - NixOS/nixpkgs: Nix Packages collection & NixOS level.

1 Like

IMHO, the trick (and challenge) is to separate the concerns correctly. I must be easy to compose a system. Sometimes options help – note the $desktop variable in the developer workstation above.

Simply what “workstation” class computers (desktop PCs, laptops) have in common: The office network configuration, for example.

I think, if made well, it should be possible to customize every aspect by composition. In the extreme case, you stop using a role and use profiles directly to compose your desired custom host configuration.

I’m only wondering whether Nix users would feel at home in such a setup?