Merge pull request #5 from snowfallorg/dev

Snowfal Lib v2
This commit is contained in:
Jake Hamilton 2023-09-14 01:10:23 -07:00 committed by GitHub
commit 0ceaa3d689
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 2499 additions and 955 deletions

193
LICENSE Normal file
View file

@ -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.

312
README.md
View file

@ -38,11 +38,12 @@ cd config
2. Create a new flake with one of the templates from [@snowfallorg/templates](https://github.com/snowfallorg/templates). 2. Create a new flake with one of the templates from [@snowfallorg/templates](https://github.com/snowfallorg/templates).
| Name | Description | | Name | Description |
|-----------|---------------------------------------------------| | --------- | ---------------------------------------------------- |
| `system` | A NixOS system and modules ready to modify. | | `system` | A NixOS system and modules ready to modify. |
| `package` | A Nix Flake that exports packages and an overlay. | | `package` | A Nix Flake that exports packages and an overlay. |
| `module` | A Nix Flake that exports NixOS modules. | | `module` | A Nix Flake that exports NixOS modules. |
| `lib` | A Nix Flake that exports a custom `lib` | | `lib` | A Nix Flake that exports a custom `lib` |
| `empty` | A basic Nix Flake for you to customize from scratch. |
```bash ```bash
# For example, to use the system template. # For example, to use the system template.
@ -66,7 +67,7 @@ library instance with `mkLib`.
description = "My Flake"; description = "My Flake";
inputs = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05"; nixpkgs.url = "github:nixos/nixpkgs/nixos-22.11";
snowfall-lib = { snowfall-lib = {
url = "github:snowfallorg/lib"; url = "github:snowfallorg/lib";
@ -81,6 +82,10 @@ library instance with `mkLib`.
# your flake. # your flake.
inherit inputs; inherit inputs;
src = ./.; src = ./.;
# You can optionally place your Snowfall-related files in another
# directory.
snowfall.root = ./nix;
}; };
in in
# We'll cover what to do here next. # We'll cover what to do here next.
@ -100,6 +105,10 @@ let
lib = inputs.snowfall-lib.mkLib { lib = inputs.snowfall-lib.mkLib {
inherit inputs; inherit inputs;
src = ./.; src = ./.;
# You can optionally place your Snowfall-related files in another
# directory.
snowfall.root = ./nix;
}; };
in lib.mkFlake { 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` ## `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. 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. │ Your Nix flake.
├─ flake.nix ├─ flake.nix
@ -184,6 +196,14 @@ flake-root/
├─ modules/ (optional modules) ├─ modules/ (optional modules)
│ │ │ │
│ │ A directory named after the `platform` type that will be used for modules within.
│ │
│ │ Supported platforms are:
│ │ - nixos
│ │ - darwin
│ │ - home
│ └─ <platform>/
│ │
│ │ Any (nestable) directory name. The name of the directory will be the │ │ Any (nestable) directory name. The name of the directory will be the
│ │ name of the module. │ │ name of the module.
│ └─ **/ │ └─ **/
@ -235,6 +255,37 @@ flake-root/
│ │ │ │
│ │ A NixOS module for your system's configuration. │ │ A NixOS module for your system's configuration.
│ └─ default.nix │ └─ 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.
│ └─ <architecture>-<format>/
│ │
│ │ A directory that contains a single home's configuration. The directory name
│ │ will be the name of the home.
│ └─ <home-name>/
│ │
│ │ A NixOS module for your home's configuration.
│ └─ default.nix
``` ```
#### Default Flake #### Default Flake
@ -247,7 +298,7 @@ packages, overlays, and shells specified by the [Flake Structure](#flake-structu
description = "My Flake"; description = "My Flake";
inputs = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05"; nixpkgs.url = "github:nixos/nixpkgs/nixos-22.11";
snowfall-lib = { snowfall-lib = {
url = "github:snowfallorg/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"; description = "My Flake";
inputs = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05"; nixpkgs.url = "github:nixos/nixpkgs/nixos-22.11";
snowfall-lib = { snowfall-lib = {
url = "github:snowfallorg/lib"; url = "github:snowfallorg/lib";
@ -338,7 +389,7 @@ on `pkgs` and consumers of your flake can use the generated `<your-flake>.overla
description = "My Flake"; description = "My Flake";
inputs = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05"; nixpkgs.url = "github:nixos/nixpkgs/nixos-22.11";
snowfall-lib = { snowfall-lib = {
url = "github:snowfallorg/lib"; url = "github:snowfallorg/lib";
@ -366,7 +417,7 @@ on `pkgs` and consumers of your flake can use the generated `<your-flake>.overla
# Optionally place all packages under a namespace when used in an overlay. # Optionally place all packages under a namespace when used in an overlay.
# Instead of accessing packages with `pkgs.<name>`, your internal packages # Instead of accessing packages with `pkgs.<name>`, your internal packages
# will be available at `pkgs.<namespace>.<name>`. # will be available at `pkgs.<namespace>.<name>`.
overlay-package-namespace = "my-namespace"; package-namespace = "my-namespace";
# You can also pass through external packages or dynamically create new ones # 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. # 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 `<your-flake>.overla
Snowfall Lib will create packages and shells based on your `packages/` and `shells` 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 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. mapping the `default` package or shell to the name of the one you want.
```nix ```nix
@ -391,7 +442,7 @@ mapping the `default` package or shell to the name of the one you want.
description = "My Flake"; description = "My Flake";
inputs = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05"; nixpkgs.url = "github:nixos/nixpkgs/nixos-22.11";
snowfall-lib = { snowfall-lib = {
url = "github:snowfallorg/lib"; url = "github:snowfallorg/lib";
@ -411,16 +462,22 @@ mapping the `default` package or shell to the name of the one you want.
}; };
in in
lib.mkFlake { lib.mkFlake {
# You can also pass through external packages or dynamically create new ones alias = {
# in addition to the ones that `lib` will create from your `packages/` directory.
outputs-builder = channels: {
packages = { packages = {
default = "my-package"; default = "my-package";
}; };
devShells = { shells = {
default = "my-shell"; 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"; description = "My Flake";
inputs = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05"; nixpkgs.url = "github:nixos/nixpkgs/nixos-22.11";
snowfall-lib = { snowfall-lib = {
url = "github:snowfallorg/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 | | vm-nogui | Same as vm, but without a GUI |
| vmware | VMWare image (VMDK) | | 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` ### `lib.snowfall.flake`
Helpers related to Nix flakes. Helpers related to Nix flakes.
@ -701,8 +800,11 @@ Result:
File system utilities. File system utilities.
#### `lib.snowfall.fs.is-file-kind` #### `lib.snowfall.fs.is-file-kind`
#### `lib.snowfall.fs.is-symlink-kind` #### `lib.snowfall.fs.is-symlink-kind`
#### `lib.snowfall.fs.is-directory-kind` #### `lib.snowfall.fs.is-directory-kind`
#### `lib.snowfall.fs.is-unknown-kind` #### `lib.snowfall.fs.is-unknown-kind`
Matchers for file kinds. These are often used with `readDir`. Matchers for file kinds. These are often used with `readDir`.
@ -739,6 +841,24 @@ Result:
"/user-source/systems" "/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` #### `lib.snowfall.fs.internal-get-file`
Get a file relative to the Snowfall Lib flake. You probably shouldn't use this! 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" ] [ "./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` ### `lib.snowfall.attrs`
Utilities for working with attribute sets. Utilities for working with attribute sets.
@ -1066,6 +1208,24 @@ Result:
false 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` #### `lib.snowfall.system.get-virtual-system-type`
Get the virtual system type of a system target. Get the virtual system type of a system target.
@ -1084,6 +1244,24 @@ Result:
"iso" "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` #### `lib.snowfall.system.get-target-systems-metadata`
Get structured data about all systems for a given target. Get structured data about all systems for a given target.
@ -1183,7 +1361,7 @@ Type: `Attrs -> Attrs`
Usage: Usage:
```nix ```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: Result:
@ -1192,6 +1370,98 @@ Result:
{ my-host = <flake-utils-plus-system-configuration>; } { my-host = <flake-utils-plus-system-configuration>; }
``` ```
### `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
<flake-utils-plus-home-configuration>
```
#### `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" = <flake-utils-plus-home-configuration>; }
```
#### `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` ### `lib.snowfall.package`
Utilities for working with flake packages. Utilities for working with flake packages.
@ -1205,7 +1475,7 @@ Type: `Attrs -> Attrs`
Usage: Usage:
```nix ```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: Result:
@ -1227,7 +1497,7 @@ Type: `Attrs -> Attrs`
Usage: Usage:
```nix ```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: Result:
@ -1249,7 +1519,7 @@ Type: `Attrs -> Attrs -> [(a -> b -> c)]`
Usage: Usage:
```nix ```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: Result:
@ -1270,7 +1540,7 @@ Usage:
create-overlays { create-overlays {
src = ./my-overlays; src = ./my-overlays;
packages-src = ./my-packages; packages-src = ./my-packages;
overlay-package-namespace = "my-namespace"; package-namespace = "my-namespace";
extra-overlays = { extra-overlays = {
my-example = final: prev: {}; my-example = final: prev: {};
}; };
@ -1300,7 +1570,7 @@ Type: `Attrs -> Attrs`
Usage: Usage:
```nix ```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: Result:

8
flake.lock generated
View file

@ -51,16 +51,16 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1665216225, "lastModified": 1677028070,
"narHash": "sha256-SUuvJXGEXhmyaGJlDlptbc9I2Wai9tUx83QYIqvipfE=", "narHash": "sha256-sUKqd8HYBrtPxCRXFWvsnQDnwqnw1uIDwu4khcZuL2k=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "1a9935bf90e0f4225756f99e772f06d5fea9edb6", "rev": "d3a15cd8dc917f4364ba0b332a1c389dc3177603",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nixos", "owner": "nixos",
"ref": "release-22.05", "ref": "release-22.11",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }

View file

@ -2,7 +2,7 @@
description = "Snowfall Lib"; description = "Snowfall Lib";
inputs = { 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-utils-plus.url = "github:gytis-ivaskevicius/flake-utils-plus";
flake-compat = { flake-compat = {
@ -22,15 +22,15 @@
# `lib.flake-utils-plus.mkApp`. # `lib.flake-utils-plus.mkApp`.
# Usage: mkLib { inherit inputs; src = ./.; } # Usage: mkLib { inherit inputs; src = ./.; }
# result: lib # 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`. # A convenience wrapper to create the library and then call `lib.mkFlake`.
# Usage: mkFlake { inherit inputs; src = ./.; ... } # Usage: mkFlake { inherit inputs; src = ./.; ... }
# result: <flake-outputs> # result: <flake-outputs>
mkFlake = flake-and-lib-options@{ inputs, src, ... }: mkFlake = flake-and-lib-options@{ inputs, src, snowfall ? { }, ... }:
let let
lib = mkLib { lib = mkLib {
inherit inputs src; inherit inputs src snowfall;
}; };
flake-options = builtins.removeAttrs flake-and-lib-options [ "inputs" "src" ]; flake-options = builtins.removeAttrs flake-and-lib-options [ "inputs" "src" ];
in in
@ -38,5 +38,48 @@
in in
{ {
inherit mkLib mkFlake; 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" ];
};
}; };
} }

View file

@ -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;
};
}

View file

@ -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;
}

View file

@ -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;
};
}

View file

@ -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);
};
}

View file

@ -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;
};
}

View file

@ -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;
};
}

