···11-use std::{
22- fs,
33- path::Path,
44- process::{Command, Stdio},
55-};
66-77-use inquire::{validator::Validation, Confirm, Editor, MultiSelect, Select, Text};
88-99-use crate::prelude::*;
1010-1111-fn list_roles(roles_folder: &Path) -> Result<Vec<String>> {
1212- Ok(fs::read_dir(roles_folder)
1313- .context("Failed to read roles")?
1414- .into_iter()
1515- .filter_map(|r| {
1616- r.ok()
1717- .map(|d| d.file_name().to_string_lossy().trim_end_matches(".nix").to_string())
1818- .filter(|n| !n.contains('+'))
1919- })
2020- .collect())
2121-}
2222-2323-const NO_HARDWARE_MOD: &str = "No NixOS Hardware Module";
2424-2525-fn list_hardware_modules() -> Result<Vec<String>> {
2626- let mut cmd = Command::new("nix");
2727- cmd.arg("eval")
2828- .arg("github:nixos/nixos-hardware#nixosModules")
2929- .arg("--raw")
3030- .arg("--apply")
3131- .arg("s: builtins.toJSON (builtins.attrNames s)")
3232- .stdout(Stdio::piped())
3333- .stderr(Stdio::null());
3434-3535- let output = cmd.output().context("Failed to fetch systems")?.stdout;
3636- let mut modules: Vec<String> = serde_json::from_slice(&output).context("Failed to parse systems")?;
3737-3838- modules.insert(0, NO_HARDWARE_MOD.into());
3939-4040- Ok(modules)
4141-}
4242-4343-fn list_targets() -> Result<Vec<String>> {
4444- let mut cmd = Command::new("nix");
4545- cmd.arg("eval")
4646- .arg("nixpkgs#lib.systems.flakeExposed")
4747- .arg("--raw")
4848- .arg("--apply")
4949- .arg("builtins.toJSON")
5050- .stdout(Stdio::piped())
5151- .stderr(Stdio::null());
5252-5353- let output = cmd.output().context("Failed to fetch systems")?.stdout;
5454- let modules = serde_json::from_slice(&output).context("Failed to parse systems")?;
5555- Ok(modules)
5656-}
5757-5858-fn get_nixpkgs_version() -> Result<String> {
5959- let mut cmd = Command::new("nix");
6060- cmd.arg("eval")
6161- .arg("nixpkgs#lib.version")
6262- .arg("--raw")
6363- .stdout(Stdio::piped())
6464- .stderr(Stdio::null());
6565-6666-6767- let output = cmd.output().context("Failed to fetch systems")?.stdout;
6868- let output = String::from_utf8_lossy(&output);
6969-7070- let otp = output.trim().split('.').take(2).collect::<Vec<_>>();
7171-7272- Ok(otp.join("."))
7373-}
7474-7575-7676-fn gen_hardware_config(root: Option<&Path>) -> Result<String> {
7777- let mut cmd = Command::new("nixos-generate-config");
7878- cmd.arg("--show-hardware-config").stdout(Stdio::piped());
7979-8080- if let Some(root) = root {
8181- cmd.arg("--root").arg(root);
8282- }
8383-8484- let output = cmd.output().context("Failed to fetch systems")?.stdout;
8585- let output = String::from_utf8_lossy(&output);
8686-8787- let otp = output.trim().split('\n').skip(3).collect::<Vec<_>>();
8888- Ok(format!("({})", otp.join("\n")))
8989-}
9090-9191-struct System {
9292- name: String,
9393- description: String,
9494- edition: String,
9595- target: String,
9696- include_base_mods: bool,
9797- roles: Vec<String>,
9898- hardware_config: String,
9999- hardware_mod: String,
100100-}
101101-102102-fn dialog(flake_root: &Path) -> Result<System> {
103103- // Name
104104-105105- let fl_root_val = flake_root.to_owned();
106106-107107- let system_filter = move |name: &str| {
108108- Ok(if !name.is_empty() && name.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-') {
109109- if fl_root_val.join("systems").join(format!("{name}.nix")).is_file() {
110110- Validation::Invalid("System config already exists!".into())
111111- } else {
112112- Validation::Valid
113113- }
114114- } else {
115115- Validation::Invalid("System configs should use kebab-case".into())
116116- })
117117- };
118118-119119- let system_name = Text::new("Enter a name for the new system")
120120- .with_validator(system_filter)
121121- .prompt()
122122- .context("Failed to get system name")?;
123123-124124- // Target
125125-126126- let targets = list_targets().context("Failed to get targets")?;
127127- let system_target = Select::new("Select what arch/OS this system will target", targets)
128128- .prompt()
129129- .context("Failed to prompt for system target")?;
130130-131131- // Description
132132-133133- let description = Text::new("Enter system description")
134134- .prompt_skippable()
135135- .context("Failed to prompt for description")?
136136- .unwrap_or_else(|| "Generic".to_string());
137137-138138- // Base Mods
139139-140140- let include_base_mods = Confirm::new("Should this system include base modules?")
141141- .with_default(true)
142142- .prompt()
143143- .context("Failed to prompt")?;
144144-145145- // Generate Hardware Config?
146146-147147- let gen_hw_config = Confirm::new("Would you like to auto-generate a hardware configuration for this system with `nixos-generate-config`?").prompt().context("Failed to prompt")?;
148148- let hw_config = if gen_hw_config {
149149- gen_hardware_config(None).context("Failed to generate hw config")?
150150- } else {
151151- String::new()
152152- };
153153-154154- // NixOS Hardware
155155-156156- let hardware_mod = if let Ok(modules) = list_hardware_modules() {
157157- let hardware_mod = Select::new("(Optional) Select a NixOS Hardware Module for the System", modules).with_starting_cursor(0).prompt().context("Failed to prompt for hw modules")?;
158158- if hardware_mod == NO_HARDWARE_MOD {
159159- String::new()
160160- } else {
161161- hardware_mod
162162- }
163163- } else {
164164- println!("Failed to fetch modules from NixOS hardware, continuing...");
165165- String::new()
166166- };
167167-168168- // Asking For System Roles
169169-170170- let roles_path = flake_root.join("roles");
171171- let roles = list_roles(&roles_path).context("Failed to list roles")?;
172172- let selected_roles = MultiSelect::new("Select the roles for this new system", roles)
173173- .prompt()
174174- .context("Failed to promp user for roles")?;
175175-176176- Ok(System {
177177- name: system_name,
178178- description: description,
179179- target: system_target,
180180- roles: selected_roles.into_iter().map(|r| format!("\"{r}\"")).collect(),
181181- edition: get_nixpkgs_version().context("Failed to get latest")?,
182182- include_base_mods,
183183- hardware_config: hw_config,
184184- hardware_mod,
185185- })
186186-}
187187-188188-impl System {
189189-190190- const FILE_TEMPLATE: &str = include_str!("sys_template.nix");
191191-192192- fn generate_file(&self) -> String {
193193- Self::FILE_TEMPLATE
194194- .replace("__TARGET__", &self.target)
195195- .replace("__DESCRIPTION__", &self.description)
196196- .replace("__EDITION__", &self.edition)
197197- .replace("__INCL_BASE_MODS__", &self.include_base_mods.to_string())
198198- .replace("__ROLES__", &self.roles.join(" "))
199199- .replace("__HARDWARE_MOD__", &self.hardware_mod)
200200- .replace("__HARDWARE_CONFIG__", &self.hardware_config)
201201- }
202202-203203-}
204204-205205-pub fn add_system_dialog(flake_root: &Path) -> Result {
206206- let sys = dialog(flake_root)?;
207207-208208- let file = sys.generate_file();
209209-210210- let edited_file = Editor::new("Review the new system file with (e), confirm with (enter)").with_predefined_text(&file).with_file_extension(".nix").prompt().context("Failed to get edits")?;
211211-212212- let path = flake_root.join("systems").join(format!("{}.nix", sys.name));
213213-214214- println!("Saving New File to {}", path.display());
215215-216216- fs::write(path, &edited_file).context("Failed to write new file")
217217-}
-22
create-sys/src/main.rs
···11-use clap::clap_derive::Parser;
22-33-mod add;
44-55-mod prelude {
66- use anyhow::Error as AError;
77- use std::result::Result as SResult;
88-99- pub type Result<T = (), E = AError> = SResult<T, E>;
1010- pub use anyhow::Context;
1111-}
1212-1313-use prelude::*;
1414-1515-#[derive(Parser)]
1616-struct Cli {}
1717-1818-fn main() -> Result {
1919- let dir = std::env::current_dir()?;
2020- let dir = dir.parent().context("")?;
2121- add::add_system_dialog(dir)
2222-}
···8787 mod,
8888 dispatch,
8989 }:
9090- builtins.genList (i: let
9090+ builtins.genList
9191+ (i: let
9192 num = builtins.toString i;
9293 in "${mod},${num},${dispatch},${
9394 if num == "0"