use anyhow::{Context, Result}; use log::{error, info}; use reqwest::Client; use serde::{self, Deserialize, Serialize}; use std::{ collections::HashMap, net::{IpAddr, Ipv4Addr}, sync::Arc, }; use crate::get_current_public_ipv4; pub struct CloudflareClient { client: Client, domains: Vec>, current_ip: Ipv4Addr, api_key: Box, zone_id: Box, } #[derive(Serialize, Deserialize, Clone, Debug)] struct DnsRecord { id: String, #[serde(rename = "type")] record_type: Box, name: Box, content: Box, ttl: u32, proxied: bool, locked: bool, zone_id: Box, zone_name: Box, modified_on: Box, created_on: Box, meta: HashMap, serde_json::Value>, } impl CloudflareClient { pub async fn new(api_key: Box, zone_id: Box, domains: Vec>) -> Result { let force_ipv4 = IpAddr::from([0, 0, 0, 0]); let client = reqwest::ClientBuilder::new() .local_address(force_ipv4) .build()?; let current_ip = get_current_public_ipv4(&client).await?; Ok(Self { client, domains, current_ip, api_key, zone_id, }) } pub async fn check(&mut self) -> Result<()> { let new_ip = get_current_public_ipv4(&self.client).await?; if new_ip == self.current_ip { return Ok(()); } self.update_dns_records(new_ip).await; self.current_ip = new_ip; Ok(()) } async fn update_dns_records(&self, new_ip: Ipv4Addr) { for domain in &self.domains { let records = match self.get_dns_records(domain).await { Ok(r) => r, Err(e) => { error!( "Could not getch dns records for domain '{}': {:?}", &domain, &e ); continue; } }; let new_ip_s = new_ip.to_string().into_boxed_str(); for mut record in records.into_iter() { if record.content == new_ip_s { continue; } info!( "On {}, updating {}: '{}' -> '{}'", &domain, record.name, record.content, &new_ip_s ); if let Err(e) = self.update_dns_record(&mut record, new_ip_s.clone()).await { error!( "On {}, failed to update {}: '{}' -> '{}': {:?}", &domain, record.name, record.content, &new_ip_s, &e ); } } } } async fn get_dns_records(&self, domain: &str) -> Result> { let url = format!( "https://api.cloudflare.com/client/v4/zones/{}/dns_records?type=A&name={}", self.zone_id, domain ); let mut response = self .client .get(&url) .header("Authorization", format!("Bearer {}", self.api_key)) .header("Content-Type", "application/json") .send() .await? .error_for_status()? .json::>>() .await?; response .remove("result") .context("Key result not in return") } async fn update_dns_record(&self, record: &mut DnsRecord, ip_content: Box) -> Result<()> { let url = format!( "https://api.cloudflare.com/client/v4/zones/{}/dns_records/{}", self.zone_id, record.id ); record.content = ip_content; self.client .put(&url) .header("Authorization", format!("Bearer {}", self.api_key)) .header("Content-Type", "application/json") .json(&record) .send() .await? .error_for_status()?; Ok(()) } }