Skip to content

Commit

Permalink
Use host and user directories for mapping outputs
Browse files Browse the repository at this point in the history
  • Loading branch information
ehllie committed Nov 12, 2023
1 parent 5e0daca commit 220242e
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 108 deletions.
55 changes: 18 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,48 +1,29 @@
# flake-parts Module for multi-system and multi-user configuration
# flake-parts module for multi-system and multi-user configuration

ez-configs flake-parts module provides a flexible framework for managing configurations across multiple systems using nixosSystem, darwinSystem (nix-darwin), and homeManagerConfiguration (home-manager).
ez-configs flake-parts module provides a flexible framework for managing configurations across multiple systems using nixosSystem, darwinSystem (nix-darwin), and homeManagerConfiguration (home-manager), as well as exporting individual modules.

## Overview

The module allows users to define system and user-specific configurations that are automatically when present. It is designed to work with a directory structure where configurations are organized by each configuration's hostname and username.
This module allows for defining configuration and module outputs in your flake, for use in nixos, nix-darwin and home-manager, using your directory structure.
This results in 6 directories the module can use:

It's pretty opinionated, but aims to offer a degree of customizability.
- [nixosModules](https://flake.parts/options/ez-configs#opt-ezConfigs.nixos.modulesDirectory)
- [nixosHosts](https://flake.parts/options/ez-configs#opt-ezConfigs.nixos.hostsDirectory)
- [darwinModules](https://flake.parts/options/ez-configs#opt-ezConfigs.darwin.modulesDirectory)
- [darwinHosts](https://flake.parts/options/ez-configs#opt-ezConfigs.darwin.hostsDirectory)
- [homeModules](https://flake.parts/options/ez-configs#opt-ezConfigs.hm.modulesDirectory)
- [users](https://flake.parts/options/ez-configs#opt-ezConfigs.hm.usersDirectory)

## Features

### nixosConfigurations and darwinConfigurations

Those two are created in quite analogous ways. Assuming most options are kept default and `ezConfigs.root` is set to `./.`, host defined in `ezConfigs.nixos.hosts` / `ezConfigs.darwin.hosts` get created using the following modules:

- `./nixos(.nix)` or `./dariwn(.nix)` respectively, if present in your root
- `./hosts/${hostname}(.nix)` if present in your root

This lets you define both platform specific and host specific configuration by simply adding nix files or directories to appropriate locations.
You're also able to define `specialArgs` that will be passed to the appropriate system builders. A useful example would be passing in `{ inherit inputs; }`.
That way you can access your flake inputs from inside your nixos/darwin modules. They can either be set individually for each builder using `ezConfigs.darwin.specialArgs`/ `ezConfigs.nixos.specialArgs` or globally for all configuration builders with `ezConfigs.globalArgs`.

### homeManagerConfigurations

It gets created using each user and system pair, with a name `${username}@${hostname}`. Users are defined in `ezConfigs.hs.users`, while hosts are all the nixos and darwin configuration.
With the same assumptions as in the above section, the home manager configuration gets created using the package set of generated by the host system and using the following modules:

- `./home(.nix)` if present in your root
- `./home/darwin(.nix)` or `./home/linux(.nix)` respectively, if present in your root
- `./users/${username}(.nix)` if present in your root
- `./users/${username}/darwin(.nix)` or `./users/${username}/linux(.nix)` respectively, if present in your root

This lets you define user and platform specific configurations in the same manner as with nixos and darwin configurations. You can also pass in `extraSpecialArgs` which are the equivalent of `specialArgs` of system builders.

## Goals

Some of these are features that I'd like to look into, with no guarantee they get implemented since I'm not sure about how ergonomic to use they would be.

- Automatically discover hosts and users when a file is created the appropriate directory
- Allow for loading home manager as a nixos module when specified in user config, rather than creating a `homeManagerConfigurations` output
- More configurability, like architecture dependent imports, changing the default `${username}@${hostname}` home manager configuration names, etc.
- Extracting individual modules to flake outputs, so that then can be consumed by other flakes.
Each `.nix` file, or a directory containing `default.nix` file gets included in the respective outputs.
When building configurations, the default module (ie. `<modulesDirectory>/default.nix` or `<modulesDirectory>/default/default.nix`) is imported, unless `importDefault` is set to `false` for that user or host configuration.
In case of home manager configurations, it also includes the `darwin` or `linux` modules depending on the system that configuration is built from.

## Usage

See the [example directory](https://github.com/ehllie/ez-configs/blob/main/example/flake.nix) for a documented example.
I also use this module in my [own dotfiles](https://github.com/ehllie/dotfiles/blob/main/flake.nix).

## TODO

- Allow for loading home manager as a nixos module when specified in user config, rather than creating a `homeManagerConfigurations` output
- Warn when configuring users or hosts that are not present in the directory tree.
File renamed without changes.
3 changes: 0 additions & 3 deletions example/flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@
ezConfigs = {
root = ./.;
globalArgs = { inherit inputs; };
hm.users.example-user = { };
nixos.hosts.example-nixos.arch = "x86_64";
darwin.hosts.example-darwin.arch = "aarch64";
};
};
}
File renamed without changes.
158 changes: 96 additions & 62 deletions flake-module.nix
Original file line number Diff line number Diff line change
@@ -1,33 +1,40 @@
{ darwin, home-manager, nixpkgs, ... }: { lib, config, ... }:
let

inherit (builtins) pathExists readDir readFileType;
inherit (builtins) pathExists readDir readFileType elemAt;
inherit (nixpkgs.lib) mkOption types nixosSystem optionals literalExpression mapAttrs concatMapAttrs;
inherit (nixpkgs.lib.strings) hasSuffix removeSuffix;
inherit (darwin.lib) darwinSystem;
inherit (home-manager.lib) homeManagerConfiguration;
cfg = config.ezConfigs;

# Creates an attrset of nixosConfigurations or darwinConfigurations.
systemsWith = { systemBuilder, systemSuffix, ezModules, hostModules, specialArgs }: hosts:
systemsWith = { systemBuilder, systemSuffix, ezModules, hostModules, specialArgs, defaultHost }: hosts:
mapAttrs
(name: { arch, importDefault }: systemBuilder {
specialArgs = specialArgs // { inherit ezModules; };
system = "${arch}-${systemSuffix}";
modules = [ (hostModules.${name} or { }) ]
++ optionals importDefault [ (ezModules.default or { }) ];
})
hosts;
(name: configModule:
let
hostSettings = hosts.${name} or defaultHost;
inherit (hostSettings) arch importDefault;
in
systemBuilder {
specialArgs = specialArgs // { inherit ezModules; };
system = "${arch}-${systemSuffix}";
modules = [ configModule ]
++ optionals importDefault [ (ezModules.default or { }) ];
})
hostModules;

allHosts =
(config.flake.nixosConfigurations //
config.flake.darwinConfigurations);

# Creates an attrset of home manager confgurations for each user on each host.
userConfigs = { ezModules, userModules, extraSpecialArgs }: users:
userConfigs = { ezModules, userModules, extraSpecialArgs, defaultUser }: users:
concatMapAttrs
(user: { importDefault, nameFunction }:
(user: configModule:
let
userSettings = users.${user} or defaultUser;
inherit (userSettings) nameFunction importDefault;
mkName =
if nameFunction == null
then host: "${user}@${host}"
Expand All @@ -50,7 +57,7 @@ let
};
})
allHosts)
users;
userModules;

readModules = dir:
if pathExists "${dir}.nix" && readFileType "${dir}.nix" == "regular" then
Expand All @@ -71,18 +78,38 @@ let
else { }
;

hostOptions.options = {
arch = mkOption {
type = types.enum [ "x86_64" "aarch64" ];
description = "The architecture of the system.";
};
# This is a workaround the types.attrsOf (type.submodule ...) functionality.
# We can't ensure that each host/ user present in the appropriate directory
# is also present in the attrset, so we need to create a default module for it.
# That way we can fallback to it if it's not present in the attrset.
# Is there a better way to do this? Maybe defining a custom type?
defaultSubmodule = submodule:
concatMapAttrs
(opt: optDef:
if optDef ? default then
{ ${opt} = optDef.default; }
else { })
submodule.options;

importDefault = mkOption {
default = true;
type = types.bool;
description = ''
Whether to import the default module for this host.
'';
# Getting the first submodule seems to work, but not sure if it's the best way.
defaultSubmoduleAttr = attrsType:
defaultSubmodule (elemAt attrsType.getSubModules 0);

hostOptions = defaultArch: {
options = {
arch = mkOption {
type = types.enum [ "x86_64" "aarch64" ];
default = defaultArch;
description = "The architecture of the system.";
};

importDefault = mkOption {
default = true;
type = types.bool;
description = ''
Whether to import the default module for this host.
'';
};
};
};

Expand All @@ -106,48 +133,52 @@ let
};
};

hostsOptions = system: {
modulesDirectory = mkOption {
default = "${cfg.root}/${system}";
defaultText = literalExpression "\"\${ezConfigs.root}/${system}\"";
type = types.pathInStore;
description = ''
The directory in which to look for ${system} modules.
'';
};
hostsOptions = system:
let
defaultArch = if system == "darwin" then "aarch64" else "x86_64";
in
{
modulesDirectory = mkOption {
default = "${cfg.root}/${system}";
defaultText = literalExpression "\"\${ezConfigs.root}/${system}\"";
type = types.pathInStore;
description = ''
The directory in which to look for ${system} modules.
'';
};

hostsDirectory = mkOption {
default = "${cfg.root}/hosts";
defaultText = literalExpression "\"\${ezConfigs.root}/hosts\"";
type = types.pathInStore;
description = ''
The directory in which to look for host ${system} configurations.
'';
};
hostsDirectory = mkOption {
default = "${cfg.root}/${system}-hosts";
defaultText = literalExpression "\"\${ezConfigs.root}/${system}-hosts\"";
type = types.pathInStore;
description = ''
The directory in which to look for host ${system} configurations.
'';
};

specialArgs = mkOption {
default = cfg.globalArgs;
defaultText = literalExpression "ezConfigs.globalArgs";
type = types.attrsOf types.anything;
description = ''
Extra arguments to pass to all ${system} configurations.
'';
};
specialArgs = mkOption {
default = cfg.globalArgs;
defaultText = literalExpression "ezConfigs.globalArgs";
type = types.attrsOf types.anything;
description = ''
Extra arguments to pass to all ${system} configurations.
'';
};

hosts = mkOption {
default = { };
type = types.attrsOf (types.submodule hostOptions);
example = literalExpression ''
{
hostA.arch = "x86_64";
hostB.arch = "aarch64";
}
'';
description = ''
An attribute set of ${system} host definitions to create configurations for.
'';
hosts = mkOption {
default = { };
type = types.attrsOf (types.submodule (hostOptions defaultArch));
example = literalExpression ''
{
hostA.arch = "x86_64";
hostB.arch = "aarch64";
}
'';
description = ''
An attribute set of ${system} host definitions to create configurations for.
'';
};
};
};

in
{
Expand Down Expand Up @@ -198,7 +229,7 @@ in
};

users = mkOption {
default = [ ];
default = { };
type = types.attrsOf (types.submodule userOptions);

example = literalExpression ''
Expand Down Expand Up @@ -227,6 +258,7 @@ in
homeConfigurations = userConfigs
{
userModules = readModules cfg.hm.usersDirectory;
defaultUser = defaultSubmodule userOptions;
ezModules = homeModules;
inherit (cfg.hm) extraSpecialArgs;
}
Expand All @@ -237,6 +269,7 @@ in
systemBuilder = nixosSystem;
systemSuffix = "linux";
hostModules = readModules cfg.nixos.hostsDirectory;
defaultHost = defaultSubmoduleAttr ((hostsOptions "nixos").hosts.type);
ezModules = nixosModules;
inherit (cfg.nixos)
specialArgs;
Expand All @@ -248,6 +281,7 @@ in
systemBuilder = darwinSystem;
systemSuffix = "darwin";
hostModules = readModules cfg.darwin.hostsDirectory;
defaultHost = defaultSubmoduleAttr ((hostsOptions "darwin").hosts.type);
ezModules = darwinModules;
inherit (cfg.darwin) specialArgs;
}
Expand Down
7 changes: 1 addition & 6 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,6 @@
inherit flakeModule;
};

ezConfigs = {
root = ./example;
hm.users.example-user = { };
nixos.hosts.example-nixos.arch = "x86_64";
darwin.hosts.example-darwin.arch = "aarch64";
};
ezConfigs.root = ./example;
};
}

0 comments on commit 220242e

Please sign in to comment.