SHA256
Most stuff should be complete.
This commit is contained in:
Generated
+14
@@ -290,6 +290,7 @@ name = "nyanit"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ratatui",
|
||||
"ratatui-textarea",
|
||||
"regex",
|
||||
"termion",
|
||||
]
|
||||
@@ -394,6 +395,19 @@ dependencies = [
|
||||
"termion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ratatui-textarea"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c78d5ba0f26f97baed69a4c479f268a31c7b5b89d68ab939842152e383d6e73"
|
||||
dependencies = [
|
||||
"ratatui-core",
|
||||
"ratatui-termion",
|
||||
"ratatui-widgets",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ratatui-widgets"
|
||||
version = "0.3.1"
|
||||
|
||||
+2
-1
@@ -5,6 +5,7 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
ratatui = { version = "0.30", default-features = false, features = ["termion"] }
|
||||
ratatui-textarea = { version = "0.9", default-features = false, features = ["termion"] }
|
||||
regex = "1.12.4"
|
||||
termion = "4"
|
||||
|
||||
@@ -14,5 +15,5 @@ lto = true
|
||||
debug = false
|
||||
incremental = false
|
||||
codegen-units = 1
|
||||
opt-level = 3
|
||||
opt-level = 2
|
||||
strip = true
|
||||
|
||||
+125
-44
@@ -14,25 +14,27 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use ratatui::widgets::Borders;
|
||||
use ratatui::{Frame, Terminal, backend::TermionBackend, widgets::Paragraph};
|
||||
use ratatui::{Frame, Terminal, backend::TermionBackend};
|
||||
use ratatui::{
|
||||
layout::{Constraint, Layout},
|
||||
style::{Style, Stylize},
|
||||
text::{Line},
|
||||
widgets::{Block, List, ListState},
|
||||
style::{Color, Style, Stylize},
|
||||
text::Line,
|
||||
widgets::{Block, Borders, List, ListState, Paragraph},
|
||||
};
|
||||
|
||||
use ratatui_textarea::TextArea;
|
||||
|
||||
use std::io::{self, Read, stdout};
|
||||
use std::process::ExitCode;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use termion::event::{Event, Key};
|
||||
use termion::event::{self, Event};
|
||||
use termion::raw::IntoRawMode;
|
||||
|
||||
mod nyan_system;
|
||||
|
||||
use nyan_system::{ExecCommandError};
|
||||
use nyan_system::ExecCommandError;
|
||||
|
||||
const BEE_CAT: &str = include_str!("bee_cat.txt");
|
||||
|
||||
@@ -71,17 +73,8 @@ enum NyanitState {
|
||||
HandOff(String),
|
||||
}
|
||||
|
||||
fn init_stage() -> Result<NyanitState, ExecCommandError> {
|
||||
// mount sysfss
|
||||
nyan_system::prepare_env()?;
|
||||
|
||||
// check if device is encrypted
|
||||
if nyan_system::bzzpss_dev_encrypted(ROOT_DEV)? {
|
||||
// if encrypted return EnterPassword so used get promted for pass
|
||||
return Ok(NyanitState::EnterPassword);
|
||||
}
|
||||
|
||||
// if unencrypted
|
||||
/// Mounting bee-root and chiking if there any snapshots
|
||||
fn mount_and_check_snaps() -> Result<NyanitState, ExecCommandError> {
|
||||
// mount bee-root
|
||||
nyan_system::mount_bee_root(TMP_ROOT, ROOT_DEV)?;
|
||||
|
||||
@@ -93,24 +86,40 @@ fn init_stage() -> Result<NyanitState, ExecCommandError> {
|
||||
return Ok(NyanitState::WaitForF2(snaps_list));
|
||||
}
|
||||
|
||||
// tell to finish boot
|
||||
// else tell to finish boot
|
||||
Ok(NyanitState::HandOff(TMP_ROOT.into()))
|
||||
}
|
||||
|
||||
pub struct NyanitTUI {
|
||||
fn init_stage() -> Result<NyanitState, ExecCommandError> {
|
||||
// mount sysfss
|
||||
nyan_system::prepare_env()?;
|
||||
|
||||
// check if device is encrypted
|
||||
if nyan_system::bzzpss_dev_encrypted(ROOT_DEV)? {
|
||||
// if encrypted return EnterPassword so used get promted for pass
|
||||
return Ok(NyanitState::EnterPassword);
|
||||
}
|
||||
|
||||
// if unencrypted
|
||||
mount_and_check_snaps()
|
||||
}
|
||||
|
||||
struct NyanitTUI<'a> {
|
||||
exit: bool,
|
||||
state: NyanitState,
|
||||
list_state: ListState,
|
||||
time_counter: u32,
|
||||
textarea: Option<TextArea<'a>>,
|
||||
}
|
||||
|
||||
impl NyanitTUI {
|
||||
impl NyanitTUI<'_> {
|
||||
pub fn new() -> io::Result<Self> {
|
||||
Ok(Self {
|
||||
exit: false,
|
||||
state: NyanitState::Init,
|
||||
list_state: ListState::default().with_selected(Some(0)),
|
||||
time_counter: 0,
|
||||
textarea: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -127,19 +136,34 @@ impl NyanitTUI {
|
||||
};
|
||||
|
||||
// if drive unencrypted and there is no root snapshots finish boot
|
||||
if let NyanitState::HandOff(root_mnt) = &self.state {
|
||||
if let Err(ex) = nyan_system::hand_off_control(root_mnt, SYS_INIT) {
|
||||
self.state = NyanitState::Error(ex.to_string())
|
||||
match &self.state {
|
||||
// if drive unencrypted and there is no root snapshots finish boot
|
||||
NyanitState::HandOff(root_mnt) => {
|
||||
if let Err(ex) = nyan_system::hand_off_control(root_mnt, SYS_INIT) {
|
||||
self.state = NyanitState::Error(ex.to_string())
|
||||
}
|
||||
}
|
||||
// init password input
|
||||
NyanitState::EnterPassword => {
|
||||
let mut textarea: TextArea<'_> = TextArea::default();
|
||||
textarea.set_cursor_line_style(Style::default());
|
||||
textarea.set_mask_char('\u{1F41D}'); //U+2022 BULLET (•)
|
||||
textarea.set_placeholder_text("Please enter your password");
|
||||
textarea.set_style(Style::default().fg(Color::LightYellow));
|
||||
textarea.set_block(Block::default().borders(Borders::ALL).title("Password"));
|
||||
self.textarea = Some(textarea);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// Init terminal
|
||||
// 1. Enable raw mode
|
||||
let stdout = stdout().into_raw_mode()?;
|
||||
// 2. Setup Terminal
|
||||
let backend = TermionBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
// init tty input and start actually drawing things now!
|
||||
// init tty async input and start actually drawing things now!
|
||||
let mut tty_input_async = termion::async_stdin().bytes();
|
||||
|
||||
self.time_counter = 0;
|
||||
@@ -207,37 +231,94 @@ impl NyanitTUI {
|
||||
&mut self.list_state,
|
||||
);
|
||||
}
|
||||
NyanitState::EnterPassword => {
|
||||
frame.render_widget(self.textarea.as_ref().unwrap(), vertical_chunks[1]);
|
||||
}
|
||||
NyanitState::HandOff(_) => {
|
||||
let ok_text = Paragraph::new("Booting\nKira Linux\n \u{1F41D} \u{1F41D} :3")
|
||||
.block(Block::bordered().light_green())
|
||||
.light_green();
|
||||
frame.render_widget(ok_text, vertical_chunks[1]);
|
||||
// just some time to look at message
|
||||
thread::sleep(Duration::from_millis(1000))
|
||||
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
|
||||
fn handle_events(&mut self, tty_input: &mut io::Bytes<termion::AsyncReader>) -> io::Result<()> {
|
||||
|
||||
// cheking if there is any input to read
|
||||
if let Some(item) = tty_input.next() {
|
||||
let read_byte = item?;
|
||||
|
||||
let e = termion::event::parse_event(read_byte, tty_input)?;
|
||||
|
||||
let e = event::parse_event(read_byte, tty_input)?;
|
||||
match &self.state {
|
||||
NyanitState::WaitForF2(vol_list) => {
|
||||
if e == Event::Key(Key::F(2)) {
|
||||
self.state = NyanitState::ChooseSnapshot(vol_list.clone());
|
||||
}
|
||||
},
|
||||
NyanitState::ChooseSnapshot(vol_list) => {
|
||||
match e {
|
||||
Event::Key(Key::Down) => self.list_state.select_next(),
|
||||
Event::Key(Key::Up) => self.list_state.select_previous(),
|
||||
Event::Key(Key::Char('\n')) => if let Some(idx) = self.list_state.selected() {
|
||||
self.state = NyanitState::HandOff(vol_list[idx].clone());
|
||||
},
|
||||
_ => (),
|
||||
// finishing boot
|
||||
NyanitState::HandOff(root_mnt) => {
|
||||
if let Err(ex) = nyan_system::hand_off_control(root_mnt, SYS_INIT) {
|
||||
self.state = NyanitState::Error(ex.to_string())
|
||||
}
|
||||
}
|
||||
NyanitState::WaitForF2(vol_list) => {
|
||||
if e == Event::Key(event::Key::F(2)) {
|
||||
self.state = NyanitState::ChooseSnapshot(vol_list.clone());
|
||||
}
|
||||
}
|
||||
NyanitState::ChooseSnapshot(vol_list) => match e {
|
||||
Event::Key(event::Key::Down) => self.list_state.select_next(),
|
||||
Event::Key(event::Key::Up) => self.list_state.select_previous(),
|
||||
Event::Key(event::Key::Char('\n')) => {
|
||||
if let Some(idx) = self.list_state.selected() {
|
||||
self.state = NyanitState::HandOff(vol_list[idx].clone());
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => ()
|
||||
NyanitState::EnterPassword => {
|
||||
match e {
|
||||
// try to unlock if enter pressed
|
||||
Event::Key(event::Key::Char('\n')) => {
|
||||
let password = self.textarea.as_ref().unwrap().lines()[0].clone();
|
||||
match nyan_system::bcachefs_unlock(password, ROOT_DEV) {
|
||||
Ok(unlocked) => {
|
||||
if unlocked {
|
||||
// need to check for subvolumes and stuff
|
||||
self.state = match mount_and_check_snaps() {
|
||||
Ok(st) => st,
|
||||
Err(ex) => NyanitState::Error(ex.to_string()),
|
||||
}
|
||||
}
|
||||
// ask to enter password again
|
||||
else {
|
||||
self.textarea
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.set_placeholder_text("Wrong password, try again");
|
||||
self.textarea
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.set_style(Style::default().fg(Color::LightRed));
|
||||
self.textarea.as_mut().unwrap().clear();
|
||||
}
|
||||
}
|
||||
Err(ex) => {
|
||||
self.state = NyanitState::Error(ex.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.textarea
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.set_style(Style::default().fg(Color::LightYellow));
|
||||
self.textarea.as_mut().unwrap().input(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Tick time
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
self.time_counter = self.time_counter.wrapping_add(1u32);
|
||||
|
||||
+20
-20
@@ -111,29 +111,29 @@ pub fn exec_command(cmd: &str, args: &Vec<&str>) -> Result<(), ExecCommandError>
|
||||
|
||||
/// exec given command with arguments, returning stdout parsed to UTF-8 string
|
||||
/// capturing stderr in case or failure
|
||||
pub 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())
|
||||
}
|
||||
}
|
||||
// pub 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())
|
||||
// }
|
||||
// }
|
||||
|
||||
// ls /dev/disk/by-partlabel/
|
||||
// here we can find gpt part labels
|
||||
pub fn list_gpt_labeled_devs() -> Result<Vec<String>, ExecCommandError> {
|
||||
use std::os::unix::fs::FileTypeExt;
|
||||
let mut res: Vec<String> = Vec::new();
|
||||
for maybe_entry in fs::read_dir("/dev/disk/by-partlabel/")? {
|
||||
let entry = maybe_entry?;
|
||||
if entry.file_type()?.is_block_device() {
|
||||
res.push(entry.path().to_str().unwrap().to_string());
|
||||
}
|
||||
}
|
||||
// pub fn list_gpt_labeled_devs() -> Result<Vec<String>, ExecCommandError> {
|
||||
// use std::os::unix::fs::FileTypeExt;
|
||||
// let mut res: Vec<String> = Vec::new();
|
||||
// for maybe_entry in fs::read_dir("/dev/disk/by-partlabel/")? {
|
||||
// let entry = maybe_entry?;
|
||||
// if entry.file_type()?.is_block_device() {
|
||||
// res.push(entry.path().to_str().unwrap().to_string());
|
||||
// }
|
||||
// }
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
// Ok(res)
|
||||
// }
|
||||
|
||||
/// bcachefs unlock --file=/tmp/bzzpsspass.txt /dev/loop0somedev
|
||||
/// Ok so. It is stupid, because bcacheutils far from ideal.
|
||||
@@ -183,7 +183,7 @@ pub fn bcachefs_unlock(pass: String, dev: &str) -> Result<bool, ExecCommandError
|
||||
|
||||
/// Cheking if device is encripted
|
||||
pub fn bzzpss_dev_encrypted(dev_name: &str) -> Result<bool, ExecCommandError> {
|
||||
match exec_command("bcachefs", &vec!["unlock", "--check"]) {
|
||||
match exec_command("bcachefs", &vec!["unlock", "--check", dev_name]) {
|
||||
Ok(_) => Ok(true), // if status OK - dev encrypted
|
||||
Err(ExecCommandError::CommandFailed(_)) => Ok(false), // if bcachefs returned error - unencrypted
|
||||
Err(ex) => Err(ex), // othervice its error
|
||||
|
||||
Reference in New Issue
Block a user