Compare commits
	
		
			3 Commits
		
	
	
		
			09afecf396
			...
			0eaddf064e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 0eaddf064e | |||
| 8de3a762ab | |||
| 0973bca227 | 
| @@ -8,6 +8,8 @@ use std::{ | |||||||
|     sync::Arc, |     sync::Arc, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | use crate::get_current_public_ipv4; | ||||||
|  |  | ||||||
| pub struct CloudflareClient { | pub struct CloudflareClient { | ||||||
|     client: Client, |     client: Client, | ||||||
|     domains: Vec<Arc<str>>, |     domains: Vec<Arc<str>>, | ||||||
| @@ -15,21 +17,6 @@ pub struct CloudflareClient { | |||||||
|     api_key: Box<str>, |     api_key: Box<str>, | ||||||
|     zone_id: Box<str>, |     zone_id: Box<str>, | ||||||
| } | } | ||||||
| // Some external site to check this |  | ||||||
| async fn get_current_public_ipv4(client: &Client) -> Result<Ipv4Addr> { |  | ||||||
|     let response = client |  | ||||||
|         .get("https://api.ipify.org?format=json") |  | ||||||
|         .send() |  | ||||||
|         .await? |  | ||||||
|         .error_for_status()? |  | ||||||
|         .json::<HashMap<String, String>>() |  | ||||||
|         .await?; |  | ||||||
|  |  | ||||||
|     Ok(response |  | ||||||
|         .get("ip") |  | ||||||
|         .context("Field 'ip' wasn't found")? |  | ||||||
|         .parse()?) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Serialize, Deserialize, Clone, Debug)] | #[derive(Serialize, Deserialize, Clone, Debug)] | ||||||
| struct DnsRecord { | struct DnsRecord { | ||||||
| @@ -45,7 +32,7 @@ struct DnsRecord { | |||||||
|     zone_name: Box<str>, |     zone_name: Box<str>, | ||||||
|     modified_on: Box<str>, |     modified_on: Box<str>, | ||||||
|     created_on: Box<str>, |     created_on: Box<str>, | ||||||
|     meta: HashMap<String, serde_json::Value>, |     meta: HashMap<Box<str>, serde_json::Value>, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl CloudflareClient { | impl CloudflareClient { | ||||||
| @@ -71,13 +58,13 @@ impl CloudflareClient { | |||||||
|         if new_ip == self.current_ip { |         if new_ip == self.current_ip { | ||||||
|             return Ok(()); |             return Ok(()); | ||||||
|         } |         } | ||||||
|         self.update_dns_records(new_ip).await; |         self.update_dns_records(new_ip).await?; | ||||||
|         self.current_ip = new_ip; |         self.current_ip = new_ip; | ||||||
|  |  | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async fn update_dns_records(&self, new_ip: Ipv4Addr) { |     async fn update_dns_records(&self, new_ip: Ipv4Addr) -> Result<()> { | ||||||
|         for domain in &self.domains { |         for domain in &self.domains { | ||||||
|             let records = match self.get_dns_records(domain).await { |             let records = match self.get_dns_records(domain).await { | ||||||
|                 Ok(r) => r, |                 Ok(r) => r, | ||||||
| @@ -104,9 +91,12 @@ impl CloudflareClient { | |||||||
|                         "On {}, failed to update {}: '{}' -> '{}': {:?}", |                         "On {}, failed to update {}: '{}' -> '{}': {:?}", | ||||||
|                         &domain, record.name, record.content, &new_ip_s, &e |                         &domain, record.name, record.content, &new_ip_s, &e | ||||||
|                     ); |                     ); | ||||||
|  |                     return Err(e); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         Ok(()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async fn get_dns_records(&self, domain: &str) -> Result<Vec<DnsRecord>> { |     async fn get_dns_records(&self, domain: &str) -> Result<Vec<DnsRecord>> { | ||||||
|   | |||||||
| @@ -9,38 +9,52 @@ use crate::PROGRAM_NAME; | |||||||
|  |  | ||||||
| #[derive(Deserialize, Serialize, Debug)] | #[derive(Deserialize, Serialize, Debug)] | ||||||
| pub struct Config { | pub struct Config { | ||||||
|     pub cloudflare_zone_id: Box<str>, |     pub zone_id: Box<str>, | ||||||
|     pub cloudflare_api_key: Box<str>, |     pub api_key: Box<str>, | ||||||
|  |     pub domains: Vec<Box<str>>, | ||||||
| } | } | ||||||
|  |  | ||||||
| pub async fn get_config_path() -> Option<PathBuf> { | pub async fn get_config_path() -> Result<PathBuf, Vec<PathBuf>> { | ||||||
|  |     let mut tried_paths = Vec::with_capacity(2); | ||||||
|  |  | ||||||
|     match env::current_dir() { |     match env::current_dir() { | ||||||
|         Ok(current_dir) => { |         Ok(current_dir) => { | ||||||
|             let cwd_config = current_dir.join(format!("{}.toml", PROGRAM_NAME)); |             let cwd_config = current_dir.join(format!("{}.toml", PROGRAM_NAME)); | ||||||
|             if let Ok(meta) = fs::metadata(&cwd_config).await { |  | ||||||
|                 if meta.is_file() { |             let is_valid_path = fs::metadata(&cwd_config) | ||||||
|                     return Some(cwd_config); |                 .await | ||||||
|                 } |                 .map_or_else(|_| false, |meta| meta.is_file()); | ||||||
|  |  | ||||||
|  |             if is_valid_path { | ||||||
|  |                 return Ok(cwd_config); | ||||||
|  |             } else { | ||||||
|  |                 tried_paths.push(cwd_config); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         Err(e) => { |         Err(e) => { | ||||||
|             warn!("Failed to get current directory {:?}", &e); |             warn!("Failed to get current working directory: {:?}", e); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     match dirs::config_dir() { |     match dirs::config_dir() { | ||||||
|         Some(config_dir) => { |         Some(config_dir) => { | ||||||
|             let config_file = config_dir.join(PROGRAM_NAME).join("config.toml"); |             let config_file = config_dir.join(PROGRAM_NAME).join("config.toml"); | ||||||
|             if let Ok(meta) = fs::metadata(&config_file).await { |  | ||||||
|                 if meta.is_file() { |             let is_valid_path = fs::metadata(&config_file) | ||||||
|                     return Some(config_file); |                 .await | ||||||
|  |                 .map_or_else(|_| false, |meta| meta.is_file()); | ||||||
|  |             if is_valid_path { | ||||||
|  |                 return Ok(config_file); | ||||||
|  |             } else { | ||||||
|  |                 tried_paths.push(config_file); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |         None => { | ||||||
|  |             warn!("No configuration directory found."); | ||||||
|         } |         } | ||||||
|         None => {} |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     None |     Err(tried_paths) | ||||||
| } | } | ||||||
|  |  | ||||||
| pub async fn read_config(path: &PathBuf) -> anyhow::Result<Config> { | pub async fn read_config(path: &PathBuf) -> anyhow::Result<Config> { | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| mod cloudflare; | mod cloudflare; | ||||||
| mod config; | mod config; | ||||||
| pub use config::{Config, get_config_path, read_config}; | mod public_ip; | ||||||
|  | pub use config::{get_config_path, read_config, Config}; | ||||||
|  | pub use public_ip::get_current_public_ipv4; | ||||||
|  |  | ||||||
| pub const PROGRAM_NAME: &'static str = "dynip-cloudflare"; | pub const PROGRAM_NAME: &'static str = "dynip-cloudflare"; | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								src/main.rs
									
									
									
									
									
								
							| @@ -26,9 +26,19 @@ const fn nl_mgrp(group: u32) -> u32 { | |||||||
| async fn main() { | async fn main() { | ||||||
|     env_logger::init(); |     env_logger::init(); | ||||||
|     let config_path = match get_config_path().await { |     let config_path = match get_config_path().await { | ||||||
|         Some(cp) => cp, |         Ok(cp) => cp, | ||||||
|         None => { |         Err(tried_paths) => { | ||||||
|             error!("Failed to find any config file"); |             let extra: String = if !tried_paths.is_empty() { | ||||||
|  |                 let joined = tried_paths | ||||||
|  |                     .iter() | ||||||
|  |                     .filter_map(|path| path.to_str()) | ||||||
|  |                     .collect::<Vec<_>>() | ||||||
|  |                     .join(", "); | ||||||
|  |                 format!(", tried the paths: '{}'", &joined) | ||||||
|  |             } else { | ||||||
|  |                 String::with_capacity(0) | ||||||
|  |             }; | ||||||
|  |             error!("Failed to find any config file{}", &extra); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|   | |||||||
							
								
								
									
										44
									
								
								src/public_ip.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/public_ip.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  |     use anyhow::{anyhow, Context, Result}; | ||||||
|  |     use log::error; | ||||||
|  |     use reqwest::Client; | ||||||
|  |     use std::{collections::HashMap, net::Ipv4Addr}; | ||||||
|  |  | ||||||
|  |     async fn ipify_org(client: &Client) -> Result<Ipv4Addr> { | ||||||
|  |         let response = client | ||||||
|  |             .get("https://api.ipify.org?format=json") | ||||||
|  |             .send() | ||||||
|  |             .await? | ||||||
|  |             .error_for_status()? | ||||||
|  |             .json::<HashMap<String, String>>() | ||||||
|  |             .await?; | ||||||
|  |  | ||||||
|  |         Ok(response | ||||||
|  |             .get("ip") | ||||||
|  |             .context("Field 'ip' wasn't found")? | ||||||
|  |             .parse()?) | ||||||
|  |     } | ||||||
|  |     async fn ifconfig_me(client: &Client) -> Result<Ipv4Addr> { | ||||||
|  |         Ok(client | ||||||
|  |             .get("https://ifconfig.me") | ||||||
|  |             .header("user-agent", "curl/8.8.0") | ||||||
|  |             .send() | ||||||
|  |             .await? | ||||||
|  |             .error_for_status()? | ||||||
|  |             .text() | ||||||
|  |             .await? | ||||||
|  |             .parse()?) | ||||||
|  |     } | ||||||
|  |     pub async fn get_current_public_ipv4(client: &Client) -> Result<Ipv4Addr> { | ||||||
|  |         let e_ipify = match ipify_org(client).await { | ||||||
|  |             Ok(ipv4) => return Ok(ipv4), | ||||||
|  |             Err(e) => { | ||||||
|  |                 error!("Failed to get ip from ipify.org: {:?}", &e); | ||||||
|  |                 e | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         ifconfig_me(client).await.map_err(|e_ifconfig| { | ||||||
|  |             error!("Failed to get ip from ifconfig.me: {:?}", &e_ifconfig); | ||||||
|  |             anyhow!("Failed to get ip from ipify.org with error '{:?}', and ifconfig.me with error {:?}", &e_ipify, &e_ifconfig) | ||||||
|  |         }) | ||||||
|  |     } | ||||||
		Reference in New Issue
	
	Block a user