diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index afb7071..8312e81 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -1 +1,2 @@ -Enables support for HTTPS, via optional user-defined protocol (#7) +Adds a check to ensure that the users version of AdGuard Home is recent enough to support the required API calls (#5) +Allows for graceful exit, instead of panicking diff --git a/Cargo.lock b/Cargo.lock index 7891d76..d1e4bdb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "adguardian" -version = "1.3.0" +version = "1.4.0" dependencies = [ "anyhow", "base64 0.13.1", @@ -14,7 +14,9 @@ dependencies = [ "futures", "ratatui", "reqwest", + "semver", "serde", + "serde_json", "tokio", ] @@ -866,6 +868,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + [[package]] name = "serde" version = "1.0.162" diff --git a/Cargo.toml b/Cargo.toml index 39070b4..5b3dbc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "adguardian" -version = "1.3.0" +version = "1.4.0" edition = "2021" authors = ["Alicia Sykes"] description = "Terminal-based, real-time traffic monitoring and statistics for your AdGuard Home instance " @@ -25,8 +25,12 @@ crossterm = { version = "0.22.0", features = ["serde"] } futures = "0.3" # HTTP client reqwest = { version = "0.11", default-features = false, features = ["blocking", "json", "rustls-tls"] } -# Decerilization of JSON responses +# Decerilization of responses serde = { version = "1.0", features = ["derive"] } +# Decerilization of JSON responses +serde_json = "1.0" +# Read and calculate semantic version numbers +semver = "1.0" # Date + time manipulation tokio = { version = "1", features = ["full"] } # Terminal UI library diff --git a/src/welcome.rs b/src/welcome.rs index 79e5750..a552696 100644 --- a/src/welcome.rs +++ b/src/welcome.rs @@ -6,6 +6,10 @@ use std::{ use reqwest::{Client, Error}; use colored::*; +use serde_json::Value; +use semver::{Version}; + +/// Reusable function that just prints success messages to the console fn print_info(text: &str, is_secondary: bool) { if is_secondary { println!("{}", text.green().italic().dimmed()); @@ -14,7 +18,7 @@ fn print_info(text: &str, is_secondary: bool) { }; } - +/// Prints the AdGuardian ASCII art to console fn print_ascii_art() { let art = r" █████╗ ██████╗ ██████╗ ██╗ ██╗ █████╗ ██████╗ ██████╗ ██╗ █████╗ ███╗ ██╗ @@ -30,19 +34,22 @@ fn print_ascii_art() { print_info("For documentation and support, please visit: https://github.com/lissy93/adguardian-term\n", true); } -fn print_error(address: &str, error: Option<&Error>) { +/// Print error message, along with (optional) stack trace, then exit +fn print_error(message: &str, sub_message: &str, error: Option<&Error>) { eprintln!( - "{}{}", - format!("Failed to connect to AdGuard at {}", address).red(), + "{}{}{}", + format!("{}", message).red(), match error { Some(err) => format!("\n{}", err).red().dimmed(), None => "".red().dimmed(), }, + format!("\n{}", sub_message).yellow(), ); - eprintln!("{}\n{}", "\nPlease check your environmental variables and try again.".yellow(), "Exiting...".blue()); -} + std::process::exit(1); +} +/// Given a key, get the value from the environmental variables, and print it to the console fn get_env(key: &str) -> Result { env::var(key).map(|v| { println!( @@ -58,6 +65,39 @@ fn get_env(key: &str) -> Result { }) } +/// Given a possibly undefined version number, check if it's present and supported +fn check_version(version: Option<&str>) { + let min_version = Version::parse("0.107.29").unwrap(); + + match version { + Some(version_str) => { + let adguard_version = Version::parse(&version_str[1..]).unwrap(); + + if adguard_version < min_version { + print_error( + "AdGuard Home version is too old, and is now unsupported", + format!("You're running AdGuard {}. Please upgrade to v{} or later.", version_str, min_version.to_string()).as_str(), + None, + ); + } + }, + None => { + print_error( + "Unsupported AdGuard Home version", + format!( + concat!( + "Failed to get the version number of your AdGuard Home instance.\n", + "This usually means you're running an old, and unsupported version.\n", + "Please upgrade to v{} or later." + ), min_version.to_string() + ).as_str(), + None, + ); + } + } +} + +/// With the users specified AdGuard details, verify the connection (exit on fail) async fn verify_connection( client: &Client, ip: String, @@ -82,16 +122,44 @@ async fn verify_connection( .send() .await { Ok(res) if res.status().is_success() => { - println!("{}", "AdGuard connection successful!\n".green()); + // Get version string (if present), and check if valid - exit if not + let body: Value = res.json().await?; + check_version(body["version"].as_str()); + // All good! Print success message :) + let safe_version = body["version"].as_str().unwrap_or("mystery version"); + println!("{}", format!("AdGuard ({}) connection successful!\n", safe_version).green()); Ok(()) } - Ok(_) | Err(_) => { - print_error(&format!("{}:{}", ip, port), None); - std::process::exit(1); + // Connection failed to authenticate. Print error and exit + Ok(_) => { + print_error( + &format!("Authentication with AdGuard at {}:{} failed", ip, port), + "Please check your environmental variables and try again.", + None, + ); + Ok(()) + }, + // Connection failed to establish. Print error and exit + Err(e) => { + print_error( + &format!("Failed to connect to AdGuard at: {}:{}", ip, port), + "Please check your environmental variables and try again.", + Some(&e), + ); + Ok(()) } } } +/// Initiate the welcome script +/// This function will: +/// - Print the AdGuardian ASCII art +/// - Check for the required environmental variables +/// - Prompt the user to enter any missing variables +/// - Verify the connection to the AdGuard instance +/// - Verify authentication is successful +/// - Verify the AdGuard Home version is supported +/// - Then either print a success message, or show instructions to fix and exit pub async fn welcome() -> Result<(), Box> { print_ascii_art(); println!("{}", "Starting initialization checks...".blue()); @@ -138,11 +206,13 @@ pub async fn welcome() -> Result<(), Box> { } } + // Grab the values of the (now set) environmental variables let ip = get_env("ADGUARD_IP")?; let port = get_env("ADGUARD_PORT")?; let protocol = get_env("ADGUARD_PROTOCOL")?; let username = get_env("ADGUARD_USERNAME")?; let password = get_env("ADGUARD_PASSWORD")?; + // Verify that we can connect, authenticate, and that version is supported (exit on failure) verify_connection(&client, ip, port, protocol, username, password).await }