Compare commits

...

3 Commits

Author SHA1 Message Date
0eaddf064e better error return in config 2024-07-14 13:18:10 +02:00
8de3a762ab add result return 2024-07-14 12:55:12 +02:00
0973bca227 public ip 2024-07-14 12:50:06 +02:00
5 changed files with 96 additions and 37 deletions

View File

@ -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>> {

View File

@ -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> {

View File

@ -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";

View File

@ -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
View 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)
})
}