From 7f0fd2c45b7d937833761801d222ac78fe2990ca Mon Sep 17 00:00:00 2001 From: JMARyA Date: Sun, 5 Jan 2025 03:03:53 +0100 Subject: [PATCH 1/3] fix --- installs/testinstall.toml | 3 ++- src/config.rs | 6 ++++-- src/install/first_boot.rs | 17 ++++++++++++----- src/install/skel.rs | 14 +++++++++++--- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/installs/testinstall.toml b/installs/testinstall.toml index ca5b5dd..de22446 100644 --- a/installs/testinstall.toml +++ b/installs/testinstall.toml @@ -14,7 +14,8 @@ mode = "Desktop" locale = "de_DE.UTF-8" # Keymap -keymap = "de-latin1" +keyboard_layout = "de" +keyboard_variant = "mac" # Timezone timezone = "Europe/Berlin" diff --git a/src/config.rs b/src/config.rs index d31b3b5..42fbc41 100644 --- a/src/config.rs +++ b/src/config.rs @@ -68,8 +68,10 @@ pub struct GeneralConfig { pub mode: InstallMode, /// System locale pub locale: String, - /// Keymap - pub keymap: String, + /// Keyboard Layout + pub keyboard_layout: String, + /// Keyboard Variant + pub keyboard_variant: Option, /// Timezone pub timezone: String, /// Hostname diff --git a/src/install/first_boot.rs b/src/install/first_boot.rs index f2e30e2..4cb450f 100644 --- a/src/install/first_boot.rs +++ b/src/install/first_boot.rs @@ -38,11 +38,7 @@ pub fn first_boot_values(conf: &GeneralConfig) { // Keymap print_status("Writing /etc/vconsole.conf"); - std::fs::write( - "/mnt/etc/vconsole.conf", - format!("KEYMAP=\"{}\"", conf.keymap), - ) - .unwrap(); + std::fs::write("/mnt/etc/vconsole.conf", build_vconsole_conf(conf)).unwrap(); // Hostname print_status("Writing /etc/hostname"); @@ -57,3 +53,14 @@ pub fn first_boot_values(conf: &GeneralConfig) { systemd_service_enable("NetworkManager.service"); } + +pub fn build_vconsole_conf(conf: &GeneralConfig) -> String { + let mut ret = format!("KEYMAP={}\n", conf.keyboard_layout); + ret.push_str(&format!("XBKLAYOUT={}\n", conf.keyboard_layout)); + + if let Some(variant) = &conf.keyboard_variant { + ret.push_str(&format!("XKBMODEL={variant}\n")); + } + + ret +} diff --git a/src/install/skel.rs b/src/install/skel.rs index 6cb3214..e4231da 100644 --- a/src/install/skel.rs +++ b/src/install/skel.rs @@ -1,6 +1,14 @@ -use crate::config::GeneralConfig; +use crate::{config::GeneralConfig, create_iso::build_kxkbrc, linux::install_file, print_status}; pub fn setup_skel(conf: &GeneralConfig) { - // TODO : Implement - unimplemented!() + print_status("Setting user config"); + std::fs::create_dir_all("/mnt/etc/skel/.config").unwrap(); + install_file( + "/mnt/etc/skel/.config/kxkbrc", + &build_kxkbrc( + conf.keyboard_layout.as_str(), + conf.keyboard_variant.as_ref().map(|x| x.as_str()), + ), + 0o644, + ); } From a3da9fb9ac04805b1be40db5397ce9f0fa8db61b Mon Sep 17 00:00:00 2001 From: JMARyA Date: Sun, 5 Jan 2025 04:33:47 +0100 Subject: [PATCH 2/3] refactor + ui --- installs/full.toml | 84 +++++++++++++++ installs/testinstall.toml | 57 ---------- src/config.rs | 17 ++- src/install/ssh.rs | 2 +- src/main.rs | 7 +- src/print.rs | 213 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 318 insertions(+), 62 deletions(-) create mode 100644 installs/full.toml delete mode 100644 installs/testinstall.toml create mode 100644 src/print.rs diff --git a/installs/full.toml b/installs/full.toml new file mode 100644 index 0000000..370e634 --- /dev/null +++ b/installs/full.toml @@ -0,0 +1,84 @@ +# Full Install Template + +# Drive Selection for Install +[drive] +# Device node for the EFI boot filesystem +boot = "/dev/null" + +# Device node for the root filesystem +root = "/dev/null" + +# Root filesystem encryption passphrase +# If this option is set the root filesystem will be encrypted with LUKS +encryption = "password" + +# General configuration +[general] +# Preset +mode = "Desktop" + +# System Locale +locale = "de_DE.UTF-8" + +# Keymap +keyboard_layout = "de" +keyboard_variant = "mac" + +# Timezone +timezone = "Europe/Berlin" + +# Hostname +hostname = "navos" + +[pkg] +# Additional packages +pkg = [ + "nano", + "micro" +] + +# Enable virtualization +virtualization = true + +# Enable docker +docker = true + +# User configuration +# The `[[user]]` directive can be repeated to create multiple users. +[[user]] +# Username +name = "testuser" + +# User password +password = "testpass" + +# Allow user to use `doas` as root +doas_root= true + +# SSH Configuration +# If `[ssh]` is set, openssh will be installed and enabled. +[ssh] +# Config file for sshd +# This file will be copied to the new system +sshd_config = "/etc/ssh/sshd_config" + +# Install a SSH keys +# To set multiple keys, repeat the `[[ssh.key]]` directive. +# Every key will be installed in the users respective `authorized_keys` file. +[[ssh.key]] +# The SSH Key +key = "ssh-rsa ... user@host" + +# The users allowed to login with this key +users = ["testuser", "root"] + +# Ollama Configuration +# If `[ai]` is set, ollama will be installed and enabled. +[ai] +# Install with CUDA supports +gpu = true + +# Pull LLMs +models = [ + "llama3.1:8b" +] diff --git a/installs/testinstall.toml b/installs/testinstall.toml deleted file mode 100644 index de22446..0000000 --- a/installs/testinstall.toml +++ /dev/null @@ -1,57 +0,0 @@ -# Drive Selection for Install -[drive] -boot = "/dev/null" -root = "/dev/null" - -# Use LUKS encryption on root drive -encryption = "password" - -[general] -# Preset -mode = "Desktop" - -# System Locale -locale = "de_DE.UTF-8" - -# Keymap -keyboard_layout = "de" -keyboard_variant = "mac" - -# Timezone -timezone = "Europe/Berlin" - -# Hostname -hostname = "navos" - -[pkg] -# Additional packages -pkg = [ - "nano", - "micro" -] - -# Enable virtualization -virtualization = true - -# Enable docker -docker = true - -[[user]] -# Username -name = "testuser" - -# User password -password = "testpass" - -# Allow user to use doas as root -doas_root= true - -# SSH Configuration -[ssh] -# Config file for sshd -sshd_config = "/etc/ssh/sshd_config" - -# Install a SSH key for the user as `authorized_keys` -[[ssh.key]] -key = "ssh-rsa ... user@host" -users = ["testuser", "root"] diff --git a/src/config.rs b/src/config.rs index 42fbc41..cc093c5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use serde::Deserialize; /// Declarative install configuration @@ -26,7 +28,7 @@ pub struct OllamaConfig { #[derive(Debug, Deserialize)] pub struct SSHConfig { pub sshd_config: Option, - pub key: Vec, + pub key: Option>, } #[derive(Debug, Deserialize)] @@ -90,3 +92,16 @@ pub enum InstallMode { // TODO : Evaluate Kiosk, } + +impl Display for InstallMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + InstallMode::Base => f.write_str("Base")?, + InstallMode::Desktop => f.write_str("Desktop")?, + InstallMode::Server => f.write_str("Server")?, + InstallMode::Kiosk => f.write_str("Kiosk")?, + } + + Ok(()) + } +} diff --git a/src/install/ssh.rs b/src/install/ssh.rs index 01f3b89..f1685de 100644 --- a/src/install/ssh.rs +++ b/src/install/ssh.rs @@ -14,7 +14,7 @@ pub fn setup_ssh(conf: Option) { install_file("/mnt/etc/ssh/sshd_config", &content, 0o644); } - for key in &conf.key { + for key in &conf.key.unwrap_or_default() { for user in &key.users { let path = if user == "root" { std::fs::create_dir_all("/root/.ssh").unwrap(); diff --git a/src/main.rs b/src/main.rs index 4f85aed..da97f49 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,9 +8,11 @@ mod create_iso; mod install; mod linux; mod pkg; +mod print; use create_iso::create_iso; use install::install; use linux::is_root; +use print::print_config; use yansi::{Color, Paint}; fn print_status(msg: &str) { @@ -97,9 +99,8 @@ fn main() { } }; - // TODO : Show config - println!("Config: {conf:?}"); - print!("\nDo you want to proceed with this configuration? (yes/no) "); + print_config(&conf); + print!("Do you want to proceed with this configuration? (yes/no) "); let mut input = String::new(); std::io::stdout().flush().expect("Error flushing stdout."); diff --git a/src/print.rs b/src/print.rs new file mode 100644 index 0000000..e8d066f --- /dev/null +++ b/src/print.rs @@ -0,0 +1,213 @@ +use yansi::{Color, Paint}; + +use crate::config::InstallConfig; + +pub fn print_config(conf: &InstallConfig) { + println!("🚀 Install Configuration:"); + + let mut root_info = Tree::new(); + + let mut drive_info = Tree::new(); + drive_info.add_str(format!( + "💾 {} {}", + conf.drive.boot.paint(Color::Red), + "[EFI]".paint(Color::Blue) + )); + drive_info.add_str(format!( + "{} {} {}", + if conf.drive.encryption.is_some() { + "🔒" + } else { + "💾" + }, + conf.drive.root.paint(Color::Red), + "[ROOT]".paint(Color::Blue) + )); + + root_info.add_tree("💾 Drive Selection", drive_info); + + let mut general_info = Tree::new(); + + general_info.add_str(format!( + "💎 {} {}", + "Mode:".paint(Color::Yellow), + conf.general.mode + )); + general_info.add_str(format!( + "🖥️ {} {}", + "Hostname:".paint(Color::Yellow), + conf.general.hostname + )); + general_info.add_str(format!( + "⌨️ {} {} {}", + "Keyboard:".paint(Color::Yellow), + conf.general.keyboard_layout, + conf.general + .keyboard_variant + .as_ref() + .unwrap_or(&String::new()) + )); + general_info.add_str(format!( + "🌍 {} {}", + "Locale:".paint(Color::Yellow), + conf.general.locale + )); + general_info.add_str(format!( + "⌛ {} {}", + "Timezone:".paint(Color::Yellow), + conf.general.timezone + )); + + root_info.add_tree("🔨 General", general_info); + + let mut pkg_info = Tree::new(); + + if conf.pkg.docker { + pkg_info.add_str(format!("🐳 Docker {}", "✔️".paint(Color::Green))); + } + + if conf.pkg.virtualization { + pkg_info.add_str(format!("🎃 Virtualization {}", "✔️".paint(Color::Green))); + } + + if !conf.pkg.pkg.is_empty() { + pkg_info.add_str(format!( + "📦 Additional packages: {}", + conf.pkg.pkg.join(" ") + )); + } + + root_info.add_tree("📦 Packages", pkg_info); + + let mut users_info = Tree::new(); + + for user in &conf.user { + users_info.add_str(format!( + "👤 {} {}", + user.name, + if user.doas_root { "🔑" } else { "" } + )); + } + + root_info.add_tree("🧛 Users", users_info); + + if let Some(ssh_conf) = &conf.ssh { + let mut ssh_info = Tree::new(); + + if let Some(sshd_conf) = &ssh_conf.sshd_config { + ssh_info.add_str(format!("📖 Installing {sshd_conf} as sshd_config")); + } + + if let Some(keys) = &ssh_conf.key { + for key in keys { + let key_origin = key.key.split(" ").nth(2).unwrap_or_default(); + + ssh_info.add_str(format!( + "🔑 Key {} for {}", + key_origin.paint(Color::Blue), + key.users.join(", ").paint(Color::Yellow) + )); + } + } + + root_info.add_tree("🌐 SSH", ssh_info); + } + + if let Some(ai_conf) = &conf.ai { + let mut ai_info = Tree::new(); + + if ai_conf.gpu { + ai_info.add_str(format!("🟩 Use CUDA {}", "✔️".paint(Color::Green))); + } + + if let Some(models) = &ai_conf.models { + ai_info.add_str(format!("⬇️ Pull Models: {}", models.join(", "))); + } + + root_info.add_tree("🦙 Ollama", ai_info); + } + + println!("{}", root_info.render(0)); +} + +pub struct Tree { + elements: Vec, +} + +pub enum TreeEntry { + Tree(String, Tree), + Str(String), +} + +impl Tree { + pub fn new() -> Self { + Self { elements: vec![] } + } + + pub fn add_tree(&mut self, key: &str, item: Tree) { + self.elements.push(TreeEntry::Tree(key.to_string(), item)); + } + + pub fn add_str(&mut self, item: String) { + self.elements.push(TreeEntry::Str(item)); + } + + pub fn render(&self, depth: i32) -> String { + const DEPTH_INCREASE: i32 = 3; + + let mut ret = String::new(); + + let len = self.elements.len(); + + for (index, val) in self.elements.iter().enumerate() { + let padding = pad_with_bars(depth, DEPTH_INCREASE); + + let path_repr = if (index + 1) == len { + if matches!(val, TreeEntry::Tree(_, _)) { + "├─" + } else { + "└─" + } + } else { + "├─" + }; + + match val { + TreeEntry::Tree(key, tree) => { + if !tree.elements.is_empty() { + ret.push_str(&format!("{padding}{path_repr} {key}\n")); + ret.push_str(&tree.render(depth + DEPTH_INCREASE)) + } + } + TreeEntry::Str(str) => { + ret.push_str(&format!("{padding}{path_repr} {str}\n")); + } + } + } + + ret + } +} + +pub fn pad_with_bars(n: i32, depth_inc: i32) -> String { + let amount = n / depth_inc; + + let mut ret = String::new(); + + if amount >= 1 { + ret.push_str("│"); + } + + ret.push_str(&pad(n - amount)); + ret +} + +pub fn pad(n: i32) -> String { + let mut ret = String::new(); + + for _ in 0..n { + ret.push_str(" "); + } + + ret +} From 40a1498c6f3a89a07c862767499124ac9931797d Mon Sep 17 00:00:00 2001 From: JMARyA Date: Sun, 5 Jan 2025 04:48:28 +0100 Subject: [PATCH 3/3] root pw + min --- installs/full.toml | 3 +++ installs/min.toml | 38 ++++++++++++++++++++++++++++++++++++++ src/config.rs | 8 +++++--- src/install/first_boot.rs | 6 +++++- src/install/mod.rs | 6 +++--- src/install/user.rs | 10 +++++----- src/print.rs | 16 +++++++++++++--- 7 files changed, 72 insertions(+), 15 deletions(-) create mode 100644 installs/min.toml diff --git a/installs/full.toml b/installs/full.toml index 370e634..42e8a1f 100644 --- a/installs/full.toml +++ b/installs/full.toml @@ -30,6 +30,9 @@ timezone = "Europe/Berlin" # Hostname hostname = "navos" +# Root password +root_password = "root" + [pkg] # Additional packages pkg = [ diff --git a/installs/min.toml b/installs/min.toml new file mode 100644 index 0000000..e2185ab --- /dev/null +++ b/installs/min.toml @@ -0,0 +1,38 @@ +# Minimal Install Template + +# Drive Selection for Install +[drive] +# Device node for the EFI boot filesystem +boot = "/dev/null" + +# Device node for the root filesystem +root = "/dev/null" + +# Root filesystem encryption passphrase +# If this option is set the root filesystem will be encrypted with LUKS +encryption = "password" + +# General configuration +[general] +# Preset +mode = "Base" + +# System Locale +locale = "de_DE.UTF-8" + +# Keymap +keyboard_layout = "de" +keyboard_variant = "mac" + +# Timezone +timezone = "Europe/Berlin" + +# Hostname +hostname = "navos_min" + +# Root password +root_password = "root" + +[pkg] +# Additional packages +pkg = [] diff --git a/src/config.rs b/src/config.rs index cc093c5..710fffd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -12,7 +12,7 @@ pub struct InstallConfig { /// Package Configuration pub pkg: PackageConfig, /// User Configuration - pub user: Vec, + pub user: Option>, /// SSH Configuration pub ssh: Option, /// Ollama AI Config @@ -49,9 +49,9 @@ pub struct PackageConfig { /// Packages to install pub pkg: Vec, /// Enable libvirt - pub virtualization: bool, + pub virtualization: Option, /// Enable docker - pub docker: bool, + pub docker: Option, } #[derive(Debug, Deserialize)] @@ -78,6 +78,8 @@ pub struct GeneralConfig { pub timezone: String, /// Hostname pub hostname: String, + // Root password + pub root_password: Option, } #[derive(Debug, Deserialize)] diff --git a/src/install/first_boot.rs b/src/install/first_boot.rs index 4cb450f..93dcf4f 100644 --- a/src/install/first_boot.rs +++ b/src/install/first_boot.rs @@ -6,7 +6,7 @@ use crate::{ print_status, }; -use super::uncomment_first_value_of; +use super::{uncomment_first_value_of, user::change_passwd}; /// Generate the `/etc/fstab` file pub fn genfstab() { @@ -51,6 +51,10 @@ pub fn first_boot_values(conf: &GeneralConfig) { arch_chroot(&["hwclock", "--systohc"], None, false); + if let Some(root_pw) = &conf.root_password { + change_passwd("root", root_pw); + } + systemd_service_enable("NetworkManager.service"); } diff --git a/src/install/mod.rs b/src/install/mod.rs index e9f0fc6..a197d79 100644 --- a/src/install/mod.rs +++ b/src/install/mod.rs @@ -88,7 +88,7 @@ pub fn install(conf: InstallConfig) { // System Setup first_boot_values(&conf.general); setup_skel(&conf.general); - setup_users(&conf.user); + setup_users(&conf.user.unwrap_or_default()); setup_ssh(conf.ssh); @@ -118,11 +118,11 @@ pub fn install(conf: InstallConfig) { } } - if conf.pkg.virtualization { + if conf.pkg.virtualization.unwrap_or_default() { // TODO : Enable virtualization } - if conf.pkg.docker { + if conf.pkg.docker.unwrap_or_default() { // TODO : Enable docker } diff --git a/src/install/user.rs b/src/install/user.rs index 0152dd9..de8c82c 100644 --- a/src/install/user.rs +++ b/src/install/user.rs @@ -4,6 +4,10 @@ use crate::{ print_status, }; +pub fn change_passwd(user: &str, pw: &str) { + arch_chroot(&["passwd", user], Some(&format!("{}\n{}\n", pw, pw)), false); +} + /// Setup the users of the system pub fn setup_users(conf: &[UserConfig]) { let mut doas_conf = String::new(); @@ -11,11 +15,7 @@ pub fn setup_users(conf: &[UserConfig]) { for user in conf { arch_chroot(&["useradd", "-m", &user.name], None, false); - arch_chroot( - &["passwd", &user.name], - Some(&format!("{}\n{}\n", user.password, user.password)), - false, - ); + change_passwd(&user.name, &user.password); if user.doas_root { print_status(&format!("Allowing root doas for {}", user.name)); diff --git a/src/print.rs b/src/print.rs index e8d066f..b8b3594 100644 --- a/src/print.rs +++ b/src/print.rs @@ -57,16 +57,23 @@ pub fn print_config(conf: &InstallConfig) { "Timezone:".paint(Color::Yellow), conf.general.timezone )); + if conf.general.root_password.is_some() { + general_info.add_str(format!( + "🔑 {} {}", + "Root Password".paint(Color::Yellow), + "✔️".paint(Color::Green) + )); + } root_info.add_tree("🔨 General", general_info); let mut pkg_info = Tree::new(); - if conf.pkg.docker { + if conf.pkg.docker.unwrap_or_default() { pkg_info.add_str(format!("🐳 Docker {}", "✔️".paint(Color::Green))); } - if conf.pkg.virtualization { + if conf.pkg.virtualization.unwrap_or_default() { pkg_info.add_str(format!("🎃 Virtualization {}", "✔️".paint(Color::Green))); } @@ -81,7 +88,10 @@ pub fn print_config(conf: &InstallConfig) { let mut users_info = Tree::new(); - for user in &conf.user { + let empty = Vec::new(); + let user_conf = conf.user.as_ref().unwrap_or(&empty); + + for user in user_conf { users_info.add_str(format!( "👤 {} {}", user.name,