1use std::{
2 io:: {self, Write},
3 env,
4 time::Duration
5};
6use reqwest::{Client, Error};
7use colored::*;
8
9use serde_json::Value;
10use serde::Deserialize;
11use semver::{Version};
12
13fn print_info(text: &str, is_secondary: bool) {
15 if is_secondary {
16 println!("{}", text.green().italic().dimmed());
17 } else {
18 println!("{}", text.green());
19 };
20}
21
22fn print_ascii_art() {
24 let art = r"
25 █████╗ ██████╗ ██████╗ ██╗ ██╗ █████╗ ██████╗ ██████╗ ██╗ █████╗ ███╗ ██╗
26██╔══██╗██╔══██╗██╔════╝ ██║ ██║██╔══██╗██╔══██╗██╔══██╗██║██╔══██╗████╗ ██║
27███████║██║ ██║██║ ███╗██║ ██║███████║██████╔╝██║ ██║██║███████║██╔██╗ ██║
28██╔══██║██║ ██║██║ ██║██║ ██║██╔══██║██╔══██╗██║ ██║██║██╔══██║██║╚██╗██║
29██║ ██║██████╔╝╚██████╔╝╚██████╔╝██║ ██║██║ ██║██████╔╝██║██║ ██║██║ ╚████║
30╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝
31";
32 print_info(art, false);
33 print_info("\nWelcome to AdGuardian Terminal Edition!", false);
34 print_info("Terminal-based, real-time traffic monitoring and statistics for your AdGuard Home instance", true);
35 print_info("For documentation and support, please visit: https://github.com/lissy93/adguardian-term", true);
36}
37
38fn print_error(message: &str, sub_message: &str, error: Option<&Error>) {
40 eprintln!(
41 "{}{}{}",
42 format!("{}", message).red(),
43 match error {
44 Some(err) => format!("\n{}", err).red().dimmed(),
45 None => "".red().dimmed(),
46 },
47 format!("\n{}", sub_message).yellow(),
48 );
49
50 std::process::exit(1);
51}
52
53fn get_env(key: &str) -> Result<String, env::VarError> {
55 env::var(key).map(|v| {
56 println!(
57 "{}",
58 format!(
59 "{} is set to {}",
60 key.bold(),
61 if key.contains("PASSWORD") { "******" } else { &v }
62 )
63 .green()
64 );
65 v
66 })
67}
68
69fn check_version(version: Option<&str>) {
71 let min_version = Version::parse("0.107.29").unwrap();
72
73 match version {
74 Some(version_str) => {
75 let adguard_version = Version::parse(&version_str[1..]).unwrap();
76
77 if adguard_version < min_version {
78 print_error(
79 "AdGuard Home version is too old, and is now unsupported",
80 format!("You're running AdGuard {}. Please upgrade to v{} or later.", version_str, min_version.to_string()).as_str(),
81 None,
82 );
83 }
84 },
85 None => {
86 print_error(
87 "Unsupported AdGuard Home version",
88 format!(
89 concat!(
90 "Failed to get the version number of your AdGuard Home instance.\n",
91 "This usually means you're running an old, and unsupported version.\n",
92 "Please upgrade to v{} or later."
93 ), min_version.to_string()
94 ).as_str(),
95 None,
96 );
97 }
98 }
99}
100
101async fn verify_connection(
103 client: &Client,
104 ip: String,
105 port: String,
106 protocol: String,
107 username: String,
108 password: String,
109) -> Result<(), Box<dyn std::error::Error>> {
110 println!("{}", "\nVerifying connection to your AdGuard instance...".blue());
111
112 let auth_string = format!("{}:{}", username, password);
113 let auth_header_value = format!("Basic {}", base64::encode(&auth_string));
114 let mut headers = reqwest::header::HeaderMap::new();
115 headers.insert("Authorization", auth_header_value.parse()?);
116
117 let url = format!("{}://{}:{}/control/status", protocol, ip, port);
118
119 match client
120 .get(&url)
121 .headers(headers)
122 .timeout(Duration::from_secs(2))
123 .send()
124 .await {
125 Ok(res) if res.status().is_success() => {
126 let body: Value = res.json().await?;
128 check_version(body["version"].as_str());
129 let safe_version = body["version"].as_str().unwrap_or("mystery version");
131 println!("{}", format!("AdGuard ({}) connection successful!\n", safe_version).green());
132 Ok(())
133 }
134 Ok(_) => {
136 print_error(
137 &format!("Authentication with AdGuard at {}:{} failed", ip, port),
138 "Please check your environmental variables and try again.",
139 None,
140 );
141 Ok(())
142 },
143 Err(e) => {
145 print_error(
146 &format!("Failed to connect to AdGuard at: {}:{}", ip, port),
147 "Please check your environmental variables and try again.",
148 Some(&e),
149 );
150 Ok(())
151 }
152 }
153}
154
155#[derive(Deserialize)]
156struct CratesIoResponse {
157 #[serde(rename = "crate")]
158 krate: Crate,
159}
160
161#[derive(Deserialize)]
162struct Crate {
163 max_version: String,
164}
165
166async fn get_latest_version(crate_name: &str) -> Result<String, Box<dyn std::error::Error>> {
168 let url = format!("https://crates.io/api/v1/crates/{}", crate_name);
169 let client = reqwest::Client::new();
170 let res = client.get(&url)
171 .header(reqwest::header::USER_AGENT, "version_check (adguardian.as93.net)")
172 .send()
173 .await?;
174
175 if res.status().is_success() {
176 let response: CratesIoResponse = res.json().await?;
177 Ok(response.krate.max_version)
178 } else {
179 let status = res.status();
180 let body = res.text().await?;
181 Err(format!("Request failed with status {}: body: {}", status, body).into())
182 }
183}
184
185async fn check_for_updates() {
187 let crate_name = env!("CARGO_PKG_NAME");
189 let crate_version = env!("CARGO_PKG_VERSION");
190 println!("{}", "\nChecking for updates...".blue());
191 let current_version = Version::parse(crate_version).unwrap_or_else(|_| {
193 Version::parse("0.0.0").unwrap()
194 });
195 let latest_version = Version::parse(
196 &get_latest_version(crate_name).await.unwrap_or_else(|_| {
197 "0.0.0".to_string()
198 })
199 ).unwrap();
200
201 if current_version == Version::parse("0.0.0").unwrap() || latest_version == Version::parse("0.0.0").unwrap() {
203 println!("{}", "Unable to check for updates".yellow());
204 } else if current_version < latest_version {
205 println!("{}",
206 format!(
207 "A new version of AdGuardian is available.\nUpdate from {} to {} for the best experience",
208 current_version.to_string().bold(),
209 latest_version.to_string().bold()
210 ).yellow()
211 );
212 } else if current_version == latest_version {
213 println!(
214 "{}",
215 format!("AdGuardian is up-to-date, running version {}", current_version.to_string().bold()).green()
216 );
217 } else if current_version > latest_version {
218 println!(
219 "{}",
220 format!("Running a pre-released edition of AdGuardian, version {}", current_version.to_string().bold()).green()
221 );
222 } else {
223 println!("{}", "Unable to check for updates".yellow());
224 }
225}
226
227
228pub async fn welcome() -> Result<(), Box<dyn std::error::Error>> {
239 print_ascii_art();
240
241 check_for_updates().await;
243
244 println!("{}", "\nStarting initialization checks...".blue());
245
246 let client = Client::new();
247
248 let flags = [
250 ("--adguard-ip", "ADGUARD_IP"),
251 ("--adguard-port", "ADGUARD_PORT"),
252 ("--adguard-username", "ADGUARD_USERNAME"),
253 ("--adguard-password", "ADGUARD_PASSWORD"),
254 ];
255
256 let protocol: String = env::var("ADGUARD_PROTOCOL").unwrap_or_else(|_| "http".into()).parse()?;
257 env::set_var("ADGUARD_PROTOCOL", protocol);
258
259 let mut args = std::env::args().peekable();
261 while let Some(arg) = args.next() {
262 for &(flag, var) in &flags {
263 if arg == flag {
264 if let Some(value) = args.peek() {
265 env::set_var(var, value);
266 args.next();
267 }
268 }
269 }
270 }
271
272 for &key in &["ADGUARD_IP", "ADGUARD_PORT", "ADGUARD_USERNAME", "ADGUARD_PASSWORD"] {
274 if env::var(key).is_err() {
275 println!(
276 "{}",
277 format!("The {} environmental variable is not yet set", key.bold()).yellow()
278 );
279 print!("{}", format!("› Enter a value for {}: ", key).blue().bold());
280 io::stdout().flush()?;
281
282 let mut value = String::new();
283 io::stdin().read_line(&mut value)?;
284 env::set_var(key, value.trim());
285 }
286 }
287
288 let ip = get_env("ADGUARD_IP")?;
290 let port = get_env("ADGUARD_PORT")?;
291 let protocol = get_env("ADGUARD_PROTOCOL")?;
292 let username = get_env("ADGUARD_USERNAME")?;
293 let password = get_env("ADGUARD_PASSWORD")?;
294
295 verify_connection(&client, ip, port, protocol, username, password).await?;
297
298 Ok(())
299}