From f85f831b3366e65b5d04388fff4639a7770e611d Mon Sep 17 00:00:00 2001 From: Jake Hamilton Date: Sat, 27 May 2023 21:29:41 -0700 Subject: [PATCH] feat: home-manager support --- README.md | 216 ++++++++++++++++++++++++++++++-- flake.nix | 10 +- lib/home/default.nix | 25 +++- lib/system/default.nix | 8 +- modules/darwin/home/default.nix | 31 ----- modules/darwin/user/default.nix | 71 +++++++++++ modules/home/user/default.nix | 43 +++++-- modules/nixos/user/default.nix | 84 +++++++++++++ 8 files changed, 432 insertions(+), 56 deletions(-) delete mode 100644 modules/darwin/home/default.nix create mode 100644 modules/darwin/user/default.nix create mode 100644 modules/nixos/user/default.nix diff --git a/README.md b/README.md index 6ea7ca0..7769dcd 100644 --- a/README.md +++ b/README.md @@ -37,12 +37,13 @@ cd config 2. Create a new flake with one of the templates from [@snowfallorg/templates](https://github.com/snowfallorg/templates). -| Name | Description | -| --------- | ------------------------------------------------- | -| `system` | A NixOS system and modules ready to modify. | -| `package` | A Nix Flake that exports packages and an overlay. | -| `module` | A Nix Flake that exports NixOS modules. | -| `lib` | A Nix Flake that exports a custom `lib` | +| Name | Description | +| --------- | ---------------------------------------------------- | +| `system` | A NixOS system and modules ready to modify. | +| `package` | A Nix Flake that exports packages and an overlay. | +| `module` | A Nix Flake that exports NixOS modules. | +| `lib` | A Nix Flake that exports a custom `lib` | +| `empty` | A basic Nix Flake for you to customize from scratch. | ```bash # For example, to use the system template. @@ -195,12 +196,20 @@ snowfall-root/ │ ├─ modules/ (optional modules) │ │ -│ │ Any (nestable) directory name. The name of the directory will be the -│ │ name of the module. -│ └─ **/ +│ │ A directory named after the `platform` type that will be used for modules within. +│ │ +│ │ Supported platforms are: +│ │ - nixos +│ │ - darwin +│ │ - home +│ └─ / │ │ -│ │ A NixOS module. -│ └─ default.nix +│ │ Any (nestable) directory name. The name of the directory will be the +│ │ name of the module. +│ └─ **/ +│ │ +│ │ A NixOS module. +│ └─ default.nix │ ├─ overlays/ (optional overlays) │ │ @@ -246,6 +255,37 @@ snowfall-root/ │ │ │ │ A NixOS module for your system's configuration. │ └─ default.nix +│ +├─ homes/ (optional homes configurations) +│ │ +│ │ A directory named after the `home` type that will be used for all homes within. +│ │ +│ │ The architecture is any supported architecture of NixPkgs, for example: +│ │ - x86_64 +│ │ - aarch64 +│ │ - i686 +│ │ +│ │ The format is any supported NixPkgs format *or* a format provided by either nix-darwin +│ │ or nixos-generators. However, in order to build systems with nix-darwin or nixos-generators, +│ │ you must add `darwin` and `nixos-generators` inputs to your flake respectively. Here +│ │ are some example formats: +│ │ - linux +│ │ - darwin +│ │ - iso +│ │ - install-iso +│ │ - do +│ │ - vmware +│ │ +│ │ With the architecture and format together (joined by a hyphen), you get the name of the +│ │ directory for the home type. +│ └─ -/ +│ │ +│ │ A directory that contains a single home's configuration. The directory name +│ │ will be the name of the home. +│ └─ / +│ │ +│ │ A NixOS module for your home's configuration. +│ └─ default.nix ``` #### Default Flake @@ -526,6 +566,48 @@ type. See the following table for a list of supported formats from NixOS Generat | vm-nogui | Same as vm, but without a GUI | | vmware | VMWare image (VMDK) | +#### Home Manager + +Snowfall Lib supports configuring [Home Manager](https://github.com/nix-community/home-manager) +for both standalone use and for use as a module with NixOS or nix-darwin. To use this feature, +your flake must include `home-manager` as an input. + +```nix +{ + description = "My Flake"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-22.11"; + + snowfall-lib = { + url = "github:snowfallorg/lib"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + # In order to use Home Manager. + home-manager = { + url = "github:nix-community/home-manager"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = inputs: + # This is an example and in your actual flake you can use `snowfall-lib.mkFlake` + # directly unless you explicitly need a feature of `lib`. + let + lib = inputs.snowfall-lib.mkLib { + # You must pass in both your flake's inputs and the root directory of + # your flake. + inherit inputs; + src = ./.; + }; + in + # No additional configuration is required to use this feature, you only + # have to add home-manager to your flake inputs. + lib.mkFlake { }; +} +``` + ### `lib.snowfall.flake` Helpers related to Nix flakes. @@ -1162,6 +1244,24 @@ Result: "iso" ``` +#### `lib.snowfall.system.get-inferred-system-name` + +Get the name of a system based on its file path. + +Type: `Path -> String` + +Usage: + +```nix +get-inferred-system-name "/systems/my-system/default.nix" +``` + +Result: + +```nix +"my-system" +``` + #### `lib.snowfall.system.get-target-systems-metadata` Get structured data about all systems for a given target. @@ -1261,7 +1361,7 @@ Type: `Attrs -> Attrs` Usage: ```nix -create-systems { hosts.my-host.specialArgs.x = true; modules = [ my-shared-module ]; } +create-systems { hosts.my-host.specialArgs.x = true; modules.nixos = [ my-shared-module ]; } ``` Result: @@ -1270,6 +1370,98 @@ Result: { my-host = ; } ``` +### `lib.snowfall.home` + +#### `lib.snowfall.home.split-user-and-host` + +Get the user and host from a combined string. + +Type: `String -> Attrs` + +Usage: + +```nix +split-user-and-host "myuser@myhost" +``` + +Result: + +```nix +{ user = "myuser"; host = "myhost"; } +``` + +#### `lib.snowfall.home.create-home` + +Create a home. + +Type: `Attrs -> Attrs` + +Usage: + +```nix +create-home { path = ./homes/my-home; } +``` + +Result: + +```nix + +``` + +#### `lib.snowfall.home.create-homes` + +Create all available homes. + +Type: `Attrs -> Attrs` + +Usage: + +```nix +create-homes { users."my-user@my-system".specialArgs.x = true; modules = [ my-shared-module ]; } +``` + +Result: + +```nix +{ "my-user@my-system" = ; } +``` + +#### `lib.snowfall.home.get-target-homes-metadata` + +Get structured data about all homes for a given target. + +Type: `String -> [Attrs]` + +Usage: + +```nix +get-target-homes-metadata ./homes +``` + +Result: + +```nix +[ { system = "x86_64-linux"; name = "my-home"; path = "/homes/x86_64-linux/my-home";} ] +``` + +#### `lib.snowfall.home.create-home-system-modules` + +Create system modules for home-manager integration. + +Type: `Attrs -> [Module]` + +Usage: + +```nix +create-home-system-modules { users."my-user@my-system".specialArgs.x = true; modules = [ my-shared-module ]; } +``` + +Result: + +```nix +[Module] +``` + ### `lib.snowfall.package` Utilities for working with flake packages. diff --git a/flake.nix b/flake.nix index a023d2a..9a5fda2 100644 --- a/flake.nix +++ b/flake.nix @@ -39,8 +39,16 @@ { inherit mkLib mkFlake; + nixosModules = { + user = ./modules/nixos/user/default.nix; + }; + + darwinModules = { + user = ./modules/darwin/user/default.nix; + }; + homeModules = { - user = ./user/default.nix; + user = ./modules/home/user/default.nix; }; }; } diff --git a/lib/home/default.nix b/lib/home/default.nix index 3f64fe1..59bb4e4 100644 --- a/lib/home/default.nix +++ b/lib/home/default.nix @@ -18,6 +18,10 @@ in hm = snowfall-lib.internal.system-lib.home-manager.hm; }); + # Get the user and host from a combined string. + # Type: String -> Attrs + # Usage: split-user-and-host "myuser@myhost" + # result: { user = "myuser"; host = "myhost"; } split-user-and-host = target: let raw-name-parts = builtins.split "@" target; @@ -35,6 +39,10 @@ in }; + # Create a home. + # Type: Attrs -> Attrs + # Usage: create-home { path = ./homes/my-home; } + # result: create-home = { path , name ? builtins.unsafeDiscardStringContext (snowfall-lib.system.get-inferred-system-name path) @@ -92,6 +100,10 @@ in }); }; + # Get structured data about all homes for a given target. + # Type: String -> [Attrs] + # Usage: get-target-homes-metadata ./homes + # result: [ { system = "x86_64-linux"; name = "my-home"; path = "/homes/x86_64-linux/my-home";} ] get-target-homes-metadata = target: let homes = snowfall-lib.fs.get-directories target; @@ -141,6 +153,10 @@ in in created-homes; + # Create system modules for home-manager integration. + # Type: Attrs -> [Module] + # Usage: create-home-system-modules { users."my-user@my-system".specialArgs.x = true; modules = [ my-shared-module ]; } + # result: [Module] create-home-system-modules = users: let created-users = create-homes users; @@ -186,10 +202,10 @@ in # @NOTE(jakehamilton): We *must* specify named attributes here in order # for home-manager to provide them. - wrapped-user-module = home-args@{ pkgs, lib, osConfig ? {}, ... }: + wrapped-user-module = home-args@{ pkgs, lib, osConfig ? { }, ... }: let user-module-result = import user-module home-args; - user-imports = + user-imports = if user-module-result ? imports then user-module-result.imports else @@ -210,7 +226,7 @@ in ({ snowfallorg.user.name = mkDefault user; }) - (osConfig.snowfallorg.home.resolvedHomes.${user} or {}) + (osConfig.snowfallorg.home.resolved-homes.${user} or { }) ]; }; in @@ -218,6 +234,9 @@ in _file = "virtual:snowfallorg/home/user/${name}"; config = mkIf host-matches { + # Initialize user information. + snowfallorg.user.${user-name} = { }; + home-manager = { users.${user-name} = wrapped-user-module; sharedModules = other-modules; diff --git a/lib/system/default.nix b/lib/system/default.nix index 296bf3e..cccf5c5 100644 --- a/lib/system/default.nix +++ b/lib/system/default.nix @@ -96,6 +96,9 @@ in specialArgs = args.specialArgs // { format = virtual-system-type; }; + modules = args.modules ++ [ + ../../modules/nixos/user/default.nix + ]; }); darwin-system-builder = args: assert assertMsg (user-inputs ? darwin) "In order to create virtual systems, you must include `darwin` as a flake input."; @@ -105,7 +108,7 @@ in format = "darwin"; }; modules = args.modules ++ [ - ../../modules/darwin/home/default.nix + ../../modules/darwin/user/default.nix ]; }); linux-system-builder = args: @@ -114,6 +117,9 @@ in specialArgs = args.specialArgs // { format = "linux"; }; + modules = args.modules ++ [ + ../../modules/nixos/user/default.nix + ]; }); in if virtual-system-type != "" then diff --git a/modules/darwin/home/default.nix b/modules/darwin/home/default.nix deleted file mode 100644 index 7466dd8..0000000 --- a/modules/darwin/home/default.nix +++ /dev/null @@ -1,31 +0,0 @@ -{ lib, options, ... }: - -let - inherit (lib) types mkOption mkIf mkMerge mkAliasDefinitions; - - cfg = options.snowfallorg; -in -{ - options.snowfallorg = { - home = mkOption { - description = "Configuration for home-manager."; - type = types.attrsOf (types.submodule ({ name, ... }: { - options.config = { - type = types.attrs; - default = { }; - }; - })); - }; - - resolvedHomes = mkOption { - type = types.attrs; - default = { }; - }; - }; - - config = mkMerge (builtins.map - (name: { - snowfallorg.resolvedHomes.${name} = mkAliasDefinitions options.snowfallorg.home.${name}.config; - }) - (builtins.attrNames cfg.home)); -} diff --git a/modules/darwin/user/default.nix b/modules/darwin/user/default.nix new file mode 100644 index 0000000..13046cc --- /dev/null +++ b/modules/darwin/user/default.nix @@ -0,0 +1,71 @@ +{ pkgs, lib, options, config, ... }: + +let + inherit (lib) types mkOption mkDefault foldl optionalAttrs; + + cfg = config.snowfallorg; + + user-names = builtins.attrNames cfg.user; + + create-system-users = system-users: name: + let + user = cfg.user.${name}; + in + system-users // (optionalAttrs user.create { + ${name} = { + home = mkDefault user.home.path; + isHidden = mkDefault false; + }; + }); + + create-resolved-home = resolved-homes: name: + let + user = cfg.user.${name}; + in + resolved-homes // { + ${name} = user.home.config; + }; +in +{ + options.snowfallorg = { + user = mkOption { + description = "User configuration."; + default = { }; + type = types.attrsOf (types.submodule ({ name, ... }: { + options = { + create = mkOption { + description = "Whether to create the user automatically."; + type = types.bool; + default = true; + }; + + home = { + path = mkOption { + type = types.str; + default = "/Users/${name}"; + }; + + config = mkOption { + type = types.attrs; + default = { }; + }; + }; + }; + })); + }; + + resolved-homes = mkOption { + type = types.attrs; + default = { }; + internal = true; + }; + }; + + config = { + users.users = (foldl (create-system-users) { } (user-names)); + + snowfallorg = { + resolved-homes = (foldl (create-resolved-home) { } (user-names)); + }; + }; +} diff --git a/modules/home/user/default.nix b/modules/home/user/default.nix index 559edc5..3cc9cba 100644 --- a/modules/home/user/default.nix +++ b/modules/home/user/default.nix @@ -1,23 +1,50 @@ -{ lib, options, ... }: +inputs@{ pkgs, lib, options, config, ... }: let - inherit (lib) types mkOption mkIf; + inherit (lib) types mkOption mkIf mkDefault; - cfg = options.snowfallorg; + cfg = config.snowfallorg; + + # @NOTE(jakehamilton): The module system chokes if it finds `osConfig` named in the module arguments + # when being used in standalone home-manager. To remedy this, we have to refer to the arguments set directly. + os-user-home = inputs.osConfig.users.users.${cfg.name}.home or null; + + default-home-directory = + if (os-user-home != null) then + os-user-home + else if pkgs.stdenv.isDarwin then + "/Users/${cfg.user.name}" + else + "/home/${cfg.user.name}"; in - # (builtins.trace (cfg.user.name or "no name")) { options.snowfallorg = { user = { + enable = mkOption { + type = types.bool; + default = true; + description = "Whether to configure the user."; + }; + name = mkOption { type = types.str; description = "The user's name."; }; + + home = { + directory = mkOption { + type = types.str; + description = "The user's home directory."; + default = default-home-directory; + }; + }; }; }; - # config = mkIf ((cfg.user.name or null) != null) { - # @TODO(jakehamilton): Get user home directory from osConfig if - # it exists. - # }; + config = mkIf cfg.user.enable { + home = { + username = mkIf (cfg.user.name or null != null) (mkDefault cfg.user.name); + homeDirectory = mkIf (cfg.user.name or null != null) (mkDefault cfg.user.home.directory); + }; + }; } diff --git a/modules/nixos/user/default.nix b/modules/nixos/user/default.nix new file mode 100644 index 0000000..81410a4 --- /dev/null +++ b/modules/nixos/user/default.nix @@ -0,0 +1,84 @@ +{ pkgs, lib, options, config, ... }: + +let + inherit (lib) types mkOption mkDefault foldl optionalAttrs optional; + + cfg = config.snowfallorg; + + user-names = builtins.attrNames cfg.user; + + create-system-users = system-users: name: + let + user = cfg.user.${name}; + in + system-users // (optionalAttrs user.create { + ${name} = { + isNormalUser = mkDefault true; + + name = mkDefault cfg.name; + + home = mkDefault user.home.path; + group = mkDefault "users"; + + extraGroups = (builtins.trace user.admin) optional user.admin "wheel"; + }; + }); + + create-resolved-home = resolved-homes: name: + let + user = cfg.user.${name}; + in + resolved-homes // { + ${name} = user.home.config; + }; +in +(builtins.trace "hello") +{ + options.snowfallorg = { + user = mkOption { + description = "User configuration."; + default = { }; + type = types.attrsOf (types.submodule ({ name, ... }: { + options = { + create = mkOption { + description = "Whether to create the user automatically."; + type = types.bool; + default = true; + }; + + admin = mkOption { + description = "Whether the user should be added to the wheel group."; + type = types.bool; + default = true; + }; + + home = { + path = mkOption { + type = types.str; + default = "/home/${name}"; + }; + + config = mkOption { + type = types.attrs; + default = { }; + }; + }; + }; + })); + }; + + resolved-homes = mkOption { + type = types.attrs; + default = { }; + internal = true; + }; + }; + + config = { + users.users = (foldl (create-system-users) { } (user-names)); + + snowfallorg = { + resolved-homes = (foldl (create-resolved-home) { } (user-names)); + }; + }; +}