cloudflare-ddns-service/src/main.rs

116 lines
3.4 KiB
Rust
Raw Normal View History

mod file;
2019-09-18 09:12:50 +00:00
mod network;
2020-03-22 21:10:50 +00:00
use file::{read_file, write_file};
2019-09-18 09:12:50 +00:00
use human_panic::setup_panic;
use network::{get_current_ip, get_dns_record_id, get_zone_identifier, update_ddns};
use quicli::prelude::*;
2019-09-18 09:12:50 +00:00
use std::path::PathBuf;
use structopt::StructOpt;
2020-03-22 21:10:50 +00:00
use anyhow::{Context, Result};
2019-09-23 08:58:41 +00:00
#[derive(Deserialize)]
struct Config {
api_token: String,
2019-09-23 08:58:41 +00:00
zone: String,
domain: String,
}
#[derive(Debug, StructOpt)]
/// Inform Cloudflare's DDNS service of the current IP address for your domain
struct Cli {
/// Your TOML config file containing all the required options (api_token, zone, domain) which you can use instead of passing the arguments to the command line
2019-09-23 08:58:41 +00:00
#[structopt(long = "config", short = "f")]
config: Option<PathBuf>,
/// The api token you need to generate in your Cloudflare profile
#[structopt(long = "token", short = "t", required_unless = "config")]
api_token: Option<String>,
2019-09-18 08:15:11 +00:00
/// The zone in which your domain is (usually that is your base domain name)
2019-09-23 08:58:41 +00:00
#[structopt(long = "zone", short = "z", required_unless = "config")]
zone: Option<String>,
/// The domain for which you want to report the current IP address
2019-09-23 08:58:41 +00:00
#[structopt(long = "domain", short = "d", required_unless = "config")]
domain: Option<String>,
/// Cache file for previously reported IP address (if skipped the IP will be reported on every execution)
#[structopt(long = "cache", short = "c")]
cache: Option<PathBuf>,
}
2020-03-22 21:10:50 +00:00
fn main() -> Result<()> {
setup_panic!();
let args = Cli::from_args();
2019-09-23 08:58:41 +00:00
let should_use_cache = args.cache.is_some();
let cached_ip: Option<String> = match args.cache.clone() {
Some(v) => {
if v.exists() {
2020-03-22 21:10:50 +00:00
Some(read_file(&v.clone()).context("Could not read cache file")?)
} else {
Some("0.0.0.0".to_owned())
}
2019-09-18 09:12:50 +00:00
}
None => None,
};
let current_ip = get_current_ip()?;
if cached_ip.is_some() && current_ip == cached_ip.unwrap() {
println!("IP is unchanged. Exiting...");
return Ok(());
}
if should_use_cache {
2019-09-18 09:12:50 +00:00
println!(
"Saving current IP {} to cache file {:?}...",
&current_ip,
&args.cache.clone().unwrap()
);
2020-03-22 21:10:50 +00:00
write_file(&args.cache.unwrap(), &current_ip)?;
}
let (api_token, zone, domain) = match args.config {
2019-09-23 08:58:41 +00:00
Some(c) => {
2020-03-22 21:10:50 +00:00
let config_str = read_file(&c)?;
2019-09-23 08:58:41 +00:00
let config: Config = toml::from_str(&config_str)?;
(config.api_token, config.zone, config.domain)
2019-09-23 08:58:41 +00:00
}
None => (
args.api_token.expect("API token is not set"),
2019-09-23 08:58:41 +00:00
args.zone.expect("Zone is not set"),
args.domain.expect("Domain is not set"),
),
};
update(&current_ip, &api_token, &zone, &domain)?;
2019-09-23 08:58:41 +00:00
println!(
"Successfully updated the A record for {} to {}",
&domain, &current_ip
);
Ok(())
}
fn update(
current_ip: &str,
api_token: &str,
2019-09-23 08:58:41 +00:00
zone: &str,
domain: &str,
2020-03-22 21:10:50 +00:00
) -> Result<()> {
let zone_id = get_zone_identifier(&zone, &api_token).context("Error getting the zone identifier")?;
let record_id = get_dns_record_id(&zone_id, &domain, &api_token).context("Error getting the DNS record ID")?;
2019-09-18 09:12:50 +00:00
update_ddns(
&current_ip,
2019-09-23 08:58:41 +00:00
&domain,
2019-09-18 09:12:50 +00:00
&zone_id,
&record_id,
&api_token,
2020-03-22 21:10:50 +00:00
).context("Error updating the DNS record")?;
2019-09-18 09:12:50 +00:00
Ok(())
}