View file

@ -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;
};
}

View file

@ -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;
};
}

View file

@ -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: <system>)
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: <flake-utils-plus-system-configuration>
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 = <flake-utils-plus-system-configuration>; }
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;
};
}

View file

@ -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;
};
}

View file

@ -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));
};
}

View file

@ -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);
};
};
}

View file

@ -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));
};
}

View file

@ -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;
};
}

View file

@ -6,9 +6,20 @@ core-inputs:
user-options: user-options:
let 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; }; 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. # Recursively merge a list of attribute sets.
# Type: [Attrs] -> Attrs # Type: [Attrs] -> Attrs
@ -48,7 +59,9 @@ let
core-inputs-libs = get-libs (without-self core-inputs); core-inputs-libs = get-libs (without-self core-inputs);
user-inputs-libs = get-libs (without-self user-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 = snowfall-lib-dirs =
let let
files = builtins.readDir snowfall-lib-root; files = builtins.readDir snowfall-lib-root;
@ -60,7 +73,7 @@ let
snowfall-lib = fix (snowfall-lib: snowfall-lib = fix (snowfall-lib:
let let
attrs = { attrs = {
inherit snowfall-lib core-inputs user-inputs; inherit snowfall-lib snowfall-config core-inputs user-inputs;
}; };
libs = builtins.map libs = builtins.map
(dir: import "${snowfall-lib-root}/${dir}" attrs) (dir: import "${snowfall-lib-root}/${dir}" attrs)
@ -87,10 +100,10 @@ let
attrs = { attrs = {
inherit (user-options) inputs; inherit (user-options) inputs;
snowfall-inputs = core-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 libs = builtins.map
(path: import path attrs) (path: callPackageWith attrs path { })
user-lib-modules; user-lib-modules;
in in
merge-deep libs merge-deep libs

View file

@ -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;
}

View file

@ -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;
};
}

244
snowfall-lib/fs/default.nix Normal file
View file

@ -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);
};
}

