diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..191bee1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,193 @@ +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS +AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + + "Legal Entity" shall mean the +union of the acting entity and all other entities that control, are controlled +by, or are under common control with that entity. For the purposes of this +definition, "control" means (i) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or otherwise, or (ii) +ownership of fifty percent (50%) or more of the outstanding shares, or (iii) +beneficial ownership of such entity. + + "You" (or "Your") shall mean +an individual or Legal Entity exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation source, and +configuration files. + + "Object" form shall mean any form resulting +from mechanical transformation or translation of a Source form, including but not +limited to compiled object code, generated documentation, and conversions to +other media types. + + "Work" shall mean the work of authorship, +whether in Source or Object form, made available under the License, as indicated +by a copyright notice that is included in or attached to the work (an example is +provided in the Appendix below). + + "Derivative Works" shall mean any +work, whether in Source or Object form, that is based on (or derived from) the +Work and for which the editorial revisions, annotations, elaborations, or other +modifications represent, as a whole, an original work of authorship. For the +purposes of this License, Derivative Works shall not include works that remain +separable from, or merely link (or bind by name) to the interfaces of, the Work +and Derivative Works thereof. + + "Contribution" shall mean any work +of authorship, including the original version of the Work and any modifications +or additions to that Work or Derivative Works thereof, that is intentionally +submitted to Licensor for inclusion in the Work by the copyright owner or by an +individual or Legal Entity authorized to submit on behalf of the copyright owner. +For the purposes of this definition, "submitted" means any form of electronic, +verbal, or written communication sent to the Licensor or its representatives, +including but not limited to communication on electronic mailing lists, source +code control systems, and issue tracking systems that are managed by, or on +behalf of, the Licensor for the purpose of discussing and improving the Work, but +excluding communication that is conspicuously marked or otherwise designated in +writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of +whom a Contribution has been received by Licensor and subsequently incorporated +within the Work. + + 2. Grant of Copyright License. Subject to the terms and +conditions of this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license +to reproduce, prepare Derivative Works of, publicly display, publicly perform, +sublicense, and distribute the Work and such Derivative Works in Source or Object +form. + + 3. Grant of Patent License. Subject to the terms and conditions of this +License, each Contributor hereby grants to You a perpetual, worldwide, +non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this +section) patent license to make, have made, use, offer to sell, sell, import, and +otherwise transfer the Work, where such license applies only to those patent +claims licensable by such Contributor that are necessarily infringed by their +Contribution(s) alone or by combination of their Contribution(s) with the Work to +which such Contribution(s) was submitted. If You institute patent litigation +against any entity (including a cross-claim or counterclaim in a lawsuit) +alleging that the Work or a Contribution incorporated within the Work constitutes +direct or contributory patent infringement, then any patent licenses granted to +You under this License for that Work shall terminate as of the date such +litigation is filed. + + 4. Redistribution. You may reproduce and distribute +copies of the Work or Derivative Works thereof in any medium, with or without +modifications, and in Source or Object form, provided that You meet the following +conditions: + + (a) You must give any other recipients of the Work or +Derivative Works a copy of this License; and + + (b) You must cause any +modified files to carry prominent notices stating that You changed the files; +and + + (c) You must retain, in the Source form of any Derivative Works that +You distribute, all copyright, patent, trademark, and attribution notices from +the Source form of the Work, excluding those notices that do not pertain to any +part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text +file as part of its distribution, then any Derivative Works that You distribute +must include a readable copy of the attribution notices contained within such +NOTICE file, excluding those notices that do not pertain to any part of the +Derivative Works, in at least one of the following places: within a NOTICE text +file distributed as part of the Derivative Works; within the Source form or +documentation, if provided along with the Derivative Works; or, within a display +generated by the Derivative Works, if and wherever such third-party notices +normally appear. The contents of the NOTICE file are for informational purposes +only and do not modify the License. You may add Your own attribution notices +within Derivative Works that You distribute, alongside or as an addendum to the +NOTICE text from the Work, provided that such additional attribution notices +cannot be construed as modifying the License. + + You may add Your own +copyright statement to Your modifications and may provide additional or different +license terms and conditions for use, reproduction, or distribution of Your +modifications, or for any such Derivative Works as a whole, provided Your use, +reproduction, and distribution of the Work otherwise complies with the conditions +stated in this License. + + 5. Submission of Contributions. Unless You explicitly +state otherwise, any Contribution intentionally submitted for inclusion in the +Work by You to the Licensor shall be under the terms and conditions of this +License, without any additional terms or conditions. Notwithstanding the above, +nothing herein shall supersede or modify the terms of any separate license +agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, +trademarks, service marks, or product names of the Licensor, except as required +for reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless +required by applicable law or agreed to in writing, Licensor provides the Work +(and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, +without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, +MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible +for determining the appropriateness of using or redistributing the Work and +assume any risks associated with Your exercise of permissions under this +License. + + 8. Limitation of Liability. In no event and under no legal theory, +whether in tort (including negligence), contract, or otherwise, unless required +by applicable law (such as deliberate and grossly negligent acts) or agreed to in +writing, shall any Contributor be liable to You for damages, including any +direct, indirect, special, incidental, or consequential damages of any character +arising as a result of this License or out of the use or inability to use the +Work (including but not limited to damages for loss of goodwill, work stoppage, +computer failure or malfunction, or any and all other commercial damages or +losses), even if such Contributor has been advised of the possibility of such +damages. + + 9. Accepting Warranty or Additional Liability. While redistributing +the Work or Derivative Works thereof, You may choose to offer, and charge a fee +for, acceptance of support, warranty, indemnity, or other liability obligations +and/or rights consistent with this License. However, in accepting such +obligations, You may act only on Your own behalf and on Your sole responsibility, +not on behalf of any other Contributor, and only if You agree to indemnify, +defend, and hold each Contributor harmless for any liability incurred by, or +claims asserted against, such Contributor by reason of your accepting any such +warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, +attach the following boilerplate notice, with the fields enclosed by brackets +"[]" replaced with your own identifying information. (Don't include the +brackets!) The text should be enclosed in the appropriate comment syntax for the +file format. We also recommend that a file or class name and description of +purpose be included on the same "printed page" as the copyright notice for easier +identification within third-party archives. + +Copyright 2023 Jake Hamilton + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.md b/README.md index 80a7e65..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. @@ -66,7 +67,7 @@ library instance with `mkLib`. description = "My Flake"; inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05"; + nixpkgs.url = "github:nixos/nixpkgs/nixos-22.11"; snowfall-lib = { url = "github:snowfallorg/lib"; @@ -81,6 +82,10 @@ library instance with `mkLib`. # your flake. inherit inputs; src = ./.; + + # You can optionally place your Snowfall-related files in another + # directory. + snowfall.root = ./nix; }; in # We'll cover what to do here next. @@ -100,6 +105,10 @@ let lib = inputs.snowfall-lib.mkLib { inherit inputs; src = ./.; + + # You can optionally place your Snowfall-related files in another + # directory. + snowfall.root = ./nix; }; in lib.mkFlake { } @@ -114,7 +123,7 @@ inputs.snowfall-lib.mkFlake { }; ``` -See [`lib.mkFlake`](#lib.mkflake) for information on how to configure your flake's outputs. +See [`lib.mkFlake`](#libmkflake) for information on how to configure your flake's outputs. ## `lib` @@ -150,7 +159,10 @@ Snowfall Lib has opinions about how a flake's files are laid out. This lets the structure that `lib` expects to find at the root of your flake. ``` -flake-root/ +snowfall-root/ +│ The Snowfall root defaults to "src", but can be changed by setting "snowfall.root". +│ This is useful if you want to add a flake to a project, but don't want to clutter the +│ root of the repository with directories. │ │ Your Nix flake. ├─ flake.nix @@ -161,7 +173,7 @@ flake-root/ │ │ A Nix function called with `inputs`, `snowfall-inputs`, and `lib`. │ │ The function should return an attribute set to merge with `lib`. │ ├─ default.nix -│ │ +│ │ │ │ Any (nestable) directory name. │ └─ **/ │ │ @@ -184,12 +196,20 @@ flake-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) │ │ @@ -235,6 +255,37 @@ flake-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 @@ -247,7 +298,7 @@ packages, overlays, and shells specified by the [Flake Structure](#flake-structu description = "My Flake"; inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05"; + nixpkgs.url = "github:nixos/nixpkgs/nixos-22.11"; snowfall-lib = { url = "github:snowfallorg/lib"; @@ -279,7 +330,7 @@ You can apply overlays and modules from your flake's inputs with the following o description = "My Flake"; inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05"; + nixpkgs.url = "github:nixos/nixpkgs/nixos-22.11"; snowfall-lib = { url = "github:snowfallorg/lib"; @@ -338,7 +389,7 @@ on `pkgs` and consumers of your flake can use the generated `.overla description = "My Flake"; inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05"; + nixpkgs.url = "github:nixos/nixpkgs/nixos-22.11"; snowfall-lib = { url = "github:snowfallorg/lib"; @@ -366,7 +417,7 @@ on `pkgs` and consumers of your flake can use the generated `.overla # Optionally place all packages under a namespace when used in an overlay. # Instead of accessing packages with `pkgs.`, your internal packages # will be available at `pkgs..`. - overlay-package-namespace = "my-namespace"; + package-namespace = "my-namespace"; # You can also pass through external packages or dynamically create new ones # in addition to the ones that `lib` will create from your `packages/` directory. @@ -383,7 +434,7 @@ on `pkgs` and consumers of your flake can use the generated `.overla Snowfall Lib will create packages and shells based on your `packages/` and `shells` directories. However, it is common to additionally map one of those packages or shells -to be their respective default. This can be achieved by using `outputs-builder` and +to be their respective default. This can be achieved by setting an `alias` and mapping the `default` package or shell to the name of the one you want. ```nix @@ -391,7 +442,7 @@ mapping the `default` package or shell to the name of the one you want. description = "My Flake"; inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05"; + nixpkgs.url = "github:nixos/nixpkgs/nixos-22.11"; snowfall-lib = { url = "github:snowfallorg/lib"; @@ -411,16 +462,22 @@ mapping the `default` package or shell to the name of the one you want. }; in lib.mkFlake { - # You can also pass through external packages or dynamically create new ones - # in addition to the ones that `lib` will create from your `packages/` directory. - outputs-builder = channels: { + alias = { packages = { default = "my-package"; }; - devShells = { + shells = { default = "my-shell"; }; + + modules = { + default = "my-module"; + }; + + templates = { + default = "my-template"; + }; }; }; } @@ -437,7 +494,7 @@ include `darwin` and/or `nixos-generators` as inputs. description = "My Flake"; inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05"; + nixpkgs.url = "github:nixos/nixpkgs/nixos-22.11"; snowfall-lib = { url = "github:snowfallorg/lib"; @@ -509,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. @@ -701,8 +800,11 @@ Result: File system utilities. #### `lib.snowfall.fs.is-file-kind` + #### `lib.snowfall.fs.is-symlink-kind` + #### `lib.snowfall.fs.is-directory-kind` + #### `lib.snowfall.fs.is-unknown-kind` Matchers for file kinds. These are often used with `readDir`. @@ -739,6 +841,24 @@ Result: "/user-source/systems" ``` +#### `lib.snowfall.fs.get-snowfall-file` + +Get a file path relative to the user's snowfall directory. + +Type: `Path -> Path` + +Usage: + +```nix +get-snowfall-file "systems" +``` + +Result: + +```nix +"/user-source/snowfall-dir/systems" +``` + #### `lib.snowfall.fs.internal-get-file` Get a file relative to the Snowfall Lib flake. You probably shouldn't use this! @@ -937,6 +1057,28 @@ Result: [ "./something/some-directory/a.nix" ] ``` +### `lib.snowfall.module` + +Utilities for working with NixOS modules. + +#### `lib.snowfall.module.create-modules` + +Create flake output modules. + +Type: `Attrs -> Attrs` + +Usage: + +```nix +create-modules { src = ./my-modules; overrides = { inherit another-module; }; alias = { default = "another-module" }; } +``` + +Result: + +```nix +{ another-module = ...; my-module = ...; default = ...; } +``` + ### `lib.snowfall.attrs` Utilities for working with attribute sets. @@ -1066,6 +1208,24 @@ Result: false ``` +#### `lib.snowfall.system.is-virtual` + +Check whether a named system is virtual. + +Type: `String -> Bool` + +Usage: + +```nix +is-linux "x86_64-iso" +``` + +Result: + +```nix +true +``` + #### `lib.snowfall.system.get-virtual-system-type` Get the virtual system type of a system target. @@ -1084,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. @@ -1175,7 +1353,7 @@ Result: ``` #### `lib.snowfall.system.create-systems` - + Create all available systems. Type: `Attrs -> Attrs` @@ -1183,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: @@ -1192,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. @@ -1205,7 +1475,7 @@ Type: `Attrs -> Attrs` Usage: ```nix -create-packages { inherit channels; src = ./my-packages; overrides = { inherit another-package; default = "my-package"; }; } +create-packages { inherit channels; src = ./my-packages; overrides = { inherit another-package; }; alias = { default = "another-package"; }; } ``` Result: @@ -1227,7 +1497,7 @@ Type: `Attrs -> Attrs` Usage: ```nix -create-shells { inherit channels; src = ./my-shells; overrides = { inherit another-shell; default = "my-shell"; }; } +create-shells { inherit channels; src = ./my-shells; overrides = { inherit another-shell; }; alias = { default = "another-shell"; }; } ``` Result: @@ -1249,7 +1519,7 @@ Type: `Attrs -> Attrs -> [(a -> b -> c)]` Usage: ```nix -create-overlays-builder { src = ./my-overlays; overlay-package-namespace = "my-packages"; extra-overlays = []; } +create-overlays-builder { src = ./my-overlays; package-namespace = "my-packages"; extra-overlays = []; } ``` Result: @@ -1270,7 +1540,7 @@ Usage: create-overlays { src = ./my-overlays; packages-src = ./my-packages; - overlay-package-namespace = "my-namespace"; + package-namespace = "my-namespace"; extra-overlays = { my-example = final: prev: {}; }; @@ -1300,7 +1570,7 @@ Type: `Attrs -> Attrs` Usage: ```nix -create-templates { src = ./my-templates; overrides = { inherit another-template; default = "my-template"; }; } +create-templates { src = ./my-templates; overrides = { inherit another-template; }; alias = { default = "another-template"; }; } ``` Result: diff --git a/flake.lock b/flake.lock index d8f9b49..369ec0e 100644 --- a/flake.lock +++ b/flake.lock @@ -51,16 +51,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1665216225, - "narHash": "sha256-SUuvJXGEXhmyaGJlDlptbc9I2Wai9tUx83QYIqvipfE=", + "lastModified": 1677028070, + "narHash": "sha256-sUKqd8HYBrtPxCRXFWvsnQDnwqnw1uIDwu4khcZuL2k=", "owner": "nixos", "repo": "nixpkgs", - "rev": "1a9935bf90e0f4225756f99e772f06d5fea9edb6", + "rev": "d3a15cd8dc917f4364ba0b332a1c389dc3177603", "type": "github" }, "original": { "owner": "nixos", - "ref": "release-22.05", + "ref": "release-22.11", "repo": "nixpkgs", "type": "github" } diff --git a/flake.nix b/flake.nix index 15dd30f..a1ab023 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,7 @@ description = "Snowfall Lib"; inputs = { - nixpkgs.url = "github:nixos/nixpkgs/release-22.05"; + nixpkgs.url = "github:nixos/nixpkgs/release-22.11"; flake-utils-plus.url = "github:gytis-ivaskevicius/flake-utils-plus"; flake-compat = { @@ -22,15 +22,15 @@ # `lib.flake-utils-plus.mkApp`. # Usage: mkLib { inherit inputs; src = ./.; } # result: lib - mkLib = import ./lib core-inputs; + mkLib = import ./snowfall-lib core-inputs; # A convenience wrapper to create the library and then call `lib.mkFlake`. # Usage: mkFlake { inherit inputs; src = ./.; ... } # result: - mkFlake = flake-and-lib-options@{ inputs, src, ... }: + mkFlake = flake-and-lib-options@{ inputs, src, snowfall ? { }, ... }: let lib = mkLib { - inherit inputs src; + inherit inputs src snowfall; }; flake-options = builtins.removeAttrs flake-and-lib-options [ "inputs" "src" ]; in @@ -38,5 +38,48 @@ in { inherit mkLib mkFlake; + + nixosModules = { + user = ./modules/nixos/user/default.nix; + }; + + darwinModules = { + user = ./modules/darwin/user/default.nix; + }; + + homeModules = { + user = ./modules/home/user/default.nix; + }; + + _snowfall = rec { + + raw-config = config; + + config = { + root = ./.; + src = ./.; + namespace = "snowfall"; + lib-dir = "snowfall-lib"; + + meta = { + name = "snowfall-lib"; + title = "Snowfall Lib"; + }; + }; + + internal-lib = + let + lib = mkLib { + src = ./.; + + inputs = inputs // { + self = { }; + }; + }; + in + builtins.removeAttrs + lib.snowfall + [ "internal" ]; + }; }; } diff --git a/lib/attrs/default.nix b/lib/attrs/default.nix deleted file mode 100644 index 9896757..0000000 --- a/lib/attrs/default.nix +++ /dev/null @@ -1,59 +0,0 @@ -{ core-inputs -, user-inputs -, snowfall-lib -}: - -let - inherit (core-inputs.nixpkgs.lib) - assertMsg - mapAttrsToList - mapAttrs - flatten - foldl - recursiveUpdate - mergeAttrs - isDerivation; -in -{ - attrs = { - # Map and flatten an attribute set into a list. - # Type: (a -> b -> [c]) -> Attrs -> [c] - # Usage: map-concat-attrs-to-list (name: value: [name value]) { x = 1; y = 2; } - # result: [ "x" 1 "y" 2 ] - map-concat-attrs-to-list = f: attrs: - flatten (mapAttrsToList f attrs); - - # Recursively merge a list of attribute sets. - # Type: [Attrs] -> Attrs - # Usage: merge-deep [{ x = 1; } { x = 2; }] - # result: { x = 2; } - merge-deep = foldl recursiveUpdate { }; - - # Merge the root of a list of attribute sets. - # Type: [Attrs] -> Attrs - # Usage: merge-shallow [{ x = 1; } { x = 2; }] - # result: { x = 2; } - merge-shallow = foldl mergeAttrs { }; - - # Merge shallow for packages, but allow one deeper layer of attribute sets. - # Type: [Attrs] -> Attrs - # Usage: merge-shallow-packages [ { inherit (pkgs) vim; some.value = true; } { some.value = false; } ] - # result: { vim = ...; some.value = false; } - merge-shallow-packages = items: - foldl - (result: item: - result // (mapAttrs - (name: value: - if isDerivation value then - value - else if builtins.isAttrs value then - (result.${name} or { }) // value - else - value - ) - item) - ) - { } - items; - }; -} diff --git a/lib/flake/default.nix b/lib/flake/default.nix deleted file mode 100644 index 318739b..0000000 --- a/lib/flake/default.nix +++ /dev/null @@ -1,139 +0,0 @@ -{ core-inputs -, user-inputs -, snowfall-lib -}: - -let - inherit (core-inputs.nixpkgs.lib) assertMsg foldl filterAttrs const; -in -rec { - flake = rec { - # Remove the `self` attribute from an attribute set. - # Type: Attrs -> Attrs - # Usage: without-self { self = {}; x = true; } - # result: { x = true; } - without-self = flake-inputs: builtins.removeAttrs flake-inputs [ "self" ]; - - # Remove the `src` attribute from an attribute set. - # Type: Attrs -> Attrs - # Usage: without-src { src = ./.; x = true; } - # result: { x = true; } - without-src = flake-inputs: builtins.removeAttrs flake-inputs [ "src" ]; - - # Remove the `src` and `self` attributes from an attribute set. - # Type: Attrs -> Attrs - # Usage: without-snowfall-inputs { self = {}; src = ./.; x = true; } - # result: { x = true; } - without-snowfall-inputs = snowfall-lib.fp.compose without-self without-src; - - # Remove Snowfall-specific attributes so the rest can be safely - # passed to flake-utils-plus. - # Type: Attrs -> Attrs - # Usage: without-snowfall-options { src = ./.; x = true; } - # result: { x = true; } - without-snowfall-options = flake-options: - builtins.removeAttrs - flake-options - [ - "systems" - "modules" - "overlays" - "packages" - "outputs-builder" - "outputsBuilder" - "packagesPrefix" - "hosts" - "channels-config" - "templates" - "overlay-package-namespace" - ]; - - # Transform an attribute set of inputs into an attribute set where - # the values are the inputs' `lib` attribute. Entries without a `lib` - # attribute are removed. - # Type: Attrs -> Attrs - # Usage: get-lib { x = nixpkgs; y = {}; } - # result: { x = nixpkgs.lib; } - get-libs = attrs: - let - # @PERF(jakehamilton): Replace filter+map with a fold. - attrs-with-libs = filterAttrs - (name: value: builtins.isAttrs (value.lib or null)) - attrs; - libs = - builtins.mapAttrs (name: input: input.lib) attrs-with-libs; - in - libs; - - }; - - mkFlake = full-flake-options: - let - custom-flake-options = flake.without-snowfall-options full-flake-options; - systems = snowfall-lib.system.create-systems (full-flake-options.systems or { }); - hosts = snowfall-lib.attrs.merge-shallow [ (full-flake-options.systems.hosts or { }) systems ]; - templates = snowfall-lib.template.create-templates { - overrides = (full-flake-options.templates or { }); - }; - modules = snowfall-lib.module.create-modules { - overrides = (full-flake-options.modules or { }); - }; - overlays = snowfall-lib.overlay.create-overlays { - overlay-package-namespace = full-flake-options.overlay-package-namespace or null; - extra-overlays = full-flake-options.extra-exported-overlays or { }; - }; - - outputs-builder = channels: - let - user-outputs-builder = - full-flake-options.outputs-builder - or full-flake-options.outputsBuilder - or (const { }); - user-outputs = user-outputs-builder channels; - packages = snowfall-lib.package.create-packages { - inherit channels; - overrides = (full-flake-options.packages or { }) // (user-outputs.packages or { }); - }; - shells = snowfall-lib.shell.create-shells { - inherit channels; - overrides = (full-flake-options.shells or { }) // (user-outputs.devShells or { }); - }; - - outputs = { - inherit packages; - - devShells = shells; - }; - in - snowfall-lib.attrs.merge-deep [ user-outputs outputs ]; - - flake-options = custom-flake-options // { - inherit hosts templates; - inherit (user-inputs) self; - - lib = snowfall-lib.internal.user-lib; - inputs = snowfall-lib.flake.without-src user-inputs; - - nixosModules = modules; - - channelsConfig = full-flake-options.channels-config or { }; - - channels.nixpkgs.overlaysBuilder = snowfall-lib.overlay.create-overlays-builder { - overlay-package-namespace = full-flake-options.overlay-package-namespace or null; - extra-overlays = full-flake-options.overlays or [ ]; - }; - - outputsBuilder = outputs-builder; - }; - - flake-utils-plus-outputs = - core-inputs.flake-utils-plus.lib.mkFlake flake-options; - - flake-outputs = - flake-utils-plus-outputs // { - inherit overlays; - overlay = overlays.default; - }; - in - flake-outputs; -} diff --git a/lib/fp/default.nix b/lib/fp/default.nix deleted file mode 100644 index 78da0ee..0000000 --- a/lib/fp/default.nix +++ /dev/null @@ -1,36 +0,0 @@ -{ core-inputs -, user-inputs -, snowfall-lib -}: - -let - inherit (builtins) baseNameOf dirOf; - inherit (core-inputs.nixpkgs.lib) id foldr flip; -in -{ - fp = rec { - # Compose two functions. - # Type: (b -> c) -> (a -> b) -> a -> c - # Usage: compose add-two add-one - # result: (x: add-two (add-one x)) - compose = f: g: x: f (g x); - - # Compose many functions. - # Type: [(x -> y)] -> a -> b - # Usage: compose-all [ add-two add-one ] - # result: (x: add-two (add-one x)) - compose-all = foldr compose id; - - # Call a function with an argument. - # Type: (a -> b) -> a -> b - # Usage: call (x: x + 1) 0 - # result: 1 - call = f: x: f x; - - # Apply an argument to a function. - # Type: a -> (a -> b) -> b - # Usage: call (x: x + 1) 0 - # result: 1 - apply = flip call; - }; -} diff --git a/lib/fs/default.nix b/lib/fs/default.nix deleted file mode 100644 index 48910ad..0000000 --- a/lib/fs/default.nix +++ /dev/null @@ -1,153 +0,0 @@ -{ core-inputs -, user-inputs -, snowfall-lib -}: - -let - inherit (builtins) readDir pathExists; - inherit (core-inputs) flake-utils-plus; - inherit (core-inputs.nixpkgs.lib) assertMsg filterAttrs mapAttrsToList flatten; - - file-name-regex = "(.*)\\.(.*)$"; -in -{ - fs = rec { - # Matchers for file kinds. These are often used with `readDir`. - # Type: String -> Bool - # Usage: is-file-kind "directory" - # result: false - is-file-kind = kind: kind == "regular"; - is-symlink-kind = kind: kind == "symlink"; - is-directory-kind = kind: kind == "directory"; - is-unknown-kind = kind: kind == "unknown"; - - # Get a file path relative to the user's flake. - # Type: Path -> Path - # Usage: get-file "systems" - # result: "/user-source/systems" - get-file = path: "${user-inputs.src}/${path}"; - - # Get a file path relative to the this flake. - # Type: Path -> Path - # Usage: get-file "systems" - # result: "/user-source/systems" - internal-get-file = path: "${core-inputs.src}/${path}"; - - # Safely read from a directory if it exists. - # Type: Path -> Attrs - # Usage: safe-read-directory ./some/path - # result: { "my-file.txt" = "regular"; } - safe-read-directory = path: - if pathExists path then - readDir path - else - {}; - - # Get directories at a given path. - # Type: Path -> [Path] - # Usage: get-directories ./something - # result: [ "./something/a-directory" ] - get-directories = path: - let - entries = safe-read-directory path; - filtered-entries = filterAttrs (name: kind: is-directory-kind kind) entries; - in - mapAttrsToList (name: kind: "${path}/${name}") filtered-entries; - - # Get files at a given path. - # Type: Path -> [Path] - # Usage: get-files ./something - # result: [ "./something/a-file" ] - get-files = path: - let - entries = safe-read-directory path; - filtered-entries = filterAttrs (name: kind: is-file-kind kind) entries; - in - mapAttrsToList (name: kind: "${path}/${name}") filtered-entries; - - # Get files at a given path, traversing any directories within. - # Type: Path -> [Path] - # Usage: get-files-recursive ./something - # result: [ "./something/some-directory/a-file" ] - get-files-recursive = path: - let - entries = safe-read-directory path; - filtered-entries = - filterAttrs - (name: kind: (is-file-kind kind) || (is-directory-kind kind)) - entries; - map-file = name: kind: - let - path' = "${path}/${name}"; - in if is-directory-kind kind then - get-files-recursive path' - else - path'; - files = snowfall-lib.attrs.map-concat-attrs-to-list - map-file - filtered-entries; - in - files; - - # Get nix files at a given path. - # Type: Path -> [Path] - # Usage: get-nix-files "./something" - # result: [ "./something/a.nix" ] - get-nix-files = path: - builtins.filter - (snowfall-lib.path.has-file-extension "nix") - (get-files path); - - # Get nix files at a given path, traversing any directories within. - # Type: Path -> [Path] - # Usage: get-nix-files "./something" - # result: [ "./something/a.nix" ] - get-nix-files-recursive = path: - builtins.filter - (snowfall-lib.path.has-file-extension "nix") - (get-files-recursive path); - - # Get nix files at a given path named "default.nix". - # Type: Path -> [Path] - # Usage: get-default-nix-files "./something" - # result: [ "./something/default.nix" ] - get-default-nix-files = path: - builtins.filter - (name: builtins.baseNameOf name == "default.nix") - (get-files path); - - # Get nix files at a given path named "default.nix", traversing any directories within. - # Type: Path -> [Path] - # Usage: get-default-nix-files-recursive "./something" - # result: [ "./something/some-directory/default.nix" ] - get-default-nix-files-recursive = path: - builtins.filter - (name: builtins.baseNameOf name == "default.nix") - (get-files-recursive path); - - # Get nix files at a given path not named "default.nix". - # Type: Path -> [Path] - # Usage: get-non-default-nix-files "./something" - # result: [ "./something/a.nix" ] - get-non-default-nix-files = path: - builtins.filter - (name: - (snowfall-lib.path.has-file-extension "nix" name) - && (builtins.baseNameOf name != "default.nix") - ) - (get-files path); - - # Get nix files at a given path not named "default.nix", - # traversing any directories within. - # Type: Path -> [Path] - # Usage: get-non-default-nix-files-recursive "./something" - # result: [ "./something/some-directory/a.nix" ] - get-non-default-nix-files-recursive = path: - builtins.filter - (name: - (snowfall-lib.path.has-file-extension "nix" name) - && (builtins.baseNameOf name != "default.nix") - ) - (get-files-recursive path); - }; -} diff --git a/lib/module/default.nix b/lib/module/default.nix deleted file mode 100644 index 5b7d49c..0000000 --- a/lib/module/default.nix +++ /dev/null @@ -1,48 +0,0 @@ -{ core-inputs -, user-inputs -, snowfall-lib -}: - -let - inherit (builtins) baseNameOf; - inherit (core-inputs.nixpkgs.lib) assertMsg foldl; - - user-modules-root = snowfall-lib.fs.get-file "modules"; -in -{ - module = { - # Create flake output modules. - # Type: Attrs -> Attrs - # Usage: create-modules { src = ./my-modules; overrides = { inherit another-module; default = "my-module"; }; } - # result: { another-module = ...; my-module = ...; default = ...; } - create-modules = - { src ? user-modules-root - , overrides ? { } - }: - let - user-modules = snowfall-lib.fs.get-default-nix-files-recursive src; - create-module-metadata = module: { - name = builtins.unsafeDiscardStringContext (snowfall-lib.path.get-parent-directory module); - path = module; - }; - modules-metadata = builtins.map create-module-metadata user-modules; - merge-modules = modules: metadata: - modules // { - ${metadata.name} = metadata.path; - }; - modules-without-default = foldl merge-modules { } modules-metadata; - default-module = - if overrides.default or null == null then - { } - else if builtins.isAttrs overrides.default then - { default = overrides.default; } - else if modules-without-default.${overrides.default} or null != null then - { default = modules-without-default.${overrides.default}; } - else - { }; - overrides-without-default = builtins.removeAttrs overrides [ "default" ]; - modules = modules-without-default // default-module // overrides-without-default; - in - modules; - }; -} diff --git a/lib/package/default.nix b/lib/package/default.nix deleted file mode 100644 index 0103103..0000000 --- a/lib/package/default.nix +++ /dev/null @@ -1,52 +0,0 @@ -{ core-inputs -, user-inputs -, snowfall-lib -}: - -let - inherit (core-inputs.nixpkgs.lib) assertMsg foldl; - - user-packages-root = snowfall-lib.fs.get-file "packages"; -in -{ - package = { - # Create flake output packages. - # Type: Attrs -> Attrs - # Usage: create-packages { inherit channels; src = ./my-packages; overrides = { inherit another-package; default = "my-package"; }; } - # result: { another-package = ...; my-package = ...; default = ...; } - create-packages = - { channels - , src ? user-packages-root - , pkgs ? channels.nixpkgs - , overrides ? { } - }: - let - user-packages = snowfall-lib.fs.get-default-nix-files-recursive src; - create-package-metadata = package: { - name = builtins.unsafeDiscardStringContext (snowfall-lib.path.get-parent-directory package); - drv = pkgs.callPackage package { - inherit channels; - lib = snowfall-lib.internal.system-lib; - }; - }; - packages-metadata = builtins.map create-package-metadata user-packages; - merge-packages = packages: metadata: - packages // { - ${metadata.name} = metadata.drv; - }; - packages-without-default = foldl merge-packages { } packages-metadata; - default-package = - if overrides.default or null == null then - { } - else if builtins.isAttrs overrides.default then - { default = overrides.default; } - else if packages-without-default.${overrides.default} or null != null then - { default = packages-without-default.${overrides.default}; } - else - { }; - overrides-without-default = builtins.removeAttrs overrides [ "default" ]; - packages = packages-without-default // default-package // overrides-without-default; - in - packages; - }; -} diff --git a/lib/path/default.nix b/lib/path/default.nix deleted file mode 100644 index 216f194..0000000 --- a/lib/path/default.nix +++ /dev/null @@ -1,77 +0,0 @@ -{ core-inputs -, user-inputs -, snowfall-lib -}: - -let - inherit (builtins) toString baseNameOf dirOf concatStringsSep; - inherit (core-inputs.nixpkgs.lib) assertMsg last init; - - file-name-regex = "(.*)\\.(.*)$"; -in -{ - path = rec { - # Split a file name and its extension. - # Type: String -> [String] - # Usage: split-file-extension "my-file.md" - # result: [ "my-file" "md" ] - split-file-extension = file: - let - match = builtins.match file-name-regex file; - in - assert assertMsg (match != null) "lib.snowfall.split-file-extension: File must have an extension to split."; - match; - - # Check if a file name has a file extension. - # Type: String -> Bool - # Usage: has-any-file-extension "my-file.txt" - # result: true - has-any-file-extension = file: - let - match = builtins.match file-name-regex (toString file); - in - match != null; - - # Get the file extension of a file name. - # Type: String -> String - # Usage: get-file-extension "my-file.final.txt" - # result: "txt" - get-file-extension = file: - if has-any-file-extension file then - let - match = builtins.match file-name-regex (toString file); - in - last match - else - ""; - - # Check if a file name has a specific file extension. - # Type: String -> String -> Bool - # Usage: has-file-extension "txt" "my-file.txt" - # result: true - has-file-extension = extension: file: - if has-any-file-extension file then - extension == get-file-extension file - else - false; - - # Get the parent directory for a given path. - # Type: Path -> Path - # Usage: get-parent-directory "/a/b/c" - # result: "/a/b" - get-parent-directory = snowfall-lib.fp.compose baseNameOf dirOf; - - # Get the file name of a path without its extension. - # Type: Path -> String - # Usage: get-file-name-without-extension ./some-directory/my-file.pdf - # result: "my-file" - get-file-name-without-extension = path: - let - file-name = baseNameOf path; - in - if has-any-file-extension file-name then - concatStringsSep "" (init (split-file-extension file-name)) - else - file-name; - }; -} diff --git a/lib/shell/default.nix b/lib/shell/default.nix deleted file mode 100644 index bbf37b1..0000000 --- a/lib/shell/default.nix +++ /dev/null @@ -1,51 +0,0 @@ -{ core-inputs -, user-inputs -, snowfall-lib -}: - -let - inherit (core-inputs.nixpkgs.lib) assertMsg foldl; - - user-shells-root = snowfall-lib.fs.get-file "shells"; -in -{ - shell = { - # Create flake output packages. - # Type: Attrs -> Attrs - # Usage: create-shells { inherit channels; src = ./my-shells; overrides = { inherit another-shell; default = "my-shell"; }; } - # result: { another-shell = ...; my-shell = ...; default = ...; } - create-shells = - { channels - , src ? user-shells-root - , overrides ? { } - }: - let - user-shells = snowfall-lib.fs.get-default-nix-files-recursive src; - create-shell-metadata = shell: - { - name = builtins.unsafeDiscardStringContext (snowfall-lib.path.get-parent-directory shell); - drv = channels.nixpkgs.callPackage shell { - lib = snowfall-lib.internal.system-lib; - }; - }; - shells-metadata = builtins.map create-shell-metadata user-shells; - merge-shells = shells: metadata: - shells // { - ${metadata.name} = metadata.drv; - }; - shells-without-default = foldl merge-shells { } shells-metadata; - default-shell = - if overrides.default or null == null then - { } - else if builtins.isAttrs overrides.default then - { default = overrides.default; } - else if shells-without-default.${overrides.default} or null != null then - { default = shells-without-default.${overrides.default}; } - else - { }; - overrides-without-default = builtins.removeAttrs overrides [ "default" ]; - shells = shells-without-default // default-shell // overrides-without-default; - in - shells; - }; -} diff --git a/lib/system/default.nix b/lib/system/default.nix deleted file mode 100644 index f4a356d..0000000 --- a/lib/system/default.nix +++ /dev/null @@ -1,200 +0,0 @@ -{ core-inputs -, user-inputs -, snowfall-lib -}: - -let - inherit (builtins) dirOf baseNameOf; - inherit (core-inputs.nixpkgs.lib) assertMsg fix hasInfix concatMap foldl; - - virtual-systems = import ./virtual-systems.nix; - - user-systems-root = snowfall-lib.fs.get-file "systems"; - user-modules-root = snowfall-lib.fs.get-file "modules"; - - get-inferred-system-name = path: - if snowfall-lib.path.has-file-extension "nix" path then - snowfall-lib.path.get-parent-directory path - else - baseNameOf path; -in -{ - system = rec { - # Check whether a named system is macOS. - # Type: String -> Bool - # Usage: is-darwin "x86_64-linux" - # result: false - is-darwin = hasInfix "darwin"; - - # Check whether a named system is Linux. - # Type: String -> Bool - # Usage: is-linux "x86_64-linux" - # result: false - is-linux = hasInfix "linux"; - - # Get the virtual system type of a system target. - # Type: String -> String - # Usage: get-virtual-system-type "x86_64-iso" - # result: "iso" - get-virtual-system-type = target: - foldl - (result: virtual-system: - if result == "" && hasInfix virtual-system target then - virtual-system - else - result - ) - "" - virtual-systems; - - # Get structured data about all systems for a given target. - # Type: String -> [Attrs] - # Usage: get-target-systems-metadata "x86_64-linux" - # result: [ { target = "x86_64-linux"; name = "my-machine"; path = "/systems/x86_64-linux/my-machine"; } ] - get-target-systems-metadata = target: - let - systems = snowfall-lib.fs.get-directories target; - existing-systems = builtins.filter (system: builtins.pathExists "${system}/default.nix") systems; - create-system-metadata = path: { - path = "${path}/default.nix"; - # We are building flake outputs based on file contents. Nix doesn't like this - # so we have to explicitly discard the string's path context to allow us to - # use the name as a variable. - name = builtins.unsafeDiscardStringContext (builtins.baseNameOf path); - # We are building flake outputs based on file contents. Nix doesn't like this - # so we have to explicitly discard the string's path context to allow us to - # use the name as a variable. - target = builtins.unsafeDiscardStringContext (builtins.baseNameOf target); - }; - system-configurations = builtins.map create-system-metadata existing-systems; - in - system-configurations; - - # Get the system builder for a given target. - # Type: String -> Function - # Usage: get-system-builder "x86_64-iso" - # result: (args: ) - get-system-builder = target: - let - virtual-system-type = get-virtual-system-type target; - virtual-system-builder = args: - assert (assertMsg (user-inputs ? nixos-generators) "In order to create virtual systems, you must include `nixos-generators` as a flake input."); - user-inputs.nixos-generators.nixosGenerate - (args // { - format = virtual-system-type; - specialArgs = args.specialArgs // { - format = virtual-system-type; - }; - }); - darwin-system-builder = args: - assert (assertMsg (user-inputs ? darwin) "In order to create virtual systems, you must include `darwin` as a flake input."); - user-inputs.darwin.lib.darwinSystem ((builtins.removeAttrs args [ "system" ]) // { - specialArgs = args.specialArgs // { - format = "darwin"; - }; - }); - linux-system-builder = args: - core-inputs.nixpkgs.lib.nixosSystem - (args // { - specialArgs = args.specialArgs // { - format = "linux"; - }; - }); - in - if virtual-system-type != "" then - virtual-system-builder - else if is-darwin target then - darwin-system-builder - else - linux-system-builder; - - # Get the flake output attribute for a system target. - # Type: String -> String - # Usage: get-system-output "aarch64-darwin" - # result: "darwinConfigurations" - get-system-output = target: - let - virtual-system-type = get-virtual-system-type target; - in - if virtual-system-type != "" then - "${virtual-system-type}Configurations" - else if is-darwin target then - "darwinConfigurations" - else - "nixosConfigurations"; - - # Get the resolved (non-virtual) system target. - # Type: String -> String - # Usage: get-resolved-system-target "x86_64-iso" - # result: "x86_64-linux" - get-resolved-system-target = target: - let - virtual-system-type = get-virtual-system-type target; - in - if virtual-system-type != "" then - builtins.replaceStrings [ virtual-system-type ] [ "linux" ] target - else - target; - - # Create a system. - # Type: Attrs -> Attrs - # Usage: create-system { path = ./systems/my-system; } - # result: - create-system = - { target ? "x86_64-linux" - , system ? get-resolved-system-target target - , path - , name ? builtins.unsafeDiscardStringContext (get-inferred-system-name path) - , modules ? [ ] - , specialArgs ? { } - , channelName ? "nixpkgs" - , builder ? get-system-builder target - , output ? get-system-output target - , systems ? { } - }: - let - lib = snowfall-lib.internal.system-lib; - in - { - inherit channelName system builder output; - - modules = [ path ] ++ modules; - - specialArgs = specialArgs // { - inherit system name systems lib; - - inputs = snowfall-lib.flake.without-src user-inputs; - }; - }; - - # Create all available systems. - # Type: Attrs -> Attrs - # Usage: create-systems { hosts.my-host.specialArgs.x = true; modules = [ my-shared-module ]; } - # result: { my-host = ; } - create-systems = systems: - let - targets = snowfall-lib.fs.get-directories user-systems-root; - target-systems-metadata = concatMap get-target-systems-metadata targets; - user-modules = snowfall-lib.fs.get-default-nix-files-recursive user-modules-root; - create-system' = created-systems: system-metadata: - let - overrides = systems.hosts.${system-metadata.name} or { }; - in - { - ${system-metadata.name} = create-system (overrides // system-metadata // { - systems = created-systems; - modules = user-modules ++ (overrides.modules or [ ]) ++ (systems.modules or [ ]); - }); - }; - created-systems = fix (created-systems: - foldl - (systems: system-metadata: - systems // (create-system' created-systems system-metadata) - ) - { } - target-systems-metadata - ); - in - created-systems; - }; -} diff --git a/lib/template/default.nix b/lib/template/default.nix deleted file mode 100644 index 6f2a47b..0000000 --- a/lib/template/default.nix +++ /dev/null @@ -1,50 +0,0 @@ -{ core-inputs -, user-inputs -, snowfall-lib -}: - -let - inherit (builtins) baseNameOf; - inherit (core-inputs.nixpkgs.lib) assertMsg foldl; - - user-templates-root = snowfall-lib.fs.get-file "templates"; -in -{ - template = { - # Create flake templates. - # Type: Attrs -> Attrs - # Usage: create-templates { src = ./my-templates; overrides = { inherit another-template; default = "my-template"; }; } - # result: { another-template = ...; my-template = ...; default = ...; } - create-templates = - { src ? user-templates-root - , overrides ? { } - }: - let - user-templates = snowfall-lib.fs.get-directories src; - create-template-metadata = template: { - name = builtins.unsafeDiscardStringContext (baseNameOf template); - path = template; - }; - templates-metadata = builtins.map create-template-metadata user-templates; - merge-templates = templates: metadata: - templates // { - ${metadata.name} = (overrides.${metadata.name} or { }) // { - inherit (metadata) path; - }; - }; - templates-without-default = foldl merge-templates { } templates-metadata; - default-template = - if overrides.default or null == null then - { } - else if builtins.isAttrs overrides.default then - { default = overrides.default; } - else if templates-without-default.${overrides.default} or null != null then - { default = templates-without-default.${overrides.default}; } - else - { }; - overrides-without-default = builtins.removeAttrs overrides [ "default" ]; - templates = templates-without-default // default-template // overrides-without-default; - in - templates; - }; -} diff --git a/modules/darwin/user/default.nix b/modules/darwin/user/default.nix new file mode 100644 index 0000000..28f0931 --- /dev/null +++ b/modules/darwin/user/default.nix @@ -0,0 +1,82 @@ +{ pkgs, lib, options, config, inputs, ... }: + +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; + }; + }); +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 = { + enable = mkOption { + type = types.bool; + default = true; + }; + + path = mkOption { + type = types.str; + default = "/Users/${name}"; + }; + + config = mkOption { + # HM-compatible options taken from: + # https://github.com/nix-community/home-manager/blob/0ee5ab611dc1fbb5180bd7d88d2aeb7841a4d179/nixos/common.nix#L14 + type = types.submoduleWith { + specialArgs = { + osConfig = config; + modulesPath = "${inputs.home-manager}/modules"; + } // config.home-manager.extraSpecialArgs; + modules = [ + ({ lib, modulesPath, ... }: { + imports = import "${modulesPath}/modules.nix" { + inherit pkgs lib; + useNixpkgsModule = !config.home-manager.useGlobalPkgs; + }; + + config = { + submoduleSupport.enable = true; + submoduleSupport.externalPackageInstall = cfg.useUserPackages; + + home.username = config.users.users.${name}.name; + home.homeDirectory = config.users.users.${name}.home; + + nix.package = config.nix.package; + }; + }) + ] ++ config.home-manager.sharedModules; + }; + }; + }; + }; + })); + }; + }; + + config = { + users.users = (foldl create-system-users { } (user-names)); + }; +} diff --git a/modules/home/user/default.nix b/modules/home/user/default.nix new file mode 100644 index 0000000..7c7c93a --- /dev/null +++ b/modules/home/user/default.nix @@ -0,0 +1,52 @@ +inputs@{ pkgs, lib, options, config, ... }: + +let + inherit (lib) types mkOption mkIf mkDefault; + + 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; + + has-user-name = (cfg.user.name or null) != 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 +{ + options.snowfallorg = { + user = { + enable = mkOption { + type = types.bool; + default = false; + 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.enable { + home = { + username = mkIf has-user-name (mkDefault cfg.user.name); + homeDirectory = mkIf has-user-name (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..e8483f1 --- /dev/null +++ b/modules/nixos/user/default.nix @@ -0,0 +1,100 @@ +args@{ pkgs, lib, options, config, ... }: + +let + inherit (lib) types mkOption mkDefault foldl optionalAttrs optional; + + cfg = config.snowfallorg; + + inputs = args.inputs or { }; + + 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 name; + + home = mkDefault user.home.path; + group = mkDefault "users"; + + extraGroups = optional user.admin "wheel"; + }; + }); + +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; + }; + + admin = mkOption { + description = "Whether the user should be added to the wheel group."; + type = types.bool; + default = true; + }; + + home = { + enable = mkOption { + type = types.bool; + default = true; + }; + + path = mkOption { + type = types.str; + default = "/home/${name}"; + }; + + config = mkOption { + # HM-compatible options taken from: + # https://github.com/nix-community/home-manager/blob/0ee5ab611dc1fbb5180bd7d88d2aeb7841a4d179/nixos/common.nix#L14 + # @NOTE(jakehamilton): This has been adapted to support documentation generation without + # having home-manager options fully declared. + type = types.submoduleWith { + specialArgs = { + osConfig = config; + modulesPath = "${inputs.home-manager or "/"}/modules"; + } // (config.home-manager.extraSpecialArgs or { }); + modules = [ + ({ lib, modulesPath, ... }: + if inputs ? home-manager then { + imports = import "${modulesPath}/modules.nix" { + inherit pkgs lib; + useNixpkgsModule = !(config.home-manager.useGlobalPkgs or false); + }; + + config = { + submoduleSupport.enable = true; + submoduleSupport.externalPackageInstall = cfg.useUserPackages; + + home.username = config.users.users.${name}.name; + home.homeDirectory = config.users.users.${name}.home; + + nix.package = config.nix.package; + }; + } else { }) + ] ++ (config.home-manager.sharedModules or [ ]); + }; + }; + }; + }; + })); + }; + }; + + config = { + users.users = (foldl (create-system-users) { } (user-names)); + }; +} diff --git a/snowfall-lib/attrs/default.nix b/snowfall-lib/attrs/default.nix new file mode 100644 index 0000000..ea5dbee --- /dev/null +++ b/snowfall-lib/attrs/default.nix @@ -0,0 +1,84 @@ +{ core-inputs +, user-inputs +, snowfall-lib +, snowfall-config +}: + +let + inherit (core-inputs.nixpkgs.lib) + assertMsg + mapAttrsToList + mapAttrs + flatten + foldl + recursiveUpdate + mergeAttrs + isDerivation; +in +{ + attrs = { + ## Map and flatten an attribute set into a list. + ## Example Usage: + ## ```nix + ## map-concat-attrs-to-list (name: value: [name value]) { x = 1; y = 2; } + ## ``` + ## Result: + ## ```nix + ## [ "x" 1 "y" 2 ] + ## ``` + #@ (a -> b -> [c]) -> Attrs -> [c] + map-concat-attrs-to-list = f: attrs: + flatten (mapAttrsToList f attrs); + + ## Recursively merge a list of attribute sets. + ## Example Usage: + ## ```nix + ## merge-deep [{ x = 1; } { x = 2; }] + ## ``` + ## Result: + ## ```nix + ## { x = 2; } + ## ``` + #@ [Attrs] -> Attrs + merge-deep = foldl recursiveUpdate { }; + + ## Merge the root of a list of attribute sets. + ## Example Usage: + ## ```nix + ## merge-shallow [{ x = 1; } { x = 2; }] + ## ``` + ## Result: + ## ```nix + ## { x = 2; } + ## ``` + #@ [Attrs] -> Attrs + merge-shallow = foldl mergeAttrs { }; + + ## Merge shallow for packages, but allow one deeper layer of attribute sets. + ## Example Usage: + ## ```nix + ## merge-shallow-packages [ { inherit (pkgs) vim; some.value = true; } { some.value = false; } ] + ## ``` + ## Result: + ## ```nix + ## { vim = ...; some.value = false; } + ## ``` + #@ [Attrs] -> Attrs + merge-shallow-packages = items: + foldl + (result: item: + result // (mapAttrs + (name: value: + if isDerivation value then + value + else if builtins.isAttrs value then + (result.${name} or { }) // value + else + value + ) + item) + ) + { } + items; + }; +} diff --git a/lib/default.nix b/snowfall-lib/default.nix similarity index 74% rename from lib/default.nix rename to snowfall-lib/default.nix index fd22b10..4b4b70d 100644 --- a/lib/default.nix +++ b/snowfall-lib/default.nix @@ -6,9 +6,20 @@ core-inputs: user-options: let + raw-snowfall-config = user-options.snowfall or { }; + snowfall-config = raw-snowfall-config // { + src = user-options.src; + root = raw-snowfall-config.root or user-options.src; + namespace = raw-snowfall-config.namespace or "internal"; + meta = { + name = raw-snowfall-config.meta.name or null; + title = raw-snowfall-config.meta.title or null; + }; + }; + user-inputs = user-options.inputs // { src = user-options.src; }; - inherit (core-inputs.nixpkgs.lib) assertMsg fix filterAttrs mergeAttrs fold recursiveUpdate; + inherit (core-inputs.nixpkgs.lib) assertMsg fix filterAttrs mergeAttrs fold recursiveUpdate callPackageWith; # Recursively merge a list of attribute sets. # Type: [Attrs] -> Attrs @@ -48,7 +59,9 @@ let core-inputs-libs = get-libs (without-self core-inputs); user-inputs-libs = get-libs (without-self user-inputs); - snowfall-lib-root = "${core-inputs.src}/lib"; + # @NOTE(jakehamilton): This root is different to accomodate the creation + # of a fake user-lib in order to run documentation on this flake. + snowfall-lib-root = "${core-inputs.src}/snowfall-lib"; snowfall-lib-dirs = let files = builtins.readDir snowfall-lib-root; @@ -60,7 +73,7 @@ let snowfall-lib = fix (snowfall-lib: let attrs = { - inherit snowfall-lib core-inputs user-inputs; + inherit snowfall-lib snowfall-config core-inputs user-inputs; }; libs = builtins.map (dir: import "${snowfall-lib-root}/${dir}" attrs) @@ -87,10 +100,10 @@ let attrs = { inherit (user-options) inputs; snowfall-inputs = core-inputs; - lib = merge-shallow [ base-lib user-lib ]; + lib = merge-shallow [ base-lib { ${snowfall-config.namespace} = user-lib; } ]; }; libs = builtins.map - (path: import path attrs) + (path: callPackageWith attrs path { }) user-lib-modules; in merge-deep libs @@ -104,6 +117,6 @@ let user-inputs-has-self = builtins.elem "self" (builtins.attrNames user-inputs); user-inputs-has-src = builtins.elem "src" (builtins.attrNames user-inputs); in - assert (assertMsg (user-inputs-has-self) "Missing attribute `self` for mkLib."); - assert (assertMsg (user-inputs-has-src) "Missing attribute `src` for mkLib."); - lib +assert (assertMsg (user-inputs-has-self) "Missing attribute `self` for mkLib."); +assert (assertMsg (user-inputs-has-src) "Missing attribute `src` for mkLib."); +lib diff --git a/snowfall-lib/flake/default.nix b/snowfall-lib/flake/default.nix new file mode 100644 index 0000000..afd037e --- /dev/null +++ b/snowfall-lib/flake/default.nix @@ -0,0 +1,196 @@ +{ core-inputs +, user-inputs +, snowfall-lib +, snowfall-config +}: + +let + inherit (core-inputs.nixpkgs.lib) assertMsg foldl filterAttrs const; +in +rec { + flake = rec { + ## Remove the `self` attribute from an attribute set. + ## Example Usage: + ## ```nix + ## without-self { self = {}; x = true; } + ## ``` + ## Result: + ## ```nix + ## { x = true; } + ## ``` + #@ Attrs -> Attrs + without-self = flake-inputs: builtins.removeAttrs flake-inputs [ "self" ]; + + ## Remove the `src` attribute from an attribute set. + ## Example Usage: + ## ```nix + ## without-src { src = ./.; x = true; } + ## ``` + ## Result: + ## ```nix + ## { x = true; } + ## ``` + #@ Attrs -> Attrs + without-src = flake-inputs: builtins.removeAttrs flake-inputs [ "src" ]; + + ## Remove the `src` and `self` attributes from an attribute set. + ## Example Usage: + ## ```nix + ## without-snowfall-inputs { self = {}; src = ./.; x = true; } + ## ``` + ## Result: + ## ```nix + ## { x = true; } + ## ``` + #@ Attrs -> Attrs + without-snowfall-inputs = snowfall-lib.fp.compose without-self without-src; + + ## Remove Snowfall-specific attributes so the rest can be safely passed to flake-utils-plus. + ## Example Usage: + ## ```nix + ## without-snowfall-options { src = ./.; x = true; } + ## ``` + ## Result: + ## ```nix + ## { x = true; } + ## ``` + #@ Attrs -> Attrs + without-snowfall-options = flake-options: + builtins.removeAttrs + flake-options + [ + "systems" + "modules" + "overlays" + "packages" + "outputs-builder" + "outputsBuilder" + "packagesPrefix" + "hosts" + "channels-config" + "templates" + "package-namespace" + "alias" + "snowfall" + ]; + + ## Transform an attribute set of inputs into an attribute set where the values are the inputs' `lib` attribute. Entries without a `lib` attribute are removed. + ## Example Usage: + ## ```nix + ## get-lib { x = nixpkgs; y = {}; } + ## ``` + ## Result: + ## ```nix + ## { x = nixpkgs.lib; } + ## ``` + #@ Attrs -> Attrs + get-libs = attrs: + let + # @PERF(jakehamilton): Replace filter+map with a fold. + attrs-with-libs = filterAttrs + (name: value: builtins.isAttrs (value.lib or null)) + attrs; + libs = + builtins.mapAttrs (name: input: input.lib) attrs-with-libs; + in + libs; + }; + + mkFlake = full-flake-options: + let + package-namespace = full-flake-options.package-namespace or snowfall-config.namespace or "internal"; + custom-flake-options = flake.without-snowfall-options full-flake-options; + alias = full-flake-options.alias or { }; + homes = snowfall-lib.home.create-homes (full-flake-options.homes or { }); + systems = snowfall-lib.system.create-systems { + systems = (full-flake-options.systems or { }); + homes = (full-flake-options.homes or { }); + }; + hosts = snowfall-lib.attrs.merge-shallow [ (full-flake-options.systems.hosts or { }) systems homes ]; + templates = snowfall-lib.template.create-templates { + overrides = (full-flake-options.templates or { }); + alias = alias.templates or { }; + }; + nixos-modules = snowfall-lib.module.create-modules { + src = snowfall-lib.fs.get-snowfall-file "modules/nixos"; + overrides = (full-flake-options.modules.nixos or { }); + alias = alias.modules.nixos or { }; + }; + darwin-modules = snowfall-lib.module.create-modules { + src = snowfall-lib.fs.get-snowfall-file "modules/darwin"; + overrides = (full-flake-options.modules.darwin or { }); + alias = alias.modules.darwin or { }; + }; + home-modules = snowfall-lib.module.create-modules { + src = snowfall-lib.fs.get-snowfall-file "modules/home"; + overrides = (full-flake-options.modules.home or { }); + alias = alias.modules.home or { }; + }; + overlays = snowfall-lib.overlay.create-overlays { + inherit package-namespace; + extra-overlays = full-flake-options.extra-exported-overlays or { }; + }; + + outputs-builder = channels: + let + user-outputs-builder = + full-flake-options.outputs-builder + or full-flake-options.outputsBuilder + or (const { }); + user-outputs = user-outputs-builder channels; + packages = snowfall-lib.package.create-packages { + inherit channels package-namespace; + overrides = (full-flake-options.packages or { }) // (user-outputs.packages or { }); + alias = alias.packages or { }; + }; + shells = snowfall-lib.shell.create-shells { + inherit channels; + overrides = (full-flake-options.shells or { }) // (user-outputs.devShells or { }); + alias = alias.shells or { }; + }; + + outputs = { + inherit packages; + + devShells = shells; + }; + in + snowfall-lib.attrs.merge-deep [ user-outputs outputs ]; + + flake-options = custom-flake-options // { + inherit hosts templates; + inherit (user-inputs) self; + + lib = snowfall-lib.internal.user-lib; + inputs = snowfall-lib.flake.without-src user-inputs; + + nixosModules = nixos-modules; + darwinModules = darwin-modules; + homeModules = home-modules; + + channelsConfig = full-flake-options.channels-config or { }; + + channels.nixpkgs.overlaysBuilder = snowfall-lib.overlay.create-overlays-builder { + inherit package-namespace; + extra-overlays = full-flake-options.overlays or [ ]; + }; + + outputsBuilder = outputs-builder; + + _snowfall = { + config = snowfall-config; + raw-config = full-flake-options.snowfall or { }; + user-lib = snowfall-lib.internal.user-lib; + }; + }; + + flake-utils-plus-outputs = + core-inputs.flake-utils-plus.lib.mkFlake flake-options; + + flake-outputs = + flake-utils-plus-outputs // { + inherit overlays; + }; + in + flake-outputs; +} diff --git a/snowfall-lib/fp/default.nix b/snowfall-lib/fp/default.nix new file mode 100644 index 0000000..04dd03c --- /dev/null +++ b/snowfall-lib/fp/default.nix @@ -0,0 +1,61 @@ +{ core-inputs +, user-inputs +, snowfall-lib +, snowfall-config +}: + +let + inherit (builtins) baseNameOf dirOf; + inherit (core-inputs.nixpkgs.lib) id foldr flip; +in +{ + fp = rec { + ## Compose two functions. + ## Example Usage: + ## ```nix + ## compose add-two add-one + ## ``` + ## Result: + ## ```nix + ## (x: add-two (add-one x)) + ## ``` + #@ (b -> c) -> (a -> b) -> a -> c + compose = f: g: x: f (g x); + + ## Compose many functions. + ## Example Usage: + ## ```nix + ## compose-all [ add-two add-one ] + ## ``` + ## Result: + ## ```nix + ## (x: add-two (add-one x)) + ## ``` + #@ [(x -> y)] -> a -> b + compose-all = foldr compose id; + + ## Call a function with an argument. + ## Example Usage: + ## ```nix + ## call (x: x + 1) 0 + ## ``` + ## Result: + ## ```nix + ## 1 + ## ``` + #@ (a -> b) -> a -> b + call = f: x: f x; + + ## Apply an argument to a function. + ## Example Usage: + ## ```nix + ## apply 0 (x: x + 1) + ## ``` + ## Result: + ## ```nix + ## 1 + ## ``` + #@ a -> (a -> b) -> b + apply = flip call; + }; +} diff --git a/snowfall-lib/fs/default.nix b/snowfall-lib/fs/default.nix new file mode 100644 index 0000000..3877408 --- /dev/null +++ b/snowfall-lib/fs/default.nix @@ -0,0 +1,244 @@ +{ core-inputs +, user-inputs +, snowfall-lib +, snowfall-config +}: + +let + inherit (builtins) readDir pathExists; + inherit (core-inputs) flake-utils-plus; + inherit (core-inputs.nixpkgs.lib) assertMsg filterAttrs mapAttrsToList flatten; + + file-name-regex = "(.*)\\.(.*)$"; +in +{ + fs = rec { + ## Matchers for file kinds. These are often used with `readDir`. + ## Example Usage: + ## ```nix + ## is-file-kind "directory" + ## ``` + ## Result: + ## ```nix + ## false + ## ``` + #@ String -> Bool + is-file-kind = kind: kind == "regular"; + is-symlink-kind = kind: kind == "symlink"; + is-directory-kind = kind: kind == "directory"; + is-unknown-kind = kind: kind == "unknown"; + + ## Get a file path relative to the user's flake. + ## Example Usage: + ## ```nix + ## get-file "systems" + ## ``` + ## Result: + ## ```nix + ## "/user-source/systems" + ## ``` + #@ Path -> Path + get-file = path: "${user-inputs.src}/${path}"; + + ## Get a file path relative to the user's snowfall directory. + ## Example Usage: + ## ```nix + ## get-snowfall-file "systems" + ## ``` + ## Result: + ## ```nix + ## "/user-source/snowfall-dir/systems" + ## ``` + #@ Path -> Path + get-snowfall-file = path: "${snowfall-config.root}/${path}"; + + ## Get a file path relative to the this flake. + ## Example Usage: + ## ```nix + ## get-file "systems" + ## ``` + ## Result: + ## ```nix + ## "/user-source/systems" + ## ``` + #@ Path -> Path + internal-get-file = path: "${core-inputs.src}/${path}"; + + ## Safely read from a directory if it exists. + ## Example Usage: + ## ```nix + ## safe-read-directory ./some/path + ## ``` + ## Result: + ## ```nix + ## { "my-file.txt" = "regular"; } + ## ``` + #@ Path -> Attrs + safe-read-directory = path: + if pathExists path then + readDir path + else + { }; + + ## Get directories at a given path. + ## Example Usage: + ## ```nix + ## get-directories ./something + ## ``` + ## Result: + ## ```nix + ## [ "./something/a-directory" ] + ## ``` + #@ Path -> [Path] + get-directories = path: + let + entries = safe-read-directory path; + filtered-entries = filterAttrs (name: kind: is-directory-kind kind) entries; + in + mapAttrsToList (name: kind: "${path}/${name}") filtered-entries; + + ## Get files at a given path. + ## Example Usage: + ## ```nix + ## get-files ./something + ## ``` + ## Result: + ## ```nix + ## [ "./something/a-file" ] + ## ``` + #@ Path -> [Path] + get-files = path: + let + entries = safe-read-directory path; + filtered-entries = filterAttrs (name: kind: is-file-kind kind) entries; + in + mapAttrsToList (name: kind: "${path}/${name}") filtered-entries; + + ## Get files at a given path, traversing any directories within. + ## Example Usage: + ## ```nix + ## get-files-recursive ./something + ## ``` + ## Result: + ## ```nix + ## [ "./something/some-directory/a-file" ] + ## ``` + #@ Path -> [Path] + get-files-recursive = path: + let + entries = safe-read-directory path; + filtered-entries = + filterAttrs + (name: kind: (is-file-kind kind) || (is-directory-kind kind)) + entries; + map-file = name: kind: + let + path' = "${path}/${name}"; + in + if is-directory-kind kind then + get-files-recursive path' + else + path'; + files = snowfall-lib.attrs.map-concat-attrs-to-list + map-file + filtered-entries; + in + files; + + ## Get nix files at a given path. + ## Example Usage: + ## ```nix + ## get-nix-files "./something" + ## ``` + ## Result: + ## ```nix + ## [ "./something/a.nix" ] + ## ``` + #@ Path -> [Path] + get-nix-files = path: + builtins.filter + (snowfall-lib.path.has-file-extension "nix") + (get-files path); + + ## Get nix files at a given path, traversing any directories within. + ## Example Usage: + ## ```nix + ## get-nix-files "./something" + ## ``` + ## Result: + ## ```nix + ## [ "./something/a.nix" ] + ## ``` + #@ Path -> [Path] + get-nix-files-recursive = path: + builtins.filter + (snowfall-lib.path.has-file-extension "nix") + (get-files-recursive path); + + ## Get nix files at a given path named "default.nix". + ## Example Usage: + ## ```nix + ## get-default-nix-files "./something" + ## ``` + ## Result: + ## ```nix + ## [ "./something/default.nix" ] + ## ``` + #@ Path -> [Path] + get-default-nix-files = path: + builtins.filter + (name: builtins.baseNameOf name == "default.nix") + (get-files path); + + ## Get nix files at a given path named "default.nix", traversing any directories within. + ## Example Usage: + ## ```nix + ## get-default-nix-files-recursive "./something" + ## ``` + ## Result: + ## ```nix + ## [ "./something/some-directory/default.nix" ] + ## ``` + #@ Path -> [Path] + get-default-nix-files-recursive = path: + builtins.filter + (name: builtins.baseNameOf name == "default.nix") + (get-files-recursive path); + + ## Get nix files at a given path not named "default.nix". + ## Example Usage: + ## ```nix + ## get-non-default-nix-files "./something" + ## ``` + ## Result: + ## ```nix + ## [ "./something/a.nix" ] + ## ``` + #@ Path -> [Path] + get-non-default-nix-files = path: + builtins.filter + (name: + (snowfall-lib.path.has-file-extension "nix" name) + && (builtins.baseNameOf name != "default.nix") + ) + (get-files path); + + ## Get nix files at a given path not named "default.nix", traversing any directories within. + ## Example Usage: + ## ```nix + ## get-non-default-nix-files-recursive "./something" + ## ``` + ## Result: + ## ```nix + ## [ "./something/some-directory/a.nix" ] + ## ``` + #@ Path -> [Path] + get-non-default-nix-files-recursive = path: + builtins.filter + (name: + (snowfall-lib.path.has-file-extension "nix" name) + && (builtins.baseNameOf name != "default.nix") + ) + (get-files-recursive path); + }; +} diff --git a/snowfall-lib/home/default.nix b/snowfall-lib/home/default.nix new file mode 100644 index 0000000..e8d1844 --- /dev/null +++ b/snowfall-lib/home/default.nix @@ -0,0 +1,350 @@ +{ core-inputs +, user-inputs +, snowfall-lib +, snowfall-config +}: + +let + inherit (core-inputs.nixpkgs.lib) + assertMsg + foldl + head + tail + concatMap + optionalAttrs + optional + mkIf + filterAttrs + mapAttrs' + mkMerge + mapAttrsToList + optionals + mkDefault + mkAliasDefinitions + mkAliasAndWrapDefinitions + mkOption + types; + + user-homes-root = snowfall-lib.fs.get-snowfall-file "homes"; + user-modules-root = snowfall-lib.fs.get-snowfall-file "modules"; +in +{ + home = rec { + # Modules in home-manager expect `hm` to be available directly on `lib` itself. + home-lib = + # @NOTE(jakehamilton): This prevents an error during evaluation if the input does + # not exist. + if user-inputs ? home-manager then + snowfall-lib.internal.system-lib.extend + (final: prev: + # @NOTE(jakehamilton): This order is important, this library's extend and other utilities must write + # _over_ the original `system-lib`. + snowfall-lib.internal.system-lib + // prev + // { + hm = snowfall-lib.internal.system-lib.home-manager.hm; + }) + else + { }; + + ## Get the user and host from a combined string. + ## Example Usage: + ## ```nix + ## split-user-and-host "myuser@myhost" + ## ``` + ## Result: + ## ```nix + ## { user = "myuser"; host = "myhost"; } + ## ``` + #@ String -> Attrs + split-user-and-host = target: + let + raw-name-parts = builtins.split "@" target; + name-parts = builtins.filter builtins.isString raw-name-parts; + + user = builtins.elemAt name-parts 0; + host = + if builtins.length name-parts > 1 then + builtins.elemAt name-parts 1 + else + ""; + in + { + inherit user host; + }; + + + ## Create a home. + ## Example Usage: + ## ```nix + ## create-home { path = ./homes/my-home; } + ## ``` + ## Result: + ## ```nix + ## + ## ``` + #@ Attrs -> Attrs + create-home = + { path + , name ? builtins.unsafeDiscardStringContext (snowfall-lib.system.get-inferred-system-name path) + , modules ? [ ] + , specialArgs ? { } + , channelName ? "nixpkgs" + , system ? "x86_64-linux" + }: + let + user-metadata = split-user-and-host name; + + # @NOTE(jakehamilton): home-manager has trouble with `pkgs` recursion if it isn't passed in here. + pkgs = user-inputs.self.pkgs.${system}.${channelName} // { lib = home-lib; }; + lib = home-lib; + in + assert assertMsg (user-inputs ? home-manager) "In order to create home-manager configurations, you must include `home-manager` as a flake input."; + assert assertMsg (user-metadata.host != "") "Snowfall Lib homes must be named with the format: user@system"; + { + inherit channelName system; + + output = "homeConfigurations"; + + modules = [ + path + ../../modules/home/user/default.nix + ] ++ modules; + + specialArgs = { + inherit name; + inherit (user-metadata) user host; + + format = "home"; + + inputs = snowfall-lib.flake.without-src user-inputs; + + # @NOTE(jakehamilton): home-manager has trouble with `pkgs` recursion if it isn't passed in here. + inherit pkgs lib; + }; + + builder = args: + user-inputs.home-manager.lib.homeManagerConfiguration + ((builtins.removeAttrs args [ "system" "specialArgs" ]) // { + inherit pkgs lib; + + modules = args.modules ++ [ + (module-args: import ./nix-registry-module.nix (module-args // { + inherit user-inputs core-inputs; + })) + ({ + snowfallorg.user = { + name = mkDefault user-metadata.user; + enable = mkDefault true; + }; + }) + ]; + + extraSpecialArgs = specialArgs // args.specialArgs; + }); + }; + + ## Get structured data about all homes for a given target. + ## Example Usage: + ## ```nix + ## get-target-homes-metadata ./homes + ## ``` + ## Result: + ## ```nix + ## [ { system = "x86_64-linux"; name = "my-home"; path = "/homes/x86_64-linux/my-home";} ] + ## ``` + #@ String -> [Attrs] + get-target-homes-metadata = target: + let + homes = snowfall-lib.fs.get-directories target; + existing-homes = builtins.filter (home: builtins.pathExists "${home}/default.nix") homes; + create-home-metadata = path: { + path = "${path}/default.nix"; + # We are building flake outputs based on file contents. Nix doesn't like this + # so we have to explicitly discard the string's path context to allow us to + # use the name as a variable. + name = builtins.unsafeDiscardStringContext (builtins.baseNameOf path); + # We are building flake outputs based on file contents. Nix doesn't like this + # so we have to explicitly discard the string's path context to allow us to + # use the name as a variable. + system = builtins.unsafeDiscardStringContext (builtins.baseNameOf target); + }; + home-configurations = builtins.map create-home-metadata existing-homes; + in + home-configurations; + + ## Create all available homes. + ## Example Usage: + ## ```nix + ## create-homes { users."my-user@my-system".specialArgs.x = true; modules = [ my-shared-module ]; } + ## ``` + ## Result: + ## ```nix + ## { "my-user@my-system" = ; } + ## ``` + #@ Attrs -> Attrs + create-homes = homes: + let + targets = snowfall-lib.fs.get-directories user-homes-root; + target-homes-metadata = concatMap get-target-homes-metadata targets; + + user-home-modules = snowfall-lib.module.create-modules { + src = "${user-modules-root}/home"; + }; + + user-home-modules-list = mapAttrsToList + (module-path: module: args@{ pkgs, ... }: (module args) // { + _file = "${user-homes-root}/${module-path}/default.nix"; + }) + user-home-modules; + + create-home' = home-metadata: + let + inherit (home-metadata) name; + overrides = homes.users.${name} or { }; + in + { + "${name}" = create-home (overrides // home-metadata // { + modules = user-home-modules-list ++ (homes.users.${name}.modules or [ ]) ++ (homes.modules or [ ]); + }); + }; + + created-homes = foldl (homes: home-metadata: homes // (create-home' home-metadata)) { } target-homes-metadata; + in + created-homes; + + ## Create system modules for home-manager integration. + ## Example Usage: + ## ```nix + ## create-home-system-modules { users."my-user@my-system".specialArgs.x = true; modules = [ my-shared-module ]; } + ## ``` + ## Result: + ## ```nix + ## [Module] + ## ``` + #@ Attrs -> [Module] + create-home-system-modules = users: + let + created-users = create-homes users; + user-home-modules = snowfall-lib.module.create-modules { + src = "${user-modules-root}/home"; + }; + + shared-modules = mapAttrsToList + (module-path: module: { + _file = "${user-modules-root}/home/${module-path}/default.nix"; + + config = { + home-manager.sharedModules = [ module ]; + }; + }) + user-home-modules; + + snowfall-user-home-module = { + _file = "virtual:snowfallorg/modules/home/user/default.nix"; + + config = { + home-manager.sharedModules = [ + ../../modules/home/user/default.nix + ]; + }; + }; + + extra-special-args-module = + args@{ config + , pkgs + , system ? pkgs.system + , target ? system + , format ? "home" + , host ? "" + , virtual ? (snowfall-lib.system.is-virtual target) + , systems ? { } + , ... + }: + { + _file = "virtual:snowfallorg/home/extra-special-args"; + + config = { + home-manager.extraSpecialArgs = { + inherit system target format virtual systems host; + + lib = home-lib; + + inputs = snowfall-lib.flake.without-src user-inputs; + }; + }; + }; + + system-modules = builtins.map + (name: + let + created-user = created-users.${name}; + user-module = head created-user.modules; + other-modules = users.users.${name}.modules or [ ]; + user-name = created-user.specialArgs.user; + in + args@{ config + , options + , pkgs + , host ? "" + , ... + }: + let + host-matches = created-user.specialArgs.host == host; + + # @NOTE(jakehamilton): To conform to the config structure of home-manager, we have to + # remap the options coming from `snowfallorg.user..home.config` since `mkAliasDefinitions` + # does not let us target options within a submodule. + wrap-user-options = user-option: + if (user-option ? "_type") && user-option._type == "merge" then + user-option // { + contents = builtins.map + (merge-entry: + merge-entry.${user-name}.home.config or { } + ) + user-option.contents; + } + else + (builtins.trace '' + ============= + Snowfall Lib: + Option value for `snowfallorg.user.${user-name}` was not detected to be merged. + + Please report the issue on GitHub with a link to your configuration so we can debug the problem: + https://github.com/snowfallorg/lib/issues/new + ============= + '') + user-option; + in + { + _file = "virtual:snowfallorg/home/user/${name}"; + + config = mkIf host-matches { + # Initialize user information. + snowfallorg.user.${user-name}.home.config = { + snowfallorg.user = { + enable = true; + name = mkDefault user-name; + }; + }; + + home-manager = { + users.${user-name} = mkAliasAndWrapDefinitions wrap-user-options options.snowfallorg.user; + + # sharedModules = other-modules ++ optional config.snowfallorg.user.${user-name}.home.enable wrapped-user-module; + sharedModules = other-modules ++ optional config.snowfallorg.user.${user-name}.home.enable user-module; + }; + }; + } + ) + (builtins.attrNames created-users); + in + [ + extra-special-args-module + snowfall-user-home-module + ] + ++ (users.modules or [ ]) + ++ shared-modules + ++ system-modules; + }; +} diff --git a/snowfall-lib/home/nix-registry-module.nix b/snowfall-lib/home/nix-registry-module.nix new file mode 100644 index 0000000..c15f9e2 --- /dev/null +++ b/snowfall-lib/home/nix-registry-module.nix @@ -0,0 +1,11 @@ +# This code is adapted from flake-utils-plus: +# https://github.com/gytis-ivaskevicius/flake-utils-plus/blob/2bf0f91643c2e5ae38c1b26893ac2927ac9bd82a/lib/options.nix +{ lib, config, user-inputs, core-inputs, ... }: + +{ + disabledModules = [ + # The module from flake-utils-plus only works on NixOS and nix-darwin. For home-manager + # to build, this module needs to be disabled. + "${core-inputs.flake-utils-plus}/lib/options.nix" + ]; +} diff --git a/lib/internal/default.nix b/snowfall-lib/internal/default.nix similarity index 81% rename from lib/internal/default.nix rename to snowfall-lib/internal/default.nix index 687fb62..e61eb73 100644 --- a/lib/internal/default.nix +++ b/snowfall-lib/internal/default.nix @@ -1,10 +1,11 @@ { core-inputs , user-inputs , snowfall-lib +, snowfall-config }: let - inherit (core-inputs.nixpkgs.lib) assertMsg fix fold filterAttrs; + inherit (core-inputs.nixpkgs.lib) assertMsg fix fold filterAttrs callPackageWith; core-inputs-libs = snowfall-lib.flake.get-libs (snowfall-lib.flake.without-self core-inputs); user-inputs-libs = snowfall-lib.flake.get-libs (snowfall-lib.flake.without-self user-inputs); @@ -27,10 +28,13 @@ let attrs = { inputs = snowfall-lib.flake.without-snowfall-inputs user-inputs; snowfall-inputs = core-inputs; - lib = snowfall-lib.attrs.merge-shallow [ base-lib user-lib ]; + lib = snowfall-lib.attrs.merge-shallow [ + base-lib + { internal = user-lib; } + ]; }; libs = builtins.map - (path: import path attrs) + (path: callPackageWith attrs path { }) user-lib-modules; in snowfall-lib.attrs.merge-deep libs @@ -38,7 +42,7 @@ let system-lib = snowfall-lib.attrs.merge-shallow [ base-lib - user-lib + { "${snowfall-config.namespace}" = user-lib; } ]; in { diff --git a/snowfall-lib/module/default.nix b/snowfall-lib/module/default.nix new file mode 100644 index 0000000..7b12e45 --- /dev/null +++ b/snowfall-lib/module/default.nix @@ -0,0 +1,86 @@ +{ core-inputs +, user-inputs +, snowfall-lib +, snowfall-config +}: + +let + inherit (builtins) baseNameOf; + inherit (core-inputs.nixpkgs.lib) assertMsg foldl mapAttrs hasPrefix; + + user-modules-root = snowfall-lib.fs.get-snowfall-file "modules"; +in +{ + module = { + ## Create flake output modules. + ## Example Usage: + ## ```nix + ## create-modules { src = ./my-modules; overrides = { inherit another-module; }; alias = { default = "another-module" }; } + ## ``` + ## Result: + ## ```nix + ## { another-module = ...; my-module = ...; default = ...; } + ## ``` + #@ Attrs -> Attrs + create-modules = + { src ? "${user-modules-root}/nixos" + , overrides ? { } + , alias ? { } + }: + let + user-modules = snowfall-lib.fs.get-default-nix-files-recursive src; + create-module-metadata = module: { + name = + let + path-name = builtins.replaceStrings [ src "/default.nix" ] [ "" "" ] (builtins.unsafeDiscardStringContext module); + in + if hasPrefix "/" path-name then + builtins.substring 1 ((builtins.stringLength path-name) - 1) path-name + else + path-name; + path = module; + }; + modules-metadata = builtins.map create-module-metadata user-modules; + merge-modules = modules: metadata: + modules // { + # @NOTE(jakehamilton): home-manager *requires* modules to specify named arguments or it will not + # pass values in. For this reason we must specify things like `pkgs` as a named attribute. + ${metadata.name} = args@{ pkgs, ... }: + let + system = args.system or args.pkgs.system; + target = args.target or system; + + format = + let + virtual-system-type = snowfall-lib.system.get-virtual-system-type target; + in + if virtual-system-type != "" then + virtual-system-type + else if snowfall-lib.system.is-darwin target then + "darwin" + else + "linux"; + + # Replicates the specialArgs from Snowfall Lib's system builder. + modified-args = args // { + inherit system target format; + virtual = args.virtual or (snowfall-lib.system.get-virtual-system-type target != ""); + systems = args.systems or { }; + + + lib = snowfall-lib.internal.system-lib; + pkgs = user-inputs.self.pkgs.${system}.nixpkgs; + + inputs = snowfall-lib.flake.without-src user-inputs; + }; + user-module = import metadata.path modified-args; + in + user-module // { _file = metadata.path; }; + }; + modules-without-aliases = foldl merge-modules { } modules-metadata; + aliased-modules = mapAttrs (name: value: modules-without-aliases.${value}) alias; + modules = modules-without-aliases // aliased-modules // overrides; + in + modules; + }; +} diff --git a/lib/overlay/default.nix b/snowfall-lib/overlay/default.nix similarity index 67% rename from lib/overlay/default.nix rename to snowfall-lib/overlay/default.nix index d8edf25..f1fd589 100644 --- a/lib/overlay/default.nix +++ b/snowfall-lib/overlay/default.nix @@ -1,23 +1,30 @@ { core-inputs , user-inputs , snowfall-lib +, snowfall-config }: let inherit (core-inputs.nixpkgs.lib) assertMsg foldl concatStringsSep; - user-overlays-root = snowfall-lib.fs.get-file "overlays"; - user-packages-root = snowfall-lib.fs.get-file "packages"; + user-overlays-root = snowfall-lib.fs.get-snowfall-file "overlays"; + user-packages-root = snowfall-lib.fs.get-snowfall-file "packages"; in { overlay = { - # Create a flake-utils-plus overlays builder. - # Type: Attrs -> Attrs -> [(a -> b -> c)] - # Usage: create-overlays { src = ./my-overlays; overlay-package-namespace = "my-packages"; } - # result: (channels: [ ... ]) + ## Create a flake-utils-plus overlays builder. + ## Example Usage: + ## ```nix + ## create-overlays { src = ./my-overlays; package-namespace = "my-packages"; } + ## ``` + ## Result: + ## ```nix + ## (channels: [ ... ]) + ## ``` + #@ Attrs -> Attrs -> [(a -> b -> c)] create-overlays-builder = { src ? user-overlays-root - , overlay-package-namespace ? null + , package-namespace ? "internal" , extra-overlays ? [ ] }: channels: let @@ -29,31 +36,32 @@ in pkgs = final; channels = channels; }; - user-packages-without-default = builtins.removeAttrs - (user-packages) [ "default" ]; in - if overlay-package-namespace == null then - user-packages-without-default - else - { - ${overlay-package-namespace} = - (prev.${overlay-package-namespace} or { }) - // user-packages-without-default; - }; - overlays = [ user-packages-overlay ] ++ extra-overlays ++ (builtins.map create-overlay user-overlays); + { + ${package-namespace} = + (prev.${package-namespace} or { }) + // user-packages; + }; + overlays = + [ user-packages-overlay ] ++ extra-overlays ++ (builtins.map create-overlay user-overlays); in overlays; - # Create exported overlays from the user flake. - # Adapted from flake-utils-plus: - # https://github.com/gytis-ivaskevicius/flake-utils-plus/blob/2bf0f91643c2e5ae38c1b26893ac2927ac9bd82a/lib/exportOverlays.nix - # Type: Attrs -> Attrs - # Usage: create-overlays { src = ./my-overlays; packages-src = ./my-packages; overlay-package-namespace = "my-namespace"; extra-overlays = {}; } - # result: { default = final: prev: ...; some-overlay = final: prev: ...; } + ## Create exported overlays from the user flake. Adapted [from flake-utils-plus](https://github.com/gytis-ivaskevicius/flake-utils-plus/blob/2bf0f91643c2e5ae38c1b26893ac2927ac9bd82a/lib/exportOverlays.nix). + ## + ## Example Usage: + ## ```nix + ## create-overlays { src = ./my-overlays; packages-src = ./my-packages; package-namespace = "my-namespace"; extra-overlays = {}; } + ## ``` + ## Result: + ## ```nix + ## { default = final: prev: ...; some-overlay = final: prev: ...; } + ## ``` + #@ Attrs -> Attrs create-overlays = { src ? user-overlays-root , packages-src ? user-packages-root - , overlay-package-namespace ? null + , package-namespace ? null , extra-overlays ? { } }: let @@ -75,12 +83,12 @@ in channels = channel-systems.${prev.system}; }; in - if overlay-package-namespace == null then + if package-namespace == null then user-packages else { - ${overlay-package-namespace} = - (prev.${overlay-package-namespace} or { }) + ${package-namespace} = + (prev.${package-namespace} or { }) // user-packages; }; @@ -93,13 +101,13 @@ in user-overlay = import file (user-inputs // { inherit channels; }); packages = user-packages-overlay final prev; prev-with-packages = - if overlay-package-namespace == null then + if package-namespace == null then prev // packages else prev // { - ${overlay-package-namespace} = - (prev.${overlay-package-namespace} or { }) - // packages.${overlay-package-namespace}; + ${package-namespace} = + (prev.${package-namespace} or { }) + // packages.${package-namespace}; }; user-overlay-packages = user-overlay @@ -118,7 +126,7 @@ in overlays else overlays // { - "nixpkgs/${name}" = overlay; + ${name} = overlay; } ); @@ -140,18 +148,18 @@ in channels = channel-systems.${prev.system}; }; in - if overlay-package-namespace == null then + if package-namespace == null then { ${name} = packages.${name}; } else { - ${overlay-package-namespace} = - (prev.${overlay-package-namespace} or { }) + ${package-namespace} = + (prev.${package-namespace} or { }) // { ${name} = packages.${name}; }; }; in package-overlays // { - "nixpkgs/${name}" = overlay; + "package/${name}" = overlay; }; package-overlays = diff --git a/snowfall-lib/package/default.nix b/snowfall-lib/package/default.nix new file mode 100644 index 0000000..3fea3b5 --- /dev/null +++ b/snowfall-lib/package/default.nix @@ -0,0 +1,72 @@ +{ core-inputs +, user-inputs +, snowfall-lib +, snowfall-config +}: + +let + inherit (core-inputs.flake-utils-plus.lib) filterPackages allSystems; + inherit (core-inputs.nixpkgs.lib) assertMsg foldl mapAttrs filterAttrs callPackageWith; + + user-packages-root = snowfall-lib.fs.get-snowfall-file "packages"; +in +{ + package = rec { + ## Create flake output packages. + ## Example Usage: + ## ```nix + ## create-packages { inherit channels; src = ./my-packages; overrides = { inherit another-package; }; alias.default = "another-package"; } + ## ``` + ## Result: + ## ```nix + ## { another-package = ...; my-package = ...; default = ...; } + ## ``` + #@ Attrs -> Attrs + create-packages = + { channels + , src ? user-packages-root + , pkgs ? channels.nixpkgs + , overrides ? { } + , alias ? { } + , package-namespace ? "internal" + }: + let + user-packages = snowfall-lib.fs.get-default-nix-files-recursive src; + create-package-metadata = package: + let + namespaced-packages = { + ${package-namespace} = packages-without-aliases; + }; + extra-inputs = pkgs // namespaced-packages // { + inherit channels; + lib = snowfall-lib.internal.system-lib; + pkgs = pkgs // namespaced-packages; + inputs = snowfall-lib.flake.without-snowfall-inputs user-inputs; + }; + in + { + name = builtins.unsafeDiscardStringContext (snowfall-lib.path.get-parent-directory package); + drv = + let + pkg = callPackageWith extra-inputs package { }; + in + pkg // { + meta = (pkg.meta or { }) // { + snowfall = { + path = package; + }; + }; + }; + }; + packages-metadata = builtins.map create-package-metadata user-packages; + merge-packages = packages: metadata: + packages // { + ${metadata.name} = metadata.drv; + }; + packages-without-aliases = foldl merge-packages { } packages-metadata; + aliased-packages = mapAttrs (name: value: packages-without-aliases.${value}) alias; + packages = packages-without-aliases // aliased-packages // overrides; + in + filterPackages pkgs.system packages; + }; +} diff --git a/snowfall-lib/path/default.nix b/snowfall-lib/path/default.nix new file mode 100644 index 0000000..d09173e --- /dev/null +++ b/snowfall-lib/path/default.nix @@ -0,0 +1,114 @@ +{ core-inputs +, user-inputs +, snowfall-lib +, snowfall-config +}: + +let + inherit (builtins) toString baseNameOf dirOf concatStringsSep; + inherit (core-inputs.nixpkgs.lib) assertMsg last init; + + file-name-regex = "(.*)\\.(.*)$"; +in +{ + path = rec { + ## Split a file name and its extension. + ## Example Usage: + ## ```nix + ## split-file-extension "my-file.md" + ## ``` + ## Result: + ## ```nix + ## [ "my-file" "md" ] + ## ``` + #@ String -> [String] + split-file-extension = file: + let + match = builtins.match file-name-regex file; + in + assert assertMsg (match != null) "lib.snowfall.split-file-extension: File must have an extension to split."; + match; + + ## Check if a file name has a file extension. + ## Example Usage: + ## ```nix + ## has-any-file-extension "my-file.txt" + ## ``` + ## Result: + ## ```nix + ## true + ## ``` + #@ String -> Bool + has-any-file-extension = file: + let + match = builtins.match file-name-regex (toString file); + in + match != null; + + ## Get the file extension of a file name. + ## Example Usage: + ## ```nix + ## get-file-extension "my-file.final.txt" + ## ``` + ## Result: + ## ```nix + ## "txt" + ## ``` + #@ String -> String + get-file-extension = file: + if has-any-file-extension file then + let + match = builtins.match file-name-regex (toString file); + in + last match + else + ""; + + ## Check if a file name has a specific file extension. + ## Example Usage: + ## ```nix + ## has-file-extension "txt" "my-file.txt" + ## ``` + ## Result: + ## ```nix + ## true + ## ``` + #@ String -> String -> Bool + has-file-extension = extension: file: + if has-any-file-extension file then + extension == get-file-extension file + else + false; + + ## Get the parent directory for a given path. + ## Example Usage: + ## ```nix + ## get-parent-directory "/a/b/c" + ## ``` + ## Result: + ## ```nix + ## "/a/b" + ## ``` + #@ Path -> Path + get-parent-directory = snowfall-lib.fp.compose baseNameOf dirOf; + + ## Get the file name of a path without its extension. + ## Example Usage: + ## ```nix + ## get-file-name-without-extension ./some-directory/my-file.pdf + ## ``` + ## Result: + ## ```nix + ## "my-file" + ## ``` + #@ Path -> String + get-file-name-without-extension = path: + let + file-name = baseNameOf path; + in + if has-any-file-extension file-name then + concatStringsSep "" (init (split-file-extension file-name)) + else + file-name; + }; +} diff --git a/snowfall-lib/shell/default.nix b/snowfall-lib/shell/default.nix new file mode 100644 index 0000000..c1cb6a7 --- /dev/null +++ b/snowfall-lib/shell/default.nix @@ -0,0 +1,57 @@ +{ core-inputs +, user-inputs +, snowfall-lib +, snowfall-config +}: + +let + inherit (core-inputs.flake-utils-plus.lib) filterPackages; + inherit (core-inputs.nixpkgs.lib) assertMsg foldl mapAttrs callPackageWith; + + user-shells-root = snowfall-lib.fs.get-snowfall-file "shells"; +in +{ + shell = { + ## Create flake output packages. + ## Example Usage: + ## ```nix + ## create-shells { inherit channels; src = ./my-shells; overrides = { inherit another-shell; }; alias = { default = "another-shell"; }; } + ## ``` + ## Result: + ## ```nix + ## { another-shell = ...; my-shell = ...; default = ...; } + ## ``` + #@ Attrs -> Attrs + create-shells = + { channels + , src ? user-shells-root + , pkgs ? channels.nixpkgs + , overrides ? { } + , alias ? { } + }: + let + user-shells = snowfall-lib.fs.get-default-nix-files-recursive src; + create-shell-metadata = shell: + let + extra-inputs = pkgs // { + inherit channels; + lib = snowfall-lib.internal.system-lib; + inputs = snowfall-lib.flake.without-src user-inputs; + }; + in + { + name = builtins.unsafeDiscardStringContext (snowfall-lib.path.get-parent-directory shell); + drv = callPackageWith extra-inputs shell { }; + }; + shells-metadata = builtins.map create-shell-metadata user-shells; + merge-shells = shells: metadata: + shells // { + ${metadata.name} = metadata.drv; + }; + shells-without-aliases = foldl merge-shells { } shells-metadata; + aliased-shells = mapAttrs (name: value: shells-without-aliases.${value}) alias; + shells = shells-without-aliases // aliased-shells // overrides; + in + filterPackages pkgs.system shells; + }; +} diff --git a/snowfall-lib/system/default.nix b/snowfall-lib/system/default.nix new file mode 100644 index 0000000..5e3e345 --- /dev/null +++ b/snowfall-lib/system/default.nix @@ -0,0 +1,318 @@ +{ core-inputs +, user-inputs +, snowfall-lib +, snowfall-config +}: + +let + inherit (builtins) dirOf baseNameOf; + inherit (core-inputs.nixpkgs.lib) assertMsg fix hasInfix concatMap foldl optionals singleton; + + virtual-systems = import ./virtual-systems.nix; + + user-systems-root = snowfall-lib.fs.get-snowfall-file "systems"; + user-modules-root = snowfall-lib.fs.get-snowfall-file "modules"; +in +{ + system = rec { + ## Get the name of a system based on its file path. + ## Example Usage: + ## ```nix + ## get-inferred-system-name "/systems/my-system/default.nix" + ## ``` + ## Result: + ## ```nix + ## "my-system" + ## ``` + #@ Path -> String + get-inferred-system-name = path: + if snowfall-lib.path.has-file-extension "nix" path then + snowfall-lib.path.get-parent-directory path + else + baseNameOf path; + + ## Check whether a named system is macOS. + ## Example Usage: + ## ```nix + ## is-darwin "x86_64-linux" + ## ``` + ## Result: + ## ```nix + ## false + ## ``` + #@ String -> Bool + is-darwin = hasInfix "darwin"; + + ## Check whether a named system is Linux. + ## Example Usage: + ## ```nix + ## is-linux "x86_64-linux" + ## ``` + ## Result: + ## ```nix + ## false + ## ``` + #@ String -> Bool + is-linux = hasInfix "linux"; + + ## Check whether a named system is virtual. + ## Example Usage: + ## ```nix + ## is-virtual "x86_64-iso" + ## ``` + ## Result: + ## ```nix + ## true + ## ``` + #@ String -> Bool + is-virtual = target: + (get-virtual-system-type target) != ""; + + ## Get the virtual system type of a system target. + ## Example Usage: + ## ```nix + ## get-virtual-system-type "x86_64-iso" + ## ``` + ## Result: + ## ```nix + ## "iso" + ## ``` + #@ String -> String + get-virtual-system-type = target: + foldl + (result: virtual-system: + if result == "" && hasInfix virtual-system target then + virtual-system + else + result + ) + "" + virtual-systems; + + ## Get structured data about all systems for a given target. + ## Example Usage: + ## ```nix + ## get-target-systems-metadata "x86_64-linux" + ## ``` + ## Result: + ## ```nix + ## [ { target = "x86_64-linux"; name = "my-machine"; path = "/systems/x86_64-linux/my-machine"; } ] + ## ``` + #@ String -> [Attrs] + get-target-systems-metadata = target: + let + systems = snowfall-lib.fs.get-directories target; + existing-systems = builtins.filter (system: builtins.pathExists "${system}/default.nix") systems; + create-system-metadata = path: { + path = "${path}/default.nix"; + # We are building flake outputs based on file contents. Nix doesn't like this + # so we have to explicitly discard the string's path context to allow us to + # use the name as a variable. + name = builtins.unsafeDiscardStringContext (builtins.baseNameOf path); + # We are building flake outputs based on file contents. Nix doesn't like this + # so we have to explicitly discard the string's path context to allow us to + # use the name as a variable. + target = builtins.unsafeDiscardStringContext (builtins.baseNameOf target); + }; + system-configurations = builtins.map create-system-metadata existing-systems; + in + system-configurations; + + ## Get the system builder for a given target. + ## Example Usage: + ## ```nix + ## get-system-builder "x86_64-iso" + ## ``` + ## Result: + ## ```nix + ## (args: ) + ## ``` + #@ String -> Function + get-system-builder = target: + let + virtual-system-type = get-virtual-system-type target; + virtual-system-builder = args: + assert assertMsg (user-inputs ? nixos-generators) "In order to create virtual systems, you must include `nixos-generators` as a flake input."; + user-inputs.nixos-generators.nixosGenerate + (args // { + format = virtual-system-type; + 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."; + user-inputs.darwin.lib.darwinSystem + ((builtins.removeAttrs args [ "system" "modules" ]) // { + specialArgs = args.specialArgs // { + format = "darwin"; + }; + modules = args.modules ++ [ + ../../modules/darwin/user/default.nix + ]; + }); + linux-system-builder = args: + core-inputs.nixpkgs.lib.nixosSystem + (args // { + specialArgs = args.specialArgs // { + format = "linux"; + }; + modules = args.modules ++ [ + ../../modules/nixos/user/default.nix + ]; + }); + in + if virtual-system-type != "" then + virtual-system-builder + else if is-darwin target then + darwin-system-builder + else + linux-system-builder; + + ## Get the flake output attribute for a system target. + ## Example Usage: + ## ```nix + ## get-system-output "aarch64-darwin" + ## ``` + ## Result: + ## ```nix + ## "darwinConfigurations" + ## ``` + #@ String -> String + get-system-output = target: + let + virtual-system-type = get-virtual-system-type target; + in + if virtual-system-type != "" then + "${virtual-system-type}Configurations" + else if is-darwin target then + "darwinConfigurations" + else + "nixosConfigurations"; + + ## Get the resolved (non-virtual) system target. + ## Example Usage: + ## ```nix + ## get-resolved-system-target "x86_64-iso" + ## ``` + ## Result: + ## ```nix + ## "x86_64-linux" + ## ``` + #@ String -> String + get-resolved-system-target = target: + let + virtual-system-type = get-virtual-system-type target; + in + if virtual-system-type != "" then + builtins.replaceStrings [ virtual-system-type ] [ "linux" ] target + else + target; + + ## Create a system. + ## Example Usage: + ## ```nix + ## create-system { path = ./systems/my-system; } + ## ``` + ## Result: + ## ```nix + ## + ## ``` + #@ Attrs -> Attrs + create-system = + { target ? "x86_64-linux" + , system ? get-resolved-system-target target + , path + , name ? builtins.unsafeDiscardStringContext (get-inferred-system-name path) + , modules ? [ ] + , specialArgs ? { } + , channelName ? "nixpkgs" + , builder ? get-system-builder target + , output ? get-system-output target + , systems ? { } + , homes ? { } + }: + let + lib = snowfall-lib.internal.system-lib; + home-system-modules = snowfall-lib.home.create-home-system-modules homes; + home-manager-module = + if is-darwin system then + user-inputs.home-manager.darwinModules.home-manager + else + user-inputs.home-manager.nixosModules.home-manager; + home-manager-modules = [ home-manager-module ] ++ home-system-modules; + in + { + inherit channelName system builder output; + + modules = [ path ] ++ modules ++ (optionals (user-inputs ? home-manager) home-manager-modules); + + specialArgs = specialArgs // { + inherit target system systems lib; + host = name; + + virtual = (get-virtual-system-type target) != ""; + inputs = snowfall-lib.flake.without-src user-inputs; + }; + }; + + ## Create all available systems. + ## Example Usage: + ## ```nix + ## create-systems { hosts.my-host.specialArgs.x = true; modules.nixos = [ my-shared-module ]; } + ## ``` + ## Result: + ## ```nix + ## { my-host = ; } + ## ``` + #@ Attrs -> Attrs + create-systems = { systems ? { }, homes ? { } }: + let + targets = snowfall-lib.fs.get-directories user-systems-root; + target-systems-metadata = concatMap get-target-systems-metadata targets; + user-nixos-modules = snowfall-lib.module.create-modules { + src = "${user-modules-root}/nixos"; + }; + user-darwin-modules = snowfall-lib.module.create-modules { + src = "${user-modules-root}/darwin"; + }; + nixos-modules = systems.modules.nixos or [ ]; + darwin-modules = systems.modules.darwin or [ ]; + + create-system' = created-systems: system-metadata: + let + overrides = systems.hosts.${system-metadata.name} or { }; + user-modules = + if is-darwin system-metadata.target then + user-darwin-modules + else + user-nixos-modules; + user-modules-list = builtins.attrValues user-modules; + system-modules = + if is-darwin system-metadata.target then + darwin-modules + else + nixos-modules; + in + { + ${system-metadata.name} = create-system (overrides // system-metadata // { + systems = created-systems; + modules = user-modules-list ++ (overrides.modules or [ ]) ++ system-modules; + inherit homes; + }); + }; + created-systems = fix (created-systems: + foldl + (systems: system-metadata: + systems // (create-system' created-systems system-metadata) + ) + { } + target-systems-metadata + ); + in + created-systems; + }; +} diff --git a/lib/system/virtual-systems.nix b/snowfall-lib/system/virtual-systems.nix similarity index 100% rename from lib/system/virtual-systems.nix rename to snowfall-lib/system/virtual-systems.nix diff --git a/snowfall-lib/template/default.nix b/snowfall-lib/template/default.nix new file mode 100644 index 0000000..40099e7 --- /dev/null +++ b/snowfall-lib/template/default.nix @@ -0,0 +1,51 @@ +{ core-inputs +, user-inputs +, snowfall-lib +, snowfall-config +}: + +let + inherit (builtins) baseNameOf; + inherit (core-inputs.nixpkgs.lib) assertMsg foldl mapAttrs; + + user-templates-root = snowfall-lib.fs.get-snowfall-file "templates"; +in +{ + template = { + ## Create flake templates. + ## + ## Example Usage: + ## ```nix + ## create-templates { src = ./my-templates; overrides = { inherit another-template; }; alias = { default = "another-template"; }; } + ## ``` + ## + ## Result: + ## ```nix + ## { another-template = ...; my-template = ...; default = ...; } + ## ``` + #@ Attrs -> Attrs + create-templates = + { src ? user-templates-root + , overrides ? { } + , alias ? { } + }: + let + user-templates = snowfall-lib.fs.get-directories src; + create-template-metadata = template: { + name = builtins.unsafeDiscardStringContext (baseNameOf template); + path = template; + }; + templates-metadata = builtins.map create-template-metadata user-templates; + merge-templates = templates: metadata: + templates // { + ${metadata.name} = (overrides.${metadata.name} or { }) // { + inherit (metadata) path; + }; + }; + templates-without-aliases = foldl merge-templates { } templates-metadata; + aliased-templates = mapAttrs (name: value: templates-without-aliases.${value}) alias; + templates = templates-without-aliases // aliased-templates // overrides; + in + templates; + }; +}