diff --git a/flake.nix b/flake.nix index 80faf5c..83bb612 100644 --- a/flake.nix +++ b/flake.nix @@ -44,7 +44,7 @@ nt, ... } @ inputs: - import ./cerulean + import ./nix { inherit inputs self nt; inherit (nt) mix; diff --git a/nix/nixos/isoImage/default.nix b/nix/nixos/isoImage/default.nix new file mode 100644 index 0000000..b09d25e --- /dev/null +++ b/nix/nixos/isoImage/default.nix @@ -0,0 +1,16 @@ +{ + modulesPath, + config, + lib, + pkgs, + ... +}: { + config = lib.mkIf (config?isoImage) { + # TODO: do i really need to import ALL of this? + imports = [(modulesPath + "/installer/cd-dvd/installation-cd-minimal.nix")]; + + environment.systemPackages = [ + pkgs.neovim + ]; + }; +} diff --git a/nix/snow/default.nix b/nix/snow/default.nix index 6993ff1..3a8a5aa 100644 --- a/nix/snow/default.nix +++ b/nix/snow/default.nix @@ -67,125 +67,129 @@ in ]; }; - nodes = module.config.nodes; - in rec { - nixosConfigurations = mapNodes nodes ( - { - base, - lib, + inherit (module) config; + nodes = config.nodes; + in + # TODO: maybe use flake-parts or another module to all merging + config.outputs + // rec { + nixosConfigurations = mapNodes nodes ( + { + base, + lib, + name, + node, + groupModules, + ... + }: let + homeManager = + if node.homeManager != null + then node.homeManager + else if nodes.homeManager != null + then nodes.homeManager + else + warn '' + [snowflake] Neither `nodes.homeManager` nor `nodes.nodes.${name}.homeManager` were specified! + [snowflake] home-manager will NOT be used! User configuration will be ignored! + '' + null; + + userArgs = nodes.args // node.args; + ceruleanArgs = { + inherit systems root base nodes node; + inherit (node) system; + inherit (this) snow; + hostname = name; + + _cerulean = { + inherit inputs userArgs ceruleanArgs homeManager; + specialArgs = userArgs // ceruleanArgs; + }; + }; + specialArgs = assert (userArgs + |> attrNames + |> all (argName: + ! ceruleanArgs ? argName + || abort '' + `specialArgs` are like super important to Cerulean my love... rollback; + magicRollback = magicRollback -> rollback; + activationTimeout = activationTimeout; + confirmTimeout = confirmTimeout; + + remoteBuild = remoteBuild; + sshUser = ssh.user; + sshOpts = + ssh.opts + ++ ( + if elem "-p" ssh.opts + then [] + else ["-p" (toString ssh.port)] + ) + ++ ( + if elem "-A" ssh.opts + then [] + else ["-A"] + ); }; - specialArgs = assert (userArgs - |> attrNames - |> all (argName: - ! ceruleanArgs ? argName - || abort '' - `specialArgs` are like super important to Cerulean my love... rollback; - magicRollback = magicRollback -> rollback; - activationTimeout = activationTimeout; - confirmTimeout = confirmTimeout; - - remoteBuild = remoteBuild; - sshUser = ssh.user; - sshOpts = - ssh.opts - ++ ( - if elem "-p" ssh.opts - then [] - else ["-p" (toString ssh.port)] - ) - ++ ( - if elem "-A" ssh.opts - then [] - else ["-A"] - ); - }; - }); - - checks = - inputs.deploy-rs.lib - |> mapAttrs (system: deployLib: - deployLib.deployChecks deploy); - }; + checks = + inputs.deploy-rs.lib + |> mapAttrs (system: deployLib: + deployLib.deployChecks deploy); + }; }) diff --git a/nix/snow/module.nix b/nix/snow/module.nix index 79b8804..c61a9df 100644 --- a/nix/snow/module.nix +++ b/nix/snow/module.nix @@ -15,9 +15,14 @@ root, snow, ... -}: { +}: let + snowFlake = snow.findImport /${root}/snow; +in { imports = [ + ./modules ./nodes - (snow.findImport /${root}/snow) + snowFlake ]; + + outputs = snowFlake; } diff --git a/nix/snow/modules/default.nix b/nix/snow/modules/default.nix new file mode 100644 index 0000000..a5d9f55 --- /dev/null +++ b/nix/snow/modules/default.nix @@ -0,0 +1,5 @@ +{...}: { + imports = [ + ./outputs.nix + ]; +} diff --git a/nix/snow/modules/outputs.nix b/nix/snow/modules/outputs.nix new file mode 100644 index 0000000..4f971c4 --- /dev/null +++ b/nix/snow/modules/outputs.nix @@ -0,0 +1,37 @@ +{lib, ...}: let + inherit + (lib) + mkOption + types + ; +in { + options = { + outputs = mkOption { + type = types.submoduleWith { + modules = [ + { + freeformType = + types.lazyAttrsOf + (types.unique + { + message = '' + No option has been declared for this flake output attribute, so its definitions can't be merged automatically. + Possible solutions: + - Load a module that defines this flake output attribute + - Declare an option for this flake output attribute + - Make sure the output attribute is spelled correctly + - Define the value only once, with a single definition in a single module + ''; + } + types.raw); + } + ]; + }; + description = '' + Raw flake output attributes. Any attribute can be set here, but some + attributes are represented by options, to provide appropriate + configuration merging. + ''; + }; + }; +} diff --git a/snow/Cargo.lock b/snow/Cargo.lock index 0ab3568..740e3bd 100644 --- a/snow/Cargo.lock +++ b/snow/Cargo.lock @@ -315,6 +315,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -403,7 +412,7 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "nix-bindings-bdwgc-sys" version = "0.2.1" -source = "git+https://github.com/nixops4/nix-bindings-rust#7de15fa26057c8cf0b6178ff166e529c09ea89a7" +source = "git+https://tearforge.net/cry/nixide#716c028bb159e62d3d295c5101d50ee117f027c6" dependencies = [ "bindgen", "pkg-config", @@ -412,7 +421,7 @@ dependencies = [ [[package]] name = "nix-bindings-expr" version = "0.2.1" -source = "git+https://github.com/nixops4/nix-bindings-rust#7de15fa26057c8cf0b6178ff166e529c09ea89a7" +source = "git+https://tearforge.net/cry/nixide#716c028bb159e62d3d295c5101d50ee117f027c6" dependencies = [ "anyhow", "cstr", @@ -430,7 +439,7 @@ dependencies = [ [[package]] name = "nix-bindings-expr-sys" version = "0.2.1" -source = "git+https://github.com/nixops4/nix-bindings-rust#7de15fa26057c8cf0b6178ff166e529c09ea89a7" +source = "git+https://tearforge.net/cry/nixide#716c028bb159e62d3d295c5101d50ee117f027c6" dependencies = [ "bindgen", "nix-bindings-store-sys", @@ -438,10 +447,64 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "nix-bindings-fetchers" +version = "0.2.1" +source = "git+https://tearforge.net/cry/nixide#716c028bb159e62d3d295c5101d50ee117f027c6" +dependencies = [ + "anyhow", + "cstr", + "ctor", + "nix-bindings-fetchers-sys", + "nix-bindings-store", + "nix-bindings-util", + "tempfile", +] + +[[package]] +name = "nix-bindings-fetchers-sys" +version = "0.2.1" +source = "git+https://tearforge.net/cry/nixide#716c028bb159e62d3d295c5101d50ee117f027c6" +dependencies = [ + "bindgen", + "nix-bindings-util-sys", + "pkg-config", +] + +[[package]] +name = "nix-bindings-flake" +version = "0.2.1" +source = "git+https://tearforge.net/cry/nixide#716c028bb159e62d3d295c5101d50ee117f027c6" +dependencies = [ + "anyhow", + "cstr", + "ctor", + "nix-bindings-expr", + "nix-bindings-fetchers", + "nix-bindings-flake-sys", + "nix-bindings-store", + "nix-bindings-util", + "tempfile", +] + +[[package]] +name = "nix-bindings-flake-sys" +version = "0.2.1" +source = "git+https://tearforge.net/cry/nixide#716c028bb159e62d3d295c5101d50ee117f027c6" +dependencies = [ + "bindgen", + "nix-bindings-bdwgc-sys", + "nix-bindings-expr-sys", + "nix-bindings-fetchers-sys", + "nix-bindings-store-sys", + "nix-bindings-util-sys", + "pkg-config", +] + [[package]] name = "nix-bindings-store" version = "0.2.1" -source = "git+https://github.com/nixops4/nix-bindings-rust#7de15fa26057c8cf0b6178ff166e529c09ea89a7" +source = "git+https://tearforge.net/cry/nixide#716c028bb159e62d3d295c5101d50ee117f027c6" dependencies = [ "anyhow", "nix-bindings-store-sys", @@ -454,7 +517,7 @@ dependencies = [ [[package]] name = "nix-bindings-store-sys" version = "0.2.1" -source = "git+https://github.com/nixops4/nix-bindings-rust#7de15fa26057c8cf0b6178ff166e529c09ea89a7" +source = "git+https://tearforge.net/cry/nixide#716c028bb159e62d3d295c5101d50ee117f027c6" dependencies = [ "bindgen", "nix-bindings-util-sys", @@ -465,7 +528,7 @@ dependencies = [ [[package]] name = "nix-bindings-util" version = "0.2.1" -source = "git+https://github.com/nixops4/nix-bindings-rust#7de15fa26057c8cf0b6178ff166e529c09ea89a7" +source = "git+https://tearforge.net/cry/nixide#716c028bb159e62d3d295c5101d50ee117f027c6" dependencies = [ "anyhow", "nix-bindings-util-sys", @@ -474,7 +537,7 @@ dependencies = [ [[package]] name = "nix-bindings-util-sys" version = "0.2.1" -source = "git+https://github.com/nixops4/nix-bindings-rust#7de15fa26057c8cf0b6178ff166e529c09ea89a7" +source = "git+https://tearforge.net/cry/nixide#716c028bb159e62d3d295c5101d50ee117f027c6" dependencies = [ "bindgen", "pkg-config", @@ -492,9 +555,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -603,6 +666,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "semver" version = "1.0.27" @@ -665,8 +734,11 @@ dependencies = [ "clap", "fern", "humantime", + "indoc", "log", "nix-bindings-expr", + "nix-bindings-fetchers", + "nix-bindings-flake", "nix-bindings-store", ] @@ -689,9 +761,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.26.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", "getrandom", @@ -960,18 +1032,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.40" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.40" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", diff --git a/snow/Cargo.toml b/snow/Cargo.toml index 0db7dcf..2604bda 100644 --- a/snow/Cargo.toml +++ b/snow/Cargo.toml @@ -6,10 +6,14 @@ authors = ["_cry64 "] edition = "2024" [dependencies] -nix-bindings-store = { git = "https://github.com/nixops4/nix-bindings-rust" } -nix-bindings-expr = { git = "https://github.com/nixops4/nix-bindings-rust" } +nix-bindings-expr = { git = "https://tearforge.net/cry/nixide" } +nix-bindings-flake = { git = "https://tearforge.net/cry/nixide" } +nix-bindings-fetchers = { git = "https://tearforge.net/cry/nixide" } +nix-bindings-store = { git = "https://tearforge.net/cry/nixide" } +indoc = "2" anyhow = "1.0.102" + clap = { version = "4.5.60", features = ["derive"] } log = "0.4.29" fern = { version="0.7.1", features = ["colored"] } diff --git a/snow/src/main.rs b/snow/src/main.rs index 5a9d19d..ea800e1 100644 --- a/snow/src/main.rs +++ b/snow/src/main.rs @@ -1,3 +1,5 @@ +mod nix; + use nix_bindings_expr::eval_state::{gc_register_my_thread, init, EvalState}; use nix_bindings_store::store::Store; use std::collections::HashMap; diff --git a/snow/src/nix/flake.rs b/snow/src/nix/flake.rs new file mode 100644 index 0000000..a741f66 --- /dev/null +++ b/snow/src/nix/flake.rs @@ -0,0 +1,114 @@ +use std::{cell::RefCell, collections::HashMap, rc::Rc}; + +use anyhow::Result; +use nix_bindings_expr::{eval_state::EvalState, value::Value}; +use nix_bindings_fetchers::FetchersSettings; +use nix_bindings_flake::{FlakeLockFlags, FlakeSettings, LockedFlake}; +use nix_bindings_store::store::Store; + +use crate::nix::FlakeRef; + +#[derive(Debug, Clone)] +pub enum FlakeLockMode { + /// Configures [LockedFlake::lock] to make incremental changes to the lock file as needed. Changes are written to file. + WriteAsNeeded, + + /// Like [FlakeLockMode::WriteAsNeeded], but does not write to the lock file. + Virtual, + + /// Make [LockedFlake::lock] check if the lock file is up to date. If not, an error is returned. + Check, +} + +pub struct FlakeBuilder { + flake_ref: Rc>, + + lock_mode: FlakeLockMode, + lock_flags: FlakeLockFlags, + eval_state: Option, +} + +pub struct Flake { + flake_ref: Rc>, + locked_flake: LockedFlake, + + lock_mode: FlakeLockMode, + eval_state: EvalState, +} + +impl FlakeBuilder { + pub fn new(flake_ref: Rc>, lock_mode: FlakeLockMode) -> Result { + let mut lock_flags = FlakeLockFlags::new(&flake_ref.as_ref().borrow().flake_settings)?; + + match lock_mode { + FlakeLockMode::WriteAsNeeded => lock_flags.set_mode_write_as_needed(), + FlakeLockMode::Virtual => lock_flags.set_mode_virtual(), + FlakeLockMode::Check => lock_flags.set_mode_check(), + }?; + + Ok(FlakeBuilder { + flake_ref: flake_ref, + + lock_mode, + lock_flags, + eval_state: None, + }) + } + + /// Adds an input override to the lock file that will be produced. + /// The [LockedFlake::lock] operation will not write to the lock file. + /// + /// # Arguments + /// + /// * `path` - The input name/path to override (must not be empty) + /// * `flake_ref` - The flake reference to use as the override + pub fn override_input(mut self, path: &str, flake_ref: &FlakeRef) -> Result { + assert!( + !path.is_empty(), + "The input path for `FlakeBuilder::override_input` cannot be an empty string slice!" + ); + + self.lock_flags.add_input_override(path, &flake_ref.ref_)?; + Ok(self) + } + + pub fn build(&mut self) -> Result { + let eval_state = match self.eval_state.take() { + Some(state) => state, + None => { + let store = Store::open(None, HashMap::new())?; + EvalState::new(store, [])? + } + }; + + let locked_flake = LockedFlake::lock( + &FetchersSettings::new()?, + &FlakeSettings::new()?, + &eval_state, + &self.lock_flags, + &self.flake_ref.as_ref().borrow().ref_, + )?; + + Ok(Flake { + flake_ref: Rc::clone(&self.flake_ref), + locked_flake, + + lock_mode: self.lock_mode.clone(), + eval_state, + }) + } +} + +impl Flake { + pub fn new(flake_ref: Rc>, lock_mode: FlakeLockMode) -> Result { + let mut builder = FlakeBuilder::new(flake_ref, lock_mode)?; + builder.build() + } + + pub fn outputs(&mut self) -> Result { + self.locked_flake.outputs( + &self.flake_ref.as_ref().borrow().flake_settings, + &mut self.eval_state, + ) + } +} diff --git a/snow/src/nix/flakeref.rs b/snow/src/nix/flakeref.rs new file mode 100644 index 0000000..6bc99be --- /dev/null +++ b/snow/src/nix/flakeref.rs @@ -0,0 +1,79 @@ +use std::path::{Path, PathBuf}; + +use anyhow::{Context, Result}; +use indoc::indoc; +use nix_bindings_fetchers::FetchersSettings; +use nix_bindings_flake::{FlakeReference, FlakeReferenceParseFlags, FlakeSettings}; + +pub struct FlakeRef { + pub(super) ref_: FlakeReference, + pub path: PathBuf, + + pub flake_settings: FlakeSettings, + pub fetch_settings: FetchersSettings, +} + +impl FlakeRef { + /// Parse a flake reference from a string. + /// The string must be a valid flake reference, such as `github:owner/repo`. + /// It may also be suffixed with a `#` and a fragment, such as `github:owner/repo#something`, + /// in which case, the returned `flake_ref.path` will contain the fragment. + pub fn new>(reference: &str, base_directory: Option

) -> Result { + let flake_settings = FlakeSettings::new()?; + let fetch_settings = FetchersSettings::new()?; + + let mut flags = FlakeReferenceParseFlags::new(&flake_settings)?; + + if let Some(base_directory) = base_directory { + flags.set_base_directory( + base_directory + .as_ref() + .to_str() + .context("The given flake reference path is not provided as valid unicode")?, + )?; + } else { + // TODO: try see if libnix uses the cwd as a fallback (aka is this assert pointless?) + assert!( + reference.starts_with("."), + indoc! {" + Attempted to construct FlakeRef from relative path without declaring `base_directory`! + Call to `FlakeRef::absolute(&str)` should actually be `FlakeRef::relative(&str, dyn AsRef)`. + "} + ); + } + + FlakeReference::parse_with_fragment(&fetch_settings, &flake_settings, &flags, reference) + .map(|(reference, path)| FlakeRef { + ref_: reference, + path: PathBuf::from(path), + flake_settings, + fetch_settings, + }) + } + + /// >[!WARNING] + /// > Do not use [FlakeRef::absolute(&str)](FlakeRef::absolute) to construct a [FlakeRef] from a relative path! + /// > Use [FlakeRef::relative(&str, dyn AsRef)](FlakeRef::relative) instead to declare the `base_directory`. + /// + /// Parse a flake reference from a string. + /// The string must be a valid flake reference, such as `github:owner/repo`. + /// It may also be suffixed with a `#` and a fragment, such as `github:owner/repo#something`, + /// in which case, the returned `flake_ref.path` will contain the fragment. + pub fn absolute(reference: &str) -> Result { + FlakeRef::new(reference, None::<&Path>) + } + + /// Parse a flake reference from a string. + /// The string must be a valid flake reference, such as `github:owner/repo`. + /// It may also be suffixed with a `#` and a fragment, such as `github:owner/repo#something`, + /// in which case, the returned `flake_ref.path` will contain the fragment. + pub fn relative>(reference: &str, base_directory: P) -> Result { + FlakeRef::new(reference, Some(base_directory)) + } +} + +impl Into for FlakeRef { + fn into(self) -> FlakeReference { + self.ref_ + } +} diff --git a/snow/src/nix/mod.rs b/snow/src/nix/mod.rs new file mode 100644 index 0000000..5f24261 --- /dev/null +++ b/snow/src/nix/mod.rs @@ -0,0 +1,7 @@ +mod flake; +mod flakeref; +mod nix; + +pub use flake::{Flake, FlakeBuilder, FlakeLockMode}; +pub use flakeref::FlakeRef; +pub use nix::Nix; diff --git a/snow/src/nix/nix.rs b/snow/src/nix/nix.rs new file mode 100644 index 0000000..7b4eebb --- /dev/null +++ b/snow/src/nix/nix.rs @@ -0,0 +1,10 @@ +use std::path::Path; + +use anyhow::{Context, Result}; +use derive_more::From; +use nix_bindings_fetchers::FetchersSettings; +use nix_bindings_flake::{FlakeReference, FlakeReferenceParseFlags, FlakeSettings}; + +pub struct Nix {} + +impl Nix {}