👷 gucc: add grub config gen from provided struct

This commit is contained in:
Vladislav Nepogodin 2024-07-02 02:03:33 +04:00
parent 9c8f8f241e
commit 24a250dd7f
No known key found for this signature in database
GPG Key ID: B62C3D10C54D5DA9
5 changed files with 435 additions and 0 deletions

View File

@ -40,6 +40,7 @@ jobs:
./gucc/tests/test-pacmanconf
./gucc/tests/test-fstab_gen
./gucc/tests/test-crypttab_gen
./gucc/tests/test-grub_config_gen
shell: bash
build-cmake_withoutdev:
name: Build with CMake (DEVENV OFF)
@ -86,6 +87,7 @@ jobs:
./gucc/tests/test-pacmanconf
./gucc/tests/test-fstab_gen
./gucc/tests/test-crypttab_gen
./gucc/tests/test-grub_config_gen
shell: bash
build-meson_withoutdev:
name: Build with Meson (DEVENV OFF)

View File

@ -1,10 +1,64 @@
#ifndef BOOTLOADER_HPP
#define BOOTLOADER_HPP
#include <cinttypes>
#include <optional> // for optional
#include <string> // for string
#include <string_view> // for string_view
namespace gucc::bootloader {
struct GrubConfig final {
// e.g GRUB_DEFAULT=0
std::string default_entry{"0"};
// e.g GRUB_TIMEOUT=5
std::int32_t grub_timeout{5};
// e.g GRUB_DISTRIBUTOR="Arch"
std::string grub_distributor{"Arch"};
// e.g GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3 quiet"
std::string cmdline_linux_default{"loglevel=3 quiet"};
// e.g GRUB_CMDLINE_LINUX=""
std::string cmdline_linux{};
// e.g GRUB_PRELOAD_MODULES="part_gpt part_msdos"
std::string preload_modules{"part_gpt part_msdos"};
// e.g #GRUB_ENABLE_CRYPTODISK=y
std::optional<bool> enable_cryptodisk{};
// e.g GRUB_TIMEOUT_STYLE=menu
std::string timeout_style{"menu"};
// e.g GRUB_TERMINAL_INPUT=console
std::string terminal_input{"console"};
// e.g #GRUB_TERMINAL_OUTPUT=console
std::optional<std::string> terminal_output{};
// e.g GRUB_GFXMODE=auto
std::string gfxmode{"auto"};
// e.g GRUB_GFXPAYLOAD_LINUX=keep
std::string gfxpayload_linux{"keep"};
// e.g #GRUB_DISABLE_LINUX_UUID=true
std::optional<bool> disable_linux_uuid{};
// e.g GRUB_DISABLE_RECOVERY=true
bool disable_recovery{true};
// e.g #GRUB_COLOR_NORMAL="light-blue/black"
std::optional<std::string> color_normal{};
// e.g #GRUB_COLOR_HIGHLIGHT="light-cyan/blue"
std::optional<std::string> color_highlight{};
// e.g #GRUB_BACKGROUND="/path/to/wallpaper"
std::optional<std::string> background{};
// e.g #GRUB_THEME="/path/to/gfxtheme"
std::optional<std::string> theme{};
// e.g #GRUB_INIT_TUNE="480 440 1"
std::optional<std::string> init_tune{};
// e.g #GRUB_SAVEDEFAULT=true
std::optional<bool> savedefault{};
// e.g #GRUB_DISABLE_SUBMENU=y
std::optional<bool> disable_submenu{};
// e.g #GRUB_DISABLE_OS_PROBER=false
std::optional<bool> disable_os_prober{};
};
// Generate grub_config into string
auto gen_grub_config(const GrubConfig& grub_config) noexcept -> std::string;
// Installs & configures systemd-boot on system
auto install_systemd_boot(std::string_view root_mountpoint, std::string_view efi_directory, bool is_volume_removable) noexcept -> bool;

View File

@ -7,10 +7,204 @@
#include <spdlog/spdlog.h>
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wold-style-cast"
#elif defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnull-dereference"
#pragma GCC diagnostic ignored "-Wuseless-cast"
#pragma GCC diagnostic ignored "-Wold-style-cast"
#endif
#include <range/v3/range/conversion.hpp>
#include <range/v3/view/join.hpp>
#include <range/v3/view/split.hpp>
#include <range/v3/view/transform.hpp>
#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
using namespace std::string_view_literals;
#define CONV_REQ_F(needle, f) \
if (line.starts_with(needle)) { \
return fmt::format(needle "{}", f); \
}
#define CONV_REQ_F_S(needle, f) \
if (line.starts_with(needle)) { \
return fmt::format(needle "\"{}\"", f); \
}
#define CONV_OPT_F(needle, f, default_val) \
if (line.starts_with(needle)) { \
if (f) { \
return fmt::format(needle "{}", *f); \
} \
return {"#" needle default_val}; \
}
#define CONV_OPT_F_S(needle, f, default_val) \
if (line.starts_with(needle)) { \
if (f) { \
return fmt::format(needle "\"{}\"", *f); \
} \
return {"#" needle "\"" default_val "\""}; \
}
#define CONV_REQ_B(needle, f) \
if (line.starts_with(needle)) { \
return fmt::format(needle "{}", convert_boolean_val(needle, f)); \
}
#define CONV_OPT_B(needle, f, default_val) \
if (line.starts_with(needle)) { \
if (f) { \
return fmt::format(needle "{}", convert_boolean_val(needle, *f)); \
} \
return {"#" needle default_val}; \
}
namespace {
// NOLINTNEXTLINE
static constexpr auto GRUB_DEFAULT_CONFIG = R"(# GRUB boot loader configuration
GRUB_DEFAULT=0
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="Arch"
GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3 quiet"
GRUB_CMDLINE_LINUX=""
# Preload both GPT and MBR modules so that they are not missed
GRUB_PRELOAD_MODULES="part_gpt part_msdos"
# Uncomment to enable booting from LUKS encrypted devices
#GRUB_ENABLE_CRYPTODISK=y
# Set to 'countdown' or 'hidden' to change timeout behavior,
# press ESC key to display menu.
GRUB_TIMEOUT_STYLE=menu
# Uncomment to use basic console
GRUB_TERMINAL_INPUT=console
# Uncomment to disable graphical terminal
#GRUB_TERMINAL_OUTPUT=console
# The resolution used on graphical terminal
# note that you can use only modes which your graphic card supports via VBE
# you can see them in real GRUB with the command `videoinfo'
GRUB_GFXMODE=auto
# Uncomment to allow the kernel use the same resolution used by grub
GRUB_GFXPAYLOAD_LINUX=keep
# Uncomment if you want GRUB to pass to the Linux kernel the old parameter
# format "root=/dev/xxx" instead of "root=/dev/disk/by-uuid/xxx"
#GRUB_DISABLE_LINUX_UUID=true
# Uncomment to disable generation of recovery mode menu entries
GRUB_DISABLE_RECOVERY=true
# Uncomment and set to the desired menu colors. Used by normal and wallpaper
# modes only. Entries specified as foreground/background.
#GRUB_COLOR_NORMAL="light-blue/black"
#GRUB_COLOR_HIGHLIGHT="light-cyan/blue"
# Uncomment one of them for the gfx desired, a image background or a gfxtheme
#GRUB_BACKGROUND="/path/to/wallpaper"
#GRUB_THEME="/path/to/gfxtheme"
# Uncomment to get a beep at GRUB start
#GRUB_INIT_TUNE="480 440 1"
# Uncomment to make GRUB remember the last selection. This requires
# setting 'GRUB_DEFAULT=saved' above.
#GRUB_SAVEDEFAULT=true
# Uncomment to disable submenus in boot menu
#GRUB_DISABLE_SUBMENU=y
# Probing for other operating systems is disabled for security reasons. Read
# documentation on GRUB_DISABLE_OS_PROBER, if still want to enable this
# functionality install os-prober and uncomment to detect and include other
# operating systems.
#GRUB_DISABLE_OS_PROBER=false
)"sv;
template <typename T, typename npos_type = std::remove_cvref_t<decltype(T::npos)>>
concept string_findable = requires(T value) {
// check that type of T::npos is T::size_type
{ npos_type{} } -> std::same_as<typename T::size_type>;
// check that type of T::find is T::size_type
{ value.find(std::string_view{""}) } -> std::same_as<typename T::size_type>;
};
// simple helper function to check if string contains a string
constexpr auto contains(string_findable auto const& str, std::string_view needle) noexcept -> bool {
using str_type = std::remove_reference_t<decltype(str)>;
return str.find(needle) != str_type::npos;
}
constexpr auto convert_boolean_val(std::string_view needle, bool value) noexcept -> std::string_view {
if (needle == "GRUB_ENABLE_CRYPTODISK="sv || needle == "GRUB_DISABLE_SUBMENU="sv) {
return value ? "y"sv : "n"sv;
}
return value ? "true"sv : "false"sv;
}
auto parse_grub_line(const gucc::bootloader::GrubConfig& grub_config, std::string_view line) noexcept -> std::string {
if (line.starts_with("#GRUB_")) {
// uncomment grub setting
line.remove_prefix(1);
}
if (line.starts_with("GRUB_")) {
CONV_REQ_F("GRUB_DEFAULT=", grub_config.default_entry);
CONV_REQ_F("GRUB_TIMEOUT=", grub_config.grub_timeout);
CONV_REQ_F_S("GRUB_DISTRIBUTOR=", grub_config.grub_distributor);
CONV_REQ_F_S("GRUB_CMDLINE_LINUX_DEFAULT=", grub_config.cmdline_linux_default);
CONV_REQ_F_S("GRUB_CMDLINE_LINUX=", grub_config.cmdline_linux);
CONV_REQ_F_S("GRUB_PRELOAD_MODULES=", grub_config.preload_modules);
CONV_REQ_F("GRUB_TIMEOUT_STYLE=", grub_config.timeout_style);
CONV_REQ_F("GRUB_TERMINAL_INPUT=", grub_config.terminal_input);
CONV_OPT_F("GRUB_TERMINAL_OUTPUT=", grub_config.terminal_output, "console");
CONV_REQ_F("GRUB_GFXMODE=", grub_config.gfxmode);
CONV_REQ_F("GRUB_GFXPAYLOAD_LINUX=", grub_config.gfxpayload_linux);
CONV_OPT_F_S("GRUB_COLOR_NORMAL=", grub_config.color_normal, "light-blue/black");
CONV_OPT_F_S("GRUB_COLOR_HIGHLIGHT=", grub_config.color_highlight, "light-cyan/blue");
CONV_OPT_F_S("GRUB_BACKGROUND=", grub_config.background, "/path/to/wallpaper");
CONV_OPT_F_S("GRUB_THEME=", grub_config.theme, "/path/to/gfxtheme");
CONV_OPT_F_S("GRUB_INIT_TUNE=", grub_config.init_tune, "480 440 1");
if (contains(line, "=y") || contains(line, "=true") || contains(line, "=false")) {
// booleans
CONV_OPT_B("GRUB_ENABLE_CRYPTODISK=", grub_config.enable_cryptodisk, "y");
CONV_OPT_B("GRUB_DISABLE_LINUX_UUID=", grub_config.disable_linux_uuid, "true");
CONV_REQ_B("GRUB_DISABLE_RECOVERY=", grub_config.disable_recovery);
CONV_OPT_B("GRUB_SAVEDEFAULT=", grub_config.savedefault, "true");
CONV_OPT_B("GRUB_DISABLE_SUBMENU=", grub_config.disable_submenu, "y");
CONV_OPT_B("GRUB_DISABLE_OS_PROBER=", grub_config.disable_os_prober, "false");
}
}
return std::string{line.data(), line.size()};
}
} // namespace
namespace gucc::bootloader {
auto gen_grub_config(const GrubConfig& grub_config) noexcept -> std::string {
std::string result = GRUB_DEFAULT_CONFIG | ranges::views::split('\n')
| ranges::views::transform([&](auto&& rng) {
auto&& line = std::string_view(&*rng.begin(), static_cast<size_t>(ranges::distance(rng)));
return parse_grub_line(grub_config, line);
})
| ranges::views::join('\n')
| ranges::to<std::string>();
result += '\n';
return result;
}
auto install_systemd_boot(std::string_view root_mountpoint, std::string_view efi_directory, bool is_volume_removable) noexcept -> bool {
// Install systemd-boot onto EFI
const auto& bootctl_cmd = fmt::format(FMT_COMPILE("bootctl --path={} install"), efi_directory);

View File

@ -29,3 +29,11 @@ executable(
link_with: [gucc_lib],
include_directories: [include_directories('../include')],
install: false)
executable(
'test-grub_config_gen',
files('unit-grub_config_gen.cpp'),
dependencies: deps,
link_with: [gucc_lib],
include_directories: [include_directories('../include')],
install: false)

View File

@ -0,0 +1,177 @@
#include "gucc/bootloader.hpp"
#include <cassert>
#include <algorithm>
#include <string>
#include <string_view>
#include <spdlog/sinks/callback_sink.h>
#include <spdlog/spdlog.h>
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wold-style-cast"
#elif defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnull-dereference"
#pragma GCC diagnostic ignored "-Wuseless-cast"
#pragma GCC diagnostic ignored "-Wold-style-cast"
#endif
#include <range/v3/range/conversion.hpp>
#include <range/v3/view/filter.hpp>
#include <range/v3/view/join.hpp>
#include <range/v3/view/split.hpp>
#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
using namespace std::string_literals;
using namespace std::string_view_literals;
static constexpr auto GRUB_DEFAULTS_TEST = R"(# GRUB boot loader configuration
GRUB_DEFAULT=0
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="Arch"
GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3 quiet"
GRUB_CMDLINE_LINUX=""
# Preload both GPT and MBR modules so that they are not missed
GRUB_PRELOAD_MODULES="part_gpt part_msdos"
# Uncomment to enable booting from LUKS encrypted devices
#GRUB_ENABLE_CRYPTODISK=y
# Set to 'countdown' or 'hidden' to change timeout behavior,
# press ESC key to display menu.
GRUB_TIMEOUT_STYLE=menu
# Uncomment to use basic console
GRUB_TERMINAL_INPUT=console
# Uncomment to disable graphical terminal
#GRUB_TERMINAL_OUTPUT=console
# The resolution used on graphical terminal
# note that you can use only modes which your graphic card supports via VBE
# you can see them in real GRUB with the command `videoinfo'
GRUB_GFXMODE=auto
# Uncomment to allow the kernel use the same resolution used by grub
GRUB_GFXPAYLOAD_LINUX=keep
# Uncomment if you want GRUB to pass to the Linux kernel the old parameter
# format "root=/dev/xxx" instead of "root=/dev/disk/by-uuid/xxx"
#GRUB_DISABLE_LINUX_UUID=true
# Uncomment to disable generation of recovery mode menu entries
GRUB_DISABLE_RECOVERY=true
# Uncomment and set to the desired menu colors. Used by normal and wallpaper
# modes only. Entries specified as foreground/background.
#GRUB_COLOR_NORMAL="light-blue/black"
#GRUB_COLOR_HIGHLIGHT="light-cyan/blue"
# Uncomment one of them for the gfx desired, a image background or a gfxtheme
#GRUB_BACKGROUND="/path/to/wallpaper"
#GRUB_THEME="/path/to/gfxtheme"
# Uncomment to get a beep at GRUB start
#GRUB_INIT_TUNE="480 440 1"
# Uncomment to make GRUB remember the last selection. This requires
# setting 'GRUB_DEFAULT=saved' above.
#GRUB_SAVEDEFAULT=true
# Uncomment to disable submenus in boot menu
#GRUB_DISABLE_SUBMENU=y
# Probing for other operating systems is disabled for security reasons. Read
# documentation on GRUB_DISABLE_OS_PROBER, if still want to enable this
# functionality install os-prober and uncomment to detect and include other
# operating systems.
#GRUB_DISABLE_OS_PROBER=false
)"sv;
static constexpr auto GRUB_OPTIONALS_TEST = R"(GRUB_DEFAULT=saved
GRUB_TIMEOUT=10
GRUB_DISTRIBUTOR="CachyOS"
GRUB_CMDLINE_LINUX_DEFAULT="nowatchdog nvme_load=YES zswap.enabled=0 splash quiet"
GRUB_CMDLINE_LINUX="quiet"
GRUB_PRELOAD_MODULES="part_gpt part_msdos part_efi"
GRUB_ENABLE_CRYPTODISK=y
GRUB_TIMEOUT_STYLE=menu
GRUB_TERMINAL_INPUT=console
GRUB_TERMINAL_OUTPUT=console
GRUB_GFXMODE=auto
GRUB_GFXPAYLOAD_LINUX=keep
GRUB_DISABLE_LINUX_UUID=true
GRUB_DISABLE_RECOVERY=true
GRUB_COLOR_NORMAL="light-blue/yellow"
GRUB_COLOR_HIGHLIGHT="light-cyan/yellow"
GRUB_BACKGROUND="/path/to/wallpaper/here"
GRUB_THEME="/path/to/gfxtheme-smth"
GRUB_INIT_TUNE="380 420 2"
GRUB_SAVEDEFAULT=true
GRUB_DISABLE_SUBMENU=y
GRUB_DISABLE_OS_PROBER=false
)"sv;
inline auto filtered_res(std::string_view content) noexcept -> std::string {
auto&& result = content | ranges::views::split('\n')
| ranges::views::filter([](auto&& rng) {
auto&& line = std::string_view(&*rng.begin(), static_cast<size_t>(ranges::distance(rng)));
return !line.empty() && !line.starts_with("# ");
})
| ranges::views::join('\n')
| ranges::to<std::string>();
return result + '\n';
}
int main() {
auto callback_sink = std::make_shared<spdlog::sinks::callback_sink_mt>([](const spdlog::details::log_msg& msg) {
// noop
});
spdlog::set_default_logger(std::make_shared<spdlog::logger>("default", callback_sink));
// default config
{
const gucc::bootloader::GrubConfig grub_config{};
const auto& grub_config_content = gucc::bootloader::gen_grub_config(grub_config);
assert(grub_config_content == GRUB_DEFAULTS_TEST);
}
// optionals set
{
const gucc::bootloader::GrubConfig grub_config{
.default_entry = "saved"s,
.grub_timeout = 10,
.grub_distributor = "CachyOS"s,
.cmdline_linux_default = "nowatchdog nvme_load=YES zswap.enabled=0 splash quiet"s,
.cmdline_linux = "quiet"s,
.preload_modules = "part_gpt part_msdos part_efi"s,
.enable_cryptodisk = true,
.timeout_style = "menu"s,
.terminal_input = "console"s,
.terminal_output = "console"s,
.gfxmode = "auto"s,
.gfxpayload_linux = "keep"s,
.disable_linux_uuid = true,
.disable_recovery = true,
.color_normal = "light-blue/yellow"s,
.color_highlight = "light-cyan/yellow"s,
.background = "/path/to/wallpaper/here"s,
.theme = "/path/to/gfxtheme-smth"s,
.init_tune = "380 420 2"s,
.savedefault = true,
.disable_submenu = true,
.disable_os_prober = false,
};
const auto& grub_config_content = filtered_res(gucc::bootloader::gen_grub_config(grub_config));
assert(grub_config_content == GRUB_OPTIONALS_TEST);
}
}