From 121cd9098860a490e145d6aabba638efc2e8c289800e28b1ed39a79c48a033e9 Mon Sep 17 00:00:00 2001 From: Kira Date: Sat, 20 Jun 2026 18:31:34 +0200 Subject: [PATCH] Basically initial commit, lots of work. --- .gitignore | 5 + Cargo.lock | 610 +++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 9 + src/bee_cat.txt | 5 + src/main.rs | 236 ++++++++++++++++++ src/nyan_system.rs | 214 ++++++++++++++++ 6 files changed, 1079 insertions(+) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/bee_cat.txt create mode 100644 src/main.rs create mode 100644 src/nyan_system.rs diff --git a/.gitignore b/.gitignore index 79711dd..e4102dc 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,8 @@ target/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + + +# Added by cargo + +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..548acf1 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,610 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "compact_str" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fast-srgb8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "instability" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" +dependencies = [ + "darling", + "indoc", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "kasuari" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" +dependencies = [ + "hashbrown 0.16.1", + "portable-atomic", + "thiserror", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "line-clipping" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8" +dependencies = [ + "bitflags", +] + +[[package]] +name = "lru" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9" +dependencies = [ + "hashbrown 0.17.1", +] + +[[package]] +name = "memchr" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "numtoa" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f" + +[[package]] +name = "nyanit" +version = "0.1.0" +dependencies = [ + "ratatui", + "regex", + "termion", +] + +[[package]] +name = "palette" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" +dependencies = [ + "approx", + "fast-srgb8", + "libm", + "palette_derive", +] + +[[package]] +name = "palette_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +dependencies = [ + "by_address", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ratatui" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1695748e3a735b34968c887ceea5a380b43545903868ae8f5b666593100f6b68" +dependencies = [ + "instability", + "ratatui-core", + "ratatui-termion", + "ratatui-widgets", + "serde", +] + +[[package]] +name = "ratatui-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3603f354bba8c595fa47860e60142d7372b7210c27044c6a7d0e1a4336b44" +dependencies = [ + "bitflags", + "compact_str", + "hashbrown 0.17.1", + "indoc", + "itertools", + "kasuari", + "lru", + "palette", + "serde", + "strum", + "thiserror", + "unicode-segmentation", + "unicode-truncate", + "unicode-width", +] + +[[package]] +name = "ratatui-termion" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c16cc35a9d9114e0b2bb4b22018b96ae7f5fe60e2595dc73e622b4e78624835" +dependencies = [ + "instability", + "ratatui-core", + "termion", +] + +[[package]] +name = "ratatui-widgets" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef4f17dd7ac3abf5adc2b920a03c61eee4bfe6a88fa5191936895525371d79c" +dependencies = [ + "bitflags", + "hashbrown 0.17.1", + "indoc", + "instability", + "itertools", + "line-clipping", + "ratatui-core", + "serde", + "strum", + "time", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "regex" +version = "1.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termion" +version = "4.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f44138a9ae08f0f502f24104d82517ef4da7330c35acd638f1f29d3cd5475ecb" +dependencies = [ + "libc", + "numtoa", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711a53c2d47bbd818258c498c8dbfe186a2526c631495cfe7e078567f86b8469" +dependencies = [ + "deranged", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde_core", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-truncate" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" +dependencies = [ + "itertools", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7c041ce --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "nyanit" +version = "0.1.0" +edition = "2024" + +[dependencies] +ratatui = { version = "0.30", default-features = false, features = ["termion"] } +regex = "1.12.4" +termion = "4" diff --git a/src/bee_cat.txt b/src/bee_cat.txt new file mode 100644 index 0000000..d1cd033 --- /dev/null +++ b/src/bee_cat.txt @@ -0,0 +1,5 @@ + _ _ + | )/ ) +\\ |//_'_ +^_^(_____)= + \ \ \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..48afcb7 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,236 @@ +// +// Copyright (C) <2026> + +// 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 . + +use ratatui::widgets::Borders; +use ratatui::{Frame, Terminal, backend::TermionBackend, border, widgets::Paragraph}; +use ratatui::{ + buffer::Buffer, + layout::{Constraint, Layout, Rect}, + style::{Style, Stylize}, + symbols::border, + text::{Line, Text}, + widgets::{Block, List, ListState, Widget}, +}; +use std::io::{self, Read, stdout}; +use std::process::ExitCode; + +use termion::event::{Event, Key}; +use termion::input::TermRead; // Required for .keys() +use termion::raw::IntoRawMode; + +mod nyan_system; + +use nyan_system::{ExecCommandError, exec_command}; + +const BEE_CAT: &str = include_str!("bee_cat.txt"); + +const ROOT_PARTLABEL: &str = "bee-root"; +const ROOT_DEV: &str = "/dev/disk/by-partlabel/bee-root"; +const TMP_ROOT: &str = "/mnt/bee_root"; +const TMP_ROOT_SNAPS: &str = "/mnt/bee-root/bzz_snaps"; +const SYS_INIT: &str = "/sbin/init"; + +// fn bee_root_is_present() -> bool { +// nyan_system::list_gpt_labeled_devs().iter().filter(|s| s.ends_with(ROOT_PARTLABEL)).count() > 0 +// } + +// fn main() -> ExitCode { +// println!("Looking for {}", ROOT_PARTLABEL); +// if bee_root_is_present() { +// println!("{} found!", ROOT_PARTLABEL); +// } +// else { +// print!("Cannot find {}!", ROOT_PARTLABEL); +// print!("Exiting..."); +// return ExitCode::FAILURE; +// } +// // 1 get labels and see if there is bzzpss-root in them + +// ExitCode::SUCCESS +// } + +#[derive(Debug, PartialEq)] +enum NyanitState { + Init, + WaitForF2(Vec), + ChooseSnapshot(Vec), + EnterPassword, + Error(String), + HandOff(String), +} + +pub fn init_stage() -> Result { + // 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 bee-root + nyan_system::mount_bee_root(TMP_ROOT, ROOT_DEV)?; + + // list snaps if any + let snaps_list = nyan_system::list_bzzpss_snaps(TMP_ROOT_SNAPS)?; + // if any snaps detected whait for user to press F2 + if snaps_list.len() > 0 { + return Ok(NyanitState::WaitForF2(snaps_list)); + } + + // tell to finish boot + Ok(NyanitState::HandOff(TMP_ROOT.into())) +} + +pub struct NyanitTUI { + counter: u32, + exit: bool, + state: NyanitState, + list_state: ListState, +} + +impl NyanitTUI { + pub fn new() -> io::Result { + Ok(Self { + counter: 0, + exit: false, + state: NyanitState::Init, + list_state: ListState::default().with_selected(Some(0)), + }) + } + + // pub fn bzz_unwrap(&mut self, val: Result) -> T { + // match val { + // Ok(v) => v, + // } + // } + /// runs the application's main loop until the user quits + pub fn run(&mut self) -> io::Result<()> { + self.state = match init_stage() { + Ok(st) => st, + Err(ex) => NyanitState::Error(format!("Error while trying to bzzz-pss things: {}", ex)), + }; + + // 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()) + } + } + + // 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! + let mut tty_input: io::Bytes = termion::get_tty()?.bytes(); + while !self.exit { + terminal.draw(|frame| self.draw(frame))?; + self.handle_events(&mut tty_input)?; + } + Ok(()) + } + + fn draw(&mut self, frame: &mut Frame) { + let area = frame.area(); + // 1. Create the outer block with borders + let outer_block = Block::bordered() + .title(Line::from(" BZZ-PSS ").centered()) + .light_yellow(); + // 2. Calculate the inner area excluding the borders + let inner_area = outer_block.inner(area); + // 3. Render the border block itself + frame.render_widget(outer_block, area); + + // 1. Create vertical layout: Top (flexible), Center (fixed height), Bottom (flexible) + let vertical_chunks = Layout::vertical([ + Constraint::Length(7), // logo top part + Constraint::Percentage(80), // Height of the paragraph block + Constraint::Length(3), // footer + ]) + .split(inner_area); + + // 2. Create horizontal layout inside the center chunk: Left (flexible), Center (fixed width), Right (flexible) + let horizontal_chunks = Layout::horizontal([ + Constraint::Fill(2), + Constraint::Length(11), + Constraint::Fill(2), + ]) + .split(vertical_chunks[0]); + + // 3. Render the logo in the upper chunk + let paragraph = Paragraph::new(BEE_CAT).left_aligned().light_yellow(); + frame.render_widget(paragraph, horizontal_chunks[1]); + + // now check app state + match &self.state { + NyanitState::Error(ex) => { + // render big red wolf in the center with err messaghe + let err_paragraph = Paragraph::new(ex.as_str()).centered().light_red().bold(); + frame.render_widget(err_paragraph, vertical_chunks[1]); + } + NyanitState::WaitForF2(_) => { + // show press F2 for options text + let f2_paragraph = Paragraph::new("Press F2 for options") + .centered() + .magenta() + .bold(); + frame.render_widget(f2_paragraph, vertical_chunks[1]); + } + NyanitState::ChooseSnapshot(snaps_list) => { + // render menu for snap choosing + frame.render_stateful_widget( + List::new(snaps_list.iter().map(String::as_str)) + .block(Block::default().borders(Borders::ALL)) + .highlight_symbol(">>") + .highlight_style(Style::new().bold()), + vertical_chunks[1], + &mut self.list_state, + ); + } + _ => (), + }; + } + + fn handle_events(&mut self, tty_input: &mut io::Bytes) -> io::Result<()> { + if let Some(item) = tty_input.next() { + let read_byte = item?; + let e = termion::event::parse_event(read_byte, tty_input)?; + match e { + Event::Key(Key::Up) => { + self.counter += 1; + } + Event::Key(Key::Down) => { + self.counter = self.counter.saturating_sub(1u32); + } + _ => (), + } + } + Ok(()) + } +} + +fn main() -> ExitCode { + let mut app = NyanitTUI::new().unwrap(); + app.run().unwrap(); + // Terminal restores automatically when `stdout` (RawTerminal) is dropped + + ExitCode::SUCCESS +} diff --git a/src/nyan_system.rs b/src/nyan_system.rs new file mode 100644 index 0000000..03157fd --- /dev/null +++ b/src/nyan_system.rs @@ -0,0 +1,214 @@ +// +// Copyright (C) <2026> + +// 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 . + +/* + Functions fot system interaction +*/ + +use std::fs; +use std::process::Command; + +#[derive(Debug)] +pub struct CommandFailed { + pub stderr: Vec, + pub 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) -> 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 for ExecCommandError { + fn from(err: std::io::Error) -> Self { + ExecCommandError::IoError(err) + } +} + +impl From for ExecCommandError { + fn from(err: std::string::FromUtf8Error) -> Self { + ExecCommandError::FromUtf8Error(err) + } +} + +impl From for ExecCommandError { + fn from(err: CommandFailed) -> Self { + ExecCommandError::CommandFailed(err) + } +} + +impl From 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 command with arguments, capturing stderr in case or failure +pub 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 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 { + 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, ExecCommandError> { + use std::os::unix::fs::FileTypeExt; + let mut res: Vec = 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) +} + +/// Cheking if device is encripted +pub fn bzzpss_dev_encrypted(dev_name: &str) -> Result { + match exec_command("bcachefs", &vec!["unlock", "--check"]) { + 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 + } +} + +//const TMP_ROOT: &str = "/mnt/bee-root"; +pub fn list_bzzpss_snaps(snaps_dir: &str) -> Result, ExecCommandError> { + use regex::Regex; + // root snapshoot in a form of bzz-yyyy-mm-ddTHH-MM-SS + let re = Regex::new(r"^bzz-[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}-[0-9]{2}-[0-9]{2}$").unwrap(); + + let mut res: Vec = Vec::new(); + for maybe_entry in fs::read_dir(snaps_dir)? { + let entry = maybe_entry?; + if entry.file_type()?.is_dir() { + if let Some(dir_name) = entry.file_name().to_str() + && re.is_match(dir_name) + { + res.push(dir_name.to_string()); + } + } + } + Ok(res) +} + +// mounting process +//# Mount essential virtual filesystems +// mount -t proc none /proc +// mount -t sysfs none /sys +// modprobe bcachefs +pub fn prepare_env() -> Result<(), ExecCommandError> { + // mount -t proc none /proc + exec_command("mount", &vec!["-t", "proc", "none", "/proc"])?; + // mount -t sysfs none /sys + exec_command("mount", &vec!["-t", "sysfs", "none", "/sys"])?; + + // modprobe bcachefs + exec_command("modprobe", &vec!["bcachefs"])?; + Ok(()) +} + +// # Mount the real root filesystem +pub fn mount_bee_root(mount_point: &str, dev_name: &str) -> Result<(), ExecCommandError> { + exec_command("mount", &vec!["-o", "ro", dev_name, mount_point])?; + Ok(()) +} + +// finish boot +pub fn hand_off_control(mnt_root: &str, init_path: &str) -> Result<(), ExecCommandError> { + use std::env; + use std::os::unix::process::CommandExt; + + // 2. Change directory to the new root (Mandatory for switch_root) + env::set_current_dir(mnt_root)?; + + // # Clean up virtual filesystems + // !!! do not do this, switch_root handle this stuff and fail if thay not present! + // exec_command("umount", &vec!["/proc"])?; + // exec_command("umount", &vec!["/sys"])?; + + // # Hand off control to the real system's init + // exec switch_root /mnt/root /sbin/init + // 3. Execute switch_root. + // This call replaces the current Rust process. + // It only returns if an error occurs. + let err = Command::new("switch_root") + .arg(mnt_root) + .arg(init_path) + .exec(); + + // If execution reaches here, exec() failed. + Err(format!("switch_root failed: {}", err).into()) +}