use anyhow::{Result, bail};
use clap::{Parser, Subcommand, ValueEnum};
use config::{Config, File, FileFormat};
use greenboot::{
handle_motd, handle_reboot, handle_rollback, run_diagnostics, run_green, run_red,
set_boot_counter, set_boot_status, unset_boot_counter,
};
use serde::Deserialize;
static GREENBOOT_CONFIG_FILE: &str = "/etc/greenboot/greenboot.conf";
static GRUB_PATH: &str = "/boot/grub2/grubenv";
static MOUNT_INFO_PATH: &str = "/proc/mounts";
#[derive(Parser)]
#[clap(author, version, about, long_about = None)]
#[clap(propagate_version = true)]
struct Cli {
#[clap(value_enum, short, long, default_value_t = LogLevel::Info)]
log_level: LogLevel,
#[clap(subcommand)]
command: Commands,
}
#[derive(Debug, Deserialize)]
struct GreenbootConfig {
max_reboot: u16,
}
impl GreenbootConfig {
fn set_default() -> Self {
Self { max_reboot: 3 }
}
fn get_config() -> Self {
let mut config = Self::set_default();
let parsed = Config::builder()
.add_source(File::new(GREENBOOT_CONFIG_FILE, FileFormat::Ini))
.build();
match parsed {
Ok(c) => {
config.max_reboot = match c.get_int("GREENBOOT_MAX_BOOT_ATTEMPTS") {
Ok(c) => c.try_into().unwrap_or_else(|e| {
log::warn!(
"{e}, config error, using default value: {}",
config.max_reboot
);
config.max_reboot
}),
Err(e) => {
log::warn!(
"{e}, config error, using default value: {}",
config.max_reboot
);
config.max_reboot
}
}
}
Err(e) => log::warn!(
"{e}, config error, using default value: {}",
config.max_reboot
),
}
config
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum LogLevel {
Trace,
Debug,
Info,
Warn,
Error,
Off,
}
impl LogLevel {
fn to_log(self) -> log::LevelFilter {
match self {
LogLevel::Trace => log::LevelFilter::Trace,
LogLevel::Debug => log::LevelFilter::Debug,
LogLevel::Info => log::LevelFilter::Info,
LogLevel::Warn => log::LevelFilter::Warn,
LogLevel::Error => log::LevelFilter::Error,
LogLevel::Off => log::LevelFilter::Off,
}
}
}
#[derive(Subcommand)]
enum Commands {
HealthCheck,
Rollback,
}
fn health_check() -> Result<()> {
let config = GreenbootConfig::get_config();
log::debug!("{config:?}");
handle_motd("healthcheck is in progress")?;
match run_diagnostics() {
Ok(()) => {
log::info!("greenboot health-check passed.");
let errors = run_green();
if !errors.is_empty() {
log::error!("There is a problem with green script runner");
errors.iter().for_each(|e| log::error!("{e}"));
}
handle_motd("healthcheck passed - status is GREEN")
.unwrap_or_else(|e| log::error!("cannot set motd: {}", e));
set_boot_status(true, GRUB_PATH, MOUNT_INFO_PATH)?;
Ok(())
}
Err(e) => {
log::error!("Greenboot error: {e}");
handle_motd("healthcheck failed - status is RED")
.unwrap_or_else(|e| log::error!("cannot set motd: {}", e));
let errors = run_red();
if !errors.is_empty() {
log::error!("There is a problem with red script runner");
errors.iter().for_each(|e| log::error!("{e}"));
}
set_boot_status(false, GRUB_PATH, MOUNT_INFO_PATH)?;
set_boot_counter(config.max_reboot, GRUB_PATH, MOUNT_INFO_PATH)
.unwrap_or_else(|e| log::error!("cannot set boot_counter: {}", e));
handle_reboot(false).unwrap_or_else(|e| log::error!("cannot reboot: {}", e));
Err(e)
}
}
}
fn trigger_rollback() -> Result<()> {
match handle_rollback() {
Ok(()) => {
log::info!("Rollback successful");
unset_boot_counter(GRUB_PATH, MOUNT_INFO_PATH)?;
handle_reboot(true)
}
Err(e) => {
bail!("{e}, Rollback is not initiated");
}
}
}
fn main() -> Result<()> {
let cli = Cli::parse();
pretty_env_logger::formatted_builder()
.filter_level(cli.log_level.to_log())
.init();
match cli.command {
Commands::HealthCheck => health_check(),
Commands::Rollback => trigger_rollback(),
}
}