View file

@ -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
## <flake-utils-plus-home-configuration>
## ```
#@ 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" = <flake-utils-plus-home-configuration>; }
## ```
#@ 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.<name>.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;
};
}

View file

@ -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"
];
}

View file

@ -1,10 +1,11 @@
{ core-inputs { core-inputs
, user-inputs , user-inputs
, snowfall-lib , snowfall-lib
, snowfall-config
}: }:
let 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); 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); user-inputs-libs = snowfall-lib.flake.get-libs (snowfall-lib.flake.without-self user-inputs);
@ -27,10 +28,13 @@ let
attrs = { attrs = {
inputs = snowfall-lib.flake.without-snowfall-inputs user-inputs; inputs = snowfall-lib.flake.without-snowfall-inputs user-inputs;
snowfall-inputs = core-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 libs = builtins.map
(path: import path attrs) (path: callPackageWith attrs path { })
user-lib-modules; user-lib-modules;
in in
snowfall-lib.attrs.merge-deep libs snowfall-lib.attrs.merge-deep libs
@ -38,7 +42,7 @@ let
system-lib = snowfall-lib.attrs.merge-shallow [ system-lib = snowfall-lib.attrs.merge-shallow [
base-lib base-lib
user-lib { "${snowfall-config.namespace}" = user-lib; }
]; ];
in in
{ {

View file

@ -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;
};
}

View file

@ -1,23 +1,30 @@
{ core-inputs { core-inputs
, user-inputs , user-inputs
, snowfall-lib , snowfall-lib
, snowfall-config
}: }:
let let
inherit (core-inputs.nixpkgs.lib) assertMsg foldl concatStringsSep; inherit (core-inputs.nixpkgs.lib) assertMsg foldl concatStringsSep;
user-overlays-root = snowfall-lib.fs.get-file "overlays"; user-overlays-root = snowfall-lib.fs.get-snowfall-file "overlays";
user-packages-root = snowfall-lib.fs.get-file "packages"; user-packages-root = snowfall-lib.fs.get-snowfall-file "packages";
in in
{ {
overlay = { overlay = {
# Create a flake-utils-plus overlays builder. ## Create a flake-utils-plus overlays builder.
# Type: Attrs -> Attrs -> [(a -> b -> c)] ## Example Usage:
# Usage: create-overlays { src = ./my-overlays; overlay-package-namespace = "my-packages"; } ## ```nix
# result: (channels: [ ... ]) ## create-overlays { src = ./my-overlays; package-namespace = "my-packages"; }
## ```
## Result:
## ```nix
## (channels: [ ... ])
## ```
#@ Attrs -> Attrs -> [(a -> b -> c)]
create-overlays-builder = create-overlays-builder =
{ src ? user-overlays-root { src ? user-overlays-root
, overlay-package-namespace ? null , package-namespace ? "internal"
, extra-overlays ? [ ] , extra-overlays ? [ ]
}: channels: }: channels:
let let
@ -29,31 +36,32 @@ in
pkgs = final; pkgs = final;
channels = channels; channels = channels;
}; };
user-packages-without-default = builtins.removeAttrs
(user-packages) [ "default" ];
in in
if overlay-package-namespace == null then
user-packages-without-default
else
{ {
${overlay-package-namespace} = ${package-namespace} =
(prev.${overlay-package-namespace} or { }) (prev.${package-namespace} or { })
// user-packages-without-default; // user-packages;
}; };
overlays = [ user-packages-overlay ] ++ extra-overlays ++ (builtins.map create-overlay user-overlays); overlays =
[ user-packages-overlay ] ++ extra-overlays ++ (builtins.map create-overlay user-overlays);
in in
overlays; overlays;
# Create exported overlays from the user flake. ## 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).
# Adapted from flake-utils-plus: ##
# https://github.com/gytis-ivaskevicius/flake-utils-plus/blob/2bf0f91643c2e5ae38c1b26893ac2927ac9bd82a/lib/exportOverlays.nix ## Example Usage:
# Type: Attrs -> Attrs ## ```nix
# Usage: create-overlays { src = ./my-overlays; packages-src = ./my-packages; overlay-package-namespace = "my-namespace"; extra-overlays = {}; } ## create-overlays { src = ./my-overlays; packages-src = ./my-packages; package-namespace = "my-namespace"; extra-overlays = {}; }
# result: { default = final: prev: ...; some-overlay = final: prev: ...; } ## ```
## Result:
## ```nix
## { default = final: prev: ...; some-overlay = final: prev: ...; }
## ```
#@ Attrs -> Attrs
create-overlays = create-overlays =
{ src ? user-overlays-root { src ? user-overlays-root
, packages-src ? user-packages-root , packages-src ? user-packages-root
, overlay-package-namespace ? null , package-namespace ? null
, extra-overlays ? { } , extra-overlays ? { }
}: }:
let let
@ -75,12 +83,12 @@ in
channels = channel-systems.${prev.system}; channels = channel-systems.${prev.system};
}; };
in in
if overlay-package-namespace == null then if package-namespace == null then
user-packages user-packages
else else
{ {
${overlay-package-namespace} = ${package-namespace} =
(prev.${overlay-package-namespace} or { }) (prev.${package-namespace} or { })
// user-packages; // user-packages;
}; };
@ -93,13 +101,13 @@ in
user-overlay = import file (user-inputs // { inherit channels; }); user-overlay = import file (user-inputs // { inherit channels; });
packages = user-packages-overlay final prev; packages = user-packages-overlay final prev;
prev-with-packages = prev-with-packages =
if overlay-package-namespace == null then if package-namespace == null then
prev // packages prev // packages
else else
prev // { prev // {
${overlay-package-namespace} = ${package-namespace} =
(prev.${overlay-package-namespace} or { }) (prev.${package-namespace} or { })
// packages.${overlay-package-namespace}; // packages.${package-namespace};
}; };
user-overlay-packages = user-overlay-packages =
user-overlay user-overlay
@ -118,7 +126,7 @@ in
overlays overlays
else else
overlays // { overlays // {
"nixpkgs/${name}" = overlay; ${name} = overlay;
} }
); );
@ -140,18 +148,18 @@ in
channels = channel-systems.${prev.system}; channels = channel-systems.${prev.system};
}; };
in in
if overlay-package-namespace == null then if package-namespace == null then
{ ${name} = packages.${name}; } { ${name} = packages.${name}; }
else else
{ {
${overlay-package-namespace} = ${package-namespace} =
(prev.${overlay-package-namespace} or { }) (prev.${package-namespace} or { })
// { ${name} = packages.${name}; }; // { ${name} = packages.${name}; };
}; };
in in
package-overlays // package-overlays //
{ {
"nixpkgs/${name}" = overlay; "package/${name}" = overlay;
}; };
package-overlays = package-overlays =

View file

@ -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;
};
}

View file

@ -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;
};
}

View file

@ -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;
};
}

View file

@ -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: <system>)
## ```
#@ 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
## <flake-utils-plus-system-configuration>
## ```
#@ 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 = <flake-utils-plus-system-configuration>; }
## ```
#@ 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;
};
}

View file

@ -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;
};
}