diff --git a/.gitignore b/.gitignore index b70f050..8ae93b1 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,5 @@ subprojects/packagecache *.exe *.out *.app + +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 31d8a8a..27c9cac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,6 +65,12 @@ CPMAddPackage( GIT_TAG 3d6e6f56e5e1a3ec4befcc7695504ea23e1d52ab EXCLUDE_FROM_ALL YES ) +CPMAddPackage( + NAME ctre + GITHUB_REPOSITORY hanickadot/compile-time-regular-expressions + GIT_TAG v3.7 + EXCLUDE_FROM_ALL YES +) ## ## CONFIGURATION @@ -115,7 +121,7 @@ if(COS_INSTALLER_BUILD_TESTS) add_subdirectory(tests) endif() -target_link_libraries(${PROJECT_NAME} PRIVATE project_warnings project_options spdlog::spdlog fmt::fmt ftxui::screen ftxui::dom ftxui::component cpr::cpr range-v3::range-v3) +target_link_libraries(${PROJECT_NAME} PRIVATE project_warnings project_options spdlog::spdlog fmt::fmt ftxui::screen ftxui::dom ftxui::component cpr::cpr range-v3::range-v3 ctre::ctre) option(ENABLE_UNITY "Enable Unity builds of projects" OFF) if(ENABLE_UNITY) diff --git a/src/disk.cpp b/src/disk.cpp index 4277e09..5d9d81a 100644 --- a/src/disk.cpp +++ b/src/disk.cpp @@ -136,6 +136,15 @@ std::vector lvm_show_vg() noexcept { return res; } +// Creates a zfs volume +void zfs_create_zvol(const std::string_view& zsize, const std::string_view& zpath) noexcept { +#ifdef NDEVENV + utils::exec(fmt::format(FMT_COMPILE("zfs create -V {}M {} 2>>/tmp/cachyos-install.log"), zsize, zpath), true); +#else + spdlog::debug("zfs create -V {}M {}", zsize, zpath); +#endif +} + // Creates a zfs filesystem, the first parameter is the ZFS path and the second is the mount path void zfs_create_dataset(const std::string_view& zpath, const std::string_view& zmount) noexcept { #ifdef NDEVENV @@ -153,6 +162,15 @@ void zfs_destroy_dataset(const std::string_view& zdataset) noexcept { #endif } +// returns a list of imported zpools +std::string zfs_list_pools() noexcept { +#ifdef NDEVENV + return utils::exec("zfs list -H -o name 2>/dev/null | grep \"/\""); +#else + return "vol0\nvol1\n"; +#endif +} + // returns a list of devices containing zfs members std::string zfs_list_devs() noexcept { std::string list_of_devices{}; @@ -177,10 +195,19 @@ std::string zfs_list_datasets(const std::string_view& type) noexcept { return utils::exec("zfs list -H -o name 2>/dev/null | grep \"/\""); #else + spdlog::debug("type := {}", type); return "zpcachyos"; #endif } +void zfs_set_property(const std::string_view& property, const std::string_view& dataset) noexcept { +#ifdef NDEVENV + utils::exec(fmt::format(FMT_COMPILE("zfs set {} {} 2>>/tmp/cachyos-install.log"), property, dataset), true); +#else + spdlog::debug("zfs set {} {}", property, dataset); +#endif +} + // Other filesystems void select_filesystem(const std::string_view& file_sys) noexcept { auto* config_instance = Config::instance(); diff --git a/src/disk.hpp b/src/disk.hpp index 7d4dddd..5cca34a 100644 --- a/src/disk.hpp +++ b/src/disk.hpp @@ -18,10 +18,13 @@ void mount_existing_subvols(const disk_part& disk) noexcept; std::vector lvm_show_vg() noexcept; // ZFS filesystem +void zfs_create_zvol(const std::string_view& zsize, const std::string_view& zpath) noexcept; void zfs_create_dataset(const std::string_view& zpath, const std::string_view& zmount) noexcept; void zfs_destroy_dataset(const std::string_view& zdataset) noexcept; +std::string zfs_list_pools() noexcept; std::string zfs_list_devs() noexcept; std::string zfs_list_datasets(const std::string_view& type = "none") noexcept; +void zfs_set_property(const std::string_view& property, const std::string_view& dataset) noexcept; // Other filesystems void select_filesystem(const std::string_view& fs) noexcept; diff --git a/src/tui.cpp b/src/tui.cpp index 849dffa..9df1faa 100644 --- a/src/tui.cpp +++ b/src/tui.cpp @@ -14,6 +14,7 @@ #include // for mount #include // for ofstream #include // for copy +#include // for ctre::match #include // for exists, is_directory #include // for basic_string #include // for Renderer, Button @@ -1466,10 +1467,132 @@ bool zfs_create_zpool() noexcept { return true; } -void zfs_import_pool() noexcept { +bool zfs_import_pool() noexcept { + const auto& zlist = utils::make_multiline(utils::exec("zpool import 2>/dev/null | grep \"^[[:space:]]*pool\" | awk -F : '{print $2}' | awk '{$1=$1};1'")); + if (zlist.empty()) { + // no available datasets + detail::infobox_widget("\nNo pools available\"\n"); + std::this_thread::sleep_for(std::chrono::seconds(3)); + return false; + } + + auto* config_instance = Config::instance(); + auto& config_data = config_instance->data(); + + std::string zfs_zpool_name{}; + { + auto screen = ScreenInteractive::Fullscreen(); + std::int32_t selected{}; + bool success{}; + auto ok_callback = [&] { + zfs_zpool_name = zlist[static_cast(selected)]; + success = true; + screen.ExitLoopClosure()(); + }; + static constexpr auto zfs_menu_body = "\nSelect a zpool from the list\n"; + const auto& content_size = size(HEIGHT, LESS_THAN, 10) | size(WIDTH, GREATER_THAN, 40); + detail::menu_widget(zlist, ok_callback, &selected, &screen, zfs_menu_body, {size(HEIGHT, LESS_THAN, 18), content_size}); + /* clang-format off */ + if (!success) { return false; } + /* clang-format on */ + } + +#ifdef NDEVENV + const auto& mountpoint = std::get(config_data["MOUNTPOINT"]); + utils::exec(fmt::format(FMT_COMPILE("zpool import -R {} {} 2>>/tmp/cachyos-install.log"), mountpoint, zfs_zpool_name), true); +#endif + + config_data["ZFS"] = 1; + + + return true; } -void zfs_new_ds() noexcept { +bool zfs_new_ds(const std::string_view& zmount = "") noexcept { + const auto& zlist = utils::make_multiline(utils::zfs_list_pools()); + if (zlist.empty()) { + // no available datasets + detail::infobox_widget("\nNo pools available\"\n"); + std::this_thread::sleep_for(std::chrono::seconds(3)); + return false; + } + + std::string zfs_zpool_name{}; + { + auto screen = ScreenInteractive::Fullscreen(); + std::int32_t selected{}; + bool success{}; + auto ok_callback = [&] { + zfs_zpool_name = zlist[static_cast(selected)]; + success = true; + screen.ExitLoopClosure()(); + }; + static constexpr auto zfs_menu_body = "\nSelect a zpool from the list\n"; + const auto& content_size = size(HEIGHT, LESS_THAN, 10) | size(WIDTH, GREATER_THAN, 40); + detail::menu_widget(zlist, ok_callback, &selected, &screen, zfs_menu_body, {size(HEIGHT, LESS_THAN, 18), content_size}); + /* clang-format off */ + if (!success) { return false; } + /* clang-format on */ + } + + static constexpr auto zfs_dataset_body = "\nEnter a name and relative path for the dataset.\n \nFor example, if you want the dataset to be placed at zpool/data/zname, enter 'data/zname'\n"; + static constexpr auto zfs_zpoolcvalidation1 = "\nzpool names must start with a letter and are limited to only alphanumeric characters and the special characters : . - _\n"; + + // We need to get a name for the dataset + std::string zfs_dataset_name{}; + auto zfs_menu_text = zfs_dataset_body; + + // Loop while zpool name is not valid. + while (true) { + if (!detail::inputbox_widget(zfs_dataset_name, zfs_menu_text, size(HEIGHT, GREATER_THAN, 1))) { + return false; + } + zfs_menu_text = zfs_dataset_body; + + // validation + if (zfs_dataset_name.empty() || std::isdigit(zfs_dataset_name[0]) || (ranges::any_of(zfs_dataset_name, [](char ch) { return (!std::isalnum(ch)) && (ch != '/') && (ch != ':') && (ch != '.') && (ch != '-') && (ch != '_'); }))) { + zfs_menu_text = zfs_zpoolcvalidation1; + } + + /* clang-format off */ + if (zfs_menu_text == zfs_dataset_body) { break; } + /* clang-format on */ + } + + if (zmount == "legacy") { + utils::zfs_create_dataset(fmt::format(FMT_COMPILE("{}/{}"), zfs_zpool_name, zfs_dataset_name), zmount); + } else if (zmount == "zvol") { + static constexpr auto zvol_size_menu_body = "\nEnter the size of the zvol in megabytes(MB)\n"; + static constexpr auto zvol_size_menu_validation = "\nYou must enter a number greater than 0\n"; + + // We need to get a name for the zvol + std::string zvol_size{}; + zfs_menu_text = zvol_size_menu_body; + + // Loop while zvol name is not valid. + while (true) { + if (!detail::inputbox_widget(zvol_size, zfs_menu_text, size(HEIGHT, GREATER_THAN, 1))) { + return false; + } + zfs_menu_text = zvol_size_menu_body; + + // validation + + if (zvol_size.empty() || ranges::any_of(zvol_size, [](char ch) { return !std::isdigit(ch); })) { + zfs_menu_text = zvol_size_menu_validation; + } + + /* clang-format off */ + if (zfs_menu_text == zvol_size_menu_body) { break; } + /* clang-format on */ + } + utils::zfs_create_zvol(zvol_size, fmt::format(FMT_COMPILE("{}/{}"), zfs_zpool_name, zfs_dataset_name)); + } else { + spdlog::error("HELLO! IMPLEMENT ME!"); + return false; + } + + return true; } void zfs_set_property() noexcept { @@ -1491,7 +1614,7 @@ void zfs_set_property() noexcept { success = true; screen.ExitLoopClosure()(); }; - static constexpr auto zfs_menu_body = "\nEnter the property and value you would like to\nset using the format property=mountpoint\n \nFor example, you could enter:\ncompression=lz4\nor\nacltype=posixacl\n"; + static constexpr auto zfs_menu_body = "\nSelect the dataset you would like to set a property on\n"; const auto& content_size = size(HEIGHT, LESS_THAN, 10) | size(WIDTH, GREATER_THAN, 40); detail::menu_widget(zlist, ok_callback, &selected, &screen, zfs_menu_body, {size(HEIGHT, LESS_THAN, 18), content_size}); /* clang-format off */ @@ -1499,13 +1622,32 @@ void zfs_set_property() noexcept { /* clang-format on */ } - // const auto& content = fmt::format(FMT_COMPILE("\nPlease confirm that you want to irrevocably\ndelete all the data on '{}'\nand the data contained on all of it's children\n"), zdataset); - // const auto& do_destroy = detail::yesno_widget(content, size(HEIGHT, LESS_THAN, 20) | size(WIDTH, LESS_THAN, 75)); - /* clang-format off */ - //if (!do_destroy) { return; } - /* clang-format on */ + static constexpr auto zfs_mountpoint_body = "\nEnter the property and value you would like to\nset using the format property=mountpoint\n \nFor example, you could enter:\ncompression=lz4\nor\nacltype=posixacl\n\n"; + static constexpr auto zfs_property_invalid = "\nInput must be the format property=mountpoint\n"; - // utils::zfs_destroy_dataset(zdataset); + // We need to get a valid property + std::string zfs_property_ent{}; + auto zfs_menu_text = zfs_mountpoint_body; + + // Loop while property is not valid. + while (true) { + if (!detail::inputbox_widget(zfs_property_ent, zfs_menu_text, size(HEIGHT, GREATER_THAN, 1))) { + return; + } + zfs_menu_text = zfs_mountpoint_body; + + // validation + if (!ctre::match<"[a-zA-Z]*=[a-zA-Z0-9]*">(zfs_property_ent)) { + zfs_menu_text = zfs_property_invalid; + } + + /* clang-format off */ + if (zfs_menu_text == zfs_mountpoint_body) { break; } + /* clang-format on */ + } + + // Set the property + utils::zfs_set_property(zfs_property_ent, zdataset); } void zfs_destroy_dataset() noexcept { @@ -1594,7 +1736,7 @@ void zfs_menu_manual() noexcept { case 0: tui::zfs_create_zpool(); break; - /*case 1: + case 1: tui::zfs_import_pool(); break; case 2: @@ -1605,7 +1747,7 @@ void zfs_menu_manual() noexcept { break; case 4: tui::zfs_new_ds("zvol"); - break;*/ + break; case 5: tui::zfs_set_property(); break; diff --git a/src/utils.cpp b/src/utils.cpp index 58feda8..421b5c8 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -448,11 +448,13 @@ void create_new_user(const std::string_view& user, const std::string_view& passw if (fs::exists(sudoers_file)) { utils::exec(fmt::format(FMT_COMPILE("sed -i '/NOPASSWD/!s/# %sudo/%sudo/g' {}"), sudoers_file)); } +#else + spdlog::debug("user := {}, password := {}", user, password); #endif } // Set password for root user -void set_root_password([[maybe_unused]] const std::string_view& password) noexcept { +void set_root_password(const std::string_view& password) noexcept { #ifdef NDEVENV auto* config_instance = Config::instance(); auto& config_data = config_instance->data(); @@ -462,6 +464,8 @@ void set_root_password([[maybe_unused]] const std::string_view& password) noexce utils::exec(fmt::format(FMT_COMPILE("echo -e \"{0}\n{0}\" > /tmp/.passwd"), password)); utils::exec(fmt::format(FMT_COMPILE("arch-chroot {} passwd root < /tmp/.passwd &>/dev/null"), mountpoint)); fs::remove("/tmp/.passwd", err); +#else + spdlog::debug("root password := {}", password); #endif } @@ -1204,7 +1208,7 @@ void get_cryptroot() noexcept { temp_out = utils::exec("lsblk -lno NAME,FSTYPE,TYPE | grep \" crypt$\" | grep -i \"LVM2_member\" | uniq | awk '{print \"/dev/mapper/\"$1}'"); if (!temp_out.empty()) { const auto& cryptparts = utils::make_multiline(temp_out); - const auto& check_functor = [&]([[maybe_unused]] const auto cryptpart) { + const auto& check_functor = [&]([[maybe_unused]] const auto& cryptpart) { auto& luks_uuid = std::get(config_data["LUKS_UUID"]); luks_uuid = utils::exec("lsblk -ino NAME,FSTYPE,TYPE,MOUNTPOINT,UUID | tac | sed -r 's/^[^[:alnum:]]+//' | sed -n -e \"/\\/mnt /,/part/p\" | awk '/crypto_LUKS/ {print $4}'"); config_data["LUKS_DEV"] = fmt::format(FMT_COMPILE("cryptdevice=UUID={}:{}"), luks_uuid, luks_name); @@ -1218,7 +1222,7 @@ void get_cryptroot() noexcept { temp_out = utils::exec("lsblk -lno NAME,FSTYPE,TYPE,MOUNTPOINT | grep \"/mnt$\" | grep \"part\" | grep -i \"crypto_luks\" | uniq | awk '{print \"/dev/\"$1}'"); if (!temp_out.empty()) { const auto& cryptparts = utils::make_multiline(temp_out); - const auto& check_functor = [&](const auto cryptpart) { + const auto& check_functor = [&](const auto& cryptpart) { auto& luks_uuid = std::get(config_data["LUKS_UUID"]); luks_uuid = utils::exec(fmt::format(FMT_COMPILE("lsblk -lno UUID,TYPE,FSTYPE {} | grep \"part\" | grep -i \"crypto_luks\" | {}"), cryptpart, "awk '{print $1}'")); config_data["LUKS_DEV"] = fmt::format(FMT_COMPILE("cryptdevice=UUID={}:{}"), luks_uuid, luks_name);