SHA256
Sawing progress
This commit is contained in:
@@ -0,0 +1,308 @@
|
||||
// <Kira Installer - universal Linux installer.>
|
||||
// Copyright (C) <2026> <Kira Foundation>
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/*
|
||||
Functions for creating with disk layout
|
||||
*/
|
||||
|
||||
use crate::kira_disk_layout::*;
|
||||
use std::process::Command;
|
||||
use std::fs;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CommandFailed{
|
||||
stderr: Vec<u8>,
|
||||
cmd: String,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CommandFailed {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "Command failed! Command: {}; Stderr: {}", self.cmd, String::from_utf8(self.stderr.clone()).unwrap_or_else(|_|{"STDEER IS NOT VALID UTF-8".to_string()}))
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandFailed {
|
||||
pub fn new(cmd: &str, args: &Vec<&str>, stderr: Vec<u8>) -> Self {
|
||||
Self {
|
||||
stderr: stderr,
|
||||
cmd: format!("{} {}", cmd, args.join(" ")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ExecCommandError {
|
||||
IoError(std::io::Error),
|
||||
FromUtf8Error(std::string::FromUtf8Error),
|
||||
CommandFailed(CommandFailed),
|
||||
Other(String),
|
||||
}
|
||||
|
||||
// 2. Implement From for each wrapped error type
|
||||
impl From<std::io::Error> for ExecCommandError {
|
||||
fn from(err: std::io::Error) -> Self {
|
||||
ExecCommandError::IoError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::string::FromUtf8Error> for ExecCommandError {
|
||||
fn from(err: std::string::FromUtf8Error) -> Self {
|
||||
ExecCommandError::FromUtf8Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CommandFailed> for ExecCommandError {
|
||||
fn from(err: CommandFailed) -> Self {
|
||||
ExecCommandError::CommandFailed(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ExecCommandError {
|
||||
fn from(err: String) -> Self {
|
||||
ExecCommandError::Other(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ExecCommandError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
ExecCommandError::IoError(e) => write!(f, "IO Error: {}", e),
|
||||
ExecCommandError::FromUtf8Error(e) => {
|
||||
write!(f, "IO streem to String parse Error: {}", e)
|
||||
}
|
||||
ExecCommandError::CommandFailed(ex) => {
|
||||
write!(f, "{}", ex)
|
||||
}
|
||||
ExecCommandError::Other(msg) => write!(f, "Error: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implement std::error::Error
|
||||
impl std::error::Error for ExecCommandError {}
|
||||
|
||||
/// exec given sommand with arguments, capturing stderr in case or failure
|
||||
fn exec_command(cmd: &str, args: &Vec<&str>) -> Result<(), ExecCommandError> {
|
||||
let out = Command::new(cmd).args(args).output()?;
|
||||
if out.status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(CommandFailed::new(cmd, args, out.stderr).into())
|
||||
}
|
||||
}
|
||||
|
||||
/// exec given sommand with arguments, returning stdout parsed to UTF-8 string
|
||||
/// capturing stderr in case or failure
|
||||
fn exec_command_out(cmd: &str, args: &Vec<&str>) -> Result<String, ExecCommandError> {
|
||||
let out = Command::new(cmd).args(args).output()?;
|
||||
if out.status.success() {
|
||||
Ok(String::from_utf8(out.stdout)?)
|
||||
} else {
|
||||
Err(CommandFailed::new(cmd, args, out.stderr).into())
|
||||
}
|
||||
}
|
||||
|
||||
/// sync system time with chrony
|
||||
/// dinitctl start chrony
|
||||
pub fn dinit_sync_time_chrony() -> Result<(), ExecCommandError> {
|
||||
exec_command("dinitctl", &vec!["start", "chrony"])
|
||||
}
|
||||
|
||||
/// Install base system
|
||||
/// basestrap /mnt base base-devel dinit elogind-dinit
|
||||
pub fn basestrap_dinit(root_dir: &str) -> Result<(), ExecCommandError> {
|
||||
exec_command("basestrap", &vec![root_dir, "base", "base-devel", "dinit", "elogind-dinit"])
|
||||
}
|
||||
|
||||
/// Executes parted command with given arguments
|
||||
fn exec_parted(args: &Vec<&str>) -> Result<String, ExecCommandError> {
|
||||
exec_command_out("parted", args)
|
||||
}
|
||||
|
||||
fn parted_get_last_part_num(dev: &BlkDev) -> Result<u32, ExecCommandError> {
|
||||
// parted -m /dev/loop0 print
|
||||
let res = exec_parted(&vec!["-m", dev.full_name().as_str(), "print"])?;
|
||||
|
||||
match res.lines().last().and_then(|last_line| {
|
||||
last_line
|
||||
.split_once(':')
|
||||
.and_then(|(first_part, _)| u32::from_str_radix(first_part, 10).ok())
|
||||
}) {
|
||||
Some(n) => Ok(n),
|
||||
None => Err("Error parsing parted output".to_string().into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// init gpt partition table
|
||||
/// parted --script $install_device mklabel gpt
|
||||
/// Its blocking function
|
||||
pub fn parted_init_gpt_part_table(dev: &BlkDev) -> Result<(), ExecCommandError> {
|
||||
exec_parted(&vec![
|
||||
"--script",
|
||||
dev.full_name().as_str(),
|
||||
"mklabel",
|
||||
"gpt",
|
||||
])
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
const LINUX_ROOT_X86_64_TYPE: &str = "4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709";
|
||||
|
||||
/// creating partition from PartInfo specification
|
||||
/// It assumes that we add partitions sequentially to empty space (for part attributes settings)
|
||||
/// Otherwise can produce bonkers layout
|
||||
pub fn parted_mkpart(dev: &BlkDev, part: &PartInfo) -> Result<(), ExecCommandError> {
|
||||
let part_end = part.start + part.size;
|
||||
|
||||
// gpt label should be in double qutes
|
||||
let gpt_label = if let Some(s) = &part.gpt_label {
|
||||
format!("\"{}\"", s)
|
||||
} else {
|
||||
"\"\"".to_string()
|
||||
};
|
||||
// creating partition
|
||||
exec_parted(&vec![
|
||||
"--script",
|
||||
dev.full_name().as_str(),
|
||||
"mkpart",
|
||||
gpt_label.as_str(),
|
||||
part.fs_type.to_parted_str(),
|
||||
part.start.to_parted_bytes_str().as_str(),
|
||||
part_end.to_parted_bytes_str().as_str(),
|
||||
])?;
|
||||
|
||||
// set partition options depending on its role
|
||||
if let Some(role) = &part.role {
|
||||
// getting part number of last created partition (hopefully)
|
||||
let part_id = parted_get_last_part_num(dev)?;
|
||||
match role {
|
||||
PartRole::EFI => {
|
||||
exec_parted(&vec![
|
||||
"--script",
|
||||
dev.full_name().as_str(),
|
||||
"set",
|
||||
&part_id.to_string(),
|
||||
"esp",
|
||||
"on",
|
||||
])?;
|
||||
}
|
||||
// parted --script $install_device type 3 4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709
|
||||
PartRole::ROOT => {
|
||||
exec_parted(&vec![
|
||||
"--script",
|
||||
dev.full_name().as_str(),
|
||||
"type",
|
||||
&part_id.to_string(),
|
||||
LINUX_ROOT_X86_64_TYPE,
|
||||
])?;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// creating single drive bcachefs volume
|
||||
pub fn bcachefs_format(
|
||||
dev_name: &str,
|
||||
compression: Option<String>,
|
||||
password: Option<String>,
|
||||
) -> Result<(), ExecCommandError> {
|
||||
// sudo bcachefs format --compression=lz4 --background_compression=lz4 --encrypted /dev/loop0
|
||||
// --data_checksum=none - do not make sense on single dev volumes
|
||||
// sudo bcachefs format --force --compression=lz4 --background_compression=lz4 --encrypted --passphrase_file=./test.pass /dev/loop0
|
||||
|
||||
let mut arg = vec!["format", "--force", "--data_checksum=none"];
|
||||
// if compression algorythm specified
|
||||
if let Some(compresss_alg) = &compression {
|
||||
arg.push(compresss_alg.as_str());
|
||||
}
|
||||
// if we want encrypted volume
|
||||
if let Some(passw) = password {
|
||||
// creating tmp file with password
|
||||
fs::write("/tmp/bzzpsspass.txt", &passw)?;
|
||||
arg.push("--encrypted");
|
||||
arg.push("--passphrase_file=/tmp/bzzpsspass.txt");
|
||||
}
|
||||
|
||||
// add dev name at the end
|
||||
arg.push(dev_name);
|
||||
|
||||
let out = exec_command("bcachefs", &arg);
|
||||
|
||||
// ignore any errors while deleting pass tmp file
|
||||
let _ = fs::remove_file("/tmp/bzzpsspass.txt");
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
/// unlok encrypted bcache volume
|
||||
pub fn bcachefs_unlock(dev_name: &str, pass: &str) -> Result<(), ExecCommandError> {
|
||||
|
||||
// bcachefs unlock --file=/tmp/bzzpsspass.txt /dev/loop0
|
||||
let arg = vec!["unlock", "--file=/tmp/bzzpsspass.txt ", dev_name];
|
||||
// creating tmp file with password
|
||||
fs::write("/tmp/bzzpsspass.txt", &pass)?;
|
||||
|
||||
|
||||
let out = exec_command("bcachefs", &arg);
|
||||
// ignore any errors while deleting pass tmp file
|
||||
let _ = fs::remove_file("/tmp/bzzpsspass.txt");
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
/// create subvolume
|
||||
pub fn bcachefs_create_subvolume(vol_path: &str) -> Result<(), ExecCommandError> {
|
||||
// sudo bcachefs subvolume create /mnt/test/nyan
|
||||
exec_command("bcachefs", &vec!["subvolume", "create", vol_path])
|
||||
}
|
||||
|
||||
/// mount bcachefs
|
||||
pub fn bcachefs_mount(dev_name: &str, mount_point: &str) -> Result<(), ExecCommandError> {
|
||||
// sudo mount -t bcachefs /dev/loop0 /mnt/test
|
||||
exec_command("mount", &vec!["-t", "bcachefs", dev_name, mount_point])
|
||||
}
|
||||
|
||||
pub fn fatlabel(dev: &str, label: &str) -> Result<(), ExecCommandError> {
|
||||
//fatlabel /dev/sda1 ESP
|
||||
exec_command("fatlabel", &vec![dev, label])
|
||||
}
|
||||
|
||||
pub fn mkfs_fat32(dev: &str, maybe_label: Option<&str>) -> Result<(), ExecCommandError> {
|
||||
//mkfs.fat -F 32 /dev/sda1
|
||||
exec_command("mkfs.fat", &vec!["-F", "32", dev])?;
|
||||
|
||||
// if need to set label
|
||||
if let Some(label) = maybe_label {
|
||||
fatlabel(dev, label)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// mkfs.fat -F 32 /dev/sda1
|
||||
/// fatlabel /dev/sda1 ESP
|
||||
pub fn mkfs_esp(dev: &str) -> Result<(), ExecCommandError> {
|
||||
mkfs_fat32(dev, Some("ESP"))
|
||||
}
|
||||
|
||||
|
||||
pub fn mkswap(dev: &str) -> Result<(), ExecCommandError> {
|
||||
//mkswap -L SWAP /dev/sda4
|
||||
exec_command("mkswap", &vec!["L", "SWAP", dev])
|
||||
}
|
||||
Reference in New Issue
Block a user