From 4c1993143c7a4708b56b3d90f86f37081ba4062e Mon Sep 17 00:00:00 2001 From: JMARyA Date: Wed, 25 Dec 2024 21:05:49 +0100 Subject: [PATCH] init --- .gitignore | 1 + Cargo.lock | 183 ++++++++++++++++++++ Cargo.toml | 9 + README.md | 7 + installs/testinstall.toml | 12 ++ src/config.rs | 43 +++++ src/main.rs | 277 +++++++++++++++++++++++++++++++ src/root/mkinitcpio/linux.preset | 16 ++ src/root/zram-generator.conf | 3 + 9 files changed, 551 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 installs/testinstall.toml create mode 100644 src/config.rs create mode 100644 src/main.rs create mode 100644 src/root/mkinitcpio/linux.preset create mode 100644 src/root/zram-generator.conf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e0636ce --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,183 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "navinstall" +version = "0.1.0" +dependencies = [ + "nix", + "serde", + "toml", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.216" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.216" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "syn" +version = "2.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..402f3dd --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "navinstall" +version = "0.1.0" +edition = "2024" + +[dependencies] +nix = { version = "0.29.0", features = ["user"] } +serde = { version = "1.0.216", features = ["derive"] } +toml = "0.8.19" diff --git a/README.md b/README.md new file mode 100644 index 0000000..43259fe --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# NavInstall + +## Features +- Install different configs +- Install with JSON file +- Install with CLI +- Install with GUI diff --git a/installs/testinstall.toml b/installs/testinstall.toml new file mode 100644 index 0000000..516a8e2 --- /dev/null +++ b/installs/testinstall.toml @@ -0,0 +1,12 @@ +[drive] +boot = "/dev/null" +root = "/dev/null" + +[general] +encryption = true +mode = "Desktop" +locale = "de_DE.UTF-8" +pkg = [ + "nano", + "micro" +] diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..7070da5 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,43 @@ +use serde::Deserialize; + +/// Declarative install configuration +#[derive(Debug, Deserialize)] +pub struct InstallConfig { + /// Drive Configuration + pub drive: DriveConfig, + /// General Configuration + pub general: GeneralConfig, +} + +#[derive(Debug, Deserialize)] +pub struct DriveConfig { + /// Boot Drive Path + pub boot: String, + /// Root Drive Path + pub root: String, +} + +#[derive(Debug, Deserialize)] +pub struct GeneralConfig { + /// Enable encryption on root + pub encryption: bool, + /// Presets + pub mode: InstallMode, + // System locale + pub locale: String, + // Packages to install + pub pkg: Vec, +} + +#[derive(Debug, Deserialize)] +pub enum InstallMode { + /// Basic Arch Linux Installation + Base, + /// navOS Desktop + Desktop, + /// navOS Server + Server, + + // TODO : Evaluate + Kiosk, +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..94a150e --- /dev/null +++ b/src/main.rs @@ -0,0 +1,277 @@ +use std::io::Write; + +use config::{DriveConfig, GeneralConfig, InstallConfig}; +use nix::unistd::{Uid, getuid}; + +mod config; + +fn is_root() -> bool { + getuid() == Uid::from_raw(0) +} + +// DRIVE SELECTION + +pub fn str_vec(v: Vec<&str>) -> Vec { + v.into_iter().map(|x| x.to_string()).collect() +} + +pub fn format_drives(conf: &DriveConfig) { + // EFI (BOOT) + run_command( + &str_vec(vec!["mkfs.vfat", "-F", "32", conf.boot.as_str()]), + None, + false, + ); + + // ROOT + run_command( + &str_vec(vec!["cryptsetup", "luksFormat", conf.root.as_str()]), + None, + true, + ); +} + +// MOUNT + +pub fn mount_drives(conf: &DriveConfig) { + run_command( + &str_vec(vec!["cryptsetup", "open", conf.root.as_str(), "root"]), + None, + true, + ); + + run_command( + &str_vec(vec!["mount", "/dev/mapper/root", "/mnt"]), + None, + false, + ); + + // TODO : Secure mount options + run_command( + &str_vec(vec!["mount", "--mkdir", conf.boot.as_str(), "/mnt/boot"]), + None, + false, + ); +} + +// PACSTRAP + +pub fn pacstrap(conf: &GeneralConfig) { + // TODO : Modes install + pkgs + + let mut cmd: Vec = vec!["pacstrap".into(), "-K".into(), "/mnt".into(), "base".into()]; + + cmd.extend(conf.pkg.clone()); + + run_command(&cmd, None, true); +} + +// GENFSTAB + +pub fn genfstab() { + let (stdout, _) = run_command(&str_vec(vec!["genfstab", "-U", "/mnt"]), None, false); + std::fs::write("/mnt/etc/fstab", stdout).unwrap(); +} + +pub fn first_boot_values(conf: &GeneralConfig) { + // CHROOT + + // SYSTEMD-FIRSTBOOT + + // LOCALE + // TODO : Logic for uncommenting a value + std::fs::write("/etc/locale.gen", &conf.locale).unwrap(); + run_command(&str_vec(vec!["locale-gen"]), None, false); +} + +pub fn setup_zram() { + // arch-chroot /mnt pacman -S zram-generator + std::fs::write( + "/mnt/etc/systemd/zram-generator.conf", + include_str!("root/zram-generator.conf"), + ) + .unwrap(); + // arch-chroot /mnt systemctl enable --now systemd-zram-setup@zram0.service +} + +// MKINITCPIO + UKI + +pub fn setup_mkinitcpio() { + std::fs::write( + "/mnt/etc/mkinitcpio.d/linux.preset", + include_str!("root/mkinitcpio/linux.preset"), + ) + .unwrap(); + run_command(&str_vec(vec!["mkinitcpio", "--allpresets"]), None, true); +} + +// SECURE BOOT + +pub fn setup_secure_boot() { + // TODO : Assert sb setup mode + + run_command(&vec!["sbctl".into(), "create-keys".into()], None, false); + + // TODO : Sign + Enroll +} + +// MODS + +/// Post Installer + +// TPM Unlock + +fn install(conf: InstallConfig) { + format_drives(&conf.drive); + mount_drives(&conf.drive); + pacstrap(&conf.general); + first_boot_values(&conf.general); + // install bootloader + setup_secure_boot(); + setup_mkinitcpio(); +} + +fn run_command(cmd: &[String], input: Option<&str>, inherit: bool) -> (String, String) { + println!("--> {}", cmd.join(" ")); + + let mut cmd_setup = std::process::Command::new(cmd[0].clone()); + let mut cmd_setup = cmd_setup.args(cmd.into_iter().skip(1).collect::>()); + + if inherit { + assert!(input.is_none()); + cmd_setup = cmd_setup + .stdout(std::process::Stdio::inherit()) + .stdin(std::process::Stdio::inherit()); + } else { + cmd_setup = cmd_setup.stdout(std::process::Stdio::piped()); + } + + if input.is_some() { + cmd_setup = cmd_setup.stdin(std::process::Stdio::piped()); + } + + let mut child = cmd_setup.spawn().unwrap(); + + if let Some(input) = input { + let stdin = child.stdin.as_mut().unwrap(); + stdin.write_all(input.as_bytes()).unwrap(); + stdin.flush().unwrap(); + } + + let status = child.wait_with_output().unwrap(); + assert!(status.status.success()); + + let output = String::from_utf8(status.stdout).unwrap(); + let stderr = String::from_utf8(status.stderr).unwrap(); + + if !stderr.trim().is_empty() { + if !inherit { + eprintln!("{}", stderr); + } + } + + if !inherit { + println!("{}", output); + } + + (output, stderr) +} + +fn main() { + println!("⚠️ Warning: This is an alpha version of the installer. DO NOT USE in PROD"); + + let args: Vec = std::env::args().collect(); + + // TODO : Cleanup CLI interface + + if args.get(1).unwrap() == "create-iso" { + create_iso(); + std::process::exit(0); + } + + if args.len() < 2 { + eprintln!("Error: No configuration file provided."); + std::process::exit(1); + } + + let config_file = &args[1]; + let config_content = std::fs::read_to_string(config_file); + + let conf: InstallConfig = match config_content { + Ok(content) => match toml::from_str(&content) { + Ok(config) => config, + Err(e) => { + eprintln!("Error: Could not deserialize TOML file. {e}"); + std::process::exit(1); + } + }, + Err(_) => { + eprintln!("Error: Could not read config file."); + std::process::exit(1); + } + }; + + // TODO : Show config + println!("Config: {conf:?}"); + println!("\nDo you want to proceed with this configuration? (yes/no)"); + + let mut input = String::new(); + std::io::stdout().flush().expect("Error flushing stdout."); + std::io::stdin() + .read_line(&mut input) + .expect("Error reading input."); + let input = input.trim().to_lowercase(); + + if input != "yes" { + println!("Installation aborted."); + std::process::exit(0); + } + + // Run the + install(conf) +} + +pub fn create_iso() { + // TODO : Check if root + if !is_root() { + eprintln!("Error: You need root to create an ISO"); + std::process::exit(1); + } + + if !std::fs::exists("./iso").unwrap() { + let cmd = str_vec(vec!["git", "clone", "https://git.hydrar.de/navos/iso"]); + run_command(&cmd, None, false); + } + + std::fs::create_dir_all("./work").unwrap(); + + let mount_cmd = str_vec(vec![ + "mount", "-t", "tmpfs", "-o", "size=10G", "tmpfs", "./work", + ]); + + run_command(&mount_cmd, None, false); + + let mkarchiso_cmd = vec![ + "mkarchiso".to_string(), + "-v".to_string(), + "-w".to_string(), + "./work".to_string(), + "-o".to_string(), + "./".to_string(), + "./iso".to_string(), + ]; + + run_command(&mkarchiso_cmd, None, true); + + let umount_cmd = str_vec(vec!["umount", "-r", "./work"]); + + run_command(&umount_cmd, None, false); + + std::fs::remove_dir_all("./work").unwrap(); +} + +// TODO : CLI +// navos create-iso +// navos create-tar +// navos create-img +// navos install diff --git a/src/root/mkinitcpio/linux.preset b/src/root/mkinitcpio/linux.preset new file mode 100644 index 0000000..f4a3b44 --- /dev/null +++ b/src/root/mkinitcpio/linux.preset @@ -0,0 +1,16 @@ +# mkinitcpio preset file for the 'linux' package + +#ALL_config="/etc/mkinitcpio.conf" +ALL_kver="/boot/vmlinuz-linux" + +PRESETS=('default' 'fallback') + +#default_config="/etc/mkinitcpio.conf" +default_image="/boot/initramfs-linux.img" +default_uki="/boot/EFI/Linux/arch-linux.efi" +#default_options="--splash /usr/share/systemd/bootctl/splash-arch.bmp" + +#fallback_config="/etc/mkinitcpio.conf" +fallback_image="/boot/initramfs-linux-fallback.img" +fallback_uki="/boot/EFI/Linux/arch-linux-fallback.efi" +fallback_options="-S autodetect" \ No newline at end of file diff --git a/src/root/zram-generator.conf b/src/root/zram-generator.conf new file mode 100644 index 0000000..dd997c9 --- /dev/null +++ b/src/root/zram-generator.conf @@ -0,0 +1,3 @@ +[zram0] +zram-size = ram / 2 +compression-algorithm = zstd