This commit is contained in:
JMARyA 2024-12-28 01:19:44 +01:00
parent eabd898ccf
commit f2b1a43f62
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
13 changed files with 453 additions and 390 deletions

7
Cargo.lock generated
View file

@ -150,6 +150,7 @@ dependencies = [
"nix",
"serde",
"toml",
"yansi",
]
[[package]]
@ -355,3 +356,9 @@ checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
dependencies = [
"memchr",
]
[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"

View file

@ -8,3 +8,4 @@ clap = { version = "4.5.23", features = ["cargo"] }
nix = { version = "0.29.0", features = ["user"] }
serde = { version = "1.0.216", features = ["derive"] }
toml = "0.8.19"
yansi = "1.0.1"

View file

@ -1,382 +0,0 @@
// TODO : Setup ssh (config + authorized_keys)
// TODO : Setup virtualization
// TODO : Setup docker
// TODO : Autojoin docker swarm
// TODO : Autojoin teleport
// DRIVE SELECTION
use crate::{
config::{DriveConfig, GeneralConfig, InstallConfig, PackageConfig, UserConfig},
pkg::{self, install_pkgs},
run_command,
};
pub fn str_vec(v: Vec<&str>) -> Vec<String> {
v.into_iter().map(|x| x.to_string()).collect()
}
pub fn format_drives(conf: &DriveConfig, encrypted: bool) {
// EFI (BOOT)
run_command(
&str_vec(vec!["mkfs.vfat", "-F", "32", conf.boot.as_str()]),
None,
false,
);
// ROOT
if encrypted {
run_command(
&str_vec(vec!["cryptsetup", "luksFormat", conf.root.as_str()]),
None,
true,
);
} else {
run_command(&str_vec(vec!["mkfs.ext4", conf.root.as_str()]), None, false);
}
}
// MOUNT
pub fn mount_drives(conf: &DriveConfig, encrypted: bool) {
if encrypted {
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,
);
} else {
run_command(
&str_vec(vec!["mount", conf.root.as_str(), "/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: &PackageConfig) {
let mut cmd: Vec<String> = vec![
"pacstrap".into(),
"-K".into(),
"/mnt".into(),
"base".into(),
"linux".into(),
"linux-firmware".into(),
"linux-headers".into(),
"git".into(),
"networkmanager".into(),
"nano".into(),
"doas".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
run_command(
&vec![
"arch-chroot".into(),
"/mnt".into(),
"systemd-firstboot".into(),
format!("--locale={}", conf.locale),
format!("--keymap={}", conf.keymap),
format!("--timezone={}", conf.timezone),
format!("--hostname={}", conf.hostname),
],
None,
false,
);
// LOCALE
uncomment_first_value_of(&conf.locale, "/mnt/etc/locale.gen");
run_command(
&str_vec(vec!["arch-chroot", "/mnt", "locale-gen"]),
None,
false,
);
run_command(
&str_vec(vec!["arch-chroot", "/mnt", "hwclock", "--systohc"]),
None,
false,
);
run_command(
&str_vec(vec![
"arch-chroot",
"/mnt",
"systemctl",
"enable",
"NetworkManager.service",
]),
None,
false,
);
}
pub fn uncomment_first_value_of(value: &str, file: &str) {
// read in the file
let content = std::fs::read_to_string(file).unwrap();
let mut new = String::new();
let mut found = false;
// search for the first instance of `value` in the file
// uncomment the '#' symbol if there is one
for line in content.lines() {
if line.contains(value) && !found {
new.push_str(&format!("{}\n", line.replace("#", "")));
found = true;
} else {
new.push_str(&format!("{line}\n"));
}
}
// write back
std::fs::write(file, new).unwrap();
}
pub fn setup_zram() {
install_pkgs(&["zram-generator"]);
std::fs::write(
"/mnt/etc/systemd/zram-generator.conf",
include_str!("root/zram-generator.conf"),
)
.unwrap();
run_command(
&str_vec(vec![
"arch-chroot",
"/mnt",
"systemctl",
"enable",
"systemd-zram-setup@zram0.service",
]),
None,
false,
);
}
// MKINITCPIO + UKI
pub fn setup_mkinitcpio() {
std::fs::write(
"/mnt/etc/mkinitcpio.d/linux.preset",
include_str!("root/mkinitcpio/linux.preset"),
)
.unwrap();
// TODO : more configs
std::fs::write(
"/mnt/etc/mkinitcpio.conf",
include_str!("root/mkinitcpio.conf"),
)
.unwrap();
run_command(
&str_vec(vec!["arch-chroot", "/mnt", "mkinitcpio", "--allpresets"]),
None,
true,
);
}
// SECURE BOOT
pub fn setup_secure_boot() {
// TODO : Assert sb setup mode
let (stdout, _) = run_command(&str_vec(vec!["sbctl", "status"]), None, false);
let binding = stdout.lines().collect::<Vec<&str>>();
let status = binding.get(2).unwrap();
if !status.contains("Setup Mode") {
println!("[!] Secure Boot is not in Setup Mode");
std::process::exit(1);
} else {
if !status.contains("Enabled") {
println!("[!] Secure Boot is not in Setup Mode");
std::process::exit(1);
}
}
run_command(&vec!["sbctl".into(), "create-keys".into()], None, false);
run_command(
&str_vec(vec!["sbctl", "enroll-keys", "--microsoft"]),
None,
false,
);
run_command(
&str_vec(vec![
"sbctl",
"sign",
"-s",
"/boot/EFI/Linux/arch-linux.efi",
]),
None,
false,
);
run_command(
&str_vec(vec![
"sbctl",
"sign",
"-s",
"/boot/EFI/Linux/arch-linux-fallback.efi",
]),
None,
false,
);
run_command(
&str_vec(vec![
"sbctl",
"sign",
"-s",
"/boot/EFI/systemd/systemd-bootx64.efi",
]),
None,
false,
);
run_command(
&str_vec(vec!["sbctl", "sign", "-s", "/boot/EFI/Boot/bootx64.efi"]),
None,
false,
);
run_command(&str_vec(vec!["sbctl", "verify"]), None, false);
}
// MODS
/// Post Installer
// TPM Unlock
pub fn setup_tpm_unlock(conf: &DriveConfig) {
install_pkgs(&["tpm2-tools"]);
// systemd-cryptenroll --tpm2-device=list
// Recovery Key
run_command(
&str_vec(vec![
"arch-chroot",
"/mnt",
"systemd-cryptenroll",
"--recovery-key",
&conf.root,
]),
None,
false,
);
run_command(
&str_vec(vec![
"arch-chroot",
"/mnt",
"systemd-cryptenroll",
"--tpm2-device=auto",
&conf.root,
"--tpm2-pcrs=7",
]),
None,
false,
);
}
pub fn setup_bootloader() {
run_command(
&str_vec(vec!["arch-chroot", "/mnt", "bootctl", "install"]),
None,
false,
);
}
pub fn setup_users(conf: &[UserConfig]) {
let mut doas_conf = String::new();
for user in conf {
run_command(
&str_vec(vec!["arch-chroot", "/mnt", "useradd", "-m", &user.name]),
None,
false,
);
run_command(
&str_vec(vec!["arch-chroot", "/mnt", "passwd", &user.name]),
Some(&format!("{}\n{}\n", user.password, user.password)),
false,
);
if user.doas_root {
doas_conf.push_str(&format!("permit {} as root\n", user.name));
}
}
std::fs::write("/mnt/etc/doas.conf", doas_conf).unwrap();
}
pub fn install(conf: InstallConfig) {
// Drive Setup
format_drives(&conf.drive, conf.general.encryption);
mount_drives(&conf.drive, conf.general.encryption);
// Base Install
pacstrap(&conf.pkg);
genfstab();
// System Setup
first_boot_values(&conf.general);
setup_users(&conf.user);
setup_bootloader();
match conf.general.mode {
crate::config::InstallMode::Base => {}
crate::config::InstallMode::Desktop => {
install_pkgs(&pkg::DESKTOP_PKG);
}
crate::config::InstallMode::Server => {
install_pkgs(&pkg::SERVER_PKG);
}
crate::config::InstallMode::Kiosk => {
// TODO
}
}
if conf.pkg.virtualization {
// TODO : Enable virtualization
}
if conf.pkg.docker {
// TODO : Enable docker
}
setup_zram();
setup_secure_boot();
setup_mkinitcpio();
setup_tpm_unlock(&conf.drive);
println!("System install complete");
}

11
src/install/boot.rs Normal file
View file

@ -0,0 +1,11 @@
use crate::run_command;
use super::str_vec;
pub fn setup_bootloader() {
run_command(
&str_vec(vec!["arch-chroot", "/mnt", "bootctl", "install"]),
None,
false,
);
}

60
src/install/drives.rs Normal file
View file

@ -0,0 +1,60 @@
use crate::{config::DriveConfig, run_command};
use super::str_vec;
pub fn format_drives(conf: &DriveConfig, encrypted: bool) {
// EFI (BOOT)
run_command(
&str_vec(vec!["mkfs.vfat", "-F", "32", conf.boot.as_str()]),
None,
false,
);
// ROOT
if encrypted {
run_command(
&str_vec(vec!["cryptsetup", "luksFormat", conf.root.as_str()]),
None,
true,
);
} else {
run_command(&str_vec(vec!["mkfs.ext4", conf.root.as_str()]), None, false);
}
}
// MOUNT
pub fn mount_drives(conf: &DriveConfig, encrypted: bool) {
if encrypted {
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,
);
} else {
run_command(
&str_vec(vec!["mount", conf.root.as_str(), "/mnt"]),
None,
false,
);
}
run_command(
&str_vec(vec![
"mount",
"--mkdir",
conf.boot.as_str(),
"/mnt/boot",
"-o",
"rw,nosuid,nodev,noatime,fmask=0137,dmask=0027",
]),
None,
false,
);
}

53
src/install/first_boot.rs Normal file
View file

@ -0,0 +1,53 @@
// GENFSTAB
use crate::{config::GeneralConfig, run_command};
use super::{str_vec, uncomment_first_value_of};
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
run_command(
&vec![
"arch-chroot".into(),
"/mnt".into(),
"systemd-firstboot".into(),
format!("--locale={}", conf.locale),
format!("--keymap={}", conf.keymap),
format!("--timezone={}", conf.timezone),
format!("--hostname={}", conf.hostname),
],
None,
false,
);
// LOCALE
uncomment_first_value_of(&conf.locale, "/mnt/etc/locale.gen");
run_command(
&str_vec(vec!["arch-chroot", "/mnt", "locale-gen"]),
None,
false,
);
run_command(
&str_vec(vec!["arch-chroot", "/mnt", "hwclock", "--systohc"]),
None,
false,
);
run_command(
&str_vec(vec![
"arch-chroot",
"/mnt",
"systemctl",
"enable",
"NetworkManager.service",
]),
None,
false,
);
}

24
src/install/kernel.rs Normal file
View file

@ -0,0 +1,24 @@
// MKINITCPIO + UKI
use crate::run_command;
use super::str_vec;
pub fn setup_mkinitcpio() {
std::fs::write(
"/mnt/etc/mkinitcpio.d/linux.preset",
include_str!("../root/mkinitcpio/linux.preset"),
)
.unwrap();
// TODO : more configs
std::fs::write(
"/mnt/etc/mkinitcpio.conf",
include_str!("../root/mkinitcpio.conf"),
)
.unwrap();
run_command(
&str_vec(vec!["arch-chroot", "/mnt", "mkinitcpio", "--allpresets"]),
None,
true,
);
}

97
src/install/mod.rs Normal file
View file

@ -0,0 +1,97 @@
// TODO : Setup ssh (config + authorized_keys)
// TODO : Setup virtualization
// TODO : Setup docker
// TODO : Autojoin docker swarm
// TODO : Autojoin teleport
// DRIVE SELECTION
use boot::setup_bootloader;
use drives::{format_drives, mount_drives};
use first_boot::{first_boot_values, genfstab};
use kernel::setup_mkinitcpio;
use security::{setup_secure_boot, setup_tpm_unlock};
use user::setup_users;
use yansi::{Color, Paint};
use zram::setup_zram;
pub mod boot;
pub mod drives;
pub mod first_boot;
pub mod kernel;
pub mod security;
pub mod user;
pub mod zram;
use crate::{
config::InstallConfig,
pkg::{self, install_pkgs, pacstrap},
};
pub fn str_vec(v: Vec<&str>) -> Vec<String> {
v.into_iter().map(|x| x.to_string()).collect()
}
pub fn uncomment_first_value_of(value: &str, file: &str) {
// read in the file
let content = std::fs::read_to_string(file).unwrap();
let mut new = String::new();
let mut found = false;
// search for the first instance of `value` in the file
// uncomment the '#' symbol if there is one
for line in content.lines() {
if line.contains(value) && !found {
new.push_str(&format!("{}\n", line.replace("#", "")));
found = true;
} else {
new.push_str(&format!("{line}\n"));
}
}
// write back
std::fs::write(file, new).unwrap();
}
pub fn install(conf: InstallConfig) {
// Drive Setup
format_drives(&conf.drive, conf.general.encryption);
mount_drives(&conf.drive, conf.general.encryption);
// Base Install
pacstrap(&conf.pkg);
genfstab();
// System Setup
first_boot_values(&conf.general);
setup_users(&conf.user);
setup_bootloader();
match conf.general.mode {
crate::config::InstallMode::Base => {}
crate::config::InstallMode::Desktop => {
install_pkgs(&pkg::DESKTOP_PKG);
}
crate::config::InstallMode::Server => {
install_pkgs(&pkg::SERVER_PKG);
}
crate::config::InstallMode::Kiosk => {
// TODO
}
}
if conf.pkg.virtualization {
// TODO : Enable virtualization
}
if conf.pkg.docker {
// TODO : Enable docker
}
setup_zram();
setup_secure_boot();
setup_mkinitcpio();
setup_tpm_unlock(&conf.drive);
println!("{}", "System install complete".paint(Color::Green));
}

103
src/install/security.rs Normal file
View file

@ -0,0 +1,103 @@
// TPM Unlock
use crate::{config::DriveConfig, pkg::install_pkgs, run_command};
use super::str_vec;
pub fn setup_tpm_unlock(conf: &DriveConfig) {
install_pkgs(&["tpm2-tools"]);
// systemd-cryptenroll --tpm2-device=list
// Recovery Key
run_command(
&str_vec(vec![
"arch-chroot",
"/mnt",
"systemd-cryptenroll",
"--recovery-key",
&conf.root,
]),
None,
false,
);
run_command(
&str_vec(vec![
"arch-chroot",
"/mnt",
"systemd-cryptenroll",
"--tpm2-device=auto",
&conf.root,
"--tpm2-pcrs=7",
]),
None,
false,
);
}
// SECURE BOOT
pub fn setup_secure_boot() {
let (stdout, _) = run_command(&str_vec(vec!["sbctl", "status"]), None, false);
let binding = stdout.lines().collect::<Vec<&str>>();
let status = binding.get(1).unwrap();
if !status.contains("Setup Mode") {
println!("[!] Secure Boot is not in Setup Mode");
std::process::exit(1);
} else {
if !status.contains("Enabled") {
println!("[!] Secure Boot is not in Setup Mode");
std::process::exit(1);
}
}
run_command(&vec!["sbctl".into(), "create-keys".into()], None, false);
run_command(
&str_vec(vec!["sbctl", "enroll-keys", "--microsoft"]),
None,
false,
);
run_command(
&str_vec(vec![
"sbctl",
"sign",
"-s",
"/boot/EFI/Linux/arch-linux.efi",
]),
None,
false,
);
run_command(
&str_vec(vec![
"sbctl",
"sign",
"-s",
"/boot/EFI/Linux/arch-linux-fallback.efi",
]),
None,
false,
);
run_command(
&str_vec(vec![
"sbctl",
"sign",
"-s",
"/boot/EFI/systemd/systemd-bootx64.efi",
]),
None,
false,
);
run_command(
&str_vec(vec!["sbctl", "sign", "-s", "/boot/EFI/Boot/bootx64.efi"]),
None,
false,
);
run_command(&str_vec(vec!["sbctl", "verify"]), None, false);
}

27
src/install/user.rs Normal file
View file

@ -0,0 +1,27 @@
use crate::{config::UserConfig, run_command};
use super::str_vec;
pub fn setup_users(conf: &[UserConfig]) {
let mut doas_conf = String::new();
for user in conf {
run_command(
&str_vec(vec!["arch-chroot", "/mnt", "useradd", "-m", &user.name]),
None,
false,
);
run_command(
&str_vec(vec!["arch-chroot", "/mnt", "passwd", &user.name]),
Some(&format!("{}\n{}\n", user.password, user.password)),
false,
);
if user.doas_root {
doas_conf.push_str(&format!("permit {} as root\n", user.name));
}
}
std::fs::write("/mnt/etc/doas.conf", doas_conf).unwrap();
}

23
src/install/zram.rs Normal file
View file

@ -0,0 +1,23 @@
use crate::{pkg::install_pkgs, run_command};
use super::str_vec;
pub fn setup_zram() {
install_pkgs(&["zram-generator"]);
std::fs::write(
"/mnt/etc/systemd/zram-generator.conf",
include_str!("../root/zram-generator.conf"),
)
.unwrap();
run_command(
&str_vec(vec![
"arch-chroot",
"/mnt",
"systemctl",
"enable",
"systemd-zram-setup@zram0.service",
]),
None,
false,
);
}

View file

@ -10,13 +10,18 @@ mod install;
mod pkg;
use create_iso::create_iso;
use install::install;
use yansi::{Color, Paint};
fn is_root() -> bool {
getuid() == Uid::from_raw(0)
}
fn run_command(cmd: &[String], input: Option<&str>, inherit: bool) -> (String, String) {
println!("--> {}", cmd.join(" "));
println!(
"{} {}",
"-->".paint(Color::Red),
cmd.join(" ").paint(Color::Blue.bold())
);
let mut cmd_setup = std::process::Command::new(cmd[0].clone());
let mut cmd_setup = cmd_setup.args(cmd.into_iter().skip(1).collect::<Vec<_>>());
@ -62,7 +67,11 @@ fn run_command(cmd: &[String], input: Option<&str>, inherit: bool) -> (String, S
}
fn main() {
println!("⚠️ Warning: This is an alpha version of the installer. DO NOT USE in PROD");
println!(
"{}",
"⚠️ Warning: This is an alpha version of the installer. DO NOT USE in PROD"
.paint(Color::Yellow)
);
let args = args::get_args();
@ -83,12 +92,16 @@ fn main() {
Ok(content) => match toml::from_str(&content) {
Ok(config) => config,
Err(e) => {
eprintln!("Error: Could not deserialize TOML file. {e}");
eprintln!(
"{} {}",
"Error: Could not deserialize TOML file.".paint(Color::Red),
e.paint(Color::Red)
);
std::process::exit(1);
}
},
Err(_) => {
eprintln!("Error: Could not read config file.");
eprintln!("{}", "Error: Could not read config file.".paint(Color::Red));
std::process::exit(1);
}
};
@ -104,19 +117,23 @@ fn main() {
Ok(content) => match toml::from_str(&content) {
Ok(config) => config,
Err(e) => {
eprintln!("Error: Could not deserialize TOML file. {e}");
eprintln!(
"{} {}",
"Error: Could not deserialize TOML file.".paint(Color::Red),
e.paint(Color::Red)
);
std::process::exit(1);
}
},
Err(_) => {
eprintln!("Error: Could not read config file.");
eprintln!("{}", "Error: Could not read config file.".paint(Color::Red));
std::process::exit(1);
}
};
// TODO : Show config
println!("Config: {conf:?}");
println!("\nDo you want to proceed with this configuration? (yes/no)");
print!("\nDo you want to proceed with this configuration? (yes/no) ");
let mut input = String::new();
std::io::stdout().flush().expect("Error flushing stdout.");

View file

@ -1,4 +1,4 @@
use crate::{install::str_vec, run_command};
use crate::{config::PackageConfig, install::str_vec, run_command};
pub const DESKTOP_PKG: [&str; 2] = ["plasma", "sddm"];
@ -12,3 +12,25 @@ pub fn install_pkgs(pkg: &[&str]) {
run_command(&str_vec(cmd), None, true);
}
// PACSTRAP
pub fn pacstrap(conf: &PackageConfig) {
let mut cmd: Vec<String> = vec![
"pacstrap".into(),
"-K".into(),
"/mnt".into(),
"base".into(),
"linux".into(),
"linux-firmware".into(),
"linux-headers".into(),
"git".into(),
"networkmanager".into(),
"nano".into(),
"doas".into(),
];
cmd.extend(conf.pkg.clone());
run_command(&cmd, None, true);
}